adhere to standard

This commit is contained in:
Daniel Cousens 2015-10-02 00:30:01 +10:00
parent 967512dc25
commit 889f233bdd
2 changed files with 72 additions and 71 deletions

View File

@ -9,31 +9,31 @@ var unorm = require('unorm')
var DEFAULT_WORDLIST = require('./wordlists/en.json') var DEFAULT_WORDLIST = require('./wordlists/en.json')
var JAPANESE_WORDLIST = require('./wordlists/ja.json') var JAPANESE_WORDLIST = require('./wordlists/ja.json')
function mnemonicToSeed(mnemonic, password) { function mnemonicToSeed (mnemonic, password) {
var mnemonicBuffer = new Buffer(unorm.nfkd(mnemonic), 'utf8') var mnemonicBuffer = new Buffer(unorm.nfkd(mnemonic), 'utf8')
var saltBuffer = new Buffer(salt(password), 'utf8') var saltBuffer = new Buffer(salt(password), 'utf8')
return pbkdf2(mnemonicBuffer, saltBuffer, 2048, 64, 'sha512') return pbkdf2(mnemonicBuffer, saltBuffer, 2048, 64, 'sha512')
} }
function mnemonicToSeedHex(mnemonic, password) { function mnemonicToSeedHex (mnemonic, password) {
return mnemonicToSeed(mnemonic, password).toString('hex') return mnemonicToSeed(mnemonic, password).toString('hex')
} }
function mnemonicToEntropy(mnemonic, wordlist) { function mnemonicToEntropy (mnemonic, wordlist) {
wordlist = wordlist || DEFAULT_WORDLIST wordlist = wordlist || DEFAULT_WORDLIST
var words = unorm.nfkd(mnemonic).split(' ') var words = unorm.nfkd(mnemonic).split(' ')
assert(words.length % 3 === 0, 'Invalid mnemonic') assert(words.length % 3 === 0, 'Invalid mnemonic')
var belongToList = words.every(function(word) { var belongToList = words.every(function (word) {
return wordlist.indexOf(word) > -1 return wordlist.indexOf(word) > -1
}) })
assert(belongToList, 'Invalid mnemonic') assert(belongToList, 'Invalid mnemonic')
// convert word indices to 11 bit binary strings // convert word indices to 11 bit binary strings
var bits = words.map(function(word) { var bits = words.map(function (word) {
var index = wordlist.indexOf(word) var index = wordlist.indexOf(word)
return lpad(index.toString(2), '0', 11) return lpad(index.toString(2), '0', 11)
}).join('') }).join('')
@ -44,7 +44,7 @@ function mnemonicToEntropy(mnemonic, wordlist) {
var checksum = bits.slice(dividerIndex) var checksum = bits.slice(dividerIndex)
// calculate the checksum and compare // calculate the checksum and compare
var entropyBytes = entropy.match(/(.{1,8})/g).map(function(bin) { var entropyBytes = entropy.match(/(.{1,8})/g).map(function (bin) {
return parseInt(bin, 2) return parseInt(bin, 2)
}) })
var entropyBuffer = new Buffer(entropyBytes) var entropyBuffer = new Buffer(entropyBytes)
@ -55,7 +55,7 @@ function mnemonicToEntropy(mnemonic, wordlist) {
return entropyBuffer.toString('hex') return entropyBuffer.toString('hex')
} }
function entropyToMnemonic(entropy, wordlist) { function entropyToMnemonic (entropy, wordlist) {
wordlist = wordlist || DEFAULT_WORDLIST wordlist = wordlist || DEFAULT_WORDLIST
var entropyBuffer = new Buffer(entropy, 'hex') var entropyBuffer = new Buffer(entropy, 'hex')
@ -65,7 +65,7 @@ function entropyToMnemonic(entropy, wordlist) {
var bits = entropyBits + checksum var bits = entropyBits + checksum
var chunks = bits.match(/(.{1,11})/g) var chunks = bits.match(/(.{1,11})/g)
var words = chunks.map(function(binary) { var words = chunks.map(function (binary) {
var index = parseInt(binary, 2) var index = parseInt(binary, 2)
return wordlist[index] return wordlist[index]
@ -74,7 +74,7 @@ function entropyToMnemonic(entropy, wordlist) {
return wordlist == JAPANESE_WORDLIST ? words.join('\u3000') : words.join(' ') return wordlist == JAPANESE_WORDLIST ? words.join('\u3000') : words.join(' ')
} }
function generateMnemonic(strength, rng, wordlist) { function generateMnemonic (strength, rng, wordlist) {
strength = strength || 128 strength = strength || 128
rng = rng || randomBytes rng = rng || randomBytes
@ -82,7 +82,7 @@ function generateMnemonic(strength, rng, wordlist) {
return entropyToMnemonic(hex, wordlist) return entropyToMnemonic(hex, wordlist)
} }
function validateMnemonic(mnemonic, wordlist) { function validateMnemonic (mnemonic, wordlist) {
try { try {
mnemonicToEntropy(mnemonic, wordlist) mnemonicToEntropy(mnemonic, wordlist)
} catch (e) { } catch (e) {
@ -92,7 +92,7 @@ function validateMnemonic(mnemonic, wordlist) {
return true return true
} }
function checksumBits(entropyBuffer) { function checksumBits (entropyBuffer) {
var hash = createHash('sha256').update(entropyBuffer).digest() var hash = createHash('sha256').update(entropyBuffer).digest()
// Calculated constants from BIP39 // Calculated constants from BIP39
@ -102,21 +102,21 @@ function checksumBits(entropyBuffer) {
return bytesToBinary([].slice.call(hash)).slice(0, CS) return bytesToBinary([].slice.call(hash)).slice(0, CS)
} }
function salt(password) { function salt (password) {
return 'mnemonic' + (unorm.nfkd(password) || '') return 'mnemonic' + (unorm.nfkd(password) || '')
} }
//=========== helper methods from bitcoinjs-lib ======== // =========== helper methods from bitcoinjs-lib ========
function bytesToBinary(bytes) { function bytesToBinary (bytes) {
return bytes.map(function(x) { return bytes.map(function (x) {
return lpad(x.toString(2), '0', 8) return lpad(x.toString(2), '0', 8)
}).join(''); }).join('')
} }
function lpad(str, padString, length) { function lpad (str, padString, length) {
while (str.length < length) str = padString + str; while (str.length < length) str = padString + str
return str; return str
} }
module.exports = { module.exports = {

View File

@ -1,7 +1,9 @@
/* global describe it */
var assert = require('assert') var assert = require('assert')
var mock = require('mock-require') var mock = require('mock-require')
mock('randombytes', function(size) { mock('randombytes', function (size) {
return new Buffer('qwertyuiopasdfghjklzxcvbnm[];,./'.slice(0, size)) return new Buffer('qwertyuiopasdfghjklzxcvbnm[];,./'.slice(0, size))
}) })
@ -15,79 +17,79 @@ var wordlists = {
var vectors = require('./vectors.json') var vectors = require('./vectors.json')
describe('BIP39', function() { describe('BIP39', function () {
describe('mnemonicToSeedHex', function() { describe('mnemonicToSeedHex', function () {
this.timeout(20000) this.timeout(20000)
vectors.english.forEach(function(v, i) { vectors.english.forEach(function (v, i) {
it('works for tests vector ' + i, function() { it('works for tests vector ' + i, function () {
assert.equal(BIP39.mnemonicToSeedHex(v[1], 'TREZOR'), v[2]) assert.equal(BIP39.mnemonicToSeedHex(v[1], 'TREZOR'), v[2])
}) })
}) })
}) })
describe('mnemonicToEntropy', function() { describe('mnemonicToEntropy', function () {
vectors.english.forEach(function(v, i) { vectors.english.forEach(function (v, i) {
it('works for tests vector ' + i, function() { it('works for tests vector ' + i, function () {
assert.equal(BIP39.mnemonicToEntropy(v[1]), v[0]) assert.equal(BIP39.mnemonicToEntropy(v[1]), v[0])
}) })
}) })
vectors.japanese.forEach(function(v, i) { vectors.japanese.forEach(function (v, i) {
it('works for japanese tests vector ' + i, function() { it('works for japanese tests vector ' + i, function () {
assert.equal(BIP39.mnemonicToEntropy(v[1], wordlists.japanese), v[0]) assert.equal(BIP39.mnemonicToEntropy(v[1], wordlists.japanese), v[0])
}) })
}) })
vectors.custom.forEach(function(v, i) { vectors.custom.forEach(function (v, i) {
it('works for custom test vector ' + i, function() { it('works for custom test vector ' + i, function () {
assert.equal(BIP39.mnemonicToEntropy(v[1], wordlists.custom), v[0]) assert.equal(BIP39.mnemonicToEntropy(v[1], wordlists.custom), v[0])
}) })
}) })
}) })
describe('entropyToMnemonic', function() { describe('entropyToMnemonic', function () {
vectors.english.forEach(function(v, i) { vectors.english.forEach(function (v, i) {
it('works for tests vector ' + i, function() { it('works for tests vector ' + i, function () {
assert.equal(BIP39.entropyToMnemonic(v[0]), v[1]) assert.equal(BIP39.entropyToMnemonic(v[0]), v[1])
}) })
}) })
vectors.japanese.forEach(function(v, i) { vectors.japanese.forEach(function (v, i) {
it('works for japanese test vector ' + i, function() { it('works for japanese test vector ' + i, function () {
assert.equal(BIP39.entropyToMnemonic(v[0], wordlists.japanese), v[1]) assert.equal(BIP39.entropyToMnemonic(v[0], wordlists.japanese), v[1])
}) })
}) })
vectors.custom.forEach(function(v, i) { vectors.custom.forEach(function (v, i) {
it('works for custom test vector ' + i, function() { it('works for custom test vector ' + i, function () {
assert.equal(BIP39.entropyToMnemonic(v[0], wordlists.custom), v[1]) assert.equal(BIP39.entropyToMnemonic(v[0], wordlists.custom), v[1])
}) })
}) })
}) })
describe('generateMnemonic', function() { describe('generateMnemonic', function () {
vectors.english.forEach(function(v, i) { vectors.english.forEach(function (v, i) {
it('works for tests vector ' + i, function() { it('works for tests vector ' + i, function () {
function rng() { return new Buffer(v[0], 'hex') } function rng () { return new Buffer(v[0], 'hex') }
assert.equal(BIP39.generateMnemonic(undefined, rng), v[1]) assert.equal(BIP39.generateMnemonic(undefined, rng), v[1])
}) })
}) })
it('can vary generated entropy bit length', function() { it('can vary generated entropy bit length', function () {
var mnemonic = BIP39.generateMnemonic(96) var mnemonic = BIP39.generateMnemonic(96)
var words = mnemonic.split(' ') var words = mnemonic.split(' ')
assert.equal(words.length, 9) assert.equal(words.length, 9)
}) })
it('defaults to randombytes for the RNG', function() { it('defaults to randombytes for the RNG', function () {
assert.equal(BIP39.generateMnemonic(32), 'imitate robot frequent') assert.equal(BIP39.generateMnemonic(32), 'imitate robot frequent')
}) })
it('allows a custom RNG to be used', function() { it('allows a custom RNG to be used', function () {
var rng = function(size) { var rng = function (size) {
var buffer = new Buffer(size) var buffer = new Buffer(size)
buffer.fill(4) // guaranteed random buffer.fill(4) // guaranteed random
return buffer return buffer
@ -97,8 +99,8 @@ describe('BIP39', function() {
assert.equal(mnemonic, 'advice cage absurd amount doctor act') assert.equal(mnemonic, 'advice cage absurd amount doctor act')
}) })
it('adheres to a custom wordlist', function() { it('adheres to a custom wordlist', function () {
var rng = function(size) { var rng = function (size) {
var buffer = new Buffer(size) var buffer = new Buffer(size)
buffer.fill(4) // guaranteed random buffer.fill(4) // guaranteed random
return buffer return buffer
@ -109,52 +111,50 @@ describe('BIP39', function() {
}) })
}) })
describe('validateMnemonic', function() { describe('validateMnemonic', function () {
vectors.english.forEach(function(v, i) { vectors.english.forEach(function (v, i) {
it('passes check ' + i, function () {
it('passes check ' + i, function() {
assert(BIP39.validateMnemonic(v[1])) assert(BIP39.validateMnemonic(v[1]))
}) })
}) })
describe('with a custom wordlist', function() { describe('with a custom wordlist', function () {
vectors.custom.forEach(function(v, i) { vectors.custom.forEach(function (v, i) {
it('passes custom check ' + i, function () {
it('passes custom check ' + i, function() {
assert(BIP39.validateMnemonic(v[1], wordlists.custom)) assert(BIP39.validateMnemonic(v[1], wordlists.custom))
}) })
}) })
}) })
it('fails for mnemonics of wrong length', function() { it('fails for mnemonics of wrong length', function () {
assert(!BIP39.validateMnemonic('sleep kitten')) assert(!BIP39.validateMnemonic('sleep kitten'))
assert(!BIP39.validateMnemonic('sleep kitten sleep kitten sleep kitten')) assert(!BIP39.validateMnemonic('sleep kitten sleep kitten sleep kitten'))
}) })
it('fails for mnemonics that contains words not from the word list', function() { it('fails for mnemonics that contains words not from the word list', function () {
assert(!BIP39.validateMnemonic("turtle front uncle idea crush write shrug there lottery flower risky shell")) assert(!BIP39.validateMnemonic('turtle front uncle idea crush write shrug there lottery flower risky shell'))
}) })
it('fails for mnemonics of invalid checksum', function() { it('fails for mnemonics of invalid checksum', function () {
assert(!BIP39.validateMnemonic('sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten')) assert(!BIP39.validateMnemonic('sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten'))
}) })
}) })
describe('utf8 passwords', function() { describe('utf8 passwords', function () {
vectors.japanese.forEach(function(v) { vectors.japanese.forEach(function (v) {
it ('creates the correct seed', function() { it('creates the correct seed', function () {
var utf8Password = "㍍ガバヴァぱばぐゞちぢ十人十色" var utf8Password = '㍍ガバヴァぱばぐゞちぢ十人十色'
assert.equal(BIP39.mnemonicToSeedHex(v[1], utf8Password), v[2]) assert.equal(BIP39.mnemonicToSeedHex(v[1], utf8Password), v[2])
}) })
it ('works with already normalized password', function() { it('works with already normalized password', function () {
var normalizedPassword = "メートルガバヴァぱばぐゞちぢ十人十色" var normalizedPassword = 'メートルガバヴァぱばぐゞちぢ十人十色'
assert.equal(BIP39.mnemonicToSeedHex(v[1], normalizedPassword), v[2]) assert.equal(BIP39.mnemonicToSeedHex(v[1], normalizedPassword), v[2])
}) })
}) })
}) })
describe('Examples in readme', function() { describe('Examples in readme', function () {
var bip39 = BIP39 var bip39 = BIP39
var mnemonic = bip39.entropyToMnemonic('133755ff') // hex input, defaults to BIP39 English word list var mnemonic = bip39.entropyToMnemonic('133755ff') // hex input, defaults to BIP39 English word list
@ -167,15 +167,15 @@ describe('BIP39', function() {
// Generate a random mnemonic using crypto.randomBytes // Generate a random mnemonic using crypto.randomBytes
mnemonic = bip39.generateMnemonic() // strength defaults to 128 bits mnemonic = bip39.generateMnemonic() // strength defaults to 128 bits
//'bench maximum balance appear cousin negative muscle inform enjoy chief vocal hello' // 'bench maximum balance appear cousin negative muscle inform enjoy chief vocal hello'
assert.ok(/^(\w+ ){11}\w+$/.test(mnemonic)) assert.ok(/^(\w+ ){11}\w+$/.test(mnemonic))
var str = bip39.mnemonicToSeedHex('basket actual') var str = bip39.mnemonicToSeedHex('basket actual')
//'5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f' // '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f'
assert.equal(str, '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f') assert.equal(str, '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f')
var buff = bip39.mnemonicToSeed('basket actual') var buff = bip39.mnemonicToSeed('basket actual')
var fiveC = 5*16+12 var fiveC = 5 * 16 + 12
assert.equal(buff[0], fiveC) assert.equal(buff[0], fiveC)
// <Buffer 5c f2 d4 a8 b0 35 5e 90 29 5b df c5 65 a0 22 a4 09 af 06 3d 53 65 bb 57 bf 74 d9 52 8f 49 4b fa 44 00 f5 3d 83 49 b8 0f da e4 40 82 d7 f9 54 1e 1d ba 2b ...> // <Buffer 5c f2 d4 a8 b0 35 5e 90 29 5b df c5 65 a0 22 a4 09 af 06 3d 53 65 bb 57 bf 74 d9 52 8f 49 4b fa 44 00 f5 3d 83 49 b8 0f da e4 40 82 d7 f9 54 1e 1d ba 2b ...>
@ -184,8 +184,9 @@ describe('BIP39', function() {
assert.ok(bool) assert.ok(bool)
bool = bip39.validateMnemonic('basket actual') bool = bip39.validateMnemonic('basket actual')
// false // false
assert.ok(! bool) assert.ok(!bool)
}) })
it('exposes standard wordlists', function () { it('exposes standard wordlists', function () {