bip39 work.
This commit is contained in:
parent
7821d65db4
commit
f2edf8f4b0
402
lib/bcoin/hd.js
402
lib/bcoin/hd.js
@ -123,16 +123,30 @@ function Mnemonic(options) {
|
||||
return new Mnemonic(options);
|
||||
|
||||
this.bits = 128;
|
||||
this.language = 'english';
|
||||
this.entropy = null;
|
||||
this.phrase = null;
|
||||
this.passphrase = '';
|
||||
this.language = 'english';
|
||||
this.seed = null;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of languages.
|
||||
* @const {String[]}
|
||||
* @default
|
||||
*/
|
||||
|
||||
Mnemonic.languages = [
|
||||
'simplified chinese',
|
||||
'traditional chinese',
|
||||
'english',
|
||||
'french',
|
||||
'italian',
|
||||
'japanese'
|
||||
];
|
||||
|
||||
/**
|
||||
* Inject properties from options object.
|
||||
* @private
|
||||
@ -143,22 +157,35 @@ Mnemonic.prototype.fromOptions = function fromOptions(options) {
|
||||
if (typeof options === 'string')
|
||||
options = { phrase: options };
|
||||
|
||||
if (options.bits != null)
|
||||
if (options.bits != null) {
|
||||
assert(utils.isNumber(options.bits));
|
||||
assert(options.bits >= 128);
|
||||
assert(options.bits % 32 === 0);
|
||||
this.bits = options.bits;
|
||||
}
|
||||
|
||||
this.entropy = options.entropy;
|
||||
this.phrase = options.phrase;
|
||||
|
||||
if (options.passphrase)
|
||||
this.passphrase = options.passphrase;
|
||||
|
||||
if (options.language)
|
||||
if (options.language) {
|
||||
assert(typeof options.language === 'string');
|
||||
assert(Mnemonic.languages.indexOf(options.language) !== -1);
|
||||
this.language = options.language;
|
||||
}
|
||||
|
||||
this.seed = options.seed;
|
||||
if (options.passphrase) {
|
||||
assert(typeof options.passphrase === 'string');
|
||||
this.passphrase = options.passphrase;
|
||||
}
|
||||
|
||||
assert(this.bits >= 128);
|
||||
assert(this.bits % 32 === 0);
|
||||
if (options.phrase) {
|
||||
this.fromPhrase(options.phrase);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (options.entropy) {
|
||||
this.fromEntropy(options.entropy);
|
||||
return this;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -171,24 +198,163 @@ Mnemonic.fromOptions = function fromOptions(options) {
|
||||
return new Mnemonic().fromOptions(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from entropy.
|
||||
* @private
|
||||
* @param {Buffer} entropy
|
||||
*/
|
||||
|
||||
Mnemonic.prototype.fromEntropy = function fromEntropy(entropy, lang) {
|
||||
assert(Buffer.isBuffer(entropy));
|
||||
assert(entropy.length * 8 >= 128);
|
||||
assert((entropy.length * 8) % 32 === 0);
|
||||
assert(!lang || Mnemonic.languages.indexOf(lang) !== -1);
|
||||
|
||||
this.entropy = entropy;
|
||||
this.bits = entropy.length * 8;
|
||||
|
||||
if (lang)
|
||||
this.language = lang;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate mnemonic from entropy.
|
||||
* @param {Buffer} entropy
|
||||
* @returns {Mnemonic}
|
||||
*/
|
||||
|
||||
Mnemonic.fromEntropy = function fromEntropy(entropy, lang) {
|
||||
return new Mnemonic().fromEntropy(entropy, lang);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from phrase.
|
||||
* @private
|
||||
* @param {String} phrase
|
||||
*/
|
||||
|
||||
Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) {
|
||||
var i, j, bits, pos, oct, bit, b, ent, entropy, lang;
|
||||
var chk, word, wordlist, index, cbits, cbytes, words;
|
||||
|
||||
assert(typeof phrase === 'string');
|
||||
|
||||
words = phrase.split(/[ \u3000]/);
|
||||
|
||||
bits = words.length * 11;
|
||||
|
||||
lang = Mnemonic.getLanguage(words[0]);
|
||||
|
||||
ent = new Buffer(Math.ceil(bits / 8));
|
||||
ent.fill(0);
|
||||
|
||||
wordlist = Mnemonic.getWordlist(lang);
|
||||
|
||||
for (i = 0; i < words.length; i++) {
|
||||
word = words[i];
|
||||
index = wordlist.indexOf(word);
|
||||
|
||||
if (index === -1)
|
||||
throw new Error('Could not find word.');
|
||||
|
||||
for (j = 0; j < 11; j++) {
|
||||
pos = i * 11 + j;
|
||||
bit = pos % 8;
|
||||
oct = (pos - bit) / 8;
|
||||
b = (index >>> (10 - j)) & 1;
|
||||
ent[oct] |= b << (7 - bit);
|
||||
}
|
||||
}
|
||||
|
||||
// Checksum bits:
|
||||
cbits = bits % 32;
|
||||
|
||||
// Checksum bytes:
|
||||
cbytes = Math.ceil(cbits / 8);
|
||||
|
||||
bits -= bits % 32;
|
||||
entropy = ent.slice(0, ent.length - cbytes);
|
||||
|
||||
ent = ent.slice(ent.length - cbytes);
|
||||
chk = utils.sha256(entropy);
|
||||
|
||||
for (i = 0; i < cbits; i++) {
|
||||
bit = i % 8;
|
||||
oct = (i - bit) / 8;
|
||||
b = (chk[oct] >>> (7 - bit)) & 1;
|
||||
j = (ent[oct] >>> (7 - bit)) & 1;
|
||||
if (b !== j)
|
||||
throw new Error('Invalid checksum.');
|
||||
}
|
||||
|
||||
assert(bits / 8 === entropy.length);
|
||||
assert(bits >= 128);
|
||||
assert(bits % 32 === 0);
|
||||
|
||||
this.bits = bits;
|
||||
this.language = lang;
|
||||
this.entropy = entropy;
|
||||
this.phrase = phrase;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate mnemonic from a phrase.
|
||||
* @param {String} phrase
|
||||
* @returns {Mnemonic}
|
||||
*/
|
||||
|
||||
Mnemonic.fromPhrase = function fromPhrase(phrase) {
|
||||
return new Mnemonic().fromPhrase(phrase);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the seed.
|
||||
* @param {String?} passphrase
|
||||
* @returns {Buffer} pbkdf2 seed.
|
||||
*/
|
||||
|
||||
Mnemonic.prototype.toSeed = function toSeed() {
|
||||
if (this.seed)
|
||||
return this.seed;
|
||||
Mnemonic.prototype.toSeed = function toSeed(passphrase) {
|
||||
if (!passphrase)
|
||||
passphrase = this.passphrase;
|
||||
|
||||
if (!this.phrase)
|
||||
this.phrase = this.createMnemonic();
|
||||
this.passphrase = passphrase;
|
||||
|
||||
this.seed = utils.pbkdf2Sync(
|
||||
nfkd(this.phrase),
|
||||
nfkd('mnemonic' + this.passphrase),
|
||||
return utils.pbkdf2Sync(
|
||||
nfkd(this.getPhrase()),
|
||||
nfkd('mnemonic' + passphrase),
|
||||
2048, 64, 'sha512');
|
||||
};
|
||||
|
||||
return this.seed;
|
||||
/**
|
||||
* Generate seed and create an hd private key.
|
||||
* @param {String?} passphrase
|
||||
* @param {(Network|NetworkType)?} network
|
||||
* @returns {HDPrivateKey}
|
||||
*/
|
||||
|
||||
Mnemonic.prototype.toKey = function toKey(passphrase, network) {
|
||||
var seed = this.toSeed(passphrase);
|
||||
var key = HDPrivateKey.fromSeed(seed, network);
|
||||
key.mnemonic = this;
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or generate entropy.
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
Mnemonic.prototype.getEntropy = function getEntropy() {
|
||||
if (!this.entropy)
|
||||
this.entropy = ec.random(this.bits / 8);
|
||||
|
||||
assert(this.bits / 8 === this.entropy.length);
|
||||
|
||||
return this.entropy;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -196,23 +362,22 @@ Mnemonic.prototype.toSeed = function toSeed() {
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
Mnemonic.prototype.createMnemonic = function createMnemonic() {
|
||||
var mnemonic = [];
|
||||
var wordlist = Mnemonic.getWordlist(this.language);
|
||||
var i, j, bits, entropy, word, oct, bit;
|
||||
Mnemonic.prototype.getPhrase = function getPhrase() {
|
||||
var i, j, phrase, wordlist, bits, entropy, index, pos, oct, bit;
|
||||
|
||||
if (!this.entropy)
|
||||
this.entropy = ec.random(this.bits / 8);
|
||||
if (this.phrase)
|
||||
return this.phrase;
|
||||
|
||||
bits = this.entropy.length * 8;
|
||||
phrase = [];
|
||||
wordlist = Mnemonic.getWordlist(this.language);
|
||||
|
||||
entropy = this.getEntropy();
|
||||
bits = this.bits;
|
||||
|
||||
// Append the hash to the entropy to
|
||||
// make things easy when grabbing
|
||||
// the checksum bits.
|
||||
entropy = Buffer.concat([
|
||||
this.entropy,
|
||||
utils.sha256(this.entropy)
|
||||
]);
|
||||
entropy = Buffer.concat([entropy, utils.sha256(entropy)]);
|
||||
|
||||
// Include the first `ENT / 32` bits
|
||||
// of the hash (the checksum).
|
||||
@ -220,24 +385,47 @@ Mnemonic.prototype.createMnemonic = function createMnemonic() {
|
||||
|
||||
// Build the mnemonic by reading
|
||||
// 11 bit indexes from the entropy.
|
||||
for (i = 0; i < bits; i++) {
|
||||
i--;
|
||||
word = 0;
|
||||
for (i = 0; i < bits / 11; i++) {
|
||||
index = 0;
|
||||
for (j = 0; j < 11; j++) {
|
||||
i++;
|
||||
bit = i % 8;
|
||||
oct = (i - bit) / 8;
|
||||
word <<= 1;
|
||||
word |= (entropy[oct] >>> (7 - bit)) & 1;
|
||||
pos = i * 11 + j;
|
||||
bit = pos % 8;
|
||||
oct = (pos - bit) / 8;
|
||||
index <<= 1;
|
||||
index |= (entropy[oct] >>> (7 - bit)) & 1;
|
||||
}
|
||||
mnemonic.push(wordlist[word]);
|
||||
phrase.push(wordlist[index]);
|
||||
}
|
||||
|
||||
// Japanese likes double-width spaces.
|
||||
if (this.language === 'japanese')
|
||||
return mnemonic.join('\u3000');
|
||||
phrase = phrase.join('\u3000');
|
||||
else
|
||||
phrase = phrase.join(' ');
|
||||
|
||||
return mnemonic.join(' ');
|
||||
this.phrase = phrase;
|
||||
|
||||
return phrase;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine a single word's language.
|
||||
* @param {String} word
|
||||
* @returns {String} Language.
|
||||
* @throws on not found.
|
||||
*/
|
||||
|
||||
Mnemonic.getLanguage = function getLanguage(word) {
|
||||
var i, lang, wordlist;
|
||||
|
||||
for (i = 0; i < Mnemonic.languages.length; i++) {
|
||||
lang = Mnemonic.languages[i];
|
||||
wordlist = Mnemonic.getWordlist(lang);
|
||||
if (wordlist.indexOf(word) !== -1)
|
||||
return lang;
|
||||
}
|
||||
|
||||
throw new Error('Could not determine language.');
|
||||
};
|
||||
|
||||
/**
|
||||
@ -261,7 +449,7 @@ Mnemonic.getWordlist = function getWordlist(language) {
|
||||
case 'japanese':
|
||||
return require('../../etc/japanese.js');
|
||||
default:
|
||||
assert(false, 'Unknown language: ' + language);
|
||||
throw new Error('Unknown language: ' + language);
|
||||
}
|
||||
};
|
||||
|
||||
@ -273,11 +461,10 @@ Mnemonic.getWordlist = function getWordlist(language) {
|
||||
Mnemonic.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
bits: this.bits,
|
||||
entropy: this.entropy ? this.entropy.toString('hex') : null,
|
||||
phrase: this.phrase,
|
||||
passphrase: this.passphrase,
|
||||
language: this.language,
|
||||
seed: this.seed ? this.seed.toString('hex') : null
|
||||
entropy: this.getEntropy().toString('hex'),
|
||||
phrase: this.getPhrase(),
|
||||
passphrase: this.passphrase
|
||||
};
|
||||
};
|
||||
|
||||
@ -289,25 +476,18 @@ Mnemonic.prototype.toJSON = function toJSON() {
|
||||
|
||||
Mnemonic.prototype.fromJSON = function fromJSON(json) {
|
||||
assert(utils.isNumber(json.bits));
|
||||
assert(!json.entropy || typeof json.entropy === 'string');
|
||||
assert(!json.phrase || typeof json.phrase === 'string');
|
||||
assert(typeof json.passphrase === 'string');
|
||||
assert(typeof json.language === 'string');
|
||||
assert(!json.seed || typeof json.seed === 'string');
|
||||
assert(typeof json.entropy === 'string');
|
||||
assert(typeof json.phrase === 'string');
|
||||
assert(typeof json.passphrase === 'string');
|
||||
|
||||
this.bits = json.bits;
|
||||
|
||||
if (json.entry)
|
||||
this.entropy = new Buffer(json.entropy, 'hex');
|
||||
|
||||
if (json.phrase)
|
||||
this.phrase = json.phrase;
|
||||
|
||||
this.passphrase = json.passphrase;
|
||||
this.language = json.language;
|
||||
this.entropy = new Buffer(json.entropy, 'hex');
|
||||
this.phrase = json.phrase;
|
||||
this.passphrase = json.passphrase;
|
||||
|
||||
if (json.seed)
|
||||
this.seed = new Buffer(json.seed, 'hex');
|
||||
assert(this.bits / 8 === this.entropy.length);
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -331,30 +511,10 @@ Mnemonic.prototype.toRaw = function toRaw(writer) {
|
||||
var p = new BufferWriter(writer);
|
||||
|
||||
p.writeU16(this.bits);
|
||||
|
||||
if (this.entropy) {
|
||||
p.writeU8(1);
|
||||
p.writeBytes(this.entropy);
|
||||
} else {
|
||||
p.writeU8(0);
|
||||
}
|
||||
|
||||
if (this.phrase) {
|
||||
p.writeU8(1);
|
||||
p.writeVarString(this.phrase, 'utf8');
|
||||
} else {
|
||||
p.writeU8(0);
|
||||
}
|
||||
|
||||
p.writeVarString(this.passphrase, 'utf8');
|
||||
p.writeVarString(this.language, 'utf8');
|
||||
|
||||
if (this.seed) {
|
||||
p.writeU8(1);
|
||||
p.writeBytes(this.seed);
|
||||
} else {
|
||||
p.writeU8(0);
|
||||
}
|
||||
p.writeBytes(this.getEntropy());
|
||||
p.writeVarString(this.getPhrase(), 'utf8');
|
||||
p.writeVarString(this.passphrase, 'utf8');
|
||||
|
||||
if (!writer)
|
||||
p = p.render();
|
||||
@ -372,18 +532,10 @@ Mnemonic.prototype.fromRaw = function fromRaw(data) {
|
||||
var p = new BufferReader(data);
|
||||
|
||||
this.bits = p.readU16();
|
||||
|
||||
if (p.readU8() === 1)
|
||||
this.entropy = p.readBytes(this.bits / 8);
|
||||
|
||||
if (p.readU8() === 1)
|
||||
this.phrase = p.readVarString('utf8');
|
||||
|
||||
this.passphrase = p.readVarString('utf8');
|
||||
this.language = p.readVarString('utf8');
|
||||
|
||||
if (p.readU8() === 1)
|
||||
this.seed = p.readBytes(64);
|
||||
this.entropy = p.readBytes(this.bits / 8);
|
||||
this.phrase = p.readVarString('utf8');
|
||||
this.passphrase = p.readVarString('utf8');
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -398,6 +550,24 @@ Mnemonic.fromRaw = function fromRaw(data) {
|
||||
return new Mnemonic().fromRaw(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the mnemonic to a string.
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
Mnemonic.prototype.toString = function toString() {
|
||||
return this.getPhrase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Inspect the mnemonic.
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
Mnemonic.prototype.inspect = function inspect() {
|
||||
return '<Mnemonic: ' + this.getPhrase() + '>';
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether an object is a Mnemonic.
|
||||
* @param {Object} obj
|
||||
@ -1038,31 +1208,8 @@ HDPrivateKey.fromSeed = function fromSeed(seed, network) {
|
||||
HDPrivateKey.prototype.fromMnemonic = function fromMnemonic(mnemonic, network) {
|
||||
if (!(mnemonic instanceof Mnemonic))
|
||||
mnemonic = new Mnemonic(mnemonic);
|
||||
|
||||
if (mnemonic.seed || mnemonic.phrase || mnemonic.entropy) {
|
||||
this.fromSeed(mnemonic.toSeed(), network);
|
||||
this.mnemonic = mnemonic;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Very unlikely, but not impossible
|
||||
// to get an invalid private key.
|
||||
for (;;) {
|
||||
try {
|
||||
this.fromSeed(mnemonic.toSeed(), network);
|
||||
this.mnemonic = mnemonic;
|
||||
} catch (e) {
|
||||
if (e.message === 'Master private key is invalid.') {
|
||||
mnemonic.seed = null;
|
||||
mnemonic.phrase = null;
|
||||
mnemonic.entropy = null;
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this.fromSeed(mnemonic.toSeed(), network);
|
||||
this.mnemonic = mnemonic;
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -1301,11 +1448,11 @@ HDPrivateKey.prototype.toJSON = function toJSON() {
|
||||
HDPrivateKey.prototype.fromJSON = function fromJSON(json) {
|
||||
assert(json.xprivkey, 'Could not handle key JSON.');
|
||||
|
||||
this.fromBase58(json.xprivkey);
|
||||
|
||||
if (json.mnemonic)
|
||||
this.mnemonic = Mnemonic.fromJSON(json.mnemonic);
|
||||
|
||||
this.fromBase58(json.xprivkey);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -1861,8 +2008,9 @@ function nfkd(str) {
|
||||
* Expose
|
||||
*/
|
||||
|
||||
HD.Mnemonic = Mnemonic;
|
||||
HD.PrivateKey = HDPrivateKey;
|
||||
HD.PublicKey = HDPublicKey;
|
||||
exports = HD;
|
||||
exports.Mnemonic = Mnemonic;
|
||||
exports.PrivateKey = HDPrivateKey;
|
||||
exports.PublicKey = HDPublicKey;
|
||||
|
||||
module.exports = HD;
|
||||
|
||||
@ -13,7 +13,6 @@ var assert = utils.assert;
|
||||
var constants = bcoin.protocol.constants;
|
||||
var Script = bcoin.script;
|
||||
var opcodes = constants.opcodes;
|
||||
var HASH160 = constants.ZERO_HASH.slice(0, 20);
|
||||
var FundingError = bcoin.errors.FundingError;
|
||||
|
||||
/**
|
||||
@ -1061,7 +1060,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
|
||||
// use a fake p2pkh output to gauge size.
|
||||
script: options.changeAddress
|
||||
? Script.fromAddress(options.changeAddress)
|
||||
: Script.fromPubkeyhash(HASH160),
|
||||
: Script.fromPubkeyhash(constants.ZERO_HASH160),
|
||||
value: 0
|
||||
});
|
||||
|
||||
|
||||
@ -19,13 +19,13 @@ describe('Mnemonic', function() {
|
||||
entropy: entropy,
|
||||
passphrase: 'TREZOR'
|
||||
});
|
||||
mnemonic.toSeed();
|
||||
assert.equal(mnemonic.phrase, phrase);
|
||||
assert.equal(mnemonic.getPhrase(), phrase);
|
||||
assert.equal(mnemonic.toSeed().toString('hex'), seed.toString('hex'));
|
||||
var key = bcoin.hd.fromMnemonic(mnemonic);
|
||||
assert.equal(key.xprivkey, xpriv);
|
||||
});
|
||||
});
|
||||
|
||||
mnemonic2.forEach(function(data, i) {
|
||||
var entropy = new Buffer(data.entropy, 'hex');
|
||||
var phrase = data.mnemonic;
|
||||
@ -38,11 +38,19 @@ describe('Mnemonic', function() {
|
||||
entropy: entropy,
|
||||
passphrase: passphrase
|
||||
});
|
||||
mnemonic.toSeed();
|
||||
assert.equal(mnemonic.phrase, phrase);
|
||||
assert.equal(mnemonic.getPhrase(), phrase);
|
||||
assert.equal(mnemonic.toSeed().toString('hex'), seed.toString('hex'));
|
||||
var key = bcoin.hd.fromMnemonic(mnemonic);
|
||||
assert.equal(key.xprivkey, xpriv);
|
||||
});
|
||||
});
|
||||
|
||||
it('should verify phrase', function() {
|
||||
var m1 = new bcoin.hd.Mnemonic();
|
||||
var m2 = bcoin.hd.Mnemonic.fromPhrase(m1.getPhrase());
|
||||
assert.deepEqual(m2.getEntropy(), m1.getEntropy());
|
||||
assert.equal(m2.bits, m1.bits);
|
||||
assert.equal(m2.language, m1.language);
|
||||
assert.deepEqual(m2.toSeed(), m1.toSeed());
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user