From 3480b8c67989148f8ad09bf9aa6d68d1eacfa87f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 27 May 2016 14:26:40 -0700 Subject: [PATCH] more wallet work. --- lib/bcoin/keyring.js | 4 - lib/bcoin/mtx.js | 2 +- lib/bcoin/wallet.js | 127 ++++++++------------- lib/bcoin/walletdb.js | 163 +++++++++++++++++++++------ test/wallet-test.js | 257 +++++++++++++++++++++++------------------- 5 files changed, 326 insertions(+), 227 deletions(-) diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index 0fea0ed0..7f450ea8 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -487,10 +487,6 @@ KeyRing.prototype.sign = function sign(tx, key, index, type) { return total; }; -KeyRing.prototype.__defineGetter__('privateKey', function() { - return this.getPrivateKey(); -}); - KeyRing.prototype.__defineGetter__('publicKey', function() { return this.getPublicKey(); }); diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index bcac1208..6fb036d2 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -986,7 +986,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { if (options.confirmed && coin.height === -1) continue; - if (options.height != null && coin.coinbase) { + if (options.height >= 0 && coin.coinbase) { if (options.height + 1 < coin.height + constants.tx.COINBASE_MATURITY) continue; } diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index bf05bf5e..73edeb69 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -45,44 +45,6 @@ var BufferWriter = require('./writer'); * (default=account key "address"). */ -function MasterKey(options) { - this.json = options.json; - this.key = options.key || null; -} - -MasterKey.prototype.decrypt = function decrypt(passphrase) { - if (this.key) - return this.key; - - if (!this.json.encrypted) - return bcoin.hd.fromJSON(this.json); - - return bcoin.hd.fromJSON(this.json, passphrase); -}; - -MasterKey.prototype.toJSON = function toJSON() { - return this.json; -}; - -MasterKey.fromKey = function fromKey(key) { - return new MasterKey({ - key: key, - json: key.toJSON() - }); -}; - -MasterKey.fromJSON = function fromJSON(json) { - var key; - - if (!json.encrypted) - key = bcoin.hd.fromJSON(json); - - return new MasterKey({ - key: key, - json: json - }); -}; - function Wallet(options) { var i, key; @@ -129,7 +91,7 @@ function Wallet(options) { this.m = options.m || 1; this.n = options.n || 1; - this.cache = new bcoin.lru(20); + this.cache = new bcoin.lru(20, 1); if (this.n > 1) this.type = 'multisig'; @@ -1114,7 +1076,7 @@ Wallet.prototype.scan = function scan(getByAddress, callback) { var self = this; var res = false; - return this._scan({}, getByAddress, function(err, depth, txs) { + return this._scan(getByAddress, function(err, depth, txs) { if (err) return callback(err); @@ -1124,42 +1086,13 @@ Wallet.prototype.scan = function scan(getByAddress, callback) { if (self.setReceiveDepth(depth.receiveDepth + 1)) res = true; - if (self.provider && self.provider.addTX) { - utils.forEachSerial(txs, function(tx, next) { - self.addTX(tx, next); - }, function(err) { - if (err) - return callback(err); - return callback(null, res, txs); - }); - return; - } - return callback(null, res, txs); }); }; -/** - * Clone the wallet (used for scanning). - * @returns {Wallet} - */ - -Wallet.prototype.clone = function clone() { - var passphrase = this.options.passphrase; - var wallet; - - delete this.options.passphrase; - - wallet = Wallet.fromJSON(this.toJSON()); - - this.options.passphrase = passphrase; - - return wallet; -}; - -Wallet.prototype._scan = function _scan(options, getByAddress, callback) { +Wallet.prototype._scan = function _scan(getByAddress, callback) { + var self = this; var depth = { changeDepth: 0, receiveDepth: 0 }; - var wallet = this.clone(); var all = []; assert(this._initialized); @@ -1170,7 +1103,7 @@ Wallet.prototype._scan = function _scan(options, getByAddress, callback) { var gap = 0; (function next() { - var address = wallet.deriveAddress(change, addressIndex++); + var address = self.deriveAddress(change, addressIndex++); getByAddress(address.getAddress(), function(err, txs) { var result; @@ -1250,10 +1183,15 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, index) { Wallet.prototype.sign = function sign(tx, passphrase, index, type) { var addresses = this.deriveInputs(tx, index); - var key = this.master.decrypt(passphrase); var total = 0; var i, address, key; + try { + key = this.master.decrypt(passphrase); + } catch (e) { + return 0; + } + for (i = 0; i < addresses.length; i++) { address = addresses[i]; @@ -1499,10 +1437,6 @@ Wallet.prototype.getAddress = function getAddress() { return this.receiveAddress.getAddress(); }; -Wallet.prototype.__defineGetter__('privateKey', function() { - return this.getPrivateKey(); -}); - Wallet.prototype.__defineGetter__('publicKey', function() { return this.getPublicKey(); }); @@ -1681,6 +1615,45 @@ Wallet.isWallet = function isWallet(obj) { && obj.deriveAddress === 'function'; }; +function MasterKey(options) { + this.json = options.json; + this.key = options.key || null; +} + +MasterKey.prototype.decrypt = function decrypt(passphrase) { + if (this.key) + return this.key; + + if (!this.json.encrypted) + return bcoin.hd.fromJSON(this.json); + + return bcoin.hd.fromJSON(this.json, passphrase); +}; + +MasterKey.prototype.toJSON = function toJSON() { + return this.json; +}; + +MasterKey.fromKey = function fromKey(key) { + return new MasterKey({ + key: key, + json: key.toJSON() + }); +}; + +MasterKey.fromJSON = function fromJSON(json) { + var key; + + if (!json.encrypted) + key = bcoin.hd.fromJSON(json); + + return new MasterKey({ + key: key, + json: json + }); +}; + + /* * Expose */ diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index a866cf55..5db57a32 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -252,6 +252,37 @@ WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) }); }; +/** + * Derive an address. + * @param {WalletID} id + * @param {Boolean} change + * @param {Function} callback + */ + +WalletDB.prototype.createAddress = function createAddress(id, change, callback) { + var self = this; + var address; + + callback = utils.ensure(callback); + + this.get(id, function(err, json) { + if (err) + return callback(err); + + if (!wallet) + return callback(new Error('No wallet.')); + + address = wallet.createAddress(change); + + self.save(wallet, function(err) { + if (err) + return callback(err); + + return callback(null, address); + }); + }); +}; + /** * Add a public account/purpose key to the wallet for multisig. * @param {WalletID} id @@ -260,7 +291,7 @@ WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) * @param {Function} callback */ -WalletDB.prototype.addKey = function addKey(id, key, callback) { +WalletDB.prototype.modifyKey = function modifyKey(id, key, remove, callback) { var self = this; callback = utils.ensure(callback); @@ -273,37 +304,10 @@ WalletDB.prototype.addKey = function addKey(id, key, callback) { return callback(new Error('No wallet.')); try { - wallet.addKey(key); - } catch (e) { - return callback(e); - } - - self.save(wallet, callback); - }); -}; - -/** - * Remove a public account/purpose key to the wallet for multisig. - * @param {WalletID} id - * @param {HDPublicKey|Base58String} key - Account (bip44) or Purpose - * (bip45) key (can be in base58 form). - * @param {Function} callback - */ - -WalletDB.prototype.removeKey = function removeKey(id, key, callback) { - var self = this; - - callback = utils.ensure(callback); - - this.get(id, function(err, wallet) { - if (err) - return callback(err); - - if (!wallet) - return callback(new Error('No wallet.')); - - try { - wallet.removeKey(key); + if (!remove) + wallet.addKey(key); + else + wallet.removeKey(key); } catch (e) { return callback(e); } @@ -698,6 +702,41 @@ WalletDB.prototype.provider = function provider() { return new Provider(this); }; +WalletDB.prototype.register = function register(id, provider) { + if (!this.listeners[id]) + this.listeners[id] = []; + + if (this.listeners[id].indexOf(provider) !== -1) + this.listeners[id].push(provider); +}; + +WalletDB.prototype.unregister = function unregister(id, provider) { + var listeners = this.listeners[id]; + var i; + + if (!listeners) + return; + + i = listeners.indexOf(provider); + if (i !== -1) + listeners.splice(i, 1); + + if (listeners.length === 0) + delete this.listeners[id]; +}; + +WalletDB.prototype.fire = function fire(id) { + var args = Array.prototype.slice.call(arguments, 1); + var listeners = this.listeners[id]; + var i; + + if (!listeners) + return; + + for (i = 0; i < listeners.length; i++) + listeners.emit.apply(listener, args); +}; + /** * Represents {@link Wallet} Provider. This is what * allows the {@link Wallet} object to access @@ -959,6 +998,66 @@ Provider.prototype.save = function save(wallet, callback) { return this.db.save(wallet, callback); }; +/** + * Notify the provider backend that a new address was + * derived (not technically necessary if you're + * implementing a provider). + * @param {Wallet} wallet + * @param {Address} address + */ + +Provider.prototype.addKey = function addKey(key, callback) { + return this.db.addKey(this.id, key, false, callback); +}; + +/** + * Notify the provider backend that a new address was + * derived (not technically necessary if you're + * implementing a provider). + * @param {Wallet} wallet + * @param {Address} address + */ + +// Provider.prototype.deriveInputs = function deriveInputs(tx, index, callback) { +// return this.db.deriveInputs(this.id, tx, index, callback); +// }; + +/** + * Notify the provider backend that a new address was + * derived (not technically necessary if you're + * implementing a provider). + * @param {Wallet} wallet + * @param {Address} address + */ + +Provider.prototype.removeKey = function removeKey(key, callback) { + return this.db.addKey(this.id, key, true, callback); +}; + +/** + * Notify the provider backend that a new address was + * derived (not technically necessary if you're + * implementing a provider). + * @param {Wallet} wallet + * @param {Address} address + */ + +Provider.prototype.createReceive = function createReceive(callback) { + return this.db.createAddress(this.id, false, callback); +}; + +/** + * Notify the provider backend that a new address was + * derived (not technically necessary if you're + * implementing a provider). + * @param {Wallet} wallet + * @param {Address} address + */ + +Provider.prototype.createChange = function createChange(callback) { + return this.db.createAddress(this.id, true, callback); +}; + /** * Zap stale transactions. * @param {Number} now - Current time. diff --git a/test/wallet-test.js b/test/wallet-test.js index 7c2d8713..50bedbd4 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -486,134 +486,165 @@ describe('Wallet', function() { n: 3 }; - wdb.create(utils.merge({}, options), function(err, w1) { - assert.ifError(err); - wdb.create(utils.merge({}, options), function(err, w2) { - assert.ifError(err); - wdb.create(utils.merge({}, options), function(err, w3) { + var w1, w2, w3, receive; + + utils.serial([ + function(next) { + wdb.create(utils.merge({}, options), function(err, w1_) { assert.ifError(err); - wdb.create({}, function(err, receive) { + w1 = w1_; + next(); + }); + }, + function(next) { + wdb.create(utils.merge({}, options), function(err, w2_) { + assert.ifError(err); + w2 = w2_; + next(); + }); + }, + function(next) { + wdb.create(utils.merge({}, options), function(err, w3_) { + assert.ifError(err); + w3 = w3_; + next(); + }); + }, + function(next) { + wdb.create({}, function(err, receive_) { + assert.ifError(err); + receive = receive_; + next(); + }); + }, + ], function(err) { + assert.ifError(err); + + w1.addKey(w2); + w1.addKey(w3); + w2.addKey(w1); + w2.addKey(w3); + w3.addKey(w1); + w3.addKey(w2); + + utils.serial([ + wdb.save.bind(wdb, w1), + wdb.save.bind(wdb, w2), + wdb.save.bind(wdb, w3), + wdb.save.bind(wdb, receive) + ], function(err) { + assert.ifError(err); + + // w3 = bcoin.wallet.fromJSON(w3.toJSON()); + + // Our p2sh address + var addr = w1.getAddress(); + + if (witness) + assert(bcoin.address.parseBase58(addr).type === 'witnessscripthash'); + else + assert(bcoin.address.parseBase58(addr).type === 'scripthash'); + + assert.equal(w1.getAddress(), addr); + assert.equal(w2.getAddress(), addr); + assert.equal(w3.getAddress(), addr); + + var paddr = w1.getProgramAddress(); + assert.equal(w1.getProgramAddress(), paddr); + assert.equal(w2.getProgramAddress(), paddr); + assert.equal(w3.getProgramAddress(), paddr); + + // Add a shared unspent transaction to our wallets + var utx = bcoin.mtx(); + if (bullshitNesting) + utx.addOutput({ address: paddr, value: 5460 * 10 }); + else + utx.addOutput({ address: addr, value: 5460 * 10 }); + + utx.addInput(dummyInput); + + assert(w1.ownOutput(utx.outputs[0])); + + // Simulate a confirmation + utx.ps = 0; + utx.ts = 1; + utx.height = 1; + + assert.equal(w1.receiveDepth, 1); + + wdb.addTX(utx, function(err) { + assert.ifError(err); + wdb.addTX(utx, function(err) { assert.ifError(err); - - w1.addKey(w2); - w1.addKey(w3); - w2.addKey(w1); - w2.addKey(w3); - w3.addKey(w1); - w3.addKey(w2); - - // w3 = bcoin.wallet.fromJSON(w3.toJSON()); - - // Our p2sh address - var addr = w1.getAddress(); - - if (witness) - assert(bcoin.address.parseBase58(addr).type === 'witnessscripthash'); - else - assert(bcoin.address.parseBase58(addr).type === 'scripthash'); - - assert.equal(w1.getAddress(), addr); - assert.equal(w2.getAddress(), addr); - assert.equal(w3.getAddress(), addr); - - var paddr = w1.getProgramAddress(); - assert.equal(w1.getProgramAddress(), paddr); - assert.equal(w2.getProgramAddress(), paddr); - assert.equal(w3.getProgramAddress(), paddr); - - // Add a shared unspent transaction to our wallets - var utx = bcoin.mtx(); - if (bullshitNesting) - utx.addOutput({ address: paddr, value: 5460 * 10 }); - else - utx.addOutput({ address: addr, value: 5460 * 10 }); - - utx.addInput(dummyInput); - - assert(w1.ownOutput(utx.outputs[0])); - - // Simulate a confirmation - utx.ps = 0; - utx.ts = 1; - utx.height = 1; - - assert.equal(w1.receiveDepth, 1); - wdb.addTX(utx, function(err) { assert.ifError(err); - wdb.addTX(utx, function(err) { + + assert.equal(w1.receiveDepth, 2); + assert.equal(w1.changeDepth, 1); + + assert(w1.getAddress() !== addr); + addr = w1.getAddress(); + assert.equal(w1.getAddress(), addr); + assert.equal(w2.getAddress(), addr); + assert.equal(w3.getAddress(), addr); + + // Create a tx requiring 2 signatures + var send = bcoin.mtx(); + send.addOutput({ address: receive.getAddress(), value: 5460 }); + assert(!send.verify(null, true, flags)); + w1.fill(send, { rate: 10000, round: true }, function(err) { assert.ifError(err); - wdb.addTX(utx, function(err) { + + w1.sign(send); + + assert(!send.verify(null, true, flags)); + w2.sign(send); + + assert(send.verify(null, true, flags)); + + assert.equal(w1.changeDepth, 1); + var change = w1.changeAddress.getAddress(); + assert.equal(w1.changeAddress.getAddress(), change); + assert.equal(w2.changeAddress.getAddress(), change); + assert.equal(w3.changeAddress.getAddress(), change); + + // Simulate a confirmation + send.ps = 0; + send.ts = 1; + send.height = 1; + + wdb.addTX(send, function(err) { assert.ifError(err); - - assert.equal(w1.receiveDepth, 2); - assert.equal(w1.changeDepth, 1); - - assert(w1.getAddress() !== addr); - addr = w1.getAddress(); - assert.equal(w1.getAddress(), addr); - assert.equal(w2.getAddress(), addr); - assert.equal(w3.getAddress(), addr); - - // Create a tx requiring 2 signatures - var send = bcoin.mtx(); - send.addOutput({ address: receive.getAddress(), value: 5460 }); - assert(!send.verify(null, true, flags)); - w1.fill(send, { rate: 10000, round: true }, function(err) { + wdb.addTX(send, function(err) { assert.ifError(err); - - w1.sign(send); - - assert(!send.verify(null, true, flags)); - w2.sign(send); - - assert(send.verify(null, true, flags)); - - assert.equal(w1.changeDepth, 1); - var change = w1.changeAddress.getAddress(); - assert.equal(w1.changeAddress.getAddress(), change); - assert.equal(w2.changeAddress.getAddress(), change); - assert.equal(w3.changeAddress.getAddress(), change); - - // Simulate a confirmation - send.ps = 0; - send.ts = 1; - send.height = 1; - wdb.addTX(send, function(err) { assert.ifError(err); - wdb.addTX(send, function(err) { - assert.ifError(err); - wdb.addTX(send, function(err) { - assert.ifError(err); - assert.equal(w1.receiveDepth, 2); - assert.equal(w1.changeDepth, 2); + assert.equal(w1.receiveDepth, 2); + assert.equal(w1.changeDepth, 2); - assert(w1.getAddress() === addr); - assert(w1.changeAddress.getAddress() !== change); - change = w1.changeAddress.getAddress(); - assert.equal(w1.changeAddress.getAddress(), change); - assert.equal(w2.changeAddress.getAddress(), change); - assert.equal(w3.changeAddress.getAddress(), change); + assert(w1.getAddress() === addr); + assert(w1.changeAddress.getAddress() !== change); + change = w1.changeAddress.getAddress(); + assert.equal(w1.changeAddress.getAddress(), change); + assert.equal(w2.changeAddress.getAddress(), change); + assert.equal(w3.changeAddress.getAddress(), change); - if (witness) - send.inputs[0].witness.items[2] = new Buffer([]); - else - send.inputs[0].script.code[2] = 0; + if (witness) + send.inputs[0].witness.items[2] = new Buffer([]); + else + send.inputs[0].script.code[2] = 0; - assert(!send.verify(null, true, flags)); - assert.equal(send.getFee(), 10000); + assert(!send.verify(null, true, flags)); + assert.equal(send.getFee(), 10000); - w3 = bcoin.wallet.fromJSON(w3.toJSON()); - assert.equal(w3.receiveDepth, 2); - assert.equal(w3.changeDepth, 2); - assert.equal(w3.getAddress(), addr); - assert.equal(w3.changeAddress.getAddress(), change); + w3 = bcoin.wallet.fromJSON(w3.toJSON()); + assert.equal(w3.receiveDepth, 2); + assert.equal(w3.changeDepth, 2); + assert.equal(w3.getAddress(), addr); + assert.equal(w3.changeAddress.getAddress(), change); - cb(); - }); - }); + cb(); }); }); });