From 6830d2e31632e7be9d2f4f2a00d9608d1564fe0a Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 29 Sep 2026 11:42:34 +0200 Subject: [PATCH 1/3] feat: Add getRandomBytes --- packages/cryptography/CHANGELOG.md | 1 + packages/cryptography/src/index.ts | 1 + packages/cryptography/src/random.test.ts | 25 ++++++++++++++++++++++++ packages/cryptography/src/random.ts | 10 ++++++++++ 4 files changed, 37 insertions(+) create mode 100644 packages/cryptography/src/random.test.ts create mode 100644 packages/cryptography/src/random.ts diff --git a/packages/cryptography/CHANGELOG.md b/packages/cryptography/CHANGELOG.md index b6531df0d11..39b35b83a8e 100644 --- a/packages/cryptography/CHANGELOG.md +++ b/packages/cryptography/CHANGELOG.md @@ -15,5 +15,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `pbkdf2Sha256`, `pbkdf2Sha384`, and `pbkdf2Sha512` functions for key derivation - Add `hkdfSha256`, `hkdfSha384`, and `hkdfSha512` functions for key derivation - Add `getPublicKey` and `getSharedSecret` functions for X25519 key derivation exported via `@metamask/cryptography/x25519` + - Add `getRandomBytes` function for generating cryptographically secure random bytes [Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/cryptography/src/index.ts b/packages/cryptography/src/index.ts index 932af223c58..753e73d9843 100644 --- a/packages/cryptography/src/index.ts +++ b/packages/cryptography/src/index.ts @@ -1,5 +1,6 @@ export * from './hkdf.js'; export * from './hmac.js'; export * from './pbkdf2.js'; +export * from './random.js'; export * from './sha.js'; export type * from './types.js'; diff --git a/packages/cryptography/src/random.test.ts b/packages/cryptography/src/random.test.ts new file mode 100644 index 00000000000..afad4c683cb --- /dev/null +++ b/packages/cryptography/src/random.test.ts @@ -0,0 +1,25 @@ +import { getRandomBytes } from './random.js'; + +describe('getRandomBytes', () => { + it('returns a Uint8Array of the requested length', () => { + const bytes = getRandomBytes(32); + + expect(bytes).toBeInstanceOf(Uint8Array); + expect(bytes).toHaveLength(32); + }); + + it('fills the array using `crypto.getRandomValues`', () => { + const spy = jest + .spyOn(globalThis.crypto, 'getRandomValues') + .mockImplementation((array) => { + (array as Uint8Array).fill(0xab); + return array; + }); + + const bytes = getRandomBytes(4); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(expect.any(Uint8Array)); + expect(bytes).toStrictEqual(new Uint8Array([0xab, 0xab, 0xab, 0xab])); + }); +}); diff --git a/packages/cryptography/src/random.ts b/packages/cryptography/src/random.ts new file mode 100644 index 00000000000..945c0858de9 --- /dev/null +++ b/packages/cryptography/src/random.ts @@ -0,0 +1,10 @@ +/** + * Generate random bytes using the platform's cryptographically secure pseudo-random number generator (CSPRNG). + * + * @param length - The number of random bytes to generate. + * @returns An `Uint8Array` of the provided length with random bytes. + */ +export function getRandomBytes(length: number): Uint8Array { + const bytes = new Uint8Array(length); + return globalThis.crypto.getRandomValues(bytes); +} From 17c6d81b7a6dc2fe52ed83b58f85a5cb8a18206c Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 29 Sep 2026 11:44:38 +0200 Subject: [PATCH 2/3] Add PR number to changelog --- packages/cryptography/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cryptography/CHANGELOG.md b/packages/cryptography/CHANGELOG.md index 39b35b83a8e..c6a0fc94ff0 100644 --- a/packages/cryptography/CHANGELOG.md +++ b/packages/cryptography/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Initial release ([#10282](https://github.com/MetaMask/core/pull/10282), [#10431](https://github.com/MetaMask/core/pull/10431), [#10403](https://github.com/MetaMask/core/pull/10403), [#10468](https://github.com/MetaMask/core/pull/10468), [#10503](https://github.com/MetaMask/core/pull/10503)) +- Initial release ([#10282](https://github.com/MetaMask/core/pull/10282), [#10431](https://github.com/MetaMask/core/pull/10431), [#10403](https://github.com/MetaMask/core/pull/10403), [#10468](https://github.com/MetaMask/core/pull/10468), [#10503](https://github.com/MetaMask/core/pull/10503), [#10563](https://github.com/MetaMask/core/pull/10563)) - Add `sha256`, `sha384`, and `sha512` functions for computing SHA digests - Add `hmacSha256`, `hmacSha384`, and `hmacSha512` functions for computing HMAC digests - Add `pbkdf2Sha256`, `pbkdf2Sha384`, and `pbkdf2Sha512` functions for key derivation From 6aa9eede8a63ecf8804c6b99a29bf6f6b72da9a9 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 29 Sep 2026 11:50:57 +0200 Subject: [PATCH 3/3] Add validation --- packages/cryptography/src/random.test.ts | 6 ++++++ packages/cryptography/src/random.ts | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/cryptography/src/random.test.ts b/packages/cryptography/src/random.test.ts index afad4c683cb..384c046c6e7 100644 --- a/packages/cryptography/src/random.test.ts +++ b/packages/cryptography/src/random.test.ts @@ -22,4 +22,10 @@ describe('getRandomBytes', () => { expect(spy).toHaveBeenCalledWith(expect.any(Uint8Array)); expect(bytes).toStrictEqual(new Uint8Array([0xab, 0xab, 0xab, 0xab])); }); + + it('throws if the length is zero', () => { + expect(() => getRandomBytes(0)).toThrow( + 'Invalid length: Length must be greater than 0.', + ); + }); }); diff --git a/packages/cryptography/src/random.ts b/packages/cryptography/src/random.ts index 945c0858de9..9490cdd2533 100644 --- a/packages/cryptography/src/random.ts +++ b/packages/cryptography/src/random.ts @@ -3,8 +3,11 @@ * * @param length - The number of random bytes to generate. * @returns An `Uint8Array` of the provided length with random bytes. + * @throws If `length` is not greater than 0. */ export function getRandomBytes(length: number): Uint8Array { - const bytes = new Uint8Array(length); - return globalThis.crypto.getRandomValues(bytes); + if (length <= 0) { + throw new Error('Invalid length: Length must be greater than 0.'); + } + return globalThis.crypto.getRandomValues(new Uint8Array(length)); }