Skip to content

Commit

Permalink
Add support for non english BIP39 dictionaries languages
Browse files Browse the repository at this point in the history
  • Loading branch information
mpolci committed Mar 20, 2017
1 parent 42a60f5 commit 279bb8f
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 9 deletions.
50 changes: 41 additions & 9 deletions lib/keystore.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ KeyStore.prototype.init = function(mnemonic, pwDerivedKey, hdPathString, salt) {
pathKsData.addresses = [];

if ( (typeof pwDerivedKey !== 'undefined') && (typeof mnemonic !== 'undefined') ){
var words = mnemonic.split(' ');
if (!Mnemonic.isValid(mnemonic, Mnemonic.Words.ENGLISH) || words.length !== 12){
var words = mnemonic.split(/\s/);
if (!KeyStore.isSeedValid(mnemonic) || words.length !== 12){
throw new Error('KeyStore: Invalid mnemonic');
}

Expand All @@ -116,6 +116,17 @@ KeyStore.prototype.init = function(mnemonic, pwDerivedKey, hdPathString, salt) {
}
}

/**
*
* @param opts
* @param {string=} opts.hdPathString BIP32 default hd path
* @param {string=} opts.seedPhrase BIP39 mnemonic phrase
* @param {string=} opts.seedLanguage BIP39 dictionary language for automatically generated seed.
* See https://github.com/bitpay/bitcore-mnemonic for supported values
* @param {string} opts.password
* @param {string|Buffer=} opts.salt
* @param cb
*/
KeyStore.createVault = function(opts, cb) {
var _this = this;

Expand All @@ -126,7 +137,7 @@ KeyStore.createVault = function(opts, cb) {

// Default seed phrase if not specified
if (!('seedPhrase' in opts)) {
opts.seedPhrase = this.generateRandomSeed();
opts.seedPhrase = this.generateRandomSeed(null, opts.seedLanguage || 'ENGLISH');
}

if (!('salt' in opts)) {
Expand Down Expand Up @@ -341,6 +352,18 @@ KeyStore._concatAndSha256 = function(entropyBuf0, entropyBuf1) {
return hashedEnt;
}

var _getDictionary = function (lang) {
var dictionary;
if (!lang) {
dictionary = Mnemonic.Words.ENGLISH
} else if (lang in Mnemonic.Words) {
dictionary = Mnemonic.Words[lang]
} else {
throw new Error('Unsupported dictionary language')
}
return dictionary
}

// External static functions


Expand All @@ -353,17 +376,18 @@ KeyStore._concatAndSha256 = function(entropyBuf0, entropyBuf1) {
// If extraEntropy is not set, the random number generator
// is used directly.

KeyStore.generateRandomSeed = function(extraEntropy) {
KeyStore.generateRandomSeed = function(extraEntropy, seedLanguage) {

var seed = '';
if (extraEntropy === undefined) {
seed = new Mnemonic(Mnemonic.Words.ENGLISH);
var dictionary = _getDictionary(seedLanguage)
if (extraEntropy == undefined) {
seed = new Mnemonic(dictionary);
}
else if (typeof extraEntropy === 'string') {
var entBuf = new Buffer(extraEntropy);
var randBuf = Random.getRandomBuffer(256 / 8);
var hashedEnt = this._concatAndSha256(randBuf, entBuf).slice(0, 128 / 8);
seed = new Mnemonic(hashedEnt, Mnemonic.Words.ENGLISH);
seed = new Mnemonic(hashedEnt, dictionary);
}
else {
throw new Error('generateRandomSeed: extraEntropy is set but not a string.')
Expand All @@ -372,8 +396,16 @@ KeyStore.generateRandomSeed = function(extraEntropy) {
return seed.toString();
};

KeyStore.isSeedValid = function(seed) {
return Mnemonic.isValid(seed, Mnemonic.Words.ENGLISH)
/**
*
* @param {string} seed
* @param {string=} seedLanguage BIP39 dictionary language, if undefined the dictionary
* will be detected from the seed. See https://github.com/bitpay/bitcore-mnemonic for supported values
* @return {boolean}
*/
KeyStore.isSeedValid = function(seed, seedLanguage) {
var wordList = seedLanguage ? _getDictionary(seedLanguage) : undefined
return Mnemonic.isValid(seed, wordList)
};

// Takes keystore serialized as string and returns an instance of KeyStore
Expand Down
33 changes: 33 additions & 0 deletions test/keystore.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var keyStore = require('../lib/keystore')
var upgrade = require('../lib/upgrade')
var fixtures = require('./fixtures/keystore')
var Promise = require('bluebird')
var Mnemonic = require('bitcore-mnemonic');

var defaultHdPathString = "m/0'/0'/0'";

Expand Down Expand Up @@ -33,6 +34,28 @@ describe("Keystore", function() {
});
});

Object.keys(Mnemonic.Words).forEach(function (lang) {
it('should generete random seed of language ' + lang, function (done) {
var fixture = fixtures.valid[0];

keyStore.createVault({
password: fixture.password,
seedLanguage: lang,
salt: fixture.salt,
}, function (err, ks) {
if (err) return done(err)
expect(ks.encSeed).to.not.equal(undefined);
var decryptedPaddedSeed = keyStore._decryptString(ks.encSeed, Uint8Array.from(fixtures.valid[0].pwDerivedKey));
// Check padding
var words = decryptedPaddedSeed.trim().split(/\s/);
words.forEach(function (w) {
expect(Mnemonic.Words[lang].indexOf(w)).to.not.equal(-1, 'word ' + w + ' is not in dictionary');
})
done();
});
});
})

it('generates a random salt for key generation', function(done) {
this.timeout(10000);
var fixture = fixtures.valid[0];
Expand Down Expand Up @@ -281,6 +304,16 @@ describe("Keystore", function() {
});

describe("Seed functions", function() {
Object.keys(Mnemonic.Words).forEach(function (lang) {
it('should generate a random phrase of language ' + lang, function() {
var seed = keyStore.generateRandomSeed(null, lang);
var words = seed.split(/\s/);
words.forEach(function (w) {
expect(Mnemonic.Words[lang].indexOf(w)).to.not.equal(-1, 'word ' + w + ' is not in dictionary');
})
});
});

it('returns the unencrypted seed', function(done) {
var ks = new keyStore(fixtures.valid[0].mnSeed, Uint8Array.from(fixtures.valid[0].pwDerivedKey))
expect(ks.getSeed(Uint8Array.from(fixtures.valid[0].pwDerivedKey))).to.equal(fixtures.valid[0].mnSeed)
Expand Down

0 comments on commit 279bb8f

Please sign in to comment.