diff --git a/lib/hdkey.js b/lib/hdkey.js index aba30b0..0a10b95 100644 --- a/lib/hdkey.js +++ b/lib/hdkey.js @@ -23,10 +23,13 @@ function HDKey(versions) { this._privateKeyInteger = BigInteger.ZERO this._publicKey = null this.chainCode = null + this._fingerprint = 0 + this.parentFingerprint = 0 } -Object.defineProperty(HDKey.prototype, 'fingerprint', {get: function() { return this.identifier.slice(0, 4) } }) +Object.defineProperty(HDKey.prototype, 'fingerprint', { get: function() { return this._fingerprint } }) Object.defineProperty(HDKey.prototype, 'identifier', {get: function() { return this._identifier } }) +Object.defineProperty(HDKey.prototype, 'pubKeyHash', {get: function() { return this.identifier }}) Object.defineProperty(HDKey.prototype, 'privateKey', { get: function() { @@ -38,6 +41,7 @@ Object.defineProperty(HDKey.prototype, 'privateKey', { this._privateKeyInteger = BigInteger.fromBuffer(this._privateKey) this._publicKey = ecparams.params.G.multiply(this._privateKeyInteger).getEncoded(true) //force compressed point this._identifier = hash160(this.publicKey) + this._fingerprint = this._identifier.slice(0, 4).readUInt32BE(0) } }) @@ -49,6 +53,8 @@ Object.defineProperty(HDKey.prototype, 'publicKey', { 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._identifier = hash160(this.publicKey) + this._fingerprint = this._identifier.slice(0, 4).readUInt32BE(0) this._privateKey = null this._privateKeyInteger = null } @@ -147,7 +153,7 @@ HDKey.prototype.deriveChild = function(index) { hd.chainCode = IR hd.depth = this.depth + 1 - hd.parentFingerprint = this.fingerprint.readUInt32BE(0) + hd.parentFingerprint = this.fingerprint//.readUInt32BE(0) hd.index = index return hd @@ -165,6 +171,31 @@ HDKey.fromMasterSeed = function(seedBuffer, versions) { return hdkey } +HDKey.fromExtendedKey = function(keyBuffer, versions) { + // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) + versions = versions || BITCOIN_VERSIONS + var hdkey = new HDKey(versions) + + var version = keyBuffer.readUInt32BE(0) + assert(version === versions.private || version === versions.public, 'Version mismatch: does not match private or public') + + hdkey.depth = keyBuffer.readUInt8(4) + hdkey.parentFingerprint = keyBuffer.readUInt32BE(5) + hdkey.index = keyBuffer.readUInt32BE(9) + hdkey.chainCode = keyBuffer.slice(13, 45) + + var key = keyBuffer.slice(45) + if (key.readUInt8(0) === 0) { //private + assert(version === versions.private, 'Version mismatch: version does not match private') + hdkey.privateKey = key.slice(1) //cut off first 0x0 byte + } else { + assert(version === versions.public, 'Version mismatch: version does not match public') + hdkey.publicKey = key + } + + return hdkey +} + function serialize(hdkey, version, key) { // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) var buffer = new Buffer(LEN) diff --git a/test/hdkey.test.js b/test/hdkey.test.js index 5850217..c3eeff2 100644 --- a/test/hdkey.test.js +++ b/test/hdkey.test.js @@ -23,6 +23,9 @@ describe('hdkey', function() { var hdkey = HDKey.fromMasterSeed(new Buffer(f.seed, 'hex')) var childkey = hdkey.derive(f.path) + if (encode(childkey.privateExtendedKey) == "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j") + console.log(childkey.fingerprint.toString('16')) + assert.equal(encode(childkey.privateExtendedKey), f.private) assert.equal(encode(childkey.publicExtendedKey), f.public) }) @@ -62,4 +65,42 @@ describe('hdkey', function() { hdkey.publicKey = pub }) }) + + describe('+ fromExtendedKey()', function() { + describe('> when private', function() { + it('should parse it', function() { + //m/0/2147483647'/1/2147483646'/2 + var key = "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" + var keyBuffer = bs58.decode(key).slice(0, 78) + var hdkey = HDKey.fromExtendedKey(keyBuffer) + assert.equal(hdkey.versions.private, 0x0488ade4) + assert.equal(hdkey.versions.public, 0x0488b21e) + assert.equal(hdkey.depth, 5) + assert.equal(hdkey.parentFingerprint, 0x31a507b8) + assert.equal(hdkey.index, 2) + assert.equal(hdkey.chainCode.toString('hex'), '9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271') + assert.equal(hdkey.privateKey.toString('hex'), 'bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23') + assert.equal(hdkey.publicKey.toString('hex'), '024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c') + assert.equal(hdkey.identifier.toString('hex'), '26132fdbe7bf89cbc64cf8dafa3f9f88b8666220') + }) + }) + + describe('> when public', function() { + it('should parse it', function() { + //m/0/2147483647'/1/2147483646'/2 + var key = "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt" + var keyBuffer = bs58.decode(key).slice(0, 78) + var hdkey = HDKey.fromExtendedKey(keyBuffer) + assert.equal(hdkey.versions.private, 0x0488ade4) + assert.equal(hdkey.versions.public, 0x0488b21e) + assert.equal(hdkey.depth, 5) + assert.equal(hdkey.parentFingerprint, 0x31a507b8) + assert.equal(hdkey.index, 2) + assert.equal(hdkey.chainCode.toString('hex'), '9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271') + assert.equal(hdkey.privateKey, null) + assert.equal(hdkey.publicKey.toString('hex'), '024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c') + assert.equal(hdkey.identifier.toString('hex'), '26132fdbe7bf89cbc64cf8dafa3f9f88b8666220') + }) + }) + }) })