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);
};
/**
* 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
* @exports HDPrivateKey
@ -1223,6 +1243,46 @@ HDPrivateKey.prototype.equal = function equal(obj) {
&& 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.
* @private
@ -1838,6 +1898,46 @@ HDPublicKey.prototype.equal = function equal(obj) {
&& 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.
* @returns {Object}

View File

@ -1134,10 +1134,10 @@ RPC.prototype.getrawmempool = function getrawmempool(args, callback) {
if (args.length > 0)
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 i, hashes, hash, entry;
@ -3239,7 +3239,6 @@ RPC.prototype._listReceived = function _listReceived(minconf, empty, account, ca
for (i = 0; i < paths.length; i++) {
path = paths[i];
hash = new Buffer(path.hash, 'hex');
map[path.hash] = {
involvesWatchonly: false,
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) {
assert(Buffer.isBuffer(key));
if (utils.indexOf(this.keys, key) !== -1)
return;
utils.binaryInsert(this.keys, key, utils.cmp, true);
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.');
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.
*/
KeyRing.prototype.scriptInputs = function scriptInputs(tx, index) {
KeyRing.prototype.scriptInputs = function scriptInputs(tx) {
var total = 0;
var i, input;
if (index && typeof index === 'object')
index = tx.inputs.indexOf(index);
for (i = 0; i < tx.inputs.length; 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))
total++;
}
@ -643,32 +629,16 @@ KeyRing.prototype.scriptInputs = function scriptInputs(tx, index) {
* to build/sign inputs that are redeemable by this address.
* @param {MTX} tx
* @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.
*/
KeyRing.prototype.sign = function sign(tx, key, index, type) {
KeyRing.prototype.sign = function sign(tx, key) {
var total = 0;
var i, input;
if (index && typeof index === 'object')
index = tx.inputs.indexOf(index);
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (index != null && index !== i)
continue;
if (!input.coin)
continue;
if (!this.ownOutput(input.coin))
continue;
if (tx.sign(i, this, key, type))
if (tx.sign(i, this, key))
total++;
}

View File

@ -220,15 +220,13 @@ MTX.prototype.addOutput = function addOutput(options, value) {
MTX.prototype.scriptInput = function scriptInput(index, ring) {
var input, prev, n, i, vector, redeemScript, witnessScript;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
// Get the input
input = this.inputs[index];
assert(input);
// 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
// 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) {
var hash;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
if (type == null)
type = 'all';
type = constants.hashType.ALL;
if (typeof type === 'string')
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 len, m, n, keys, vector, version;
if (typeof index !== 'number')
index = this.inputs.indexOf(index);
// Get the input
input = this.inputs[index];
assert(input);
// 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
prev = input.coin.script;
@ -712,12 +712,7 @@ MTX.prototype.isSigned = function isSigned() {
*/
MTX.prototype.sign = function sign(index, ring, key, type) {
var input;
if (index && typeof index === 'object')
index = this.inputs.indexOf(index);
input = this.inputs[index];
var input = this.inputs[index];
assert(input);
// Build script for input

View File

@ -1393,10 +1393,10 @@ TXDB.prototype.filterLocked = function filterLocked(coins) {
for (i = 0; i < coins.length; i++) {
coin = coins[i];
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
*/
utils.binaryInsert = function binaryInsert(items, item, compare) {
utils.binaryInsert = function binaryInsert(items, item, compare, uniq) {
var i = utils.binarySearch(items, item, compare, true);
if (uniq && i < items.length) {
if (compare(items[i], item) === 0)
return -1;
}
if (i === 0)
items.unshift(item);
else if (i === items.length)
items.push(item);
else
items.splice(i, 0, item);
return i;
};

View File

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

View File

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