Browse Source

added method fromMasterSeed, changed constructor, added privateKey and publicKey

master
JP Richardson 11 years ago
parent
commit
1058ea2e70
  1. 3
      CHANGELOG.md
  2. 98
      lib/hdkey.js
  3. 20
      test/hdkey.test.js

3
CHANGELOG.md

@ -4,6 +4,9 @@ x.y.z / 2014-06-dd
- removed `ECKey` dep
- added `ecurve` dep
- removed `terst` dev dep for `assert`
- added method `fromMasterSeed(seedBuffer, [versions])`
- changed constructor from `new HDKey(masterSeed, [versions])` to `new HDKey([versions])`
- added properties: `privateKey` and `publicKey`
0.0.1 / 2014-05-29
------------------

98
lib/hdkey.js

@ -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))
}
}

20
test/hdkey.test.js

@ -12,14 +12,16 @@ function encode(buf) {
}
describe('hdkey', function() {
var f = fixtures.valid.forEach(function(f) {
it('should properly derive the chain path: ' + f.path, function() {
var hdkey = new HDKey(new Buffer(f.seed, 'hex'))
var childkey = hdkey.derive(f.path)
describe('+ fromMasterSeed', function() {
var f = fixtures.valid.forEach(function(f) {
it('should properly derive the chain path: ' + f.path, function() {
var hdkey = HDKey.fromMasterSeed(new Buffer(f.seed, 'hex'))
var childkey = hdkey.derive(f.path)
assert.equal(encode(childkey.private), f.private)
assert.equal(encode(childkey.public), f.public)
})
})
assert.equal(encode(childkey.privateOld), f.private)
assert.equal(encode(childkey.publicOld), f.public)
})
})
})
})
Loading…
Cancel
Save