bitcore/lib/hdprivatekey.js

386 lines
14 KiB
JavaScript
Raw Normal View History

2014-11-24 10:40:20 -08:00
'use strict';
var _ = require('lodash');
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-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 assert = require('assert');
var buffer = require('buffer');
var util = require('./util');
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-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) {
2014-11-26 10:12:24 -08:00
if (_.isString(arg) || buffer.Buffer.isBuffer(arg)) {
2014-11-24 10:40:20 -08:00
if (HDPrivateKey.isValidSerialized(arg)) {
this._buildFromSerialized(arg);
2014-11-27 14:03:27 -08:00
} else if (util.isValidJson(arg)) {
this._buildFromJson(arg);
2014-11-24 10:40:20 -08:00
} else {
2014-11-26 12:55:34 -08:00
throw new Error(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 Error(HDPrivateKey.Errors.UnrecognizedArgument);
}
}
} else {
2014-11-27 14:03:27 -08:00
return this._generateRandomly();
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 Error(HDPrivateKey.Errors.InvalidDerivationArgument);
}
};
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) {
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-24 10:40:20 -08:00
var indexBuffer = util.integerAsBuffer(index);
var data;
if (hardened) {
2014-11-26 10:12:24 -08:00
data = buffer.Buffer.concat([new buffer.Buffer([0]), this.privateKey.toBuffer(), indexBuffer]);
2014-11-24 10:40:20 -08:00
} else {
2014-11-26 10:12:24 -08:00
data = buffer.Buffer.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
return new HDPrivateKey({
network: this.network,
depth: this.depth + 1,
parentFingerPrint: this.fingerPrint,
childIndex: index,
chainCode: chainCode,
privateKey: privateKey
});
};
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 Error(HDPrivateKey.Errors.InvalidPath);
}
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.
* @return {HDPrivateKey.Errors|null}
*/
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) || buffer.Buffer.isBuffer(data))) {
return HDPrivateKey.Errors.InvalidArgument;
}
if (!Base58.validCharacters(data)) {
return HDPrivateKey.Errors.InvalidB58Char;
}
2014-11-26 12:55:34 -08:00
try {
data = Base58Check.decode(data);
} catch (e) {
2014-11-26 10:12:24 -08:00
return HDPrivateKey.Errors.InvalidB58Checksum;
}
if (data.length !== 78) {
return HDPrivateKey.Errors.InvalidLength;
}
if (!_.isUndefined(network)) {
var error = HDPrivateKey._validateNetwork(data, network);
if (error) {
return error;
}
}
return null;
};
2014-11-26 13:38:15 -08:00
HDPrivateKey._validateNetwork = function(data, network) {
2014-11-26 10:12:24 -08:00
network = Network.get(network);
if (!network) {
return HDPrivateKey.Errors.InvalidNetworkArgument;
}
2014-11-27 14:03:27 -08:00
var version = data.slice(0, 4);
if (util.integerFromBuffer(version) !== network.xprivkey) {
2014-11-26 10:12:24 -08:00
return HDPrivateKey.Errors.InvalidNetwork;
}
return null;
};
2014-11-26 13:38:15 -08:00
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-26 10:12:24 -08:00
// TODO: Type validation
var buffers = {
2014-11-27 14:03:27 -08:00
version: arg.network ? util.integerAsBuffer(Network.get(arg.network).xprivkey) : arg.version,
2014-11-26 12:55:34 -08:00
depth: util.integerAsSingleByteBuffer(arg.depth),
2014-11-26 13:38:15 -08:00
parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? util.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
childIndex: _.isNumber(arg.childIndex) ? util.integerAsBuffer(arg.childIndex) : arg.childIndex,
2014-11-26 12:55:34 -08:00
chainCode: _.isString(arg.chainCode) ? util.hexToBuffer(arg.chainCode) : arg.chainCode,
2014-11-27 14:03:27 -08:00
privateKey: (_.isString(arg.privateKey) && util.isHexa(arg.privateKey)) ? util.hexToBuffer(arg.privateKey) : arg.privateKey,
checksum: arg.checksum ? (arg.checksum.length ? arg.checksum : util.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-26 13:38:15 -08:00
HDPrivateKey.fromSeed = function(hexa, network) {
2014-11-26 10:12:24 -08:00
/* jshint maxcomplexity: 8 */
if (util.isHexaString(hexa)) {
hexa = util.hexToBuffer(hexa);
}
if (!Buffer.isBuffer(hexa)) {
2014-11-27 14:03:27 -08:00
throw new Error(HDPrivateKey.Errors.InvalidEntropyArg);
2014-11-26 10:12:24 -08:00
}
if (hexa.length < MINIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
2014-11-27 14:03:27 -08:00
throw new Error(HDPrivateKey.Errors.NotEnoughEntropy);
2014-11-26 10:12:24 -08:00
}
if (hexa.length > MAXIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
2014-11-27 14:03:27 -08:00
throw new Error(HDPrivateKey.Errors.TooMuchEntropy);
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);
this._buffers = arg;
2014-11-26 10:12:24 -08:00
var sequence = [
arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode,
2014-11-26 12:55:34 -08:00
util.emptyBuffer(1), arg.privateKey
2014-11-26 10:12:24 -08:00
];
2014-11-26 12:55:34 -08:00
if (!arg.checksum || !arg.checksum.length) {
arg.checksum = Base58Check.checksum(buffer.Buffer.concat(sequence));
2014-11-26 10:12:24 -08:00
} else {
2014-11-27 14:03:27 -08:00
if (arg.checksum.toString() !== Base58Check.checksum(buffer.Buffer.concat(sequence)).toString()) {
2014-11-26 10:12:24 -08:00
throw new Error(HDPrivateKey.Errors.InvalidB58Checksum);
}
}
2014-11-24 10:40:20 -08:00
if (!arg.xprivkey) {
2014-11-26 13:38:15 -08:00
this.xprivkey = Base58Check.encode(buffer.Buffer.concat(sequence));
2014-11-24 10:40:20 -08:00
} else {
this.xprivkey = arg.xprivkey;
}
2014-11-26 13:38:15 -08:00
this.network = Network.get(util.integerFromBuffer(arg.version));
this.depth = util.integerFromSingleByteBuffer(arg.depth);
this.privateKey = new PrivateKey(BN().fromBuffer(arg.privateKey));
this.publicKey = this.privateKey.toPublicKey();
2014-11-24 10:40:20 -08:00
2014-11-26 13:38:15 -08:00
this.fingerPrint = Hash.sha256ripemd160(this.publicKey.toBuffer()).slice(0, HDPrivateKey.ParentFingerPrintSize);
2014-11-24 10:40:20 -08:00
2014-11-26 13:38:15 -08:00
var HDPublicKey = require('./hdpublickey');
this.hdPublicKey = new HDPublicKey(this);
this.xpubkey = this.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(buffer.Buffer.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-26 13:38:15 -08:00
HDPrivateKey.prototype.toString = function() {
2014-11-24 10:40:20 -08:00
return this.xprivkey;
};
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype.toObject = function() {
2014-11-24 10:40:20 -08:00
return {
2014-11-26 13:38:15 -08:00
network: Network.get(util.integerFromBuffer(this._buffers.version)).name,
depth: util.integerFromSingleByteBuffer(this._buffers.depth),
2014-11-27 14:03:27 -08:00
fingerPrint: util.integerFromBuffer(this.fingerPrint),
2014-11-24 10:40:20 -08:00
parentFingerPrint: util.integerFromBuffer(this._buffers.parentFingerPrint),
childIndex: util.integerFromBuffer(this._buffers.childIndex),
chainCode: util.bufferToHex(this._buffers.chainCode),
2014-11-27 14:03:27 -08:00
privateKey: this.privateKey.toBuffer().toString('hex'),
2014-11-24 10:40:20 -08:00
checksum: util.integerFromBuffer(this._buffers.checksum),
xprivkey: this.xprivkey
};
};
2014-11-26 13:38:15 -08:00
HDPrivateKey.prototype.toJson = function() {
2014-11-24 10:40:20 -08:00
return JSON.stringify(this.toObject());
};
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-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
HDPrivateKey.Errors = {};
HDPrivateKey.Errors.InvalidArgument = 'Invalid argument, expected string or Buffer';
HDPrivateKey.Errors.InvalidB58Char = 'Invalid Base 58 character';
HDPrivateKey.Errors.InvalidB58Checksum = 'Invalid Base 58 checksum';
HDPrivateKey.Errors.InvalidChildIndex = 'Invalid Child Index - must be a number';
HDPrivateKey.Errors.InvalidConstant = 'Unrecognized xprivkey version';
HDPrivateKey.Errors.InvalidDepth = 'Invalid depth parameter - must be a number';
HDPrivateKey.Errors.InvalidDerivationArgument = 'Invalid argument, expected number and boolean or string';
2014-11-26 10:12:24 -08:00
HDPrivateKey.Errors.InvalidEntropyArg = 'Invalid argument: entropy must be an hexa string or binary buffer';
2014-11-24 10:40:20 -08:00
HDPrivateKey.Errors.InvalidLength = 'Invalid length for xprivkey format';
HDPrivateKey.Errors.InvalidNetwork = 'Unexpected version for network';
HDPrivateKey.Errors.InvalidNetworkArgument = 'Network argument must be \'livenet\' or \'testnet\'';
HDPrivateKey.Errors.InvalidParentFingerPrint = 'Invalid Parent Fingerprint - must be a number';
HDPrivateKey.Errors.InvalidPath = 'Invalid path for derivation: must start with "m"';
2014-11-26 10:12:24 -08:00
HDPrivateKey.Errors.NotEnoughEntropy = 'Need more than 128 bytes of entropy';
HDPrivateKey.Errors.TooMuchEntropy = 'More than 512 bytes of entropy is non standard';
2014-11-24 10:40:20 -08:00
HDPrivateKey.Errors.UnrecognizedArgument = 'Creating a HDPrivateKey requires a string, a buffer, a json, or an object';
module.exports = HDPrivateKey;