Browse Source

Merge pull request #36 from bitcoinjs/smallfixes

Various package updates
300
Daniel Cousens 8 years ago
committed by GitHub
parent
commit
95122ca9fa
  1. 3
      .gitignore
  2. 43
      README.md
  3. 20
      index.js
  4. 16
      package.json
  5. 260
      test/index.js
  6. 4
      test/vectors.json

3
.gitignore

@ -1,2 +1,3 @@
node_modules
bip39.js
.nyc_output/
npm-debug.log

43
README.md

@ -16,41 +16,36 @@ 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`
## Usage
`npm install bip39`
```javascript
var bip39 = require('bip39')
var mnemonic = bip39.entropyToMnemonic('133755ff') // hex input, defaults to BIP39 English word list
// 'basket rival lemon'
bip39.mnemonicToEntropy(mnemonic) // hex input, defaults to BIP39 English word list
// '133755ff'
// Generate a random mnemonic using crypto.randomBytes
mnemonic = bip39.generateMnemonic() // strength defaults to 128 bits
// 'seed sock milk update focus rotate barely fade car face mechanic mercy'
## Examples
``` js
// Generate a random mnemonic (uses crypto.randomBytes under the hood), defaults to 128-bits of entropy
var mnemonic = bip39.generateMnemonic()
// => 'seed sock milk update focus rotate barely fade car face mechanic mercy'
bip39.mnemonicToSeedHex('basket actual')
// '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f'
// => '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f'
bip39.mnemonicToSeed('basket actual')
// <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 ...>
bip39.validateMnemonic(mnemonic)
// true
// => true
bip39.validateMnemonic('basket actual')
// false
// => false
```
### Browser
Compile `bip39.js` with the following command:
``` js
var bip39 = require('bip39')
$ npm run compile
// defaults to BIP39 English word list
// uses HEX strings for entropy
var mnemonic = bip39.entropyToMnemonic('133755ff')
// => basket rival lemon
After loading this file in your browser, you will be able to use the global `bip39` object.
// reversible
bip39.mnemonicToEntropy(mnemonic)
// => '133755ff'
```

20
index.js

