mirror of https://github.com/lukechilds/hdkey.git
JP Richardson
11 years ago
4 changed files with 164 additions and 15 deletions
@ -1,27 +1,179 @@ |
|||
var sha512 = require('sha512') |
|||
var ECKey = require('eckey') |
|||
var BigInteger = require('bigi') |
|||
var crypto = require('crypto') |
|||
var assert = require('assert') |
|||
|
|||
module.exports = HDKey |
|||
|
|||
var MASTER_SECRET = new Buffer('Bitcoin seed') |
|||
var HARDENED_BASE = 0x80000000 |
|||
var HARDENED_OFFSET = 0x80000000 |
|||
var LEN = 78 |
|||
|
|||
//I hate that this is hardcoded, but for now...
|
|||
var N = BigInteger.fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141") |
|||
|
|||
//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 || !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) |
|||
|
|||
this.priv = new ECKey(IL, true) |
|||
this.pub = this.priv.publicPoint |
|||
this.chaincode = IR |
|||
this.depth = 0 |
|||
this.index = 0 |
|||
} |
|||
|
|||
Object.defineProperty(HDKey.prototype, 'private', { |
|||
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.priv, 'Missing private key') |
|||
|
|||
// 0x00 + k for private keys
|
|||
buffer.writeUInt8(0, 45) |
|||
this.priv.privateKey.copy(buffer, 46) |
|||
|
|||
|
|||
return buffer |
|||
} |
|||
}) |
|||
|
|||
HDKey.prototype.getIdentifier = function() { |
|||
//just computing pubKeyHash here
|
|||
var buf = new Buffer(this.pub.getEncoded(true)) |
|||
var sha = crypto.createHash('sha256').update(buf).digest() |
|||
return crypto.createHash('rmd160').update(sha).digest() |
|||
} |
|||
|
|||
HDKey.prototype.getFingerprint = function() { |
|||
return this.getIdentifier().slice(0, 4) |
|||
} |
|||
|
|||
|
|||
HDKey.prototype.derive = function(path) { |
|||
return new HDKey(new Buffer([0])) |
|||
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.priv, 'Could not derive hardened child key') |
|||
|
|||
var pk = this.priv.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([ |
|||
new Buffer(this.publicPoint.getEncoded(true)), |
|||
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() |
|||
var pIL = BigInteger.fromBuffer(IL) |
|||
|
|||
// Private parent key -> private child key
|
|||
if (this.priv) { |
|||
// ki = parse256(IL) + kpar (mod n)
|
|||
var ki = pIL.add(BigInteger.fromBuffer(this.priv.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 |
|||
|
|||
// 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 |
|||
} |
Loading…
Reference in new issue