From 142b84796815d24c38b134f3dd3f5ef25c3b3ead Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Tue, 11 Mar 2014 22:22:03 +0800 Subject: [PATCH 01/10] HD wallet fromMaterHex allows specifying network [#60] --- src/hdwallet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index 309955d..89eb41b 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -36,9 +36,9 @@ function arrayEqual(a, b) { HDWallet.getChecksum = base58.getChecksum; -HDWallet.fromMasterHex = function(hex) { +HDWallet.fromMasterHex = function(hex, network) { var bytes = convert.hexToBytes(hex) - return new HDWallet(convert.bytesToString(bytes)) + return new HDWallet(convert.bytesToString(bytes), network) } HDWallet.fromBase58 = function(input) { From 44012b47b5c817fcebe566c4bfc841f1fb2b49bc Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Tue, 11 Mar 2014 22:41:20 +0800 Subject: [PATCH 02/10] change hd wallet constructor to take bytes original constructor -> fromSeedString, fromMasterHex -> fromSeedHex [#60] --- src/hdwallet.js | 11 +++++++---- test/hdwallet.js | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/hdwallet.js b/src/hdwallet.js index 89eb41b..43207a1 100644 --- a/src/hdwallet.js +++ b/src/hdwallet.js @@ -13,7 +13,7 @@ var Network = require('./network') var HDWallet = module.exports = function(seed, network) { if (seed === undefined) return - var seedWords = convert.bytesToWordArray(convert.stringToBytes(seed)) + var seedWords = convert.bytesToWordArray(seed) var I = convert.wordArrayToBytes(HmacSHA512(seedWords, 'Bitcoin seed')) this.chaincode = I.slice(32) this.network = network || 'mainnet' @@ -36,9 +36,12 @@ function arrayEqual(a, b) { HDWallet.getChecksum = base58.getChecksum; -HDWallet.fromMasterHex = function(hex, network) { - var bytes = convert.hexToBytes(hex) - return new HDWallet(convert.bytesToString(bytes), network) +HDWallet.fromSeedHex = function(hex, network) { + return new HDWallet(convert.hexToBytes(hex), network) +} + +HDWallet.fromSeedString = function(string, network) { + return new HDWallet(convert.stringToBytes(string), network) } HDWallet.fromBase58 = function(input) { diff --git a/test/hdwallet.js b/test/hdwallet.js index 85d2ae4..f637535 100644 --- a/test/hdwallet.js +++ b/test/hdwallet.js @@ -32,19 +32,46 @@ describe('HDWallet', function() { }) }) - describe('ctor', function() { - it('creates from seed', function() { - var seed = 'crazy horse battery staple' - , hd = new HDWallet(seed) + describe('constructor & seed deserialization', function() { + var expectedPrivKey, seed; - assert(hd.priv) + beforeEach(function(){ + expectedPrivKey = 'KwkW62Lzm4a7Eo5nPLezrVjWBGFh2KMfpyf4Swz9NmfsVaLoeXv9' + seed = [ + 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, expectedPrivKey) assert(hd.pub) }) + + describe('fromSeedHex', function() { + it('creates from hex seed', function() { + var hd = HDWallet.fromSeedHex(b2h(seed)) + + assert.equal(hd.priv, expectedPrivKey) + assert(hd.pub) + }) + }) + + describe('fromSeedString', function() { + it('creates from string seed', function() { + var hd = HDWallet.fromSeedString(convert.bytesToString(seed)) + + assert.equal(hd.priv, expectedPrivKey) + assert(hd.pub) + }) + }) }) describe('Test vectors', function() { it('Test vector 1', function() { - var hd = HDWallet.fromMasterHex('000102030405060708090a0b0c0d0e0f') + var hd = HDWallet.fromSeedHex('000102030405060708090a0b0c0d0e0f') // m assert.equal(b2h(hd.getIdentifier()), '3442193e1bb70916e914552172cd4e2dbc9df811') @@ -131,7 +158,7 @@ describe('HDWallet', function() { }) it('Test vector 2', function() { - var hd = HDWallet.fromMasterHex('fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542') + var hd = HDWallet.fromSeedHex('fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542') // m assert.equal(b2h(hd.getIdentifier()), 'bd16bee53961a47d6ad888e29545434a89bdfe95') From 1212099bfcafd04213f6d70797b7ee6fca1160e1 Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Tue, 11 Mar 2014 23:42:01 +0800 Subject: [PATCH 03/10] Wallet address derivation follows bip32/bitcoinj keychain structure Also wallet accepts bytes in constructor [#60] --- src/wallet.js | 29 ++++++++++++-------------- test/wallet.js | 56 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index d203a54..0af210d 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -6,7 +6,7 @@ var BigInteger = require('./jsbn/jsbn'); var Transaction = require('./transaction').Transaction; var TransactionIn = require('./transaction').TransactionIn; var TransactionOut = require('./transaction').TransactionOut; -var HDWallet = require('./hdwallet.js') +var HDNode = require('./hdwallet.js') var SecureRandom = require('./jsbn/rng'); var rng = new SecureRandom(); @@ -16,13 +16,6 @@ var Wallet = function (seed, options) { var options = options || {} var network = options.network || 'mainnet' - // HD first-level child derivation method (i.e. public or private child key derivation) - // NB: if not specified, defaults to private child derivation - // Also see https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 - this.derivationMethod = options.derivationMethod || 'private' - assert(this.derivationMethod == 'public' || this.derivationMethod == 'private', - "derivationMethod must be either 'public' or 'private'"); - // Stored in a closure to make accidental serialization less likely var keys = []; var masterkey = null; @@ -37,22 +30,26 @@ var Wallet = function (seed, options) { // Make a new master key this.newMasterKey = function(seed, network) { if (!seed) { - var seedBytes = new Array(32); + var seed= new Array(32); rng.nextBytes(seedBytes); - seed = convert.bytesToString(seedBytes) } - masterkey = new HDWallet(seed, network); + masterkey = new HDNode(seed, network); keys = [] } this.newMasterKey(seed, network) + // HD first-level child derivation method (i.e. public or private child key derivation) + // NB: if not specified, defaults to private child derivation + // Also see https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 + this.accountZero = masterkey.derivePrivate(0) + this.externalAccount = this.accountZero.derive(0) + this.internalAccount = this.accountZero.derive(1) + // Add a new address this.generateAddress = function() { - if(this.derivationMethod == 'private') - keys.push(masterkey.derivePrivate(keys.length)); - else - keys.push(masterkey.derive(keys.length)); - this.addresses.push(keys[keys.length-1].getBitcoinAddress().toString()) + var key = this.externalAccount.derive(keys.length) + keys.push(key); // consider removing this and derive on-demand for simplified encrypted keychain + this.addresses.push(key.getBitcoinAddress().toString()) return this.addresses[this.addresses.length - 1] } diff --git a/test/wallet.js b/test/wallet.js index 650ad5c..0f6747c 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -1,10 +1,17 @@ var Wallet = require('../src/wallet.js') +var HDNode = require('../src/hdwallet.js') +var convert = require('../src/convert.js') var assert = require('assert') +var SHA256 = require('crypto-js/sha256') +var Crypto = require('crypto-js') describe('Wallet', function() { - var seed = 'crazy horse battery staple' + var seed; + beforeEach(function(){ + seed = convert.wordArrayToBytes(SHA256("don't use a string seed like this in real life")) + }) - describe('default constructor', function() { + describe('constructor', function() { var wallet; beforeEach(function() { wallet = new Wallet(seed) @@ -14,24 +21,43 @@ describe('Wallet', function() { assert.equal(wallet.getMasterKey().network, 'mainnet') }) - it('defaults to private derivationMethod', function() { - assert.equal(wallet.derivationMethod, 'private') + it("generates m/0' as the main account", function() { + var mainAccount = wallet.accountZero + assert.equal(mainAccount.index, 0 + HDNode.HIGHEST_BIT) + assert.equal(mainAccount.depth, 1) + }) + + it("generates m/0'/0 as the external account", function() { + var account = wallet.externalAccount + assert.equal(account.index, 0) + assert.equal(account.depth, 2) + }) + + it("generates m/0'/1 as the internal account", function() { + var account = wallet.internalAccount + assert.equal(account.index, 1) + assert.equal(account.depth, 2) + }) + + describe('constructor options', function() { + var wallet; + beforeEach(function() { + wallet = new Wallet(seed, {network: 'testnet'}) + }) + + it('uses the network if specified', function() { + assert.equal(wallet.getMasterKey().network, 'testnet') + }) }) }) - describe('constructor options', function() { + describe('generateAddress', function(){ var wallet; - beforeEach(function() { - wallet = new Wallet(seed, {network: 'testnet', derivationMethod: 'public'}) - }) + beforeEach(function() { wallet = new Wallet(seed, {network: 'testnet'}) }) - it('uses the network if specified', function() { - assert.equal(wallet.getMasterKey().network, 'testnet') - }) - - it('uses the derivationMethod if specified', function() { - assert.equal(wallet.derivationMethod, 'public') + it('defaults to generating receiving addresses', function(){ + assert.equal(wallet.generateAddress(), "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa") + assert.equal(wallet.generateAddress(), "n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X") }) }) - }) From ab9e782d14dcf3edff682b98bdde2a9a0cbadc7a Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Thu, 13 Mar 2014 17:43:35 +0800 Subject: [PATCH 04/10] private a method to generate change addresses --- src/wallet.js | 16 ++++++++++------ test/wallet.js | 24 +++++++++++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index 0af210d..995a823 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -23,6 +23,7 @@ var Wallet = function (seed, options) { // Addresses this.addresses = []; + this.changeAddresses = []; // Transaction output data this.outputs = {}; @@ -38,21 +39,24 @@ var Wallet = function (seed, options) { } this.newMasterKey(seed, network) - // HD first-level child derivation method (i.e. public or private child key derivation) - // NB: if not specified, defaults to private child derivation - // Also see https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 + // HD first-level child derivation method should be private + // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 this.accountZero = masterkey.derivePrivate(0) this.externalAccount = this.accountZero.derive(0) this.internalAccount = this.accountZero.derive(1) - // Add a new address this.generateAddress = function() { - var key = this.externalAccount.derive(keys.length) - keys.push(key); // consider removing this and derive on-demand for simplified encrypted keychain + var key = this.externalAccount.derive(this.addresses.length) this.addresses.push(key.getBitcoinAddress().toString()) return this.addresses[this.addresses.length - 1] } + this.generateChangeAddress = function() { + var key = this.internalAccount.derive(this.changeAddresses.length) + this.changeAddresses.push(key.getBitcoinAddress().toString()) + return this.changeAddresses[this.changeAddresses.length - 1] + } + // Processes a transaction object // If "verified" is true, then we trust the transaction as "final" this.processTx = function(tx, verified) { diff --git a/test/wallet.js b/test/wallet.js index 0f6747c..39a8500 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -52,12 +52,26 @@ describe('Wallet', function() { }) describe('generateAddress', function(){ - var wallet; - beforeEach(function() { wallet = new Wallet(seed, {network: 'testnet'}) }) + it('generate receiving addresses', function(){ + var wallet = new Wallet(seed, {network: 'testnet'}) + var expectedAddresses = [ + "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa", + "n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X" + ] - it('defaults to generating receiving addresses', function(){ - assert.equal(wallet.generateAddress(), "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa") - assert.equal(wallet.generateAddress(), "n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X") + assert.equal(wallet.generateAddress(), expectedAddresses[0]) + assert.equal(wallet.generateAddress(), expectedAddresses[1]) + assert.deepEqual(wallet.addresses, expectedAddresses) + }) + }) + + describe('generateChangeAddress', function(){ + it('generates change addresses', function(){ + var wallet = new Wallet(seed, {network: 'testnet'}) + var expectedAddresses = ["mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"] + + assert.equal(wallet.generateChangeAddress(), expectedAddresses[0]) + assert.deepEqual(wallet.changeAddresses, expectedAddresses) }) }) }) From 9200479159bc43aa88fe24cd65284294c20172b4 Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Thu, 13 Mar 2014 19:05:55 +0800 Subject: [PATCH 05/10] getPrivateKey derives key on the fly also added getPrivateKeyFromAddress --- src/wallet.js | 20 +++++++++++++++----- test/wallet.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index 995a823..a7fccfc 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -182,13 +182,23 @@ var Wallet = function (seed, options) { this.getMasterKey = function() { return masterkey } this.getPrivateKey = function(index) { - if (typeof index == "string") - return keys.filter(function(i,k){ return addresses[i] == index })[0] - else - return keys[index] + return this.externalAccount.derive(index) } - this.getPrivateKeys = function() { return keys } + this.getInternalPrivateKey = function(index) { + return this.internalAccount.derive(index) + } + + this.getPrivateKeyForAddress = function(address) { + var index; + if((index = this.addresses.indexOf(address)) > -1) { + return this.getPrivateKey(index) + } else if((index = this.changeAddresses.indexOf(address)) > -1) { + return this.getInternalPrivateKey(index) + } else { + throw new Error('Unknown address. Make sure the address is from the keychain and has been generated.') + } + } }; module.exports = Wallet; diff --git a/test/wallet.js b/test/wallet.js index 39a8500..3b1a2ca 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -74,4 +74,47 @@ describe('Wallet', function() { assert.deepEqual(wallet.changeAddresses, expectedAddresses) }) }) + + describe('getPrivateKey', function(){ + it('returns the private key at the given index of external account', function(){ + var wallet = new Wallet(seed, {network: 'testnet'}) + + assertPrivateKeyEqual(wallet.getPrivateKey(0), wallet.externalAccount.derive(0)) + assertPrivateKeyEqual(wallet.getPrivateKey(1), wallet.externalAccount.derive(1)) + }) + }) + + describe('getInternalPrivateKey', function(){ + it('returns the private key at the given index of internal account', function(){ + var wallet = new Wallet(seed, {network: 'testnet'}) + + assertPrivateKeyEqual(wallet.getInternalPrivateKey(0), wallet.internalAccount.derive(0)) + assertPrivateKeyEqual(wallet.getInternalPrivateKey(1), wallet.internalAccount.derive(1)) + }) + }) + + describe('getPrivateKeyForAddress', function(){ + it('returns the private key for the given address', function(){ + var wallet = new Wallet(seed, {network: 'testnet'}) + wallet.generateChangeAddress() + wallet.generateAddress() + wallet.generateAddress() + + assertPrivateKeyEqual(wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X"), + wallet.externalAccount.derive(1)) + assertPrivateKeyEqual(wallet.getPrivateKeyForAddress("mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"), + wallet.internalAccount.derive(0)) + }) + + it('raises an error when address is not found', function(){ + var wallet = new Wallet(seed, {network: 'testnet'}) + assert.throws(function() { + wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X") + }, Error, 'Unknown address. Make sure the address is from the keychain and has been generated.') + }) + }) + + function assertPrivateKeyEqual(key1, key2){ + assert.equal(key1.toString(), key2.toString()) + } }) From 207163704b5e6b434e224e2268679db598d37313 Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Thu, 13 Mar 2014 19:12:52 +0800 Subject: [PATCH 06/10] return private key rather than HD wallet node --- src/wallet.js | 4 ++-- test/wallet.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index a7fccfc..212d8a8 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -182,11 +182,11 @@ var Wallet = function (seed, options) { this.getMasterKey = function() { return masterkey } this.getPrivateKey = function(index) { - return this.externalAccount.derive(index) + return this.externalAccount.derive(index).priv } this.getInternalPrivateKey = function(index) { - return this.internalAccount.derive(index) + return this.internalAccount.derive(index).priv } this.getPrivateKeyForAddress = function(address) { diff --git a/test/wallet.js b/test/wallet.js index 3b1a2ca..fe18b31 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -79,8 +79,8 @@ describe('Wallet', function() { it('returns the private key at the given index of external account', function(){ var wallet = new Wallet(seed, {network: 'testnet'}) - assertPrivateKeyEqual(wallet.getPrivateKey(0), wallet.externalAccount.derive(0)) - assertPrivateKeyEqual(wallet.getPrivateKey(1), wallet.externalAccount.derive(1)) + assertPrivateKeyEqual(wallet.getPrivateKey(0), wallet.externalAccount.derive(0).priv) + assertPrivateKeyEqual(wallet.getPrivateKey(1), wallet.externalAccount.derive(1).priv) }) }) @@ -88,8 +88,8 @@ describe('Wallet', function() { it('returns the private key at the given index of internal account', function(){ var wallet = new Wallet(seed, {network: 'testnet'}) - assertPrivateKeyEqual(wallet.getInternalPrivateKey(0), wallet.internalAccount.derive(0)) - assertPrivateKeyEqual(wallet.getInternalPrivateKey(1), wallet.internalAccount.derive(1)) + assertPrivateKeyEqual(wallet.getInternalPrivateKey(0), wallet.internalAccount.derive(0).priv) + assertPrivateKeyEqual(wallet.getInternalPrivateKey(1), wallet.internalAccount.derive(1).priv) }) }) @@ -101,9 +101,9 @@ describe('Wallet', function() { wallet.generateAddress() assertPrivateKeyEqual(wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X"), - wallet.externalAccount.derive(1)) + wallet.externalAccount.derive(1).priv) assertPrivateKeyEqual(wallet.getPrivateKeyForAddress("mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"), - wallet.internalAccount.derive(0)) + wallet.internalAccount.derive(0).priv) }) it('raises an error when address is not found', function(){ From d4f3398d91485fa185c7134dbbd57eeddb521533 Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Fri, 14 Mar 2014 10:12:44 +0800 Subject: [PATCH 07/10] remove keys array and usage --- src/wallet.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index 212d8a8..908be55 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -17,7 +17,6 @@ var Wallet = function (seed, options) { var network = options.network || 'mainnet' // Stored in a closure to make accidental serialization less likely - var keys = []; var masterkey = null; var me = this; @@ -35,7 +34,6 @@ var Wallet = function (seed, options) { rng.nextBytes(seedBytes); } masterkey = new HDNode(seed, network); - keys = [] } this.newMasterKey(seed, network) @@ -168,12 +166,8 @@ var Wallet = function (seed, options) { tx.ins.map(function(inp,i) { var inp = inp.outpoint.hash+':'+inp.outpoint.index; if (me.outputs[inp]) { - var address = me.outputs[inp].address, - ind = me.addresses.indexOf(address); - if (ind >= 0) { - var key = keys[ind] - tx.sign(ind,key) - } + var address = me.outputs[inp].address + tx.sign(i, me.getPrivateKeyForAddress(address)) } }) return tx; From b2f010428f6e7e9eb92cd51c8cc6677f3f9b4dd0 Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Fri, 14 Mar 2014 10:31:26 +0800 Subject: [PATCH 08/10] store accounts in closure instead of on object --- src/wallet.js | 20 +++++++++++++------- test/wallet.js | 18 +++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index 908be55..640576d 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -19,6 +19,9 @@ var Wallet = function (seed, options) { // Stored in a closure to make accidental serialization less likely var masterkey = null; var me = this; + var accountZero = null; + var internalAccount = null; + var externalAccount = null; // Addresses this.addresses = []; @@ -39,18 +42,18 @@ var Wallet = function (seed, options) { // HD first-level child derivation method should be private // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 - this.accountZero = masterkey.derivePrivate(0) - this.externalAccount = this.accountZero.derive(0) - this.internalAccount = this.accountZero.derive(1) + accountZero = masterkey.derivePrivate(0) + externalAccount = accountZero.derive(0) + internalAccount = accountZero.derive(1) this.generateAddress = function() { - var key = this.externalAccount.derive(this.addresses.length) + var key = externalAccount.derive(this.addresses.length) this.addresses.push(key.getBitcoinAddress().toString()) return this.addresses[this.addresses.length - 1] } this.generateChangeAddress = function() { - var key = this.internalAccount.derive(this.changeAddresses.length) + var key = internalAccount.derive(this.changeAddresses.length) this.changeAddresses.push(key.getBitcoinAddress().toString()) return this.changeAddresses[this.changeAddresses.length - 1] } @@ -174,13 +177,16 @@ var Wallet = function (seed, options) { } this.getMasterKey = function() { return masterkey } + this.getAccountZero = function() { return accountZero } + this.getInternalAccount = function() { return internalAccount } + this.getExternalAccount = function() { return externalAccount } this.getPrivateKey = function(index) { - return this.externalAccount.derive(index).priv + return externalAccount.derive(index).priv } this.getInternalPrivateKey = function(index) { - return this.internalAccount.derive(index).priv + return internalAccount.derive(index).priv } this.getPrivateKeyForAddress = function(address) { diff --git a/test/wallet.js b/test/wallet.js index fe18b31..176be26 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -22,19 +22,19 @@ describe('Wallet', function() { }) it("generates m/0' as the main account", function() { - var mainAccount = wallet.accountZero + var mainAccount = wallet.getAccountZero() assert.equal(mainAccount.index, 0 + HDNode.HIGHEST_BIT) assert.equal(mainAccount.depth, 1) }) it("generates m/0'/0 as the external account", function() { - var account = wallet.externalAccount + var account = wallet.getExternalAccount() assert.equal(account.index, 0) assert.equal(account.depth, 2) }) it("generates m/0'/1 as the internal account", function() { - var account = wallet.internalAccount + var account = wallet.getInternalAccount() assert.equal(account.index, 1) assert.equal(account.depth, 2) }) @@ -79,8 +79,8 @@ describe('Wallet', function() { it('returns the private key at the given index of external account', function(){ var wallet = new Wallet(seed, {network: 'testnet'}) - assertPrivateKeyEqual(wallet.getPrivateKey(0), wallet.externalAccount.derive(0).priv) - assertPrivateKeyEqual(wallet.getPrivateKey(1), wallet.externalAccount.derive(1).priv) + assertPrivateKeyEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).priv) + assertPrivateKeyEqual(wallet.getPrivateKey(1), wallet.getExternalAccount().derive(1).priv) }) }) @@ -88,8 +88,8 @@ describe('Wallet', function() { it('returns the private key at the given index of internal account', function(){ var wallet = new Wallet(seed, {network: 'testnet'}) - assertPrivateKeyEqual(wallet.getInternalPrivateKey(0), wallet.internalAccount.derive(0).priv) - assertPrivateKeyEqual(wallet.getInternalPrivateKey(1), wallet.internalAccount.derive(1).priv) + assertPrivateKeyEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).priv) + assertPrivateKeyEqual(wallet.getInternalPrivateKey(1), wallet.getInternalAccount().derive(1).priv) }) }) @@ -101,9 +101,9 @@ describe('Wallet', function() { wallet.generateAddress() assertPrivateKeyEqual(wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X"), - wallet.externalAccount.derive(1).priv) + wallet.getExternalAccount().derive(1).priv) assertPrivateKeyEqual(wallet.getPrivateKeyForAddress("mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"), - wallet.internalAccount.derive(0).priv) + wallet.getInternalAccount().derive(0).priv) }) it('raises an error when address is not found', function(){ From a7a7999e3f10b4b4b1fc3fa2b30256d534bab458 Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Sat, 15 Mar 2014 10:18:59 +0800 Subject: [PATCH 09/10] fix Wallet default constructor --- src/wallet.js | 2 +- test/wallet.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/wallet.js b/src/wallet.js index 640576d..6e579ec 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -34,7 +34,7 @@ var Wallet = function (seed, options) { this.newMasterKey = function(seed, network) { if (!seed) { var seed= new Array(32); - rng.nextBytes(seedBytes); + rng.nextBytes(seed); } masterkey = new HDNode(seed, network); } diff --git a/test/wallet.js b/test/wallet.js index 176be26..b1a1579 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -39,6 +39,13 @@ describe('Wallet', function() { assert.equal(account.depth, 2) }) + describe('when seed is not specified', function(){ + it('generates a seed', function(){ + var wallet = new Wallet() + assert.ok(wallet.getMasterKey()) + }) + }) + describe('constructor options', function() { var wallet; beforeEach(function() { From 31cfb111786ea7765f7526539b82dd0f37699b7a Mon Sep 17 00:00:00 2001 From: Wei Lu <luwei.here@gmail.com> Date: Sun, 16 Mar 2014 13:31:46 +0800 Subject: [PATCH 10/10] new master key resets accounts and addresses --- src/wallet.js | 16 +++++++++++----- test/wallet.js | 48 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index 6e579ec..698f9a3 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -37,14 +37,20 @@ var Wallet = function (seed, options) { rng.nextBytes(seed); } masterkey = new HDNode(seed, network); + + // HD first-level child derivation method should be private + // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 + accountZero = masterkey.derivePrivate(0) + externalAccount = accountZero.derive(0) + internalAccount = accountZero.derive(1) + + me.addresses = []; + me.changeAddresses = []; + + me.outputs = {}; } this.newMasterKey(seed, network) - // HD first-level child derivation method should be private - // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 - accountZero = masterkey.derivePrivate(0) - externalAccount = accountZero.derive(0) - internalAccount = accountZero.derive(1) this.generateAddress = function() { var key = externalAccount.derive(this.addresses.length) diff --git a/test/wallet.js b/test/wallet.js index b1a1579..b72e2d7 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -58,6 +58,34 @@ describe('Wallet', function() { }) }) + describe('newMasterKey', function(){ + it('resets accounts', function(){ + var wallet = new Wallet() + var oldAccountZero = wallet.getAccountZero() + var oldExternalAccount = wallet.getExternalAccount() + var oldInternalAccount = wallet.getInternalAccount() + + wallet.newMasterKey(seed) + assertNotEqual(wallet.getAccountZero(), oldAccountZero) + assertNotEqual(wallet.getExternalAccount(), oldExternalAccount) + assertNotEqual(wallet.getInternalAccount(), oldInternalAccount) + }) + + it('resets addresses', function(){ + var wallet = new Wallet() + wallet.generateAddress() + wallet.generateChangeAddress() + var oldAddresses = wallet.addresses + var oldChangeAddresses = wallet.changeAddresses + assert.notDeepEqual(oldAddresses, []) + assert.notDeepEqual(oldChangeAddresses, []) + + wallet.newMasterKey(seed) + assert.deepEqual(wallet.addresses, []) + assert.deepEqual(wallet.changeAddresses, []) + }) + }) + describe('generateAddress', function(){ it('generate receiving addresses', function(){ var wallet = new Wallet(seed, {network: 'testnet'}) @@ -86,8 +114,8 @@ describe('Wallet', function() { it('returns the private key at the given index of external account', function(){ var wallet = new Wallet(seed, {network: 'testnet'}) - assertPrivateKeyEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).priv) - assertPrivateKeyEqual(wallet.getPrivateKey(1), wallet.getExternalAccount().derive(1).priv) + assertEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).priv) + assertEqual(wallet.getPrivateKey(1), wallet.getExternalAccount().derive(1).priv) }) }) @@ -95,8 +123,8 @@ describe('Wallet', function() { it('returns the private key at the given index of internal account', function(){ var wallet = new Wallet(seed, {network: 'testnet'}) - assertPrivateKeyEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).priv) - assertPrivateKeyEqual(wallet.getInternalPrivateKey(1), wallet.getInternalAccount().derive(1).priv) + assertEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).priv) + assertEqual(wallet.getInternalPrivateKey(1), wallet.getInternalAccount().derive(1).priv) }) }) @@ -107,9 +135,9 @@ describe('Wallet', function() { wallet.generateAddress() wallet.generateAddress() - assertPrivateKeyEqual(wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X"), + assertEqual(wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X"), wallet.getExternalAccount().derive(1).priv) - assertPrivateKeyEqual(wallet.getPrivateKeyForAddress("mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"), + assertEqual(wallet.getPrivateKeyForAddress("mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"), wallet.getInternalAccount().derive(0).priv) }) @@ -121,7 +149,11 @@ describe('Wallet', function() { }) }) - function assertPrivateKeyEqual(key1, key2){ - assert.equal(key1.toString(), key2.toString()) + function assertEqual(obj1, obj2){ + assert.equal(obj1.toString(), obj2.toString()) + } + + function assertNotEqual(obj1, obj2){ + assert.notEqual(obj1.toString(), obj2.toString()) } })