From 7b3134d7821fc034e3d44bc75d5cb4e1107c7863 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 2 Oct 2016 19:39:35 -0700 Subject: [PATCH] account: hd watchonly support. --- lib/wallet/account.js | 14 +++++++++++++- lib/wallet/wallet.js | 23 ++++++++++++++++++----- test/wallet-test.js | 27 +++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 3d2ea482..675acefa 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -69,6 +69,7 @@ function Account(db, options) { this.n = 1; this.keys = []; this.initialized = false; + this.watchOnly = false; if (options) this.fromOptions(options); @@ -171,6 +172,11 @@ Account.prototype.fromOptions = function fromOptions(options) { this.initialized = options.initialized; } + if (options.watchOnly != null) { + assert(typeof options.watchOnly === 'boolean'); + this.watchOnly = options.watchOnly; + } + if (this.n > 1) this.type = Account.types.MULTISIG; @@ -533,7 +539,7 @@ Account.prototype.deriveKey = function deriveKey(branch, index, master) { assert(typeof branch === 'number'); - if (master && master.key) { + if (master && master.key && !this.watchOnly) { key = master.key.deriveAccount44(this.accountIndex); key = key.derive(branch).derive(index); } else { @@ -668,6 +674,7 @@ Account.prototype.inspect = function inspect() { name: this.name, network: this.network, initialized: this.initialized, + watchOnly: this.watchOnly, type: Account.typesByVal[this.type].toLowerCase(), m: this.m, n: this.n, @@ -701,6 +708,7 @@ Account.prototype.toJSON = function toJSON() { wid: this.wid, name: this.name, initialized: this.initialized, + watchOnly: this.watchOnly, type: Account.typesByVal[this.type].toLowerCase(), m: this.m, n: this.n, @@ -739,6 +747,7 @@ Account.prototype.fromJSON = function fromJSON(json) { assert(utils.isName(json.id), 'Bad wallet ID.'); assert(utils.isName(json.name), 'Bad account name.'); assert(typeof json.initialized === 'boolean'); + assert(typeof json.watchOnly === 'boolean'); assert(typeof json.type === 'string'); assert(utils.isNumber(json.m)); assert(utils.isNumber(json.n)); @@ -752,6 +761,7 @@ Account.prototype.fromJSON = function fromJSON(json) { this.wid = json.wid; this.name = json.name; this.initialized = json.initialized; + this.watchOnly = json.watchOnly; this.type = Account.types[json.type.toUpperCase()]; this.m = json.m; this.n = json.n; @@ -784,6 +794,7 @@ Account.prototype.toRaw = function toRaw(writer) { p.writeU32(this.network.magic); p.writeVarString(this.name, 'ascii'); p.writeU8(this.initialized ? 1 : 0); + p.writeU8(this.watchOnly ? 1 : 0); p.writeU8(this.type); p.writeU8(this.m); p.writeU8(this.n); @@ -820,6 +831,7 @@ Account.prototype.fromRaw = function fromRaw(data) { this.network = Network.fromMagic(p.readU32()); this.name = p.readVarString('ascii'); this.initialized = p.readU8() === 1; + this.watchOnly = p.readU8() === 1; this.type = p.readU8(); this.m = p.readU8(); this.n = p.readU8(); diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 42de1cff..255ae50c 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -96,11 +96,10 @@ Wallet.prototype.fromOptions = function fromOptions(options) { if (!master) master = HD.fromMnemonic(null, this.network); - if (!HD.isHD(master) && !MasterKey.isMasterKey(master)) + if (!HD.isHD(master)) master = HD.from(master, this.network); - if (HD.isHD(master)) - master = MasterKey.fromKey(master); + master = MasterKey.fromKey(master); assert(MasterKey.isMasterKey(master)); @@ -633,6 +632,7 @@ Wallet.prototype._createAccount = co(function* createAccount(options) { var passphrase = options.passphrase; var timeout = options.timeout; var name = options.name; + var watchOnly = options.watchOnly === true; var key, master, account; if (typeof options.account === 'string') @@ -643,7 +643,13 @@ Wallet.prototype._createAccount = co(function* createAccount(options) { master = yield this.unlock(passphrase, timeout); - key = master.deriveAccount44(this.accountDepth); + if (watchOnly && options.accountKey) { + key = options.accountKey; + if (!HD.isHD(key)) + key = HD.from(key, this.network); + } else { + key = master.deriveAccount44(this.accountDepth); + } options = { network: this.network, @@ -656,7 +662,8 @@ Wallet.prototype._createAccount = co(function* createAccount(options) { type: options.type, keys: options.keys, m: options.m, - n: options.n + n: options.n, + watchOnly: watchOnly }; this.start(); @@ -981,6 +988,9 @@ Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase) if (account.type !== Account.types.PUBKEYHASH) throw new Error('Cannot import into non-pkh account.'); + if (!ring.privateKey && !account.watchOnly) + throw new Error('Cannot import pubkey into non-watchonly account.'); + yield this.unlock(passphrase); ring = WalletKey.fromRing(account, ring); @@ -1055,6 +1065,9 @@ Wallet.prototype._importAddress = co(function* importAddress(account, address) { if (account.type !== Account.types.PUBKEYHASH) throw new Error('Cannot import into non-pkh account.'); + if (!account.watchOnly) + throw new Error('Cannot import address into non-watchonly account.'); + path = Path.fromAddress(account, address); this.start(); diff --git a/test/wallet-test.js b/test/wallet-test.js index 659ac0a6..850e0338 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -900,7 +900,7 @@ describe('Wallet', function() { assert.equal(balance.total, 21840); })); - it('should import key', cob(function *() { + it('should import privkey', cob(function *() { var key = bcoin.keyring.generate(); var w = yield walletdb.create({ passphrase: 'test' }); var options, k, t1, t2, tx; @@ -943,16 +943,39 @@ describe('Wallet', function() { ekey = key; })); + it('should import pubkey', cob(function *() { + var priv = bcoin.keyring.generate(); + var key = new bcoin.keyring(priv.publicKey); + var w = yield walletdb.create(); + var options, k, t1, t2, tx; + + yield w.createAccount({ name: 'watchonly', watchOnly: true }); + + yield w.importKey('watchonly', key); + + k = yield w.getPath(key.getHash('hex')); + + assert.equal(k.hash, key.getHash('hex')); + + k = yield w.getKey(key.getHash('hex')); + assert(k); + })); + it('should import address', cob(function *() { var key = bcoin.keyring.generate(); var w = yield walletdb.create(); var options, k, t1, t2, tx; - yield w.importAddress('default', key.getAddress()); + yield w.createAccount({ name: 'watchonly', watchOnly: true }); + + yield w.importAddress('watchonly', key.getAddress()); k = yield w.getPath(key.getHash('hex')); assert.equal(k.hash, key.getHash('hex')); + + k = yield w.getKey(key.getHash('hex')); + assert(!k); })); it('should get details', cob(function *() {