bitcore-lib-zcash/lib/hdprivatekey.js

459 lines
15 KiB
JavaScript
Raw Normal View History

2014-11-24 10:40:20 -08:00
'use strict';
var assert = require('assert');
var buffer = require('buffer');
2014-11-24 10:40:20 -08:00
var _ = require('lodash');
2014-11-24 10:40:20 -08:00
var BN = require('./crypto/bn');
var Base58 = require('./encoding/base58');
var Base58Check = require('./encoding/base58check');
var Hash = require('./crypto/hash');
2014-11-26 12:55:34 -08:00
var Network = require('./networks');
2014-11-28 07:24:32 -08:00
var HDKeyCache = require('./hdkeycache');
2014-11-24 10:40:20 -08:00
var Point = require('./crypto/point');
2014-11-26 12:55:34 -08:00
var PrivateKey = require('./privatekey');
2014-11-26 10:12:24 -08:00
var Random = require('./crypto/random');
2014-11-24 10:40:20 -08:00
var errors = require('./errors');
var hdErrors = errors.HDPrivateKey;
var bufferUtil = require('./util/buffer');
var jsUtil = require('./util/js');
2014-11-24 10:40:20 -08:00
2014-11-26 10:12:24 -08:00
var MINIMUM_ENTROPY_BITS = 128;
2014-11-26 12:55:34 -08:00
var BITS_TO_BYTES = 1/8;
2014-11-26 10:12:24 -08:00
var MAXIMUM_ENTROPY_BITS = 512;
2014-11-28 12:16:51 -08:00
/**
* Represents an instance of an hierarchically derived private key.
*
* More info on https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
*
* @constructor
* @param {string|Buffer|Object} arg
*/
2014-11-24 10:40:20 -08:00
function HDPrivateKey(arg) {
2014-11-26 10:12:24 -08:00
/* jshint maxcomplexity: 10 */
if (arg instanceof HDPrivateKey) {
return arg;
}
2014-11-26 12:55:34 -08:00
if (!(this instanceof HDPrivateKey)) {
2014-11-26 10:12:24 -08:00
return new HDPrivateKey(arg);
}
2014-11-24 10:40:20 -08:00
if (arg) {
if (_.isString(arg) || bufferUtil.isBuffer(arg)) {
2014-11-24 10:40:20 -08:00
if (HDPrivateKey.isValidSerialized(arg)) {
this._buildFromSerialized(arg);
} else if (jsUtil.isValidJSON(arg)) {
this._buildFromJSON(arg);
2014-11-24 10:40:20 -08:00
} else {
2014-11-28 13:11:03 -08:00
throw HDPrivateKey.getSerializedError(arg);
2014-11-24 10:40:20 -08:00
}
} else {
if (_.isObject(arg)) {
2014-11-26 10:12:24 -08:00
this._buildFromObject(arg);
2014-11-24 10:40:20 -08:00
} else {
throw new hdErrors.UnrecognizedArgument(arg);
2014-11-24 10:40:20 -08:00
}
}
} else {
2014-11-27 14:03:27 -08:00
return this._generateRandomly();
2014-11-24 10:40:20 -08:00
}
}
2014-11-28 12:16:51 -08:00
/**
* Get a derivated child based on a string or number.
*
* If the first argument is a string, it's parsed as the full path of
* derivation. Valid values for this argument include "m" (which returns the
* same private key), "m/0/1/40/2'/1000", where the ' quote means a hardened
* derivation.
*
* If the first argument is a number, the child with that index will be
* derived. If the second argument is truthy, the hardened version will be
* derived. See the example usage for clarification.
*
* @example
* var parent = new HDPrivateKey('xprv...');
* var child_0_1_2h = parent.derive(0).derive(1).derive(2, true);
* var copy_of_child_0_1_2h = parent.derive("m/0/1/2'");
* assert(child_0_1_2h.xprivkey === copy_of_child_0_1_2h);
*
* @param {string|number} arg
* @param {boolean?} hardened
*/
2014-11-24 10:40:20 -08:00
HDPrivateKey.prototype.derive = function(arg, hardened) {
if (_.isNumber(arg)) {
return this._deriveWithNumber(arg, hardened);
} else if (_.isString(arg)) {
return this._deriveFromString(arg);
} else {
throw new hdErrors.InvalidDerivationArgument(arg);
2014-11-24 10:40:20 -08:00
}
};
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) {
2014-11-28 07:24:32 -08:00
/* jshint maxstatements: 20 */
/* jshint maxcomplexity: 10 */
2014-11-24 10:40:20 -08:00
if (index >= HDPrivateKey.Hardened) {
hardened = true;
}
2014-11-26 13:38:15 -08:00
if (index < HDPrivateKey.Hardened && hardened) {
index += HDPrivateKey.Hardened;
}
2014-11-28 07:24:32 -08:00
var cached = HDKeyCache.get(this.xprivkey, index, hardened);
if (cached) {
return cached;
}
2014-11-24 10:40:20 -08:00
var indexBuffer = bufferUtil.integerAsBuffer(index);
2014-11-24 10:40:20 -08:00
var data;
if (hardened) {
data = bufferUtil.concat([new buffer.Buffer([0]), this.privateKey.toBuffer(), indexBuffer]);
2014-11-24 10:40:20 -08:00
} else {
data = bufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]);
2014-11-24 10:40:20 -08:00
}
2014-11-26 12:55:34 -08:00
var hash = Hash.sha512hmac(data, this._buffers.chainCode);
2014-11-24 10:40:20 -08:00
var leftPart = BN().fromBuffer(hash.slice(0, 32), {size: 32});
var chainCode = hash.slice(32, 64);
2014-11-26 12:55:34 -08:00
var privateKey = leftPart.add(this.privateKey.toBigNumber()).mod(Point.getN()).toBuffer({size: 32});
2014-11-24 10:40:20 -08:00
2014-11-28 07:24:32 -08:00
var derived = new HDPrivateKey({
2014-11-24 10:40:20 -08:00
network: this.network,
depth: this.depth + 1,
parentFingerPrint: this.fingerPrint,
childIndex: index,
chainCode: chainCode,
privateKey: privateKey
});
2014-11-28 07:24:32 -08:00
HDKeyCache.set(this.xprivkey, index, hardened, derived);
return derived;
2014-11-24 10:40:20 -08:00
};
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype._deriveFromString = function(path) {
2014-11-24 10:40:20 -08:00
var steps = path.split('/');
// Special cases:
if (_.contains(HDPrivateKey.RootElementAlias, path)) {
return this;
}
if (!_.contains(HDPrivateKey.RootElementAlias, steps[0])) {
throw new hdErrors.InvalidPath(path);
2014-11-24 10:40:20 -08:00
}
steps = steps.slice(1);
var result = this;
for (var step in steps) {
2014-11-26 13:38:15 -08:00
var index = parseInt(steps[step]);
var hardened = steps[step] !== index.toString();
result = result._deriveWithNumber(index, hardened);
2014-11-24 10:40:20 -08:00
}
return result;
};
2014-11-26 10:12:24 -08:00
/**
* Verifies that a given serialized private key in base58 with checksum format
* is valid.
*
* @param {string|Buffer} data - the serialized private key
* @param {string|Network=} network - optional, if present, checks that the
* network provided matches the network serialized.
* @return {boolean}
*/
2014-11-26 13:38:15 -08:00
HDPrivateKey.isValidSerialized = function(data, network) {
2014-11-26 10:12:24 -08:00
return !HDPrivateKey.getSerializedError(data, network);
};
/**
* Checks what's the error that causes the validation of a serialized private key
* in base58 with checksum to fail.
*
* @param {string|Buffer} data - the serialized private key
* @param {string|Network=} network - optional, if present, checks that the
* network provided matches the network serialized.
2014-11-29 13:23:38 -08:00
* @return {errors.InvalidArgument|null}
2014-11-26 10:12:24 -08:00
*/
2014-11-26 13:38:15 -08:00
HDPrivateKey.getSerializedError = function(data, network) {
2014-11-26 10:12:24 -08:00
/* jshint maxcomplexity: 10 */
if (!(_.isString(data) || bufferUtil.isBuffer(data))) {
return new hdErrors.UnrecognizedArgument('Expected string or buffer');
2014-11-26 10:12:24 -08:00
}
if (!Base58.validCharacters(data)) {
2014-11-29 13:23:38 -08:00
return new errors.InvalidB58Char('(unknown)', data);
2014-11-26 10:12:24 -08:00
}
2014-11-26 12:55:34 -08:00
try {
data = Base58Check.decode(data);
} catch (e) {
2014-11-29 13:23:38 -08:00
return new errors.InvalidB58Checksum(data);
2014-11-26 10:12:24 -08:00
}
2014-11-28 13:11:03 -08:00
if (data.length !== HDPrivateKey.DataLength) {
return new hdErrors.InvalidLength(data);
2014-11-26 10:12:24 -08:00
}
if (!_.isUndefined(network)) {
var error = HDPrivateKey._validateNetwork(data, network);
if (error) {
return error;
}
}
return null;
};
2014-11-28 13:11:03 -08:00
HDPrivateKey._validateNetwork = function(data, networkArg) {
var network = Network.get(networkArg);
2014-11-26 10:12:24 -08:00
if (!network) {
2014-11-29 13:23:38 -08:00
return new errors.InvalidNetworkArgument(networkArg);
2014-11-26 10:12:24 -08:00
}
2014-11-27 14:03:27 -08:00
var version = data.slice(0, 4);
if (bufferUtil.integerFromBuffer(version) !== network.xprivkey) {
2014-11-29 13:23:38 -08:00
return new errors.InvalidNetwork(version);
2014-11-26 10:12:24 -08:00
}
return null;
};
HDPrivateKey.prototype._buildFromJSON = function(arg) {
2014-11-24 10:40:20 -08:00
return this._buildFromObject(JSON.parse(arg));
};
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype._buildFromObject = function(arg) {
2014-11-28 07:24:32 -08:00
/* jshint maxcomplexity: 12 */
2014-11-26 10:12:24 -08:00
// TODO: Type validation
var buffers = {
version: arg.network ? bufferUtil.integerAsBuffer(Network.get(arg.network).xprivkey) : arg.version,
depth: bufferUtil.integerAsSingleByteBuffer(arg.depth),
parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? bufferUtil.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
childIndex: _.isNumber(arg.childIndex) ? bufferUtil.integerAsBuffer(arg.childIndex) : arg.childIndex,
chainCode: _.isString(arg.chainCode) ? bufferUtil.hexToBuffer(arg.chainCode) : arg.chainCode,
privateKey: (_.isString(arg.privateKey) && jsUtil.isHexa(arg.privateKey)) ? bufferUtil.hexToBuffer(arg.privateKey) : arg.privateKey,
checksum: arg.checksum ? (arg.checksum.length ? arg.checksum : bufferUtil.integerAsBuffer(arg.checksum)) : undefined
2014-11-26 10:12:24 -08:00
};
return this._buildFromBuffers(buffers);
};
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype._buildFromSerialized = function(arg) {
2014-11-24 10:40:20 -08:00
var decoded = Base58Check.decode(arg);
var buffers = {
version: decoded.slice(HDPrivateKey.VersionStart, HDPrivateKey.VersionEnd),
depth: decoded.slice(HDPrivateKey.DepthStart, HDPrivateKey.DepthEnd),
parentFingerPrint: decoded.slice(HDPrivateKey.ParentFingerPrintStart,
HDPrivateKey.ParentFingerPrintEnd),
childIndex: decoded.slice(HDPrivateKey.ChildIndexStart, HDPrivateKey.ChildIndexEnd),
chainCode: decoded.slice(HDPrivateKey.ChainCodeStart, HDPrivateKey.ChainCodeEnd),
privateKey: decoded.slice(HDPrivateKey.PrivateKeyStart, HDPrivateKey.PrivateKeyEnd),
checksum: decoded.slice(HDPrivateKey.ChecksumStart, HDPrivateKey.ChecksumEnd),
2014-11-26 12:55:34 -08:00
xprivkey: arg
2014-11-24 10:40:20 -08:00
};
return this._buildFromBuffers(buffers);
};
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype._generateRandomly = function(network) {
return HDPrivateKey.fromSeed(Random.getRandomBuffer(64), network);
2014-11-26 10:12:24 -08:00
};
2014-11-28 12:16:51 -08:00
/**
* Generate a private key from a seed, as described in BIP32
*
* @param {string|Buffer} hexa
* @param {*} network
* @return HDPrivateKey
*/
2014-11-26 13:38:15 -08:00
HDPrivateKey.fromSeed = function(hexa, network) {
2014-11-26 10:12:24 -08:00
/* jshint maxcomplexity: 8 */
if (jsUtil.isHexaString(hexa)) {
hexa = bufferUtil.hexToBuffer(hexa);
2014-11-26 10:12:24 -08:00
}
if (!Buffer.isBuffer(hexa)) {
throw new hdErrors.InvalidEntropyArgument(hexa);
2014-11-26 10:12:24 -08:00
}
if (hexa.length < MINIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
throw new hdErrors.InvalidEntropyArgument.NotEnoughEntropy(hexa);
2014-11-26 10:12:24 -08:00
}
if (hexa.length > MAXIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
throw new hdErrors.InvalidEntropyArgument.TooMuchEntropy(hexa);
2014-11-26 10:12:24 -08:00
}
var hash = Hash.sha512hmac(hexa, new buffer.Buffer('Bitcoin seed'));
return new HDPrivateKey({
network: Network.get(network) || Network.livenet,
depth: 0,
parentFingerPrint: 0,
childIndex: 0,
2014-11-26 12:55:34 -08:00
privateKey: hash.slice(0, 32),
chainCode: hash.slice(32, 64)
2014-11-26 10:12:24 -08:00
});
2014-11-24 10:40:20 -08:00
};
/**
* Receives a object with buffers in all the properties and populates the
* internal structure
*
* @param {Object} arg
2014-11-26 10:12:24 -08:00
* @param {buffer.Buffer} arg.version
* @param {buffer.Buffer} arg.depth
* @param {buffer.Buffer} arg.parentFingerPrint
* @param {buffer.Buffer} arg.childIndex
* @param {buffer.Buffer} arg.chainCode
* @param {buffer.Buffer} arg.privateKey
* @param {buffer.Buffer} arg.checksum
2014-11-24 10:40:20 -08:00
* @param {string=} arg.xprivkey - if set, don't recalculate the base58
* representation
* @return {HDPrivateKey} this
*/
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype._buildFromBuffers = function(arg) {
2014-11-26 12:55:34 -08:00
/* jshint maxcomplexity: 8 */
2014-11-26 13:38:15 -08:00
/* jshint maxstatements: 20 */
2014-11-24 10:40:20 -08:00
HDPrivateKey._validateBufferArguments(arg);
Object.defineProperty(this, '_buffers', {
configurable: false,
value: arg
});
2014-11-24 10:40:20 -08:00
2014-11-26 10:12:24 -08:00
var sequence = [
arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode,
bufferUtil.emptyBuffer(1), arg.privateKey
2014-11-26 10:12:24 -08:00
];
2014-11-28 13:11:03 -08:00
var concat = buffer.Buffer.concat(sequence);
2014-11-26 12:55:34 -08:00
if (!arg.checksum || !arg.checksum.length) {
2014-11-28 13:11:03 -08:00
arg.checksum = Base58Check.checksum(concat);
2014-11-26 10:12:24 -08:00
} else {
2014-11-28 13:11:03 -08:00
if (arg.checksum.toString() !== Base58Check.checksum(concat).toString()) {
2014-11-29 13:23:38 -08:00
throw new errors.InvalidB58Checksum(concat);
2014-11-26 10:12:24 -08:00
}
}
var xprivkey;
2014-11-24 10:40:20 -08:00
if (!arg.xprivkey) {
xprivkey = Base58Check.encode(buffer.Buffer.concat(sequence));
2014-11-24 10:40:20 -08:00
} else {
xprivkey = arg.xprivkey;
2014-11-24 10:40:20 -08:00
}
var privateKey = new PrivateKey(BN().fromBuffer(arg.privateKey));
var publicKey = privateKey.toPublicKey();
var size = HDPrivateKey.ParentFingerPrintSize;
var fingerPrint = Hash.sha256ripemd160(publicKey.toBuffer()).slice(0, size);
jsUtil.defineImmutable(this, {
xprivkey: xprivkey,
network: Network.get(bufferUtil.integerFromBuffer(arg.version)),
depth: bufferUtil.integerFromSingleByteBuffer(arg.depth),
privateKey: privateKey,
publicKey: publicKey,
fingerPrint: fingerPrint
});
2014-11-24 10:40:20 -08:00
2014-11-26 13:38:15 -08:00
var HDPublicKey = require('./hdpublickey');
var hdPublicKey = new HDPublicKey(this);
jsUtil.defineImmutable(this, {
hdPublicKey: hdPublicKey,
xpubkey: hdPublicKey.xpubkey
});
2014-11-24 10:40:20 -08:00
return this;
};
2014-11-26 13:38:15 -08:00
HDPrivateKey._validateBufferArguments = function(arg) {
2014-11-24 10:40:20 -08:00
var checkBuffer = function(name, size) {
2014-11-26 12:55:34 -08:00
var buff = arg[name];
assert(bufferUtil.isBuffer(buff), name + ' argument is not a buffer');
2014-11-24 10:40:20 -08:00
assert(
2014-11-26 12:55:34 -08:00
buff.length === size,
name + ' has not the expected size: found ' + buff.length + ', expected ' + size
2014-11-24 10:40:20 -08:00
);
};
checkBuffer('version', HDPrivateKey.VersionSize);
2014-11-26 12:55:34 -08:00
checkBuffer('depth', HDPrivateKey.DepthSize);
2014-11-24 10:40:20 -08:00
checkBuffer('parentFingerPrint', HDPrivateKey.ParentFingerPrintSize);
checkBuffer('childIndex', HDPrivateKey.ChildIndexSize);
checkBuffer('chainCode', HDPrivateKey.ChainCodeSize);
checkBuffer('privateKey', HDPrivateKey.PrivateKeySize);
2014-11-26 12:55:34 -08:00
if (arg.checksum && arg.checksum.length) {
checkBuffer('checksum', HDPrivateKey.CheckSumSize);
}
2014-11-24 10:40:20 -08:00
};
2014-11-28 12:16:51 -08:00
/**
* Returns the string representation of this private key (a string starting
* with "xprv..."
*
* @return string
*/
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype.toString = function() {
2014-11-24 10:40:20 -08:00
return this.xprivkey;
};
2014-11-28 12:16:51 -08:00
/**
* Returns a plain object with a representation of this private key.
*
* Fields include:
* * network: either 'livenet' or 'testnet'
* * depth: a number ranging from 0 to 255
* * fingerPrint: a number ranging from 0 to 2^32-1, taken from the hash of the
* associated public key
* * parentFingerPrint: a number ranging from 0 to 2^32-1, taken from the hash
* of this parent's associated public key or zero.
* * childIndex: the index from which this child was derived (or zero)
* * chainCode: an hexa string representing a number used in the derivation
* * privateKey: the private key associated, in hexa representation
* * xprivkey: the representation of this extended private key in checksum
* base58 format
* * checksum: the base58 checksum of xprivkey
*
* @return {Object}
*/
HDPrivateKey.prototype.toJSON = function() {
2014-11-24 10:40:20 -08:00
return {
network: Network.get(bufferUtil.integerFromBuffer(this._buffers.version)).name,
depth: bufferUtil.integerFromSingleByteBuffer(this._buffers.depth),
fingerPrint: bufferUtil.integerFromBuffer(this.fingerPrint),
parentFingerPrint: bufferUtil.integerFromBuffer(this._buffers.parentFingerPrint),
childIndex: bufferUtil.integerFromBuffer(this._buffers.childIndex),
chainCode: bufferUtil.bufferToHex(this._buffers.chainCode),
2014-11-27 14:03:27 -08:00
privateKey: this.privateKey.toBuffer().toString('hex'),
checksum: bufferUtil.integerFromBuffer(this._buffers.checksum),
2014-11-24 10:40:20 -08:00
xprivkey: this.xprivkey
};
};
2014-11-26 10:12:24 -08:00
HDPrivateKey.DefaultDepth = 0;
HDPrivateKey.DefaultFingerprint = 0;
HDPrivateKey.DefaultChildIndex = 0;
HDPrivateKey.DefaultNetwork = Network.livenet;
HDPrivateKey.Hardened = 0x80000000;
HDPrivateKey.RootElementAlias = ['m', 'M', 'm\'', 'M\''];
2014-11-24 10:40:20 -08:00
HDPrivateKey.VersionSize = 4;
2014-11-26 12:55:34 -08:00
HDPrivateKey.DepthSize = 1;
2014-11-24 10:40:20 -08:00
HDPrivateKey.ParentFingerPrintSize = 4;
HDPrivateKey.ChildIndexSize = 4;
HDPrivateKey.ChainCodeSize = 32;
HDPrivateKey.PrivateKeySize = 32;
HDPrivateKey.CheckSumSize = 4;
2014-11-28 13:11:03 -08:00
HDPrivateKey.DataLength = 78;
2014-11-26 12:55:34 -08:00
HDPrivateKey.SerializedByteSize = 82;
HDPrivateKey.VersionStart = 0;
HDPrivateKey.VersionEnd = HDPrivateKey.VersionStart + HDPrivateKey.VersionSize;
HDPrivateKey.DepthStart = HDPrivateKey.VersionEnd;
HDPrivateKey.DepthEnd = HDPrivateKey.DepthStart + HDPrivateKey.DepthSize;
HDPrivateKey.ParentFingerPrintStart = HDPrivateKey.DepthEnd;
HDPrivateKey.ParentFingerPrintEnd = HDPrivateKey.ParentFingerPrintStart + HDPrivateKey.ParentFingerPrintSize;
HDPrivateKey.ChildIndexStart = HDPrivateKey.ParentFingerPrintEnd;
HDPrivateKey.ChildIndexEnd = HDPrivateKey.ChildIndexStart + HDPrivateKey.ChildIndexSize;
HDPrivateKey.ChainCodeStart = HDPrivateKey.ChildIndexEnd;
HDPrivateKey.ChainCodeEnd = HDPrivateKey.ChainCodeStart + HDPrivateKey.ChainCodeSize;
HDPrivateKey.PrivateKeyStart = HDPrivateKey.ChainCodeEnd + 1;
HDPrivateKey.PrivateKeyEnd = HDPrivateKey.PrivateKeyStart + HDPrivateKey.PrivateKeySize;
HDPrivateKey.ChecksumStart = HDPrivateKey.PrivateKeyEnd;
HDPrivateKey.ChecksumEnd = HDPrivateKey.ChecksumStart + HDPrivateKey.CheckSumSize;
assert(HDPrivateKey.ChecksumEnd === HDPrivateKey.SerializedByteSize);
2014-11-24 10:40:20 -08:00
module.exports = HDPrivateKey;