From 799b69eb1e511207947232f30a9d06142bf0eb69 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 31 May 2014 15:24:03 +1000 Subject: [PATCH 01/28] HDWallet: use assert.throws --- test/hdwallet.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/hdwallet.js b/test/hdwallet.js index 0fc7987..4de534a 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -23,13 +23,10 @@ describe('HDWallet', function() { it('fails with priv=true when theres no private key', function() { var hd = HDWallet.fromBase58('xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon') - try { + + assert.throws(function() { hd.toBase58(true) - } catch(e) { - assert(e.message.match(/private key/i)) - return - } - assert.fail() + }, /Missing private key/) }) }) From 58dd86bda1c59409a58f7c636bbc569c68a8eee3 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 31 May 2014 15:32:31 +1000 Subject: [PATCH 02/28] HDWallet: moves fromBuffer tests to data driven style --- test/fixtures/hdwallet.json | 14 +++++++++++++ test/hdwallet.js | 41 ++++++++++++------------------------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdwallet.json index bcebf2c..a983b8f 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdwallet.json @@ -190,6 +190,20 @@ "exception": "Invalid BIP32 string", "string": "37hdAfw3aMiWcBGPP2ywmY5jizTeSSP5GXayKY3RxkEZ7f3SBnRE1pN6eY3VzGkgx6vbdNtuKfrHgEaYvW9KkFZCycaPvWiA9TtfmeVB592Sf9RfSzQzXo72" } + ], + "fromBuffer": [ + { + "exception": "Invalid parent fingerprint", + "hex": "0488b21e00ffffffff000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" + }, + { + "exception": "Invalid index", + "hex": "0488b21e0000000000ffffffff7ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" + }, + { + "exception": "Could not find version 22222222", + "hex": "222222220000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" + } ] } } diff --git a/test/hdwallet.js b/test/hdwallet.js index 4de534a..cebca2d 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -40,12 +40,21 @@ describe('HDWallet', function() { }) }) + describe('fromBuffer', function() { + fixtures.invalid.fromBuffer.forEach(function(f) { + it('throws on ' + f.exception, function() { + var buffer = new Buffer(f.hex, 'hex') + + assert.throws(function() { + HDWallet.fromBuffer(buffer) + }, new RegExp(f.exception)) + }) + }) + }) + describe('constructor & seed deserialization', function() { var expectedPrivateKey = '0fd71c652e847ba7ea7956e3cf3fc0a0985871846b1b2c23b9c6a29a38cee860' - var seed = new Buffer([ - 99, 114, 97, 122, 121, 32, 104, 111, 114, 115, 101, 32, 98, - 97, 116, 116, 101, 114, 121, 32, 115, 116, 97, 112, 108, 101 - ]) + var seed = new Buffer('6372617a7920686f727365206261747465727920737461706c65', 'hex') it('creates from binary seed', function() { var hd = new HDWallet(seed) @@ -62,30 +71,6 @@ describe('HDWallet', function() { assert(hd.pub) }) }) - - describe('fromBuffer', function() { - it('fails for invalid parent fingerprint', function() { - var buffer = new HDWallet(seed).toBuffer() - buffer.writeUInt8(0x00, 4) - buffer.writeUInt32BE(0xFFFFFFFF, 5) - assert.throws(function() { HDWallet.fromBuffer(buffer) }, /Invalid parent fingerprint/) - }) - - it('fails for invalid index', function() { - var buffer = new HDWallet(seed).toBuffer() - buffer.writeUInt32BE(0xFFFFFFFF, 9) - assert.throws(function() { HDWallet.fromBuffer(buffer) }, /Invalid index/) - }) - - it('fails for an invalid network type', function() { - var network = { bip32: { priv: 0x11111111, pub: 0x22222222 } } - var buffer = new HDWallet(seed, network).toBuffer() - - assert.throws(function() { - HDWallet.fromBuffer(buffer) - }, /Could not find version 22222222/) - }) - }) }) describe('Test vectors', function() { From 02f5fceda7cfca962566ef163032e706574256da Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 31 May 2014 18:53:11 +1000 Subject: [PATCH 03/28] HDWallet: remove unused imports --- src/hdwallet.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index b0ce031..86b6540 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -1,8 +1,6 @@ var assert = require('assert') var base58 = require('./base58') -var convert = require('./convert') -var Address = require('./address') var BigInteger = require('bigi') var crypto = require('./crypto') var ECKey = require('./eckey') From ac9e259fcd3f5bfb19425e27d6e7f8633ad87d48 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 31 May 2014 18:53:39 +1000 Subject: [PATCH 04/28] HDWallet: remove getKeyVersion --- src/hdwallet.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index 86b6540..2888683 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -118,7 +118,7 @@ HDWallet.prototype.getFingerprint = function() { } HDWallet.prototype.getAddress = function() { - return this.pub.getAddress(this.getKeyVersion()) + return this.pub.getAddress(this.network.pubKeyHash) } HDWallet.prototype.toBuffer = function(priv) { @@ -249,10 +249,6 @@ HDWallet.prototype.derivePrivate = function(index) { return this.derive(index + HDWallet.HIGHEST_BIT) } -HDWallet.prototype.getKeyVersion = function() { - return this.network.pubKeyHash -} - HDWallet.prototype.toString = HDWallet.prototype.toBase58 module.exports = HDWallet From c0006c299ff1ba676cf8193363972962521f8894 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 31 May 2014 20:16:42 +1000 Subject: [PATCH 05/28] HDWallet: use new constructor --- src/hdwallet.js | 128 ++++++++++++++++++++---------------- src/wallet.js | 2 +- test/fixtures/hdwallet.json | 24 +++---- test/hdwallet.js | 28 ++++---- 4 files changed, 99 insertions(+), 83 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index 2888683..22413a8 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -5,39 +5,49 @@ var BigInteger = require('bigi') var crypto = require('./crypto') var ECKey = require('./eckey') var ECPubKey = require('./ecpubkey') +var ECPointFp = require('./ec').ECPointFp var networks = require('./networks') var sec = require('./sec') var ecparams = sec("secp256k1") -function HDWallet(seed, network) { - if (seed == undefined) return; // FIXME: Boo, should be stricter - +function HDWallet(K, chainCode, network) { network = network || networks.bitcoin - assert(network.bip32, 'Unknown BIP32 constants for network') - var I = crypto.HmacSHA512(seed, HDWallet.MASTER_SECRET) - var IL = I.slice(0, 32) - var IR = I.slice(32) - - // In case IL is 0 or >= n, the master key is invalid (handled by ECKey.fromBuffer) - var pIL = BigInteger.fromBuffer(IL) - - this.network = network - this.priv = new ECKey(pIL, true) - this.pub = this.priv.pub + assert(Buffer.isBuffer(chainCode), 'Expected Buffer, got ' + chainCode) + assert(network.bip32, 'Unknown BIP32 constants for network') - this.chaincode = IR + this.chainCode = chainCode this.depth = 0 this.index = 0 + this.network = network + + if (K instanceof BigInteger) { + this.priv = new ECKey(K, true) + this.pub = this.priv.pub + } else { + this.pub = new ECPubKey(K, true) + } } HDWallet.MASTER_SECRET = new Buffer('Bitcoin seed') HDWallet.HIGHEST_BIT = 0x80000000 HDWallet.LENGTH = 78 +HDWallet.fromSeedBuffer = function(seed, network) { + var I = crypto.HmacSHA512(seed, HDWallet.MASTER_SECRET) + var IL = I.slice(0, 32) + var IR = I.slice(32) + + // In case IL is 0 or >= n, the master key is invalid + // This is handled by `new ECKey` in the HDWallet constructor + var pIL = BigInteger.fromBuffer(IL) + + return new HDWallet(pIL, IR, network) +} + HDWallet.fromSeedHex = function(hex, network) { - return new HDWallet(new Buffer(hex, 'hex'), network) + return HDWallet.fromSeedBuffer(new Buffer(hex, 'hex'), network) } HDWallet.fromBase58 = function(string) { @@ -53,59 +63,66 @@ HDWallet.fromBase58 = function(string) { return HDWallet.fromBuffer(payload) } -HDWallet.fromBuffer = function(input) { - assert.strictEqual(input.length, HDWallet.LENGTH, 'Invalid buffer length') - - var hd = new HDWallet() +HDWallet.fromBuffer = function(buffer) { + assert.strictEqual(buffer.length, HDWallet.LENGTH, 'Invalid buffer length') // 4 byte: version bytes - var version = input.readUInt32BE(0) + var version = buffer.readUInt32BE(0) - var type + var hdNetwork, isPrivate for (var name in networks) { var network = networks[name] - for (var t in network.bip32) { - if (version != network.bip32[t]) continue + for (var type in network.bip32) { + if (version != network.bip32[type]) continue - type = t - hd.network = network + hdNetwork = network + isPrivate = (type === 'priv') } } - if (!hd.network) { - throw new Error('Could not find version ' + version.toString(16)) - } + assert(hdNetwork, 'Could not find version ' + version.toString(16)) // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... - hd.depth = input.readUInt8(4) + var depth = buffer.readUInt8(4) // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) - hd.parentFingerprint = input.readUInt32BE(5) - if (hd.depth === 0) { - assert.strictEqual(hd.parentFingerprint, 0x00000000, 'Invalid parent fingerprint') + var parentFingerprint = buffer.readUInt32BE(5) + if (depth === 0) { + assert.strictEqual(parentFingerprint, 0x00000000, 'Invalid parent fingerprint') } // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. // This is encoded in MSB order. (0x00000000 if master key) - hd.index = input.readUInt32BE(9) - assert(hd.depth > 0 || hd.index === 0, 'Invalid index') + var index = buffer.readUInt32BE(9) + assert(depth > 0 || index === 0, 'Invalid index') // 32 bytes: the chain code - hd.chaincode = input.slice(13, 45) + var chainCode = buffer.slice(13, 45) // 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for // public keys, 0x00 + k for private keys) - if (type == 'priv') { - assert.equal(input.readUInt8(45), 0, 'Invalid private key') - var D = BigInteger.fromBuffer(input.slice(46, 78)) + var data = buffer.slice(45, 78) + + var hd + if (isPrivate) { + assert.strictEqual(data.readUInt8(0), 0x00, 'Invalid private key') + data = data.slice(1) - hd.priv = new ECKey(D, true) - hd.pub = hd.priv.pub + var D = BigInteger.fromBuffer(data) + hd = new HDWallet(D, chainCode, hdNetwork) } else { - hd.pub = ECPubKey.fromBuffer(input.slice(45, 78), true) + + var decode = ECPointFp.decodeFrom(ecparams.getCurve(), data) + assert.equal(decode.compressed, true, 'Invalid public key') + + hd = new HDWallet(decode.Q, chainCode, hdNetwork) } + hd.depth = depth + hd.index = index + hd.parentFingerprint = parentFingerprint + return hd } @@ -142,7 +159,7 @@ HDWallet.prototype.toBuffer = function(priv) { 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 if (priv) { @@ -201,25 +218,28 @@ HDWallet.prototype.derive = function(index) { ]) } - var I = crypto.HmacSHA512(data, this.chaincode) + var I = crypto.HmacSHA512(data, this.chainCode) var IL = I.slice(0, 32) var IR = I.slice(32) - var hd = new HDWallet() var pIL = BigInteger.fromBuffer(IL) + // In case parse256(IL) >= n, proceed with the next value for i + if (pIL.compareTo(ecparams.getN()) >= 0) { + return this.derive(index + 1) + } + // Private parent key -> private child key if (this.priv) { // ki = parse256(IL) + kpar (mod n) var ki = pIL.add(this.priv.D).mod(ecparams.getN()) - // In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i - if (pIL.compareTo(ecparams.getN()) >= 0 || ki.signum() === 0) { + // In case ki == 0, proceed with the next value for i + if (ki.signum() === 0) { return this.derive(index + 1) } - hd.priv = new ECKey(ki, true) - hd.pub = hd.priv.pub + hd = new HDWallet(ki, IR, this.network) // Public parent key -> public child key } else { @@ -227,19 +247,17 @@ HDWallet.prototype.derive = function(index) { // = 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()) { + // In case Ki is the point at infinity, proceed with the next value for i + if (Ki.isInfinity()) { return this.derive(index + 1) } - hd.pub = new ECPubKey(Ki, true) + hd = new HDWallet(Ki, IR, this.network) } - hd.chaincode = IR hd.depth = this.depth + 1 - hd.network = this.network - hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) hd.index = index + hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) return hd } diff --git a/src/wallet.js b/src/wallet.js index 846a5ad..1255228 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -29,7 +29,7 @@ function Wallet(seed, network) { // Make a new master key this.newMasterKey = function(seed) { seed = seed || new Buffer(rng(32)) - masterkey = new HDNode(seed, network) + masterkey = HDNode.fromSeedBuffer(seed, network) // HD first-level child derivation method should be private // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdwallet.json index a983b8f..5ad7005 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdwallet.json @@ -8,7 +8,7 @@ "address": "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma", "wif": "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW", "pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", - "chaincode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", + "chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", "hexPriv": "0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", "base58": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", @@ -23,7 +23,7 @@ "address": "19Q2WoS5hSS6T8GjhK8KZLMgmWaq4neXrh", "wif": "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT", "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", - "chaincode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", + "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", "hex": "0488b21e013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", "hexPriv": "0488ade4013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae623614100edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea", "base58": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", @@ -37,7 +37,7 @@ "address": "1JQheacLPdM5ySCkrZkV66G2ApAXe1mqLj", "wif": "KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM", "pubKey": "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c", - "chaincode": "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19", + "chainCode": "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19", "hex": "0488b21e025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c1903501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c", "hexPriv": "0488ade4025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19003c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368", "base58": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", @@ -51,7 +51,7 @@ "address": "1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x", "wif": "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU", "pubKey": "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", - "chaincode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", + "chainCode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", "hex": "0488b21e03bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", "hexPriv": "0488ade403bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f00cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca", "base58": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", @@ -65,7 +65,7 @@ "address": "1LjmJcdPnDHhNTUgrWyhLGnRDKxQjoxAgt", "wif": "KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR", "pubKey": "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29", - "chaincode": "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd", + "chainCode": "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd", "hex": "0488b21e04ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29", "hexPriv": "0488ade404ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd000f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4", "base58": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", @@ -79,7 +79,7 @@ "address": "1LZiqrop2HGR4qrH1ULZPyBpU6AUP49Uam", "wif": "Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs", "pubKey": "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011", - "chaincode": "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e", + "chainCode": "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e", "hex": "0488b21e05d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011", "hexPriv": "0488ade405d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e00471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8", "base58": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", @@ -95,7 +95,7 @@ "address": "1JEoxevbLLG8cVqeoGKQiAwoWbNYSUyYjg", "wif": "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy", "pubKey": "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", - "chaincode": "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689", + "chainCode": "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689", "hex": "0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", "hexPriv": "0488ade400000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689004b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e", "base58": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", @@ -110,7 +110,7 @@ "address": "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ", "wif": "L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj", "pubKey": "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", - "chaincode": "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c", + "chainCode": "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c", "hex": "0488b21e01bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", "hexPriv": "0488ade401bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c00abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e", "base58": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", @@ -124,7 +124,7 @@ "address": "1Lke9bXGhn5VPrBuXgN12uGUphrttUErmk", "wif": "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr", "pubKey": "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", - "chaincode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9", + "chainCode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9", "hex": "0488b21e025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d903c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", "hexPriv": "0488ade4025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d900877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93", "base58": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", @@ -138,7 +138,7 @@ "address": "1BxrAr2pHpeBheusmd6fHDP2tSLAUa3qsW", "wif": "KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2", "pubKey": "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9", - "chaincode": "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb", + "chainCode": "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb", "hex": "0488b21e03d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9", "hexPriv": "0488ade403d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb00704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7", "base58": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", @@ -152,7 +152,7 @@ "address": "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R", "wif": "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF", "pubKey": "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", - "chaincode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", + "chainCode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", "hex": "0488b21e0478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2902d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", "hexPriv": "0488ade40478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2900f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d", @@ -167,7 +167,7 @@ "address": "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt", "wif": "L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK", "pubKey": "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", - "chaincode": "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271", + "chainCode": "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271", "hex": "0488b21e0531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", "hexPriv": "0488ade40531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed27100bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23", "base58": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", diff --git a/test/hdwallet.js b/test/hdwallet.js index cebca2d..d39eac3 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -52,12 +52,12 @@ describe('HDWallet', function() { }) }) - describe('constructor & seed deserialization', function() { + describe('fromSeedBuffer', function() { var expectedPrivateKey = '0fd71c652e847ba7ea7956e3cf3fc0a0985871846b1b2c23b9c6a29a38cee860' - var seed = new Buffer('6372617a7920686f727365206261747465727920737461706c65', 'hex') + var seedHex = '6372617a7920686f727365206261747465727920737461706c65' - it('creates from binary seed', function() { - var hd = new HDWallet(seed) + it('creates from a binary seed', function() { + var hd = HDWallet.fromSeedBuffer(new Buffer(seedHex, 'hex')) assert.equal(hd.priv.D.toHex(), expectedPrivateKey) assert(hd.pub) @@ -65,7 +65,7 @@ describe('HDWallet', function() { describe('fromSeedHex', function() { it('creates from hex seed', function() { - var hd = HDWallet.fromSeedHex(seed.toString('hex')) + var hd = HDWallet.fromSeedHex(seedHex) assert.equal(hd.priv.D.toHex(), expectedPrivateKey) assert(hd.pub) @@ -80,9 +80,7 @@ describe('HDWallet', function() { assert.equal(hd.getAddress().toString(), v.address) assert.equal(hd.priv.toWIF(), v.wif) assert.equal(hd.pub.toHex(), v.pubKey) - assert.equal(b2h(hd.chaincode), v.chaincode) - assert.equal(hd.toHex(false), v.hex) - assert.equal(hd.toHex(true), v.hexPriv) + assert.equal(b2h(hd.chainCode), v.chainCode) assert.equal(hd.toBase58(false), v.base58) assert.equal(hd.toBase58(true), v.base58Priv) } @@ -128,28 +126,28 @@ describe('HDWallet', function() { }) it('ensure that a bitcoin wallet is the default', function() { - var wallet = new HDWallet(seed) + var hd = HDWallet.fromSeedBuffer(seed) - assert.equal(wallet.network, networks.bitcoin) + assert.equal(hd.network, networks.bitcoin) }) it('ensures that a bitcoin Wallet generates bitcoin addresses', function() { - var wallet = new HDWallet(seed) - var address = wallet.getAddress().toString() + var hd = HDWallet.fromSeedBuffer(seed, networks.bitcoin) + var address = hd.getAddress().toString() assert.equal(address, '17SnB9hyGwJPoKpLb9eVPHjsujyEuBpMAA') }) it('ensures that a testnet Wallet generates testnet addresses', function() { - var wallet = new HDWallet(seed, networks.testnet) - var address = wallet.getAddress().toString() + var hd = HDWallet.fromSeedBuffer(seed, networks.testnet) + var address = hd.getAddress().toString() assert.equal(address, 'mmxjUCnx5xjeaSHxJicsDCxCmjZwq8KTbv') }) it('throws an exception when unknown network type is passed in', function() { assert.throws(function() { - new HDWallet(seed, {}) + HDWallet.fromSeedBuffer(seed, {}) }, /Unknown BIP32 constants for network/) }) }) From 759aeb9abcc57de0b9267b7179d7e88387d75088 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 31 May 2014 20:17:41 +1000 Subject: [PATCH 06/28] HDWallet: remove repeated assertion --- src/hdwallet.js | 3 +-- test/fixtures/hdwallet.json | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index 22413a8..9d5f2e2 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -55,10 +55,9 @@ HDWallet.fromBase58 = function(string) { var payload = buffer.slice(0, -4) var checksum = buffer.slice(-4) - var newChecksum = crypto.hash256(payload).slice(0, 4) + var newChecksum = crypto.hash256(payload).slice(0, 4) assert.deepEqual(newChecksum, checksum, 'Invalid checksum') - assert.equal(payload.length, HDWallet.LENGTH, 'Invalid BIP32 string') return HDWallet.fromBuffer(payload) } diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdwallet.json index 5ad7005..cf00869 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdwallet.json @@ -183,11 +183,11 @@ "string": "xprvQQQQQQQQQQQQQQQQCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" }, { - "exception": "Invalid BIP32 string", + "exception": "Invalid buffer length", "string": "SQ8nQ2jWarXqLo9oHGKKP6iQDsQbPRftq7rjtYY3hqJRPQRgrmeunFnDKbH7B15yGPLZBrhhkKXx3pwD6LcBooJRGq6x7matAXpMsgn" }, { - "exception": "Invalid BIP32 string", + "exception": "Invalid buffer length", "string": "37hdAfw3aMiWcBGPP2ywmY5jizTeSSP5GXayKY3RxkEZ7f3SBnRE1pN6eY3VzGkgx6vbdNtuKfrHgEaYvW9KkFZCycaPvWiA9TtfmeVB592Sf9RfSzQzXo72" } ], From 708e27aa2db3f99e7d62ece9b7ac0f0943cb6e37 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 31 May 2014 20:22:32 +1000 Subject: [PATCH 07/28] HDWallet: extract BIP32 params search function --- src/hdwallet.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index 9d5f2e2..5432043 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -11,6 +11,23 @@ var networks = require('./networks') var sec = require('./sec') var ecparams = sec("secp256k1") +function findBIP32ParamsByVersion(version) { + for (var name in networks) { + var network = networks[name] + + for (var type in network.bip32) { + if (version != network.bip32[type]) continue + + return { + isPrivate: (type === 'priv'), + network: network + } + } + } + + assert(false, 'Could not find version ' + version.toString(16)) +} + function HDWallet(K, chainCode, network) { network = network || networks.bitcoin @@ -67,20 +84,7 @@ HDWallet.fromBuffer = function(buffer) { // 4 byte: version bytes var version = buffer.readUInt32BE(0) - - var hdNetwork, isPrivate - for (var name in networks) { - var network = networks[name] - - for (var type in network.bip32) { - if (version != network.bip32[type]) continue - - hdNetwork = network - isPrivate = (type === 'priv') - } - } - - assert(hdNetwork, 'Could not find version ' + version.toString(16)) + var params = findBIP32ParamsByVersion(version) // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... var depth = buffer.readUInt8(4) @@ -104,18 +108,18 @@ HDWallet.fromBuffer = function(buffer) { var data = buffer.slice(45, 78) var hd - if (isPrivate) { + if (params.isPrivate) { assert.strictEqual(data.readUInt8(0), 0x00, 'Invalid private key') data = data.slice(1) var D = BigInteger.fromBuffer(data) - hd = new HDWallet(D, chainCode, hdNetwork) + hd = new HDWallet(D, chainCode, params.network) } else { var decode = ECPointFp.decodeFrom(ecparams.getCurve(), data) assert.equal(decode.compressed, true, 'Invalid public key') - hd = new HDWallet(decode.Q, chainCode, hdNetwork) + hd = new HDWallet(decode.Q, chainCode, params.network) } hd.depth = depth From e8e862f6325c15c1a1c67f6dc19e873145fc0a67 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sat, 31 May 2014 20:28:16 +1000 Subject: [PATCH 08/28] HDWallet: adds PubKey validation --- src/hdwallet.js | 3 +++ test/fixtures/hdwallet.json | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/hdwallet.js b/src/hdwallet.js index 5432043..cfcc5fe 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -119,6 +119,9 @@ HDWallet.fromBuffer = function(buffer) { var decode = ECPointFp.decodeFrom(ecparams.getCurve(), data) assert.equal(decode.compressed, true, 'Invalid public key') + // When importing a serialized extended public key, implementations must verify whether the X coordinate in the public key data corresponds to a point on the curve. If not, the extended public key is invalid. + decode.Q.validate() + hd = new HDWallet(decode.Q, chainCode, params.network) } diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdwallet.json index cf00869..7c93daa 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdwallet.json @@ -203,6 +203,10 @@ { "exception": "Could not find version 22222222", "hex": "222222220000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" + }, + { + "exception": "Point is not on the curve", + "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508020045400697100007000037899988826500030092003000016366806305909050" } ] } From 3f24e87c541c58683955fd50033091fc957ed6cd Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 1 Jun 2014 16:04:44 +1000 Subject: [PATCH 09/28] HDWallet: more explicit branch for parentFingerprint --- src/hdwallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index cfcc5fe..1283750 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -157,7 +157,7 @@ HDWallet.prototype.toBuffer = function(priv) { 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 + var fingerprint = (this.depth === 0) ? 0x00000000 : this.parentFingerprint buffer.writeUInt32BE(fingerprint, 5) // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. From 533d61380a5cb95442d0efe83059cf25b7e4606c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 1 Jun 2014 16:05:47 +1000 Subject: [PATCH 10/28] HDWallet: improve testing coverage --- test/hdwallet.js | 243 +++++++++++++++++++++++++++++------------------ 1 file changed, 152 insertions(+), 91 deletions(-) diff --git a/test/hdwallet.js b/test/hdwallet.js index d39eac3..5e22865 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -1,28 +1,90 @@ var assert = require('assert') var networks = require('../src/networks') +var sec = require('../src/sec') +var ecparams = sec("secp256k1") +var BigInteger = require('bigi') var HDWallet = require('../src/hdwallet') -var fixtures = require('./fixtures/hdwallet.json') -function b2h(buf) { - assert(Buffer.isBuffer(buf)) - return buf.toString('hex') -} +var fixtures = require('./fixtures/hdwallet.json') describe('HDWallet', function() { + describe('Constructor', function() { + var D = BigInteger.ONE + var Q = ecparams.getG().multiply(D) + var chainCode = new Buffer(32) + chainCode.fill(1) + + it('calculates the publicKey from a BigInteger', function() { + var hd = new HDWallet(D, chainCode) + + assert(hd.pub.Q.equals(Q)) + }) + + it('only uses compressed points', function() { + var hd = new HDWallet(Q, chainCode) + var hdP = new HDWallet(D, chainCode) + + assert.strictEqual(hd.pub.compressed, true) + assert.strictEqual(hdP.pub.compressed, true) + }) + + it('has a default depth/index of 0', function() { + var hd = new HDWallet(Q, chainCode) + + assert.strictEqual(hd.depth, 0) + assert.strictEqual(hd.index, 0) + }) + + it('defaults to the bitcoin network', function() { + var hd = new HDWallet(Q, chainCode) + + assert.equal(hd.network, networks.bitcoin) + }) + + it('supports alternative networks', function() { + var hd = new HDWallet(Q, chainCode, networks.testnet) + + assert.equal(hd.network, networks.testnet) + }) + + it('throws an exception when an unknown network is given', function() { + assert.throws(function() { + new HDWallet(D, chainCode, {}) + }, /Unknown BIP32 constants for network/) + }) + }) + + describe('fromSeed*', function() { + fixtures.valid.forEach(function(f) { + it('calculates privKey and chainCode for ' + f.master.fingerprint, function() { + var hd = HDWallet.fromSeedHex(f.master.seed) + + assert.equal(hd.priv.toWIF(), f.master.wif) + assert.equal(hd.chainCode.toString('hex'), f.master.chainCode) + }) + }) + }) + describe('toBase58', function() { - it('reproduces input', function() { - var input = 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5' - var output = HDWallet.fromBase58(input).toBase58(false) - assert.equal(output, input) + fixtures.valid.forEach(function(f) { + it('exports ' + f.master.base58 + ' (public) correctly', function() { + var hd = HDWallet.fromSeedHex(f.master.seed) + + assert.equal(hd.toBase58(), f.master.base58) + }) + }) - input = 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334' - output = HDWallet.fromBase58(input).toBase58(true) - assert.equal(output, input) + fixtures.valid.forEach(function(f) { + it('exports ' + f.master.base58Priv + ' (private) correctly', function() { + var hd = HDWallet.fromSeedHex(f.master.seed) + + assert.equal(hd.toBase58(true), f.master.base58Priv) + }) }) - it('fails with priv=true when theres no private key', function() { - var hd = HDWallet.fromBase58('xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon') + it('fails when there is no private key', function() { + var hd = HDWallet.fromBase58(fixtures.valid[0].master.base58) assert.throws(function() { hd.toBase58(true) @@ -31,6 +93,22 @@ describe('HDWallet', function() { }) describe('fromBase58', function() { + fixtures.valid.forEach(function(f) { + it('imports ' + f.master.base58 + ' (public) correctly', function() { + var hd = HDWallet.fromBase58(f.master.base58) + + assert.equal(hd.toBase58(), f.master.base58) + }) + }) + + fixtures.valid.forEach(function(f) { + it('imports ' + f.master.base58Priv + ' (private) correctly', function() { + var hd = HDWallet.fromBase58(f.master.base58Priv) + + assert.equal(hd.toBase58(true), f.master.base58Priv) + }) + }) + fixtures.invalid.fromBase58.forEach(function(f) { it('throws on ' + f.string, function() { assert.throws(function() { @@ -41,114 +119,97 @@ describe('HDWallet', function() { }) describe('fromBuffer', function() { - fixtures.invalid.fromBuffer.forEach(function(f) { - it('throws on ' + f.exception, function() { - var buffer = new Buffer(f.hex, 'hex') + fixtures.valid.forEach(function(f) { + it('imports ' + f.master.hex + ' (public) correctly', function() { + var hd = HDWallet.fromBuffer(new Buffer(f.master.hex, 'hex')) - assert.throws(function() { - HDWallet.fromBuffer(buffer) - }, new RegExp(f.exception)) + assert.equal(hd.toBuffer().toString('hex'), f.master.hex) }) }) - }) - - describe('fromSeedBuffer', function() { - var expectedPrivateKey = '0fd71c652e847ba7ea7956e3cf3fc0a0985871846b1b2c23b9c6a29a38cee860' - var seedHex = '6372617a7920686f727365206261747465727920737461706c65' - it('creates from a binary seed', function() { - var hd = HDWallet.fromSeedBuffer(new Buffer(seedHex, 'hex')) + fixtures.valid.forEach(function(f) { + it('imports ' + f.master.hexPriv + ' (private) correctly', function() { + var hd = HDWallet.fromBuffer(new Buffer(f.master.hexPriv, 'hex')) - assert.equal(hd.priv.D.toHex(), expectedPrivateKey) - assert(hd.pub) + assert.equal(hd.toBuffer(true).toString('hex'), f.master.hexPriv) + }) }) - describe('fromSeedHex', function() { - it('creates from hex seed', function() { - var hd = HDWallet.fromSeedHex(seedHex) - - assert.equal(hd.priv.D.toHex(), expectedPrivateKey) - assert(hd.pub) + fixtures.invalid.fromBuffer.forEach(function(f) { + it('throws on ' + f.string, function() { + assert.throws(function() { + HDWallet.fromBuffer(new Buffer(f.hex, 'hex')) + }, new RegExp(f.exception)) }) }) }) - describe('Test vectors', function() { - function verifyVector(hd, v) { - assert.equal(b2h(hd.getIdentifier()), v.identifier) - assert.equal(b2h(hd.getFingerprint()), v.fingerprint) - assert.equal(hd.getAddress().toString(), v.address) - assert.equal(hd.priv.toWIF(), v.wif) - assert.equal(hd.pub.toHex(), v.pubKey) - assert.equal(b2h(hd.chainCode), v.chainCode) - assert.equal(hd.toBase58(false), v.base58) - assert.equal(hd.toBase58(true), v.base58Priv) - } + describe('getIdentifier', function() { + var f = fixtures.valid[0] - it('matches the test vectors', function() { - fixtures.valid.forEach(function(f) { - var hd = HDWallet.fromSeedHex(f.master.seed) - verifyVector(hd, f.master) + it('returns the identifier for ' + f.master.fingerprint, function() { + var hd = HDWallet.fromBase58(f.master.base58) - f.children.forEach(function(c) { - // FIXME: c.description could be shown - if (c.mPriv != undefined) { - hd = hd.derivePrivate(c.mPriv) - } else { - hd = hd.derive(c.m) - } - - verifyVector(hd, c) - }) - }) + assert.equal(hd.getIdentifier().toString('hex'), f.master.identifier) }) }) - describe('derive', function() { - describe('m/0', function() { - var wallet = HDWallet.fromBase58('xpub6CxuB8ifZCMXeS3KbyNkYvrsJEHqxedCSiUhrNwH1nKtb8hcJpxDbDxkdoVCTR2bQ1G8hY4UMv85gef9SEpgFFUftBjt37FUSZxVx4AU9Qh').derive(0) + describe('getFingerprint', function() { + var f = fixtures.valid[0] - it('derives the correct public key', function() { - assert.equal(wallet.pub.toHex(), '02df843e6ae2017e0772d0584f76f56b8f2f5181a3045c7a7740a9d86dc7c80ce7') - }) + it('returns the fingerprint for ' + f.master.fingerprint, function() { + var hd = HDWallet.fromBase58(f.master.base58) - it('derives the correct depth', function() { - assert.equal(wallet.depth, 4) - }) + assert.equal(hd.getFingerprint().toString('hex'), f.master.fingerprint) }) }) - describe('network types', function() { - var seed + describe('getAddress', function() { + var f = fixtures.valid[0] + + it('returns the Address (pubKeyHash) for ' + f.master.fingerprint, function() { + var hd = HDWallet.fromBase58(f.master.base58) - beforeEach(function() { - seed = new Buffer('foobar') + assert.equal(hd.getAddress().toString(), f.master.address) }) - it('ensure that a bitcoin wallet is the default', function() { - var hd = HDWallet.fromSeedBuffer(seed) + it('supports alternative networks', function() { + var hd = HDWallet.fromBase58(f.master.base58) + hd.network = networks.testnet - assert.equal(hd.network, networks.bitcoin) + assert.equal(hd.getAddress().version, networks.testnet.pubKeyHash) }) + }) - it('ensures that a bitcoin Wallet generates bitcoin addresses', function() { - var hd = HDWallet.fromSeedBuffer(seed, networks.bitcoin) - var address = hd.getAddress().toString() + describe('derive', function() { + function verifyVector(hd, v, depth) { + assert.equal(hd.priv.toWIF(), v.wif) + assert.equal(hd.pub.toHex(), v.pubKey) + assert.equal(hd.chainCode.toString('hex'), v.chainCode) + assert.equal(hd.depth, depth || 0) + + if (v.mPriv != undefined) { + assert.equal(hd.index, v.mPriv + HDWallet.HIGHEST_BIT) + } else { + assert.equal(hd.index, v.m) + } + } - assert.equal(address, '17SnB9hyGwJPoKpLb9eVPHjsujyEuBpMAA') - }) + fixtures.valid.forEach(function(f, j) { + var hd = HDWallet.fromSeedHex(f.master.seed) - it('ensures that a testnet Wallet generates testnet addresses', function() { - var hd = HDWallet.fromSeedBuffer(seed, networks.testnet) - var address = hd.getAddress().toString() + f.children.forEach(function(c, i) { + it(c.description + ' from ' + f.master.fingerprint, function() { + if (c.mPriv != undefined) { + hd = hd.derivePrivate(c.mPriv) - assert.equal(address, 'mmxjUCnx5xjeaSHxJicsDCxCmjZwq8KTbv') - }) + } else { + hd = hd.derive(c.m) + } - it('throws an exception when unknown network type is passed in', function() { - assert.throws(function() { - HDWallet.fromSeedBuffer(seed, {}) - }, /Unknown BIP32 constants for network/) + verifyVector(hd, c, i + 1) + }) + }) }) }) }) From 0e61f41a5273965d9d1d5a8629b6297e3d9b68d4 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 1 Jun 2014 16:06:32 +1000 Subject: [PATCH 11/28] HDWallet: fix test data descriptions --- test/fixtures/hdwallet.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdwallet.json index 7c93daa..9702462 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdwallet.json @@ -16,7 +16,7 @@ }, "children": [ { - "description": "m/0", + "description": "m/0'", "mPriv": 0, "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", "fingerprint": "5c1bd648", @@ -44,7 +44,7 @@ "base58Priv": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" }, { - "description": "m/0'/1/2", + "description": "m/0'/1/2'", "mPriv": 2, "identifier": "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72", "fingerprint": "ee7ab90c", From 260705a24a45a9a7aeff703d5ab342d4885dc2a9 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 1 Jun 2014 16:09:54 +1000 Subject: [PATCH 12/28] HDWallet: consistent test data ordering Also adds network parameter for later moving the network specific tests to be data driven. --- test/fixtures/hdwallet.json | 123 ++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 56 deletions(-) diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdwallet.json index 9702462..6f469f3 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdwallet.json @@ -3,175 +3,186 @@ { "master": { "seed": "000102030405060708090a0b0c0d0e0f", - "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", - "fingerprint": "3442193e", - "address": "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma", "wif": "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW", "pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", "chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", "hexPriv": "0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", + "network": "bitcoin", "base58": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", - "base58Priv": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" + "base58Priv": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", + "fingerprint": "3442193e", + "address": "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma" }, "children": [ { "description": "m/0'", "mPriv": 0, - "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", - "fingerprint": "5c1bd648", - "address": "19Q2WoS5hSS6T8GjhK8KZLMgmWaq4neXrh", "wif": "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT", "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", "hex": "0488b21e013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", "hexPriv": "0488ade4013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae623614100edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea", + "network": "bitcoin", "base58": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", - "base58Priv": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7" + "base58Priv": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", + "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", + "fingerprint": "5c1bd648", + "address": "19Q2WoS5hSS6T8GjhK8KZLMgmWaq4neXrh" }, { "description": "m/0'/1", "m": 1, - "identifier": "bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe", - "fingerprint": "bef5a2f9", - "address": "1JQheacLPdM5ySCkrZkV66G2ApAXe1mqLj", "wif": "KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM", "pubKey": "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c", "chainCode": "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19", "hex": "0488b21e025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c1903501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c", "hexPriv": "0488ade4025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19003c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368", + "network": "bitcoin", "base58": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", - "base58Priv": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" + "base58Priv": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", + "identifier": "bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe", + "fingerprint": "bef5a2f9", + "address": "1JQheacLPdM5ySCkrZkV66G2ApAXe1mqLj" }, { "description": "m/0'/1/2'", "mPriv": 2, - "identifier": "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72", - "fingerprint": "ee7ab90c", - "address": "1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x", "wif": "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU", "pubKey": "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", "chainCode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", "hex": "0488b21e03bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", "hexPriv": "0488ade403bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f00cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca", + "network": "bitcoin", "base58": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", - "base58Priv": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM" + "base58Priv": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", + "identifier": "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72", + "fingerprint": "ee7ab90c", + "address": "1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x" }, { "description": "m/0'/1/2'/2", "m": 2, - "identifier": "d880d7d893848509a62d8fb74e32148dac68412f", - "fingerprint": "d880d7d8", - "address": "1LjmJcdPnDHhNTUgrWyhLGnRDKxQjoxAgt", "wif": "KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR", "pubKey": "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29", "chainCode": "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd", "hex": "0488b21e04ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29", "hexPriv": "0488ade404ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd000f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4", + "network": "bitcoin", "base58": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", - "base58Priv": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" + "base58Priv": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", + "identifier": "d880d7d893848509a62d8fb74e32148dac68412f", + "fingerprint": "d880d7d8", + "address": "1LjmJcdPnDHhNTUgrWyhLGnRDKxQjoxAgt" }, { "description": "m/0'/1/2'/2/1000000000", "m": 1000000000, - "identifier": "d69aa102255fed74378278c7812701ea641fdf32", - "fingerprint": "d69aa102", - "address": "1LZiqrop2HGR4qrH1ULZPyBpU6AUP49Uam", "wif": "Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs", "pubKey": "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011", "chainCode": "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e", "hex": "0488b21e05d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011", "hexPriv": "0488ade405d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e00471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8", + "network": "bitcoin", "base58": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", - "base58Priv": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" + "base58Priv": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", + "identifier": "d69aa102255fed74378278c7812701ea641fdf32", + "fingerprint": "d69aa102", + "address": "1LZiqrop2HGR4qrH1ULZPyBpU6AUP49Uam" } ] }, { "master": { - "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", - "identifier": "bd16bee53961a47d6ad888e29545434a89bdfe95", - "fingerprint": "bd16bee5", - "address": "1JEoxevbLLG8cVqeoGKQiAwoWbNYSUyYjg", - "wif": "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy", - "pubKey": "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", - "chainCode": "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689", - "hex": "0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", - "hexPriv": "0488ade400000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689004b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e", - "base58": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", - "base58Priv": "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" + "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + "wif": "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy", + "pubKey": "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", + "chainCode": "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689", + "network": "bitcoin", + "base58": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", + "base58Priv": "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", + "hex": "0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", + "hexPriv": "0488ade400000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689004b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e", + "identifier": "bd16bee53961a47d6ad888e29545434a89bdfe95", + "fingerprint": "bd16bee5", + "address": "1JEoxevbLLG8cVqeoGKQiAwoWbNYSUyYjg" }, "children": [ { "description": "m/0", "m": 0, - "identifier": "5a61ff8eb7aaca3010db97ebda76121610b78096", - "fingerprint": "5a61ff8e", - "address": "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ", "wif": "L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj", "pubKey": "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", "chainCode": "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c", "hex": "0488b21e01bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", "hexPriv": "0488ade401bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c00abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e", + "network": "bitcoin", "base58": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", - "base58Priv": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt" + "base58Priv": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", + "identifier": "5a61ff8eb7aaca3010db97ebda76121610b78096", + "fingerprint": "5a61ff8e", + "address": "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ" }, { "description": "m/0/2147483647", "mPriv": 2147483647, - "identifier": "d8ab493736da02f11ed682f88339e720fb0379d1", - "fingerprint": "d8ab4937", - "address": "1Lke9bXGhn5VPrBuXgN12uGUphrttUErmk", "wif": "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr", "pubKey": "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", "chainCode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9", "hex": "0488b21e025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d903c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", "hexPriv": "0488ade4025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d900877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93", + "network": "bitcoin", "base58": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", - "base58Priv": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9" + "base58Priv": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", + "identifier": "d8ab493736da02f11ed682f88339e720fb0379d1", + "fingerprint": "d8ab4937", + "address": "1Lke9bXGhn5VPrBuXgN12uGUphrttUErmk" }, { "description": "m/0/2147483647'/1", "m": 1, - "identifier": "78412e3a2296a40de124307b6485bd19833e2e34", - "fingerprint": "78412e3a", - "address": "1BxrAr2pHpeBheusmd6fHDP2tSLAUa3qsW", "wif": "KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2", "pubKey": "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9", "chainCode": "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb", "hex": "0488b21e03d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9", "hexPriv": "0488ade403d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb00704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7", + "network": "bitcoin", "base58": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", - "base58Priv": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" + "base58Priv": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", + "identifier": "78412e3a2296a40de124307b6485bd19833e2e34", + "fingerprint": "78412e3a", + "address": "1BxrAr2pHpeBheusmd6fHDP2tSLAUa3qsW" }, { "description": "m/0/2147483647'/1/2147483646", "mPriv": 2147483646, - "identifier": "31a507b815593dfc51ffc7245ae7e5aee304246e", - "fingerprint": "31a507b8", - "address": "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R", "wif": "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF", "pubKey": "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", "chainCode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", - "hex": "0488b21e0478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2902d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", "hexPriv": "0488ade40478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2900f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d", + "network": "bitcoin", "base58": "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", - "base58Priv": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc" + "base58Priv": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", + "identifier": "31a507b815593dfc51ffc7245ae7e5aee304246e", + "fingerprint": "31a507b8", + "address": "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R" }, { "description": "m/0/2147483647'/1/2147483646'/2", "m": 2, - "identifier": "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220", - "fingerprint": "26132fdb", - "address": "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt", "wif": "L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK", "pubKey": "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", "chainCode": "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271", "hex": "0488b21e0531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", "hexPriv": "0488ade40531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed27100bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23", + "network": "bitcoin", "base58": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", - "base58Priv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" + "base58Priv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", + "identifier": "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220", + "fingerprint": "26132fdb", + "address": "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt" } ] } From 4cec42a8d852b2f53e3312f42591606dea95441c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 1 Jun 2014 16:39:07 +1000 Subject: [PATCH 13/28] HDWallet: add to/fromHex tests --- src/hdwallet.js | 5 +++++ test/hdwallet.js | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index 1283750..014d0a0 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -132,6 +132,10 @@ HDWallet.fromBuffer = function(buffer) { return hd } +HDWallet.fromHex = function(hex, priv) { + return HDWallet.fromBuffer(new Buffer(hex, 'hex')) +} + HDWallet.prototype.getIdentifier = function() { return crypto.hash160(this.pub.toBuffer()) } @@ -182,6 +186,7 @@ HDWallet.prototype.toBuffer = function(priv) { return buffer } + HDWallet.prototype.toHex = function(priv) { return this.toBuffer(priv).toString('hex') } diff --git a/test/hdwallet.js b/test/hdwallet.js index 5e22865..4ceebb1 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -118,10 +118,10 @@ describe('HDWallet', function() { }) }) - describe('fromBuffer', function() { + describe('fromBuffer/fromHex', function() { fixtures.valid.forEach(function(f) { it('imports ' + f.master.hex + ' (public) correctly', function() { - var hd = HDWallet.fromBuffer(new Buffer(f.master.hex, 'hex')) + var hd = HDWallet.fromHex(f.master.hex) assert.equal(hd.toBuffer().toString('hex'), f.master.hex) }) @@ -129,7 +129,7 @@ describe('HDWallet', function() { fixtures.valid.forEach(function(f) { it('imports ' + f.master.hexPriv + ' (private) correctly', function() { - var hd = HDWallet.fromBuffer(new Buffer(f.master.hexPriv, 'hex')) + var hd = HDWallet.fromHex(f.master.hexPriv) assert.equal(hd.toBuffer(true).toString('hex'), f.master.hexPriv) }) @@ -138,12 +138,38 @@ describe('HDWallet', function() { fixtures.invalid.fromBuffer.forEach(function(f) { it('throws on ' + f.string, function() { assert.throws(function() { - HDWallet.fromBuffer(new Buffer(f.hex, 'hex')) + HDWallet.fromHex(f.hex) }, new RegExp(f.exception)) }) }) }) + describe('toBuffer/toHex', function() { + fixtures.valid.forEach(function(f) { + it('exports ' + f.master.hex + ' (public) correctly', function() { + var hd = HDWallet.fromSeedHex(f.master.seed) + + assert.equal(hd.toHex(), f.master.hex) + }) + }) + + fixtures.valid.forEach(function(f) { + it('exports ' + f.master.hexPriv + ' (private) correctly', function() { + var hd = HDWallet.fromSeedHex(f.master.seed) + + assert.equal(hd.toHex(true), f.master.hexPriv) + }) + }) + + it('fails when there is no private key', function() { + var hd = HDWallet.fromHex(fixtures.valid[0].master.hex) + + assert.throws(function() { + hd.toHex(true) + }, /Missing private key/) + }) + }) + describe('getIdentifier', function() { var f = fixtures.valid[0] From 938c43649d476bf8135ce9678c9a837ef5da93ab Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 1 Jun 2014 16:48:41 +1000 Subject: [PATCH 14/28] HDWallet: remove unnecessary Base58 context on fromBuffer tests --- test/fixtures/hdwallet.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdwallet.json index 6f469f3..443a41d 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdwallet.json @@ -192,17 +192,17 @@ { "exception": "Invalid checksum", "string": "xprvQQQQQQQQQQQQQQQQCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" - }, + } + ], + "fromBuffer": [ { "exception": "Invalid buffer length", - "string": "SQ8nQ2jWarXqLo9oHGKKP6iQDsQbPRftq7rjtYY3hqJRPQRgrmeunFnDKbH7B15yGPLZBrhhkKXx3pwD6LcBooJRGq6x7matAXpMsgn" + "hex": "0488b21e0000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee4" }, { "exception": "Invalid buffer length", - "string": "37hdAfw3aMiWcBGPP2ywmY5jizTeSSP5GXayKY3RxkEZ7f3SBnRE1pN6eY3VzGkgx6vbdNtuKfrHgEaYvW9KkFZCycaPvWiA9TtfmeVB592Sf9RfSzQzXo72" - } - ], - "fromBuffer": [ + "hex": "0488b21e0000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee35500000000" + }, { "exception": "Invalid parent fingerprint", "hex": "0488b21e00ffffffff000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" From 91b8833e520c625da9a15a19ccb889cc84dd40cb Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 15:58:42 +1000 Subject: [PATCH 15/28] HDWallet: rename local priv to isPrivate --- src/hdwallet.js | 18 +++++++++--------- src/networks.js | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index 014d0a0..a09eb9e 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -19,7 +19,7 @@ function findBIP32ParamsByVersion(version) { if (version != network.bip32[type]) continue return { - isPrivate: (type === 'priv'), + isPrivate: (type === 'private'), network: network } } @@ -132,7 +132,7 @@ HDWallet.fromBuffer = function(buffer) { return hd } -HDWallet.fromHex = function(hex, priv) { +HDWallet.fromHex = function(hex, isPrivate) { return HDWallet.fromBuffer(new Buffer(hex, 'hex')) } @@ -148,9 +148,9 @@ HDWallet.prototype.getAddress = function() { return this.pub.getAddress(this.network.pubKeyHash) } -HDWallet.prototype.toBuffer = function(priv) { +HDWallet.prototype.toBuffer = function(isPrivate) { // Version - var version = this.network.bip32[priv ? 'priv' : 'pub'] + var version = isPrivate ? this.network.bip32.private : this.network.bip32.public var buffer = new Buffer(HDWallet.LENGTH) // 4 bytes: version bytes @@ -172,7 +172,7 @@ HDWallet.prototype.toBuffer = function(priv) { this.chainCode.copy(buffer, 13) // 33 bytes: the public key or private key data - if (priv) { + if (isPrivate) { assert(this.priv, 'Missing private key') // 0x00 + k for private keys @@ -187,12 +187,12 @@ HDWallet.prototype.toBuffer = function(priv) { return buffer } -HDWallet.prototype.toHex = function(priv) { - return this.toBuffer(priv).toString('hex') +HDWallet.prototype.toHex = function(isPrivate) { + return this.toBuffer(isPrivate).toString('hex') } -HDWallet.prototype.toBase58 = function(priv) { - var buffer = this.toBuffer(priv) +HDWallet.prototype.toBase58 = function(isPrivate) { + var buffer = this.toBuffer(isPrivate) var checksum = crypto.hash256(buffer).slice(0, 4) return base58.encode(Buffer.concat([ diff --git a/src/networks.js b/src/networks.js index 5a9ed3e..da71d23 100644 --- a/src/networks.js +++ b/src/networks.js @@ -4,8 +4,8 @@ module.exports = { bitcoin: { magicPrefix: '\x18Bitcoin Signed Message:\n', bip32: { - pub: 0x0488b21e, - priv: 0x0488ade4 + public: 0x0488b21e, + private: 0x0488ade4 }, pubKeyHash: 0x00, scriptHash: 0x05, @@ -14,8 +14,8 @@ module.exports = { dogecoin: { magicPrefix: '\x19Dogecoin Signed Message:\n', bip32: { - pub: 0x02facafd, - priv: 0x02fac398 + public: 0x02facafd, + private: 0x02fac398 }, pubKeyHash: 0x1e, scriptHash: 0x16, @@ -24,8 +24,8 @@ module.exports = { litecoin: { magicPrefix: '\x19Litecoin Signed Message:\n', bip32: { - pub: 0x019da462, - priv: 0x019d9cfe + public: 0x019da462, + private: 0x019d9cfe }, pubKeyHash: 0x30, scriptHash: 0x05, @@ -34,8 +34,8 @@ module.exports = { testnet: { magicPrefix: '\x18Bitcoin Signed Message:\n', bip32: { - pub: 0x043587cf, - priv: 0x04358394 + public: 0x043587cf, + private: 0x04358394 }, pubKeyHash: 0x6f, scriptHash: 0xc4, From 99bb69bf209a82698fc70058b45f741463875a31 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 16:12:36 +1000 Subject: [PATCH 16/28] HDWallet: moves hardened info to test data more explicitly --- test/fixtures/hdwallet.json | 12 ++++++++---- test/hdwallet.js | 8 ++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdwallet.json index 443a41d..86040a3 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdwallet.json @@ -18,7 +18,8 @@ "children": [ { "description": "m/0'", - "mPriv": 0, + "m": 0, + "hardened": true, "wif": "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT", "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", @@ -48,7 +49,8 @@ }, { "description": "m/0'/1/2'", - "mPriv": 2, + "m": 2, + "hardened": true, "wif": "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU", "pubKey": "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", "chainCode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", @@ -126,7 +128,8 @@ }, { "description": "m/0/2147483647", - "mPriv": 2147483647, + "m": 2147483647, + "hardened": true, "wif": "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr", "pubKey": "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", "chainCode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9", @@ -156,7 +159,8 @@ }, { "description": "m/0/2147483647'/1/2147483646", - "mPriv": 2147483646, + "m": 2147483646, + "hardened": true, "wif": "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF", "pubKey": "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", "chainCode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", diff --git a/test/hdwallet.js b/test/hdwallet.js index 4ceebb1..e2332b0 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -214,8 +214,8 @@ describe('HDWallet', function() { assert.equal(hd.chainCode.toString('hex'), v.chainCode) assert.equal(hd.depth, depth || 0) - if (v.mPriv != undefined) { - assert.equal(hd.index, v.mPriv + HDWallet.HIGHEST_BIT) + if (v.hardened) { + assert.equal(hd.index, v.m + HDWallet.HIGHEST_BIT) } else { assert.equal(hd.index, v.m) } @@ -226,8 +226,8 @@ describe('HDWallet', function() { f.children.forEach(function(c, i) { it(c.description + ' from ' + f.master.fingerprint, function() { - if (c.mPriv != undefined) { - hd = hd.derivePrivate(c.mPriv) + if (c.hardened) { + hd = hd.derivePrivate(c.m) } else { hd = hd.derive(c.m) From 29fbbaa58db3d561dda2cb1a0431c3073e3d1f1d Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 16:13:20 +1000 Subject: [PATCH 17/28] HDWallet: adds Public -> public tests --- test/hdwallet.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/hdwallet.js b/test/hdwallet.js index e2332b0..aa78c5d 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -237,5 +237,26 @@ describe('HDWallet', function() { }) }) }) + + it('works for public -> public', function() { + var f = fixtures.valid[1] + var c = f.children[0] + + var parent = HDWallet.fromBase58(f.master.base58) + var child = parent.derive(c.m) + + assert.equal(child.toBase58(), c.base58) + }) + + it('throws on public -> public (hardened)', function() { + var f = fixtures.valid[1] + var c = f.children[0] + + var parent = HDWallet.fromBase58(f.master.base58) + + assert.throws(function() { + parent.derivePrivate(c.m) + }, /Could not derive hardened child key/) + }) }) }) From dca284a131589f4225972993a57b69db58e5b77b Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 16:14:15 +1000 Subject: [PATCH 18/28] HDWallet: rename derivePrivate to deriveHardened --- src/hdwallet.js | 2 +- src/wallet.js | 4 ++-- test/hdwallet.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index a09eb9e..c836e73 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -273,7 +273,7 @@ HDWallet.prototype.derive = function(index) { return hd } -HDWallet.prototype.derivePrivate = function(index) { +HDWallet.prototype.deriveHardened = function(index) { // Only derives hardened private keys by default return this.derive(index + HDWallet.HIGHEST_BIT) } diff --git a/src/wallet.js b/src/wallet.js index 1255228..e34e311 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -31,9 +31,9 @@ function Wallet(seed, network) { seed = seed || new Buffer(rng(32)) masterkey = HDNode.fromSeedBuffer(seed, network) - // HD first-level child derivation method should be private + // HD first-level child derivation method should be hardened // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 - accountZero = masterkey.derivePrivate(0) + accountZero = masterkey.deriveHardened(0) externalAccount = accountZero.derive(0) internalAccount = accountZero.derive(1) diff --git a/test/hdwallet.js b/test/hdwallet.js index aa78c5d..f4ad018 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -227,7 +227,7 @@ describe('HDWallet', function() { f.children.forEach(function(c, i) { it(c.description + ' from ' + f.master.fingerprint, function() { if (c.hardened) { - hd = hd.derivePrivate(c.m) + hd = hd.deriveHardened(c.m) } else { hd = hd.derive(c.m) @@ -255,7 +255,7 @@ describe('HDWallet', function() { var parent = HDWallet.fromBase58(f.master.base58) assert.throws(function() { - parent.derivePrivate(c.m) + parent.deriveHardened(c.m) }, /Could not derive hardened child key/) }) }) From 48503f252cfeb739a8ed68b0552e21b459587259 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 16:19:44 +1000 Subject: [PATCH 19/28] HDWallet: move toBase58 --- src/hdwallet.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index c836e73..f259d63 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -148,6 +148,16 @@ HDWallet.prototype.getAddress = function() { return this.pub.getAddress(this.network.pubKeyHash) } +HDWallet.prototype.toBase58 = function(isPrivate) { + var buffer = this.toBuffer(isPrivate) + var checksum = crypto.hash256(buffer).slice(0, 4) + + return base58.encode(Buffer.concat([ + buffer, + checksum + ])) +} + HDWallet.prototype.toBuffer = function(isPrivate) { // Version var version = isPrivate ? this.network.bip32.private : this.network.bip32.public @@ -191,16 +201,6 @@ HDWallet.prototype.toHex = function(isPrivate) { return this.toBuffer(isPrivate).toString('hex') } -HDWallet.prototype.toBase58 = function(isPrivate) { - var buffer = this.toBuffer(isPrivate) - var checksum = crypto.hash256(buffer).slice(0, 4) - - return base58.encode(Buffer.concat([ - buffer, - checksum - ])) -} - // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions HDWallet.prototype.derive = function(index) { var isHardened = index >= HDWallet.HIGHEST_BIT From c340f5cf83ac782457a13d9b81ec2ca5f3955639 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 16:20:37 +1000 Subject: [PATCH 20/28] HDWallet: now export all information by default --- src/hdwallet.js | 2 ++ test/hdwallet.js | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index f259d63..a5036a3 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -159,6 +159,8 @@ HDWallet.prototype.toBase58 = function(isPrivate) { } HDWallet.prototype.toBuffer = function(isPrivate) { + if (isPrivate == undefined) isPrivate = !!this.priv + // Version var version = isPrivate ? this.network.bip32.private : this.network.bip32.public var buffer = new Buffer(HDWallet.LENGTH) diff --git a/test/hdwallet.js b/test/hdwallet.js index f4ad018..035cde1 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -71,7 +71,7 @@ describe('HDWallet', function() { it('exports ' + f.master.base58 + ' (public) correctly', function() { var hd = HDWallet.fromSeedHex(f.master.seed) - assert.equal(hd.toBase58(), f.master.base58) + assert.equal(hd.toBase58(false), f.master.base58) }) }) @@ -105,7 +105,7 @@ describe('HDWallet', function() { it('imports ' + f.master.base58Priv + ' (private) correctly', function() { var hd = HDWallet.fromBase58(f.master.base58Priv) - assert.equal(hd.toBase58(true), f.master.base58Priv) + assert.equal(hd.toBase58(), f.master.base58Priv) }) }) @@ -131,7 +131,7 @@ describe('HDWallet', function() { it('imports ' + f.master.hexPriv + ' (private) correctly', function() { var hd = HDWallet.fromHex(f.master.hexPriv) - assert.equal(hd.toBuffer(true).toString('hex'), f.master.hexPriv) + assert.equal(hd.toBuffer().toString('hex'), f.master.hexPriv) }) }) @@ -149,7 +149,7 @@ describe('HDWallet', function() { it('exports ' + f.master.hex + ' (public) correctly', function() { var hd = HDWallet.fromSeedHex(f.master.seed) - assert.equal(hd.toHex(), f.master.hex) + assert.equal(hd.toHex(false), f.master.hex) }) }) From 64e307b13fe88e7adfa04f2b194f0e13df1f74b4 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 17:08:42 +1000 Subject: [PATCH 21/28] HDWallet: rename to HDNode --- src/{hdwallet.js => hdnode.js} | 66 ++++++++++---------- src/index.js | 2 +- src/wallet.js | 2 +- test/fixtures/{hdwallet.json => hdnode.json} | 0 test/{hdwallet.js => hdnode.js} | 66 ++++++++++---------- test/wallet.js | 4 +- 6 files changed, 70 insertions(+), 70 deletions(-) rename src/{hdwallet.js => hdnode.js} (79%) rename test/fixtures/{hdwallet.json => hdnode.json} (100%) rename test/{hdwallet.js => hdnode.js} (78%) diff --git a/src/hdwallet.js b/src/hdnode.js similarity index 79% rename from src/hdwallet.js rename to src/hdnode.js index a5036a3..b415a0c 100644 --- a/src/hdwallet.js +++ b/src/hdnode.js @@ -28,7 +28,7 @@ function findBIP32ParamsByVersion(version) { assert(false, 'Could not find version ' + version.toString(16)) } -function HDWallet(K, chainCode, network) { +function HDNode(K, chainCode, network) { network = network || networks.bitcoin assert(Buffer.isBuffer(chainCode), 'Expected Buffer, got ' + chainCode) @@ -47,27 +47,27 @@ function HDWallet(K, chainCode, network) { } } -HDWallet.MASTER_SECRET = new Buffer('Bitcoin seed') -HDWallet.HIGHEST_BIT = 0x80000000 -HDWallet.LENGTH = 78 +HDNode.MASTER_SECRET = new Buffer('Bitcoin seed') +HDNode.HIGHEST_BIT = 0x80000000 +HDNode.LENGTH = 78 -HDWallet.fromSeedBuffer = function(seed, network) { - var I = crypto.HmacSHA512(seed, HDWallet.MASTER_SECRET) +HDNode.fromSeedBuffer = function(seed, network) { + var I = crypto.HmacSHA512(seed, HDNode.MASTER_SECRET) var IL = I.slice(0, 32) var IR = I.slice(32) // In case IL is 0 or >= n, the master key is invalid - // This is handled by `new ECKey` in the HDWallet constructor + // This is handled by `new ECKey` in the HDNode constructor var pIL = BigInteger.fromBuffer(IL) - return new HDWallet(pIL, IR, network) + return new HDNode(pIL, IR, network) } -HDWallet.fromSeedHex = function(hex, network) { - return HDWallet.fromSeedBuffer(new Buffer(hex, 'hex'), network) +HDNode.fromSeedHex = function(hex, network) { + return HDNode.fromSeedBuffer(new Buffer(hex, 'hex'), network) } -HDWallet.fromBase58 = function(string) { +HDNode.fromBase58 = function(string) { var buffer = base58.decode(string) var payload = buffer.slice(0, -4) @@ -76,11 +76,11 @@ HDWallet.fromBase58 = function(string) { var newChecksum = crypto.hash256(payload).slice(0, 4) assert.deepEqual(newChecksum, checksum, 'Invalid checksum') - return HDWallet.fromBuffer(payload) + return HDNode.fromBuffer(payload) } -HDWallet.fromBuffer = function(buffer) { - assert.strictEqual(buffer.length, HDWallet.LENGTH, 'Invalid buffer length') +HDNode.fromBuffer = function(buffer) { + assert.strictEqual(buffer.length, HDNode.LENGTH, 'Invalid buffer length') // 4 byte: version bytes var version = buffer.readUInt32BE(0) @@ -113,7 +113,7 @@ HDWallet.fromBuffer = function(buffer) { data = data.slice(1) var D = BigInteger.fromBuffer(data) - hd = new HDWallet(D, chainCode, params.network) + hd = new HDNode(D, chainCode, params.network) } else { var decode = ECPointFp.decodeFrom(ecparams.getCurve(), data) @@ -122,7 +122,7 @@ HDWallet.fromBuffer = function(buffer) { // When importing a serialized extended public key, implementations must verify whether the X coordinate in the public key data corresponds to a point on the curve. If not, the extended public key is invalid. decode.Q.validate() - hd = new HDWallet(decode.Q, chainCode, params.network) + hd = new HDNode(decode.Q, chainCode, params.network) } hd.depth = depth @@ -132,23 +132,23 @@ HDWallet.fromBuffer = function(buffer) { return hd } -HDWallet.fromHex = function(hex, isPrivate) { - return HDWallet.fromBuffer(new Buffer(hex, 'hex')) +HDNode.fromHex = function(hex, isPrivate) { + return HDNode.fromBuffer(new Buffer(hex, 'hex')) } -HDWallet.prototype.getIdentifier = function() { +HDNode.prototype.getIdentifier = function() { return crypto.hash160(this.pub.toBuffer()) } -HDWallet.prototype.getFingerprint = function() { +HDNode.prototype.getFingerprint = function() { return this.getIdentifier().slice(0, 4) } -HDWallet.prototype.getAddress = function() { +HDNode.prototype.getAddress = function() { return this.pub.getAddress(this.network.pubKeyHash) } -HDWallet.prototype.toBase58 = function(isPrivate) { +HDNode.prototype.toBase58 = function(isPrivate) { var buffer = this.toBuffer(isPrivate) var checksum = crypto.hash256(buffer).slice(0, 4) @@ -158,12 +158,12 @@ HDWallet.prototype.toBase58 = function(isPrivate) { ])) } -HDWallet.prototype.toBuffer = function(isPrivate) { +HDNode.prototype.toBuffer = function(isPrivate) { if (isPrivate == undefined) isPrivate = !!this.priv // Version var version = isPrivate ? this.network.bip32.private : this.network.bip32.public - var buffer = new Buffer(HDWallet.LENGTH) + var buffer = new Buffer(HDNode.LENGTH) // 4 bytes: version bytes buffer.writeUInt32BE(version, 0) @@ -199,13 +199,13 @@ HDWallet.prototype.toBuffer = function(isPrivate) { return buffer } -HDWallet.prototype.toHex = function(isPrivate) { +HDNode.prototype.toHex = function(isPrivate) { return this.toBuffer(isPrivate).toString('hex') } // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions -HDWallet.prototype.derive = function(index) { - var isHardened = index >= HDWallet.HIGHEST_BIT +HDNode.prototype.derive = function(index) { + var isHardened = index >= HDNode.HIGHEST_BIT var indexBuffer = new Buffer(4) indexBuffer.writeUInt32BE(index, 0) @@ -252,7 +252,7 @@ HDWallet.prototype.derive = function(index) { return this.derive(index + 1) } - hd = new HDWallet(ki, IR, this.network) + hd = new HDNode(ki, IR, this.network) // Public parent key -> public child key } else { @@ -265,7 +265,7 @@ HDWallet.prototype.derive = function(index) { return this.derive(index + 1) } - hd = new HDWallet(Ki, IR, this.network) + hd = new HDNode(Ki, IR, this.network) } hd.depth = this.depth + 1 @@ -275,11 +275,11 @@ HDWallet.prototype.derive = function(index) { return hd } -HDWallet.prototype.deriveHardened = function(index) { +HDNode.prototype.deriveHardened = function(index) { // Only derives hardened private keys by default - return this.derive(index + HDWallet.HIGHEST_BIT) + return this.derive(index + HDNode.HIGHEST_BIT) } -HDWallet.prototype.toString = HDWallet.prototype.toBase58 +HDNode.prototype.toString = HDNode.prototype.toBase58 -module.exports = HDWallet +module.exports = HDNode diff --git a/src/index.js b/src/index.js index 5e2f025..fb4c80b 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,7 @@ module.exports = { ECPubKey: require('./ecpubkey'), Message: require('./message'), opcodes: require('./opcodes'), - HDWallet: require('./hdwallet'), + HDNode: require('./hdnode'), Script: require('./script'), sec: require('./sec'), Transaction: T.Transaction, diff --git a/src/wallet.js b/src/wallet.js index e34e311..41e97ad 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -3,7 +3,7 @@ var networks = require('./networks') var rng = require('secure-random') var Address = require('./address') -var HDNode = require('./hdwallet') +var HDNode = require('./hdnode') var Transaction = require('./transaction').Transaction function Wallet(seed, network) { diff --git a/test/fixtures/hdwallet.json b/test/fixtures/hdnode.json similarity index 100% rename from test/fixtures/hdwallet.json rename to test/fixtures/hdnode.json diff --git a/test/hdwallet.js b/test/hdnode.js similarity index 78% rename from test/hdwallet.js rename to test/hdnode.js index 035cde1..59515fd 100644 --- a/test/hdwallet.js +++ b/test/hdnode.js @@ -4,11 +4,11 @@ var sec = require('../src/sec') var ecparams = sec("secp256k1") var BigInteger = require('bigi') -var HDWallet = require('../src/hdwallet') +var HDNode = require('../src/hdnode') -var fixtures = require('./fixtures/hdwallet.json') +var fixtures = require('./fixtures/hdnode.json') -describe('HDWallet', function() { +describe('HDNode', function() { describe('Constructor', function() { var D = BigInteger.ONE var Q = ecparams.getG().multiply(D) @@ -16,41 +16,41 @@ describe('HDWallet', function() { chainCode.fill(1) it('calculates the publicKey from a BigInteger', function() { - var hd = new HDWallet(D, chainCode) + var hd = new HDNode(D, chainCode) assert(hd.pub.Q.equals(Q)) }) it('only uses compressed points', function() { - var hd = new HDWallet(Q, chainCode) - var hdP = new HDWallet(D, chainCode) + var hd = new HDNode(Q, chainCode) + var hdP = new HDNode(D, chainCode) assert.strictEqual(hd.pub.compressed, true) assert.strictEqual(hdP.pub.compressed, true) }) it('has a default depth/index of 0', function() { - var hd = new HDWallet(Q, chainCode) + var hd = new HDNode(Q, chainCode) assert.strictEqual(hd.depth, 0) assert.strictEqual(hd.index, 0) }) it('defaults to the bitcoin network', function() { - var hd = new HDWallet(Q, chainCode) + var hd = new HDNode(Q, chainCode) assert.equal(hd.network, networks.bitcoin) }) it('supports alternative networks', function() { - var hd = new HDWallet(Q, chainCode, networks.testnet) + var hd = new HDNode(Q, chainCode, networks.testnet) assert.equal(hd.network, networks.testnet) }) it('throws an exception when an unknown network is given', function() { assert.throws(function() { - new HDWallet(D, chainCode, {}) + new HDNode(D, chainCode, {}) }, /Unknown BIP32 constants for network/) }) }) @@ -58,7 +58,7 @@ describe('HDWallet', function() { describe('fromSeed*', function() { fixtures.valid.forEach(function(f) { it('calculates privKey and chainCode for ' + f.master.fingerprint, function() { - var hd = HDWallet.fromSeedHex(f.master.seed) + var hd = HDNode.fromSeedHex(f.master.seed) assert.equal(hd.priv.toWIF(), f.master.wif) assert.equal(hd.chainCode.toString('hex'), f.master.chainCode) @@ -69,7 +69,7 @@ describe('HDWallet', function() { describe('toBase58', function() { fixtures.valid.forEach(function(f) { it('exports ' + f.master.base58 + ' (public) correctly', function() { - var hd = HDWallet.fromSeedHex(f.master.seed) + var hd = HDNode.fromSeedHex(f.master.seed) assert.equal(hd.toBase58(false), f.master.base58) }) @@ -77,14 +77,14 @@ describe('HDWallet', function() { fixtures.valid.forEach(function(f) { it('exports ' + f.master.base58Priv + ' (private) correctly', function() { - var hd = HDWallet.fromSeedHex(f.master.seed) + var hd = HDNode.fromSeedHex(f.master.seed) assert.equal(hd.toBase58(true), f.master.base58Priv) }) }) it('fails when there is no private key', function() { - var hd = HDWallet.fromBase58(fixtures.valid[0].master.base58) + var hd = HDNode.fromBase58(fixtures.valid[0].master.base58) assert.throws(function() { hd.toBase58(true) @@ -95,7 +95,7 @@ describe('HDWallet', function() { describe('fromBase58', function() { fixtures.valid.forEach(function(f) { it('imports ' + f.master.base58 + ' (public) correctly', function() { - var hd = HDWallet.fromBase58(f.master.base58) + var hd = HDNode.fromBase58(f.master.base58) assert.equal(hd.toBase58(), f.master.base58) }) @@ -103,7 +103,7 @@ describe('HDWallet', function() { fixtures.valid.forEach(function(f) { it('imports ' + f.master.base58Priv + ' (private) correctly', function() { - var hd = HDWallet.fromBase58(f.master.base58Priv) + var hd = HDNode.fromBase58(f.master.base58Priv) assert.equal(hd.toBase58(), f.master.base58Priv) }) @@ -112,7 +112,7 @@ describe('HDWallet', function() { fixtures.invalid.fromBase58.forEach(function(f) { it('throws on ' + f.string, function() { assert.throws(function() { - HDWallet.fromBase58(f.string) + HDNode.fromBase58(f.string) }, new RegExp(f.exception)) }) }) @@ -121,7 +121,7 @@ describe('HDWallet', function() { describe('fromBuffer/fromHex', function() { fixtures.valid.forEach(function(f) { it('imports ' + f.master.hex + ' (public) correctly', function() { - var hd = HDWallet.fromHex(f.master.hex) + var hd = HDNode.fromHex(f.master.hex) assert.equal(hd.toBuffer().toString('hex'), f.master.hex) }) @@ -129,7 +129,7 @@ describe('HDWallet', function() { fixtures.valid.forEach(function(f) { it('imports ' + f.master.hexPriv + ' (private) correctly', function() { - var hd = HDWallet.fromHex(f.master.hexPriv) + var hd = HDNode.fromHex(f.master.hexPriv) assert.equal(hd.toBuffer().toString('hex'), f.master.hexPriv) }) @@ -138,7 +138,7 @@ describe('HDWallet', function() { fixtures.invalid.fromBuffer.forEach(function(f) { it('throws on ' + f.string, function() { assert.throws(function() { - HDWallet.fromHex(f.hex) + HDNode.fromHex(f.hex) }, new RegExp(f.exception)) }) }) @@ -147,7 +147,7 @@ describe('HDWallet', function() { describe('toBuffer/toHex', function() { fixtures.valid.forEach(function(f) { it('exports ' + f.master.hex + ' (public) correctly', function() { - var hd = HDWallet.fromSeedHex(f.master.seed) + var hd = HDNode.fromSeedHex(f.master.seed) assert.equal(hd.toHex(false), f.master.hex) }) @@ -155,14 +155,14 @@ describe('HDWallet', function() { fixtures.valid.forEach(function(f) { it('exports ' + f.master.hexPriv + ' (private) correctly', function() { - var hd = HDWallet.fromSeedHex(f.master.seed) + var hd = HDNode.fromSeedHex(f.master.seed) assert.equal(hd.toHex(true), f.master.hexPriv) }) }) it('fails when there is no private key', function() { - var hd = HDWallet.fromHex(fixtures.valid[0].master.hex) + var hd = HDNode.fromHex(fixtures.valid[0].master.hex) assert.throws(function() { hd.toHex(true) @@ -174,7 +174,7 @@ describe('HDWallet', function() { var f = fixtures.valid[0] it('returns the identifier for ' + f.master.fingerprint, function() { - var hd = HDWallet.fromBase58(f.master.base58) + var hd = HDNode.fromBase58(f.master.base58) assert.equal(hd.getIdentifier().toString('hex'), f.master.identifier) }) @@ -184,7 +184,7 @@ describe('HDWallet', function() { var f = fixtures.valid[0] it('returns the fingerprint for ' + f.master.fingerprint, function() { - var hd = HDWallet.fromBase58(f.master.base58) + var hd = HDNode.fromBase58(f.master.base58) assert.equal(hd.getFingerprint().toString('hex'), f.master.fingerprint) }) @@ -194,13 +194,13 @@ describe('HDWallet', function() { var f = fixtures.valid[0] it('returns the Address (pubKeyHash) for ' + f.master.fingerprint, function() { - var hd = HDWallet.fromBase58(f.master.base58) + var hd = HDNode.fromBase58(f.master.base58) assert.equal(hd.getAddress().toString(), f.master.address) }) it('supports alternative networks', function() { - var hd = HDWallet.fromBase58(f.master.base58) + var hd = HDNode.fromBase58(f.master.base58) hd.network = networks.testnet assert.equal(hd.getAddress().version, networks.testnet.pubKeyHash) @@ -215,14 +215,14 @@ describe('HDWallet', function() { assert.equal(hd.depth, depth || 0) if (v.hardened) { - assert.equal(hd.index, v.m + HDWallet.HIGHEST_BIT) + assert.equal(hd.index, v.m + HDNode.HIGHEST_BIT) } else { assert.equal(hd.index, v.m) } } fixtures.valid.forEach(function(f, j) { - var hd = HDWallet.fromSeedHex(f.master.seed) + var hd = HDNode.fromSeedHex(f.master.seed) f.children.forEach(function(c, i) { it(c.description + ' from ' + f.master.fingerprint, function() { @@ -242,8 +242,8 @@ describe('HDWallet', function() { var f = fixtures.valid[1] var c = f.children[0] - var parent = HDWallet.fromBase58(f.master.base58) - var child = parent.derive(c.m) + var parentNode = HDNode.fromBase58(f.master.base58) + var child = parentNode.derive(c.m) assert.equal(child.toBase58(), c.base58) }) @@ -252,10 +252,10 @@ describe('HDWallet', function() { var f = fixtures.valid[1] var c = f.children[0] - var parent = HDWallet.fromBase58(f.master.base58) + var parentNode = HDNode.fromBase58(f.master.base58) assert.throws(function() { - parent.deriveHardened(c.m) + parentNode.deriveHardened(c.m) }, /Could not derive hardened child key/) }) }) diff --git a/test/wallet.js b/test/wallet.js index c3b459b..4c9d803 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -4,7 +4,7 @@ var networks = require('../src/networks') var sinon = require('sinon') var Address = require('../src/address') -var HDWallet = require('../src/hdwallet') +var HDNode = require('../src/hdnode') var Script = require('../src/script') var Transaction = require('../src/transaction').Transaction var Wallet = require('../src/wallet') @@ -31,7 +31,7 @@ describe('Wallet', function() { it("generates m/0' as the main account", function() { var mainAccount = wallet.getAccountZero() - assert.equal(mainAccount.index, 0 + HDWallet.HIGHEST_BIT) + assert.equal(mainAccount.index, 0 + HDNode.HIGHEST_BIT) assert.equal(mainAccount.depth, 1) }) From dca193be4eb7f0a66043bfbf9355c6dc9185856e Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 17:22:13 +1000 Subject: [PATCH 22/28] HDNode: adds neutered test examples --- test/hdnode.js | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/test/hdnode.js b/test/hdnode.js index 59515fd..ecd4328 100644 --- a/test/hdnode.js +++ b/test/hdnode.js @@ -193,7 +193,7 @@ describe('HDNode', function() { describe('getAddress', function() { var f = fixtures.valid[0] - it('returns the Address (pubKeyHash) for ' + f.master.fingerprint, function() { + it('returns the Address (pubHash) for ' + f.master.fingerprint, function() { var hd = HDNode.fromBase58(f.master.base58) assert.equal(hd.getAddress().toString(), f.master.address) @@ -224,6 +224,7 @@ describe('HDNode', function() { fixtures.valid.forEach(function(f, j) { var hd = HDNode.fromSeedHex(f.master.seed) + // FIXME: test data is only testing Private -> private for now f.children.forEach(function(c, i) { it(c.description + ' from ' + f.master.fingerprint, function() { if (c.hardened) { @@ -238,7 +239,31 @@ describe('HDNode', function() { }) }) - it('works for public -> public', function() { + it('works for Private -> public (neutered)', function() { + var f = fixtures.valid[1] + var c = f.children[0] + + var parentNode = HDNode.fromBase58(f.master.base58Priv) + var child = parentNode.derive(c.m) + + // FIXME: N(CKDpriv((kpar, cpar), i)), could be done better... + var childNeutered = HDNode.fromBase58(child.toBase58(false)) // neuter + assert.equal(childNeutered.toBase58(), c.base58) + }) + + it('works for Private -> public (neutered, hardened)', function() { + var f = fixtures.valid[0] + var c = f.children[0] + + var parentNode = HDNode.fromBase58(f.master.base58Priv) + var child = parentNode.deriveHardened(c.m) + + // FIXME: N(CKDpriv((kpar, cpar), i)), could be done better... + var childNeutered = HDNode.fromBase58(child.toBase58(false)) // neuter + assert.equal(childNeutered.toBase58(), c.base58) + }) + + it('works for Public -> public', function() { var f = fixtures.valid[1] var c = f.children[0] @@ -248,8 +273,8 @@ describe('HDNode', function() { assert.equal(child.toBase58(), c.base58) }) - it('throws on public -> public (hardened)', function() { - var f = fixtures.valid[1] + it('throws on Public -> public (hardened)', function() { + var f = fixtures.valid[0] var c = f.children[0] var parentNode = HDNode.fromBase58(f.master.base58) From 6a73bc02f51311c27a8410fe433d623be0108839 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 17:30:05 +1000 Subject: [PATCH 23/28] HDNode: rename priv/pub to privKey/pubKey --- src/hdnode.js | 30 +++++++++++++++--------------- src/wallet.js | 4 ++-- test/hdnode.js | 12 ++++++------ test/wallet.js | 12 ++++++------ 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/hdnode.js b/src/hdnode.js index b415a0c..14e3158 100644 --- a/src/hdnode.js +++ b/src/hdnode.js @@ -40,10 +40,10 @@ function HDNode(K, chainCode, network) { this.network = network if (K instanceof BigInteger) { - this.priv = new ECKey(K, true) - this.pub = this.priv.pub + this.privKey = new ECKey(K, true) + this.pubKey = this.privKey.pub } else { - this.pub = new ECPubKey(K, true) + this.pubKey = new ECPubKey(K, true) } } @@ -137,7 +137,7 @@ HDNode.fromHex = function(hex, isPrivate) { } HDNode.prototype.getIdentifier = function() { - return crypto.hash160(this.pub.toBuffer()) + return crypto.hash160(this.pubKey.toBuffer()) } HDNode.prototype.getFingerprint = function() { @@ -145,7 +145,7 @@ HDNode.prototype.getFingerprint = function() { } HDNode.prototype.getAddress = function() { - return this.pub.getAddress(this.network.pubKeyHash) + return this.pubKey.getAddress(this.network.pubKeyHash) } HDNode.prototype.toBase58 = function(isPrivate) { @@ -159,7 +159,7 @@ HDNode.prototype.toBase58 = function(isPrivate) { } HDNode.prototype.toBuffer = function(isPrivate) { - if (isPrivate == undefined) isPrivate = !!this.priv + if (isPrivate == undefined) isPrivate = !!this.privKey // Version var version = isPrivate ? this.network.bip32.private : this.network.bip32.public @@ -185,15 +185,15 @@ HDNode.prototype.toBuffer = function(isPrivate) { // 33 bytes: the public key or private key data if (isPrivate) { - assert(this.priv, 'Missing private key') + assert(this.privKey, 'Missing private key') // 0x00 + k for private keys buffer.writeUInt8(0, 45) - this.priv.D.toBuffer(32).copy(buffer, 46) + this.privKey.D.toBuffer(32).copy(buffer, 46) } else { // X9.62 encoding for public keys - this.pub.toBuffer().copy(buffer, 45) + this.pubKey.toBuffer().copy(buffer, 45) } return buffer @@ -213,11 +213,11 @@ HDNode.prototype.derive = function(index) { // Hardened child if (isHardened) { - assert(this.priv, 'Could not derive hardened child key') + assert(this.privKey, 'Could not derive hardened child key') // data = 0x00 || ser256(kpar) || ser32(index) data = Buffer.concat([ - this.priv.D.toBuffer(33), + this.privKey.D.toBuffer(33), indexBuffer ]) @@ -226,7 +226,7 @@ HDNode.prototype.derive = function(index) { // data = serP(point(kpar)) || ser32(index) // = serP(Kpar) || ser32(index) data = Buffer.concat([ - this.pub.toBuffer(), + this.pubKey.toBuffer(), indexBuffer ]) } @@ -243,9 +243,9 @@ HDNode.prototype.derive = function(index) { } // Private parent key -> private child key - if (this.priv) { + if (this.privKey) { // ki = parse256(IL) + kpar (mod n) - var ki = pIL.add(this.priv.D).mod(ecparams.getN()) + var ki = pIL.add(this.privKey.D).mod(ecparams.getN()) // In case ki == 0, proceed with the next value for i if (ki.signum() === 0) { @@ -258,7 +258,7 @@ HDNode.prototype.derive = function(index) { } else { // Ki = point(parse256(IL)) + Kpar // = G*IL + Kpar - var Ki = ecparams.getG().multiply(pIL).add(this.pub.Q) + var Ki = ecparams.getG().multiply(pIL).add(this.pubKey.Q) // In case Ki is the point at infinity, proceed with the next value for i if (Ki.isInfinity()) { diff --git a/src/wallet.js b/src/wallet.js index 41e97ad..2616272 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -246,11 +246,11 @@ function Wallet(seed, network) { this.getExternalAccount = function() { return externalAccount } this.getPrivateKey = function(index) { - return externalAccount.derive(index).priv + return externalAccount.derive(index).privKey } this.getInternalPrivateKey = function(index) { - return internalAccount.derive(index).priv + return internalAccount.derive(index).privKey } this.getPrivateKeyForAddress = function(address) { diff --git a/test/hdnode.js b/test/hdnode.js index ecd4328..71a87c7 100644 --- a/test/hdnode.js +++ b/test/hdnode.js @@ -18,15 +18,15 @@ describe('HDNode', function() { it('calculates the publicKey from a BigInteger', function() { var hd = new HDNode(D, chainCode) - assert(hd.pub.Q.equals(Q)) + assert(hd.pubKey.Q.equals(Q)) }) it('only uses compressed points', function() { var hd = new HDNode(Q, chainCode) var hdP = new HDNode(D, chainCode) - assert.strictEqual(hd.pub.compressed, true) - assert.strictEqual(hdP.pub.compressed, true) + assert.strictEqual(hd.pubKey.compressed, true) + assert.strictEqual(hdP.pubKey.compressed, true) }) it('has a default depth/index of 0', function() { @@ -60,7 +60,7 @@ describe('HDNode', function() { it('calculates privKey and chainCode for ' + f.master.fingerprint, function() { var hd = HDNode.fromSeedHex(f.master.seed) - assert.equal(hd.priv.toWIF(), f.master.wif) + assert.equal(hd.privKey.toWIF(), f.master.wif) assert.equal(hd.chainCode.toString('hex'), f.master.chainCode) }) }) @@ -209,8 +209,8 @@ describe('HDNode', function() { describe('derive', function() { function verifyVector(hd, v, depth) { - assert.equal(hd.priv.toWIF(), v.wif) - assert.equal(hd.pub.toHex(), v.pubKey) + assert.equal(hd.privKey.toWIF(), v.wif) + assert.equal(hd.pubKey.toHex(), v.pubKey) assert.equal(hd.chainCode.toString('hex'), v.chainCode) assert.equal(hd.depth, depth || 0) diff --git a/test/wallet.js b/test/wallet.js index 4c9d803..955eba1 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -121,8 +121,8 @@ describe('Wallet', function() { it('returns the private key at the given index of external account', function(){ var wallet = new Wallet(seed, networks.testnet) - assertEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).priv) - assertEqual(wallet.getPrivateKey(1), wallet.getExternalAccount().derive(1).priv) + assertEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).privKey) + assertEqual(wallet.getPrivateKey(1), wallet.getExternalAccount().derive(1).privKey) }) }) @@ -130,8 +130,8 @@ describe('Wallet', function() { it('returns the private key at the given index of internal account', function(){ var wallet = new Wallet(seed, networks.testnet) - assertEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).priv) - assertEqual(wallet.getInternalPrivateKey(1), wallet.getInternalAccount().derive(1).priv) + assertEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).privKey) + assertEqual(wallet.getInternalPrivateKey(1), wallet.getInternalAccount().derive(1).privKey) }) }) @@ -144,11 +144,11 @@ describe('Wallet', function() { assertEqual( wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X"), - wallet.getExternalAccount().derive(1).priv + wallet.getExternalAccount().derive(1).privKey ) assertEqual( wallet.getPrivateKeyForAddress("mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"), - wallet.getInternalAccount().derive(0).priv + wallet.getInternalAccount().derive(0).privKey ) }) From 9f798ef2c3001627c598a5ca9cb18ad3be06af7a Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 3 Jun 2014 21:01:16 +1000 Subject: [PATCH 24/28] HDWallet: adds missing hd declaration Only a problem if "use strict" is enforced --- src/hdnode.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hdnode.js b/src/hdnode.js index 14e3158..158df49 100644 --- a/src/hdnode.js +++ b/src/hdnode.js @@ -243,6 +243,7 @@ HDNode.prototype.derive = function(index) { } // Private parent key -> private child key + var hd if (this.privKey) { // ki = parse256(IL) + kpar (mod n) var ki = pIL.add(this.privKey.D).mod(ecparams.getN()) From 15cc03a57f831e3a9a7139a5f36fb16f6658846d Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 4 Jun 2014 13:57:44 +1000 Subject: [PATCH 25/28] HDNode: removed unused variable --- src/hdnode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hdnode.js b/src/hdnode.js index 158df49..005b206 100644 --- a/src/hdnode.js +++ b/src/hdnode.js @@ -132,7 +132,7 @@ HDNode.fromBuffer = function(buffer) { return hd } -HDNode.fromHex = function(hex, isPrivate) { +HDNode.fromHex = function(hex) { return HDNode.fromBuffer(new Buffer(hex, 'hex')) } From 22f1dee65bb9b9400b2a1e62f3d2f5c194d4fce2 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 4 Jun 2014 14:36:06 +1000 Subject: [PATCH 26/28] HDNode: remove unnecessary slice operation --- src/hdnode.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/hdnode.js b/src/hdnode.js index 005b206..4486ec5 100644 --- a/src/hdnode.js +++ b/src/hdnode.js @@ -105,17 +105,16 @@ HDNode.fromBuffer = function(buffer) { // 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for // public keys, 0x00 + k for private keys) - var data = buffer.slice(45, 78) - var hd if (params.isPrivate) { - assert.strictEqual(data.readUInt8(0), 0x00, 'Invalid private key') - data = data.slice(1) + assert.strictEqual(buffer.readUInt8(45), 0x00, 'Invalid private key') + var data = buffer.slice(46, 78) var D = BigInteger.fromBuffer(data) hd = new HDNode(D, chainCode, params.network) - } else { + } else { + var data = buffer.slice(45, 78) var decode = ECPointFp.decodeFrom(ecparams.getCurve(), data) assert.equal(decode.compressed, true, 'Invalid public key') From 56a88b8a70d6ca992a57989aaa57ca47e5492e43 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 4 Jun 2014 14:36:19 +1000 Subject: [PATCH 27/28] HDNode: shorten comment for Q validation --- src/hdnode.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hdnode.js b/src/hdnode.js index 4486ec5..d726ce3 100644 --- a/src/hdnode.js +++ b/src/hdnode.js @@ -118,7 +118,8 @@ HDNode.fromBuffer = function(buffer) { var decode = ECPointFp.decodeFrom(ecparams.getCurve(), data) assert.equal(decode.compressed, true, 'Invalid public key') - // When importing a serialized extended public key, implementations must verify whether the X coordinate in the public key data corresponds to a point on the curve. If not, the extended public key is invalid. + // Verify that the X coordinate in the public point corresponds to a point on the curve. + // If not, the extended public key is invalid. decode.Q.validate() hd = new HDNode(decode.Q, chainCode, params.network) From ee04826464a267be294f538523a7f2a7d2a5ab3b Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 4 Jun 2014 14:47:39 +1000 Subject: [PATCH 28/28] HDNode: clearer branch comments --- src/hdnode.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hdnode.js b/src/hdnode.js index d726ce3..f4bf7e1 100644 --- a/src/hdnode.js +++ b/src/hdnode.js @@ -102,17 +102,16 @@ HDNode.fromBuffer = function(buffer) { // 32 bytes: the chain code var chainCode = buffer.slice(13, 45) - - // 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for - // public keys, 0x00 + k for private keys) var hd + + // 33 bytes: private key data (0x00 + k) if (params.isPrivate) { assert.strictEqual(buffer.readUInt8(45), 0x00, 'Invalid private key') - var data = buffer.slice(46, 78) var D = BigInteger.fromBuffer(data) hd = new HDNode(D, chainCode, params.network) + // 33 bytes: public key data (0x02 + X or 0x03 + X) } else { var data = buffer.slice(45, 78) var decode = ECPointFp.decodeFrom(ecparams.getCurve(), data)