bitcore-mnemonic
Version:
BIP39 Mnemonics implemented for Bitcore.
273 lines (219 loc) • 9.61 kB
JavaScript
;
const should = require('chai').should();
const unorm = require('unorm');
const Mnemonic = require('..');
const errors = require('bitcore-lib').errors;
const bip39_vectors = require('./data/bip39-vectors.json');
describe('Mnemonic', function() {
this.timeout(30000);
describe('Constructor', function() {
it('does not require new keyword', function() {
const mnemonic = Mnemonic(); // jshint ignore:line
mnemonic.should.be.instanceof(Mnemonic);
});
it('should fail with invalid data', function() {
(function() {
return new Mnemonic({});
}).should.throw(errors.InvalidArgument);
});
it('should fail with unknown word list', function() {
(function() {
return new Mnemonic('pilots foster august tomorrow kit daughter unknown awesome model town village master');
}).should.throw(errors.Mnemonic.UnknownWordlist);
});
it('should fail with invalid mnemonic', function() {
(function() {
return new Mnemonic('monster foster august tomorrow kit daughter unknown awesome model town village pilot');
}).should.throw(errors.Mnemonic.InvalidMnemonic);
});
it('should fail with invalid ENT', function() {
(function() {
return new Mnemonic(64);
}).should.throw(errors.InvalidArgument);
});
it('constructor should fail with invalid seed', function() {
(function() {
return new Mnemonic(Buffer.alloc(1));
}).should.throw(errors.InvalidEntropy);
});
it('constructor defaults to english worldlist', function() {
var mnemonic = new Mnemonic();
mnemonic.wordlist.should.equal(Mnemonic.Words.ENGLISH);
});
it('allow using different worldlists', function() {
var mnemonic = new Mnemonic(Mnemonic.Words.SPANISH);
mnemonic.wordlist.should.equal(Mnemonic.Words.SPANISH);
});
it('constructor honor both length and wordlist', function() {
var mnemonic = new Mnemonic(32 * 7, Mnemonic.Words.SPANISH);
mnemonic.phrase.split(' ').length.should.equal(21);
mnemonic.wordlist.should.equal(Mnemonic.Words.SPANISH);
});
it('constructor should detect standard wordlist', function() {
var mnemonic = new Mnemonic('afirmar diseño hielo fideo etapa ogro cambio fideo toalla pomelo número buscar');
mnemonic.wordlist.should.equal(Mnemonic.Words.SPANISH);
});
});
it('chinese wordlist is complete', function() {
Mnemonic.Words.CHINESE.length.should.equal(2048);
Mnemonic.Words.CHINESE[0].should.equal('的');
Mnemonic.Words.CHINESE.should.equal(Mnemonic.Words.CHINESE_SIMPLIFIED);
});
it('chinese (simplified) wordlist is complete', function() {
Mnemonic.Words.CHINESE_SIMPLIFIED.length.should.equal(2048);
Mnemonic.Words.CHINESE_SIMPLIFIED[0].should.equal('的');
Mnemonic.Words.CHINESE_SIMPLIFIED[14].should.equal('个');
});
it('chinese (traditional) wordlist is complete', function() {
Mnemonic.Words.CHINESE_TRADITIONAL.length.should.equal(2048);
Mnemonic.Words.CHINESE_TRADITIONAL[0].should.equal('的');
Mnemonic.Words.CHINESE_TRADITIONAL[14].should.equal('個');
});
it('czech wordlist is complete', function() {
Mnemonic.Words.CZECH.length.should.equal(2048);
Mnemonic.Words.CZECH[0].should.equal('abdikace');
});
it('english wordlist is complete', function() {
Mnemonic.Words.ENGLISH.length.should.equal(2048);
Mnemonic.Words.ENGLISH[0].should.equal('abandon');
});
it('french wordlist is complete', function() {
Mnemonic.Words.FRENCH.length.should.equal(2048);
Mnemonic.Words.FRENCH[0].should.equal('abaisser');
});
it('italian wordlist is complete', function() {
Mnemonic.Words.ITALIAN.length.should.equal(2048);
Mnemonic.Words.ITALIAN[0].should.equal('abaco');
});
it('japanese wordlist is complete', function() {
Mnemonic.Words.JAPANESE.length.should.equal(2048);
Mnemonic.Words.JAPANESE[0].should.equal('あいこくしん');
});
it('korean wordlist is complete', function() {
Mnemonic.Words.KOREAN.length.should.equal(2048);
Mnemonic.Words.KOREAN[0].should.equal('가격');
});
it('portuguese wordlist is complete', function() {
Mnemonic.Words.PORTUGUESE.length.should.equal(2048);
Mnemonic.Words.PORTUGUESE[0].should.equal('abacate');
});
it('russian wordlist is complete', function() {
Mnemonic.Words.RUSSIAN.length.should.equal(2048);
Mnemonic.Words.RUSSIAN[0].should.equal('абзац');
});
it('spanish wordlist is complete', function() {
Mnemonic.Words.SPANISH.length.should.equal(2048);
Mnemonic.Words.SPANISH[0].should.equal('ábaco');
});
it('turkish wordlist is complete', function() {
Mnemonic.Words.TURKISH.length.should.equal(2048);
Mnemonic.Words.TURKISH[0].should.equal('abajur');
});
it('allows use different phrase lengths', function() {
var mnemonic;
mnemonic = new Mnemonic(32 * 4);
mnemonic.phrase.split(' ').length.should.equal(12);
mnemonic = new Mnemonic(32 * 5);
mnemonic.phrase.split(' ').length.should.equal(15);
mnemonic = new Mnemonic(32 * 6);
mnemonic.phrase.split(' ').length.should.equal(18);
mnemonic = new Mnemonic(32 * 7);
mnemonic.phrase.split(' ').length.should.equal(21);
mnemonic = new Mnemonic(32 * 8);
mnemonic.phrase.split(' ').length.should.equal(24);
});
it('validates a phrase', function() {
var valid = Mnemonic.isValid('afirmar diseño hielo fideo etapa ogro cambio fideo toalla pomelo número buscar');
valid.should.equal(true);
var invalid = Mnemonic.isValid('afirmar diseño hielo fideo etapa ogro cambio fideo hielo pomelo número buscar');
invalid.should.equal(false);
var invalid2 = Mnemonic.isValid('afirmar diseño hielo fideo etapa ogro cambio fideo hielo pomelo número oneInvalidWord');
invalid2.should.equal(false);
var invalid3 = Mnemonic.isValid('totally invalid phrase');
invalid3.should.equal(false);
var valid2 = Mnemonic.isValid('caution opprimer époque belote devenir ficeler filleul caneton apologie nectar frapper fouiller');
valid2.should.equal(true);
});
it('has a toString method', function() {
var mnemonic = new Mnemonic();
mnemonic.toString().should.equal(mnemonic.phrase);
});
it('has an inspect method', function() {
var mnemonic = new Mnemonic();
mnemonic.inspect().should.have.string('<Mnemonic:');
});
it('derives a seed without a passphrase', function() {
var mnemonic = new Mnemonic();
var seed = mnemonic.toSeed();
should.exist(seed);
});
it('derives a seed using a passphrase', function() {
var mnemonic = new Mnemonic();
var seed = mnemonic.toSeed('my passphrase');
should.exist(seed);
});
it('derives an extended private key', function() {
var mnemonic = new Mnemonic();
var pk = mnemonic.toHDPrivateKey();
should.exist(pk);
});
it('derives an extended private key for keyType ed25519', function() {
const phrase = 'crush desk brain index action subject tackle idea trim unveil lawn live';
const mnemonic = new Mnemonic(phrase);
const pk = mnemonic.toHDPrivateKey('', 'livenet', 'ed25519');
should.exist(pk);
pk.toString().should.equal('xprv9s21ZrQH143K3aKdQ6kXF1vj7R6LtkoLCiUXfM5bdbGXmhQkC1iXdnFfrxAAtaTunPUCCLwUQ3cpNixGLMbLAH1gzeCr8VZDe4gPgmKLb2X');
});
it('deriving an extended private key should fail for invalid seed', function() {
(function() {
var mnemonic = new Mnemonic();
return mnemonic.toHDPrivateKey('', 'livenet', 'bad seed');
}).should.throw('Invalid Key Type: bad seed');
});
it('Mnemonic.fromSeed should fail with invalid wordlist', function() {
(function() {
return Mnemonic.fromSeed(Buffer.alloc(1));
}).should.throw(errors.InvalidArgument);
});
it('Mnemonic.fromSeed should fail with invalid seed', function() {
(function() {
return Mnemonic.fromSeed();
}).should.throw(errors.InvalidArgument);
});
it('should fail with invalid entropy', function() {
(function() {
return Mnemonic.fromSeed(Buffer.alloc(512), Mnemonic.Words.ENGLISH);
}).should.throw(errors.InvalidArgument);
});
// To add new vectors for different languages:
// 1. Add and implement the wordlist so it appears in Mnemonic.Words
// 2. Add the vectors and make sure the key is lowercase of the key for Mnemonic.Words
function getWords(language) {
return Mnemonic.Words[language.toUpperCase()];
}
for (const language in bip39_vectors) {
if (bip39_vectors.hasOwnProperty(language)) {
for (let i = 0; i < bip39_vectors[language].length; i++) {
it(`should pass test vector for ${language} #${i}`, function() {
const wordlist = getWords(language);
const vector = bip39_vectors[language][i];
const [
code,
_mnemonic,
seed,
xpriv
] = vector;
const passphrase = 'TREZOR';
const mnemonic = unorm.nfkd(_mnemonic); // Normalization Form Compatibility Decomposition (NKFD)
const mnemonic1 = Mnemonic.fromSeed(Buffer.from(code, 'hex'), wordlist).phrase;
mnemonic1.should.equal(mnemonic);
const m = new Mnemonic(_mnemonic);
m.toString().should.equal(mnemonic); // should be normalized
m.toSeed(passphrase).toString('hex').should.equal(seed);
Mnemonic.isValid(mnemonic, wordlist).should.equal(true);
m.toHDPrivateKey(passphrase).toString().should.equal(xpriv);
});
}
}
}
});