Skip to content

fix: replace Math.random() with crypto.getRandomValues() (WAPI-1127)#73

Open
chakra-guy wants to merge 5 commits intocyfrin/wapi-1118from
cyfrin/wapi-1127
Open

fix: replace Math.random() with crypto.getRandomValues() (WAPI-1127)#73
chakra-guy wants to merge 5 commits intocyfrin/wapi-1118from
cyfrin/wapi-1127

Conversation

@chakra-guy
Copy link
Collaborator

@chakra-guy chakra-guy commented Feb 25, 2026

Summary

  • Replaces all Math.random() calls with crypto.getRandomValues() for cryptographically secure randomness
  • Critical fix: OTP generation in the wallet untrusted connection handler now uses CSPRNG
  • Demo app log entry IDs updated for consistency (not security-critical but removes all Math.random from the codebase)

Background

The Cyfrin audit flagged Math.random() usage for OTP generation. Math.random() uses a non-cryptographic PRNG and its output is predictable, making OTP values potentially guessable. crypto.getRandomValues() is available cross-platform via globalThis.crypto (Node 19+, all modern browsers, React Native).

Changes

  • packages/wallet-client/src/handlers/untrusted-connection-handler.ts: OTP generation uses Uint32Array + crypto.getRandomValues()
  • apps/web-demo/src/components/UntrustedDemo.tsx: Log entry IDs
  • apps/web-demo/src/components/TrustedDemo.tsx: Log entry IDs
  • apps/web-demo/src/components/MetaMaskMobileDemo.tsx: Log entry IDs
  • apps/rn-demo/context/WalletContext.tsx: Log entry IDs

Test plan

  • yarn build passes
  • yarn test:unit passes (68/68 tests)
  • yarn lint passes (no new warnings)
  • Integration tests pass (7/7 tests)
  • Verified zero Math.random() calls remain in source files

Note

Medium Risk
Touches security-sensitive handshake OTP generation; while the change is small, any regression could break untrusted pairing or alter OTP distribution, especially across Node/runtime environments.

Overview
Untrusted connection OTP generation now uses a cryptographically secure RNG via globalThis.crypto.getRandomValues() (with a Node <19 node:crypto.randomFillSync fallback) instead of Math.random().

Unit tests are updated to run on Node 18 by polyfilling globalThis.crypto.getRandomValues, and the wallet-client changelog records the fix under Unreleased.

Written by Cursor Bugbot for commit 89b9097. This will update automatically on new commits. Configure here.

Math.random() is not cryptographically secure and should never be used
for security-sensitive values. This replaces all usages with
crypto.getRandomValues() which is available cross-platform via
globalThis.crypto (Node 19+, all modern browsers, React Native).

The critical fix is in OTP generation for the untrusted connection flow.
Demo app log entry IDs are also updated for consistency.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Use rejection sampling in _uniformRandom() to produce perfectly uniform
random values, eliminating the modulo bias flagged in PR review.

Fix CI: add globalThis.crypto polyfill for Node 18 test environment,
and reorder changelog sections to match Keep a Changelog spec
(Changed before Fixed).
Revert Math.random() replacements in demo app log entry IDs - these
are not security-sensitive (just React keys) and don't need CSPRNG.

Simplify OTP generation back to direct modulo. The modulo bias is
~0.004% which is negligible for a time-limited 6-digit OTP.

Keep the changelog section ordering fix and Node 18 test polyfill.
globalThis.crypto is not available by default in Node.js <19.
Fall back to require("node:crypto").randomFillSync() when the
Web Crypto API is not present.
Comment on lines +9 to +18
// Node 18 doesn't expose globalThis.crypto; polyfill it for tests.
if (!globalThis.crypto) {
globalThis.crypto = {
getRandomValues<T extends ArrayBufferView | null>(array: T): T {
if (array) randomFillSync(array as any);
return array;
},
} as Crypto;
}

Copy link

@adonesky1 adonesky1 Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is reminding me we should create a ticket to set minimum node version to 20.x and update CI runners accordingly

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

globalThis.crypto was added in Node 19. Every production runtime for this library already has it — browsers, React Native (Hermes/JSC), and Node 20+ LTS. Node 18 hit EOL April 2025, and eciesjs 0.4.16 (which this project depends on) already dropped Node 18 support. The fallback branch will never execute in production — it only exists to satisfy CI running on an outdated Node version.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants