hd rewrite.
This commit is contained in:
parent
2dede909c3
commit
c9bdad9bdf
806
lib/bcoin/hd.js
806
lib/bcoin/hd.js
@ -59,6 +59,8 @@ var constants = bcoin.protocol.constants;
|
||||
var network = bcoin.protocol.network;
|
||||
var KeyPair = require('./keypair');
|
||||
var LRU = require('./lru');
|
||||
var BufferWriter = require('./writer');
|
||||
var BufferReader = require('./reader');
|
||||
|
||||
var english = require('../../etc/english.json');
|
||||
|
||||
@ -91,7 +93,10 @@ HDSeed.prototype.createSeed = function createSeed() {
|
||||
if (!this.mnemonic)
|
||||
this.mnemonic = this.createMnemonic(this.entropy);
|
||||
|
||||
this.seed = utils.pbkdf2(this.mnemonic, 'mnemonic' + this.passphrase, 2048, 64);
|
||||
this.seed = utils.pbkdf2(
|
||||
this.mnemonic,
|
||||
'mnemonic' + this.passphrase,
|
||||
2048, 64);
|
||||
|
||||
return this.seed;
|
||||
};
|
||||
@ -126,22 +131,29 @@ function HD(options) {
|
||||
return new HDPrivateKey(options);
|
||||
}
|
||||
|
||||
HD.generate = function generate(privateKey, entropy) {
|
||||
return HDPrivateKey.generate(privateKey, entropy);
|
||||
HD.fromBase58 = function fromBase58(xkey) {
|
||||
if (HDPrivateKey.isExtended(xkey))
|
||||
return HDPrivateKey.fromBase58(xkey);
|
||||
return HDPublicKey.fromBase58(xkey);
|
||||
};
|
||||
|
||||
HD.fromSeed = function fromSeed(options) {
|
||||
return HDPrivateKey.fromSeed(options);
|
||||
HD.generate = function generate(options, networkType) {
|
||||
return HDPrivateKey.generate(options, networkType);
|
||||
};
|
||||
|
||||
HD.fromSeed = function fromSeed(options, networkType) {
|
||||
return HDPrivateKey.fromSeed(options, networkType);
|
||||
};
|
||||
|
||||
HD.cache = new LRU(500);
|
||||
|
||||
HD.isHD = function isHD(obj) {
|
||||
return HDPrivateKey.isHDPrivateKey(obj) || HDPublicKey.isHDPublicKey(obj);
|
||||
return HDPrivateKey.isHDPrivateKey(obj)
|
||||
|| HDPublicKey.isHDPublicKey(obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* HD Private Key
|
||||
* HDPrivateKey
|
||||
*/
|
||||
|
||||
function HDPrivateKey(options) {
|
||||
@ -150,11 +162,10 @@ function HDPrivateKey(options) {
|
||||
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);
|
||||
|
||||
if (HDPrivateKey.isExtended(options))
|
||||
options = { xkey: options };
|
||||
|
||||
@ -167,39 +178,125 @@ function HDPrivateKey(options) {
|
||||
if (HDPublicKey.isExtended(options.xkey))
|
||||
return new HDPublicKey(options);
|
||||
|
||||
this.network = options.network || network.type;
|
||||
this.networkType = options.networkType || network.type;
|
||||
this.xprivkey = options.xkey;
|
||||
this.seed = options.seed;
|
||||
|
||||
if (options.xkey) {
|
||||
data = this._unbuild(options.xkey);
|
||||
} else if (options.seed) {
|
||||
data = this._seed(options.seed);
|
||||
} else {
|
||||
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.');
|
||||
}
|
||||
assert(data);
|
||||
|
||||
data = this._normalize(data);
|
||||
assert(data.depth <= 0xff, 'Depth is too high.');
|
||||
|
||||
this.data = data;
|
||||
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._build(data);
|
||||
this.publicKey = ec.publicKeyCreate(data.privateKey, true);
|
||||
this.fingerPrint = null;
|
||||
|
||||
if (utils.readU32BE(data.parentFingerPrint) === 0)
|
||||
this.isMaster = true;
|
||||
else
|
||||
this.isMaster = false;
|
||||
this.hdPrivateKey = this;
|
||||
|
||||
if (!this.xprivkey)
|
||||
this.xprivkey = HDPrivateKey.render(data);
|
||||
|
||||
this.isPrivate = true;
|
||||
this.isPublic = false;
|
||||
}
|
||||
|
||||
HDPublicKey.isHDPrivateKey = function isHDPrivateKey(obj) {
|
||||
return obj && obj.isPrivate && typeof obj._unbuild === 'function';
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._hdPublicKey;
|
||||
});
|
||||
|
||||
HDPrivateKey.prototype.__defineGetter__('xpubkey', function() {
|
||||
return this.hdPublicKey.xpubkey;
|
||||
});
|
||||
|
||||
HDPrivateKey.prototype.derive = function derive(index, hardened) {
|
||||
var cached, p, data, hash, leftPart, chainCode, privateKey, child;
|
||||
|
||||
if (typeof index === 'string')
|
||||
return this.derivePath(index);
|
||||
|
||||
cached = HD.cache.get(this.xprivkey + '/' + index);
|
||||
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
hardened = index >= constants.hd.hardened ? true : hardened;
|
||||
if (index < constants.hd.hardened && hardened)
|
||||
index += constants.hd.hardened;
|
||||
|
||||
p = new BufferWriter();
|
||||
|
||||
if (hardened) {
|
||||
p.writeU8(0);
|
||||
p.writeBytes(this.privateKey);
|
||||
p.writeU32BE(index);
|
||||
} else {
|
||||
p.writeBytes(this.publicKey);
|
||||
p.writeU32BE(index);
|
||||
}
|
||||
|
||||
data = p.render();
|
||||
|
||||
hash = utils.sha512hmac(data, this.chainCode);
|
||||
leftPart = new bn(hash.slice(0, 32));
|
||||
chainCode = hash.slice(32, 64);
|
||||
|
||||
privateKey = leftPart
|
||||
.add(new bn(this.privateKey))
|
||||
.mod(ec.elliptic.curve.n)
|
||||
.toBuffer('be', 32);
|
||||
|
||||
if (!this.fingerPrint) {
|
||||
this.fingerPrint = utils.ripesha(this.publicKey)
|
||||
.slice(0, constants.hd.parentFingerPrintSize);
|
||||
}
|
||||
|
||||
child = new HDPrivateKey({
|
||||
networkType: this.networkType,
|
||||
data: {
|
||||
version: this.version,
|
||||
depth: this.depth + 1,
|
||||
parentFingerPrint: this.fingerPrint,
|
||||
childIndex: index,
|
||||
chainCode: chainCode,
|
||||
privateKey: privateKey
|
||||
}
|
||||
});
|
||||
|
||||
HD.cache.set(this.xprivkey + '/' + index, child);
|
||||
|
||||
return child;
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.deriveAccount44 = function deriveAccount44(options) {
|
||||
var coinType, accountIndex, child;
|
||||
|
||||
@ -215,7 +312,7 @@ HDPrivateKey.prototype.deriveAccount44 = function deriveAccount44(options) {
|
||||
}
|
||||
|
||||
if (coinType == null)
|
||||
coinType = network[this.network].type === 'main' ? 0 : 1;
|
||||
coinType = this.networkType === 'main' ? 0 : 1;
|
||||
|
||||
assert(utils.isFinite(coinType));
|
||||
assert(utils.isFinite(accountIndex));
|
||||
@ -246,15 +343,15 @@ HDPrivateKey.prototype.derivePurpose45 = function derivePurpose45() {
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.isPurpose45 = function isPurpose45() {
|
||||
if (utils.readU8(this.depth) !== 1)
|
||||
if (this.depth !== 1)
|
||||
return false;
|
||||
return utils.readU32BE(this.childIndex) === constants.hd.hardened + 45;
|
||||
return this.childIndex === constants.hd.hardened + 45;
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.isAccount44 = function isAccount44() {
|
||||
if (utils.readU32BE(this.childIndex) < constants.hd.hardened)
|
||||
if (this.childIndex < constants.hd.hardened)
|
||||
return false;
|
||||
return utils.readU8(this.depth) === 3;
|
||||
return this.depth === 3;
|
||||
};
|
||||
|
||||
HDPrivateKey.isExtended = function isExtended(data) {
|
||||
@ -264,258 +361,6 @@ HDPrivateKey.isExtended = function isExtended(data) {
|
||||
return network.xprivkeys[data.slice(0, 4)];
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype._normalize = function _normalize(data) {
|
||||
if (!data.version) {
|
||||
data.version = (this instanceof HDPrivateKey)
|
||||
? network[this.network].prefixes.xprivkey
|
||||
: network[this.network].prefixes.xpubkey;
|
||||
}
|
||||
|
||||
// version = uint_32be
|
||||
if (typeof data.version === 'number')
|
||||
data.version = array32(data.version);
|
||||
|
||||
// depth = unsigned char
|
||||
if (typeof data.depth === 'number')
|
||||
data.depth = new Buffer([data.depth]);
|
||||
|
||||
if (data.depth.length > 1)
|
||||
throw new Error('Depth is too high');
|
||||
|
||||
// parent finger print = uint_32be
|
||||
if (typeof data.parentFingerPrint === 'number')
|
||||
data.parentFingerPrint = array32(data.parentFingerPrint);
|
||||
|
||||
// child index = uint_32be
|
||||
if (typeof data.childIndex === 'number')
|
||||
data.childIndex = array32(data.childIndex);
|
||||
|
||||
// chain code = 32 bytes
|
||||
if (typeof data.chainCode === 'string')
|
||||
data.chainCode = new Buffer(data.chainCode, 'hex');
|
||||
|
||||
// checksum = 4 bytes
|
||||
if (typeof data.checksum === 'string')
|
||||
data.checksum = new Buffer(data.checksum, 'hex');
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype._seed = function _seed(seed) {
|
||||
var hash;
|
||||
|
||||
if (seed instanceof HDSeed)
|
||||
seed = seed.createSeed();
|
||||
|
||||
if (utils.isHex(seed))
|
||||
seed = new Buffer(seed, 'hex');
|
||||
|
||||
if (seed.length < constants.hd.minEntropy
|
||||
|| seed.length > constants.hd.maxEntropy) {
|
||||
throw new Error('entropy not in range');
|
||||
}
|
||||
|
||||
hash = utils.sha512hmac(seed, 'Bitcoin seed');
|
||||
|
||||
return {
|
||||
version: array32(network[this.network].prefixes.xprivkey),
|
||||
depth: new Buffer([0]),
|
||||
parentFingerPrint: new Buffer([0, 0, 0, 0]),
|
||||
childIndex: new Buffer([0, 0, 0, 0]),
|
||||
chainCode: hash.slice(32, 64),
|
||||
privateKey: hash.slice(0, 32),
|
||||
checksum: null
|
||||
};
|
||||
};
|
||||
|
||||
HDPrivateKey.fromSeed = function fromSeed(options) {
|
||||
var seed = (options instanceof HDSeed) ? options : new HDSeed(options);
|
||||
return new HDPrivateKey({ seed: seed });
|
||||
};
|
||||
|
||||
HDPrivateKey._generate = function _generate(privateKey, entropy) {
|
||||
if (!privateKey)
|
||||
privateKey = ec.generatePrivateKey();
|
||||
|
||||
if (!entropy)
|
||||
entropy = ec.random(32);
|
||||
|
||||
return {
|
||||
version: array32(network.prefixes.xprivkey),
|
||||
depth: new Buffer([0]),
|
||||
parentFingerPrint: new Buffer([0, 0, 0, 0]),
|
||||
childIndex: new Buffer([0, 0, 0, 0]),
|
||||
chainCode: entropy,
|
||||
privateKey: privateKey,
|
||||
checksum: null
|
||||
};
|
||||
};
|
||||
|
||||
HDPrivateKey.generate = function generate(privateKey, entropy) {
|
||||
return new HDPrivateKey(HDPrivateKey._generate(privateKey, entropy));
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype._generate = function _generate(privateKey, entropy) {
|
||||
var data = HDPrivateKey._generate(privateKey, entropy);
|
||||
data.version = array32(network[this.network].prefixes.xprivkey);
|
||||
return data;
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype._unbuild = function _unbuild(xkey) {
|
||||
var raw = utils.fromBase58(xkey);
|
||||
var data = {};
|
||||
var off = 0;
|
||||
var hash;
|
||||
|
||||
data.version = raw.slice(off, off + 4);
|
||||
off += 4;
|
||||
data.depth = raw.slice(off, off + 1);
|
||||
off += 1;
|
||||
data.parentFingerPrint = raw.slice(off, off + 4);
|
||||
off += 4;
|
||||
data.childIndex = raw.slice(off, off + 4);
|
||||
off += 4;
|
||||
data.chainCode = raw.slice(off, off + 32);
|
||||
off += 32;
|
||||
off += 1; // nul byte
|
||||
data.privateKey = raw.slice(off, off + 32);
|
||||
off += 32;
|
||||
data.checksum = raw.slice(off, off + 4);
|
||||
off += 4;
|
||||
|
||||
hash = utils.dsha256(raw.slice(0, -4)).slice(0, 4);
|
||||
if (!utils.isEqual(data.checksum, hash))
|
||||
throw new Error('checksum mismatch');
|
||||
|
||||
network.types.some(function(type) {
|
||||
if (utils.readU32BE(data.version) === network[type].prefixes.xprivkey) {
|
||||
this.network = type;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, this);
|
||||
|
||||
this.xprivkey = xkey;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype._build = function _build(data) {
|
||||
var off = 0;
|
||||
var sequence, checksum;
|
||||
|
||||
if (!this.xprivkey) {
|
||||
sequence = new Buffer(82);
|
||||
off += utils.copy(data.version, sequence, off);
|
||||
off += utils.copy(data.depth, sequence, off);
|
||||
off += utils.copy(data.parentFingerPrint, sequence, off);
|
||||
off += utils.copy(data.childIndex, sequence, off);
|
||||
off += utils.copy(data.chainCode, sequence, off);
|
||||
off += utils.writeU8(sequence, 0, off);
|
||||
off += utils.copy(data.privateKey, sequence, off);
|
||||
assert(off === 78, off);
|
||||
checksum = utils.dsha256(sequence.slice(0, off)).slice(0, 4);
|
||||
off += utils.copy(checksum, sequence, off);
|
||||
assert(off === 82, off);
|
||||
|
||||
this.xprivkey = utils.toBase58(sequence);
|
||||
}
|
||||
|
||||
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.checksum = null;
|
||||
|
||||
this.publicKey = ec.publicKeyCreate(data.privateKey, true);
|
||||
this.fingerPrint = null;
|
||||
|
||||
this.hdPrivateKey = this;
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.__defineGetter__('hdPublicKey', function() {
|
||||
if (!this._hdPublicKey) {
|
||||
this._hdPublicKey = new HDPublicKey({
|
||||
network: this.network,
|
||||
data: {
|
||||
version: array32(network[this.network].prefixes.xpubkey),
|
||||
depth: this.depth,
|
||||
parentFingerPrint: this.parentFingerPrint,
|
||||
childIndex: this.childIndex,
|
||||
chainCode: this.chainCode,
|
||||
checksum: this.checksum,
|
||||
publicKey: this.publicKey
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._hdPublicKey;
|
||||
});
|
||||
|
||||
HDPrivateKey.prototype.__defineGetter__('xpubkey', function() {
|
||||
return this.hdPublicKey.xpubkey;
|
||||
});
|
||||
|
||||
HDPrivateKey.prototype.derive = function derive(index, hardened) {
|
||||
var cached, data, hash, leftPart, chainCode, privateKey, child;
|
||||
var off = 0;
|
||||
|
||||
if (typeof index === 'string')
|
||||
return this.derivePath(index);
|
||||
|
||||
cached = HD.cache.get(this.xprivkey + '/' + index);
|
||||
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
hardened = index >= constants.hd.hardened ? true : hardened;
|
||||
if (index < constants.hd.hardened && hardened)
|
||||
index += constants.hd.hardened;
|
||||
|
||||
if (hardened) {
|
||||
data = new Buffer(1 + this.privateKey.length + 4);
|
||||
off += utils.writeU8(data, 0, off);
|
||||
off += utils.copy(this.privateKey, data, off);
|
||||
off += utils.writeU32BE(data, index, off);
|
||||
} else {
|
||||
data = new Buffer(this.publicKey.length + 4);
|
||||
off += utils.copy(this.publicKey, data, off);
|
||||
off += utils.writeU32BE(data, index, off);
|
||||
}
|
||||
|
||||
hash = utils.sha512hmac(data, this.chainCode);
|
||||
leftPart = new bn(hash.slice(0, 32));
|
||||
chainCode = hash.slice(32, 64);
|
||||
|
||||
privateKey = leftPart
|
||||
.add(new bn(this.privateKey))
|
||||
.mod(ec.elliptic.curve.n)
|
||||
.toBuffer('be', 32);
|
||||
|
||||
if (!this.fingerPrint) {
|
||||
this.fingerPrint = utils.ripesha(this.publicKey)
|
||||
.slice(0, constants.hd.parentFingerPrintSize);
|
||||
}
|
||||
|
||||
child = new HDPrivateKey({
|
||||
network: this.network,
|
||||
data: {
|
||||
version: this.version,
|
||||
depth: utils.readU8(this.depth) + 1,
|
||||
parentFingerPrint: this.fingerPrint,
|
||||
childIndex: index,
|
||||
chainCode: chainCode,
|
||||
privateKey: privateKey,
|
||||
checksum: null
|
||||
}
|
||||
});
|
||||
|
||||
HD.cache.set(this.xprivkey + '/' + index, child);
|
||||
|
||||
return child;
|
||||
};
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||
HDPrivateKey._getIndexes = function _getIndexes(path) {
|
||||
var steps = path.split('/');
|
||||
@ -580,6 +425,128 @@ HDPrivateKey.prototype.derivePath = function derivePath(path) {
|
||||
}, this);
|
||||
};
|
||||
|
||||
HDPrivateKey._fromSeed = function _fromSeed(seed, networkType) {
|
||||
var data = seed.createSeed();
|
||||
var hash;
|
||||
|
||||
if (data.length < constants.hd.minEntropy
|
||||
|| data.length > constants.hd.maxEntropy) {
|
||||
throw new Error('Entropy not in range.');
|
||||
}
|
||||
|
||||
hash = utils.sha512hmac(data, 'Bitcoin seed');
|
||||
|
||||
return {
|
||||
version: networkType
|
||||
? network[networkType].prefixes.xprivkey
|
||||
: network.prefixes.xprivkey,
|
||||
depth: 0,
|
||||
parentFingerPrint: new Buffer([0, 0, 0, 0]),
|
||||
childIndex: 0,
|
||||
chainCode: hash.slice(32, 64),
|
||||
privateKey: hash.slice(0, 32)
|
||||
};
|
||||
};
|
||||
|
||||
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._generate = function _generate(options, networkType) {
|
||||
if (!options)
|
||||
opitons = {};
|
||||
|
||||
if (Buffer.isBuffer(options))
|
||||
options = { privateKey: options };
|
||||
|
||||
if (!options.privateKey)
|
||||
options.privateKey = ec.generatePrivateKey();
|
||||
|
||||
if (!options.entropy)
|
||||
options.entropy = ec.random(32);
|
||||
|
||||
return {
|
||||
version: networkType
|
||||
? network[networkType].prefixes.xprivkey
|
||||
: network.prefixes.xprivkey,
|
||||
depth: 0,
|
||||
parentFingerPrint: new Buffer([0, 0, 0, 0]),
|
||||
childIndex: 0,
|
||||
chainCode: entropy,
|
||||
privateKey: privateKey
|
||||
};
|
||||
};
|
||||
|
||||
HDPrivateKey.generate = function generate(options, networkType) {
|
||||
return new HDPrivateKey({
|
||||
data: HDPrivateKey._generate(options, networkType)
|
||||
});
|
||||
};
|
||||
|
||||
HDPrivateKey.parse = function parse(xkey) {
|
||||
var raw = utils.fromBase58(xkey);
|
||||
var p = new BufferReader(raw, true);
|
||||
var data = {};
|
||||
var i, type, prefix;
|
||||
|
||||
data.version = p.readU32BE();
|
||||
data.depth = p.readU8();
|
||||
data.parentFingerPrint = p.readBytes(4);
|
||||
data.childIndex = p.readU32BE();
|
||||
data.chainCode = p.readBytes(32);
|
||||
p.readU8();
|
||||
data.privateKey = p.readBytes(32);
|
||||
p.verifyChecksum();
|
||||
|
||||
for (i = 0; i < network.types.length; i++) {
|
||||
type = network.types[i];
|
||||
prefix = network[type].prefixes.xprivkey;
|
||||
if (data.version === prefix)
|
||||
break;
|
||||
}
|
||||
|
||||
assert(i < network.types.length, 'Network not found.');
|
||||
|
||||
return {
|
||||
networkType: type,
|
||||
xprivkey: xkey,
|
||||
data: data
|
||||
};
|
||||
};
|
||||
|
||||
HDPrivateKey.fromBase58 = function fromBase58(xkey) {
|
||||
var data = HDPrivateKey.parse(xkey);
|
||||
return new HDPrivateKey(data);
|
||||
};
|
||||
|
||||
HDPrivateKey.render = function render(data) {
|
||||
var p = new BufferWriter();
|
||||
p.writeU32BE(data.version);
|
||||
p.writeU8(data.depth);
|
||||
p.writeBytes(data.parentFingerPrint);
|
||||
p.writeU32BE(data.childIndex);
|
||||
p.writeBytes(data.chainCode);
|
||||
p.writeU8(0);
|
||||
p.writeBytes(data.privateKey);
|
||||
p.writeChecksum();
|
||||
return utils.toBase58(p.render());
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.toJSON = function toJSON(passphrase) {
|
||||
var json = {
|
||||
v: 1,
|
||||
@ -647,27 +614,31 @@ HDPrivateKey._fromJSON = function _fromJSON(json, passphrase) {
|
||||
};
|
||||
|
||||
HDPrivateKey.fromJSON = function fromJSON(json, passphrase) {
|
||||
var key;
|
||||
|
||||
json = HDPrivateKey._fromJSON(json, passphrase);
|
||||
|
||||
if (json.xprivkey) {
|
||||
return new HDPrivateKey({
|
||||
xkey: json.xprivkey,
|
||||
seed: json.seed ? new HDSeed(json.seed) : null
|
||||
});
|
||||
key = HDPrivateKey.fromBase58(json.xprivkey);
|
||||
key.seed = json.seed ? new HDSeed(json.seed) : null;
|
||||
return key;
|
||||
}
|
||||
|
||||
if (json.seed)
|
||||
return HDPrivateKey.fromSeed(json.seed);
|
||||
|
||||
if (json.xpubkey) {
|
||||
return new HDPublicKey({
|
||||
xkey: json.xpubkey
|
||||
});
|
||||
}
|
||||
if (json.xpubkey)
|
||||
return HDPublicKey.fromBase58(json.xprivkey);
|
||||
|
||||
assert(false, 'Could not handle HD key JSON.');
|
||||
};
|
||||
|
||||
HDPublicKey.isHDPrivateKey = function isHDPrivateKey(obj) {
|
||||
return obj && obj.isPrivate && typeof obj.derive === 'function';
|
||||
};
|
||||
|
||||
/**
|
||||
* HD Public Key
|
||||
* HDPublicKey
|
||||
*/
|
||||
|
||||
function HDPublicKey(options) {
|
||||
@ -676,9 +647,7 @@ function HDPublicKey(options) {
|
||||
if (!(this instanceof HDPublicKey))
|
||||
return new HDPublicKey(options);
|
||||
|
||||
if (!options)
|
||||
throw new Error('No options for HDPublicKey');
|
||||
|
||||
assert(options, 'No options for HDPublicKey');
|
||||
assert(!(options instanceof HDPrivateKey));
|
||||
assert(!(options instanceof HDPublicKey));
|
||||
|
||||
@ -694,120 +663,27 @@ function HDPublicKey(options) {
|
||||
if (HDPrivateKey.isExtended(options.xkey))
|
||||
throw new Error('Cannot pass xprivkey into HDPublicKey');
|
||||
|
||||
this.network = options.network || network.type;
|
||||
this.networkType = options.networkType || network.type;
|
||||
this.xpubkey = options.xkey;
|
||||
|
||||
data = options.xkey
|
||||
? this._unbuild(options.xkey)
|
||||
: options.data;
|
||||
assert(data);
|
||||
|
||||
data = this._normalize(data);
|
||||
|
||||
this.data = data;
|
||||
|
||||
this._build(data);
|
||||
|
||||
if (utils.readU32BE(data.parentFingerPrint) === 0)
|
||||
this.isMaster = true;
|
||||
else
|
||||
this.isMaster = false;
|
||||
|
||||
this.isPrivate = false;
|
||||
this.isPublic = true;
|
||||
}
|
||||
|
||||
utils.inherits(HDPublicKey, HD);
|
||||
|
||||
HDPublicKey.isHDPublicKey = function isHDPublicKey(obj) {
|
||||
return obj && obj.isPublic && typeof obj._unbuild === 'function';
|
||||
};
|
||||
|
||||
HDPublicKey.prototype.deriveAccount44 = HDPrivateKey.prototype.deriveAccount44;
|
||||
HDPublicKey.prototype.derivePurpose45 = HDPrivateKey.prototype.derivePurpose45;
|
||||
HDPublicKey.prototype.isPurpose45 = HDPrivateKey.prototype.isPurpose45;
|
||||
HDPublicKey.prototype.isAccount44 = HDPrivateKey.prototype.isAccount44;
|
||||
HDPublicKey.prototype.toJSON = HDPrivateKey.prototype.toJSON;
|
||||
HDPublicKey.fromJSON = HDPrivateKey.fromJSON;
|
||||
|
||||
HDPublicKey.isExtended = function isExtended(data) {
|
||||
if (typeof data !== 'string')
|
||||
return false;
|
||||
|
||||
return network.xpubkeys[data.slice(0, 4)];
|
||||
};
|
||||
|
||||
HDPublicKey.prototype._normalize = HDPrivateKey.prototype._normalize;
|
||||
|
||||
HDPublicKey.prototype._unbuild = function _unbuild(xkey) {
|
||||
var raw = utils.fromBase58(xkey);
|
||||
var data = {};
|
||||
var off = 0;
|
||||
var hash;
|
||||
|
||||
data.version = raw.slice(off, off + 4);
|
||||
off += 4;
|
||||
data.depth = raw.slice(off, off + 1);
|
||||
off += 1;
|
||||
data.parentFingerPrint = raw.slice(off, off + 4);
|
||||
off += 4;
|
||||
data.childIndex = raw.slice(off, off + 4);
|
||||
off += 4;
|
||||
data.chainCode = raw.slice(off, off + 32);
|
||||
off += 32;
|
||||
data.publicKey = raw.slice(off, off + 33);
|
||||
off += 33;
|
||||
data.checksum = raw.slice(off, off + 4);
|
||||
off += 4;
|
||||
|
||||
hash = utils.dsha256(raw.slice(0, -4)).slice(0, 4);
|
||||
if (!utils.isEqual(data.checksum, hash))
|
||||
throw new Error('checksum mismatch');
|
||||
|
||||
network.types.some(function(type) {
|
||||
if (utils.readU32BE(data.version) === network[type].prefixes.xprivkey) {
|
||||
this.network = type;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, this);
|
||||
|
||||
this.xpubkey = xkey;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
HDPublicKey.prototype._build = function _build(data) {
|
||||
var off = 0;
|
||||
var sequence, checksum;
|
||||
|
||||
if (!this.xpubkey) {
|
||||
sequence = new Buffer(82);
|
||||
off += utils.copy(data.version, sequence, off);
|
||||
off += utils.copy(data.depth, sequence, off);
|
||||
off += utils.copy(data.parentFingerPrint, sequence, off);
|
||||
off += utils.copy(data.childIndex, sequence, off);
|
||||
off += utils.copy(data.chainCode, sequence, off);
|
||||
off += utils.copy(data.publicKey, sequence, off);
|
||||
assert(off === 78, off);
|
||||
checksum = utils.dsha256(sequence.slice(0, off)).slice(0, 4);
|
||||
off += utils.copy(checksum, sequence, off);
|
||||
assert(off === 82, off);
|
||||
|
||||
if (!data.checksum || !data.checksum.length)
|
||||
data.checksum = checksum;
|
||||
else if (utils.toHex(checksum) !== utils.toHex(data.checksum))
|
||||
throw new Error('checksum mismatch');
|
||||
|
||||
this.xpubkey = utils.toBase58(sequence);
|
||||
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.checksum = null;
|
||||
|
||||
this.privateKey = null;
|
||||
this.fingerPrint = null;
|
||||
@ -816,11 +692,18 @@ HDPublicKey.prototype._build = function _build(data) {
|
||||
|
||||
this.hdPrivateKey = null;
|
||||
this.xprivkey = null;
|
||||
};
|
||||
|
||||
if (!this.xpubkey)
|
||||
this.xpubkey = HDPublicKey.render(data);
|
||||
|
||||
this.isPrivate = false;
|
||||
this.isPublic = true;
|
||||
}
|
||||
|
||||
utils.inherits(HDPublicKey, HD);
|
||||
|
||||
HDPublicKey.prototype.derive = function derive(index, hardened) {
|
||||
var off = 0;
|
||||
var cached, data, hash, leftPart, chainCode;
|
||||
var cached, p, data, hash, leftPart, chainCode;
|
||||
var publicPoint, point, publicKey, child;
|
||||
|
||||
if (typeof index === 'string')
|
||||
@ -832,14 +715,15 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
|
||||
return cached;
|
||||
|
||||
if (index >= constants.hd.hardened || hardened)
|
||||
throw new Error('invalid index');
|
||||
throw new Error('Invalid index.');
|
||||
|
||||
if (index < 0)
|
||||
throw new Error('invalid path');
|
||||
throw new Error('Invalid path.');
|
||||
|
||||
data = new Buffer(this.publicKey.length + 4);
|
||||
off += utils.copy(this.publicKey, data, off);
|
||||
off += utils.writeU32BE(data, index, off);
|
||||
p = new BufferWriter();
|
||||
p.writeBytes(this.publicKey);
|
||||
p.writeU32BE(index);
|
||||
data = p.render();
|
||||
|
||||
hash = utils.sha512hmac(data, this.chainCode);
|
||||
leftPart = new bn(hash.slice(0, 32));
|
||||
@ -855,15 +739,14 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
|
||||
}
|
||||
|
||||
child = new HDPublicKey({
|
||||
network: this.network,
|
||||
networkType: this.networkType,
|
||||
data: {
|
||||
version: this.version,
|
||||
depth: utils.readU8(this.depth) + 1,
|
||||
depth: this.depth + 1,
|
||||
parentFingerPrint: this.fingerPrint,
|
||||
childIndex: index,
|
||||
chainCode: chainCode,
|
||||
publicKey: publicKey,
|
||||
checksum: null
|
||||
publicKey: publicKey
|
||||
}
|
||||
});
|
||||
|
||||
@ -872,9 +755,16 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
|
||||
return child;
|
||||
};
|
||||
|
||||
HDPublicKey.prototype.deriveAccount44 = HDPrivateKey.prototype.deriveAccount44;
|
||||
HDPublicKey.prototype.derivePurpose45 = HDPrivateKey.prototype.derivePurpose45;
|
||||
HDPublicKey.prototype.isPurpose45 = HDPrivateKey.prototype.isPurpose45;
|
||||
HDPublicKey.prototype.isAccount44 = HDPrivateKey.prototype.isAccount44;
|
||||
|
||||
HDPublicKey.isValidPath = function isValidPath(arg) {
|
||||
var indexes;
|
||||
|
||||
if (typeof arg === 'string') {
|
||||
var indexes = HDPrivateKey._getIndexes(arg);
|
||||
indexes = HDPrivateKey._getIndexes(arg);
|
||||
return indexes !== null && indexes.every(HDPublicKey.isValidPath);
|
||||
}
|
||||
|
||||
@ -885,20 +775,84 @@ HDPublicKey.isValidPath = function isValidPath(arg) {
|
||||
};
|
||||
|
||||
HDPublicKey.prototype.derivePath = function derivePath(path) {
|
||||
if (~path.indexOf('\''))
|
||||
throw new Error('cannot derive hardened');
|
||||
else if (!HDPublicKey.isValidPath(path))
|
||||
throw new Error('invalid path');
|
||||
var indexes;
|
||||
|
||||
var indexes = HDPrivateKey._getIndexes(path);
|
||||
if (path.indexOf('\'') !== -1)
|
||||
throw new Error('Cannot derive hardened.');
|
||||
|
||||
if (!HDPublicKey.isValidPath(path))
|
||||
throw new Error('Invalid path.');
|
||||
|
||||
indexes = HDPrivateKey._getIndexes(path);
|
||||
|
||||
return indexes.reduce(function(prev, index) {
|
||||
return prev.derive(index);
|
||||
}, this);
|
||||
};
|
||||
|
||||
HDPublicKey.prototype.toJSON = HDPrivateKey.prototype.toJSON;
|
||||
HDPublicKey.fromJSON = HDPrivateKey.fromJSON;
|
||||
|
||||
HDPublicKey.isExtended = function isExtended(data) {
|
||||
if (typeof data !== 'string')
|
||||
return false;
|
||||
|
||||
return network.xpubkeys[data.slice(0, 4)];
|
||||
};
|
||||
|
||||
HDPublicKey.parse = function parse(xkey) {
|
||||
var raw = utils.fromBase58(xkey);
|
||||
var p = new BufferReader(raw, true);
|
||||
var data = {};
|
||||
var i, type, prefix;
|
||||
|
||||
data.version = p.readU32BE();
|
||||
data.depth = p.readU8();
|
||||
data.parentFingerPrint = p.readBytes(4);
|
||||
data.childIndex = p.readU32BE();
|
||||
data.chainCode = p.readBytes(32);
|
||||
data.publicKey = p.readBytes(33);
|
||||
p.verifyChecksum();
|
||||
|
||||
for (i = 0; i < network.types.length; i++) {
|
||||
type = network.types[i];
|
||||
prefix = network[type].prefixes.xpubkey;
|
||||
if (data.version === prefix)
|
||||
break;
|
||||
}
|
||||
|
||||
assert(i < network.types.length, 'Network not found.');
|
||||
|
||||
return {
|
||||
networkType: type,
|
||||
xpubkey: xkey,
|
||||
data: data
|
||||
};
|
||||
};
|
||||
|
||||
HDPublicKey.render = function render(data) {
|
||||
var p = new BufferWriter();
|
||||
p.writeU32BE(data.version);
|
||||
p.writeU8(data.depth);
|
||||
p.writeBytes(data.parentFingerPrint);
|
||||
p.writeU32BE(data.childIndex);
|
||||
p.writeBytes(data.chainCode);
|
||||
p.writeBytes(data.publicKey);
|
||||
p.writeChecksum();
|
||||
return utils.toBase58(p.render());
|
||||
};
|
||||
|
||||
HDPublicKey.fromBase58 = function fromBase58(xkey) {
|
||||
var data = HDPublicKey.parse(xkey);
|
||||
return new HDPublicKey(data);
|
||||
};
|
||||
|
||||
HDPublicKey.isHDPublicKey = function isHDPublicKey(obj) {
|
||||
return obj && obj.isPublic && typeof obj.derive === 'function';
|
||||
};
|
||||
|
||||
/**
|
||||
* Make HD keys behave like elliptic KeyPairs
|
||||
* Make HD keys behave like KeyPairs
|
||||
*/
|
||||
|
||||
[HDPrivateKey, HDPublicKey].forEach(function(HD) {
|
||||
@ -925,16 +879,6 @@ HDPrivateKey.prototype.toSecret = function toSecret() {
|
||||
return KeyPair.toSecret.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function array32(data) {
|
||||
var b = new Buffer(4);
|
||||
utils.writeU32BE(b, data, 0);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
@ -283,6 +283,21 @@ BufferReader.prototype.seek = function seek(off) {
|
||||
return off;
|
||||
};
|
||||
|
||||
BufferReader.prototype.createChecksum = function createChecksum() {
|
||||
assert(this.offset + 4 >= 0);
|
||||
assert(this.offset + 4 <= this.data.length);
|
||||
var start = this.stack[this.stack.length - 1] || 0;
|
||||
var data = this.data.slice(start, this.offset);
|
||||
return utils.readU32BE(utils.checksum(data), 0);
|
||||
};
|
||||
|
||||
BufferReader.prototype.verifyChecksum = function verifyChecksum() {
|
||||
var chk = this.createChecksum();
|
||||
var checksum = this.readU32BE();
|
||||
assert(chk === checksum, 'Checksum mismatch.');
|
||||
return checksum;
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
@ -36,7 +36,7 @@ function Wallet(options) {
|
||||
}
|
||||
|
||||
if (!options.master)
|
||||
options.master = bcoin.hd.fromSeed();
|
||||
options.master = bcoin.hd.privateKey.fromSeed();
|
||||
|
||||
this.options = options;
|
||||
this.provider = options.provider || null;
|
||||
@ -188,9 +188,9 @@ Wallet.prototype.addKey = function addKey(key) {
|
||||
}
|
||||
|
||||
if (bcoin.hd.privateKey.isExtended(key))
|
||||
key = bcoin.hd.privateKey(key);
|
||||
key = bcoin.hd.privateKey.fromBase58(key);
|
||||
else if (bcoin.hd.publicKey.isExtended(key))
|
||||
key = bcoin.hd.publicKey(key);
|
||||
key = bcoin.hd.publicKey.fromBase58(key);
|
||||
|
||||
if (key instanceof bcoin.hd.privateKey)
|
||||
key = key.hdPublicKey;
|
||||
@ -229,9 +229,9 @@ Wallet.prototype.removeKey = function removeKey(key) {
|
||||
}
|
||||
|
||||
if (bcoin.hd.privateKey.isExtended(key))
|
||||
key = bcoin.hd.privateKey(key);
|
||||
key = bcoin.hd.privateKey.fromBase58(key);
|
||||
else if (bcoin.hd.publicKey.isExtended(key))
|
||||
key = bcoin.hd.publicKey(key);
|
||||
key = bcoin.hd.publicKey.fromBase58(key);
|
||||
|
||||
if (key instanceof bcoin.hd.privateKey)
|
||||
key = key.hdPublicKey;
|
||||
|
||||
@ -46,6 +46,7 @@ BufferWriter.prototype.render = function render(keep) {
|
||||
case '64be': off += utils.write64BE(data, item[1], off); break;
|
||||
case 'varint': off += utils.writeVarint(data, item[1], off); break;
|
||||
case 'bytes': off += utils.copy(item[1], data, off); break;
|
||||
case 'checksum': off += utils.copy(utils.checksum(data.slice(0, off)), data, off); break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +177,11 @@ BufferWriter.prototype.writeVarint = function writeVarint(value) {
|
||||
this.data.push(['varint', value]);
|
||||
};
|
||||
|
||||
BufferWriter.prototype.writeChecksum = function writeChecksum() {
|
||||
this.written += 4;
|
||||
this.data.push(['checksum']);
|
||||
};
|
||||
|
||||
BufferWriter.prototype.fill = function fill(value, size) {
|
||||
assert(size >= 0);
|
||||
var buf = new Buffer(size);
|
||||
|
||||
@ -37,7 +37,9 @@ describe('HD', function() {
|
||||
});
|
||||
|
||||
it('should create master private key', function() {
|
||||
master = bcoin.hd.priv({ seed: seed });
|
||||
var s = new bcoin.hd.seed();
|
||||
s.seed = new Buffer(seed, 'hex');
|
||||
master = bcoin.hd.priv.fromSeed(s);
|
||||
assert.equal(master.xprivkey, master_priv);
|
||||
assert.equal(master.xpubkey, master_pub);
|
||||
});
|
||||
@ -78,11 +80,11 @@ describe('HD', function() {
|
||||
});
|
||||
|
||||
it('should deserialize master private key', function() {
|
||||
master._unbuild(master.xprivkey);
|
||||
bcoin.hd.priv.parse(master.xprivkey);
|
||||
});
|
||||
|
||||
it('should deserialize master public key', function() {
|
||||
master.hdPublicKey._unbuild(master.hdPublicKey.xpubkey);
|
||||
bcoin.hd.pub.parse(master.hdPublicKey.xpubkey);
|
||||
});
|
||||
|
||||
it('should deserialize and reserialize', function() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user