Skip to content

Commit

Permalink
Allow excluding wordlists when building for browserify
Browse files Browse the repository at this point in the history
  • Loading branch information
junderw committed Mar 26, 2019
1 parent 6dedef0 commit c30b47b
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 63 deletions.
56 changes: 56 additions & 0 deletions README.md
Expand Up @@ -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
Expand Down
51 changes: 51 additions & 0 deletions 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) { }
41 changes: 19 additions & 22 deletions src/index.js
Expand Up @@ -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.';

This comment has been minimized.

Copy link
@ccsalway

ccsalway Apr 27, 2021

Repeated the word "explicitly"

function lpad(str, padString, length) {
while (str.length < length)
str = padString + str;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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(' ');
}
Expand All @@ -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;
42 changes: 42 additions & 0 deletions 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 };
50 changes: 22 additions & 28 deletions 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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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(' ');
}
Expand Down Expand Up @@ -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';
3 changes: 2 additions & 1 deletion tsconfig.json
Expand Up @@ -24,7 +24,8 @@
"resolveJsonModule": true
},
"include": [
"ts_src/**/*.ts"
"ts_src/**/*.ts",
"ts_src/**/*.json"
],
"exclude": [
"**/*.spec.ts",
Expand Down
5 changes: 5 additions & 0 deletions types/_wordlists.d.ts
@@ -0,0 +1,5 @@
declare const wordlists: {
[index: string]: string[];
};
declare let _default: string[] | undefined;
export { wordlists, _default };
14 changes: 2 additions & 12 deletions types/index.d.ts
Expand Up @@ -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';
5 changes: 5 additions & 0 deletions types/wordlists.d.ts
@@ -0,0 +1,5 @@
declare const wordlists: {
[index: string]: string[];
};
declare let _default: string[] | undefined;
export { wordlists, _default };

0 comments on commit c30b47b

Please sign in to comment.