Skip to content

Commit

Permalink
Allow wordlist? param to mnemonic* functions (#1814)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacogr authored Apr 21, 2023
1 parent cdf6cc6 commit b5d83f8
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 28 deletions.
23 changes: 20 additions & 3 deletions packages/util-crypto/src/mnemonic/generate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/// <reference types="@polkadot/dev-test/globals.d.ts" />

import { cryptoWaitReady } from '../index.js';
import { french as frenchWords } from './wordlists/index.js';
import { mnemonicGenerate } from './generate.js';
import { mnemonicValidate } from './validate.js';

Expand All @@ -16,11 +17,27 @@ describe('mnemonicGenerate', (): void => {
).toEqual(true);
});

it('generates a french mnemonic', (): void => {
const mnemonic = mnemonicGenerate(24, frenchWords);
const words = mnemonic.split(' ');

expect(words).toHaveLength(24);
expect(
mnemonicValidate(mnemonic, frenchWords)
).toEqual(true);
expect(
mnemonicValidate(mnemonic)
).toEqual(false);
expect(
words.every((w) => frenchWords.includes(w))
).toEqual(true);
});

for (const onlyJs of [false, true]) {
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
for (const num of [12, 15, 18, 21, 24] as const) {
it(`generates a valid mnemonic (${num} words)`, (): void => {
const mnemonic = mnemonicGenerate(num, onlyJs);
const mnemonic = mnemonicGenerate(num, undefined, onlyJs);
const isValid = mnemonicValidate(mnemonic);

expect(mnemonic.split(' ')).toHaveLength(num);
Expand All @@ -29,8 +46,8 @@ describe('mnemonicGenerate', (): void => {
}

it('generates non-deterministic', (): void => {
const m1 = mnemonicGenerate(24, onlyJs);
const m2 = mnemonicGenerate(24, onlyJs);
const m1 = mnemonicGenerate(24, undefined, onlyJs);
const m2 = mnemonicGenerate(24, undefined, onlyJs);

expect(m1 === m2).toEqual(false);
expect(mnemonicValidate(m1)).toEqual(true);
Expand Down
6 changes: 3 additions & 3 deletions packages/util-crypto/src/mnemonic/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { generateMnemonic } from './bip39.js';
* const mnemonic = mnemonicGenerate(); // => string
* ```
*/
export function mnemonicGenerate (numWords: 12 | 15 | 18 | 21 | 24 = 12, onlyJs?: boolean): string {
return !hasBigInt || (!onlyJs && isReady())
export function mnemonicGenerate (numWords: 12 | 15 | 18 | 21 | 24 = 12, wordlist?: string[], onlyJs?: boolean): string {
return !hasBigInt || (!wordlist && !onlyJs && isReady())
? bip39Generate(numWords)
: generateMnemonic(numWords);
: generateMnemonic(numWords, wordlist);
}
14 changes: 13 additions & 1 deletion packages/util-crypto/src/mnemonic/toEntropy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { u8aToHex } from '@polkadot/util';

import { cryptoWaitReady } from '../index.js';
import tests from '../sr25519/pair/testing.spec.js';
import { french as frenchWords } from './wordlists/index.js';
import { mnemonicToEntropy } from './toEntropy.js';

await cryptoWaitReady();
Expand All @@ -16,9 +17,20 @@ describe('mnemonicToEntropy', (): void => {
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
tests.forEach(([mnemonic, entropy], index): void => {
it(`Created correct entropy for ${index}`, (): void => {
expect(u8aToHex(mnemonicToEntropy(mnemonic, onlyJs))).toEqual(entropy);
expect(u8aToHex(mnemonicToEntropy(mnemonic, undefined, onlyJs))).toEqual(entropy);
});
});
});
}

it('has the correct entropy for non-Englist mnemonics', (): void => {
const mnemonic = 'pompier circuler pulpe injure aspect abyssal nuque boueux équerre balisage pieuvre médecin petit suffixe soleil cumuler monstre arlequin liasse pixel garrigue noble buisson scandale';

expect(
() => mnemonicToEntropy(mnemonic)
).toThrow();
expect(
mnemonicToEntropy(mnemonic, frenchWords)
).toEqual(new Uint8Array([189, 230, 55, 17, 65, 33, 40, 4, 106, 9, 11, 88, 227, 26, 229, 76, 59, 123, 200, 55, 177, 232, 158, 66, 34, 54, 93, 54, 255, 74, 137, 70]));
});
});
6 changes: 3 additions & 3 deletions packages/util-crypto/src/mnemonic/toEntropy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { bip39ToEntropy, isReady } from '@polkadot/wasm-crypto';

import { mnemonicToEntropy as jsToEntropy } from './bip39.js';

export function mnemonicToEntropy (mnemonic: string, onlyJs?: boolean): Uint8Array {
return !hasBigInt || (!onlyJs && isReady())
export function mnemonicToEntropy (mnemonic: string, wordlist?: string[], onlyJs?: boolean): Uint8Array {
return !hasBigInt || (!wordlist && !onlyJs && isReady())
? bip39ToEntropy(mnemonic)
: jsToEntropy(mnemonic);
: jsToEntropy(mnemonic, wordlist);
}
22 changes: 17 additions & 5 deletions packages/util-crypto/src/mnemonic/toMiniSecret.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { u8aEq, u8aToHex } from '@polkadot/util';

import { cryptoWaitReady } from '../index.js';
import tests from '../sr25519/pair/testing.spec.js';
import { korean as koreanWords } from './wordlists/index.js';
import { mnemonicToMiniSecret } from './toMiniSecret.js';

const MNEMONIC = 'seed sock milk update focus rotate barely fade car face mechanic mercy';
Expand All @@ -19,31 +20,42 @@ describe('mnemonicToMiniSecret', (): void => {
it(`generates Wasm & Js equivalents for password=${password || 'undefined'}`, (): void => {
expect(
u8aEq(
mnemonicToMiniSecret(MNEMONIC, password, true),
mnemonicToMiniSecret(MNEMONIC, password, false)
mnemonicToMiniSecret(MNEMONIC, password, undefined, true),
mnemonicToMiniSecret(MNEMONIC, password, undefined, false)
)
).toEqual(true);
});
}

it('creates a known minisecret from a non-english mnemonic', (): void => {
const mnemonic = '엉덩이 능동적 숫자 팩시밀리 비난 서적 파출소 도움 독창적 인생 상류 먼지 답변 음반 수박 사업 노란색 공사 우체국 특급 도대체 금지 굉장히 고무신';

expect(
() => mnemonicToMiniSecret(mnemonic, 'testing')
).toThrow();
expect(
u8aToHex(mnemonicToMiniSecret(mnemonic, 'testing', koreanWords))
).toEqual('0xefa278a62535581767a2f49cb542ed91b65fb911e1b05e7a09c702b257f10c13');
});

for (const onlyJs of [false, true]) {
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
it('generates a valid seed', (): void => {
expect(
u8aToHex(mnemonicToMiniSecret(MNEMONIC, undefined, onlyJs))
u8aToHex(mnemonicToMiniSecret(MNEMONIC, undefined, undefined, onlyJs))
).toEqual(SEED);
});

it('fails with non-mnemonics', (): void => {
expect(
() => mnemonicToMiniSecret('foo bar baz', undefined, onlyJs)
() => mnemonicToMiniSecret('foo bar baz', undefined, undefined, onlyJs)
).toThrow(/mnemonic specified/);
});

tests.forEach(([mnemonic, , seed], index): void => {
it(`Created correct seed for ${index}`, (): void => {
expect(
u8aToHex(mnemonicToMiniSecret(mnemonic, 'Substrate', onlyJs))
u8aToHex(mnemonicToMiniSecret(mnemonic, 'Substrate', undefined, onlyJs))
).toEqual(
// mini returned here, only check first 32-bytes (64 hex + 2 prefix)
seed.substring(0, 66)
Expand Down
10 changes: 4 additions & 6 deletions packages/util-crypto/src/mnemonic/toMiniSecret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ import { pbkdf2Encode } from '../pbkdf2/index.js';
import { mnemonicToEntropy } from './toEntropy.js';
import { mnemonicValidate } from './validate.js';

export function mnemonicToMiniSecret (mnemonic: string, password = '', onlyJs?: boolean): Uint8Array {
if (!mnemonicValidate(mnemonic)) {
export function mnemonicToMiniSecret (mnemonic: string, password = '', wordlist?: string[], onlyJs?: boolean): Uint8Array {
if (!mnemonicValidate(mnemonic, wordlist, onlyJs)) {
throw new Error('Invalid bip39 mnemonic specified');
}

if (!onlyJs && isReady()) {
} else if (!wordlist && !onlyJs && isReady()) {
return bip39ToMiniSecret(mnemonic, password);
}

const entropy = mnemonicToEntropy(mnemonic);
const entropy = mnemonicToEntropy(mnemonic, wordlist);
const salt = stringToU8a(`mnemonic${password}`);

// return the first 32 bytes as the seed
Expand Down
4 changes: 2 additions & 2 deletions packages/util-crypto/src/mnemonic/toMiniSecretCmp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ for (const onlyJsMnemonic of [false, true]) {
// do iterations to check and re-check that all matches
for (const count of arrayRange(NUM_CHECKS)) {
it(`check=${count + 1}`, (): void => {
const minisecret = mnemonicToMiniSecret(mnemonic, count ? `${count}` : '', onlyJsMnemonic);
const minisecret = mnemonicToMiniSecret(mnemonic, count ? `${count}` : '', undefined, onlyJsMnemonic);
const edpub = ed25519PairFromSeed(minisecret).publicKey;
const srpub = sr25519PairFromSeed(minisecret).publicKey;
const testmini = mnemonicToMiniSecret(mnemonic, count ? `${count}` : '', onlyJsMini);
const testmini = mnemonicToMiniSecret(mnemonic, count ? `${count}` : '', undefined, onlyJsMini);

// explicit minisecret compare
expect(
Expand Down
16 changes: 14 additions & 2 deletions packages/util-crypto/src/mnemonic/validate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/// <reference types="@polkadot/dev-test/globals.d.ts" />

import { cryptoWaitReady } from '../index.js';
import { french as frenchWords } from './wordlists/index.js';
import { mnemonicValidate } from './validate.js';

await cryptoWaitReady();
Expand All @@ -13,15 +14,26 @@ describe('mnemonicValidate', (): void => {
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
it('returns true on valid', (): void => {
expect(
mnemonicValidate('seed sock milk update focus rotate barely fade car face mechanic mercy', onlyJs)
mnemonicValidate('seed sock milk update focus rotate barely fade car face mechanic mercy', undefined, onlyJs)
).toEqual(true);
});

it('returns false on invalid', (): void => {
expect(
mnemonicValidate('wine photo extra cushion basket dwarf humor cloud truck job boat submit', onlyJs)
mnemonicValidate('wine photo extra cushion basket dwarf humor cloud truck job boat submit', undefined, onlyJs)
).toEqual(false);
});
});
}

it('allows usage of a different wordlist', (): void => {
const mnemonic = 'pompier circuler pulpe injure aspect abyssal nuque boueux équerre balisage pieuvre médecin petit suffixe soleil cumuler monstre arlequin liasse pixel garrigue noble buisson scandale';

expect(
mnemonicValidate(mnemonic, frenchWords)
).toEqual(true);
expect(
mnemonicValidate(mnemonic)
).toEqual(false);
});
});
6 changes: 3 additions & 3 deletions packages/util-crypto/src/mnemonic/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { validateMnemonic } from './bip39.js';
* const isValidMnemonic = mnemonicValidate(mnemonic); // => boolean
* ```
*/
export function mnemonicValidate (mnemonic: string, onlyJs?: boolean): boolean {
return !hasBigInt || (!onlyJs && isReady())
export function mnemonicValidate (mnemonic: string, wordlist?: string[], onlyJs?: boolean): boolean {
return !hasBigInt || (!wordlist && !onlyJs && isReady())
? bip39Validate(mnemonic)
: validateMnemonic(mnemonic);
: validateMnemonic(mnemonic, wordlist);
}

0 comments on commit b5d83f8

Please sign in to comment.