From 960393a53ff26a7cfe98220a9981a25d97b6e09e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 1 Oct 2016 20:05:28 -0700 Subject: [PATCH] wallet: use separate branch for nested addrs. --- bin/cli | 9 +++ lib/http/client.js | 21 +++++ lib/http/server.js | 7 ++ lib/http/wallet.js | 8 ++ lib/primitives/keyring.js | 42 +++++++--- lib/utils/utils.js | 4 +- lib/wallet/account.js | 166 +++++++++++++++++++++++++------------- lib/wallet/path.js | 5 +- lib/wallet/txdb.js | 52 ++++++------ lib/wallet/wallet.js | 129 ++++++++++++++++++----------- lib/wallet/walletdb.js | 72 ++++++++--------- lib/wallet/walletkey.js | 31 ++----- test/wallet-test.js | 40 +++++---- 13 files changed, 372 insertions(+), 214 deletions(-) diff --git a/bin/cli b/bin/cli index 529d5896..049ba32c 100755 --- a/bin/cli +++ b/bin/cli @@ -88,6 +88,12 @@ CLI.prototype.createAddress = co(function* createAddress() { this.log(addr); }); +CLI.prototype.createNested = co(function* createNested() { + var account = this.argv[0]; + var addr = yield this.wallet.createNested(account); + this.log(addr); +}); + CLI.prototype.getAccounts = co(function* getAccounts() { var accounts = yield this.wallet.getAccounts(); this.log(accounts); @@ -343,6 +349,8 @@ CLI.prototype.handleWallet = co(function* handleWallet() { return yield this.getAccount(); case 'address': return yield this.createAddress(); + case 'nested': + return yield this.createNested(); case 'retoken': return yield this.retoken(); case 'sign': @@ -370,6 +378,7 @@ CLI.prototype.handleWallet = co(function* handleWallet() { this.log(' $ account create [account-name]: Create account.'); this.log(' $ account get [account-name]: Get account details.'); this.log(' $ address: Derive new address.'); + this.log(' $ nested: Derive new nested address.'); this.log(' $ retoken: Create new api key.'); this.log(' $ send [address] [value]: Send transaction.'); this.log(' $ mktx [address] [value]: Create transaction.'); diff --git a/lib/http/client.js b/lib/http/client.js index 8a668e2c..5b71ab8f 100644 --- a/lib/http/client.js +++ b/lib/http/client.js @@ -756,6 +756,27 @@ HTTPClient.prototype.createAddress = function createAddress(id, options) { return this._post(path, options); }; +/** + * Create address. + * @param {WalletID} id + * @param {Object} options + * @returns {Promise} - Returns Array. + */ + +HTTPClient.prototype.createNested = function createNested(id, options) { + var path; + + if (!options) + options = {}; + + if (typeof options === 'string') + options = { account: options }; + + path = '/wallet/' + id + '/nested'; + + return this._post(path, options); +}; + /* * Helpers */ diff --git a/lib/http/server.js b/lib/http/server.js index 7b4bbbf8..7f180052 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -714,6 +714,13 @@ HTTPServer.prototype._init = function _init() { send(200, address.toJSON()); })); + // Create nested address + this.post('/wallet/:id/nested', con(function *(req, res, send, next) { + var account = req.options.account; + var address = yield req.wallet.createNested(account); + send(200, address.toJSON()); + })); + // Wallet Balance this.get('/wallet/:id/balance', con(function *(req, res, send, next) { var account = req.options.account; diff --git a/lib/http/wallet.js b/lib/http/wallet.js index de55bc80..5270bd3e 100644 --- a/lib/http/wallet.js +++ b/lib/http/wallet.js @@ -281,6 +281,14 @@ HTTPWallet.prototype.createAddress = function createAddress(account) { return this.client.createAddress(this.id, account); }; +/** + * @see Wallet#createAddress + */ + +HTTPWallet.prototype.createNested = function createNested(account) { + return this.client.createNested(this.id, account); +}; + /** * @see Wallet#setPassphrase */ diff --git a/lib/primitives/keyring.js b/lib/primitives/keyring.js index 9e1974f4..436f6afe 100644 --- a/lib/primitives/keyring.js +++ b/lib/primitives/keyring.js @@ -37,6 +37,7 @@ function KeyRing(options, network) { this.network = bcoin.network.get(); this.witness = false; + this.nested = false; this.publicKey = constants.ZERO_KEY; this.privateKey = null; this.script = null; @@ -82,6 +83,11 @@ KeyRing.prototype.fromOptions = function fromOptions(options, network) { this.witness = options.witness; } + if (options.nested != null) { + assert(typeof options.nested === 'boolean'); + this.nested = options.nested; + } + if (script) return this.fromScript(key, script, compressed, network); @@ -572,6 +578,8 @@ KeyRing.prototype.compile = function compile(hash, type, version) { */ KeyRing.prototype.getHash = function getHash(enc) { + if (this.nested) + return this.getNestedHash(enc); if (this.script) return this.getScriptHash(enc); return this.getKeyHash(enc); @@ -584,6 +592,8 @@ KeyRing.prototype.getHash = function getHash(enc) { */ KeyRing.prototype.getAddress = function getAddress(enc) { + if (this.nested) + return this.getNestedAddress(enc); if (this.script) return this.getScriptAddress(enc); return this.getKeyAddress(enc); @@ -704,14 +714,14 @@ KeyRing.prototype.verify = function verify(msg, sig) { * @returns {ScriptType} */ -KeyRing.prototype.getType = function getType() { - if (this.program) - return this.program.getType(); +KeyRing.prototype.getVersion = function getVersion() { + if (!this.witness) + return -1; - if (this.script) - return this.script.getType(); + if (this.nested) + return -1; - return scriptTypes.PUBKEYHASH; + return 0; }; /** @@ -719,14 +729,19 @@ KeyRing.prototype.getType = function getType() { * @returns {ScriptType} */ -KeyRing.prototype.getAddressType = function getAddressType() { +KeyRing.prototype.getType = function getType() { + if (this.nested) + return scriptTypes.SCRIPTHASH; + if (this.witness) { if (this.script) return scriptTypes.WITNESSSCRIPTHASH; return scriptTypes.WITNESSPUBKEYHASH; } + if (this.script) return scriptTypes.SCRIPTHASH; + return scriptTypes.PUBKEYHASH; }; @@ -738,6 +753,10 @@ KeyRing.prototype.__defineGetter__('type', function() { return this.getType(); }); +KeyRing.prototype.__defineGetter__('version', function() { + return this.getVersion(); +}); + KeyRing.prototype.__defineGetter__('scriptHash', function() { return this.getScriptHash(); }); @@ -800,11 +819,12 @@ KeyRing.prototype.toJSON = function toJSON() { return { network: this.network.type, witness: this.witness, + nested: this.nested, publicKey: this.publicKey.toString('hex'), script: this.script ? this.script.toRaw().toString('hex') : null, + program: this.program ? this.program.toRaw().toString('hex') : null, type: constants.scriptTypesByVal[this.type].toLowerCase(), - address: this.getAddress('base58'), - nestedAddress: this.getNestedAddress('base58') + address: this.getAddress('base58') }; }; @@ -818,11 +838,13 @@ KeyRing.prototype.fromJSON = function fromJSON(json) { assert(json); assert(typeof json.network === 'string'); assert(typeof json.witness === 'boolean'); + assert(typeof json.nested === 'boolean'); assert(typeof json.publicKey === 'string'); assert(!json.script || typeof json.script === 'string'); this.nework = bcoin.network.get(json.network); this.witness = json.witness; + this.nested = json.nested; this.publicKey = new Buffer(json.publicKey, 'hex'); if (json.script) @@ -850,6 +872,7 @@ KeyRing.prototype.toRaw = function toRaw(writer) { var p = new BufferWriter(writer); p.writeU8(this.witness ? 1 : 0); + p.writeU8(this.nested ? 1 : 0); if (this.privateKey) { p.writeVarBytes(this.privateKey); @@ -881,6 +904,7 @@ KeyRing.prototype.fromRaw = function fromRaw(data, network) { this.network = bcoin.network.get(network); this.witness = p.readU8() === 1; + this.nested = p.readU8() === 1; key = p.readVarBytes(); diff --git a/lib/utils/utils.js b/lib/utils/utils.js index 35febc16..29c93854 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -2344,7 +2344,9 @@ utils.isName = function isName(key) { if (typeof key !== 'string') return false; - // Maximum worst case size: 80 bytes + if (!/^[\-\._0-9A-Za-z]+$/.test(key)) + return false; + return key.length >= 1 && key.length <= 40; }; diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 18ef66f0..542d70ac 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -16,6 +16,7 @@ var BufferWriter = require('../utils/writer'); var Path = require('./path'); var Script = require('../script/script'); var WalletKey = require('./walletkey'); +var HD = require('../hd/hd'); /** * Represents a BIP44 Account belonging to a {@link Wallet}. @@ -53,6 +54,7 @@ function Account(db, options) { this.receive = null; this.change = null; + this.nested = null; this.wid = 0; this.id = null; @@ -62,6 +64,7 @@ function Account(db, options) { this.accountIndex = 0; this.receiveDepth = 0; this.changeDepth = 0; + this.nestedDepth = 0; this.type = Account.types.PUBKEYHASH; this.m = 1; this.n = 1; @@ -105,7 +108,7 @@ Account.prototype.fromOptions = function fromOptions(options) { assert(options, 'Options are required.'); assert(utils.isNumber(options.wid)); assert(utils.isName(options.id), 'Bad Wallet ID.'); - assert(bcoin.hd.isHD(options.accountKey), 'Account key is required.'); + assert(HD.isHD(options.accountKey), 'Account key is required.'); assert(utils.isNumber(options.accountIndex), 'Account index is required.'); this.wid = options.wid; @@ -138,6 +141,11 @@ Account.prototype.fromOptions = function fromOptions(options) { this.changeDepth = options.changeDepth; } + if (options.nestedDepth != null) { + assert(utils.isNumber(options.nestedDepth)); + this.nestedDepth = options.nestedDepth; + } + if (options.type != null) { if (typeof options.type === 'string') { this.type = Account.types[options.type.toUpperCase()]; @@ -218,9 +226,10 @@ Account.prototype.init = co(function* init() { assert(this.receiveDepth === 0); assert(this.changeDepth === 0); + assert(this.nestedDepth === 0); this.initialized = true; - yield this.setDepth(1, 1); + yield this.setDepth(1, 1, 1); }); /** @@ -235,6 +244,9 @@ Account.prototype.open = function open() { this.receive = this.deriveReceive(this.receiveDepth - 1); this.change = this.deriveChange(this.changeDepth - 1); + if (this.witness) + this.nested = this.deriveReceive(this.nestedDepth - 1); + return Promise.resolve(null); }; @@ -249,10 +261,10 @@ Account.prototype.open = function open() { Account.prototype.pushKey = function pushKey(key) { var index; - if (bcoin.hd.isExtended(key)) - key = bcoin.hd.fromBase58(key); + if (HD.isExtended(key)) + key = HD.fromBase58(key); - if (!bcoin.hd.isPublic(key)) + if (!HD.isPublic(key)) throw new Error('Must add HD keys to wallet.'); if (!key.isAccount44()) @@ -283,10 +295,10 @@ Account.prototype.pushKey = function pushKey(key) { */ Account.prototype.spliceKey = function spliceKey(key) { - if (bcoin.hd.isExtended(key)) - key = bcoin.hd.fromBase58(key); + if (HD.isExtended(key)) + key = HD.fromBase58(key); - if (!bcoin.hd.isHDPublicKey(key)) + if (!HD.isHDPublicKey(key)) throw new Error('Must add HD keys to wallet.'); if (!key.isAccount44()) @@ -379,7 +391,7 @@ Account.prototype.removeKey = function removeKey(key) { */ Account.prototype.createReceive = function createReceive() { - return this.createKey(false); + return this.createKey(0); }; /** @@ -388,7 +400,16 @@ Account.prototype.createReceive = function createReceive() { */ Account.prototype.createChange = function createChange() { - return this.createKey(true); + return this.createKey(1); +}; + +/** + * Create a new change address (increments receiveDepth). + * @returns {WalletKey} + */ + +Account.prototype.createNested = function createNested() { + return this.createKey(2); }; /** @@ -397,23 +418,36 @@ Account.prototype.createChange = function createChange() { * @returns {Promise} - Returns {@link WalletKey}. */ -Account.prototype.createKey = co(function* createKey(change) { +Account.prototype.createKey = co(function* createKey(branch) { var ring, lookahead; - if (change) { - ring = this.deriveChange(this.changeDepth); - lookahead = this.deriveChange(this.changeDepth + this.lookahead); - yield this.saveKey(ring); - yield this.saveKey(lookahead); - this.changeDepth++; - this.change = ring; - } else { - ring = this.deriveReceive(this.receiveDepth); - lookahead = this.deriveReceive(this.receiveDepth + this.lookahead); - yield this.saveKey(ring); - yield this.saveKey(lookahead); - this.receiveDepth++; - this.receive = ring; + switch (branch) { + case 0: + ring = this.deriveReceive(this.receiveDepth); + lookahead = this.deriveReceive(this.receiveDepth + this.lookahead); + yield this.saveKey(ring); + yield this.saveKey(lookahead); + this.receiveDepth++; + this.receive = ring; + break; + case 1: + ring = this.deriveChange(this.changeDepth); + lookahead = this.deriveChange(this.changeDepth + this.lookahead); + yield this.saveKey(ring); + yield this.saveKey(lookahead); + this.changeDepth++; + this.change = ring; + break; + case 2: + ring = this.deriveNested(this.nestedDepth); + lookahead = this.deriveNested(this.nestedDepth + this.lookahead); + yield this.saveKey(ring); + yield this.saveKey(lookahead); + this.nestedDepth++; + this.nested = ring; + break; + default: + throw new Error('Bad branch: ' + branch); } this.save(); @@ -428,7 +462,7 @@ Account.prototype.createKey = co(function* createKey(change) { */ Account.prototype.deriveReceive = function deriveReceive(index, master) { - return this.deriveKey(false, index, master); + return this.deriveKey(0, index, master); }; /** @@ -438,7 +472,20 @@ Account.prototype.deriveReceive = function deriveReceive(index, master) { */ Account.prototype.deriveChange = function deriveChange(index, master) { - return this.deriveKey(true, index, master); + return this.deriveKey(1, index, master); +}; + +/** + * Derive a nested address at `index`. Do not increment depth. + * @param {Number} index + * @returns {WalletKey} + */ + +Account.prototype.deriveNested = function deriveNested(index, master) { + if (!this.witness) + throw new Error('Cannot derive nested on non-witness account.'); + + return this.deriveKey(2, index, master); }; /** @@ -485,7 +532,7 @@ Account.prototype.deriveKey = function deriveKey(branch, index, master) { var keys = []; var i, key, shared, ring, hash; - branch = +branch; + assert(typeof branch === 'number'); if (master && master.key) { key = master.key.deriveAccount44(this.accountIndex); @@ -516,22 +563,6 @@ Account.prototype.deriveKey = function deriveKey(branch, index, master) { return ring; }; -/** - * Get address type. - * @returns {ScriptType} - */ - -Account.prototype.getAddressType = function getAddressType() { - if (this.witness) { - if (this.type === Account.types.MULTISIG) - return Script.types.WITNESSSCRIPTHASH; - return Script.types.WITNESSPUBKEYHASH; - } - if (this.type === Account.types.MULTISIG) - return Script.types.SCRIPTHASH; - return Script.types.PUBKEYHASH; -}; - /** * Save the account to the database. Necessary * when address depth and keys change. @@ -570,9 +601,9 @@ Account.prototype.savePath = function savePath(path) { * @returns {Promise} - Returns {@link WalletKey}, {@link WalletKey}. */ -Account.prototype.setDepth = co(function* setDepth(receiveDepth, changeDepth) { +Account.prototype.setDepth = co(function* setDepth(receiveDepth, changeDepth, nestedDepth) { var i = -1; - var receive, change, lookahead; + var receive, change, nested, lookahead; if (receiveDepth > this.receiveDepth) { for (i = this.receiveDepth; i < receiveDepth; i++) { @@ -604,12 +635,27 @@ Account.prototype.setDepth = co(function* setDepth(receiveDepth, changeDepth) { this.changeDepth = changeDepth; } + if (this.witness && nestedDepth > this.nestedDepth) { + for (i = this.nestedDepth; i < nestedDepth; i++) { + nested = this.deriveNested(i); + yield this.saveKey(nested); + } + + for (i = nestedDepth; i < nestedDepth + this.lookahead; i++) { + lookahead = this.deriveNested(i); + yield this.saveKey(lookahead); + } + + this.nested = nested; + this.nestedDepth = nestedDepth; + } + if (i === -1) return; this.save(); - return receive; + return receive || nested; }); /** @@ -629,13 +675,14 @@ Account.prototype.inspect = function inspect() { address: this.initialized ? this.receive.getAddress() : null, - nestedAddress: this.initialized - ? this.receive.getNestedAddress() + nestedAddress: this.initialized && this.nested + ? this.nested.getAddress() : null, witness: this.witness, accountIndex: this.accountIndex, receiveDepth: this.receiveDepth, changeDepth: this.changeDepth, + nestedDepth: this.nestedDepth, accountKey: this.accountKey.xpubkey, keys: this.keys.map(function(key) { return key.xpubkey; @@ -662,11 +709,12 @@ Account.prototype.toJSON = function toJSON() { accountIndex: this.accountIndex, receiveDepth: this.receiveDepth, changeDepth: this.changeDepth, + nestedDepth: this.nestedDepth, receiveAddress: this.receive ? this.receive.getAddress('base58') : null, - nestedAddress: this.receive - ? this.receive.getNestedAddress('base58') + nestedAddress: this.nested + ? this.nested.getAddress('base58') : null, changeAddress: this.change ? this.change.getAddress('base58') @@ -699,6 +747,7 @@ Account.prototype.fromJSON = function fromJSON(json) { assert(utils.isNumber(json.accountIndex)); assert(utils.isNumber(json.receiveDepth)); assert(utils.isNumber(json.changeDepth)); + assert(utils.isNumber(json.nestedDepth)); assert(Array.isArray(json.keys)); this.wid = json.wid; @@ -711,12 +760,13 @@ Account.prototype.fromJSON = function fromJSON(json) { this.accountIndex = json.accountIndex; this.receiveDepth = json.receiveDepth; this.changeDepth = json.changeDepth; - this.accountKey = bcoin.hd.fromBase58(json.accountKey); + this.nestedDepth = json.nestedDepth; + this.accountKey = HD.fromBase58(json.accountKey); assert(this.type != null); for (i = 0; i < json.keys.length; i++) { - key = bcoin.hd.fromBase58(json.keys[i]); + key = HD.fromBase58(json.keys[i]); this.pushKey(key); } @@ -733,7 +783,7 @@ Account.prototype.toRaw = function toRaw(writer) { var i, key; p.writeU32(this.network.magic); - p.writeVarString(this.name, 'utf8'); + p.writeVarString(this.name, 'ascii'); p.writeU8(this.initialized ? 1 : 0); p.writeU8(this.type); p.writeU8(this.m); @@ -742,6 +792,7 @@ Account.prototype.toRaw = function toRaw(writer) { p.writeU32(this.accountIndex); p.writeU32(this.receiveDepth); p.writeU32(this.changeDepth); + p.writeU32(this.nestedDepth); p.writeBytes(this.accountKey.toRaw()); p.writeU8(this.keys.length); @@ -768,7 +819,7 @@ Account.prototype.fromRaw = function fromRaw(data) { var i, count, key; this.network = bcoin.network.fromMagic(p.readU32()); - this.name = p.readVarString('utf8'); + this.name = p.readVarString('ascii'); this.initialized = p.readU8() === 1; this.type = p.readU8(); this.m = p.readU8(); @@ -777,14 +828,15 @@ Account.prototype.fromRaw = function fromRaw(data) { this.accountIndex = p.readU32(); this.receiveDepth = p.readU32(); this.changeDepth = p.readU32(); - this.accountKey = bcoin.hd.fromRaw(p.readBytes(82)); + this.nestedDepth = p.readU32(); + this.accountKey = HD.fromRaw(p.readBytes(82)); assert(Account.typesByVal[this.type]); count = p.readU8(); for (i = 0; i < count; i++) { - key = bcoin.hd.fromRaw(p.readBytes(82)); + key = HD.fromRaw(p.readBytes(82)); this.pushKey(key); } diff --git a/lib/wallet/path.js b/lib/wallet/path.js index f9c60694..a326e4be 100644 --- a/lib/wallet/path.js +++ b/lib/wallet/path.js @@ -9,10 +9,11 @@ var bcoin = require('../env'); var utils = require('../utils/utils'); var assert = utils.assert; -var constants = bcoin.constants; +var constants = require('../protocol/constants'); var BufferReader = require('../utils/reader'); var BufferWriter = require('../utils/writer'); var Address = require('../primitives/address'); +var Script = require('../script/script'); /** * Path @@ -43,7 +44,7 @@ function Path() { this.data = null; // Currently unused. - this.type = bcoin.script.types.PUBKEYHASH; + this.type = Script.types.PUBKEYHASH; this.version = -1; this.hash = null; // Passed in by caller. } diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index dee978f5..f65c414a 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -7,15 +7,19 @@ 'use strict'; -var bcoin = require('../env'); var utils = require('../utils/utils'); +var Locker = require('../utils/locker'); +var LRU = require('../utils/lru'); var spawn = require('../utils/spawn'); var co = spawn.co; -var assert = bcoin.utils.assert; -var constants = bcoin.constants; -var DUMMY = new Buffer([0]); +var assert = utils.assert; +var constants = require('../protocol/constants'); var BufferReader = require('../utils/reader'); var BufferWriter = require('../utils/writer'); +var TX = require('../primitives/tx'); +var Coin = require('../primitives/coin'); +var Outpoint = require('../primitives/outpoint'); +var DUMMY = new Buffer([0]); /* * Database Layout: @@ -211,8 +215,8 @@ function TXDB(wallet) { this.options = wallet.db.options; this.locked = {}; - this.locker = new bcoin.locker(); - this.coinCache = new bcoin.lru(10000); + this.locker = new Locker(); + this.coinCache = new LRU(10000); this.current = null; this.balance = null; @@ -446,7 +450,7 @@ TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) { inputs = []; while (p.left()) - inputs.push(bcoin.outpoint.fromRaw(p)); + inputs.push(Outpoint.fromRaw(p)); for (i = 0; i < inputs.length; i++) { input = inputs[i]; @@ -558,7 +562,7 @@ TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) { this.del(layout.o(hash, index)); - coin = bcoin.coin.fromTX(tx, index); + coin = Coin.fromTX(tx, index); // Add input to orphan for (i = 0; i < orphans.length; i++) { @@ -677,7 +681,7 @@ TXDB.prototype._add = co(function* add(tx, info) { key = prevout.hash + prevout.index; // s[outpoint-key] -> [spender-hash]|[spender-input-index] - spender = bcoin.outpoint.fromTX(tx, i).toRaw(); + spender = Outpoint.fromTX(tx, i).toRaw(); this.put(layout.s(prevout.hash, prevout.index), spender); // Add orphan, if no parent transaction is yet known @@ -722,7 +726,7 @@ TXDB.prototype._add = co(function* add(tx, info) { if (orphans) continue; - coin = bcoin.coin.fromTX(tx, i); + coin = Coin.fromTX(tx, i); this.balance.add(coin); coin = coin.toRaw(); @@ -873,7 +877,7 @@ TXDB.prototype.isSpent = co(function* isSpent(hash, index) { if (!data) return; - return bcoin.outpoint.fromRaw(data); + return Outpoint.fromRaw(data); }); /** @@ -1103,7 +1107,7 @@ TXDB.prototype.__remove = co(function* remove(tx, info) { if (!path) continue; - coin = bcoin.coin.fromTX(tx, i); + coin = Coin.fromTX(tx, i); this.balance.sub(coin); @@ -1321,7 +1325,7 @@ TXDB.prototype.getLocked = function getLocked() { key = keys[i]; hash = key.slice(0, 64); index = +key.slice(64); - outpoint = new bcoin.outpoint(hash, index); + outpoint = new Outpoint(hash, index); outpoints.push(outpoint); } @@ -1395,10 +1399,10 @@ TXDB.prototype.getOutpoints = function getOutpoints(account) { parse: function(key) { if (account != null) { key = layout.Cc(key); - return new bcoin.outpoint(key[1], key[2]); + return new Outpoint(key[1], key[2]); } key = layout.cc(key); - return new bcoin.outpoint(key[0], key[1]); + return new Outpoint(key[0], key[1]); } }); }; @@ -1563,7 +1567,7 @@ TXDB.prototype.getHistory = function getHistory(account) { return this.values({ gte: layout.t(constants.NULL_HASH), lte: layout.t(constants.HIGH_HASH), - parse: bcoin.tx.fromExtended + parse: TX.fromExtended }); }; @@ -1638,7 +1642,7 @@ TXDB.prototype.getCoins = function getCoins(account) { var parts = layout.cc(key); var hash = parts[0]; var index = parts[1]; - var coin = bcoin.coin.fromRaw(value); + var coin = Coin.fromRaw(value); coin.hash = hash; coin.index = index; key = hash + index; @@ -1691,7 +1695,7 @@ TXDB.prototype.fillHistory = function fillHistory(tx) { lte: layout.d(hash, 0xffffffff), parse: function(key, value) { var index = layout.dd(key)[1]; - var coin = bcoin.coin.fromRaw(value); + var coin = Coin.fromRaw(value); var input = tx.inputs[index]; coin.hash = input.prevout.hash; coin.index = input.prevout.index; @@ -1740,7 +1744,7 @@ TXDB.prototype.getTX = co(function* getTX(hash) { if (!tx) return; - return bcoin.tx.fromExtended(tx); + return TX.fromExtended(tx); }); /** @@ -1817,7 +1821,7 @@ TXDB.prototype.getCoin = co(function* getCoin(hash, index) { var coin; if (data) { - coin = bcoin.coin.fromRaw(data); + coin = Coin.fromRaw(data); coin.hash = hash; coin.index = index; return coin; @@ -1828,7 +1832,7 @@ TXDB.prototype.getCoin = co(function* getCoin(hash, index) { if (!data) return; - coin = bcoin.coin.fromRaw(data); + coin = Coin.fromRaw(data); coin.hash = hash; coin.index = index; @@ -1851,7 +1855,7 @@ TXDB.prototype.getSpentCoin = co(function* getSpentCoin(spent, prevout) { if (!data) return; - coin = bcoin.coin.fromRaw(data); + coin = Coin.fromRaw(data); coin.hash = prevout.hash; coin.index = prevout.index; @@ -1866,7 +1870,7 @@ TXDB.prototype.getSpentCoin = co(function* getSpentCoin(spent, prevout) { */ TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(tx, i) { - var prevout = bcoin.outpoint.fromTX(tx, i); + var prevout = Outpoint.fromTX(tx, i); var spent = yield this.isSpent(prevout.hash, prevout.index); var coin; @@ -2002,7 +2006,7 @@ TXDB.prototype._zap = co(function* zap(account, age) { txs = yield this.getRange(account, { start: 0, - end: bcoin.now() - age + end: utils.now() - age }); for (i = 0; i < txs.length; i++) { diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 7446962c..609fce33 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -9,8 +9,9 @@ var bcoin = require('../env'); var EventEmitter = require('events').EventEmitter; -var constants = bcoin.constants; +var constants = require('../protocol/constants'); var utils = require('../utils/utils'); +var Locker = require('../utils/locker'); var spawn = require('../utils/spawn'); var co = spawn.co; var crypto = require('../crypto/crypto'); @@ -20,7 +21,12 @@ var BufferWriter = require('../utils/writer'); var TXDB = require('./txdb'); var Path = require('./path'); var Address = require('../primitives/address'); +var MTX = require('../primitives/mtx'); var WalletKey = require('./walletkey'); +var HD = require('../hd/hd'); +var Account = require('./account'); +var Input = require('../primitives/input'); +var Output = require('../primitives/output'); /** * BIP44 Wallet @@ -58,8 +64,8 @@ function Wallet(db, options) { this.db = db; this.network = db.network; this.logger = db.logger; - this.writeLock = new bcoin.locker(); - this.fundLock = new bcoin.locker(); + this.writeLock = new Locker(); + this.fundLock = new Locker(); this.wid = 0; this.id = null; @@ -89,12 +95,12 @@ Wallet.prototype.fromOptions = function fromOptions(options) { var id, token; if (!master) - master = bcoin.hd.fromMnemonic(null, this.network); + master = HD.fromMnemonic(null, this.network); - if (!bcoin.hd.isHD(master) && !MasterKey.isMasterKey(master)) - master = bcoin.hd.from(master, this.network); + if (!HD.isHD(master) && !MasterKey.isMasterKey(master)) + master = HD.from(master, this.network); - if (bcoin.hd.isHD(master)) + if (HD.isHD(master)) master = MasterKey.fromKey(master); assert(MasterKey.isMasterKey(master)); @@ -681,7 +687,7 @@ Wallet.prototype.hasAccount = function hasAccount(account) { */ Wallet.prototype.createReceive = function createReceive(account) { - return this.createKey(account, false); + return this.createKey(account, 0); }; /** @@ -691,7 +697,17 @@ Wallet.prototype.createReceive = function createReceive(account) { */ Wallet.prototype.createChange = function createChange(account) { - return this.createKey(account, true); + return this.createKey(account, 1); +}; + +/** + * Create a new nested address (increments receiveDepth). + * @param {(Number|String)?} account + * @returns {Promise} - Returns {@link WalletKey}. + */ + +Wallet.prototype.createNested = function createNested(account) { + return this.createKey(account, 2); }; /** @@ -893,7 +909,7 @@ Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase) if (!account) throw new Error('Account not found.'); - if (account.type !== bcoin.account.types.PUBKEYHASH) + if (account.type !== Account.types.PUBKEYHASH) throw new Error('Cannot import into non-pkh account.'); yield this.unlock(passphrase); @@ -949,7 +965,7 @@ Wallet.prototype.importAddress = co(function* importAddress(account, address) { Wallet.prototype._importAddress = co(function* importAddress(account, address) { var exists, path; - if (account instanceof Address) { + if (!address) { address = account; account = null; } @@ -957,7 +973,7 @@ Wallet.prototype._importAddress = co(function* importAddress(account, address) { if (account == null) account = 0; - exists = yield this.getPath(address.getHash('hex')); + exists = yield this.getPath(address); if (exists) throw new Error('Address already exists.'); @@ -967,7 +983,7 @@ Wallet.prototype._importAddress = co(function* importAddress(account, address) { if (!account) throw new Error('Account not found.'); - if (account.type !== bcoin.account.types.PUBKEYHASH) + if (account.type !== Account.types.PUBKEYHASH) throw new Error('Cannot import into non-pkh account.'); path = Path.fromAddress(account, address); @@ -1093,7 +1109,7 @@ Wallet.prototype.createTX = co(function* createTX(options, force) { throw new Error('No outputs.'); // Create mutable tx - tx = bcoin.mtx(); + tx = new MTX(); // Add the outputs for (i = 0; i < outputs.length; i++) @@ -1255,7 +1271,7 @@ Wallet.prototype.getInputPaths = co(function* getInputPaths(tx) { var hashes = []; var i, hash, path; - if (tx instanceof bcoin.input) { + if (tx instanceof Input) { if (!tx.coin) throw new Error('Not all coins available.'); @@ -1293,7 +1309,7 @@ Wallet.prototype.getOutputPaths = co(function* getOutputPaths(tx) { var hashes = []; var i, hash, path; - if (tx instanceof bcoin.output) { + if (tx instanceof Output) { hash = tx.getHash('hex'); if (hash) hashes.push(hash); @@ -1337,10 +1353,10 @@ Wallet.prototype.syncOutputDepth = co(function* syncOutputDepth(info) { */ Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) { - var receive = []; + var derived = []; var accounts = {}; var i, j, path, paths, account; - var receiveDepth, changeDepth, ring; + var receive, change, nested, ring; this.start(); @@ -1361,43 +1377,52 @@ Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) { for (i = 0; i < accounts.length; i++) { paths = accounts[i]; account = paths[0].account; - receiveDepth = -1; - changeDepth = -1; + receive = -1; + change = -1; + nested = -1; for (j = 0; j < paths.length; j++) { path = paths[j]; - if (path.branch) { - if (path.index > changeDepth) - changeDepth = path.index; - } else { - if (path.index > receiveDepth) - receiveDepth = path.index; + switch (path.branch) { + case 0: + if (path.index > receive) + receive = path.index; + break; + case 1: + if (path.index > change) + change = path.index; + break; + case 2: + if (path.index > nested) + nested = path.index; + break; } } - receiveDepth += 2; - changeDepth += 2; + receive += 2; + change += 2; + nested += 2; account = yield this.getAccount(account); if (!account) continue; - ring = yield account.setDepth(receiveDepth, changeDepth); + ring = yield account.setDepth(receive, change, nested); if (ring) - receive.push(ring); + derived.push(ring); } yield this.commit(); - if (receive.length > 0) { - this.db.emit('address', this.id, receive); - this.emit('address', receive); + if (derived.length > 0) { + this.db.emit('address', this.id, derived); + this.emit('address', derived); } - return receive; + return derived; }); /** @@ -1773,9 +1798,9 @@ Wallet.prototype.getProgram = function getProgram() { */ Wallet.prototype.getNestedHash = function getNestedHash(enc) { - if (!this.receive) + if (!this.nested) return; - return this.receive.getNestedHash(enc); + return this.nested.getHash(enc); }; /** @@ -1786,9 +1811,9 @@ Wallet.prototype.getNestedHash = function getNestedHash(enc) { */ Wallet.prototype.getNestedAddress = function getNestedAddress(enc) { - if (!this.receive) + if (!this.nested) return; - return this.receive.getNestedAddress(enc); + return this.nested.getAddress(enc); }; /** @@ -1903,6 +1928,12 @@ Wallet.prototype.__defineGetter__('changeDepth', function() { return this.account.changeDepth; }); +Wallet.prototype.__defineGetter__('nestedDepth', function() { + if (!this.account) + return -1; + return this.account.nestedDepth; +}); + Wallet.prototype.__defineGetter__('accountKey', function() { if (!this.account) return; @@ -1921,6 +1952,12 @@ Wallet.prototype.__defineGetter__('change', function() { return this.account.change; }); +Wallet.prototype.__defineGetter__('nested', function() { + if (!this.account) + return; + return this.account.nested; +}); + /** * Convert the wallet to a more inspection-friendly object. * @returns {Object} @@ -1997,7 +2034,7 @@ Wallet.prototype.toRaw = function toRaw(writer) { p.writeU32(this.network.magic); p.writeU32(this.wid); - p.writeVarString(this.id, 'utf8'); + p.writeVarString(this.id, 'ascii'); p.writeU8(this.initialized ? 1 : 0); p.writeU32(this.accountDepth); p.writeBytes(this.token); @@ -2020,7 +2057,7 @@ Wallet.prototype.fromRaw = function fromRaw(data) { var p = new BufferReader(data); this.network = bcoin.network.fromMagic(p.readU32()); this.wid = p.readU32(); - this.id = p.readVarString('utf8'); + this.id = p.readVarString('ascii'); this.initialized = p.readU8() === 1; this.accountDepth = p.readU32(); this.token = p.readBytes(32); @@ -2083,7 +2120,7 @@ function MasterKey(options) { this.timer = null; this.until = 0; this._destroy = this.destroy.bind(this); - this.locker = new bcoin.locker(this); + this.locker = new Locker(this); } /** @@ -2111,7 +2148,7 @@ MasterKey.prototype.fromOptions = function fromOptions(options) { } if (options.key) { - assert(bcoin.hd.isHD(options.key)); + assert(HD.isHD(options.key)); this.key = options.key; } @@ -2167,7 +2204,7 @@ MasterKey.prototype._unlock = co(function* _unlock(passphrase, timeout) { key = yield crypto.derive(passphrase); data = crypto.decipher(this.ciphertext, key, this.iv); - this.key = bcoin.hd.fromExtended(data); + this.key = HD.fromExtended(data); this.start(timeout); @@ -2305,7 +2342,7 @@ MasterKey.prototype._decrypt = co(function* decrypt(passphrase) { data = yield crypto.decrypt(this.ciphertext, passphrase, this.iv); - this.key = bcoin.hd.fromExtended(data); + this.key = HD.fromExtended(data); this.encrypted = false; this.iv = null; this.ciphertext = null; @@ -2418,7 +2455,7 @@ MasterKey.prototype.fromRaw = function fromRaw(raw) { return this; } - this.key = bcoin.hd.fromExtended(p.readVarBytes()); + this.key = HD.fromExtended(p.readVarBytes()); return this; }; @@ -2503,7 +2540,7 @@ MasterKey.prototype.fromJSON = function fromJSON(json) { this.iv = new Buffer(json.iv, 'hex'); this.ciphertext = new Buffer(json.ciphertext, 'hex'); } else { - this.key = bcoin.hd.fromJSON(json.key); + this.key = HD.fromJSON(json.key); } return this; diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index c908c5f9..ca941c31 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -11,15 +11,20 @@ var bcoin = require('../env'); var AsyncObject = require('../utils/async'); var utils = require('../utils/utils'); var spawn = require('../utils/spawn'); +var Locker = require('../utils/locker'); +var LRU = require('../utils/lru'); var co = spawn.co; var crypto = require('../crypto/crypto'); var assert = utils.assert; -var constants = bcoin.constants; +var constants = require('../protocol/constants'); var BufferReader = require('../utils/reader'); var BufferWriter = require('../utils/writer'); var Path = require('./path'); var Script = require('../script/script'); -var MAX_POINT = String.fromCharCode(0xdbff, 0xdfff); // U+10FFFF +var Wallet = require('./wallet'); +var Account = require('./account'); +var ldb = require('../db/ldb'); +var Bloom = require('../utils/bloom'); /* * Database Layout: @@ -65,15 +70,15 @@ var layout = { return key.readUInt32BE(1, true); }, l: function(id) { - var len = Buffer.byteLength(id, 'utf8'); + var len = Buffer.byteLength(id, 'ascii'); var key = new Buffer(1 + len); key[0] = 0x6c; if (len > 0) - key.write(id, 1, 'utf8'); + key.write(id, 1, 'ascii'); return key; }, ll: function(key) { - return key.toString('utf8', 1); + return key.toString('ascii', 1); }, a: function a(wid, index) { var key = new Buffer(9); @@ -83,16 +88,16 @@ var layout = { return key; }, i: function i(wid, name) { - var len = Buffer.byteLength(name, 'utf8'); + var len = Buffer.byteLength(name, 'ascii'); var key = new Buffer(5 + len); key[0] = 0x69; key.writeUInt32BE(wid, 1, true); if (len > 0) - key.write(name, 5, 'utf8'); + key.write(name, 5, 'ascii'); return key; }, ii: function ii(key) { - return [key.readUInt32BE(1, true), key.toString('utf8', 5)]; + return [key.readUInt32BE(1, true), key.toString('ascii', 5)]; }, R: new Buffer([0x52]), b: function b(hash) { @@ -147,15 +152,15 @@ function WalletDB(options) { // We need one read lock for `get` and `create`. // It will hold locks specific to wallet ids. - this.readLock = new bcoin.locker.mapped(); - this.writeLock = new bcoin.locker(); - this.txLock = new bcoin.locker(); + this.readLock = new Locker.mapped(); + this.writeLock = new Locker(); + this.txLock = new Locker(); - this.widCache = new bcoin.lru(10000); - this.indexCache = new bcoin.lru(10000); - this.accountCache = new bcoin.lru(10000); - this.pathCache = new bcoin.lru(100000); - this.pathMapCache = new bcoin.lru(100000); + this.widCache = new LRU(10000); + this.indexCache = new LRU(10000); + this.accountCache = new LRU(10000); + this.pathCache = new LRU(100000); + this.pathMapCache = new LRU(100000); // Try to optimize for up to 1m addresses. // We use a regular bloom filter here @@ -164,10 +169,10 @@ function WalletDB(options) { // degrades. // Memory used: 1.7mb this.filter = this.options.useFilter !== false - ? bcoin.bloom.fromRate(1000000, 0.001, -1) + ? Bloom.fromRate(1000000, 0.001, -1) : null; - this.db = bcoin.ldb({ + this.db = ldb({ location: this.options.location, db: this.options.db, maxOpenFiles: this.options.maxFiles, @@ -489,7 +494,7 @@ WalletDB.prototype._get = co(function* get(wid) { if (!data) return; - wallet = bcoin.wallet.fromRaw(this, data); + wallet = Wallet.fromRaw(this, data); this.register(wallet); @@ -679,7 +684,7 @@ WalletDB.prototype._create = co(function* create(options) { if (exists) throw new Error('Wallet already exists.'); - wallet = bcoin.wallet.fromOptions(this, options); + wallet = Wallet.fromOptions(this, options); wallet.wid = this.depth++; this.register(wallet); @@ -760,7 +765,7 @@ WalletDB.prototype._getAccount = co(function* getAccount(wid, index) { if (!data) return; - account = bcoin.account.fromRaw(this, data); + account = Account.fromRaw(this, data); this.accountCache.set(key, account); @@ -778,8 +783,8 @@ WalletDB.prototype.getAccounts = co(function* getAccounts(wid) { var i, items, item, name, index, accounts; items = yield this.db.range({ - gte: layout.i(wid, ''), - lte: layout.i(wid, MAX_POINT) + gte: layout.i(wid, '\x00'), + lte: layout.i(wid, '\xff') }); for (i = 0; i < items.length; i++) { @@ -888,7 +893,7 @@ WalletDB.prototype.createAccount = co(function* createAccount(options) { if (exists) throw new Error('Account already exists.'); - account = bcoin.account.fromOptions(this, options); + account = Account.fromOptions(this, options); yield account.init(); @@ -959,14 +964,9 @@ WalletDB.prototype.getWalletsByHash = co(function* getWalletsByHash(hash) { * @returns {Promise} */ -WalletDB.prototype.saveKey = co(function* saveKey(wid, ring) { - yield this.savePath(wid, ring.toPath()); - - if (!ring.witness) - return; - - yield this.savePath(wid, ring.toNestedPath()); -}); +WalletDB.prototype.saveKey = function saveKey(wid, ring) { + return this.savePath(wid, ring.toPath()); +}; /** * Save a path to the path map. @@ -1143,8 +1143,8 @@ WalletDB.prototype.getWalletPaths = co(function* getWalletPaths(wid) { WalletDB.prototype.getWallets = function getWallets() { return this.db.keys({ - gte: layout.l(''), - lte: layout.l(MAX_POINT), + gte: layout.l('\x00'), + lte: layout.l('\xff'), parse: layout.ll }); }; @@ -1255,7 +1255,7 @@ WalletDB.prototype.resend = co(function* resend() { if (!data) continue; - tx = bcoin.tx.fromExtended(data); + tx = TX.fromExtended(data); this.emit('send', tx); } @@ -1852,7 +1852,7 @@ Details.prototype._insert = function _insert(vector, target, table) { io = vector[i]; member = new DetailsMember(); - if (io instanceof bcoin.input) + if (io.prevout) member.value = io.coin ? io.coin.value : 0; else member.value = io.value; diff --git a/lib/wallet/walletkey.js b/lib/wallet/walletkey.js index fc974b82..c849ef70 100644 --- a/lib/wallet/walletkey.js +++ b/lib/wallet/walletkey.js @@ -129,8 +129,10 @@ WalletKey.prototype.toJSON = function toJSON() { return { network: this.network.type, witness: this.witness, + nested: this.nested, publicKey: this.publicKey.toString('hex'), script: this.script ? this.script.toRaw().toString('hex') : null, + program: this.program ? this.program.toRaw().toString('hex') : null, type: constants.scriptTypesByVal[this.type].toLowerCase(), wid: this.wid, id: this.id, @@ -138,8 +140,7 @@ WalletKey.prototype.toJSON = function toJSON() { account: this.account, branch: this.branch, index: this.index, - address: this.getAddress('base58'), - nestedAddress: this.getNestedAddress('base58') + address: this.getAddress('base58') }; }; @@ -178,6 +179,7 @@ WalletKey.prototype.fromHD = function fromHD(account, key, branch, index) { this.branch = branch; this.index = index; this.witness = account.witness; + this.nested = branch === 2; if (key.privateKey) return this.fromPrivate(key.privateKey, key.network); @@ -266,7 +268,7 @@ WalletKey.isWalletKey = function isWalletKey(obj) { * @returns {Boolean} */ -WalletKey.prototype.toPath = function toPath(nested) { +WalletKey.prototype.toPath = function toPath() { var path = new Path(); path.id = this.id; @@ -286,30 +288,13 @@ WalletKey.prototype.toPath = function toPath(nested) { path.keyType = this.keyType; - if (nested) { - assert(this.witness); - path.version = -1; - path.type = Script.types.SCRIPTHASH; - path.hash = this.getNestedHash('hex'); - } else { - path.version = this.witness ? 0 : -1; - path.type = this.getAddressType(); - path.hash = this.getHash('hex'); - } + path.version = this.getVersion(); + path.type = this.getType(); + path.hash = this.getHash('hex'); return path; }; -/** - * Test whether an object is a WalletKey. - * @param {Object} obj - * @returns {Boolean} - */ - -WalletKey.prototype.toNestedPath = function toNestedPath() { - return this.toPath(true); -}; - /* * Expose */ diff --git a/test/wallet-test.js b/test/wallet-test.js index 9ae12d44..af101b1b 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -507,6 +507,9 @@ describe('Wallet', function() { var flags = bcoin.constants.flags.STANDARD_VERIFY_FLAGS; var options, w1, w2, w3, receive, b58, addr, paddr, utx, send, change; + var rec = bullshitNesting ? 'nested' : 'receive'; + var depth = bullshitNesting ? 'nestedDepth' : 'receiveDepth'; + if (witness) flags |= bcoin.constants.flags.VERIFY_WITNESS; @@ -531,17 +534,21 @@ describe('Wallet', function() { yield w3.addKey(w2.accountKey); // Our p2sh address - b58 = w1.getAddress('base58'); + b58 = w1[rec].getAddress('base58'); addr = bcoin.address.fromBase58(b58); - if (witness) - assert.equal(addr.type, scriptTypes.WITNESSSCRIPTHASH); - else + if (witness) { + if (bullshitNesting) + assert.equal(addr.type, scriptTypes.SCRIPTHASH); + else + assert.equal(addr.type, scriptTypes.WITNESSSCRIPTHASH); + } else { assert.equal(addr.type, scriptTypes.SCRIPTHASH); + } - assert.equal(w1.getAddress('base58'), b58); - assert.equal(w2.getAddress('base58'), b58); - assert.equal(w3.getAddress('base58'), b58); + assert.equal(w1[rec].getAddress('base58'), b58); + assert.equal(w2[rec].getAddress('base58'), b58); + assert.equal(w3[rec].getAddress('base58'), b58); paddr = w1.getNestedAddress('base58'); assert.equal(w1.getNestedAddress('base58'), paddr); @@ -563,20 +570,21 @@ describe('Wallet', function() { utx.ts = 1; utx.height = 1; - assert.equal(w1.receiveDepth, 1); + assert.equal(w1[depth], 1); yield walletdb.addTX(utx); yield walletdb.addTX(utx); yield walletdb.addTX(utx); - assert.equal(w1.receiveDepth, 2); + assert.equal(w1[depth], 2); + assert.equal(w1.changeDepth, 1); - assert(w1.getAddress('base58') !== b58); - b58 = w1.getAddress('base58'); - assert.equal(w1.getAddress('base58'), b58); - assert.equal(w2.getAddress('base58'), b58); - assert.equal(w3.getAddress('base58'), b58); + assert(w1[rec].getAddress('base58') !== b58); + b58 = w1[rec].getAddress('base58'); + assert.equal(w1[rec].getAddress('base58'), b58); + assert.equal(w2[rec].getAddress('base58'), b58); + assert.equal(w3[rec].getAddress('base58'), b58); // Create a tx requiring 2 signatures send = bcoin.mtx(); @@ -609,10 +617,10 @@ describe('Wallet', function() { yield walletdb.addTX(send); yield walletdb.addTX(send); - assert.equal(w1.receiveDepth, 2); + assert.equal(w1[depth], 2); assert.equal(w1.changeDepth, 2); - assert(w1.getAddress('base58') === b58); + assert(w1[rec].getAddress('base58') === b58); assert(w1.change.getAddress('base58') !== change); change = w1.change.getAddress('base58'); assert.equal(w1.change.getAddress('base58'), change);