mirror of https://github.com/lukechilds/hdkey.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
6.6 KiB
251 lines
6.6 KiB
var assert = require('assert')
|
|
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
|
|
|
|
var MASTER_SECRET = new Buffer('Bitcoin seed')
|
|
var HARDENED_OFFSET = 0x80000000
|
|
var LEN = 78
|
|
|
|
var N = ecparams.params.n
|
|
|
|
//Bitcoin hardcoded by default, can use package `coininfo` for others
|
|
var VERSIONS = {private: 0x0488ADE4, public: 0x0488B21E}
|
|
|
|
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, '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, 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
|
|
var buffer = new Buffer(LEN)
|
|
|
|
// 4 bytes: version bytes
|
|
buffer.writeUInt32BE(version, 0)
|
|
|
|
// Depth
|
|
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ....
|
|
buffer.writeUInt8(this.depth, 4)
|
|
|
|
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
|
var fingerprint = this.depth ? this.parentFingerprint : 0x00000000
|
|
buffer.writeUInt32BE(fingerprint, 5)
|
|
|
|
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
|
// This is encoded in Big endian. (0x00000000 if master key)
|
|
buffer.writeUInt32BE(this.index, 9)
|
|
|
|
// 32 bytes: the chain code
|
|
this.chainCode.copy(buffer, 13)
|
|
|
|
// 33 bytes: the public key or private key data
|
|
assert(this.privateKey, 'Missing private key')
|
|
|
|
// 0x00 + k for private keys
|
|
buffer.writeUInt8(0, 45)
|
|
this.privateKey.copy(buffer, 46)
|
|
|
|
|
|
return buffer
|
|
}
|
|
})
|
|
|
|
Object.defineProperty(HDKey.prototype, 'publicOld', {
|
|
get: function() {
|
|
// Version
|
|
var version = VERSIONS.public
|
|
var buffer = new Buffer(LEN)
|
|
|
|
// 4 bytes: version bytes
|
|
buffer.writeUInt32BE(version, 0)
|
|
|
|
// Depth
|
|
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ....
|
|
buffer.writeUInt8(this.depth, 4)
|
|
|
|
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
|
var fingerprint = this.depth ? this.parentFingerprint : 0x00000000
|
|
buffer.writeUInt32BE(fingerprint, 5)
|
|
|
|
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
|
// This is encoded in Big endian. (0x00000000 if master key)
|
|
buffer.writeUInt32BE(this.index, 9)
|
|
|
|
// 32 bytes: the chain code
|
|
this.chainCode.copy(buffer, 13)
|
|
|
|
// X9.62 encoding for public keys
|
|
var buf = this.publicKey
|
|
buf.copy(buffer, 45)
|
|
|
|
return buffer
|
|
}
|
|
})
|
|
|
|
HDKey.prototype.getIdentifier = function() {
|
|
//just computing pubKeyHash here
|
|
var sha = crypto.createHash('sha256').update(this.publicKey).digest()
|
|
return crypto.createHash('rmd160').update(sha).digest()
|
|
}
|
|
|
|
HDKey.prototype.getFingerprint = function() {
|
|
return this.getIdentifier().slice(0, 4)
|
|
}
|
|
|
|
|
|
HDKey.prototype.derive = function(path) {
|
|
var e = path.split('/')
|
|
|
|
// Special cases:
|
|
if (path == 'm' || path == 'M' || path == 'm\'' || path == 'M\'')
|
|
return this
|
|
|
|
var hkey = this
|
|
for (var i in e) {
|
|
var c = e[i]
|
|
|
|
if (i == 0 ) {
|
|
if (c != 'm') throw new Error('invalid path')
|
|
continue
|
|
}
|
|
|
|
var usePrivate = (c.length > 1) && (c[c.length-1] == '\'')
|
|
var childIndex = parseInt(usePrivate ? c.slice(0, c.length - 1) : c) & 0x7fffffff
|
|
|
|
if (usePrivate)
|
|
childIndex += HARDENED_OFFSET
|
|
|
|
hkey = hkey.deriveChild(childIndex)
|
|
}
|
|
|
|
return hkey
|
|
}
|
|
|
|
HDKey.prototype.deriveChild = function(index) {
|
|
var isHardened = index >= HARDENED_OFFSET
|
|
var indexBuffer = new Buffer(4)
|
|
indexBuffer.writeUInt32BE(index, 0)
|
|
|
|
var data
|
|
|
|
// Hardened child
|
|
if (isHardened) {
|
|
assert(this.privateKey, 'Could not derive hardened child key')
|
|
|
|
var pk = this.privateKey
|
|
var zb = new Buffer([0])
|
|
pk = Buffer.concat([zb, pk])
|
|
|
|
// data = 0x00 || ser256(kpar) || ser32(index)
|
|
data = Buffer.concat([pk, indexBuffer])
|
|
|
|
// Normal child
|
|
} else {
|
|
// data = serP(point(kpar)) || ser32(index)
|
|
// = serP(Kpar) || ser32(index)
|
|
data = Buffer.concat([
|
|
this.publicKey,
|
|
indexBuffer
|
|
])
|
|
}
|
|
|
|
//var I = crypto.HmacSHA512(data, this.chaincode)
|
|
var I = sha512.hmac(this.chainCode).finalize(data)
|
|
var IL = I.slice(0, 32)
|
|
var IR = I.slice(32)
|
|
|
|
var hd = new HDKey(this.versions)
|
|
var pIL = BigInteger.fromBuffer(IL)
|
|
|
|
// Private parent key -> private child key
|
|
if (this.privateKey) {
|
|
// ki = parse256(IL) + kpar (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) {
|
|
return this.derive(index + 1)
|
|
}
|
|
|
|
//hd.priv = new ECKey(ki.toBuffer(), true)
|
|
//hd.pub = hd.priv.publicPoint
|
|
hd.privateKey = ki.toBuffer()
|
|
|
|
// Public parent key -> public child key
|
|
} else {
|
|
// Ki = point(parse256(IL)) + Kpar
|
|
// = G*IL + Kpar
|
|
//var Ki = ecparams.getG().multiply(pIL).add(this.pub.Q)
|
|
|
|
// In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i
|
|
//if (pIL.compareTo(ecparams.getN()) >= 0 || Ki.isInfinity()) {
|
|
// return this.derive(index + 1)
|
|
//}
|
|
|
|
//hd.pub = new ECPubKey(Ki, true)
|
|
}
|
|
|
|
hd.chainCode = IR
|
|
hd.depth = this.depth + 1
|
|
hd.parentFingerprint = this.getFingerprint().readUInt32BE(0)
|
|
hd.index = 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
|
|
|
|
}
|
|
|
|
|