BIP39: allow for custom wordlists

This commit is contained in:
Daniel Cousens 2014-07-04 14:33:49 +10:00
parent 32d8270b87
commit 8e6380367d
5 changed files with 2175 additions and 69 deletions

View File

@ -2,13 +2,10 @@ var CryptoJS = require('crypto-js')
var crypto = require('crypto') var crypto = require('crypto')
var secureRandom = require('secure-random') var secureRandom = require('secure-random')
var includeFolder = require('include-folder') var DEFAULT_WORDLIST = require('./wordlists/en.json')
var path = require('path')
var wordlists = includeFolder(path.join(__dirname, 'wordlists'))
function BIP39(language) { function BIP39(wordlist) {
language = language || 'en' this.wordlist = wordlist || DEFAULT_WORDLIST
this.wordlist = JSON.parse(wordlists[language])
} }
BIP39.prototype.mnemonicToSeed = function(mnemonic, password) { BIP39.prototype.mnemonicToSeed = function(mnemonic, password) {
@ -38,7 +35,7 @@ BIP39.prototype.generateMnemonic = function(strength, rng) {
rng = rng || secureRandom.randomBuffer rng = rng || secureRandom.randomBuffer
var hex = rng(strength / 8).toString('hex') var hex = rng(strength / 8).toString('hex')
return this.entropyToMnemonic(hex) return this.entropyToMnemonic(hex, this.wordlist)
} }
BIP39.prototype.validate = function(mnemonic) { BIP39.prototype.validate = function(mnemonic) {

View File

@ -15,18 +15,10 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"crypto-js": "^3.1.2-2", "crypto-js": "^3.1.2-2",
"require-json-tree": "~1.1.0",
"include-folder": "~0.7.0",
"secure-random": "1.0.0" "secure-random": "1.0.0"
}, },
"devDependencies": { "devDependencies": {
"browserify": "^5.9.1", "browserify": "^5.9.1",
"folderify": "~0.6.0",
"mocha": "^1.17.1" "mocha": "^1.17.1"
},
"browserify": {
"transform": [
"folderify"
]
} }
} }

View File

