fcoin/lib/bcoin/hd.js
Christopher Jeffrey fa22c79dbe hd and wallet work.
2016-02-04 02:44:16 -08:00

1141 lines
29 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.
*/
var hd = exports;
/**
* Modules
*/
var bcoin = require('../bcoin');
var hash = require('hash.js');
var bn = require('bn.js');
var elliptic = require('elliptic');
var utils = bcoin.utils;
var assert = utils.assert;
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var EventEmitter = require('events').EventEmitter;
var english = require('../../etc/english.json');
var ec = elliptic.curves.secp256k1;
/**
* HD Seeds
*/
function HDSeed(options) {
if (!(this instanceof HDSeed))
return new HDSeed(options);
options = options || {};
this.bits = options.bits || 128;
this.entropy = options.entropy || HDSeed._entropy(this.bits / 8);
this.mnemonic = options.mnemonic || HDSeed._mnemonic(this.entropy);
this.seed = this.createSeed(options.passphrase);
}
HDSeed.create = function create(options) {
var obj = new HDSeed(options);
return obj.seed || obj;
};
HDSeed.prototype.createSeed = function createSeed(passphrase) {
this.passphrase = passphrase || '';
return pbkdf2(this.mnemonic, 'mnemonic' + passphrase, 2048, 64);
};
HDSeed._entropy = function _entropy(size) {
return randomBytes(size);
};
HDSeed._mnemonic = function _mnemonic(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(' ');
};
/**
* HD Private Key
*/
function HDPrivateKey(options) {
var data;
if (!(this instanceof HDPrivateKey))
return new HDPrivateKey(options);
if (!options)
options = { seed: bcoin.hd.seed() };
if (HDPrivateKey.isExtended(options))
options = { xkey: options };
if (HDPublicKey.isExtended(options))
return new HDPublicKey(options);
if (options.passphrase !== undefined
|| options.bits
|| options.entropy
|| options.mnemonic) {
options.seed = bcoin.hd.seed(options);
}
if (options.seed
&& typeof options.seed === 'object'
&& !utils.isBuffer(options.seed)
&& !(options.seed instanceof bcoin.hd.seed)) {
options.seed = bcoin.hd.seed(options.seed);
}
if (options.seed) {
this.seed = options.seed;
data = this._seed(options.seed);
} else if (options.xkey) {
data = this._unbuild(options.xkey);
} else {
data = options;
}
data = this._normalize(data, network.prefixes.xprivkey);
this.data = data;
this._build(data);
if (new bn(data.parentFingerPrint).cmpn(0) === 0) {
this.isMaster = true;
this.master = this;
} else {
this.master = options.master;
}
this.isPrivate = true;
}
HDPrivateKey.prototype.scan44 = function scan44(options, txByAddress, callback) {
var self = this;
var keys = [];
var coinType;
// 0. get the root node
if (!(this instanceof HDPublicKey)) {
coinType = options.coinType;
if (coinType == null)
coinType = network.type === 'main' ? 0 : 1;
assert(utils.isFinite(coinType));
root = this
.derive(44, true)
.derive(coinType, true);
}
return (function scanner(accountIndex) {
var addressIndex = 0;
var total = 0;
var gap = 0;
// 1. derive the first account's node (index = 0)
var account = (self instanceof HDPublicKey)
? self
: root.derive(accountIndex, true);
// 2. derive the external chain node of this account
var chain = account.derive(0);
// 3. scan addresses of the external chain;
// respect the gap limit described below
return (function next() {
var address = chain.derive(addressIndex++);
var addr = bcoin.address.hash2addr(
bcoin.address.key2hash(address.publicKey),
'pubkey');
return txByAddress(addr, function(err, txs) {
var result;
if (err)
return callback(err);
if (txs) {
if (typeof txs === 'boolean')
result = txs;
else if (Array.isArray(txs))
result = txs.length > 0;
else
result = false;
}
if (result) {
keys.push(address);
total++;
gap = 0;
return next();
}
if (++gap < 20)
return next();
// 4. if no transactions are found on the
// external chain, stop discovery
if (total === 0)
return callback(null, keys);
// 5. if there are some transactions, increase
// the account index and go to step 1
if (self instanceof HDPublicKey)
return callback(null, keys);
return scanner(accountIndex + 1);
});
})();
})(0);
};
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 = network.type === '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.deriveBIP44 = function deriveBIP44(options) {
var chain = options.chain;
var addressIndex = options.addressIndex;
if (chain == null)
chain = options.change ? 1 : 0;
assert(utils.isFinite(chain));
assert(utils.isFinite(addressIndex));
return this
.deriveAccount44(options)
.derive(chain)
.derive(addressIndex);
};
HDPrivateKey.prototype.deriveChange = function deriveChange(accountIndex, addressIndex) {
if (this instanceof HDPublicKey) {
addressIndex = accountIndex;
accountIndex = null;
}
return this.deriveBIP44({
accountIndex: accountIndex,
chain: 1,
addressIndex: addressIndex
});
};
HDPrivateKey.prototype.deriveAddress = function deriveAddress(accountIndex, addressIndex) {
if (this instanceof HDPublicKey) {
addressIndex = accountIndex;
accountIndex = null;
}
return this.deriveBIP44({
accountIndex: accountIndex,
chain: 0,
addressIndex: addressIndex
});
};
HDPrivateKey.prototype.scan45 = function scan45(options, txByAddress, callback) {
var keys = [];
var root;
root = this.derivePurpose45(options);
return (function chainCheck(chainConstant) {
return (function scanner(cosignerIndex) {
var addressIndex = 0;
var total = 0;
var gap = 0;
var cosigner = root.derive(cosignerIndex);
var chain = cosigner.derive(chainConstant);
return (function next() {
var address = chain.derive(addressIndex++);
var addr = bcoin.address.hash2addr(
bcoin.address.key2hash(address.publicKey),
'pubkey');
return txByAddress(addr, function(err, txs) {
var result;
if (err)
return callback(err);
if (txs) {
if (typeof txs === 'boolean')
result = txs;
else if (Array.isArray(txs))
result = txs.length > 0;
else
result = false;
}
if (result) {
keys.push(address);
total++;
gap = 0;
return next();
}
if (++gap < 20)
return next();
if (total === 0) {
if (chainConstant === 0)
return chainCheck(1);
return callback(null, keys);
}
return scanner(accountIndex + 1);
});
})();
})(0);
})(0);
};
HDPrivateKey.prototype.derivePurpose45 = function derivePurpose45(options) {
var child;
if (this instanceof HDPublicKey) {
assert(this.isPurpose45());
return this;
}
child = this.derive(45, true);
assert(child.isPurpose45());
return child;
};
HDPrivateKey.prototype.deriveBIP45 = function deriveBIP45(options) {
var cosignerIndex = options.cosignerIndex;
var chain = options.chain;
var addressIndex = options.addressIndex;
if (chain == null)
chain = options.change ? 1 : 0;
assert(utils.isFinite(cosignerIndex));
assert(utils.isFinite(chain));
assert(utils.isFinite(addressIndex));
return this
.derivePurpose45(options)
.derive(cosignerIndex)
.derive(chain)
.derive(addressIndex);
};
HDPrivateKey.prototype.deriveCosignerChange = function deriveCosignerChange(cosignerIndex, addressIndex) {
return this.deriveBIP45({
cosignerIndex: cosignerIndex,
chain: 1,
addressIndex: addressIndex
});
};
HDPrivateKey.prototype.deriveCosignerAddress = function deriveCosignerAddress(cosignerIndex, addressIndex) {
return this.deriveBIP45({
cosignerIndex: cosignerIndex,
chain: 0,
addressIndex: addressIndex
});
};
HDPrivateKey.prototype.isPurpose45 = function isPurpose45(options) {
return new bn(this.childIndex).toNumber() === constants.hd.hardened + 45;
};
HDPrivateKey.prototype.isAccount44 = function isAccount44(options) {
return new bn(this.depth).toNumber() === 3;
};
HDPrivateKey.getPath = function getPath(options) {
var purpose, coinType, accountIndex, chain, addressIndex;
if (!options)
options = {};
purpose = options.purpose;
coinType = options.coinType;
accountIndex = options.accountIndex;
chain = options.chain;
addressIndex = options.addressIndex;
if (purpose == null)
purpose = 44;
if (coinType == null)
coinType = network.type === 'main' ? 0 : 1;
if (chain == null)
chain = options.change ? 1 : 0;
return 'm/' + purpose + '\'/'
+ coinType + '\'/'
+ accountIndex + '\'/'
+ chain + '/'
+ addressIndex;
};
HDPrivateKey.isExtended = function isExtended(data) {
if (typeof data !== 'string')
return false;
return data.indexOf('xprv') === 0 || data.indexOf('tprv') === 0;
};
HDPrivateKey.prototype._normalize = function _normalize(data, version) {
data.version = version || network.prefixes.xprivkey;
data.privateKey = data.privateKey || data.priv;
data.publicKey = data.publicKey || data.pub;
// version = uint_32be
if (typeof data.version === 'string')
data.version = utils.toArray(data.version, 'hex');
else if (typeof data.version === 'number')
data.version = array32(data.version);
// depth = unsigned char
if (typeof data.depth === 'string')
data.depth = utils.toArray(data.depth, 'hex');
else if (typeof data.depth === 'number')
data.depth = [data.depth];
if (new bn(data.depth).toNumber() > 0xff)
throw new Error('Depth is too high');
// parent finger print = uint_32be
if (typeof data.parentFingerPrint === 'string')
data.parentFingerPrint = utils.toArray(data.parentFingerPrint, 'hex');
else if (typeof data.parentFingerPrint === 'number')
data.parentFingerPrint = array32(data.parentFingerPrint);
// child index = uint_32be
if (typeof data.childIndex === 'string')
data.childIndex = utils.toArray(data.childIndex, 'hex');
else if (typeof data.childIndex === 'number')
data.childIndex = array32(data.childIndex);
// chain code = 32 bytes
if (typeof data.chainCode === 'string')
data.chainCode = utils.toArray(data.chainCode, 'hex');
// private key = 32 bytes
if (data.privateKey) {
if (data.privateKey.getPrivate)
data.privateKey = data.privateKey.getPrivate().toArray();
else if (typeof data.privateKey === 'string')
data.privateKey = utils.toBuffer(data.privateKey);
}
// public key = 33 bytes
if (data.publicKey) {
if (data.publicKey.getPublic)
data.publicKey = data.privateKey.getPublic(true, 'array');
else if (typeof data.publicKey === 'string')
data.publicKey = utils.toBuffer(data.publicKey);
}
// checksum = 4 bytes
if (typeof data.checksum === 'string')
data.checksum = utils.toArray(data.checksum, 'hex');
else if (typeof data.checksum === 'number')
data.checksum = array32(data.checksum);
return data;
};
HDPrivateKey.prototype._seed = function _seed(seed) {
if (seed instanceof HDSeed)
seed = seed.seed;
if (utils.isHex(seed))
seed = utils.toArray(seed, 'hex');
if (seed.length < constants.hd.minEntropy
|| seed.length > constants.hd.maxEntropy) {
throw new Error('entropy not in range');
}
var hash = sha512hmac(seed, 'Bitcoin seed');
return {
version: null,
depth: 0,
parentFingerPrint: 0,
childIndex: 0,
chainCode: hash.slice(32, 64),
privateKey: hash.slice(0, 32),
checksum: null
};
};
HDPrivateKey.prototype._unbuild = function _unbuild(xkey) {
var raw = utils.fromBase58(xkey);
var data = {};
var off = 0;
var hash;
data.version = utils.readU32BE(raw, off);
off += 4;
data.depth = raw[off];
off += 1;
data.parentFingerPrint = utils.readU32BE(raw, off);
off += 4;
data.childIndex = utils.readU32BE(raw, off);
off += 4;
data.chainCode = raw.slice(off, off + 32);
off += data.chainCode.length;
off += 1; // nul byte
data.privateKey = raw.slice(off, off + 32);
off += data.privateKey.length;
data.checksum = utils.readU32BE(raw, off);
off += 4;
hash = utils.dsha256(raw.slice(0, -4));
if (data.checksum !== utils.readU32BE(hash, 0))
throw new Error('checksum mismatch');
return data;
};
HDPrivateKey.prototype._build = function _build(data) {
var sequence = new Array(82);
var off = 0;
var checksum, xprivkey, pair, privateKey, publicKey, size, fingerPrint;
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([0], sequence, off);
off += utils.copy(data.privateKey, sequence, off);
checksum = utils.dsha256(sequence.slice(0, off)).slice(0, 4);
off += utils.copy(checksum, sequence, off);
assert(off === 82);
xprivkey = utils.toBase58(sequence);
pair = bcoin.ecdsa.keyPair({ priv: data.privateKey });
privateKey = pair.getPrivate().toArray();
publicKey = pair.getPublic(true, 'array');
size = constants.hd.parentFingerPrintSize;
fingerPrint = utils.ripesha(publicKey).slice(0, size);
this.version = data.version;
this.depth = data.depth;
this.parentFingerPrint = data.parentFingerPrint;
this.childIndex = data.childIndex;
this.chainCode = data.chainCode;
this.privateKey = privateKey;
this.checksum = null;
this.xprivkey = xprivkey;
this.fingerPrint = fingerPrint;
this.publicKey = publicKey;
this.hdpub = new HDPublicKey({
version: this.version,
depth: this.depth,
parentFingerPrint: this.parentFingerPrint,
childIndex: this.childIndex,
chainCode: this.chainCode,
privateKey: this.privateKey,
checksum: this.checksum,
publicKey: this.publicKey,
master: this.master
});
this.xpubkey = this.hdpub.xpubkey;
this.pair = bcoin.ecdsa.keyPair({ priv: this.privateKey });
};
HDPrivateKey.prototype.derive = function derive(index, hardened) {
var data, hash, leftPart, chainCode, privateKey;
if (typeof index === 'string')
return this.deriveString(index);
hardened = index >= constants.hd.hardened ? true : hardened;
if (index < constants.hd.hardened && hardened)
index += constants.hd.hardened;
data = hardened
? [0].concat(this.privateKey).concat(array32(index))
: data = [].concat(this.publicKey).concat(array32(index));
hash = 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.curve.n).toArray();
return new HDPrivateKey({
version: null,
depth: new bn(this.depth).toNumber() + 1,
parentFingerPrint: this.fingerPrint,
childIndex: index,
chainCode: chainCode,
privateKey: privateKey,
checksum: null,
master: this.master
});
};
// 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.deriveString = function deriveString(path) {
var indexes, child;
if (!HDPrivateKey.isValidPath(path))
throw new Error('invalid path');
indexes = HDPrivateKey._getIndexes(path);
return indexes.reduce(function(prev, index, i) {
return prev.derive(index);
}, this);
};
HDPrivateKey.prototype.toJSON = function toJSON(encrypt) {
var json = {
v: 1,
name: 'hdkey',
encrypted: encrypt ? true : false
};
if (this instanceof HDPrivateKey) {
if (this.seed) {
json.mnemonic = encrypt
? encrypt(this.seed.mnemonic)
: this.seed.mnemonic;
json.passphrase = encrypt
? encrypt(this.seed.passphrase)
: this.seed.passphrase;
return json;
}
json.xprivkey = encrypt
? encrypt(this.xprivkey)
: this.xprivkey;
return json;
}
json.xpubkey = this.hd.xpubkey;
return json;
};
HDPrivateKey.fromJSON = function fromJSON(json, decrypt) {
assert.equal(json.v, 1);
assert.equal(json.name, 'hdkey');
if (json.encrypted && !decrypt)
throw new Error('Cannot decrypt address');
if (json.mnemonic) {
return new HDPrivateKey({
seed: new HDSeed({
mnemonic: json.encrypted
? decrypt(json.mnemonic)
: json.mnemonic,
passphrase: json.encrypted
? decrypt(json.passphrase)
: json.passphrase
})
});
}
if (json.xprivkey) {
return new HDPrivateKey({
xkey: json.encrypted
? decrypt(json.xprivkey)
: json.xprivkey
});
}
if (json.xpubkey) {
return new HDPublicKey({
xkey: json.xpubkey
});
}
assert(false);
};
/**
* HD Public Key
*/
function HDPublicKey(options) {
var data;
if (!(this instanceof HDPublicKey))
return new HDPublicKey(options);
if (!options)
throw new Error('No options for HDPublicKey');
if (HDPublicKey.isExtended(data))
options = { xkey: options };
data = options.xkey
? this._unbuild(options.xkey)
: options;
data = this._normalize(data, network.prefixes.xpubkey);
this.data = data;
this._build(data);
if (new bn(data.parentFingerPrint).cmpn(0) === 0) {
this.isMaster = true;
this.master = this;
} else {
this.master = options.master;
}
this.isPublic = true;
}
HDPublicKey.prototype.scan44 = HDPrivateKey.prototype.scan44;
HDPublicKey.prototype.deriveAccount44 = HDPrivateKey.prototype.deriveAccount44;
HDPublicKey.prototype.deriveBIP44 = HDPrivateKey.prototype.deriveBIP44;
HDPublicKey.prototype.deriveChange = HDPrivateKey.prototype.deriveChange;
HDPublicKey.prototype.deriveAddress = HDPrivateKey.prototype.deriveAddress;
HDPublicKey.prototype.scan45 = HDPrivateKey.prototype.scan45;
HDPublicKey.prototype.derivePurpose45 = HDPrivateKey.prototype.derivePurpose45;
HDPublicKey.prototype.deriveBIP45 = HDPrivateKey.prototype.deriveBIP45;
HDPublicKey.prototype.deriveCosignerChange = HDPrivateKey.prototype.deriveCosignerChange;
HDPublicKey.prototype.deriveCosignerAddress = HDPrivateKey.prototype.deriveCosignerAddress;
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 data.indexOf('xpub') === 0 || data.indexOf('tpub') === 0;
};
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 = utils.readU32BE(raw, off);
off += 4;
data.depth = raw[off];
off += 1;
data.parentFingerPrint = utils.readU32BE(raw, off);
off += 4;
data.childIndex = utils.readU32BE(raw, off);
off += 4;
data.chainCode = raw.slice(off, off + 32);
off += data.chainCode.length;
data.publicKey = raw.slice(off, off + 33);
off += data.publicKey.length;
data.checksum = utils.readU32BE(raw, off);
off += 4;
hash = utils.dsha256(raw.slice(0, -4));
if (data.checksum !== utils.readU32BE(hash, 0))
throw new Error('checksum mismatch');
return data;
};
HDPublicKey.prototype._build = function _build(data) {
var sequence = new Array(82);
var off = 0;
var checksum, xpubkey, publicKey, size, fingerPrint;
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);
checksum = utils.dsha256(sequence.slice(0, off)).slice(0, 4);
off += utils.copy(checksum, sequence, off);
assert(off === 82);
if (!data.checksum || !data.checksum.length)
data.checksum = checksum;
else if (utils.toHex(checksum) !== utils.toHex(data.checksum))
throw new Error('checksum mismatch');
xpubkey = utils.toBase58(sequence);
publicKey = data.publicKey;
size = constants.hd.parentFingerPrintSize;
fingerPrint = utils.ripesha(publicKey).slice(0, size);
this.version = data.version;
this.depth = data.depth;
this.parentFingerPrint = data.parentFingerPrint;
this.childIndex = data.childIndex;
this.chainCode = data.chainCode;
this.publicKey = publicKey;
this.checksum = null;
this.xpubkey = xpubkey;
this.fingerPrint = fingerPrint;
this.xprivkey = data.xprivkey;
this.pair = bcoin.ecdsa.keyPair({ pub: this.publicKey });
};
HDPublicKey.prototype.derive = function derive(index, hardened) {
var data, hash, leftPart, chainCode, pair, point, publicKey;
if (typeof index === 'string')
return this.deriveString(index);
if (index >= constants.hd.hardened || hardened)
throw new Error('invalid index');
if (index < 0)
throw new Error('invalid path');
data = [].concat(this.publicKey).concat(array32(index));
hash = sha512hmac(data, this.chainCode);
leftPart = new bn(hash.slice(0, 32));
chainCode = hash.slice(32, 64);
pair = bcoin.ecdsa.keyPair({ pub: this.publicKey });
point = ec.curve.g.mul(leftPart).add(pair.pub);
publicKey = bcoin.ecdsa.keyPair({ pub: point }).getPublic(true, 'array');
return new HDPublicKey({
version: null,
depth: new bn(this.depth).toNumber() + 1,
parentFingerPrint: this.fingerPrint,
childIndex: index,
chainCode: chainCode,
publicKey: publicKey,
checksum: null,
master: this.master
});
};
HDPublicKey.isValidPath = function isValidPath(arg) {
if (typeof arg === 'string') {
var 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.deriveString = function deriveString(path) {
if (~path.indexOf('\''))
throw new Error('cannot derive hardened');
else if (!HDPublicKey.isValidPath(path))
throw new Error('invalid path');
var indexes = HDPrivateKey._getIndexes(path);
return indexes.reduce(function(prev, index) {
return prev.derive(index);
}, this);
};
/**
* Make HD keys behave like elliptic KeyPairs
*/
[HDPrivateKey, HDPublicKey].forEach(function(HD) {
HD.prototype.validate = function validate() {
return this.pair.validate.apply(this.pair, arguments);
};
HD.prototype.getPublic = function getPublic() {
return this.pair.getPublic.apply(this.pair, arguments);
};
HD.prototype.getPrivate = function getPrivate() {
return this.pair.getPrivate.apply(this.pair, arguments);
};
HD.prototype.sign = function sign(msg) {
return this.pair.sign.apply(this.pair, arguments);
};
HD.prototype.verify = function verify(msg, signature) {
return this.pair.verify.apply(this.pair, arguments);
};
HD.prototype.__defineGetter__('pub', function() {
return this.pair.pub;
});
HD.prototype.__defineGetter__('priv', function() {
return this.pair.priv;
});
});
/**
* Helpers
*/
var isBrowser = (typeof process !== 'undefined' && process.browser)
|| typeof window !== 'undefined';
function sha512hmac(data, salt) {
if (isBrowser) {
var hmac = hash.hmac(hash.sha512, utils.toArray(salt));
return hmac.update(utils.toArray(data)).digest();
}
var crypto = require('crypto');
var hmac = crypto.createHmac('sha512', new Buffer(salt));
var h = hmac.update(new Buffer(data)).digest();
return Array.prototype.slice.call(h);
}
function randomBytes(size) {
if (isBrowser) {
var a = Uint8Array(size);
var buf = new Array(size);
(window.crypto || window.msCrypto).getRandomValues(a);
utils.copy(a, buf, 0);
return buf;
}
var crypto = require('crypto');
return Array.prototype.slice.call(crypto.randomBytes(size));
}
function array32(data) {
var b = [];
utils.writeU32BE(b, data, 0);
return b;
}
/**
* PDKBF2
* Credit to: https://github.com/stayradiated/pbkdf2-sha512
* Copyright (c) 2014, JP Richardson Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved
*/
function pbkdf2(key, salt, iterations, dkLen) {
'use strict';
var hLen = 64;
if (dkLen > (Math.pow(2, 32) - 1) * hLen)
throw Error('Requested key length too long');
if (typeof key !== 'string' && typeof key.length !== 'number')
throw new TypeError('key must a string or array');
if (typeof salt !== 'string' && typeof salt.length !== 'number')
throw new TypeError('salt must a string or array');
if (typeof key === 'string')
key = utils.toArray(key, null);
if (typeof salt === 'string')
salt = utils.toArray(salt, null);
var DK = new Buffer(dkLen);
var U = new Buffer(hLen);
var T = new Buffer(hLen);
var block1 = new Buffer(salt.length + 4);
var l = Math.ceil(dkLen / hLen);
var r = dkLen - (l - 1) * hLen;
var i, j, k, destPos, len;
utils.copy(salt.slice(0, salt.length), block1, 0);
for (i = 1; i <= l; i++) {
block1[salt.length + 0] = i >> 24 & 0xff;
block1[salt.length + 1] = i >> 16 & 0xff;
block1[salt.length + 2] = i >> 8 & 0xff;
block1[salt.length + 3] = i >> 0 & 0xff;
U = sha512hmac(block1, key);
utils.copy(U.slice(0, hLen), T, 0);
for (j = 1; j < iterations; j++) {
U = sha512hmac(U, key);
for (k = 0; k < hLen; k++)
T[k] ^= U[k];
}
destPos = (i - 1) * hLen;
len = i === l ? r : hLen;
utils.copy(T.slice(0, len), DK, 0);
}
return DK;
}
/**
* Expose
*/
hd.seed = HDSeed;
hd.priv = HDPrivateKey;
hd.pub = HDPublicKey;
hd.privateKey = HDPrivateKey;
hd.publicKey = HDPublicKey;
hd.pbkdf2 = pbkdf2;
hd.fromJSON = HDPrivateKey.fromJSON;