Align JS bip39 with wasm-crypto (#730)

* Align JS bip39 with wasm-crypto

* Cleanups
This commit is contained in:
Jaco Greeff
2020-10-26 22:57:11 +01:00
committed by GitHub
parent badbbd76cb
commit 6715bef24a
8 changed files with 2235 additions and 26 deletions
+2 -1
View File
@@ -30,9 +30,9 @@
"@polkadot/util": "^3.6.2-5",
"@polkadot/wasm-crypto": "^1.4.1",
"base-x": "^3.0.8",
"bip39": "^3.0.2",
"blakejs": "^1.1.0",
"bn.js": "^5.1.3",
"create-hash": "^1.2.0",
"elliptic": "^6.5.3",
"js-sha3": "^0.8.0",
"pbkdf2": "^3.1.1",
@@ -42,6 +42,7 @@
},
"devDependencies": {
"@types/bn.js": "^4.11.6",
"@types/create-hash": "^1.2.0",
"@types/elliptic": "^6.4.12",
"@types/pbkdf2": "^3.1.0",
"@types/scryptsy": "^2.0.0",
File diff suppressed because it is too large Load Diff
+157
View File
@@ -0,0 +1,157 @@
// Copyright 2017-2020 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Adapted from the bitcoinjs/bip39 source
// https://github.com/bitcoinjs/bip39/blob/1d063b6a6aee4145b34d701037cd3e67f5446ff9/ts_src/index.ts
// Copyright (c) 2014, Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com>
// ISC Licence
//
// Change made in this version -
// - Adjust formatting (just eslint differences)
// - Only English wordlist (this aligns with the wasm-crypto implementation)
// - Use util-crypto randomAsU8a (instead of randombytes)
// - Remove setting of wordlost passing of wordlist in functions
// - Remove mnemonicToSeed (we only use the sync variant)
import createHash from 'create-hash';
import { pbkdf2Sync } from 'pbkdf2';
import randomAsU8a from '../random/asU8a';
import DEFAULT_WORDLIST from './bip39-en';
const INVALID_MNEMONIC = 'Invalid mnemonic';
const INVALID_ENTROPY = 'Invalid entropy';
const INVALID_CHECKSUM = 'Invalid mnemonic checksum';
function normalize (str?: string): string {
return (str || '').normalize('NFKD');
}
function binaryToByte (bin: string): number {
return parseInt(bin, 2);
}
function bytesToBinary (bytes: number[]): string {
return bytes.map((x: number) => x.toString(2).padStart(8, '0')).join('');
}
function deriveChecksumBits (entropyBuffer: Uint8Array): string {
const ENT = entropyBuffer.length * 8;
const CS = ENT / 32;
const hash = createHash('sha256')
.update(entropyBuffer)
.digest();
return bytesToBinary(Array.from(hash)).slice(0, CS);
}
function salt (password?: string): string {
return 'mnemonic' + (password || '');
}
export function mnemonicToSeedSync (mnemonic: string, password?: string): Buffer {
const mnemonicBuffer = Buffer.from(normalize(mnemonic), 'utf8');
const saltBuffer = Buffer.from(salt(normalize(password)), 'utf8');
return pbkdf2Sync(mnemonicBuffer, saltBuffer, 2048, 64, 'sha512');
}
export function mnemonicToEntropy (mnemonic: string): string {
const words = normalize(mnemonic).split(' ');
if (words.length % 3 !== 0) {
throw new Error(INVALID_MNEMONIC);
}
// convert word indices to 11 bit binary strings
const bits = words
.map(
(word: string): string => {
const index = DEFAULT_WORDLIST.indexOf(word);
if (index === -1) {
throw new Error(INVALID_MNEMONIC);
}
return index.toString(2).padStart(11, '0');
}
)
.join('');
// split the binary string into ENT/CS
const dividerIndex = Math.floor(bits.length / 33) * 32;
const entropyBits = bits.slice(0, dividerIndex);
const checksumBits = bits.slice(dividerIndex);
// calculate the checksum and compare
const entropyBytes = entropyBits.match(/(.{1,8})/g)?.map(binaryToByte);
if (!entropyBytes || entropyBytes.length < 16) {
throw new Error(INVALID_ENTROPY);
} else if (entropyBytes.length > 32) {
throw new Error(INVALID_ENTROPY);
} else if (entropyBytes.length % 4 !== 0) {
throw new Error(INVALID_ENTROPY);
}
const entropy = Buffer.from(entropyBytes);
const newChecksum = deriveChecksumBits(entropy);
if (newChecksum !== checksumBits) {
throw new Error(INVALID_CHECKSUM);
}
return entropy.toString('hex');
}
export function entropyToMnemonic (entropy: Uint8Array): string {
// 128 <= ENT <= 256
if (entropy.length < 16) {
throw new TypeError(INVALID_ENTROPY);
}
if (entropy.length > 32) {
throw new TypeError(INVALID_ENTROPY);
} else if (entropy.length % 4 !== 0) {
throw new TypeError(INVALID_ENTROPY);
}
const entropyBits = bytesToBinary(Array.from(entropy));
const checksumBits = deriveChecksumBits(entropy);
const bits = entropyBits + checksumBits;
// we just set it prior, so this is a safe check
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const chunks = bits.match(/(.{1,11})/g)!;
const words = chunks.map(
(binary: string): string => {
const index = binaryToByte(binary);
return DEFAULT_WORDLIST[index];
}
);
return words.join(' ');
}
export function generateMnemonic (strength?: number): string {
strength = strength || 128;
if (strength % 32 !== 0) {
throw new TypeError(INVALID_ENTROPY);
}
return entropyToMnemonic(randomAsU8a(strength / 8));
}
export function validateMnemonic (mnemonic: string): boolean {
try {
mnemonicToEntropy(mnemonic);
} catch (e) {
return false;
}
return true;
}
@@ -3,9 +3,10 @@
import '../polyfill';
import { generateMnemonic } from 'bip39';
import { bip39Generate, isReady } from '@polkadot/wasm-crypto';
import { generateMnemonic } from './bip39';
export type WordCount = 12 | 15 | 18 | 21 | 24;
// mapping of words to the actual strength (as expected)
@@ -3,10 +3,11 @@
import '../polyfill';
import { mnemonicToEntropy } from 'bip39';
import { hexAddPrefix, hexToU8a } from '@polkadot/util';
import { bip39ToEntropy, isReady } from '@polkadot/wasm-crypto';
import { mnemonicToEntropy } from './bip39';
export default function toEntropy (mnemonic: string): Uint8Array {
return isReady()
? bip39ToEntropy(mnemonic)
@@ -1,11 +1,12 @@
// Copyright 2017-2020 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { mnemonicToSeedSync } from 'bip39';
import { bufferToU8a } from '@polkadot/util';
import { bip39ToSeed, isReady } from '@polkadot/wasm-crypto';
import { mnemonicToSeedSync } from './bip39';
/**
* @name toSeed
* @summary Creates a valid Ethereum/Bitcoin-compatible seed from a mnemonic input
@@ -3,9 +3,10 @@
import '../polyfill';
import { validateMnemonic } from 'bip39';
import { bip39Validate, isReady } from '@polkadot/wasm-crypto';
import { validateMnemonic } from './bip39';
/**
* @name mnemonicValidate
* @summary Validates a mnemonic input using [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).
+12 -21
View File
@@ -1845,14 +1845,15 @@ __metadata:
"@polkadot/util": ^3.6.2-5
"@polkadot/wasm-crypto": ^1.4.1
"@types/bn.js": ^4.11.6
"@types/create-hash": ^1.2.0
"@types/elliptic": ^6.4.12
"@types/pbkdf2": ^3.1.0
"@types/scryptsy": ^2.0.0
"@types/xxhashjs": ^0.2.2
base-x: ^3.0.8
bip39: ^3.0.2
blakejs: ^1.1.0
bn.js: ^5.1.3
create-hash: ^1.2.0
elliptic: ^6.5.3
js-sha3: ^0.8.0
pbkdf2: ^3.1.1
@@ -2015,6 +2016,15 @@ __metadata:
languageName: node
linkType: hard
"@types/create-hash@npm:^1.2.0":
version: 1.2.2
resolution: "@types/create-hash@npm:1.2.2"
dependencies:
"@types/node": "*"
checksum: d9bd1821c26137be78eeda9d78e6a402ae766637a30323241adf23f6601d3053f2d37073798e1d36dd370fea75592a73f6ccda50a20f2b4080d2cfaee9264eac
languageName: node
linkType: hard
"@types/elliptic@npm:^6.4.12":
version: 6.4.12
resolution: "@types/elliptic@npm:6.4.12"
@@ -2131,13 +2141,6 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:11.11.6":
version: 11.11.6
resolution: "@types/node@npm:11.11.6"
checksum: 2b64bfc2344f60fdcf3baea2f9f2e065189cee3a6144d3dbdcb32cb6728dd64933808e2da8bd4d69033f04510f7de1397c08d54d8f89d505b3a957b24dcc583f
languageName: node
linkType: hard
"@types/normalize-package-data@npm:^2.4.0":
version: 2.4.0
resolution: "@types/normalize-package-data@npm:2.4.0"
@@ -3174,18 +3177,6 @@ __metadata:
languageName: node
linkType: hard
"bip39@npm:^3.0.2":
version: 3.0.2
resolution: "bip39@npm:3.0.2"
dependencies:
"@types/node": 11.11.6
create-hash: ^1.1.0
pbkdf2: ^3.0.9
randombytes: ^2.0.1
checksum: 24c59dafc6c3b5c4ff0b8d0861509c0007bc0301b217125d1f1de6a39e84d21c9cfc1df743d472237c2ec4116387a46389843b36c95585b0eacebb6f5be6ecbd
languageName: node
linkType: hard
"blakejs@npm:^1.1.0":
version: 1.1.0
resolution: "blakejs@npm:1.1.0"
@@ -8955,7 +8946,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
"pbkdf2@npm:^3.0.3, pbkdf2@npm:^3.0.9, pbkdf2@npm:^3.1.1":
"pbkdf2@npm:^3.0.3, pbkdf2@npm:^3.1.1":
version: 3.1.1
resolution: "pbkdf2@npm:3.1.1"
dependencies: