From c30b47b63d866f30351b83268e14059d82131aef Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 26 Mar 2019 11:58:52 +0900 Subject: [PATCH] Allow excluding wordlists when building for browserify --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++ src/_wordlists.js | 51 +++++++++++++++++++++++++++++++++++++++ src/index.js | 41 +++++++++++++++---------------- ts_src/_wordlists.ts | 42 ++++++++++++++++++++++++++++++++ ts_src/index.ts | 50 +++++++++++++++++--------------------- tsconfig.json | 3 ++- types/_wordlists.d.ts | 5 ++++ types/index.d.ts | 14 ++--------- types/wordlists.d.ts | 5 ++++ 9 files changed, 204 insertions(+), 63 deletions(-) create mode 100644 src/_wordlists.js create mode 100644 ts_src/_wordlists.ts create mode 100644 types/_wordlists.d.ts create mode 100644 types/wordlists.d.ts diff --git a/README.md b/README.md index 6053a50..50d6858 100644 --- a/README.md +++ b/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 diff --git a/src/_wordlists.js b/src/_wordlists.js new file mode 100644 index 0000000..9f2f8b2 --- /dev/null +++ b/src/_wordlists.js @@ -0,0 +1,51 @@ +"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 wordlists = {}; +exports.wordlists = wordlists; +let _default; +exports._default = _default; +try { + exports._default = _default = require('./wordlists/chinese_simplified.json'); + wordlists.chinese_simplified = _default; +} +catch (err) { } +try { + exports._default = _default = require('./wordlists/chinese_traditional.json'); + wordlists.chinese_traditional = _default; +} +catch (err) { } +try { + exports._default = _default = require('./wordlists/korean.json'); + wordlists.korean = _default; +} +catch (err) { } +try { + exports._default = _default = require('./wordlists/french.json'); + wordlists.french = _default; +} +catch (err) { } +try { + exports._default = _default = require('./wordlists/italian.json'); + wordlists.italian = _default; +} +catch (err) { } +try { + exports._default = _default = require('./wordlists/spanish.json'); + wordlists.spanish = _default; +} +catch (err) { } +try { + exports._default = _default = require('./wordlists/japanese.json'); + wordlists.japanese = _default; + wordlists.JA = _default; +} +catch (err) { } +try { + exports._default = _default = require('./wordlists/english.json'); + wordlists.english = _default; + wordlists.EN = _default; +} +catch (err) { } diff --git a/src/index.js b/src/index.js index 61a1952..51aa2dd 100644 --- a/src/index.js +++ b/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,11 @@ 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; +} +exports.setDefaultWordlist = setDefaultWordlist; +var _wordlists_2 = require("./_wordlists"); +exports.wordlists = _wordlists_2.wordlists; diff --git a/ts_src/_wordlists.ts b/ts_src/_wordlists.ts new file mode 100644 index 0000000..746467d --- /dev/null +++ b/ts_src/_wordlists.ts @@ -0,0 +1,42 @@ +// 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 wordlists: { [index: string]: string[] } = {}; +let _default: string[] | undefined; +try { + _default = require('./wordlists/chinese_simplified.json'); + wordlists.chinese_simplified = _default as string[]; +} catch (err) {} +try { + _default = require('./wordlists/chinese_traditional.json'); + wordlists.chinese_traditional = _default as string[]; +} catch (err) {} +try { + _default = require('./wordlists/korean.json'); + wordlists.korean = _default as string[]; +} catch (err) {} +try { + _default = require('./wordlists/french.json'); + wordlists.french = _default as string[]; +} catch (err) {} +try { + _default = require('./wordlists/italian.json'); + wordlists.italian = _default as string[]; +} catch (err) {} +try { + _default = require('./wordlists/spanish.json'); + wordlists.spanish = _default as string[]; +} catch (err) {} +try { + _default = require('./wordlists/japanese.json'); + wordlists.japanese = _default as string[]; + wordlists.JA = _default as string[]; +} catch (err) {} +try { + _default = require('./wordlists/english.json'); + wordlists.english = _default as string[]; + wordlists.EN = _default as string[]; +} catch (err) {} + +// Last one to overwrite wordlist gets to be default. +export { wordlists, _default }; diff --git a/ts_src/index.ts b/ts_src/index.ts index b686150..62dbb66 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -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 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; +import * as unorm from 'unorm'; +import { _default as _DEFAULT_WORDLIST, wordlists } from './_wordlists'; + +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,9 @@ export function validateMnemonic( return true; } -export const 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, -}; +export function setDefaultWordlist(language: string): void { + const result = wordlists[language]; + if (result) DEFAULT_WORDLIST = result; +} + +export { wordlists } from './_wordlists'; diff --git a/tsconfig.json b/tsconfig.json index 00de8d5..5de6a74 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,8 @@ "resolveJsonModule": true }, "include": [ - "ts_src/**/*.ts" + "ts_src/**/*.ts", + "ts_src/**/*.json" ], "exclude": [ "**/*.spec.ts", diff --git a/types/_wordlists.d.ts b/types/_wordlists.d.ts new file mode 100644 index 0000000..24269f9 --- /dev/null +++ b/types/_wordlists.d.ts @@ -0,0 +1,5 @@ +declare const wordlists: { + [index: string]: string[]; +}; +declare let _default: string[] | undefined; +export { wordlists, _default }; diff --git a/types/index.d.ts b/types/index.d.ts index 5d248f3..4bdf33a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -7,15 +7,5 @@ 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 { wordlists } from './_wordlists'; diff --git a/types/wordlists.d.ts b/types/wordlists.d.ts new file mode 100644 index 0000000..24269f9 --- /dev/null +++ b/types/wordlists.d.ts @@ -0,0 +1,5 @@ +declare const wordlists: { + [index: string]: string[]; +}; +declare let _default: string[] | undefined; +export { wordlists, _default };