@ -1,69 +1,124 @@
var assert = require('assert') var assert = require('assert')
var wordlist = require('../wordlists/en.json')
var vectors = require('./vectors.json').english
var BIP39 = require('../index.js') var BIP39 = require('../index.js')
var bip39 = new BIP39()
describe('constructor', function() { var wordlists = {
it('defaults language to english', function() { english: require('../wordlists/en.json'),
assert.deepEqual(bip39.wordlist, wordlist) custom: require('./wordlist.json')
}) }
})
describe('mnemonicToSeed', function() { var vectors = require('./vectors.json')
vectors.forEach(function(v, i) {
it('works for tests vector ' + i, function() { describe('BIP39', function() {
assert.equal(bip39.mnemonicToSeed(v[1], 'TREZOR'), v[2]) describe('constructor', function() {
it('defaults language to english', function() {
var bip39 = new BIP39()
assert.deepEqual(bip39.wordlist, wordlists.english)
}) })
})
})
describe('entropyToMnemonic', function() { it('accepts a custom wordlist', function() {
vectors.forEach(function(v, i) { var bip39 = new BIP39(wordlists.custom)
it('works for tests vector ' + i, function() { assert.deepEqual(bip39.wordlist, wordlists.custom)
assert.equal(bip39.entropyToMnemonic(v[0]), v[1])
})
})
})
describe('generateMnemonic', function() {
it('generates a mnemonic', function() {
var mnemonic = bip39.generateMnemonic(96)
var words = mnemonic.split(' ')
assert.equal(words.length, 9)
})
it('allows a custom RNG to be used', function() {
var rng = function(size) {
var buffer = new Buffer(size)
buffer.fill(4) // guaranteed random
return buffer
}
var mnemonic = bip39.generateMnemonic(64, rng)
assert.equal(mnemonic, 'advice cage absurd amount doctor act')
})
})
describe('validate', function() {
vectors.forEach(function(v, i) {
it('passes check ' + i, function() {
assert(bip39.validate(v[1]))
}) })
}) })
it('fails for mnemonics of wrong length', function() { describe('mnemonicToSeed', function() {
assert(!bip39.validate('sleep kitten')) this.timeout(20000)
assert(!bip39.validate('sleep kitten sleep kitten sleep kitten'))
var bip39
beforeEach(function() {
bip39 = new BIP39()
})
vectors.english.forEach(function(v, i) {
it('works for tests vector ' + i, function() {
assert.equal(bip39.mnemonicToSeed(v[1], 'TREZOR'), v[2])
})
})
}) })
it('fails for mnemonics that contains words not from the word list', function() { describe('entropyToMnemonic', function() {
assert(!bip39.validate("turtle front uncle idea crush write shrug there lottery flower risky shell")) vectors.english.forEach(function(v, i) {
it('works for tests vector ' + i, function() {
var bip39 = new BIP39()
assert.equal(bip39.entropyToMnemonic(v[0]), v[1])
})
})
vectors.custom.forEach(function(v, i) {
it('works for custom test vector ' + i, function() {
var bip39 = new BIP39(wordlists.custom)
assert.equal(bip39.entropyToMnemonic(v[0]), v[1])
})
})
}) })
it('fails for mnemonics of invalid checksum', function() { describe('generateMnemonic', function() {
assert(!bip39.validate('sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten')) it('generates a mnemonic', function() {
var bip39 = new BIP39()
var mnemonic = bip39.generateMnemonic(96)
var words = mnemonic.split(' ')
assert.equal(words.length, 9)
})
it('allows a custom RNG to be used', function() {
var rng = function(size) {
var buffer = new Buffer(size)
buffer.fill(4) // guaranteed random
return buffer
}
var bip39 = new BIP39()
var mnemonic = bip39.generateMnemonic(64, rng)
assert.equal(mnemonic, 'advice cage absurd amount doctor act')
})
it('adheres to a custom wordlist', function() {
var rng = function(size) {
var buffer = new Buffer(size)
buffer.fill(4) // guaranteed random
return buffer
}
var bip39 = new BIP39(wordlists.custom)
var mnemonic = bip39.generateMnemonic(64, rng)
assert.equal(mnemonic, 'adv1c3 cag3 ab5urd am0unt d0ct0r act')
})
})
describe('validate', function() {
vectors.english.forEach(function(v, i) {
var bip39 = new BIP39()
it('passes check ' + i, function() {
assert(bip39.validate(v[1]))
})
})
describe('with a custom wordlist', function() {
vectors.custom.forEach(function(v, i) {
var bip39 = new BIP39(wordlists.custom)
it('passes custom check ' + i, function() {
assert(bip39.validate(v[1]))
})
})
})
it('fails for mnemonics of wrong length', function() {
var bip39 = new BIP39()
assert(!bip39.validate('sleep kitten'))
assert(!bip39.validate('sleep kitten sleep kitten sleep kitten'))
})
it('fails for mnemonics that contains words not from the word list', function() {
var bip39 = new BIP39()
assert(!bip39.validate("turtle front uncle idea crush write shrug there lottery flower risky shell"))
})
it('fails for mnemonics of invalid checksum', function() {
var bip39 = new BIP39()
assert(!bip39.validate('sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten'))
})
}) })
}) })

View File

@ -120,5 +120,17 @@
"beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut", "beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut",
"b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd" "b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd"
] ]
],
"custom": [
[
"00000000000000000000000000000000",
"aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n ab0ut",
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"
],
[
"15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419",
"b3y0nd 5tag3 5l33p cl1p b3cau53 tw15t t0k3n l3af at0m b3auty g3n1u5 f00d bu51n355 51d3 gr1d unabl3 m1ddl3 arm3d 0b53rv3 pa1r cr0uch t0n1ght away c0c0nut",
"b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd"
]
] ]
} }

2050
test/wordlist.json Normal file

File diff suppressed because it is too large Load Diff