@ -1,4 +1,3 @@
var assert = require('assert')
var createHash = require('create-hash')
var pbkdf2 = require('pbkdf2').pbkdf2Sync
var randomBytes = require('randombytes')
@ -28,13 +27,10 @@ function mnemonicToEntropy (mnemonic, wordlist) {
wordlist = wordlist || DEFAULT_WORDLIST
var words = unorm.nfkd(mnemonic).split(' ')
assert(words.length % 3 === 0, 'Invalid mnemonic')
var belongToList = words.every(function (word) {
return wordlist.indexOf(word) > -1
})
assert(belongToList, 'Invalid mnemonic')
if (words.length % 3 !== 0) throw new Error('Invalid mnemonic')
if (words.some(function (word) {
return wordlist.indexOf(word) === -1
})) throw new Error('Invalid mnemonic')
// convert word indices to 11 bit binary strings
var bits = words.map(function (word) {
@ -57,12 +53,14 @@ function mnemonicToEntropy (mnemonic, wordlist) {
// recreate properly chunked and padded bits to get the properly padded checksum
var bits2 = (entropy + newChecksum).match(/(.{1,11})/g).map(function (index) {
return lpad(index, '0', 11)
}).join('')
var dividerIndex2 = Math.floor(bits2.length / 33) * 32
var newChecksum2 = bits2.slice(dividerIndex2)
assert(newChecksum2 === checksum, 'Invalid mnemonic checksum')
if (newChecksum2 !== checksum) {
throw new Error('Invalid mnemonic checksum')
}
return entropyBuffer.toString('hex')
}
@ -83,7 +81,7 @@ function entropyToMnemonic (entropy, wordlist) {
return wordlist[index]
})
return wordlist == JAPANESE_WORDLIST ? words.join('\u3000') : words.join(' ')
return wordlist === JAPANESE_WORDLIST ? words.join('\u3000') : words.join(' ')
}
function generateMnemonic (strength, rng, wordlist) {

16
package.json

@ -4,8 +4,10 @@
"description": "Bitcoin BIP39: Mnemonic code for generating deterministic keys",
"main": "index.js",
"scripts": {
"test": "mocha --reporter list test/*.js",
"compile": "browserify index.js -s bip39 > bip39.js"
"coverage": "nyc --branches 100 --functions 100 --check-coverage npm run unit",
"standard": "standard",
"test": "npm run standard && npm run unit",
"unit": "tape test/*.js"
},
"author": "Wei Lu",
"contributors": [
@ -22,14 +24,14 @@
"license": "ISC",
"dependencies": {
"create-hash": "^1.1.0",
"pbkdf2": "^3.0.0",
"pbkdf2": "^3.0.9",
"randombytes": "^2.0.1",
"unorm": "^1.3.3"
},
"devDependencies": {
"browserify": "^9.0.0",
"mocha": "^2.2.0",
"mock-require": "^1.0.5",
"standard": "*"
"nyc": "^8.3.0",
"proxyquire": "^1.7.10",
"standard": "*",
"tape": "^4.6.2"
}
}

260
test/index.js

@ -1,196 +1,118 @@
/* global describe it */
var assert = require('assert')
var mock = require('mock-require')
mock('randombytes', function (size) {
return new Buffer('qwertyuiopasdfghjklzxcvbnm[];,./'.slice(0, size))
})
var BIP39 = require('../index.js')
var wordlists = {
var bip39 = require('../')
var proxyquire = require('proxyquire')
var WORDLISTS = {
english: require('../wordlists/en.json'),
japanese: require('../wordlists/ja.json'),
custom: require('./wordlist.json')
}
var vectors = require('./vectors.json')
var test = require('tape')
describe('BIP39', function () {
describe('mnemonicToSeedHex', function () {
this.timeout(20000)
function testVector (description, wordlist, password, v, i) {
var ventropy = v[0]
var vmnemonic = v[1]
var vseedHex = v[2]
vectors.english.forEach(function (v, i) {
it('works for tests vector ' + i, function () {
assert.equal(BIP39.mnemonicToSeedHex(v[1], 'TREZOR'), v[2])
})
})
})
test('for ' + description + ' test vector ' + i, function (t) {
t.plan(5)
describe('mnemonicToEntropy', function () {
vectors.english.forEach(function (v, i) {
it('works for tests vector ' + i, function () {
assert.equal(BIP39.mnemonicToEntropy(v[1]), v[0])
})
})
vectors.japanese.forEach(function (v, i) {
it('works for japanese tests vector ' + i, function () {
assert.equal(BIP39.mnemonicToEntropy(v[1], wordlists.japanese), v[0])
})
})
vectors.custom.forEach(function (v, i) {
it('works for custom test vector ' + i, function () {
assert.equal(BIP39.mnemonicToEntropy(v[1], wordlists.custom), v[0])
})
})
})
t.equal(bip39.mnemonicToEntropy(vmnemonic, wordlist), ventropy, 'mnemonicToEntropy returns ' + ventropy.slice(0, 40) + '...')
t.equal(bip39.mnemonicToSeedHex(vmnemonic, password), vseedHex, 'mnemonicToSeedHex returns ' + vseedHex.slice(0, 40) + '...')
describe('entropyToMnemonic', function () {
vectors.english.forEach(function (v, i) {
it('works for tests vector ' + i, function () {
assert.equal(BIP39.entropyToMnemonic(v[0]), v[1])
})
})
vectors.japanese.forEach(function (v, i) {
it('works for japanese test vector ' + i, function () {
assert.equal(BIP39.entropyToMnemonic(v[0], wordlists.japanese), v[1])
})
})
vectors.custom.forEach(function (v, i) {
it('works for custom test vector ' + i, function () {
assert.equal(BIP39.entropyToMnemonic(v[0], wordlists.custom), v[1])
})
})
})
t.equal(bip39.entropyToMnemonic(ventropy, wordlist), vmnemonic, 'entropyToMnemonic returns ' + vmnemonic.slice(0, 40) + '...')
describe('generateMnemonic', function () {
vectors.english.forEach(function (v, i) {
it('works for tests vector ' + i, function () {
function rng () { return new Buffer(v[0], 'hex') }
assert.equal(BIP39.generateMnemonic(undefined, rng), v[1])
})
})
it('can vary generated entropy bit length', function () {
var mnemonic = BIP39.generateMnemonic(96)
var words = mnemonic.split(' ')
assert.equal(words.length, 9)
})
it('defaults to randombytes for the RNG', function () {
assert.equal(BIP39.generateMnemonic(32), 'imitate robot frequent')
})
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')
})
it('adheres to a custom wordlist', function () {
var rng = function (size) {
var buffer = new Buffer(size)
buffer.fill(4) // guaranteed random
return buffer
}
var mnemonic = BIP39.generateMnemonic(64, rng, wordlists.custom)
assert.equal(mnemonic, 'adv1c3 cag3 ab5urd am0unt d0ct0r act')
})
function rng () { return new Buffer(ventropy, 'hex') }
t.equal(bip39.generateMnemonic(undefined, rng, wordlist), vmnemonic, 'generateMnemonic returns RNG entropy unmodified')
t.equal(bip39.validateMnemonic(vmnemonic, wordlist), true, 'validateMnemonic returns true')
})
}
describe('validateMnemonic', function () {
vectors.english.forEach(function (v, i) {
it('passes check ' + i, function () {
assert(BIP39.validateMnemonic(v[1]))
})
})
describe('with a custom wordlist', function () {
vectors.custom.forEach(function (v, i) {
it('passes custom check ' + i, function () {
assert(BIP39.validateMnemonic(v[1], wordlists.custom))
})
})
})
it('fails for mnemonics of wrong length', function () {
assert(!BIP39.validateMnemonic('sleep kitten'))
assert(!BIP39.validateMnemonic('sleep kitten sleep kitten sleep kitten'))
})
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'))
})
it('fails for mnemonics of invalid checksum', function () {
assert(!BIP39.validateMnemonic('sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten'))
})
})
vectors.english.forEach(function (v, i) { testVector('English', undefined, 'TREZOR', v, i) })
vectors.japanese.forEach(function (v, i) { testVector('Japanese', WORDLISTS.japanese, '㍍ガバヴァぱばぐゞちぢ十人十色', v, i) })
vectors.custom.forEach(function (v, i) { testVector('Custom', WORDLISTS.custom, undefined, v, i) })
test('UTF8 passwords', function (t) {
t.plan(vectors.japanese.length * 2)
vectors.japanese.forEach(function (v) {
var vmnemonic = v[1]
var vseedHex = v[2]
var password = '㍍ガバヴァぱばぐゞちぢ十人十色'
var normalizedPassword = 'メートルガバヴァぱばぐゞちぢ十人十色'
describe('utf8 passwords', function () {
vectors.japanese.forEach(function (v) {
it('creates the correct seed', function () {
var utf8Password = '㍍ガバヴァぱばぐゞちぢ十人十色'
assert.equal(BIP39.mnemonicToSeedHex(v[1], utf8Password), v[2])
})
it('works with already normalized password', function () {
var normalizedPassword = 'メートルガバヴァぱばぐゞちぢ十人十色'
assert.equal(BIP39.mnemonicToSeedHex(v[1], normalizedPassword), v[2])
})
})
t.equal(bip39.mnemonicToSeedHex(vmnemonic, password), vseedHex, 'mnemonicToSeedHex normalizes passwords')
t.equal(bip39.mnemonicToSeedHex(vmnemonic, normalizedPassword), vseedHex, 'mnemonicToSeedHex leaves normalizes passwords as-is')
})
})
describe('Examples in readme', function () {
var bip39 = BIP39
test('README example 1', function (t) {
// defaults to BIP39 English word list
var entropy = '133755ff'
var mnemonic = bip39.entropyToMnemonic(entropy)
var mnemonic = bip39.entropyToMnemonic('133755ff') // hex input, defaults to BIP39 English word list
// 'basket rival lemon'
assert.ok((/^\w+ \w+ \w+$/).test(mnemonic))
t.plan(2)
t.equal(mnemonic, 'basket rival lemon')
var temp = bip39.mnemonicToEntropy(mnemonic) // hex input, defaults to BIP39 English word list
// '133755ff'
assert.equal(temp, '133755ff')
// reversible
t.equal(bip39.mnemonicToEntropy(mnemonic), entropy)
})
// Generate a random mnemonic using crypto.randomBytes
mnemonic = bip39.generateMnemonic() // strength defaults to 128 bits
// 'bench maximum balance appear cousin negative muscle inform enjoy chief vocal hello'
assert.ok(/^(\w+ ){11}\w+$/.test(mnemonic))
test('README example 2', function (t) {
var stub = {
randombytes: function (size) {
return new Buffer('qwertyuiopasdfghjklzxcvbnm[];,./'.slice(0, size))
}
}
var proxiedbip39 = proxyquire('../', stub)
var str = bip39.mnemonicToSeedHex('basket actual')
// '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f'
assert.equal(str, '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f')
// mnemonic strength defaults to 128 bits
var mnemonic = proxiedbip39.generateMnemonic()
var buff = bip39.mnemonicToSeed('basket actual')
var fiveC = 5 * 16 + 12
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 ...>
t.plan(2)
t.equal(mnemonic, 'imitate robot frame trophy nuclear regret saddle around inflict case oil spice')
t.equal(bip39.validateMnemonic(mnemonic), true)
})
var bool = bip39.validateMnemonic(mnemonic)
// true
assert.ok(bool)
test('README example 3', function (t) {
var mnemonic = 'basket actual'
var seed = bip39.mnemonicToSeed(mnemonic)
var seedHex = bip39.mnemonicToSeedHex(mnemonic)
bool = bip39.validateMnemonic('basket actual')
t.plan(3)
t.equal(seed.toString('hex'), seedHex)
t.equal(seedHex, '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f')
t.equal(bip39.validateMnemonic(mnemonic), false)
})
// false
assert.ok(!bool)
})
test('generateMnemonic can vary entropy length', function (t) {
var words = bip39.generateMnemonic(96).split(' ')
t.plan(1)
t.equal(words.length, 9, 'can vary generated entropy bit length')
})
it('exposes standard wordlists', function () {
assert(BIP39.wordlists.EN)
assert.equal(BIP39.wordlists.EN.length, 2048)
test('generateMnemonic only requests the exact amount of data from an RNG', function (t) {
t.plan(1)
bip39.generateMnemonic(96, function (size) {
t.equal(size, 96 / 8)
return new Buffer(size)
})
})
test('validateMnemonic', function (t) {
t.plan(4)
t.equal(bip39.validateMnemonic('sleep kitten'), false, 'fails for a mnemonic that is too short')
t.equal(bip39.validateMnemonic('sleep kitten sleep kitten sleep kitten'), false, 'fails for a mnemonic that is too short')
t.equal(bip39.validateMnemonic('turtle front uncle idea crush write shrug there lottery flower risky shell'), false, 'fails if mnemonic words are not in the word list')
t.equal(bip39.validateMnemonic('sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten sleep kitten'), false, 'fails for invalid checksum')
})
test('exposes standard wordlists', function (t) {
t.plan(2)
t.same(bip39.wordlists.EN, WORDLISTS.english)
t.equal(bip39.wordlists.EN.length, 2048)
})

4
test/vectors.json

@ -130,12 +130,12 @@
[
"00000000000000000000000000000000",
"aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n aband0n ab0ut",
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"
"a3f1b782bc3315cea2f93e8a6db3190a18b4870afe6fb40f6e3ac2fdc2216dfe33b7ef97e31845f710231d8a7a30a49fe82df5707f4a35917a92337a4da8184d"
],
[
"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"
"2e9a0929ca67cd8c1a11cf71abee2c8b51c2555758f37a133ea9f491f55c352a4a831b2bf8dda61e9a4ed0ffeeae7324704f26d1304ab35ffebf8c997f73badd"
]
],
"japanese": [

Loading…
Cancel
Save