diff --git a/src/index.ts b/src/index.ts index 8f661ca..c3a8a62 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,9 +6,6 @@ import { sha512 } from '@noble/hashes/sha512'; import { randomBytes } from '@noble/hashes/utils'; import { utils as baseUtils } from '@scure/base'; -// Japanese wordlist -const isJapanese = (wordlist: string[]) => wordlist[0] === '\u3042\u3044\u3053\u304f\u3057\u3093'; - // Normalization replaces equivalent sequences of characters // so that any two texts that are equivalent will be reduced // to the same sequence of code points, called the normal form of the original text. @@ -36,7 +33,7 @@ function assertEntropy(entropy: Uint8Array) { * generateMnemonic(wordlist, 128) * // 'legal winner thank year wave sausage worth useful legal winner thank yellow' */ -export function generateMnemonic(wordlist: string[], strength: number = 128): string { +export function generateMnemonic(wordlist: string[], strength: number = 128): Uint8Array { assert.number(strength); if (strength % 32 !== 0 || strength > 256) throw new TypeError('Invalid entropy'); return entropyToMnemonic(randomBytes(strength / 8), wordlist); @@ -76,9 +73,17 @@ function getCoder(wordlist: string[]) { * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f * ]) */ -export function mnemonicToEntropy(mnemonic: string, wordlist: string[]): Uint8Array { - const { words } = normalize(mnemonic); - const entropy = getCoder(wordlist).decode(words); +export function mnemonicToEntropy(mnemonic: string | Uint8Array, wordlist: string[]): Uint8Array { + let entropy; + if (typeof mnemonic === 'string') { + const { words } = normalize(mnemonic); + entropy = getCoder(wordlist).decode(words) + } else { + // expected intanceOf Uint8Array when used with eth-hd-keyring + entropy = getCoder(wordlist).decode( + Array.from(new Uint16Array(mnemonic.buffer)).map((i) => wordlist[i]) + ); + } assertEntropy(entropy); return entropy; } @@ -96,16 +101,18 @@ export function mnemonicToEntropy(mnemonic: string, wordlist: string[]): Uint8Ar * entropyToMnemonic(ent, wordlist); * // 'legal winner thank year wave sausage worth useful legal winner thank yellow' */ -export function entropyToMnemonic(entropy: Uint8Array, wordlist: string[]): string { +export function entropyToMnemonic(entropy: Uint8Array, wordlist: string[]): Uint8Array { assertEntropy(entropy); const words = getCoder(wordlist).encode(entropy); - return words.join(isJapanese(wordlist) ? '\u3000' : ' '); + const indices = words.map((word) => wordlist.indexOf(word)); + const uInt8ArrayOfMnemonic = new Uint8Array(new Uint16Array(indices).buffer); + return uInt8ArrayOfMnemonic; } /** * Validates mnemonic for being 12-24 words contained in `wordlist`. */ -export function validateMnemonic(mnemonic: string, wordlist: string[]): boolean { +export function validateMnemonic(mnemonic: string | Uint8Array, wordlist: string[]): boolean { try { mnemonicToEntropy(mnemonic, wordlist); } catch (e) { @@ -140,6 +147,20 @@ export function mnemonicToSeed(mnemonic: string, passphrase = '') { * mnemonicToSeedSync(mnem, 'password'); * // new Uint8Array([...64 bytes]) */ -export function mnemonicToSeedSync(mnemonic: string, passphrase = '') { - return pbkdf2(sha512, normalize(mnemonic).nfkd, salt(passphrase), { c: 2048, dkLen: 64 }); +export function mnemonicToSeedSync( + mnemonic: string | Uint8Array, + wordlist: string[], + passphrase = '' +) { + let mnemonicUint8Array; + if (typeof mnemonic === 'string') { + mnemonicUint8Array = new TextEncoder().encode(normalize(mnemonic).nfkd); + } else { + mnemonicUint8Array = new TextEncoder().encode( + Array.from(new Uint16Array(mnemonic.buffer)) + .map((i) => wordlist[i]) + .join(' ') + ); + } + return pbkdf2(sha512, mnemonicUint8Array, salt(passphrase), { c: 2048, dkLen: 64 }); } diff --git a/test/bip39.test.ts b/test/bip39.test.ts index 9619556..0d597fe 100644 --- a/test/bip39.test.ts +++ b/test/bip39.test.ts @@ -40,15 +40,15 @@ export function hexToBytes(hex: string): Uint8Array { describe('BIP39', () => { describe('Mnemonic generation', () => { - it('should create a valid menomic', () => { + it('should create a valid menomic', async () => { const mnemonic = generateMnemonic(englishWordlist, 128); - deepStrictEqual(validateMnemonic(mnemonic, englishWordlist), true); + await deepStrictEqual(validateMnemonic(mnemonic, englishWordlist), true); }); }); describe('Mnemonic validation', () => { - it('should accept valid menomics', () => { - deepStrictEqual( + it('should accept valid menomics', async () => { + await deepStrictEqual( validateMnemonic( 'jump police vessel depth mutual idea cable soap trophy dust hold wink', englishWordlist @@ -56,211 +56,360 @@ describe('BIP39', () => { true ); - deepStrictEqual( + await deepStrictEqual( validateMnemonic( 'koala óxido urbe crudo momia idioma boina rostro títere dilema himno víspera', spanishWordlist ), true ); + + const indices = [ + 'jump', + 'police', + 'vessel', + 'depth', + 'mutual', + 'idea', + 'cable', + 'soap', + 'trophy', + 'dust', + 'hold', + 'wink', + ].map((word) => englishWordlist.indexOf(word)); + const uInt8ArrayOfMnemonic = new Uint8Array(new Uint16Array(indices).buffer); + await deepStrictEqual(validateMnemonic(uInt8ArrayOfMnemonic, englishWordlist), true); }); - it('should reject invalid menomics', () => { - deepStrictEqual(validateMnemonic('asd', englishWordlist), false); - deepStrictEqual( - validateMnemonic(generateMnemonic(englishWordlist, 128), spanishWordlist), - false - ); + it('should reject invalid menomics', async () => { + await deepStrictEqual(validateMnemonic('asd', englishWordlist), false); + + const indices = [ + 'test', + 'test', + 'vessel', + 'depth', + 'mutual', + 'idea', + 'cable', + 'soap', + 'trophy', + 'dust', + 'hold', + 'wink', + ].map((word) => englishWordlist.indexOf(word)); + const uInt8ArrayOfMnemonic = new Uint8Array(new Uint16Array(indices).buffer); + await deepStrictEqual(validateMnemonic(uInt8ArrayOfMnemonic, englishWordlist), false); }); }); describe('Entropy-mnemonic convertions', () => { describe('Should convert from mnemonic to entropy and back', () => { - it('should work with the English wodlist', () => { + it('should work with the English wodlist', async () => { const mnemonic = generateMnemonic(englishWordlist, 128); const entropy = mnemonicToEntropy(mnemonic, englishWordlist); - deepStrictEqual(entropyToMnemonic(entropy, englishWordlist), mnemonic); + await deepStrictEqual(entropyToMnemonic(entropy, englishWordlist), mnemonic); }); - it('should work with the Spanish wodlist', () => { + it('should work with the Spanish wodlist', async () => { const mnemonic = generateMnemonic(spanishWordlist, 128); const entropy = mnemonicToEntropy(mnemonic, spanishWordlist); - deepStrictEqual(entropyToMnemonic(entropy, spanishWordlist), mnemonic); + await deepStrictEqual(entropyToMnemonic(entropy, spanishWordlist), mnemonic); }); }); }); - describe('Menonic to seed', () => { + describe('Mnemonic to seed', () => { describe('Without passphrase', () => { - const MENMONIC = - 'koala óxido urbe crudo momia idioma boina rostro títere dilema himno víspera'; - - const SEED = hexToBytes( - 'e9dc495a155c2c5b577847874323853efc11e2379cc25fdcd26f3ad2ecca8b05d2a0e995fb6738dbcf65760e571863e0e8f518b5626b7865ac74f2ab814c050f' - ); + const mnemonic = + 'pill frown erosion humor invest inquiry rich garment seek such mention punch'; + const seed = new Uint8Array([ + 213, 198, 189, 89, 252, 121, 48, 207, 56, 105, 8, 152, 129, 116, 186, 218, 26, 71, 225, 55, + 201, 122, 153, 178, 5, 235, 40, 132, 179, 248, 166, 147, 18, 128, 248, 25, 184, 206, 113, + 170, 71, 235, 73, 144, 0, 134, 22, 244, 18, 229, 222, 139, 246, 28, 123, 131, 16, 215, 191, + 216, 252, 159, 213, 235, + ]); describe('Sync', () => { - it('Should recover the right seed', () => { - const recoveredSeed = mnemonicToSeedSync(MENMONIC); - deepStrictEqual(equalsBytes(SEED, recoveredSeed), true); + it('Should recover the right seed', async () => { + const recoveredSeed = mnemonicToSeedSync(mnemonic, englishWordlist); + await deepStrictEqual(equalsBytes(seed, recoveredSeed), true); + + // with Uint8Array formatted mnemonic + const indices = mnemonic.split(' ').map((word) => englishWordlist.indexOf(word)); + const uInt8ArrayOfMnemonic = new Uint8Array(new Uint16Array(indices).buffer); + + const recoveredSeedWithUint8ArrayMnemonic = mnemonicToSeedSync( + uInt8ArrayOfMnemonic, + englishWordlist + ); + await deepStrictEqual(equalsBytes(seed, recoveredSeedWithUint8ArrayMnemonic), true); }); }); describe('Async', () => { it('Should recover the right seed', async () => { - const recoveredSeed = await mnemonicToSeed(MENMONIC); - deepStrictEqual(equalsBytes(SEED, recoveredSeed), true); + const recoveredSeed = await mnemonicToSeed(mnemonic); + await deepStrictEqual(equalsBytes(seed, recoveredSeed), true); }); }); }); describe('With passphrase', () => { - const MENMONIC = - 'koala óxido urbe crudo momia idioma boina rostro títere dilema himno víspera'; + const mnemonic = + 'pill frown erosion humor invest inquiry rich garment seek such mention punch'; + const seed = new Uint8Array([ + 180, 211, 212, 196, 151, 216, 92, 25, 11, 35, 14, 186, 80, 80, 141, 156, 245, 11, 25, 118, + 50, 75, 80, 36, 116, 113, 11, 112, 36, 86, 70, 188, 92, 156, 172, 167, 83, 159, 47, 149, 92, + 107, 130, 66, 39, 251, 34, 169, 115, 143, 121, 110, 166, 28, 221, 93, 252, 165, 155, 127, + 19, 138, 107, 135, + ]); const PASSPHRASE = 'passphrase'; - const SEED = hexToBytes( - 'c54939df37bf94ab65973c6c1b8e5eaf855dc4ab200698961c398685714c99f9d9e5c8518769619ba606bdaf5254d4ef34c0789089a2e4f21e357a1aae906f9d' - ); - describe('Sync', () => { - it('Should recover the right seed', () => { - const recoveredSeed = mnemonicToSeedSync(MENMONIC, PASSPHRASE); - deepStrictEqual(SEED, recoveredSeed); + it('Should recover the right seed with mnemonic passed as string', async () => { + const recoveredSeed = mnemonicToSeedSync(mnemonic, englishWordlist, PASSPHRASE); + await deepStrictEqual(seed, recoveredSeed); + }); + + it('Should recover the right seed with mnemonic passed as Uint8Array', async () => { + // with Uint8Array formatted mnemonic + const indices = mnemonic.split(' ').map((word) => englishWordlist.indexOf(word)); + const uInt8ArrayOfMnemonic = new Uint8Array(new Uint16Array(indices).buffer); + + const recoveredSeedWithUint8ArrayMnemonic = mnemonicToSeedSync( + uInt8ArrayOfMnemonic, + englishWordlist, + PASSPHRASE + ); + + await deepStrictEqual(seed, recoveredSeedWithUint8ArrayMnemonic); }); }); describe('Async', () => { it('Should recover the right seed', async () => { - const recoveredSeed = await mnemonicToSeed(MENMONIC, PASSPHRASE); - deepStrictEqual(SEED, recoveredSeed); + const recoveredSeed = await mnemonicToSeed(mnemonic, PASSPHRASE); + await deepStrictEqual(seed, recoveredSeed); }); }); }); }); // Based on https://github.com/bitcoinjs/bip39/blob/cfea218ee2e6c3157baabb1e2ec684d36cce89c5/test/index.js - const VECTORS: Record> = { + const VECTORS: Record> = { english: [ [ '00000000000000000000000000000000', 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', 'c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04', + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0]), ], [ '7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f', 'legal winner thank year wave sausage worth useful legal winner thank yellow', '2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607', + new Uint8Array([ + 251, 3, 223, 7, 254, 6, 247, 7, 191, 7, 253, 5, 239, 7, 127, 7, 251, 3, 223, 7, 254, 6, + 248, 7, + ]), ], [ '80808080808080808080808080808080', 'letter advice cage absurd amount doctor acoustic avoid letter advice cage above', 'd71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8', + new Uint8Array([ + 4, 4, 32, 0, 1, 1, 8, 0, 64, 0, 2, 2, 16, 0, 128, 0, 4, 4, 32, 0, 1, 1, 4, 0, + ]), ], [ 'ffffffffffffffffffffffffffffffff', 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong', 'ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069', + new Uint8Array([ + 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, + 245, 7, + ]), ], [ '000000000000000000000000000000000000000000000000', 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent', '035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa', + new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 39, 0, + ]), ], [ '7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f', 'legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will', 'f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd', + new Uint8Array([ + 251, 3, 223, 7, 254, 6, 247, 7, 191, 7, 253, 5, 239, 7, 127, 7, 251, 3, 223, 7, 254, 6, + 247, 7, 191, 7, 253, 5, 239, 7, 127, 7, 251, 3, 217, 7, + ]), ], [ '808080808080808080808080808080808080808080808080', 'letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always', '107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65', + new Uint8Array([ + 4, 4, 32, 0, 1, 1, 8, 0, 64, 0, 2, 2, 16, 0, 128, 0, 4, 4, 32, 0, 1, 1, 8, 0, 64, 0, 2, 2, + 16, 0, 128, 0, 4, 4, 60, 0, + ]), ], [ 'ffffffffffffffffffffffffffffffffffffffffffffffff', 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when', '0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528', + new Uint8Array([ + 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, + 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 209, 7, + ]), ], [ '0000000000000000000000000000000000000000000000000000000000000000', 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art', 'bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8', + new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, + ]), ], [ '7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f', 'legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title', 'bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87', + new Uint8Array([ + 251, 3, 223, 7, 254, 6, 247, 7, 191, 7, 253, 5, 239, 7, 127, 7, 251, 3, 223, 7, 254, 6, + 247, 7, 191, 7, 253, 5, 239, 7, 127, 7, 251, 3, 223, 7, 254, 6, 247, 7, 191, 7, 253, 5, + 239, 7, 23, 7, + ]), ], [ '8080808080808080808080808080808080808080808080808080808080808080', 'letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless', 'c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f', + new Uint8Array([ + 4, 4, 32, 0, 1, 1, 8, 0, 64, 0, 2, 2, 16, 0, 128, 0, 4, 4, 32, 0, 1, 1, 8, 0, 64, 0, 2, 2, + 16, 0, 128, 0, 4, 4, 32, 0, 1, 1, 8, 0, 64, 0, 2, 2, 16, 0, 189, 0, + ]), ], [ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote', 'dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad', + new Uint8Array([ + 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, + 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, 255, 7, + 255, 7, 175, 7, + ]), ], [ '77c2b00716cec7213839159e404db50d', 'jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge', 'b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f672a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff', + new Uint8Array([ + 190, 3, 172, 0, 14, 0, 108, 1, 99, 7, 132, 4, 7, 7, 21, 1, 242, 4, 19, 0, 106, 3, 222, 0, + ]), ], [ 'b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b', 'renew stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap', '9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5', + new Uint8Array([ + 177, 5, 167, 6, 179, 0, 110, 2, 32, 3, 202, 7, 29, 1, 193, 3, 24, 0, 95, 0, 59, 6, 159, 2, + 20, 4, 44, 4, 180, 7, 189, 6, 123, 7, 218, 6, + ]), ], [ '3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982', 'dignity pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic', 'ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da20af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67', + new Uint8Array([ + 240, 1, 5, 5, 19, 4, 151, 3, 153, 4, 217, 6, 64, 5, 13, 6, 110, 6, 28, 7, 245, 3, 29, 5, + 139, 4, 223, 1, 112, 3, 32, 7, 143, 1, 23, 2, 152, 6, 21, 0, 47, 6, 250, 1, 48, 1, 128, 2, + ]), ], [ '0460ef47585604c5660618db2e6a7e7f', 'afford alter spike radar gate glance object seek swamp infant panel yellow', '65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41f8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4', + new Uint8Array([ + 35, 0, 59, 0, 142, 6, 133, 5, 2, 3, 21, 3, 192, 4, 24, 6, 217, 6, 154, 3, 252, 4, 248, 7, + ]), ], [ '72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f', 'indicate race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left', '3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349dbe2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba', + new Uint8Array([ + 151, 3, 131, 5, 117, 5, 93, 4, 197, 6, 118, 3, 165, 1, 37, 2, 60, 5, 196, 5, 88, 0, 206, + 3, 13, 1, 0, 7, 56, 5, 0, 2, 59, 0, 249, 3, + ]), ], [ '2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416', 'clutch control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste', 'fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f86360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449', + new Uint8Array([ + 100, 1, 123, 1, 143, 7, 36, 7, 114, 7, 92, 1, 165, 7, 129, 3, 55, 5, 25, 3, 157, 5, 32, 6, + 217, 4, 114, 3, 161, 7, 216, 1, 110, 7, 20, 7, 192, 3, 21, 2, 133, 5, 163, 5, 130, 4, 241, + 6, + ]), ], [ 'eaebabb2383351fd31d703840b32e9e2', 'turtle front uncle idea crush write shrug there lottery flower risk shell', 'bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c404f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c', + new Uint8Array([ + 87, 7, 234, 2, 100, 7, 131, 3, 168, 1, 244, 7, 58, 6, 3, 7, 32, 4, 204, 2, 211, 5, 43, 6, + ]), ], [ '7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78', 'kiss carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment', 'ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b079403485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79', + new Uint8Array([ + 214, 3, 23, 1, 252, 1, 114, 7, 119, 1, 177, 1, 117, 7, 79, 0, 225, 5, 86, 3, 172, 7, 180, + 1, 229, 2, 191, 4, 189, 4, 101, 3, 83, 5, 25, 6, + ]), ], [ '4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef', 'exile ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top', '095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b14d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c', + new Uint8Array([ + 125, 2, 106, 0, 120, 1, 230, 3, 192, 6, 184, 3, 98, 2, 5, 6, 116, 0, 139, 1, 48, 0, 32, 1, + 138, 1, 76, 2, 37, 4, 126, 3, 31, 6, 238, 4, 54, 6, 143, 0, 180, 0, 42, 3, 221, 7, 39, 7, + ]), ], [ '18ab19a9f54a9274f03e5209a2ac8a91', 'board flee heavy tunnel powder denial science ski answer betray cargo cat', '6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe005831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8', + new Uint8Array([ + 197, 0, 198, 2, 83, 3, 84, 7, 73, 5, 211, 1, 7, 6, 82, 6, 77, 0, 171, 0, 21, 1, 30, 1, + ]), ], [ '18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4', 'board blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief', 'f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf07c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9', + new Uint8Array([ + 197, 0, 184, 0, 176, 3, 184, 1, 103, 7, 202, 6, 102, 4, 173, 3, 88, 6, 5, 3, 75, 7, 158, + 3, 187, 5, 49, 3, 160, 3, 219, 5, 141, 4, 62, 1, + ]), ], [ '15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419', 'beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut', 'b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd', + new Uint8Array([ + 174, 0, 161, 6, 89, 6, 90, 1, 158, 0, 92, 7, 31, 7, 245, 3, 114, 0, 157, 0, 7, 3, 214, 2, + 248, 0, 63, 6, 51, 3, 98, 7, 98, 4, 94, 0, 195, 4, 248, 4, 161, 1, 36, 7, 131, 0, 103, 1, + ]), ], ], japanese: [ @@ -385,27 +534,40 @@ describe('BIP39', () => { '346b7321d8c04f6f37b49fdf062a2fddc8e1bf8f1d33171b65074531ec546d1d3469974beccb1a09263440fc92e1042580a557fdce314e27ee4eabb25fa5e5fe', ], ], + }; describe('BIP39-lib vectors', () => { function testVector( description: string, wordlist: string[], password: string, - v: [string, string, string], + v: [string, string, string, Uint8Array?], i: number ) { - const [entropy, mnemonic, seed] = v; - describe(`for ${description} (${i}), ${entropy}`, async () => { - deepStrictEqual(toHex(mnemonicToEntropy(mnemonic, wordlist)), entropy, 'mnemonicToEntropy'); - deepStrictEqual(toHex(mnemonicToSeedSync(mnemonic, password)), seed, 'mnemonicToSeedSync'); + let [entropy, mnemonic, seed, mnemonicAsUint8Array] = v; + if (!mnemonicAsUint8Array) { + const indices = mnemonic.split(' ').map((word) => wordlist.indexOf(word)); + mnemonicAsUint8Array = new Uint8Array(new Uint16Array(indices).buffer); + } + it(`for ${description} (${i}), ${entropy}`, async () => { + await deepStrictEqual( + toHex(mnemonicToEntropy(mnemonic, wordlist)), + entropy, + 'mnemonicToEntropy' + ); + await deepStrictEqual( + toHex(mnemonicToSeedSync(mnemonic, wordlist, password)), + seed, + 'mnemonicToSeedSync' + ); const res = await mnemonicToSeed(mnemonic, password); - deepStrictEqual(toHex(res), seed, 'mnemonicToSeed'); - deepStrictEqual( + await deepStrictEqual(toHex(res), seed, 'mnemonicToSeed'); + await deepStrictEqual( entropyToMnemonic(hexToBytes(entropy), wordlist), - mnemonic, + mnemonicAsUint8Array, 'entropyToMnemonic' ); - deepStrictEqual(validateMnemonic(mnemonic, wordlist), true, 'validateMnemonic'); + await deepStrictEqual(validateMnemonic(mnemonic, wordlist), true, 'validateMnemonic'); }); } for (let i = 0; i < VECTORS.english.length; i++) { @@ -420,46 +582,47 @@ describe('BIP39', () => { i ); } - describe('Invalid entropy', () => { + it('Invalid entropy', () => { throws(() => entropyToMnemonic(new Uint8Array([]), englishWordlist)); throws(() => entropyToMnemonic(new Uint8Array([0, 0, 0]), englishWordlist)); throws(() => entropyToMnemonic(new Uint8Array(1028), englishWordlist)); }); - describe('UTF8 passwords', () => { + it('UTF8 passwords', async () => { for (const [_, mnemonic, seed] of VECTORS.japanese) { const password = '㍍ガバヴァぱばぐゞちぢ十人十色'; const normalizedPassword = 'メートルガバヴァぱばぐゞちぢ十人十色'; - deepStrictEqual( - toHex(mnemonicToSeedSync(mnemonic, password)), + await deepStrictEqual( + toHex(mnemonicToSeedSync(mnemonic, japaneseWordlist, password)), seed, 'mnemonicToSeedSync normalizes passwords' ); - deepStrictEqual( - toHex(mnemonicToSeedSync(mnemonic, normalizedPassword)), + await deepStrictEqual( + toHex(mnemonicToSeedSync(mnemonic, japaneseWordlist, normalizedPassword)), seed, 'mnemonicToSeedSync leaves normalizes passwords as-is' ); } }); - describe('generateMnemonic can vary entropy length', () => { - deepStrictEqual( - generateMnemonic(englishWordlist, 160).split(' ').length, - 15, - 'can vary generated entropy bit length' - ); + it('generateMnemonic can vary entropy length', async () => { + const mnemonicAsUint8 = generateMnemonic(englishWordlist, 160); + const recoveredIndices = Array.from(new Uint16Array(mnemonicAsUint8.buffer)); + + const mnemonicAsArray = recoveredIndices.map((i) => englishWordlist[i]); + + await deepStrictEqual(mnemonicAsArray.length, 15, 'can vary generated entropy bit length'); }); - describe('validateMnemonic', () => { - deepStrictEqual( + it('validateMnemonic', async () => { + await deepStrictEqual( validateMnemonic('sleep kitten', englishWordlist), false, 'fails for a mnemonic that is too short' ); - deepStrictEqual( + await deepStrictEqual( validateMnemonic('sleep kitten sleep kitten sleep kitten', englishWordlist), false, 'fails for a mnemonic that is too short' ); - deepStrictEqual( + await deepStrictEqual( validateMnemonic( 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about end grace oxygen maze bright face loan ticket trial leg cruel lizard bread worry reject journey perfect chef section caught neither install industry', englishWordlist @@ -467,7 +630,7 @@ describe('BIP39', () => { false, 'fails for a mnemonic that is too long' ); - deepStrictEqual( + await deepStrictEqual( validateMnemonic( 'turtle front uncle idea crush write shrug there lottery flower risky shell', englishWordlist @@ -475,7 +638,7 @@ describe('BIP39', () => { false, 'fails if mnemonic words are not in the word list' ); - deepStrictEqual( + await deepStrictEqual( validateMnemonic( 'sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten', englishWordlist