wallet: move watchOnly flag to wallet for safety.

This commit is contained in:
Christopher Jeffrey 2016-10-03 03:03:10 -07:00
parent 25946b8909
commit 2097450b42
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 36 additions and 20 deletions

View File

@ -211,7 +211,7 @@ Account.fromOptions = function fromOptions(db, options) {
* @const {Number} * @const {Number}
*/ */
Account.MAX_LOOKAHEAD = 5; Account.MAX_LOOKAHEAD = 10;
/** /**
* Attempt to intialize the account (generating * Attempt to intialize the account (generating
@ -794,7 +794,6 @@ Account.prototype.toRaw = function toRaw(writer) {
p.writeU32(this.network.magic); p.writeU32(this.network.magic);
p.writeVarString(this.name, 'ascii'); p.writeVarString(this.name, 'ascii');
p.writeU8(this.initialized ? 1 : 0); p.writeU8(this.initialized ? 1 : 0);
p.writeU8(this.watchOnly ? 1 : 0);
p.writeU8(this.type); p.writeU8(this.type);
p.writeU8(this.m); p.writeU8(this.m);
p.writeU8(this.n); p.writeU8(this.n);
@ -831,7 +830,6 @@ Account.prototype.fromRaw = function fromRaw(data) {
this.network = Network.fromMagic(p.readU32()); this.network = Network.fromMagic(p.readU32());
this.name = p.readVarString('ascii'); this.name = p.readVarString('ascii');
this.initialized = p.readU8() === 1; this.initialized = p.readU8() === 1;
this.watchOnly = p.readU8() === 1;
this.type = p.readU8(); this.type = p.readU8();
this.m = p.readU8(); this.m = p.readU8();
this.n = p.readU8(); this.n = p.readU8();

View File

@ -71,6 +71,7 @@ function Wallet(db, options) {
this.id = null; this.id = null;
this.master = null; this.master = null;
this.initialized = false; this.initialized = false;
this.watchOnly = false;
this.accountDepth = 0; this.accountDepth = 0;
this.token = constants.ZERO_HASH; this.token = constants.ZERO_HASH;
this.tokenDepth = 0; this.tokenDepth = 0;
@ -111,6 +112,11 @@ Wallet.prototype.fromOptions = function fromOptions(options) {
this.initialized = options.initialized; this.initialized = options.initialized;
} }
if (options.watchOnly != null) {
assert(typeof options.watchOnly === 'boolean');
this.watchOnly = options.watchOnly;
}
if (options.accountDepth != null) { if (options.accountDepth != null) {
assert(utils.isNumber(options.accountDepth)); assert(utils.isNumber(options.accountDepth));
this.accountDepth = options.accountDepth; this.accountDepth = options.accountDepth;
@ -633,7 +639,6 @@ Wallet.prototype._createAccount = co(function* createAccount(options) {
var passphrase = options.passphrase; var passphrase = options.passphrase;
var timeout = options.timeout; var timeout = options.timeout;
var name = options.name; var name = options.name;
var watchOnly = options.watchOnly === true;
var key, master, account; var key, master, account;
if (typeof options.account === 'string') if (typeof options.account === 'string')
@ -644,7 +649,7 @@ Wallet.prototype._createAccount = co(function* createAccount(options) {
master = yield this.unlock(passphrase, timeout); master = yield this.unlock(passphrase, timeout);
if (watchOnly && options.accountKey) { if (this.watchOnly && options.accountKey) {
key = options.accountKey; key = options.accountKey;
if (!HD.isHD(key)) if (!HD.isHD(key))
key = HD.from(key, this.network); key = HD.from(key, this.network);
@ -664,7 +669,7 @@ Wallet.prototype._createAccount = co(function* createAccount(options) {
keys: options.keys, keys: options.keys,
m: options.m, m: options.m,
n: options.n, n: options.n,
watchOnly: watchOnly watchOnly: this.watchOnly
}; };
this.start(); this.start();
@ -742,6 +747,7 @@ Wallet.prototype.getAccount = co(function* getAccount(account) {
account.wid = this.wid; account.wid = this.wid;
account.id = this.id; account.id = this.id;
account.watchOnly = this.watchOnly;
return account; return account;
}); });
@ -976,6 +982,14 @@ Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase)
if (account == null) if (account == null)
account = 0; account = 0;
if (!this.watchOnly) {
if (!ring.privateKey)
throw new Error('Cannot import pubkey into non watch-only wallet.');
} else {
if (ring.privateKey)
throw new Error('Cannot import privkey into watch-only wallet.');
}
exists = yield this.getPath(ring.getHash('hex')); exists = yield this.getPath(ring.getHash('hex'));
if (exists) if (exists)
@ -989,9 +1003,6 @@ Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase)
if (account.type !== Account.types.PUBKEYHASH) if (account.type !== Account.types.PUBKEYHASH)
throw new Error('Cannot import into non-pkh account.'); 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); yield this.unlock(passphrase);
ring = WalletKey.fromRing(account, ring); ring = WalletKey.fromRing(account, ring);
@ -1053,6 +1064,9 @@ Wallet.prototype._importAddress = co(function* importAddress(account, address) {
if (account == null) if (account == null)
account = 0; account = 0;
if (!this.watchOnly)
throw new Error('Cannot import address into non watch-only wallet.');
exists = yield this.getPath(address); exists = yield this.getPath(address);
if (exists) if (exists)
@ -1066,9 +1080,6 @@ Wallet.prototype._importAddress = co(function* importAddress(account, address) {
if (account.type !== Account.types.PUBKEYHASH) if (account.type !== Account.types.PUBKEYHASH)
throw new Error('Cannot import into non-pkh account.'); 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); path = Path.fromAddress(account, address);
this.start(); this.start();
@ -1132,6 +1143,9 @@ Wallet.prototype._fund = co(function* fund(tx, options) {
if (!this.initialized) if (!this.initialized)
throw new Error('Wallet is not initialized.'); throw new Error('Wallet is not initialized.');
if (this.watchOnly)
throw new Error('Cannot fund from watch-only wallet.');
if (options.account != null) { if (options.account != null) {
account = yield this.getAccount(options.account); account = yield this.getAccount(options.account);
if (!account) if (!account)
@ -1595,6 +1609,9 @@ Wallet.prototype.sign = co(function* sign(tx, options) {
if (typeof options === 'string' || Buffer.isBuffer(options)) if (typeof options === 'string' || Buffer.isBuffer(options))
options = { passphrase: options }; options = { passphrase: options };
if (this.watchOnly)
throw new Error('Cannot sign from a watch-only wallet.');
yield this.unlock(options.passphrase, options.timeout); yield this.unlock(options.passphrase, options.timeout);
rings = yield this.deriveInputs(tx); rings = yield this.deriveInputs(tx);
@ -2073,6 +2090,7 @@ Wallet.prototype.toJSON = function toJSON() {
wid: this.wid, wid: this.wid,
id: this.id, id: this.id,
initialized: this.initialized, initialized: this.initialized,
watchOnly: this.watchOnly,
accountDepth: this.accountDepth, accountDepth: this.accountDepth,
token: this.token.toString('hex'), token: this.token.toString('hex'),
tokenDepth: this.tokenDepth, tokenDepth: this.tokenDepth,
@ -2090,6 +2108,7 @@ Wallet.prototype.toJSON = function toJSON() {
Wallet.prototype.fromJSON = function fromJSON(json) { Wallet.prototype.fromJSON = function fromJSON(json) {
assert(utils.isNumber(json.wid)); assert(utils.isNumber(json.wid));
assert(typeof json.initialized === 'boolean'); assert(typeof json.initialized === 'boolean');
assert(typeof json.watchOnly === 'boolean');
assert(utils.isName(json.id), 'Bad wallet ID.'); assert(utils.isName(json.id), 'Bad wallet ID.');
assert(utils.isNumber(json.accountDepth)); assert(utils.isNumber(json.accountDepth));
assert(typeof json.token === 'string'); assert(typeof json.token === 'string');
@ -2100,6 +2119,7 @@ Wallet.prototype.fromJSON = function fromJSON(json) {
this.wid = json.wid; this.wid = json.wid;
this.id = json.id; this.id = json.id;
this.initialized = json.initialized; this.initialized = json.initialized;
this.watchOnly = json.watchOnly;
this.accountDepth = json.accountDepth; this.accountDepth = json.accountDepth;
this.token = new Buffer(json.token, 'hex'); this.token = new Buffer(json.token, 'hex');
this.master = MasterKey.fromJSON(json.master); this.master = MasterKey.fromJSON(json.master);
@ -2119,6 +2139,7 @@ Wallet.prototype.toRaw = function toRaw(writer) {
p.writeU32(this.wid); p.writeU32(this.wid);
p.writeVarString(this.id, 'ascii'); p.writeVarString(this.id, 'ascii');
p.writeU8(this.initialized ? 1 : 0); p.writeU8(this.initialized ? 1 : 0);
p.writeU8(this.watchOnly ? 1 : 0);
p.writeU32(this.accountDepth); p.writeU32(this.accountDepth);
p.writeBytes(this.token); p.writeBytes(this.token);
p.writeU32(this.tokenDepth); p.writeU32(this.tokenDepth);
@ -2142,6 +2163,7 @@ Wallet.prototype.fromRaw = function fromRaw(data) {
this.wid = p.readU32(); this.wid = p.readU32();
this.id = p.readVarString('ascii'); this.id = p.readVarString('ascii');
this.initialized = p.readU8() === 1; this.initialized = p.readU8() === 1;
this.watchOnly = p.readU8() === 1;
this.accountDepth = p.readU32(); this.accountDepth = p.readU32();
this.token = p.readBytes(32); this.token = p.readBytes(32);
this.tokenDepth = p.readU32(); this.tokenDepth = p.readU32();

View File

@ -946,12 +946,10 @@ describe('Wallet', function() {
it('should import pubkey', cob(function *() { it('should import pubkey', cob(function *() {
var priv = bcoin.keyring.generate(); var priv = bcoin.keyring.generate();
var key = new bcoin.keyring(priv.publicKey); var key = new bcoin.keyring(priv.publicKey);
var w = yield walletdb.create(); var w = yield walletdb.create({ watchOnly: true });
var options, k, t1, t2, tx; var options, k, t1, t2, tx;
yield w.createAccount({ name: 'watchonly', watchOnly: true }); yield w.importKey('default', key);
yield w.importKey('watchonly', key);
k = yield w.getPath(key.getHash('hex')); k = yield w.getPath(key.getHash('hex'));
@ -963,12 +961,10 @@ describe('Wallet', function() {
it('should import address', cob(function *() { it('should import address', cob(function *() {
var key = bcoin.keyring.generate(); var key = bcoin.keyring.generate();
var w = yield walletdb.create(); var w = yield walletdb.create({ watchOnly: true });
var options, k, t1, t2, tx; var options, k, t1, t2, tx;
yield w.createAccount({ name: 'watchonly', watchOnly: true }); yield w.importAddress('default', key.getAddress());
yield w.importAddress('watchonly', key.getAddress());
k = yield w.getPath(key.getHash('hex')); k = yield w.getPath(key.getHash('hex'));