hd refactor.

This commit is contained in:
Christopher Jeffrey 2016-07-01 19:21:20 -07:00
parent b59f6ae384
commit 4bf31f32bf
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 49 additions and 30 deletions

View File

@ -38,6 +38,7 @@ ec.elliptic = elliptic.ec('secp256k1');
/** /**
* elliptic.js signature constructor. * elliptic.js signature constructor.
* @static * @static
* @type {Function}
*/ */
ec.signature = require('elliptic/lib/elliptic/ec/signature'); ec.signature = require('elliptic/lib/elliptic/ec/signature');
@ -45,10 +46,18 @@ ec.signature = require('elliptic/lib/elliptic/ec/signature');
/** /**
* elliptic.js keypair constructor. * elliptic.js keypair constructor.
* @static * @static
* @type {Function}
*/ */
ec.keypair = require('elliptic/lib/elliptic/ec/key'); ec.keypair = require('elliptic/lib/elliptic/ec/key');
/**
* A reference to the secp256k1 curve.
* @const {Object}
*/
ec.curve = ec.elliptic.curve;
/** /**
* Generate a private key. * Generate a private key.
* @returns {Buffer} Private key. * @returns {Buffer} Private key.

View File

@ -122,7 +122,7 @@ function Mnemonic(options) {
if (!(this instanceof Mnemonic)) if (!(this instanceof Mnemonic))
return new Mnemonic(options); return new Mnemonic(options);
this.bits = 128; this.bits = constants.hd.MIN_ENTROPY;
this.language = 'english'; this.language = 'english';
this.entropy = null; this.entropy = null;
this.phrase = null; this.phrase = null;
@ -159,7 +159,8 @@ Mnemonic.prototype.fromOptions = function fromOptions(options) {
if (options.bits != null) { if (options.bits != null) {
assert(utils.isNumber(options.bits)); assert(utils.isNumber(options.bits));
assert(options.bits >= 128); assert(options.bits >= constants.hd.MIN_ENTROPY);
assert(options.bits <= constants.hd.MAX_ENTROPY);
assert(options.bits % 32 === 0); assert(options.bits % 32 === 0);
this.bits = options.bits; this.bits = options.bits;
} }
@ -309,12 +310,20 @@ Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) {
words = phrase.split(/[ \u3000]+/); words = phrase.split(/[ \u3000]+/);
bits = words.length * 11; bits = words.length * 11;
lang = Mnemonic.getLanguage(words[0]); cbits = bits % 32;
ent = new Buffer(Math.ceil(bits / 8)); cbytes = Math.ceil(cbits / 8);
wordlist = Mnemonic.getWordlist(lang); bits -= cbits;
assert(bits >= constants.hd.MIN_ENTROPY);
assert(bits <= constants.hd.MAX_ENTROPY);
assert(bits % 32 === 0);
ent = new Buffer(Math.ceil((bits + cbits) / 8));
ent.fill(0); ent.fill(0);
lang = Mnemonic.getLanguage(words[0]);
wordlist = Mnemonic.getWordlist(lang);
for (i = 0; i < words.length; i++) { for (i = 0; i < words.length; i++) {
word = words[i]; word = words[i];
index = wordlist.indexOf(word); index = wordlist.indexOf(word);
@ -331,10 +340,6 @@ Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) {
} }
} }
// Checksum bits:
cbits = bits % 32;
cbytes = Math.ceil(cbits / 8);
entropy = ent.slice(0, ent.length - cbytes); entropy = ent.slice(0, ent.length - cbytes);
ent = ent.slice(ent.length - cbytes); ent = ent.slice(ent.length - cbytes);
chk = utils.sha256(entropy); chk = utils.sha256(entropy);
@ -348,11 +353,7 @@ Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) {
throw new Error('Invalid checksum.'); throw new Error('Invalid checksum.');
} }
bits -= cbits;
assert(bits / 8 === entropy.length); assert(bits / 8 === entropy.length);
assert(bits >= 128);
assert(bits % 32 === 0);
this.bits = bits; this.bits = bits;
this.language = lang; this.language = lang;
@ -382,7 +383,8 @@ Mnemonic.fromPhrase = function fromPhrase(phrase) {
Mnemonic.prototype.fromEntropy = function fromEntropy(entropy, lang) { Mnemonic.prototype.fromEntropy = function fromEntropy(entropy, lang) {
assert(Buffer.isBuffer(entropy)); assert(Buffer.isBuffer(entropy));
assert(entropy.length * 8 >= 128); assert(entropy.length * 8 >= constants.hd.MIN_ENTROPY);
assert(entropy.length * 8 <= constants.hd.MAX_ENTROPY);
assert((entropy.length * 8) % 32 === 0); assert((entropy.length * 8) % 32 === 0);
assert(!lang || Mnemonic.languages.indexOf(lang) !== -1); assert(!lang || Mnemonic.languages.indexOf(lang) !== -1);
@ -478,6 +480,10 @@ Mnemonic.prototype.fromJSON = function fromJSON(json) {
assert(typeof json.entropy === 'string'); assert(typeof json.entropy === 'string');
assert(typeof json.phrase === 'string'); assert(typeof json.phrase === 'string');
assert(typeof json.passphrase === 'string'); assert(typeof json.passphrase === 'string');
assert(json.bits >= constants.hd.MIN_ENTROPY);
assert(json.bits <= constants.hd.MAX_ENTROPY);
assert(json.bits % 32 === 0);
assert(json.bits / 8 === json.entropy.length / 2);
this.bits = json.bits; this.bits = json.bits;
this.language = json.language; this.language = json.language;
@ -485,10 +491,6 @@ Mnemonic.prototype.fromJSON = function fromJSON(json) {
this.phrase = json.phrase; this.phrase = json.phrase;
this.passphrase = json.passphrase; this.passphrase = json.passphrase;
assert(this.bits >= 128);
assert(this.bits % 32 === 0);
assert(this.bits / 8 === this.entropy.length);
return this; return this;
}; };
@ -541,7 +543,8 @@ Mnemonic.prototype.fromRaw = function fromRaw(data) {
this.passphrase = p.readVarString('utf8'); this.passphrase = p.readVarString('utf8');
assert(this.language); assert(this.language);
assert(this.bits >= 128); assert(this.bits >= constants.hd.MIN_ENTROPY);
assert(this.bits <= constants.hd.MAX_ENTROPY);
assert(this.bits % 32 === 0); assert(this.bits % 32 === 0);
return this; return this;
@ -881,7 +884,6 @@ HDPrivateKey.prototype.fromOptions = function fromOptions(options) {
this.childIndex = options.childIndex; this.childIndex = options.childIndex;
this.chainCode = options.chainCode; this.chainCode = options.chainCode;
this.privateKey = options.privateKey; this.privateKey = options.privateKey;
this.publicKey = ec.publicKeyCreate(options.privateKey, true); this.publicKey = ec.publicKeyCreate(options.privateKey, true);
if (options.mnemonic) { if (options.mnemonic) {
@ -983,9 +985,13 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) {
privateKey = left privateKey = left
.add(new bn(this.privateKey)) .add(new bn(this.privateKey))
.mod(ec.elliptic.curve.n) .mod(ec.curve.n)
.toArrayLike(Buffer, 'be', 32); .toArrayLike(Buffer, 'be', 32);
// Only a 1 in 2^127 chance of happening.
if (!ec.privateKeyVerify(privateKey))
throw new Error('Private key is invalid.');
if (!this.fingerPrint) if (!this.fingerPrint)
this.fingerPrint = utils.hash160(this.publicKey).slice(0, 4); this.fingerPrint = utils.hash160(this.publicKey).slice(0, 4);
@ -1176,8 +1182,8 @@ HDPrivateKey.prototype.fromSeed = function fromSeed(seed, network) {
assert(Buffer.isBuffer(seed)); assert(Buffer.isBuffer(seed));
if (seed.length < constants.hd.MIN_ENTROPY if (!(seed.length * 8 >= constants.hd.MIN_ENTROPY
|| seed.length > constants.hd.MAX_ENTROPY) { && seed.length * 8 <= constants.hd.MAX_ENTROPY)) {
throw new Error('Entropy not in range.'); throw new Error('Entropy not in range.');
} }
@ -1186,6 +1192,7 @@ HDPrivateKey.prototype.fromSeed = function fromSeed(seed, network) {
privateKey = hash.slice(0, 32); privateKey = hash.slice(0, 32);
chainCode = hash.slice(32, 64); chainCode = hash.slice(32, 64);
// Only a 1 in 2^127 chance of happening.
if (!ec.privateKeyVerify(privateKey)) if (!ec.privateKeyVerify(privateKey))
throw new Error('Master private key is invalid.'); throw new Error('Master private key is invalid.');
@ -1622,11 +1629,14 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
left = new bn(hash.slice(0, 32)); left = new bn(hash.slice(0, 32));
chainCode = hash.slice(32, 64); chainCode = hash.slice(32, 64);
point = ec.elliptic.curve.decodePoint(this.publicKey); point = ec.decodePoint(this.publicKey);
point = ec.elliptic.curve.g.mul(left).add(point); point = ec.curve.g.mul(left).add(point);
publicKey = new Buffer(point.encode('array', true)); publicKey = new Buffer(point.encode('array', true));
assert(publicKey.length === 33); assert(publicKey.length === 33);
if (!ec.publicKeyVerify(publicKey))
throw new Error('Public key is invalid.');
if (!this.fingerPrint) if (!this.fingerPrint)
this.fingerPrint = utils.hash160(this.publicKey).slice(0, 4); this.fingerPrint = utils.hash160(this.publicKey).slice(0, 4);

View File

@ -522,8 +522,8 @@ exports.rejectByVal = utils.revMap(exports.reject);
exports.hd = { exports.hd = {
HARDENED: 0x80000000, HARDENED: 0x80000000,
MAX_INDEX: 0x100000000, MAX_INDEX: 0x100000000,
MIN_ENTROPY: 128 / 8, MIN_ENTROPY: 128,
MAX_ENTROPY: 512 / 8 MAX_ENTROPY: 512
}; };
/** /**

View File

@ -178,7 +178,7 @@ Witness.prototype.getInputType = function getInputType() {
/** /**
* "Guess" the address of the witness. * "Guess" the address of the witness.
* This method is not 100% reliable. * This method is not 100% reliable.
* @returns {String|null} * @returns {Address|null}
*/ */
Witness.prototype.getInputAddress = function getInputAddress() { Witness.prototype.getInputAddress = function getInputAddress() {
@ -2807,7 +2807,7 @@ Script.prototype.getSize = function getSize() {
/** /**
* "Guess" the address of the input script. * "Guess" the address of the input script.
* This method is not 100% reliable. * This method is not 100% reliable.
* @returns {Base58Address|null} * @returns {Address|null}
*/ */
Script.prototype.getInputAddress = function getInputAddress() { Script.prototype.getInputAddress = function getInputAddress() {
@ -2818,7 +2818,7 @@ Script.prototype.getInputAddress = function getInputAddress() {
* Get the address of the script if present. Note that * Get the address of the script if present. Note that
* pubkey and multisig scripts will be treated as though * pubkey and multisig scripts will be treated as though
* they are pubkeyhash and scripthashes respectively. * they are pubkeyhash and scripthashes respectively.
* @returns {Base58Address|null} * @returns {Address|null}
*/ */
Script.prototype.getAddress = function getAddress() { Script.prototype.getAddress = function getAddress() {