mirror of
https://github.com/status-im/bip39.git
synced 2025-02-20 08:18:10 +00:00
Merge pull request #105 from bitcoinjs/tsAllowExclude
Allow excluding wordlists when building for browserify
This commit is contained in:
commit
68d0bb46cf
@ -6,6 +6,10 @@ node_js:
|
||||
- "lts/*"
|
||||
- "9"
|
||||
- "10"
|
||||
matrix:
|
||||
include:
|
||||
- node_js: "lts/*"
|
||||
env: TEST_SUITE=gitdiff:ci
|
||||
env:
|
||||
- TEST_SUITE=test
|
||||
script: npm run-script $TEST_SUITE
|
||||
|
56
README.md
56
README.md
@ -16,6 +16,62 @@ When a checksum is invalid, warn the user that the phrase is not something gener
|
||||
|
||||
However, there should be other checks in place, such as checking to make sure the user is inputting 12 words or more separated by a space. ie. `phrase.trim().split(/\s+/g).length >= 12`
|
||||
|
||||
## Removing wordlists from webpack/browserify
|
||||
|
||||
Browserify/Webpack bundles can get very large if you include all the wordlists, so you can now exclude wordlists to make your bundle lighter.
|
||||
|
||||
For example, if we want to exclude all wordlists besides chinese_simplified, you could build using the browserify command below.
|
||||
|
||||
```bash
|
||||
$ browserify -r bip39 -s bip39 \
|
||||
--exclude=./wordlists/english.json \
|
||||
--exclude=./wordlists/japanese.json \
|
||||
--exclude=./wordlists/spanish.json \
|
||||
--exclude=./wordlists/italian.json \
|
||||
--exclude=./wordlists/french.json \
|
||||
--exclude=./wordlists/korean.json \
|
||||
--exclude=./wordlists/chinese_traditional.json \
|
||||
> bip39.browser.js
|
||||
```
|
||||
|
||||
This will create a bundle that only contains the chinese_simplified wordlist, and it will be the default wordlist for all calls without explicit wordlists.
|
||||
|
||||
This is how it will look in the browser console.
|
||||
|
||||
```javascript
|
||||
> bip39.entropyToMnemonic('00000000000000000000000000000000')
|
||||
"的 的 的 的 的 的 的 的 的 的 的 在"
|
||||
> bip39.wordlists.chinese_simplified
|
||||
Array(2048) [ "的", "一", "是", "在", "不", "了", "有", "和", "人", "这", … ]
|
||||
> bip39.wordlists.english
|
||||
undefined
|
||||
> bip39.wordlists.japanese
|
||||
undefined
|
||||
> bip39.wordlists.spanish
|
||||
undefined
|
||||
> bip39.wordlists.italian
|
||||
undefined
|
||||
> bip39.wordlists.french
|
||||
undefined
|
||||
> bip39.wordlists.korean
|
||||
undefined
|
||||
> bip39.wordlists.chinese_traditional
|
||||
undefined
|
||||
```
|
||||
|
||||
For a list of supported wordlists check the wordlists folder. The name of the json file (minus the extension) is the name of the key to access the wordlist.
|
||||
|
||||
You can also change the default wordlist at runtime if you dislike the wordlist you were given as default.
|
||||
|
||||
```javascript
|
||||
> bip39.entropyToMnemonic('00000000000000000000000000000fff')
|
||||
"あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あまい ろんり"
|
||||
> bip39.setDefaultWordlist('italian')
|
||||
undefined
|
||||
> bip39.entropyToMnemonic('00000000000000000000000000000fff')
|
||||
"abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco aforisma zibetto"
|
||||
```
|
||||
|
||||
## Installation
|
||||
``` bash
|
||||
npm install bip39
|
||||
|
@ -10,6 +10,7 @@
|
||||
"coverage": "nyc --branches 100 --functions 100 --check-coverage npm run unit",
|
||||
"format": "npm run prettier -- --write",
|
||||
"format:ci": "npm run prettier -- --check",
|
||||
"gitdiff:ci": "npm run build && git diff --exit-code",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore",
|
||||
"test": "npm run build && npm run format:ci && npm run lint && npm run unit",
|
||||
|
30
src/_wordlists.js
Normal file
30
src/_wordlists.js
Normal file
@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// browserify by default only pulls in files that are hard coded in requires
|
||||
// In order of last to first in this file, the default wordlist will be chosen
|
||||
// based on what is present. (Bundles may remove wordlists they don't need)
|
||||
const wordlistFilenames = [
|
||||
'chinese_simplified',
|
||||
'chinese_traditional',
|
||||
'korean',
|
||||
'french',
|
||||
'italian',
|
||||
'spanish',
|
||||
'japanese',
|
||||
'english',
|
||||
];
|
||||
const wordlists = {};
|
||||
exports.wordlists = wordlists;
|
||||
let _default;
|
||||
exports._default = _default;
|
||||
wordlistFilenames.forEach(lang => {
|
||||
try {
|
||||
exports._default = _default = require('./wordlists/' + lang + '.json');
|
||||
wordlists[lang] = _default;
|
||||
if (lang === 'japanese')
|
||||
wordlists.JA = _default;
|
||||
if (lang === 'english')
|
||||
wordlists.EN = _default;
|
||||
}
|
||||
catch (err) { }
|
||||
});
|
53
src/index.js
53
src/index.js
@ -5,18 +5,13 @@ const pbkdf2_1 = require("pbkdf2");
|
||||
const randomBytes = require("randombytes");
|
||||
// use unorm until String.prototype.normalize gets better browser support
|
||||
const unorm = require("unorm");
|
||||
const CHINESE_SIMPLIFIED_WORDLIST = require("./wordlists/chinese_simplified.json");
|
||||
const CHINESE_TRADITIONAL_WORDLIST = require("./wordlists/chinese_traditional.json");
|
||||
const ENGLISH_WORDLIST = require("./wordlists/english.json");
|
||||
const FRENCH_WORDLIST = require("./wordlists/french.json");
|
||||
const ITALIAN_WORDLIST = require("./wordlists/italian.json");
|
||||
const JAPANESE_WORDLIST = require("./wordlists/japanese.json");
|
||||
const KOREAN_WORDLIST = require("./wordlists/korean.json");
|
||||
const SPANISH_WORDLIST = require("./wordlists/spanish.json");
|
||||
const DEFAULT_WORDLIST = ENGLISH_WORDLIST;
|
||||
const _wordlists_1 = require("./_wordlists");
|
||||
let DEFAULT_WORDLIST = _wordlists_1._default;
|
||||
const INVALID_MNEMONIC = 'Invalid mnemonic';
|
||||
const INVALID_ENTROPY = 'Invalid entropy';
|
||||
const INVALID_CHECKSUM = 'Invalid mnemonic checksum';
|
||||
const WORDLIST_REQUIRED = 'A wordlist is required but a default could not be found.\n' +
|
||||
'Please explicitly pass a 2048 word array explicitly.';
|
||||
function lpad(str, padString, length) {
|
||||
while (str.length < length)
|
||||
str = padString + str;
|
||||
@ -74,6 +69,9 @@ async function mnemonicToSeedHexAsync(mnemonic, password) {
|
||||
exports.mnemonicToSeedHexAsync = mnemonicToSeedHexAsync;
|
||||
function mnemonicToEntropy(mnemonic, wordlist) {
|
||||
wordlist = wordlist || DEFAULT_WORDLIST;
|
||||
if (!wordlist) {
|
||||
throw new Error(WORDLIST_REQUIRED);
|
||||
}
|
||||
const words = unorm.nfkd(mnemonic).split(' ');
|
||||
if (words.length % 3 !== 0)
|
||||
throw new Error(INVALID_MNEMONIC);
|
||||
@ -109,6 +107,9 @@ function entropyToMnemonic(entropy, wordlist) {
|
||||
if (!Buffer.isBuffer(entropy))
|
||||
entropy = Buffer.from(entropy, 'hex');
|
||||
wordlist = wordlist || DEFAULT_WORDLIST;
|
||||
if (!wordlist) {
|
||||
throw new Error(WORDLIST_REQUIRED);
|
||||
}
|
||||
// 128 <= ENT <= 256
|
||||
if (entropy.length < 16)
|
||||
throw new TypeError(INVALID_ENTROPY);
|
||||
@ -124,7 +125,7 @@ function entropyToMnemonic(entropy, wordlist) {
|
||||
const index = binaryToByte(binary);
|
||||
return wordlist[index];
|
||||
});
|
||||
return wordlist === JAPANESE_WORDLIST
|
||||
return wordlist[0] === '\u3042\u3044\u3053\u304f\u3057\u3093' // Japanese wordlist
|
||||
? words.join('\u3000')
|
||||
: words.join(' ');
|
||||
}
|
||||
@ -147,15 +148,23 @@ function validateMnemonic(mnemonic, wordlist) {
|
||||
return true;
|
||||
}
|
||||
exports.validateMnemonic = validateMnemonic;
|
||||
exports.wordlists = {
|
||||
EN: ENGLISH_WORDLIST,
|
||||
JA: JAPANESE_WORDLIST,
|
||||
chinese_simplified: CHINESE_SIMPLIFIED_WORDLIST,
|
||||
chinese_traditional: CHINESE_TRADITIONAL_WORDLIST,
|
||||
english: ENGLISH_WORDLIST,
|
||||
french: FRENCH_WORDLIST,
|
||||
italian: ITALIAN_WORDLIST,
|
||||
japanese: JAPANESE_WORDLIST,
|
||||
korean: KOREAN_WORDLIST,
|
||||
spanish: SPANISH_WORDLIST,
|
||||
};
|
||||
function setDefaultWordlist(language) {
|
||||
const result = _wordlists_1.wordlists[language];
|
||||
if (result)
|
||||
DEFAULT_WORDLIST = result;
|
||||
else
|
||||
throw new Error('Could not find wordlist for language "' + language + '"');
|
||||
}
|
||||
exports.setDefaultWordlist = setDefaultWordlist;
|
||||
function getDefaultWordlist() {
|
||||
if (!DEFAULT_WORDLIST)
|
||||
throw new Error('No Default Wordlist set');
|
||||
return Object.keys(_wordlists_1.wordlists).filter(lang => {
|
||||
if (lang === 'JA' || lang === 'EN')
|
||||
return false;
|
||||
return _wordlists_1.wordlists[lang].every((word, index) => word === DEFAULT_WORDLIST[index]);
|
||||
})[0];
|
||||
}
|
||||
exports.getDefaultWordlist = getDefaultWordlist;
|
||||
var _wordlists_2 = require("./_wordlists");
|
||||
exports.wordlists = _wordlists_2.wordlists;
|
||||
|
@ -34,6 +34,46 @@ vectors.english.forEach(function (v, i) { testVector('English', undefined, 'TREZ
|
||||
vectors.japanese.forEach(function (v, i) { testVector('Japanese', WORDLISTS.japanese, '㍍ガバヴァぱばぐゞちぢ十人十色', v, i) })
|
||||
vectors.custom.forEach(function (v, i) { testVector('Custom', WORDLISTS.custom, undefined, v, i) })
|
||||
|
||||
test('getDefaultWordlist returns "english"', function (t) {
|
||||
t.plan(1)
|
||||
const english = bip39.getDefaultWordlist()
|
||||
t.equal(english, 'english')
|
||||
// TODO: Test that Error throws when called if no wordlists are compiled with bip39
|
||||
})
|
||||
|
||||
test('setDefaultWordlist changes default wordlist', function (t) {
|
||||
t.plan(4)
|
||||
const english = bip39.getDefaultWordlist()
|
||||
t.equal(english, 'english')
|
||||
|
||||
bip39.setDefaultWordlist('italian')
|
||||
|
||||
const italian = bip39.getDefaultWordlist()
|
||||
t.equal(italian, 'italian')
|
||||
|
||||
const phraseItalian = bip39.entropyToMnemonic('00000000000000000000000000000000')
|
||||
t.equal(phraseItalian.slice(0, 5), 'abaco')
|
||||
|
||||
bip39.setDefaultWordlist('english')
|
||||
|
||||
const phraseEnglish = bip39.entropyToMnemonic('00000000000000000000000000000000')
|
||||
t.equal(phraseEnglish.slice(0, 7), 'abandon')
|
||||
})
|
||||
|
||||
test('setDefaultWordlist throws on unknown wordlist', function (t) {
|
||||
t.plan(2)
|
||||
const english = bip39.getDefaultWordlist()
|
||||
t.equal(english, 'english')
|
||||
|
||||
try {
|
||||
bip39.setDefaultWordlist('abcdefghijklmnop')
|
||||
} catch (error) {
|
||||
t.equal(error.message, 'Could not find wordlist for language "abcdefghijklmnop"')
|
||||
return
|
||||
}
|
||||
t.assert(false)
|
||||
})
|
||||
|
||||
test('invalid entropy', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
|
26
ts_src/_wordlists.ts
Normal file
26
ts_src/_wordlists.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// browserify by default only pulls in files that are hard coded in requires
|
||||
// In order of last to first in this file, the default wordlist will be chosen
|
||||
// based on what is present. (Bundles may remove wordlists they don't need)
|
||||
const wordlistFilenames: string[] = [
|
||||
'chinese_simplified',
|
||||
'chinese_traditional',
|
||||
'korean',
|
||||
'french',
|
||||
'italian',
|
||||
'spanish',
|
||||
'japanese',
|
||||
'english', // Last language available in list will be the default.
|
||||
];
|
||||
const wordlists: { [index: string]: string[] } = {};
|
||||
let _default: string[] | undefined;
|
||||
wordlistFilenames.forEach(lang => {
|
||||
try {
|
||||
_default = require('./wordlists/' + lang + '.json');
|
||||
wordlists[lang] = _default as string[];
|
||||
if (lang === 'japanese') wordlists.JA = _default as string[];
|
||||
if (lang === 'english') wordlists.EN = _default as string[];
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
// Last one to overwrite wordlist gets to be default.
|
||||
export { wordlists, _default };
|
@ -1,23 +1,18 @@
|
||||
import createHash = require('create-hash');
|
||||
import * as createHash from 'create-hash';
|
||||
import { pbkdf2 as pbkdf2Async, pbkdf2Sync as pbkdf2 } from 'pbkdf2';
|
||||
import randomBytes = require('randombytes');
|
||||
|
||||
import * as randomBytes from 'randombytes';
|
||||
// use unorm until String.prototype.normalize gets better browser support
|
||||
import unorm = require('unorm');
|
||||
import * as unorm from 'unorm';
|
||||
import { _default as _DEFAULT_WORDLIST, wordlists } from './_wordlists';
|
||||
|
||||
import CHINESE_SIMPLIFIED_WORDLIST = require('./wordlists/chinese_simplified.json');
|
||||
import CHINESE_TRADITIONAL_WORDLIST = require('./wordlists/chinese_traditional.json');
|
||||
import ENGLISH_WORDLIST = require('./wordlists/english.json');
|
||||
import FRENCH_WORDLIST = require('./wordlists/french.json');
|
||||
import ITALIAN_WORDLIST = require('./wordlists/italian.json');
|
||||
import JAPANESE_WORDLIST = require('./wordlists/japanese.json');
|
||||
import KOREAN_WORDLIST = require('./wordlists/korean.json');
|
||||
import SPANISH_WORDLIST = require('./wordlists/spanish.json');
|
||||
const DEFAULT_WORDLIST = ENGLISH_WORDLIST;
|
||||
let DEFAULT_WORDLIST: string[] | undefined = _DEFAULT_WORDLIST;
|
||||
|
||||
const INVALID_MNEMONIC = 'Invalid mnemonic';
|
||||
const INVALID_ENTROPY = 'Invalid entropy';
|
||||
const INVALID_CHECKSUM = 'Invalid mnemonic checksum';
|
||||
const WORDLIST_REQUIRED =
|
||||
'A wordlist is required but a default could not be found.\n' +
|
||||
'Please explicitly pass a 2048 word array explicitly.';
|
||||
|
||||
function lpad(str: string, padString: string, length: number): string {
|
||||
while (str.length < length) str = padString + str;
|
||||
@ -97,6 +92,9 @@ export function mnemonicToEntropy(
|
||||
wordlist?: string[],
|
||||
): string {
|
||||
wordlist = wordlist || DEFAULT_WORDLIST;
|
||||
if (!wordlist) {
|
||||
throw new Error(WORDLIST_REQUIRED);
|
||||
}
|
||||
|
||||
const words = unorm.nfkd(mnemonic).split(' ');
|
||||
if (words.length % 3 !== 0) throw new Error(INVALID_MNEMONIC);
|
||||
@ -135,6 +133,9 @@ export function entropyToMnemonic(
|
||||
): string {
|
||||
if (!Buffer.isBuffer(entropy)) entropy = Buffer.from(entropy, 'hex');
|
||||
wordlist = wordlist || DEFAULT_WORDLIST;
|
||||
if (!wordlist) {
|
||||
throw new Error(WORDLIST_REQUIRED);
|
||||
}
|
||||
|
||||
// 128 <= ENT <= 256
|
||||
if (entropy.length < 16) throw new TypeError(INVALID_ENTROPY);
|
||||
@ -151,7 +152,7 @@ export function entropyToMnemonic(
|
||||
return wordlist![index];
|
||||
});
|
||||
|
||||
return wordlist === JAPANESE_WORDLIST
|
||||
return wordlist[0] === '\u3042\u3044\u3053\u304f\u3057\u3093' // Japanese wordlist
|
||||
? words.join('\u3000')
|
||||
: words.join(' ');
|
||||
}
|
||||
@ -181,16 +182,21 @@ export function validateMnemonic(
|
||||
return true;
|
||||
}
|
||||
|
||||
export const wordlists = {
|
||||
EN: ENGLISH_WORDLIST,
|
||||
JA: JAPANESE_WORDLIST,
|
||||
export function setDefaultWordlist(language: string): void {
|
||||
const result = wordlists[language];
|
||||
if (result) DEFAULT_WORDLIST = result;
|
||||
else
|
||||
throw new Error('Could not find wordlist for language "' + language + '"');
|
||||
}
|
||||
|
||||
chinese_simplified: CHINESE_SIMPLIFIED_WORDLIST,
|
||||
chinese_traditional: CHINESE_TRADITIONAL_WORDLIST,
|
||||
english: ENGLISH_WORDLIST,
|
||||
french: FRENCH_WORDLIST,
|
||||
italian: ITALIAN_WORDLIST,
|
||||
japanese: JAPANESE_WORDLIST,
|
||||
korean: KOREAN_WORDLIST,
|
||||
spanish: SPANISH_WORDLIST,
|
||||
};
|
||||
export function getDefaultWordlist(): string {
|
||||
if (!DEFAULT_WORDLIST) throw new Error('No Default Wordlist set');
|
||||
return Object.keys(wordlists).filter(lang => {
|
||||
if (lang === 'JA' || lang === 'EN') return false;
|
||||
return wordlists[lang].every(
|
||||
(word, index) => word === DEFAULT_WORDLIST![index],
|
||||
);
|
||||
})[0];
|
||||
}
|
||||
|
||||
export { wordlists } from './_wordlists';
|
||||
|
@ -24,7 +24,8 @@
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"ts_src/**/*.ts"
|
||||
"ts_src/**/*.ts",
|
||||
"ts_src/**/*.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
|
5
types/_wordlists.d.ts
vendored
Normal file
5
types/_wordlists.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare const wordlists: {
|
||||
[index: string]: string[];
|
||||
};
|
||||
declare let _default: string[] | undefined;
|
||||
export { wordlists, _default };
|
15
types/index.d.ts
vendored
15
types/index.d.ts
vendored
@ -7,15 +7,6 @@ export declare function mnemonicToEntropy(mnemonic: string, wordlist?: string[])
|
||||
export declare function entropyToMnemonic(entropy: Buffer | string, wordlist?: string[]): string;
|
||||
export declare function generateMnemonic(strength?: number, rng?: (size: number) => Buffer, wordlist?: string[]): string;
|
||||
export declare function validateMnemonic(mnemonic: string, wordlist?: string[]): boolean;
|
||||
export declare const wordlists: {
|
||||
EN: string[];
|
||||
JA: string[];
|
||||
chinese_simplified: string[];
|
||||
chinese_traditional: string[];
|
||||
english: string[];
|
||||
french: string[];
|
||||
italian: string[];
|
||||
japanese: string[];
|
||||
korean: string[];
|
||||
spanish: string[];
|
||||
};
|
||||
export declare function setDefaultWordlist(language: string): void;
|
||||
export declare function getDefaultWordlist(): string;
|
||||
export { wordlists } from './_wordlists';
|
||||
|
5
types/wordlists.d.ts
vendored
Normal file
5
types/wordlists.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare const wordlists: {
|
||||
[index: string]: string[];
|
||||
};
|
||||
declare let _default: string[] | undefined;
|
||||
export { wordlists, _default };
|
Loading…
x
Reference in New Issue
Block a user