896 lines
22 KiB
JavaScript
896 lines
22 KiB
JavaScript
/**
|
|
* hd.js - hd seeds and keys (BIP32, BIP39) for bcoin.
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* https://github.com/indutny/bcoin
|
|
* https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
|
* https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
|
*/
|
|
|
|
/**
|
|
* Code adapted from bitcore-lib:
|
|
* https://github.com/bitpay/bitcore-lib/blob/master/lib/hdprivatekey.js
|
|
* https://github.com/bitpay/bitcore-lib/blob/master/lib/hdpublickey.js
|
|
* https://github.com/ryanxcharles/fullnode/blob/master/lib/bip32.js
|
|
*
|
|
* Copyright (c) 2013-2015 BitPay, Inc.
|
|
*
|
|
* Parts of this software are based on Bitcoin Core
|
|
* Copyright (c) 2009-2015 The Bitcoin Core developers
|
|
*
|
|
* Parts of this software are based on fullnode
|
|
* Copyright (c) 2014 Ryan X. Charles
|
|
* Copyright (c) 2014 reddit, Inc.
|
|
*
|
|
* Parts of this software are based on BitcoinJS
|
|
* Copyright (c) 2011 Stefan Thomas <justmoon@members.fsf.org>
|
|
*
|
|
* Parts of this software are based on BitcoinJ
|
|
* Copyright (c) 2011 Google Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
/**
|
|
* Modules
|
|
*/
|
|
|
|
var bcoin = require('../bcoin');
|
|
var bn = require('bn.js');
|
|
var utils = require('./utils');
|
|
var ec = require('./ec');
|
|
var assert = utils.assert;
|
|
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');
|
|
|
|
/**
|
|
* HD Seeds
|
|
*/
|
|
|
|
function HDSeed(options) {
|
|
if (!(this instanceof HDSeed))
|
|
return new HDSeed(options);
|
|
|
|
if (!options)
|
|
options = {};
|
|
|
|
this.bits = options.bits || 128;
|
|
this.entropy = options.entropy;
|
|
this.mnemonic = options.mnemonic;
|
|
this.passphrase = options.passphrase || '';
|
|
|
|
assert(this.bits % 8 === 0);
|
|
}
|
|
|
|
HDSeed.prototype.createSeed = function createSeed() {
|
|
if (this.seed)
|
|
return this.seed;
|
|
|
|
if (!this.entropy)
|
|
this.entropy = ec.random(this.bits / 8);
|
|
|
|
if (!this.mnemonic)
|
|
this.mnemonic = this.createMnemonic(this.entropy);
|
|
|
|
this.seed = utils.pbkdf2(
|
|
this.mnemonic,
|
|
'mnemonic' + this.passphrase,
|
|
2048, 64);
|
|
|
|
return this.seed;
|
|
};
|
|
|
|
HDSeed.prototype.createMnemonic = function createMnemonic(entropy) {
|
|
var bin = '';
|
|
var mnemonic = [];
|
|
var i, wi;
|
|
|
|
for (i = 0; i < entropy.length; i++)
|
|
bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8);
|
|
|
|
for (i = 0; i < bin.length / 11; i++) {
|
|
wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2);
|
|
mnemonic.push(english[wi]);
|
|
}
|
|
|
|
return mnemonic.join(' ');
|
|
};
|
|
|
|
HDSeed.isHDSeed = function isHDSeed(obj) {
|
|
return obj
|
|
&& typeof obj.bits === 'number'
|
|
&& typeof obj.createSeed === 'function';
|
|
};
|
|
|
|
/**
|
|
* Abstract
|
|
*/
|
|
|
|
function HD(options) {
|
|
return new HDPrivateKey(options);
|
|
}
|
|
|
|
HD.fromBase58 = function fromBase58(xkey) {
|
|
if (HDPrivateKey.isExtended(xkey))
|
|
return HDPrivateKey.fromBase58(xkey);
|
|
return HDPublicKey.fromBase58(xkey);
|
|
};
|
|
|
|
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);
|
|
};
|
|
|
|
/**
|
|
* HDPrivateKey
|
|
*/
|
|
|
|
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));
|
|
|
|
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.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.');
|
|
}
|
|
|
|
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.fingerPrint = null;
|
|
|
|
this.hdPrivateKey = this;
|
|
|
|
if (!this.xprivkey)
|
|
this.xprivkey = HDPrivateKey.render(data);
|
|
|
|
this.isPrivate = true;
|
|
this.isPublic = false;
|
|
}
|
|
|
|
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;
|
|
|
|
if (typeof options === 'number')
|
|
options = { accountIndex: options };
|
|
|
|
coinType = options.coinType;
|
|
accountIndex = options.accountIndex;
|
|
|
|
if (this instanceof HDPublicKey) {
|
|
assert(this.isAccount44());
|
|
return this;
|
|
}
|
|
|
|
if (coinType == null)
|
|
coinType = this.networkType === 'main' ? 0 : 1;
|
|
|
|
assert(utils.isFinite(coinType));
|
|
assert(utils.isFinite(accountIndex));
|
|
|
|
child = this
|
|
.derive(44, true)
|
|
.derive(coinType, true)
|
|
.derive(accountIndex, true);
|
|
|
|
assert(child.isAccount44());
|
|
|
|
return child;
|
|
};
|
|
|
|
HDPrivateKey.prototype.derivePurpose45 = function derivePurpose45() {
|
|
var child;
|
|
|
|
if (this instanceof HDPublicKey) {
|
|
assert(this.isPurpose45());
|
|
return this;
|
|
}
|
|
|
|
child = this.derive(45, true);
|
|
|
|
assert(child.isPurpose45());
|
|
|
|
return child;
|
|
};
|
|
|
|
HDPrivateKey.prototype.isPurpose45 = function isPurpose45() {
|
|
if (this.depth !== 1)
|
|
return false;
|
|
return this.childIndex === constants.hd.hardened + 45;
|
|
};
|
|
|
|
HDPrivateKey.prototype.isAccount44 = function isAccount44() {
|
|
if (this.childIndex < constants.hd.hardened)
|
|
return false;
|
|
return this.depth === 3;
|
|
};
|
|
|
|
HDPrivateKey.isExtended = function isExtended(data) {
|
|
if (typeof data !== 'string')
|
|
return false;
|
|
|
|
return network.xprivkeys[data.slice(0, 4)];
|
|
};
|
|
|
|
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
|
HDPrivateKey._getIndexes = function _getIndexes(path) {
|
|
var steps = path.split('/');
|
|
var root = steps.shift();
|
|
var indexes = [];
|
|
var i, step, hardened, index;
|
|
|
|
if (~constants.hd.pathRoots.indexOf(path))
|
|
return indexes;
|
|
|
|
if (!~constants.hd.pathRoots.indexOf(root))
|
|
return null;
|
|
|
|
for (i = 0; i < steps.length; i++) {
|
|
step = steps[i];
|
|
hardened = step[step.length - 1] === '\'';
|
|
|
|
if (hardened)
|
|
step = step.slice(0, -1);
|
|
|
|
if (!step || step[0] === '-')
|
|
return null;
|
|
|
|
index = +step;
|
|
|
|
if (hardened)
|
|
index += constants.hd.hardened;
|
|
|
|
indexes.push(index);
|
|
}
|
|
|
|
return indexes;
|
|
};
|
|
|
|
HDPrivateKey.isValidPath = function isValidPath(path, hardened) {
|
|
var indexes;
|
|
|
|
if (typeof path === 'string') {
|
|
indexes = HDPrivateKey._getIndexes(path);
|
|
return indexes !== null && indexes.every(HDPrivateKey.isValidPath);
|
|
}
|
|
|
|
if (typeof path === 'number') {
|
|
if (path < constants.hd.hardened && hardened)
|
|
path += constants.hd.hardened;
|
|
return path >= 0 && path < constants.hd.maxIndex;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
HDPrivateKey.prototype.derivePath = function derivePath(path) {
|
|
var indexes;
|
|
|
|
if (!HDPrivateKey.isValidPath(path))
|
|
throw new Error('Invalid path.');
|
|
|
|
indexes = HDPrivateKey._getIndexes(path);
|
|
|
|
return indexes.reduce(function(prev, index) {
|
|
return prev.derive(index);
|
|
}, 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,
|
|
name: 'hdkey',
|
|
encrypted: false
|
|
};
|
|
|
|
if (this instanceof HDPrivateKey) {
|
|
json.encrypted = passphrase ? true : false;
|
|
if (this.seed) {
|
|
json.mnemonic = passphrase
|
|
? utils.encrypt(this.seed.mnemonic, passphrase)
|
|
: this.seed.mnemonic;
|
|
json.passphrase = passphrase
|
|
? utils.encrypt(this.seed.passphrase, passphrase)
|
|
: this.seed.passphrase;
|
|
}
|
|
json.xprivkey = passphrase
|
|
? utils.encrypt(this.xprivkey, passphrase)
|
|
: this.xprivkey;
|
|
return json;
|
|
}
|
|
|
|
json.xpubkey = this.xpubkey;
|
|
|
|
return json;
|
|
};
|
|
|
|
HDPrivateKey._fromJSON = function _fromJSON(json, passphrase) {
|
|
var data = {};
|
|
|
|
assert.equal(json.v, 1);
|
|
assert.equal(json.name, 'hdkey');
|
|
|
|
if (json.encrypted && !passphrase)
|
|
throw new Error('Cannot decrypt address');
|
|
|
|
if (json.mnemonic) {
|
|
data.seed = {
|
|
mnemonic: json.encrypted
|
|
? utils.decrypt(json.mnemonic, passphrase)
|
|
: json.mnemonic,
|
|
passphrase: json.encrypted
|
|
? utils.decrypt(json.passphrase, passphrase)
|
|
: json.passphrase
|
|
};
|
|
if (!json.xprivkey)
|
|
return data;
|
|
}
|
|
|
|
if (json.xprivkey) {
|
|
data.xprivkey = json.encrypted
|
|
? utils.decrypt(json.xprivkey, passphrase)
|
|
: json.xprivkey;
|
|
return data;
|
|
}
|
|
|
|
if (json.xpubkey) {
|
|
return {
|
|
xpubkey: json.xpubkey
|
|
};
|
|
}
|
|
|
|
assert(false);
|
|
};
|
|
|
|
HDPrivateKey.fromJSON = function fromJSON(json, passphrase) {
|
|
var key;
|
|
|
|
json = HDPrivateKey._fromJSON(json, passphrase);
|
|
|
|
if (json.xprivkey) {
|
|
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 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';
|
|
};
|
|
|
|
/**
|
|
* HDPublicKey
|
|
*/
|
|
|
|
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));
|
|
|
|
if (HDPublicKey.isExtended(options))
|
|
options = { xkey: options };
|
|
|
|
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.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;
|
|
}
|
|
|
|
utils.inherits(HDPublicKey, HD);
|
|
|
|
HDPublicKey.prototype.derive = function derive(index, hardened) {
|
|
var cached, p, data, hash, leftPart, chainCode;
|
|
var publicPoint, point, publicKey, child;
|
|
|
|
if (typeof index === 'string')
|
|
return this.derivePath(index);
|
|
|
|
cached = HD.cache.get(this.xpubkey + '/' + index);
|
|
|
|
if (cached)
|
|
return cached;
|
|
|
|
if (index >= constants.hd.hardened || hardened)
|
|
throw new Error('Invalid index.');
|
|
|
|
if (index < 0)
|
|
throw new Error('Invalid path.');
|
|
|
|
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));
|
|
chainCode = hash.slice(32, 64);
|
|
|
|
publicPoint = ec.elliptic.curve.decodePoint(this.publicKey);
|
|
point = ec.elliptic.curve.g.mul(leftPart).add(publicPoint);
|
|
publicKey = new Buffer(point.encode('array', true));
|
|
|
|
if (!this.fingerPrint) {
|
|
this.fingerPrint = utils.ripesha(this.publicKey)
|
|
.slice(0, constants.hd.parentFingerPrintSize);
|
|
}
|
|
|
|
child = new HDPublicKey({
|
|
networkType: this.networkType,
|
|
data: {
|
|
version: this.version,
|
|
depth: this.depth + 1,
|
|
parentFingerPrint: this.fingerPrint,
|
|
childIndex: index,
|
|
chainCode: chainCode,
|
|
publicKey: publicKey
|
|
}
|
|
});
|
|
|
|
HD.cache.set(this.xpubkey + '/' + index, child);
|
|
|
|
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') {
|
|
indexes = HDPrivateKey._getIndexes(arg);
|
|
return indexes !== null && indexes.every(HDPublicKey.isValidPath);
|
|
}
|
|
|
|
if (typeof arg === 'number')
|
|
return arg >= 0 && arg < constants.hd.hardened;
|
|
|
|
return false;
|
|
};
|
|
|
|
HDPublicKey.prototype.derivePath = function derivePath(path) {
|
|
var indexes;
|
|
|
|
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 KeyPairs
|
|
*/
|
|
|
|
[HDPrivateKey, HDPublicKey].forEach(function(HD) {
|
|
HD.prototype.getPrivateKey = function getPrivateKey() {
|
|
return KeyPair.prototype.getPrivateKey.apply(this, arguments);
|
|
};
|
|
|
|
HD.prototype.getPublicKey = function getPublicKey() {
|
|
return KeyPair.prototype.getPublicKey.apply(this, arguments);
|
|
};
|
|
|
|
HD.prototype.sign = function sign() {
|
|
return KeyPair.prototype.sign.apply(this, arguments);
|
|
};
|
|
|
|
HD.prototype.verify = function verify() {
|
|
return KeyPair.prototype.verify.apply(this, arguments);
|
|
};
|
|
|
|
HD.prototype.compressed = true;
|
|
});
|
|
|
|
HDPrivateKey.prototype.toSecret = function toSecret() {
|
|
return KeyPair.toSecret.call(this);
|
|
};
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
exports = HD;
|
|
|
|
exports.seed = HDSeed;
|
|
exports.priv = HDPrivateKey;
|
|
exports.pub = HDPublicKey;
|
|
exports.privateKey = HDPrivateKey;
|
|
exports.publicKey = HDPublicKey;
|
|
exports.fromJSON = HDPrivateKey.fromJSON;
|
|
|
|
module.exports = HD;
|