wallet: multiple refactors.

This commit is contained in:
Christopher Jeffrey 2016-08-18 05:22:41 -07:00
parent 60bd3e751e
commit e8f2c3321c
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 204 additions and 163 deletions

View File

@ -821,6 +821,26 @@ HD.isHD = function isHD(obj) {
|| HDPublicKey.isHDPublicKey(obj); || HDPublicKey.isHDPublicKey(obj);
}; };
/**
* Test whether an object is an HD private key.
* @param {Object} obj
* @returns {Boolean}
*/
HD.isPrivate = function isPrivate(obj) {
return HDPrivateKey.isHDPrivateKey(obj);
};
/**
* Test whether an object is an HD public key.
* @param {Object} obj
* @returns {Boolean}
*/
HD.isPublic = function isPublic(obj) {
return HDPublicKey.isHDPublicKey(obj);
};
/** /**
* HDPrivateKey * HDPrivateKey
* @exports HDPrivateKey * @exports HDPrivateKey
@ -1223,6 +1243,46 @@ HDPrivateKey.prototype.equal = function equal(obj) {
&& utils.equal(this.privateKey, obj.privateKey); && utils.equal(this.privateKey, obj.privateKey);
}; };
/**
* Compare a key against an object.
* @param {Object} obj
* @returns {Boolean}
*/
HDPrivateKey.prototype.compare = function compare(key) {
var cmp;
if (!HDPrivateKey.isHDPrivateKey(key))
return 1;
cmp = this.depth - key.depth;
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.parentFingerPrint, key.parentFingerPrint);
if (cmp !== 0)
return cmp;
cmp = this.childIndex - key.childIndex;
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.chainCode, key.chainCode);
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.privateKey, key.privateKey);
if (cmp !== 0)
return cmp;
return 0;
};
/** /**
* Inject properties from seed. * Inject properties from seed.
* @private * @private
@ -1838,6 +1898,46 @@ HDPublicKey.prototype.equal = function equal(obj) {
&& utils.equal(this.publicKey, obj.publicKey); && utils.equal(this.publicKey, obj.publicKey);
}; };
/**
* Compare a key against an object.
* @param {Object} obj
* @returns {Boolean}
*/
HDPublicKey.prototype.compare = function compare(key) {
var cmp;
if (!HDPublicKey.isHDPublicKey(key))
return 1;
cmp = this.depth - key.depth;
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.parentFingerPrint, key.parentFingerPrint);
if (cmp !== 0)
return cmp;
cmp = this.childIndex - key.childIndex;
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.chainCode, key.chainCode);
if (cmp !== 0)
return cmp;
cmp = utils.cmp(this.publicKey, key.publicKey);
if (cmp !== 0)
return cmp;
return 0;
};
/** /**
* Convert key to a more json-friendly object. * Convert key to a more json-friendly object.
* @returns {Object} * @returns {Object}

View File

@ -1134,10 +1134,10 @@ RPC.prototype.getrawmempool = function getrawmempool(args, callback) {
if (args.length > 0) if (args.length > 0)
verbose = toBool(args[0], false); verbose = toBool(args[0], false);
this.mempoolToJSON(verbose, callback); this._mempoolToJSON(verbose, callback);
}; };
RPC.prototype.mempoolToJSON = function mempoolToJSON(verbose, callback) { RPC.prototype._mempoolToJSON = function _mempoolToJSON(verbose, callback) {
var out = {}; var out = {};
var i, hashes, hash, entry; var i, hashes, hash, entry;
@ -3239,7 +3239,6 @@ RPC.prototype._listReceived = function _listReceived(minconf, empty, account, ca
for (i = 0; i < paths.length; i++) { for (i = 0; i < paths.length; i++) {
path = paths[i]; path = paths[i];
hash = new Buffer(path.hash, 'hex');
map[path.hash] = { map[path.hash] = {
involvesWatchonly: false, involvesWatchonly: false,
address: path.toAddress().toBase58(self.network), address: path.toAddress().toBase58(self.network),

View File

@ -246,13 +246,12 @@ KeyRing.fromAccount = function fromAccount(account, key, keys, change, index) {
KeyRing.prototype.addKey = function addKey(key) { KeyRing.prototype.addKey = function addKey(key) {
assert(Buffer.isBuffer(key)); assert(Buffer.isBuffer(key));
if (utils.indexOf(this.keys, key) !== -1) utils.binaryInsert(this.keys, key, utils.cmp, true);
return;
if (this.keys.length === this.n) if (this.keys.length > this.n) {
utils.binaryRemove(this.keys, key, utils.cmp);
throw new Error('Cannot add more keys.'); throw new Error('Cannot add more keys.');
}
utils.binaryInsert(this.keys, key, utils.cmp);
}; };
/** /**
@ -612,25 +611,12 @@ KeyRing.prototype.ownOutput = function ownOutput(tx, index) {
* @returns {Number} Total number of scripts built. * @returns {Number} Total number of scripts built.
*/ */
KeyRing.prototype.scriptInputs = function scriptInputs(tx, index) { KeyRing.prototype.scriptInputs = function scriptInputs(tx) {
var total = 0; var total = 0;
var i, input; var i, input;
if (index && typeof index === 'object')
index = tx.inputs.indexOf(index);
for (i = 0; i < tx.inputs.length; i++) { for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i]; input = tx.inputs[i];
if (index != null && index !== i)
continue;
if (!input.coin)
continue;
if (!this.ownOutput(input.coin))
continue;
if (tx.scriptInput(i, this)) if (tx.scriptInput(i, this))
total++; total++;
} }
@ -643,32 +629,16 @@ KeyRing.prototype.scriptInputs = function scriptInputs(tx, index) {
* to build/sign inputs that are redeemable by this address. * to build/sign inputs that are redeemable by this address.
* @param {MTX} tx * @param {MTX} tx
* @param {HDPrivateKey|KeyPair|Buffer} key - Private key. * @param {HDPrivateKey|KeyPair|Buffer} key - Private key.
* @param {Number?} index - Index of input. If not present,
* it will attempt to build and sign all redeemable inputs.
* @param {SighashType?} type
* @returns {Number} Total number of inputs scripts built and signed. * @returns {Number} Total number of inputs scripts built and signed.
*/ */
KeyRing.prototype.sign = function sign(tx, key, index, type) { KeyRing.prototype.sign = function sign(tx, key) {
var total = 0; var total = 0;
var i, input; var i, input;
if (index && typeof index === 'object')
index = tx.inputs.indexOf(index);
for (i = 0; i < tx.inputs.length; i++) { for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i]; input = tx.inputs[i];
if (tx.sign(i, this, key))
if (index != null && index !== i)
continue;
if (!input.coin)
continue;
if (!this.ownOutput(input.coin))
continue;
if (tx.sign(i, this, key, type))
total++; total++;
} }

View File

@ -220,15 +220,13 @@ MTX.prototype.addOutput = function addOutput(options, value) {
MTX.prototype.scriptInput = function scriptInput(index, ring) { MTX.prototype.scriptInput = function scriptInput(index, ring) {
var input, prev, n, i, vector, redeemScript, witnessScript; var input, prev, n, i, vector, redeemScript, witnessScript;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
// Get the input // Get the input
input = this.inputs[index]; input = this.inputs[index];
assert(input); assert(input);
// We should have previous outputs by now. // We should have previous outputs by now.
assert(input.coin, 'Coins are not available for scripting.'); if (!input.coin)
return false;
// Optimization: Don't bother with any below // Optimization: Don't bother with any below
// calculation if the output is already templated. // calculation if the output is already templated.
@ -391,11 +389,8 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
MTX.prototype.createSignature = function createSignature(index, prev, key, type, version) { MTX.prototype.createSignature = function createSignature(index, prev, key, type, version) {
var hash; var hash;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
if (type == null) if (type == null)
type = 'all'; type = constants.hashType.ALL;
if (typeof type === 'string') if (typeof type === 'string')
type = constants.hashType[type.toUpperCase()]; type = constants.hashType[type.toUpperCase()];
@ -423,15 +418,20 @@ MTX.prototype.signInput = function signInput(index, ring, key, type) {
var input, prev, signature, keyIndex, signatures, i; var input, prev, signature, keyIndex, signatures, i;
var len, m, n, keys, vector, version; var len, m, n, keys, vector, version;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
// Get the input // Get the input
input = this.inputs[index]; input = this.inputs[index];
assert(input); assert(input);
// We should have previous outputs by now. // We should have previous outputs by now.
assert(input.coin, 'Coins are not available for signing.'); if (!input.coin)
return false;
// Optimization: test output against the
// address map to avoid unnecessary calculation.
// A hash table lookup may be faster than all
// the nonsense below.
if (!ring.ownOutput(input.coin))
return false;
// Get the previous output's script // Get the previous output's script
prev = input.coin.script; prev = input.coin.script;
@ -712,12 +712,7 @@ MTX.prototype.isSigned = function isSigned() {
*/ */
MTX.prototype.sign = function sign(index, ring, key, type) { MTX.prototype.sign = function sign(index, ring, key, type) {
var input; var input = this.inputs[index];
if (index && typeof index === 'object')
index = this.inputs.indexOf(index);
input = this.inputs[index];
assert(input); assert(input);
// Build script for input // Build script for input

View File

@ -1393,10 +1393,10 @@ TXDB.prototype.filterLocked = function filterLocked(coins) {
for (i = 0; i < coins.length; i++) { for (i = 0; i < coins.length; i++) {
coin = coins[i]; coin = coins[i];
if (!this.isLocked(coin)) if (!this.isLocked(coin))
out.push(coins); out.push(coin);
} }
return coins; return out;
}; };
/** /**

View File

@ -2256,14 +2256,21 @@ utils.binarySearch = function binarySearch(items, key, compare, insert) {
* @returns {Number} index * @returns {Number} index
*/ */
utils.binaryInsert = function binaryInsert(items, item, compare) { utils.binaryInsert = function binaryInsert(items, item, compare, uniq) {
var i = utils.binarySearch(items, item, compare, true); var i = utils.binarySearch(items, item, compare, true);
if (uniq && i < items.length) {
if (compare(items[i], item) === 0)
return -1;
}
if (i === 0) if (i === 0)
items.unshift(item); items.unshift(item);
else if (i === items.length) else if (i === items.length)
items.push(item); items.push(item);
else else
items.splice(i, 0, item); items.splice(i, 0, item);
return i; return i;
}; };

View File

@ -1360,8 +1360,6 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) {
* sign, only creates signature slots). Only builds scripts * sign, only creates signature slots). Only builds scripts
* for inputs that are redeemable by this wallet. * for inputs that are redeemable by this wallet.
* @param {MTX} tx * @param {MTX} tx
* @param {Number?} index - Index of input. If not present,
* it will attempt to sign all redeemable inputs.
* @param {Function} callback - Returns [Error, Number] * @param {Function} callback - Returns [Error, Number]
* (total number of scripts built). * (total number of scripts built).
*/ */
@ -1386,16 +1384,13 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, callback) {
* to build/sign inputs that are redeemable by this wallet. * to build/sign inputs that are redeemable by this wallet.
* @param {MTX} tx * @param {MTX} tx
* @param {Object|String|Buffer} options - Options or passphrase. * @param {Object|String|Buffer} options - Options or passphrase.
* @param {Number?} options.index - Index of input. If not present,
* it will attempt to build and sign all redeemable inputs.
* @param {SighashType?} options.type
* @param {Function} callback - Returns [Error, Number] (total number * @param {Function} callback - Returns [Error, Number] (total number
* of inputs scripts built and signed). * of inputs scripts built and signed).
*/ */
Wallet.prototype.sign = function sign(tx, options, callback) { Wallet.prototype.sign = function sign(tx, options, callback) {
var self = this; var self = this;
var passphrase, timeout, index, type; var passphrase, timeout;
if (typeof options === 'function') { if (typeof options === 'function') {
callback = options; callback = options;
@ -1407,8 +1402,6 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
passphrase = options.passphrase; passphrase = options.passphrase;
timeout = options.timeout; timeout = options.timeout;
index = options.index;
type = options.type;
this.deriveInputs(tx, function(err, rings) { this.deriveInputs(tx, function(err, rings) {
if (err) if (err)
@ -1418,34 +1411,11 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
if (err) if (err)
return callback(err); return callback(err);
self._sign(rings, master, tx, index, type, callback); self.signAsync(rings, master, tx, callback);
}); });
}); });
}; };
/**
* Sign a transaction.
* @param {KeyRing[]} rings
* @param {HDPrivateKey} master
* @param {MTX} tx
* @param {Number?} index
* @param {SighashType?} type
*/
Wallet.sign = function sign(rings, master, tx, index, type) {
var total = 0;
var i, ring, key;
for (i = 0; i < rings.length; i++) {
ring = rings[i];
key = ring.derive(master);
assert(utils.equal(key.getPublicKey(), ring.key));
total += ring.sign(tx, key, index, type);
}
return total;
};
/** /**
* Sign a transaction asynchronously. * Sign a transaction asynchronously.
* @param {KeyRing[]} rings * @param {KeyRing[]} rings
@ -1457,20 +1427,43 @@ Wallet.sign = function sign(rings, master, tx, index, type) {
* of inputs scripts built and signed). * of inputs scripts built and signed).
*/ */
Wallet.prototype._sign = function _sign(rings, master, tx, index, type, callback) { Wallet.prototype.signAsync = function signAsync(rings, master, tx, callback) {
var result; var result;
if (!this.workerPool) { if (!this.workerPool) {
callback = utils.asyncify(callback); callback = utils.asyncify(callback);
try { try {
result = Wallet.sign(rings, master, tx, index, type); result = Wallet.sign(rings, master, tx);
} catch (e) { } catch (e) {
return callback(e); return callback(e);
} }
return callback(null, result); return callback(null, result);
} }
this.workerPool.sign(rings, master, tx, index, type, callback); this.workerPool.sign(rings, master, tx, callback);
};
/**
* Sign a transaction.
* @param {KeyRing[]} rings
* @param {HDPrivateKey} master
* @param {MTX} tx
* @param {Number?} index
* @param {SighashType?} type
*/
Wallet.sign = function sign(rings, master, tx) {
var total = 0;
var i, ring, key;
for (i = 0; i < rings.length; i++) {
ring = rings[i];
key = ring.derive(master);
assert(utils.equal(key.getPublicKey(), ring.key));
total += ring.sign(tx, key);
}
return total;
}; };
/** /**
@ -2216,8 +2209,6 @@ Account.prototype.fromOptions = function fromOptions(options) {
if (!this.name) if (!this.name)
this.name = this.accountIndex + ''; this.name = this.accountIndex + '';
this.pushKey(this.accountKey);
if (options.keys) { if (options.keys) {
assert(Array.isArray(options.keys)); assert(Array.isArray(options.keys));
for (i = 0; i < options.keys.length; i++) for (i = 0; i < options.keys.length; i++)
@ -2255,7 +2246,7 @@ Account.MAX_LOOKAHEAD = 5;
Account.prototype.init = function init(callback) { Account.prototype.init = function init(callback) {
// Waiting for more keys. // Waiting for more keys.
if (this.keys.length !== this.n) { if (this.keys.length !== this.n - 1) {
assert(!this.initialized); assert(!this.initialized);
this.save(); this.save();
return callback(); return callback();
@ -2292,40 +2283,29 @@ Account.prototype.open = function open(callback) {
*/ */
Account.prototype.pushKey = function pushKey(key) { Account.prototype.pushKey = function pushKey(key) {
var index = -1; var index;
var i;
assert(key, 'Key required.');
if (key.accountKey)
key = key.accountKey;
if (bcoin.hd.isExtended(key)) if (bcoin.hd.isExtended(key))
key = bcoin.hd.fromBase58(key); key = bcoin.hd.fromBase58(key);
if (key.hdPublicKey) if (!bcoin.hd.isPublic(key))
key = key.hdPublicKey;
if (!bcoin.hd.isHD(key))
throw new Error('Must add HD keys to wallet.'); throw new Error('Must add HD keys to wallet.');
if (!key.isAccount44()) if (!key.isAccount44())
throw new Error('Must add HD account keys to BIP44 wallet.'); throw new Error('Must add HD account keys to BIP44 wallet.');
for (i = 0; i < this.keys.length; i++) { if (key.equal(this.accountKey))
if (this.keys[i].equal(key)) { throw new Error('Cannot add own key.');
index = i;
break;
}
}
if (index !== -1) index = utils.binaryInsert(this.keys, key, cmp, true);
if (index === -1)
return false; return false;
if (this.keys.length === this.n) if (this.keys.length > this.n - 1) {
utils.binaryRemove(this.keys, key, cmp);
throw new Error('Cannot add more keys.'); throw new Error('Cannot add more keys.');
}
this.keys.push(key);
return true; return true;
}; };
@ -2339,42 +2319,22 @@ Account.prototype.pushKey = function pushKey(key) {
*/ */
Account.prototype.spliceKey = function spliceKey(key) { Account.prototype.spliceKey = function spliceKey(key) {
var index = -1;
var i;
assert(key, 'Key required.');
if (key.accountKey)
key = key.accountKey;
if (bcoin.hd.isExtended(key)) if (bcoin.hd.isExtended(key))
key = bcoin.hd.fromBase58(key); key = bcoin.hd.fromBase58(key);
if (key.hdPublicKey) if (!bcoin.hd.isHDPublicKey(key))
key = key.hdPublicKey;
if (!bcoin.hd.isHD(key))
throw new Error('Must add HD keys to wallet.'); throw new Error('Must add HD keys to wallet.');
if (!key.isAccount44()) if (!key.isAccount44())
throw new Error('Must add HD account keys to BIP44 wallet.'); throw new Error('Must add HD account keys to BIP44 wallet.');
for (i = 0; i < this.keys.length; i++) { if (key.equal(this.accountKey))
if (this.keys[i].equal(key)) { throw new Error('Cannot remove own key.');
index = i;
break;
}
}
if (index === -1) if (this.keys.length === this.n - 1)
return false;
if (this.keys.length === this.n)
throw new Error('Cannot remove key.'); throw new Error('Cannot remove key.');
this.keys.splice(index, 1); return utils.binaryRemove(this.keys, key, cmp);
return true;
}; };
/** /**
@ -2426,7 +2386,7 @@ Account.prototype._checkKeys = function _checkKeys(callback) {
if (this.initialized || this.type !== keyTypes.MULTISIG) if (this.initialized || this.type !== keyTypes.MULTISIG)
return callback(null, false); return callback(null, false);
if (this.keys.length !== this.n) if (this.keys.length !== this.n - 1)
return callback(null, false); return callback(null, false);
ring = this.deriveReceive(0); ring = this.deriveReceive(0);
@ -2708,7 +2668,7 @@ Account.prototype.toJSON = function toJSON() {
*/ */
Account.prototype.fromJSON = function fromJSON(json) { Account.prototype.fromJSON = function fromJSON(json) {
var i; var i, key;
assert.equal(json.network, this.network.type); assert.equal(json.network, this.network.type);
assert(utils.isNumber(json.wid)); assert(utils.isNumber(json.wid));
@ -2738,8 +2698,10 @@ Account.prototype.fromJSON = function fromJSON(json) {
assert(this.type != null); assert(this.type != null);
for (i = 0; i < json.keys.length; i++) for (i = 0; i < json.keys.length; i++) {
this.keys.push(bcoin.hd.fromBase58(json.keys[i])); key = bcoin.hd.fromBase58(json.keys[i]);
this.pushKey(key);
}
return this; return this;
}; };
@ -2751,7 +2713,7 @@ Account.prototype.fromJSON = function fromJSON(json) {
Account.prototype.toRaw = function toRaw(writer) { Account.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer); var p = new BufferWriter(writer);
var i; var i, key;
p.writeU32(this.network.magic); p.writeU32(this.network.magic);
p.writeVarString(this.name, 'utf8'); p.writeVarString(this.name, 'utf8');
@ -2766,8 +2728,10 @@ Account.prototype.toRaw = function toRaw(writer) {
p.writeBytes(this.accountKey.toRaw()); p.writeBytes(this.accountKey.toRaw());
p.writeU8(this.keys.length); p.writeU8(this.keys.length);
for (i = 0; i < this.keys.length; i++) for (i = 0; i < this.keys.length; i++) {
p.writeBytes(this.keys[i].toRaw()); key = this.keys[i];
p.writeBytes(key.toRaw());
}
if (!writer) if (!writer)
p = p.render(); p = p.render();
@ -2784,7 +2748,7 @@ Account.prototype.toRaw = function toRaw(writer) {
Account.prototype.fromRaw = function fromRaw(data) { Account.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data); var p = new BufferReader(data);
var i, count; var i, count, key;
this.network = bcoin.network.fromMagic(p.readU32()); this.network = bcoin.network.fromMagic(p.readU32());
this.name = p.readVarString('utf8'); this.name = p.readVarString('utf8');
@ -2802,8 +2766,10 @@ Account.prototype.fromRaw = function fromRaw(data) {
count = p.readU8(); count = p.readU8();
for (i = 0; i < count; i++) for (i = 0; i < count; i++) {
this.keys.push(bcoin.hd.fromRaw(p.readBytes(82))); key = bcoin.hd.fromRaw(p.readBytes(82));
this.pushKey(key);
}
return this; return this;
}; };
@ -3279,6 +3245,14 @@ MasterKey.isMasterKey = function isMasterKey(obj) {
&& typeof obj.decrypt === 'function'; && typeof obj.decrypt === 'function';
}; };
/*
* Helpers
*/
function cmp(key1, key2) {
return key1.compare(key2);
}
/* /*
* Expose * Expose
*/ */

View File

@ -245,13 +245,11 @@ Workers.prototype.verify = function verify(tx, flags, callback) {
* @param {KeyRing[]} rings * @param {KeyRing[]} rings
* @param {HDPrivateKey} master * @param {HDPrivateKey} master
* @param {MTX} tx * @param {MTX} tx
* @param {Number?} index
* @param {SighashType?} type
* @param {Function} callback * @param {Function} callback
*/ */
Workers.prototype.sign = function sign(rings, master, tx, index, type, callback) { Workers.prototype.sign = function sign(rings, master, tx, callback) {
var args = [rings, master, tx, index, type]; var args = [rings, master, tx];
var i, input, sig, sigs, total; var i, input, sig, sigs, total;
this.execute('sign', args, -1, function(err, result) { this.execute('sign', args, -1, function(err, result) {
@ -775,12 +773,10 @@ jobs.verify = function verify(tx, flags) {
* @param {KeyRing[]} rings * @param {KeyRing[]} rings
* @param {HDPrivateKey} master * @param {HDPrivateKey} master
* @param {MTX} tx * @param {MTX} tx
* @param {Number?} index
* @param {SighashType?} type
*/ */
jobs.sign = function sign(rings, master, tx, index, type) { jobs.sign = function sign(rings, master, tx) {
var total = bcoin.wallet.sign(rings, master, tx, index, type); var total = bcoin.wallet.sign(rings, master, tx);
var sigs = []; var sigs = [];
var i, input; var i, input;