diff --git a/src/hdnode.js b/src/hdnode.js new file mode 100644 index 0000000..f4bf7e1 --- /dev/null +++ b/src/hdnode.js @@ -0,0 +1,285 @@ +var assert = require('assert') +var base58 = require('./base58') + +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 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 === 'private'), + network: network + } + } + } + + assert(false, 'Could not find version ' + version.toString(16)) +} + +function HDNode(K, chainCode, network) { + network = network || networks.bitcoin + + assert(Buffer.isBuffer(chainCode), 'Expected Buffer, got ' + chainCode) + assert(network.bip32, 'Unknown BIP32 constants for network') + + this.chainCode = chainCode + this.depth = 0 + this.index = 0 + this.network = network + + if (K instanceof BigInteger) { + this.privKey = new ECKey(K, true) + this.pubKey = this.privKey.pub + } else { + this.pubKey = new ECPubKey(K, true) + } +} + +HDNode.MASTER_SECRET = new Buffer('Bitcoin seed') +HDNode.HIGHEST_BIT = 0x80000000 +HDNode.LENGTH = 78 + +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 HDNode constructor + var pIL = BigInteger.fromBuffer(IL) + + return new HDNode(pIL, IR, network) +} + +HDNode.fromSeedHex = function(hex, network) { + return HDNode.fromSeedBuffer(new Buffer(hex, 'hex'), network) +} + +HDNode.fromBase58 = function(string) { + var buffer = base58.decode(string) + + var payload = buffer.slice(0, -4) + var checksum = buffer.slice(-4) + + var newChecksum = crypto.hash256(payload).slice(0, 4) + assert.deepEqual(newChecksum, checksum, 'Invalid checksum') + + return HDNode.fromBuffer(payload) +} + +HDNode.fromBuffer = function(buffer) { + assert.strictEqual(buffer.length, HDNode.LENGTH, 'Invalid buffer length') + + // 4 byte: version bytes + var version = buffer.readUInt32BE(0) + var params = findBIP32ParamsByVersion(version) + + // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... + var depth = buffer.readUInt8(4) + + // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) + 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) + var index = buffer.readUInt32BE(9) + assert(depth > 0 || index === 0, 'Invalid index') + + // 32 bytes: the chain code + var chainCode = buffer.slice(13, 45) + 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) + assert.equal(decode.compressed, true, 'Invalid public key') + + // 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) + } + + hd.depth = depth + hd.index = index + hd.parentFingerprint = parentFingerprint + + return hd +} + +HDNode.fromHex = function(hex) { + return HDNode.fromBuffer(new Buffer(hex, 'hex')) +} + +HDNode.prototype.getIdentifier = function() { + return crypto.hash160(this.pubKey.toBuffer()) +} + +HDNode.prototype.getFingerprint = function() { + return this.getIdentifier().slice(0, 4) +} + +HDNode.prototype.getAddress = function() { + return this.pubKey.getAddress(this.network.pubKeyHash) +} + +HDNode.prototype.toBase58 = function(isPrivate) { + var buffer = this.toBuffer(isPrivate) + var checksum = crypto.hash256(buffer).slice(0, 4) + + return base58.encode(Buffer.concat([ + buffer, + checksum + ])) +} + +HDNode.prototype.toBuffer = function(isPrivate) { + if (isPrivate == undefined) isPrivate = !!this.privKey + + // Version + var version = isPrivate ? this.network.bip32.private : this.network.bip32.public + var buffer = new Buffer(HDNode.LENGTH) + + // 4 bytes: version bytes + buffer.writeUInt32BE(version, 0) + + // Depth + // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, .... + buffer.writeUInt8(this.depth, 4) + + // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) + var fingerprint = (this.depth === 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. + // This is encoded in Big endian. (0x00000000 if master key) + buffer.writeUInt32BE(this.index, 9) + + // 32 bytes: the chain code + this.chainCode.copy(buffer, 13) + + // 33 bytes: the public key or private key data + if (isPrivate) { + assert(this.privKey, 'Missing private key') + + // 0x00 + k for private keys + buffer.writeUInt8(0, 45) + this.privKey.D.toBuffer(32).copy(buffer, 46) + } else { + + // X9.62 encoding for public keys + this.pubKey.toBuffer().copy(buffer, 45) + } + + return buffer +} + +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 +HDNode.prototype.derive = function(index) { + var isHardened = index >= HDNode.HIGHEST_BIT + var indexBuffer = new Buffer(4) + indexBuffer.writeUInt32BE(index, 0) + + var data + + // Hardened child + if (isHardened) { + assert(this.privKey, 'Could not derive hardened child key') + + // data = 0x00 || ser256(kpar) || ser32(index) + data = Buffer.concat([ + this.privKey.D.toBuffer(33), + indexBuffer + ]) + + // Normal child + } else { + // data = serP(point(kpar)) || ser32(index) + // = serP(Kpar) || ser32(index) + data = Buffer.concat([ + this.pubKey.toBuffer(), + indexBuffer + ]) + } + + var I = crypto.HmacSHA512(data, this.chainCode) + var IL = I.slice(0, 32) + var IR = I.slice(32) + + 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 + var hd + if (this.privKey) { + // ki = parse256(IL) + kpar (mod n) + 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) { + return this.derive(index + 1) + } + + hd = new HDNode(ki, IR, this.network) + + // Public parent key -> public child key + } else { + // Ki = point(parse256(IL)) + Kpar + // = G*IL + Kpar + 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()) { + return this.derive(index + 1) + } + + hd = new HDNode(Ki, IR, this.network) + } + + hd.depth = this.depth + 1 + hd.index = index + hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) + + return hd +} + +HDNode.prototype.deriveHardened = function(index) { + // Only derives hardened private keys by default + return this.derive(index + HDNode.HIGHEST_BIT) +} + +HDNode.prototype.toString = HDNode.prototype.toBase58 + +module.exports = HDNode diff --git a/src/hdwallet.js b/src/hdwallet.js deleted file mode 100644 index b0ce031..0000000 --- a/src/hdwallet.js +++ /dev/null @@ -1,260 +0,0 @@ -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') -var ECPubKey = require('./ecpubkey') -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 - - 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 - - this.chaincode = IR - this.depth = 0 - this.index = 0 -} - -HDWallet.MASTER_SECRET = new Buffer('Bitcoin seed') -HDWallet.HIGHEST_BIT = 0x80000000 -HDWallet.LENGTH = 78 - -HDWallet.fromSeedHex = function(hex, network) { - return new HDWallet(new Buffer(hex, 'hex'), network) -} - -HDWallet.fromBase58 = function(string) { - var buffer = base58.decode(string) - - var payload = buffer.slice(0, -4) - var checksum = buffer.slice(-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) -} - -HDWallet.fromBuffer = function(input) { - assert.strictEqual(input.length, HDWallet.LENGTH, 'Invalid buffer length') - - var hd = new HDWallet() - - // 4 byte: version bytes - var version = input.readUInt32BE(0) - - var type - for (var name in networks) { - var network = networks[name] - - for (var t in network.bip32) { - if (version != network.bip32[t]) continue - - type = t - hd.network = network - } - } - - if (!hd.network) { - throw new Error('Could not find version ' + version.toString(16)) - } - - // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... - hd.depth = input.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') - } - - // 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') - - // 32 bytes: the chain code - hd.chaincode = input.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)) - - hd.priv = new ECKey(D, true) - hd.pub = hd.priv.pub - } else { - hd.pub = ECPubKey.fromBuffer(input.slice(45, 78), true) - } - - return hd -} - -HDWallet.prototype.getIdentifier = function() { - return crypto.hash160(this.pub.toBuffer()) -} - -HDWallet.prototype.getFingerprint = function() { - return this.getIdentifier().slice(0, 4) -} - -HDWallet.prototype.getAddress = function() { - return this.pub.getAddress(this.getKeyVersion()) -} - -HDWallet.prototype.toBuffer = function(priv) { - // Version - var version = this.network.bip32[priv ? 'priv' : 'pub'] - var buffer = new Buffer(HDWallet.LENGTH) - - // 4 bytes: version bytes - buffer.writeUInt32BE(version, 0) - - // Depth - // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, .... - buffer.writeUInt8(this.depth, 4) - - // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) - var fingerprint = this.depth ? this.parentFingerprint : 0x00000000 - buffer.writeUInt32BE(fingerprint, 5) - - // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. - // This is encoded in Big endian. (0x00000000 if master key) - buffer.writeUInt32BE(this.index, 9) - - // 32 bytes: the chain code - this.chaincode.copy(buffer, 13) - - // 33 bytes: the public key or private key data - if (priv) { - assert(this.priv, 'Missing private key') - - // 0x00 + k for private keys - buffer.writeUInt8(0, 45) - this.priv.D.toBuffer(32).copy(buffer, 46) - } else { - - // X9.62 encoding for public keys - this.pub.toBuffer().copy(buffer, 45) - } - - return buffer -} -HDWallet.prototype.toHex = function(priv) { - return this.toBuffer(priv).toString('hex') -} - -HDWallet.prototype.toBase58 = function(priv) { - var buffer = this.toBuffer(priv) - 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 - var indexBuffer = new Buffer(4) - indexBuffer.writeUInt32BE(index, 0) - - var data - - // Hardened child - if (isHardened) { - assert(this.priv, 'Could not derive hardened child key') - - // data = 0x00 || ser256(kpar) || ser32(index) - data = Buffer.concat([ - this.priv.D.toBuffer(33), - indexBuffer - ]) - - // Normal child - } else { - // data = serP(point(kpar)) || ser32(index) - // = serP(Kpar) || ser32(index) - data = Buffer.concat([ - this.pub.toBuffer(), - indexBuffer - ]) - } - - 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) - - // 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) { - return this.derive(index + 1) - } - - hd.priv = new ECKey(ki, true) - hd.pub = hd.priv.pub - - // Public parent key -> public child key - } else { - // Ki = point(parse256(IL)) + Kpar - // = G*IL + Kpar - var Ki = ecparams.getG().multiply(pIL).add(this.pub.Q) - - // In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i - if (pIL.compareTo(ecparams.getN()) >= 0 || Ki.isInfinity()) { - return this.derive(index + 1) - } - - hd.pub = new ECPubKey(Ki, true) - } - - hd.chaincode = IR - hd.depth = this.depth + 1 - hd.network = this.network - hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) - hd.index = index - - return hd -} - -HDWallet.prototype.derivePrivate = function(index) { - // Only derives hardened private keys by default - return this.derive(index + HDWallet.HIGHEST_BIT) -} - -HDWallet.prototype.getKeyVersion = function() { - return this.network.pubKeyHash -} - -HDWallet.prototype.toString = HDWallet.prototype.toBase58 - -module.exports = HDWallet 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/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, diff --git a/src/wallet.js b/src/wallet.js index 846a5ad..2616272 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) { @@ -29,11 +29,11 @@ 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 + // 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) @@ -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/fixtures/hdwallet.json b/test/fixtures/hdnode.json similarity index 70% rename from test/fixtures/hdwallet.json rename to test/fixtures/hdnode.json index bcebf2c..86040a3 100644 --- a/test/fixtures/hdwallet.json +++ b/test/fixtures/hdnode.json @@ -3,175 +3,190 @@ { "master": { "seed": "000102030405060708090a0b0c0d0e0f", - "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", - "fingerprint": "3442193e", - "address": "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma", "wif": "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW", "pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", - "chaincode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", + "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", + "description": "m/0'", + "m": 0, + "hardened": true, "wif": "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT", "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", - "chaincode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", + "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", + "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", + "description": "m/0'/1/2'", + "m": 2, + "hardened": true, "wif": "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU", "pubKey": "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", - "chaincode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", + "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", + "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", + "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", + "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", + "m": 2147483647, + "hardened": true, "wif": "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr", "pubKey": "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", - "chaincode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9", + "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", + "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", + "m": 2147483646, + "hardened": true, "wif": "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF", "pubKey": "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", - "chaincode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", - + "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", + "chainCode": "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271", "hex": "0488b21e0531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", "hexPriv": "0488ade40531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed27100bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23", + "network": "bitcoin", "base58": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", - "base58Priv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" + "base58Priv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", + "identifier": "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220", + "fingerprint": "26132fdb", + "address": "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt" } ] } @@ -181,14 +196,32 @@ { "exception": "Invalid checksum", "string": "xprvQQQQQQQQQQQQQQQQCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" + } + ], + "fromBuffer": [ + { + "exception": "Invalid buffer length", + "hex": "0488b21e0000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee4" }, { - "exception": "Invalid BIP32 string", - "string": "SQ8nQ2jWarXqLo9oHGKKP6iQDsQbPRftq7rjtYY3hqJRPQRgrmeunFnDKbH7B15yGPLZBrhhkKXx3pwD6LcBooJRGq6x7matAXpMsgn" + "exception": "Invalid buffer length", + "hex": "0488b21e0000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee35500000000" }, { - "exception": "Invalid BIP32 string", - "string": "37hdAfw3aMiWcBGPP2ywmY5jizTeSSP5GXayKY3RxkEZ7f3SBnRE1pN6eY3VzGkgx6vbdNtuKfrHgEaYvW9KkFZCycaPvWiA9TtfmeVB592Sf9RfSzQzXo72" + "exception": "Invalid parent fingerprint", + "hex": "0488b21e00ffffffff000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" + }, + { + "exception": "Invalid index", + "hex": "0488b21e0000000000ffffffff7ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" + }, + { + "exception": "Could not find version 22222222", + "hex": "222222220000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" + }, + { + "exception": "Point is not on the curve", + "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508020045400697100007000037899988826500030092003000016366806305909050" } ] } diff --git a/test/hdnode.js b/test/hdnode.js new file mode 100644 index 0000000..71a87c7 --- /dev/null +++ b/test/hdnode.js @@ -0,0 +1,287 @@ +var assert = require('assert') +var networks = require('../src/networks') +var sec = require('../src/sec') +var ecparams = sec("secp256k1") + +var BigInteger = require('bigi') +var HDNode = require('../src/hdnode') + +var fixtures = require('./fixtures/hdnode.json') + +describe('HDNode', 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 HDNode(D, chainCode) + + 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.pubKey.compressed, true) + assert.strictEqual(hdP.pubKey.compressed, true) + }) + + it('has a default depth/index of 0', function() { + 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 HDNode(Q, chainCode) + + assert.equal(hd.network, networks.bitcoin) + }) + + it('supports alternative networks', function() { + 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 HDNode(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 = HDNode.fromSeedHex(f.master.seed) + + assert.equal(hd.privKey.toWIF(), f.master.wif) + assert.equal(hd.chainCode.toString('hex'), f.master.chainCode) + }) + }) + }) + + describe('toBase58', function() { + fixtures.valid.forEach(function(f) { + it('exports ' + f.master.base58 + ' (public) correctly', function() { + var hd = HDNode.fromSeedHex(f.master.seed) + + assert.equal(hd.toBase58(false), f.master.base58) + }) + }) + + fixtures.valid.forEach(function(f) { + it('exports ' + f.master.base58Priv + ' (private) correctly', function() { + 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 = HDNode.fromBase58(fixtures.valid[0].master.base58) + + assert.throws(function() { + hd.toBase58(true) + }, /Missing private key/) + }) + }) + + describe('fromBase58', function() { + fixtures.valid.forEach(function(f) { + it('imports ' + f.master.base58 + ' (public) correctly', function() { + var hd = HDNode.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 = HDNode.fromBase58(f.master.base58Priv) + + assert.equal(hd.toBase58(), f.master.base58Priv) + }) + }) + + fixtures.invalid.fromBase58.forEach(function(f) { + it('throws on ' + f.string, function() { + assert.throws(function() { + HDNode.fromBase58(f.string) + }, new RegExp(f.exception)) + }) + }) + }) + + describe('fromBuffer/fromHex', function() { + fixtures.valid.forEach(function(f) { + it('imports ' + f.master.hex + ' (public) correctly', function() { + var hd = HDNode.fromHex(f.master.hex) + + assert.equal(hd.toBuffer().toString('hex'), f.master.hex) + }) + }) + + fixtures.valid.forEach(function(f) { + it('imports ' + f.master.hexPriv + ' (private) correctly', function() { + var hd = HDNode.fromHex(f.master.hexPriv) + + assert.equal(hd.toBuffer().toString('hex'), f.master.hexPriv) + }) + }) + + fixtures.invalid.fromBuffer.forEach(function(f) { + it('throws on ' + f.string, function() { + assert.throws(function() { + HDNode.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 = HDNode.fromSeedHex(f.master.seed) + + assert.equal(hd.toHex(false), f.master.hex) + }) + }) + + fixtures.valid.forEach(function(f) { + it('exports ' + f.master.hexPriv + ' (private) correctly', function() { + 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 = HDNode.fromHex(fixtures.valid[0].master.hex) + + assert.throws(function() { + hd.toHex(true) + }, /Missing private key/) + }) + }) + + describe('getIdentifier', function() { + var f = fixtures.valid[0] + + it('returns the identifier for ' + f.master.fingerprint, function() { + var hd = HDNode.fromBase58(f.master.base58) + + assert.equal(hd.getIdentifier().toString('hex'), f.master.identifier) + }) + }) + + describe('getFingerprint', function() { + var f = fixtures.valid[0] + + it('returns the fingerprint for ' + f.master.fingerprint, function() { + var hd = HDNode.fromBase58(f.master.base58) + + assert.equal(hd.getFingerprint().toString('hex'), f.master.fingerprint) + }) + }) + + describe('getAddress', function() { + var f = fixtures.valid[0] + + 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) + }) + + it('supports alternative networks', function() { + var hd = HDNode.fromBase58(f.master.base58) + hd.network = networks.testnet + + assert.equal(hd.getAddress().version, networks.testnet.pubKeyHash) + }) + }) + + describe('derive', function() { + function verifyVector(hd, v, depth) { + 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) + + if (v.hardened) { + assert.equal(hd.index, v.m + HDNode.HIGHEST_BIT) + } else { + assert.equal(hd.index, v.m) + } + } + + 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) { + hd = hd.deriveHardened(c.m) + + } else { + hd = hd.derive(c.m) + } + + verifyVector(hd, c, i + 1) + }) + }) + }) + + 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] + + var parentNode = HDNode.fromBase58(f.master.base58) + var child = parentNode.derive(c.m) + + assert.equal(child.toBase58(), c.base58) + }) + + it('throws on Public -> public (hardened)', function() { + var f = fixtures.valid[0] + var c = f.children[0] + + var parentNode = HDNode.fromBase58(f.master.base58) + + assert.throws(function() { + parentNode.deriveHardened(c.m) + }, /Could not derive hardened child key/) + }) + }) +}) diff --git a/test/hdwallet.js b/test/hdwallet.js deleted file mode 100644 index 0fc7987..0000000 --- a/test/hdwallet.js +++ /dev/null @@ -1,174 +0,0 @@ -var assert = require('assert') -var networks = require('../src/networks') - -var HDWallet = require('../src/hdwallet') -var fixtures = require('./fixtures/hdwallet.json') - -function b2h(buf) { - assert(Buffer.isBuffer(buf)) - return buf.toString('hex') -} - -describe('HDWallet', function() { - describe('toBase58', function() { - it('reproduces input', function() { - var input = 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5' - var output = HDWallet.fromBase58(input).toBase58(false) - assert.equal(output, input) - - input = 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334' - output = HDWallet.fromBase58(input).toBase58(true) - assert.equal(output, input) - }) - - it('fails with priv=true when theres no private key', function() { - var hd = HDWallet.fromBase58('xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon') - try { - hd.toBase58(true) - } catch(e) { - assert(e.message.match(/private key/i)) - return - } - assert.fail() - }) - }) - - describe('fromBase58', function() { - fixtures.invalid.fromBase58.forEach(function(f) { - it('throws on ' + f.string, function() { - assert.throws(function() { - HDWallet.fromBase58(f.string) - }, 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 - ]) - - it('creates from binary seed', function() { - var hd = new HDWallet(seed) - - assert.equal(hd.priv.D.toHex(), expectedPrivateKey) - assert(hd.pub) - }) - - describe('fromSeedHex', function() { - it('creates from hex seed', function() { - var hd = HDWallet.fromSeedHex(seed.toString('hex')) - - assert.equal(hd.priv.D.toHex(), expectedPrivateKey) - 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() { - 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.toHex(false), v.hex) - assert.equal(hd.toHex(true), v.hexPriv) - assert.equal(hd.toBase58(false), v.base58) - assert.equal(hd.toBase58(true), v.base58Priv) - } - - it('matches the test vectors', function() { - fixtures.valid.forEach(function(f) { - var hd = HDWallet.fromSeedHex(f.master.seed) - verifyVector(hd, f.master) - - 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) - }) - }) - }) - }) - - describe('derive', function() { - describe('m/0', function() { - var wallet = HDWallet.fromBase58('xpub6CxuB8ifZCMXeS3KbyNkYvrsJEHqxedCSiUhrNwH1nKtb8hcJpxDbDxkdoVCTR2bQ1G8hY4UMv85gef9SEpgFFUftBjt37FUSZxVx4AU9Qh').derive(0) - - it('derives the correct public key', function() { - assert.equal(wallet.pub.toHex(), '02df843e6ae2017e0772d0584f76f56b8f2f5181a3045c7a7740a9d86dc7c80ce7') - }) - - it('derives the correct depth', function() { - assert.equal(wallet.depth, 4) - }) - }) - }) - - describe('network types', function() { - var seed - - beforeEach(function() { - seed = new Buffer('foobar') - }) - - it('ensure that a bitcoin wallet is the default', function() { - var wallet = new HDWallet(seed) - - assert.equal(wallet.network, networks.bitcoin) - }) - - it('ensures that a bitcoin Wallet generates bitcoin addresses', function() { - var wallet = new HDWallet(seed) - var address = wallet.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() - - assert.equal(address, 'mmxjUCnx5xjeaSHxJicsDCxCmjZwq8KTbv') - }) - - it('throws an exception when unknown network type is passed in', function() { - assert.throws(function() { - new HDWallet(seed, {}) - }, /Unknown BIP32 constants for network/) - }) - }) -}) diff --git a/test/wallet.js b/test/wallet.js index c3b459b..955eba1 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) }) @@ -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 ) })