From c926dc529d1a2f6bff3f7e7f70a9d3a1e6b081c4 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 1 Apr 2026 21:31:28 +0200 Subject: [PATCH 1/3] feat(keyring-sdk): add encodeMnemonic --- packages/keyring-sdk/package.json | 1 + packages/keyring-sdk/src/index.ts | 1 + packages/keyring-sdk/src/mnemonic.test.ts | 34 +++++++++++++++++++++++ packages/keyring-sdk/src/mnemonic.ts | 15 ++++++++++ yarn.lock | 1 + 5 files changed, 52 insertions(+) create mode 100644 packages/keyring-sdk/src/mnemonic.test.ts create mode 100644 packages/keyring-sdk/src/mnemonic.ts diff --git a/packages/keyring-sdk/package.json b/packages/keyring-sdk/package.json index 8a3329e8..f324e157 100644 --- a/packages/keyring-sdk/package.json +++ b/packages/keyring-sdk/package.json @@ -49,6 +49,7 @@ "@metamask/eth-sig-util": "^8.2.0", "@metamask/keyring-api": "^22.0.0", "@metamask/keyring-utils": "^3.2.0", + "@metamask/scure-bip39": "^2.1.1", "@metamask/superstruct": "^3.1.0", "@metamask/utils": "^11.10.0", "async-mutex": "^0.5.0", diff --git a/packages/keyring-sdk/src/index.ts b/packages/keyring-sdk/src/index.ts index ba41eb93..f942b0d5 100644 --- a/packages/keyring-sdk/src/index.ts +++ b/packages/keyring-sdk/src/index.ts @@ -1,3 +1,4 @@ export * from './keyring-wrapper'; export * from './keyring-account-registry'; +export * from './mnemonic'; export * from './eth'; diff --git a/packages/keyring-sdk/src/mnemonic.test.ts b/packages/keyring-sdk/src/mnemonic.test.ts new file mode 100644 index 00000000..17b39747 --- /dev/null +++ b/packages/keyring-sdk/src/mnemonic.test.ts @@ -0,0 +1,34 @@ +import { encodeMnemonic } from './mnemonic'; + +const toIndicesBytes = (indices: number[]): Uint8Array => + new Uint8Array(new Uint16Array(indices).buffer); + +describe('encodeMnemonic', () => { + it('returns an empty array for empty input', () => { + expect(encodeMnemonic(toIndicesBytes([]))).toStrictEqual([]); + }); + + it('encodes a single word (index 0 → "abandon")', () => { + const expected = Array.from(new TextEncoder().encode('abandon')); + expect(encodeMnemonic(toIndicesBytes([0]))).toStrictEqual(expected); + }); + + it('encodes two words separated by a space', () => { + // index 0 → "abandon", index 1 → "ability" + const expected = Array.from(new TextEncoder().encode('abandon ability')); + expect(encodeMnemonic(toIndicesBytes([0, 1]))).toStrictEqual(expected); + }); + + it('encodes a standard 12-word mnemonic (all "abandon")', () => { + const indices = new Array(12).fill(0); + const expected = Array.from( + new TextEncoder().encode(new Array(12).fill('abandon').join(' ')), + ); + expect(encodeMnemonic(toIndicesBytes(indices))).toStrictEqual(expected); + }); + + it('returns number[] (not a Uint8Array)', () => { + const result = encodeMnemonic(toIndicesBytes([0])); + expect(Array.isArray(result)).toBe(true); + }); +}); diff --git a/packages/keyring-sdk/src/mnemonic.ts b/packages/keyring-sdk/src/mnemonic.ts new file mode 100644 index 00000000..62478d0a --- /dev/null +++ b/packages/keyring-sdk/src/mnemonic.ts @@ -0,0 +1,15 @@ +import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english'; + +/** + * Encodes a mnemonic as an array of bytes (UTF-8). + * + * @param mnemonicIndicesBytes - An array of bytes (16-bit unsigned integers) representing the indices of the words in the mnemonic. + * @returns An array of bytes (UTF-8) representing the mnemonic. + */ +export function encodeMnemonic(mnemonicIndicesBytes: Uint8Array): number[] { + const mnemonicIndices = Array.from( + new Uint16Array(mnemonicIndicesBytes.buffer), + ); + const mnemonic = mnemonicIndices.map((i) => wordlist[i]).join(' '); + return Array.from(new TextEncoder().encode(mnemonic)); +} diff --git a/yarn.lock b/yarn.lock index a1ba37be..13fdd6d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2126,6 +2126,7 @@ __metadata: "@metamask/eth-sig-util": "npm:^8.2.0" "@metamask/keyring-api": "npm:^22.0.0" "@metamask/keyring-utils": "npm:^3.2.0" + "@metamask/scure-bip39": "npm:^2.1.1" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^11.10.0" "@ts-bridge/cli": "npm:^0.6.3" From 608ae364bfdf4d1e875aae494fdfd3b575116c96 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 1 Apr 2026 21:37:22 +0200 Subject: [PATCH 2/3] chore: changelog --- packages/keyring-sdk/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/keyring-sdk/CHANGELOG.md b/packages/keyring-sdk/CHANGELOG.md index 82995ff6..d82090d2 100644 --- a/packages/keyring-sdk/CHANGELOG.md +++ b/packages/keyring-sdk/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `encodeMnemonic` ([#495](https://github.com/MetaMask/accounts/pull/495)) + ### Changed - Bump `@metamask/utils` from `^11.1.0` to `^11.10.0` ([#489](https://github.com/MetaMask/accounts/pull/489)) From 4611227138cf1249d474fcf7f104fd57d885e1c2 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 1 Apr 2026 23:17:24 +0200 Subject: [PATCH 3/3] fix: fix invalid views on buffers --- packages/keyring-sdk/src/mnemonic.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/keyring-sdk/src/mnemonic.ts b/packages/keyring-sdk/src/mnemonic.ts index 62478d0a..524e5d2c 100644 --- a/packages/keyring-sdk/src/mnemonic.ts +++ b/packages/keyring-sdk/src/mnemonic.ts @@ -8,7 +8,10 @@ import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english'; */ export function encodeMnemonic(mnemonicIndicesBytes: Uint8Array): number[] { const mnemonicIndices = Array.from( - new Uint16Array(mnemonicIndicesBytes.buffer), + // Create a new `Uint8Array` to ensure we have a proper view on the buffer + // without having to worry about `byteOffset` and `byteLength` of + // the inner buffer. + new Uint16Array(new Uint8Array(mnemonicIndicesBytes).buffer), ); const mnemonic = mnemonicIndices.map((i) => wordlist[i]).join(' '); return Array.from(new TextEncoder().encode(mnemonic));