From 60dd3ee035344eb578559d1b2126fdbe6be7e433 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Thu, 6 Oct 2016 01:02:59 +1100 Subject: [PATCH] switch to tape for testing --- index.js | 1 - package.json | 10 +- test/index.js | 283 +++++++++++++++++----------------------------- test/vectors.json | 4 +- 4 files changed, 109 insertions(+), 189 deletions(-) diff --git a/index.js b/index.js index abf66f6..621c23f 100644 --- a/index.js +++ b/index.js @@ -28,7 +28,6 @@ function mnemonicToEntropy (mnemonic, wordlist) { var words = unorm.nfkd(mnemonic).split(' ') 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') diff --git a/package.json b/package.json index 1c4bc00..c0cf9d5 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "Bitcoin BIP39: Mnemonic code for generating deterministic keys", "main": "index.js", "scripts": { + "coverage": "nyc --branches 100 --functions 100 --check-coverage npm run unit", + "standard": "standard", "test": "npm run standard && npm run unit", - "unit": "mocha --reporter list test/*.js", - "standard": "standard" + "unit": "tape test/*.js" }, "author": "Wei Lu", "contributors": [ @@ -28,8 +29,9 @@ "unorm": "^1.3.3" }, "devDependencies": { - "mocha": "^2.2.0", + "nyc": "^8.3.0", "proxyquire": "^1.7.10", - "standard": "*" + "standard": "*", + "tape": "^4.6.2" } } diff --git a/test/index.js b/test/index.js index 6fbfeb6..910f6eb 100644 --- a/test/index.js +++ b/test/index.js @@ -1,199 +1,118 @@ -/* global describe it */ - -var assert = require('assert') +var bip39 = require('../') var proxyquire = require('proxyquire') - -var BIP39 = require('../index.js') - -var wordlists = { +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) + + 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) + '...') + + t.equal(bip39.entropyToMnemonic(ventropy, wordlist), vmnemonic, 'entropyToMnemonic returns ' + vmnemonic.slice(0, 40) + '...') + + 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('mnemonicToEntropy', function () { - vectors.english.forEach(function (v, i) { - it('works for tests vector ' + i, function () { - assert.equal(BIP39.mnemonicToEntropy(v[1]), v[0]) - }) - }) +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) }) - vectors.japanese.forEach(function (v, i) { - it('works for japanese tests vector ' + i, function () { - assert.equal(BIP39.mnemonicToEntropy(v[1], wordlists.japanese), v[0]) - }) - }) +test('UTF8 passwords', function (t) { + t.plan(vectors.japanese.length * 2) - vectors.custom.forEach(function (v, i) { - it('works for custom test vector ' + i, function () { - assert.equal(BIP39.mnemonicToEntropy(v[1], wordlists.custom), v[0]) - }) - }) - }) + vectors.japanese.forEach(function (v) { + var vmnemonic = v[1] + var vseedHex = v[2] - describe('entropyToMnemonic', function () { - vectors.english.forEach(function (v, i) { - it('works for tests vector ' + i, function () { - assert.equal(BIP39.entropyToMnemonic(v[0]), v[1]) - }) - }) + var password = '㍍ガバヴァぱばぐゞちぢ十人十色' + var normalizedPassword = 'メートルガバヴァぱばぐゞちぢ十人十色' - 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]) - }) - }) - }) - - 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') - }) - }) - - 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')) - }) - }) - - 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]) - }) - }) - }) - - describe('Examples in readme', function () { - var bip39 = BIP39 - - var mnemonic = bip39.entropyToMnemonic('133755ff') // hex input, defaults to BIP39 English word list - // 'basket rival lemon' - assert.ok((/^\w+ \w+ \w+$/).test(mnemonic)) - - var temp = bip39.mnemonicToEntropy(mnemonic) // hex input, defaults to BIP39 English word list - // '133755ff' - assert.equal(temp, '133755ff') - - var stub = { - randombytes: function (size) { - return new Buffer('qwertyuiopasdfghjklzxcvbnm[];,./'.slice(0, size)) - } - } - var proxiedBIP39 = proxyquire('../', stub) - - // Generate a random mnemonic using crypto.randomBytes - mnemonic = proxiedBIP39.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)) - - var str = bip39.mnemonicToSeedHex('basket actual') - // '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f' - assert.equal(str, '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f') - - var buff = bip39.mnemonicToSeed('basket actual') - var fiveC = 5 * 16 + 12 - assert.equal(buff[0], fiveC) - // - - var bool = bip39.validateMnemonic(mnemonic) - // true - assert.ok(bool) - - bool = bip39.validateMnemonic('basket actual') - - // false - assert.ok(!bool) - }) - - it('exposes standard wordlists', function () { - assert(BIP39.wordlists.EN) - assert.equal(BIP39.wordlists.EN.length, 2048) + t.equal(bip39.mnemonicToSeedHex(vmnemonic, password), vseedHex, 'mnemonicToSeedHex normalizes passwords') + t.equal(bip39.mnemonicToSeedHex(vmnemonic, normalizedPassword), vseedHex, 'mnemonicToSeedHex leaves normalizes passwords as-is') }) }) + +test('README example 1', function (t) { + // defaults to BIP39 English word list + var entropy = '133755ff' + var mnemonic = bip39.entropyToMnemonic(entropy) + + t.plan(2) + t.equal(mnemonic, 'basket rival lemon') + + // reversible + t.equal(bip39.mnemonicToEntropy(mnemonic), entropy) +}) + +test('README example 2', function (t) { + var stub = { + randombytes: function (size) { + return new Buffer('qwertyuiopasdfghjklzxcvbnm[];,./'.slice(0, size)) + } + } + var proxiedbip39 = proxyquire('../', stub) + + // mnemonic strength defaults to 128 bits + var mnemonic = proxiedbip39.generateMnemonic() + + t.plan(2) + t.equal(mnemonic, 'bench maximum balance appear cousin negative muscle inform enjoy chief vocal hello') + t.equal(bip39.validateMnemonic(mnemonic), true) +}) + +test('README example 3', function (t) { + var mnemonic = 'basket actual' + var seed = bip39.mnemonicToSeed(mnemonic) + var seedHex = bip39.mnemonicToSeedHex(mnemonic) + + t.plan(2) + t.equal(seed.toString('hex'), seedHex) + t.equal(seedHex, '5cf2d4a8b0355e90295bdfc565a022a409af063d5365bb57bf74d9528f494bfa4400f53d8349b80fdae44082d7f9541e1dba2b003bcfec9d0d53781ca676651f') + t.equal(bip39.validateMnemonic(mnemonic), false) +}) + +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') +}) + +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) +}) diff --git a/test/vectors.json b/test/vectors.json index fc32ddd..12b0515 100644 --- a/test/vectors.json +++ b/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": [