refactor hd.

This commit is contained in:
Christopher Jeffrey 2016-04-19 10:39:15 -07:00
parent 77f203f7cb
commit c8e771d05b
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 140 additions and 173 deletions

View File

@ -91,6 +91,9 @@ function HDSeed(options) {
this.mnemonic = options.mnemonic;
this.passphrase = options.passphrase || '';
if (Buffer.isBuffer(options))
this.seed = options;
assert(this.bits % 8 === 0);
}
@ -163,7 +166,9 @@ HDSeed.isHDSeed = function isHDSeed(obj) {
*/
function HD(options) {
return new HDPrivateKey(options);
if (!options)
return HD.fromSeed();
return HD.fromAny(options);
}
/**
@ -202,6 +207,42 @@ HD.fromSeed = function fromSeed(options, networkType) {
return HDPrivateKey.fromSeed(options, networkType);
};
/**
* Generate an hdkey from any number of options.
* @param {Object|HDSeed} options - HD seed, HD seed
* options, buffer seed, or base58 key.
* @param {String?} networkType
* @returns {HDPrivateKey|HDPublicKey}
*/
HD.fromAny = function fromAny(options, networkType) {
var xkey;
assert(options, 'Options required.');
if (options.xkey)
xkey = options.xkey;
else if (options.xpubkey)
xkey = options.xpubkey;
else if (options.xprivkey)
xkey = options.xprivkey;
else
xkey = options;
if (HDPrivateKey.isExtended(xkey))
return HDPrivateKey.fromBase58(xkey);
if (HDPublicKey.isExtended(xkey))
return HDPublicKey.fromBase58(xkey);
return HDPrivateKey.fromSeed(options, networkType);
};
/**
* LRU cache to avoid deriving keys twice.
* @type {LRU}
*/
HD.cache = new LRU(500);
/**
@ -228,7 +269,7 @@ HD.isHD = function isHD(obj) {
* @param {Number?} options.childIndex
* @param {Buffer?} options.chainCode
* @param {Buffer?} options.privateKey
* @property {String} networkType
* @property {String} network
* @property {Base58String} xprivkey
* @property {Base58String} xpubkey
* @property {HDSeed?} seed
@ -239,67 +280,33 @@ HD.isHD = function isHD(obj) {
* @property {Buffer} chainCode
* @property {Buffer} privateKey
* @property {HDPublicKey} hdPublicKey
* @property {Boolean} isPrivate
* @property {Boolean} isPublic
*/
function HDPrivateKey(options) {
var data;
if (!(this instanceof HDPrivateKey))
return new HDPrivateKey(options);
assert(options, 'No options for HD private key.');
assert(!(options instanceof HDPrivateKey));
assert(!(options instanceof HDPublicKey));
assert(options.depth <= 0xff, 'Depth is too high.');
if (HDPrivateKey.isExtended(options))
options = { xkey: options };
if (options.xpubkey)
options.xkey = options.xpubkey;
if (options.xprivkey)
options.xkey = options.xprivkey;
if (HDPublicKey.isExtended(options.xkey))
return new HDPublicKey(options);
this.networkType = options.networkType || network.type;
this.xprivkey = options.xkey;
this.network = options.network || network.type;
this.xprivkey = options.xprivkey;
this.seed = options.seed;
if (options.data) {
data = options.data;
} else if (options.xkey) {
data = HDPrivateKey.parse(options.xkey);
this.networkType = data.networkType;
data = data.data;
} else if (options.seed) {
data = HDPrivateKey._fromSeed(options.seed, this.networkType);
} else {
assert(false, 'No data passed to HD key.');
}
this.version = options.version;
this.depth = options.depth;
this.parentFingerPrint = options.parentFingerPrint;
this.childIndex = options.childIndex;
this.chainCode = options.chainCode;
this.privateKey = options.privateKey;
assert(data.depth <= 0xff, 'Depth is too high.');
this.version = data.version;
this.depth = data.depth;
this.parentFingerPrint = data.parentFingerPrint;
this.childIndex = data.childIndex;
this.chainCode = data.chainCode;
this.privateKey = data.privateKey;
this.publicKey = ec.publicKeyCreate(data.privateKey, true);
this.publicKey = ec.publicKeyCreate(options.privateKey, true);
this.fingerPrint = null;
this.hdPrivateKey = this;
if (!this.xprivkey)
this.xprivkey = HDPrivateKey.render(data);
this.isPrivate = true;
this.isPublic = false;
this.xprivkey = HDPrivateKey.render(options);
}
utils.inherits(HDPrivateKey, HD);
@ -307,15 +314,13 @@ utils.inherits(HDPrivateKey, HD);
HDPrivateKey.prototype.__defineGetter__('hdPublicKey', function() {
if (!this._hdPublicKey) {
this._hdPublicKey = new HDPublicKey({
networkType: this.networkType,
data: {
version: network[this.networkType].prefixes.xpubkey,
depth: this.depth,
parentFingerPrint: this.parentFingerPrint,
childIndex: this.childIndex,
chainCode: this.chainCode,
publicKey: this.publicKey
}
network: this.network,
version: network[this.network].prefixes.xpubkey,
depth: this.depth,
parentFingerPrint: this.parentFingerPrint,
childIndex: this.childIndex,
chainCode: this.chainCode,
publicKey: this.publicKey
});
}
return this._hdPublicKey;
@ -375,15 +380,13 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) {
}
child = new HDPrivateKey({
networkType: this.networkType,
data: {
version: this.version,
depth: this.depth + 1,
parentFingerPrint: this.fingerPrint,
childIndex: index,
chainCode: chainCode,
privateKey: privateKey
}
network: this.network,
version: this.version,
depth: this.depth + 1,
parentFingerPrint: this.fingerPrint,
childIndex: index,
chainCode: chainCode,
privateKey: privateKey
});
HD.cache.set(this.xprivkey + '/' + index, child);
@ -413,7 +416,7 @@ HDPrivateKey.prototype.deriveAccount44 = function deriveAccount44(options) {
}
if (coinType == null)
coinType = this.networkType === 'main' ? 0 : 1;
coinType = this.network === 'main' ? 0 : 1;
assert(utils.isFinite(coinType));
assert(utils.isFinite(accountIndex));
@ -577,7 +580,7 @@ HDPrivateKey.prototype.derivePath = function derivePath(path) {
/**
* Create an hd private key from a seed.
* @param {HDSeed} seed
* @param {Buffer|HDSeed|Object} options - A buffer, HD seed, or HD seed options.
* @param {String?} networkType
* @returns {Object} A "naked" key (a
* plain javascript object which is suitable
@ -585,8 +588,20 @@ HDPrivateKey.prototype.derivePath = function derivePath(path) {
*/
HDPrivateKey._fromSeed = function _fromSeed(seed, networkType) {
var data = seed.createSeed();
var hash;
var data, hash;
if (!seed)
seed = {};
if (Buffer.isBuffer(seed)) {
data = seed;
seed = null;
} else if (seed instanceof HDSeed) {
data = seed.createSeed();
} else {
seed = new HDSeed(seed);
data = seed.createSeed();
}
if (data.length < constants.hd.MIN_ENTROPY
|| data.length > constants.hd.MAX_ENTROPY) {
@ -603,34 +618,20 @@ HDPrivateKey._fromSeed = function _fromSeed(seed, networkType) {
parentFingerPrint: new Buffer([0, 0, 0, 0]),
childIndex: 0,
chainCode: hash.slice(32, 64),
privateKey: hash.slice(0, 32)
privateKey: hash.slice(0, 32),
seed: seed
};
};
/**
* Instantiate a transaction from an HD seed.
* @param {HDSeed|Object} options - An HD seed or HD seed options.
* @param {Buffer|HDSeed|Object} seed - A buffer, HD seed, or HD seed options.
* @param {String?} networkType
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromSeed = function fromSeed(options, networkType) {
var seed, key;
if (!options)
options = {};
seed = (options instanceof HDSeed)
? options
: new HDSeed(options);
key = new HDPrivateKey({
data: HDPrivateKey._fromSeed(seed, networkType)
});
key.seed = seed;
return key;
HDPrivateKey.fromSeed = function fromSeed(seed, networkType) {
return new HDPrivateKey(HDPrivateKey._fromSeed(seed, networkType));
};
/**
@ -679,9 +680,7 @@ HDPrivateKey._generate = function _generate(options, networkType) {
*/
HDPrivateKey.generate = function generate(options, networkType) {
return new HDPrivateKey({
data: HDPrivateKey._generate(options, networkType)
});
return new HDPrivateKey(HDPrivateKey._generate(options, networkType));
};
/**
@ -714,11 +713,10 @@ HDPrivateKey.parse = function parse(xkey) {
assert(i < network.types.length, 'Network not found.');
return {
networkType: type,
xprivkey: xkey,
data: data
};
data.network = type;
data.xprivkey = xkey;
return data;
};
/**
@ -865,7 +863,7 @@ HDPrivateKey.fromJSON = function fromJSON(json, passphrase) {
*/
HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) {
return obj && obj.isPrivate && typeof obj.derive === 'function';
return obj && obj.xprivkey && typeof obj.derive === 'function';
};
/**
@ -880,7 +878,7 @@ HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) {
* @param {Number?} options.childIndex
* @param {Buffer?} options.chainCode
* @param {Buffer?} options.publicKey
* @property {String} networkType
* @property {String} network
* @property {Base58String} xpubkey
* @property {Number} version
* @property {Number} depth
@ -888,67 +886,34 @@ HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) {
* @property {Number} childIndex
* @property {Buffer} chainCode
* @property {Buffer} publicKey
* @property {Boolean} isPrivate
* @property {Boolean} isPublic
*/
function HDPublicKey(options) {
var data;
if (!(this instanceof HDPublicKey))
return new HDPublicKey(options);
assert(options, 'No options for HDPublicKey');
assert(!(options instanceof HDPrivateKey));
assert(!(options instanceof HDPublicKey));
assert(options.depth <= 0xff, 'Depth is too high.');
if (HDPublicKey.isExtended(options))
options = { xkey: options };
this.network = options.network || network.type;
this.xpubkey = options.xpubkey;
this.xprivkey = null;
if (options.xprivkey)
options.xkey = options.xprivkey;
if (options.xpubkey)
options.xkey = options.xpubkey;
if (HDPrivateKey.isExtended(options.xkey))
throw new Error('Cannot pass xprivkey into HDPublicKey');
this.networkType = options.networkType || network.type;
this.xpubkey = options.xkey;
if (options.data) {
data = options.data;
} else if (options.xkey) {
data = HDPublicKey.parse(options.xkey);
this.networkType = data.networkType;
data = data.data;
} else {
assert(false, 'No data passed to HD key.');
}
assert(data.depth <= 0xff, 'Depth is too high.');
this.version = data.version;
this.depth = data.depth;
this.parentFingerPrint = data.parentFingerPrint;
this.childIndex = data.childIndex;
this.chainCode = data.chainCode;
this.publicKey = data.publicKey;
this.version = options.version;
this.depth = options.depth;
this.parentFingerPrint = options.parentFingerPrint;
this.childIndex = options.childIndex;
this.chainCode = options.chainCode;
this.publicKey = options.publicKey;
this.privateKey = null;
this.fingerPrint = null;
this.hdPublicKey = this;
this.hdPrivateKey = null;
this.xprivkey = null;
if (!this.xpubkey)
this.xpubkey = HDPublicKey.render(data);
this.isPrivate = false;
this.isPublic = true;
this.xpubkey = HDPublicKey.render(options);
}
utils.inherits(HDPublicKey, HD);
@ -999,15 +964,13 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
}
child = new HDPublicKey({
networkType: this.networkType,
data: {
version: this.version,
depth: this.depth + 1,
parentFingerPrint: this.fingerPrint,
childIndex: index,
chainCode: chainCode,
publicKey: publicKey
}
network: this.network,
version: this.version,
depth: this.depth + 1,
parentFingerPrint: this.fingerPrint,
childIndex: index,
chainCode: chainCode,
publicKey: publicKey
});
HD.cache.set(this.xpubkey + '/' + index, child);
@ -1015,7 +978,6 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
return child;
};
/**
* Derive a BIP44 account key (does not derive, only ensures account key).
* @method
@ -1164,11 +1126,10 @@ HDPublicKey.parse = function parse(xkey) {
assert(i < network.types.length, 'Network not found.');
return {
networkType: type,
xpubkey: xkey,
data: data
};
data.network = type;
data.xpubkey = xkey;
return data;
};
/**
@ -1207,7 +1168,10 @@ HDPublicKey.fromBase58 = function fromBase58(xkey) {
*/
HDPublicKey.isHDPublicKey = function isHDPublicKey(obj) {
return obj && obj.isPublic && typeof obj.derive === 'function';
return obj
&& obj.xpubkey
&& !obj.xprivkey
&& typeof obj.derive === 'function';
};
[HDPrivateKey, HDPublicKey].forEach(function(HD) {

View File

@ -44,6 +44,9 @@ var assert = utils.assert;
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var BufferWriter = require('./writer');
var HD = bcoin.hd;
var HDPrivateKey = bcoin.hd.privateKey;
var HDPublicKey = bcoin.hd.publicKey;
/**
* HD BIP-44/45 wallet
@ -95,12 +98,12 @@ function Wallet(options) {
if (options.master
&& typeof options.master === 'object'
&& !(options.master instanceof bcoin.hd)) {
options.master = bcoin.hd(options.master);
&& !(options.master instanceof HD)) {
options.master = HD.fromAny(options.master);
}
if (!options.master)
options.master = bcoin.hd.privateKey.fromSeed();
options.master = HD.fromSeed();
this.options = options;
this.provider = options.provider || null;
@ -274,15 +277,15 @@ Wallet.prototype.addKey = function addKey(key) {
key = key.accountKey;
}
if (bcoin.hd.privateKey.isExtended(key))
key = bcoin.hd.privateKey.fromBase58(key);
else if (bcoin.hd.publicKey.isExtended(key))
key = bcoin.hd.publicKey.fromBase58(key);
if (HDPrivateKey.isExtended(key))
key = HDPrivateKey.fromBase58(key);
else if (HDPublicKey.isExtended(key))
key = HDPublicKey.fromBase58(key);
if (key instanceof bcoin.hd.privateKey)
if (key instanceof HDPrivateKey)
key = key.hdPublicKey;
assert(key instanceof bcoin.hd, 'Must add HD keys to wallet.');
assert(key instanceof HD, 'Must add HD keys to wallet.');
if (this.derivation === 'bip44') {
if (!key || !key.isAccount44())
@ -327,15 +330,15 @@ Wallet.prototype.removeKey = function removeKey(key) {
key = key.accountKey;
}
if (bcoin.hd.privateKey.isExtended(key))
key = bcoin.hd.privateKey.fromBase58(key);
else if (bcoin.hd.publicKey.isExtended(key))
key = bcoin.hd.publicKey.fromBase58(key);
if (HDPrivateKey.isExtended(key))
key = HDPrivateKey.fromBase58(key);
else if (HDPublicKey.isExtended(key))
key = HDPublicKey.fromBase58(key);
if (key instanceof bcoin.hd.privateKey)
if (key instanceof HDPrivateKey)
key = key.hdPublicKey;
assert(key instanceof bcoin.hd, 'Must add HD keys to wallet.');
assert(key instanceof HD, 'Must add HD keys to wallet.');
if (this.derivation === 'bip44') {
if (!key || !key.isAccount44())