|
|
@ -3,6 +3,7 @@ var crypto = require('crypto') |
|
|
|
var BigInteger = require('bigi') |
|
|
|
var ecurve = require('ecurve') |
|
|
|
var ecparams = ecurve.getCurveByName('secp256k1') |
|
|
|
var Point = ecurve.Point |
|
|
|
var sha512 = require('sha512') |
|
|
|
|
|
|
|
module.exports = HDKey |
|
|
@ -11,29 +12,47 @@ var MASTER_SECRET = new Buffer('Bitcoin seed') |
|
|
|
var HARDENED_OFFSET = 0x80000000 |
|
|
|
var LEN = 78 |
|
|
|
|
|
|
|
//I hate that this is hardcoded, but for now...
|
|
|
|
//var N = BigInteger.fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141")
|
|
|
|
var N = ecparams.params.n |
|
|
|
|
|
|
|
//Bitcoin hardcoded by default, can use package `coininfo` for others
|
|
|
|
var VERSIONS = {private: 0x0488ADE4, public: 0x0488B21E} |
|
|
|
|
|
|
|
function HDKey(seed) { |
|
|
|
//if (seed == null || !Buffer.isBuffer(seed)) throw new Error('Must pass a seed that is a buffer.')
|
|
|
|
if (seed == null) return //this is for deriveChild()
|
|
|
|
|
|
|
|
var I = sha512.hmac(MASTER_SECRET).finalize(seed) |
|
|
|
var IL = I.slice(0, 32) |
|
|
|
var IR = I.slice(32) |
|
|
|
|
|
|
|
setPrivPub(this, IL) |
|
|
|
|
|
|
|
this.chaincode = IR |
|
|
|
function HDKey(versions) { |
|
|
|
this.versions = versions || VERSIONS |
|
|
|
this.depth = 0 |
|
|
|
this.index = 0 |
|
|
|
this._privateKey = null |
|
|
|
this._privateKeyInteger = BigInteger.ZERO |
|
|
|
this._publicKey = null |
|
|
|
this.chainCode = null |
|
|
|
} |
|
|
|
|
|
|
|
Object.defineProperty(HDKey.prototype, 'private', { |
|
|
|
Object.defineProperty(HDKey.prototype, 'privateKey', { |
|
|
|
get: function() { |
|
|
|
return this._privateKey |
|
|
|
}, |
|
|
|
set: function(value) { |
|
|
|
assert.equal(value.length, 32, 'Private key must be 32 bytes.') |
|
|
|
this._privateKey = value |
|
|
|
this._privateKeyInteger = BigInteger.fromBuffer(this._privateKey) |
|
|
|
this._publicKey = ecparams.params.G.multiply(this._privateKeyInteger).getEncoded(true) //force compressed point
|
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
Object.defineProperty(HDKey.prototype, 'publicKey', { |
|
|
|
get: function() { |
|
|
|
return this._publicKey |
|
|
|
}, |
|
|
|
set: function(value) { |
|
|
|
assert(value.length === 33 || value.length === 65, 'Public key must be 33 or 65 bytes.') |
|
|
|
var pt = Point.decodeFrom(ecparams.curve, value) |
|
|
|
this._publicKey = pt.getEncoded(true) //force compressed point
|
|
|
|
this._privateKey = null |
|
|
|
this._privateKeyInteger = null |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
Object.defineProperty(HDKey.prototype, 'privateOld', { |
|
|
|
get: function() { |
|
|
|
// Version
|
|
|
|
var version = VERSIONS.private |
|
|
@ -55,21 +74,21 @@ Object.defineProperty(HDKey.prototype, 'private', { |
|
|
|
buffer.writeUInt32BE(this.index, 9) |
|
|
|
|
|
|
|
// 32 bytes: the chain code
|
|
|
|
this.chaincode.copy(buffer, 13) |
|
|
|
this.chainCode.copy(buffer, 13) |
|
|
|
|
|
|
|
// 33 bytes: the public key or private key data
|
|
|
|
assert(this.priv, 'Missing private key') |
|
|
|
assert(this.privateKey, 'Missing private key') |
|
|
|
|
|
|
|
// 0x00 + k for private keys
|
|
|
|
buffer.writeUInt8(0, 45) |
|
|
|
this.priv.copy(buffer, 46) |
|
|
|
this.privateKey.copy(buffer, 46) |
|
|
|
|
|
|
|
|
|
|
|
return buffer |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
Object.defineProperty(HDKey.prototype, 'public', { |
|
|
|
Object.defineProperty(HDKey.prototype, 'publicOld', { |
|
|
|
get: function() { |
|
|
|
// Version
|
|
|
|
var version = VERSIONS.public |
|
|
@ -91,10 +110,10 @@ Object.defineProperty(HDKey.prototype, 'public', { |
|
|
|
buffer.writeUInt32BE(this.index, 9) |
|
|
|
|
|
|
|
// 32 bytes: the chain code
|
|
|
|
this.chaincode.copy(buffer, 13) |
|
|
|
this.chainCode.copy(buffer, 13) |
|
|
|
|
|
|
|
// X9.62 encoding for public keys
|
|
|
|
var buf = new Buffer(this.pub.getEncoded(true)) |
|
|
|
var buf = this.publicKey |
|
|
|
buf.copy(buffer, 45) |
|
|
|
|
|
|
|
return buffer |
|
|
@ -103,8 +122,7 @@ Object.defineProperty(HDKey.prototype, 'public', { |
|
|
|
|
|
|
|
HDKey.prototype.getIdentifier = function() { |
|
|
|
//just computing pubKeyHash here
|
|
|
|
var buf = new Buffer(this.pub.getEncoded(true)) |
|
|
|
var sha = crypto.createHash('sha256').update(buf).digest() |
|
|
|
var sha = crypto.createHash('sha256').update(this.publicKey).digest() |
|
|
|
return crypto.createHash('rmd160').update(sha).digest() |
|
|
|
} |
|
|
|
|
|
|
@ -150,9 +168,9 @@ HDKey.prototype.deriveChild = function(index) { |
|
|
|
|
|
|
|
// Hardened child
|
|
|
|
if (isHardened) { |
|
|
|
assert(this.priv, 'Could not derive hardened child key') |
|
|
|
assert(this.privateKey, 'Could not derive hardened child key') |
|
|
|
|
|
|
|
var pk = this.priv |
|
|
|
var pk = this.privateKey |
|
|
|
var zb = new Buffer([0]) |
|
|
|
pk = Buffer.concat([zb, pk]) |
|
|
|
|
|
|
@ -164,23 +182,23 @@ HDKey.prototype.deriveChild = function(index) { |
|
|
|
// data = serP(point(kpar)) || ser32(index)
|
|
|
|
// = serP(Kpar) || ser32(index)
|
|
|
|
data = Buffer.concat([ |
|
|
|
new Buffer(this.pub.getEncoded(true)), |
|
|
|
this.publicKey, |
|
|
|
indexBuffer |
|
|
|
]) |
|
|
|
} |
|
|
|
|
|
|
|
//var I = crypto.HmacSHA512(data, this.chaincode)
|
|
|
|
var I = sha512.hmac(this.chaincode).finalize(data) |
|
|
|
var I = sha512.hmac(this.chainCode).finalize(data) |
|
|
|
var IL = I.slice(0, 32) |
|
|
|
var IR = I.slice(32) |
|
|
|
|
|
|
|
var hd = new HDKey() |
|
|
|
var hd = new HDKey(this.versions) |
|
|
|
var pIL = BigInteger.fromBuffer(IL) |
|
|
|
|
|
|
|
// Private parent key -> private child key
|
|
|
|
if (this.priv) { |
|
|
|
if (this.privateKey) { |
|
|
|
// ki = parse256(IL) + kpar (mod n)
|
|
|
|
var ki = pIL.add(BigInteger.fromBuffer(this.priv)).mod(N) |
|
|
|
var ki = pIL.add(BigInteger.fromBuffer(this.privateKey)).mod(N) |
|
|
|
|
|
|
|
// In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i
|
|
|
|
if (pIL.compareTo(N) >= 0 || ki.signum() === 0) { |
|
|
@ -189,7 +207,7 @@ HDKey.prototype.deriveChild = function(index) { |
|
|
|
|
|
|
|
//hd.priv = new ECKey(ki.toBuffer(), true)
|
|
|
|
//hd.pub = hd.priv.publicPoint
|
|
|
|
setPrivPub(hd, ki.toBuffer()) |
|
|
|
hd.privateKey = ki.toBuffer() |
|
|
|
|
|
|
|
// Public parent key -> public child key
|
|
|
|
} else { |
|
|
@ -205,7 +223,7 @@ HDKey.prototype.deriveChild = function(index) { |
|
|
|
//hd.pub = new ECPubKey(Ki, true)
|
|
|
|
} |
|
|
|
|
|
|
|
hd.chaincode = IR |
|
|
|
hd.chainCode = IR |
|
|
|
hd.depth = this.depth + 1 |
|
|
|
hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) |
|
|
|
hd.index = index |
|
|
@ -213,9 +231,21 @@ HDKey.prototype.deriveChild = function(index) { |
|
|
|
return hd |
|
|
|
} |
|
|
|
|
|
|
|
HDKey.fromMasterSeed = function(seedBuffer, versions) { |
|
|
|
var I = sha512.hmac(MASTER_SECRET).finalize(seedBuffer) |
|
|
|
var IL = I.slice(0, 32) |
|
|
|
var IR = I.slice(32) |
|
|
|
|
|
|
|
var hdkey = new HDKey(versions) |
|
|
|
hdkey.chainCode = IR |
|
|
|
hdkey.privateKey = IL |
|
|
|
|
|
|
|
return hdkey |
|
|
|
} |
|
|
|
|
|
|
|
//temporary
|
|
|
|
function setPrivPub(hd, privKey) { |
|
|
|
hd.priv = privKey |
|
|
|
hd.compressed = true |
|
|
|
hd.pub = ecparams.params.G.multiply(BigInteger.fromBuffer(privKey)) |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|