From 1212099bfcafd04213f6d70797b7ee6fca1160e1 Mon Sep 17 00:00:00 2001 From: Wei Lu Date: Tue, 11 Mar 2014 23:42:01 +0800 Subject: [PATCH] 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") }) }) - })