mirror of https://github.com/status-im/bip39.git
adhere to standard
This commit is contained in:
parent
967512dc25
commit
889f233bdd
38
index.js
38
index.js
|
@ -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 = {
|
||||||
|
|
105
test/index.js
105
test/index.js
|
@ -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 () {
|
||||||
|
|
Loading…
Reference in New Issue