diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index d678f0de..551bbdb1 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -268,6 +268,8 @@ HDPrivateKey.prototype.scan44 = function scan44(options, txByAddress, callback) if (total === 0) { if (chainConstant === 0) return chainCheck(1); + if (isAccount) + return callback(null, accounts[accountIndex]); return callback(null, accounts); } @@ -276,7 +278,7 @@ HDPrivateKey.prototype.scan44 = function scan44(options, txByAddress, callback) if (isAccount) { if (chainConstant === 0) return chainCheck(1); - return callback(null, accounts[0]); + return callback(null, accounts[accountIndex]); } return scanner(accountIndex + 1); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 40903cfb..d9b6e841 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -664,6 +664,175 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx) { this.setReceiveDepth(depth.receive + 1); }; +Wallet.prototype.scan = function scan(txByAddress, callback) { + function done(err, depths) { + if (err) + return callback(err); + + if (depths.changeDepth >= this.changeDepth) + this.setChangeDepth(depths.changeDepth + 1); + + if (depths.receiveDepth >= this.receiveDepth) + this.setReceiveDepth(depths.receiveDepth + 1); + } + + if (this.derivation === 'bip44') + return this._scan44({ current: true }, txByAddress, done); + + if (this.derivation === 'bip45') + return this._scan45({}, txByAddress, done); +}; + +Wallet.prototype._scan44 = function scan44(options, txByAddress, callback) { + var self = this; + var accounts = []; + var isAccount = this.master.isAccount44(); + var _accountKey = this.accountKey; + + assert(this._initialized); + + return (function chainCheck(change) { + return (function scanner(accountIndex) { + var addressIndex = 0; + var total = 0; + var gap = 0; + + if (options.current) + accountIndex = self.accountIndex; + + // 1. derive the first account's node (index = 0) + self.accountKey = isAccount + ? self.master + : self.master.deriveAccount44(accountIndex); + + if (isAccount) + accountIndex = new bn(self.master.childIndex).toNumber() - constants.hd.hardened; + + // 2. derive the external chain node of this account + // 3. scan addresses of the external chain; + // respect the gap limit described below + return (function next() { + var address = self.deriveAddress(change, addressIndex++); + var addr = address.getAddress(); + + return txByAddress(addr, function(err, txs) { + var result; + + if (err) { + self.accountKey = _accountKey; + return callback(err); + } + + if (txs) { + if (typeof txs === 'boolean') + result = txs; + else if (typeof txs === 'number') + result = txs > 0; + else if (Array.isArray(txs)) + result = txs.length > 0; + else + result = false; + } + + if (result) { + total++; + gap = 0; + return next(); + } + + if (++gap < 20) + return next(); + + assert(accounts[accountIndex] == null || change === true); + + if (change === false) + accounts[accountIndex] = { receiveDepth: addressIndex }; + else + accounts[accountIndex].changeDepth = addressIndex; + + // 4. if no transactions are found on the + // external chain, stop discovery + if (total === 0) { + if (change === false) + return chainCheck(true); + self.accountKey = _accountKey; + if (isAccount || options.current) + return callback(null, accounts[accountIndex]); + return callback(null, accounts); + } + + // 5. if there are some transactions, increase + // the account index and go to step 1 + if (isAccount || options.current) { + if (change === false) + return chainCheck(true); + self.accountKey = _accountKey; + return callback(null, accounts[accountIndex]); + } + + return scanner(accountIndex + 1); + }); + })(); + })(0); + })(false); +}; + +Wallet.prototype._scan45 = function scan45(options, txByAddress, callback) { + var depths = { changeDepth: 0, receiveDepth: 0 }; + + assert(this._initialized); + + return (function chainCheck(change) { + var addressIndex = 0; + var total = 0; + var gap = 0; + + return (function next() { + var address = self.deriveAddress(change, addressIndex++); + var addr = address.getAddress(); + + return txByAddress(addr, function(err, txs) { + var result; + + if (err) + return callback(err); + + if (txs) { + if (typeof txs === 'boolean') + result = txs; + else if (typeof txs === 'number') + result = txs > 0; + else if (Array.isArray(txs)) + result = txs.length > 0; + else + result = false; + } + + if (result) { + total++; + gap = 0; + return next(); + } + + if (++gap < 20) + return next(); + + assert(depths.receiveDepth === 0 || change === true); + + if (change === false) + depths.receiveDepth = addressIndex; + else + depths.changeDepth = addressIndex; + + if (change === false) + return chainCheck(true); + + return callback(null, depths); + }); + })(); + })(false); +}; + Wallet.prototype.scriptInputs = function scriptInputs(tx, index) { this.fillPrevout(tx); var addresses = this.deriveInputs(tx);