mtx/wallet: refactor keyring.

This commit is contained in:
Christopher Jeffrey 2016-08-19 00:39:56 -07:00
parent 0f23304a68
commit c836786b99
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 572 additions and 531 deletions

View File

@ -278,9 +278,6 @@ ec.rand = function rand(min, max) {
ec.verify = function verify(msg, sig, key, historical, high) { ec.verify = function verify(msg, sig, key, historical, high) {
var result; var result;
if (key.getPublicKey)
key = key.getPublicKey();
assert(Buffer.isBuffer(msg)); assert(Buffer.isBuffer(msg));
assert(Buffer.isBuffer(sig)); assert(Buffer.isBuffer(sig));
assert(Buffer.isBuffer(key)); assert(Buffer.isBuffer(key));
@ -375,8 +372,8 @@ ec.privateKeyVerify = function privateKeyVerify(key) {
ec.sign = function sign(msg, key) { ec.sign = function sign(msg, key) {
var sig; var sig;
if (key.getPrivateKey) assert(Buffer.isBuffer(msg));
key = key.getPrivateKey(); assert(Buffer.isBuffer(key));
if (secp256k1) { if (secp256k1) {
// Sign message // Sign message

View File

@ -2028,7 +2028,7 @@ RPC.prototype.signrawtransaction = function signrawtransaction(args, callback) {
RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, args, callback) { RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, args, callback) {
var keys = []; var keys = [];
var keyMap = {}; var keyMap = {};
var k, i, secret, key, addr; var k, i, secret, key;
var coins, prevout, prev; var coins, prevout, prev;
var hash, index, script, value; var hash, index, script, value;
var redeem, op, j; var redeem, op, j;
@ -2038,12 +2038,12 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg
k = args[2]; k = args[2];
for (i = 0; i < k.length; i++) { for (i = 0; i < k.length; i++) {
secret = k[i]; secret = k[i];
if (!utils.isBase58(secret)) if (!utils.isBase58(secret))
return callback(new RPCError('Invalid parameter')); return callback(new RPCError('Invalid parameter'));
key = bcoin.keypair.fromSecret(secret);
addr = new bcoin.keyring({ publicKey: key.getPublicKey() }); key = bcoin.keyring.fromSecret(secret);
key = { addr: addr, key: key.getPrivateKey() }; keyMap[key.getPublicKey('hex')] = key;
keyMap[addr.getPublicKey('hex')] = key;
keys.push(key); keys.push(key);
} }
} }
@ -2051,14 +2051,18 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg
coins = []; coins = [];
if (args.length > 1 && Array.isArray(args[1])) { if (args.length > 1 && Array.isArray(args[1])) {
prevout = args[1]; prevout = args[1];
for (i = 0; i < prevout.length; i++) { for (i = 0; i < prevout.length; i++) {
prev = prevout[i]; prev = prevout[i];
if (!prev) if (!prev)
return callback(new RPCError('Invalid parameter')); return callback(new RPCError('Invalid parameter'));
hash = toHash(prev.txid); hash = toHash(prev.txid);
index = prev.vout; index = prev.vout;
script = prev.scriptPubKey; script = prev.scriptPubKey;
value = toSatoshi(prev.amount); value = toSatoshi(prev.amount);
if (!hash if (!hash
|| !utils.isNumber(index) || !utils.isNumber(index)
|| index < 0 || index < 0
@ -2081,22 +2085,22 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg
if (script.isScripthash() || script.isWitnessScripthash()) { if (script.isScripthash() || script.isWitnessScripthash()) {
redeem = bcoin.script.fromRaw(prev.redeemScript, 'hex'); redeem = bcoin.script.fromRaw(prev.redeemScript, 'hex');
if (!redeem.isMultisig()) for (j = 0; j < redeem.length; j++) {
continue;
for (j = 1; j < redeem.length - 2; j++) {
op = redeem.get(j); op = redeem.get(j);
if (!Buffer.isBuffer(op))
continue;
key = keyMap[op.toString('hex')]; key = keyMap[op.toString('hex')];
if (key) { if (key) {
key.addr.type = bcoin.keyring.types.MULTISIG; key.script = redeem;
key.addr.m = redeem.getSmall(0); key.witness = script.isWitnessScripthash();
key.addr.n = redeem.getSmall(redeem.length - 1);
key.addr.keys = redeem.slice(1, -2);
key.addr.witness = script.isWitnessScripthash();
break; break;
} }
} }
} }
} }
tx.fillCoins(coins); tx.fillCoins(coins);
} }
@ -2117,17 +2121,14 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg
for (i = 0; i < keys.length; i++) { for (i = 0; i < keys.length; i++) {
key = keys[i]; key = keys[i];
key.addr.sign(merged, key.key, null, type); merged.sign(key, type);
} }
this.wallet.sign(merged, { type: type }, function(err) { this.wallet.sign(merged, { type: type }, function(err) {
if (err) if (err)
return callback(err); return callback(err);
for (i = 1; i < txs.length; i++) { // TODO: Merge with other txs here.
tx = txs[i];
mergeSigs(merged, tx);
}
callback(null, { callback(null, {
hex: merged.toRaw().toString('hex'), hex: merged.toRaw().toString('hex'),
@ -2136,28 +2137,6 @@ RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, arg
}); });
}; };
function mergeSigs(a, b) {
var map = {};
var i, input, prev, key, ia, ib;
for (i = 0; i < b.inputs.length; i++) {
input = b.inputs[i];
prev = input.prevout;
key = prev.hash + prev.index;
map[key] = input;
}
for (i = 0; i < b.inputs.length; i++) {
ia = a.inputs[i];
if (!ia || ia.length !== 0)
break;
key = prev.hash + prev.index;
ib = map[key];
if (ib)
ia.script = ib.script;
}
}
RPC.prototype.fundrawtransaction = function fundrawtransaction(args, callback) { RPC.prototype.fundrawtransaction = function fundrawtransaction(args, callback) {
var tx, options, changeAddress, feeRate; var tx, options, changeAddress, feeRate;
@ -2223,6 +2202,7 @@ RPC.prototype._createRedeem = function _createRedeem(args, callback) {
} }
hash = bcoin.address.getHash(key, 'hex'); hash = bcoin.address.getHash(key, 'hex');
if (!hash) if (!hash)
return next(new RPCError('Invalid key.')); return next(new RPCError('Invalid key.'));
@ -2233,7 +2213,7 @@ RPC.prototype._createRedeem = function _createRedeem(args, callback) {
if (!ring) if (!ring)
return next(new RPCError('Invalid key.')); return next(new RPCError('Invalid key.'));
keys[i] = ring.getPublicKey(); keys[i] = ring.publicKey;
next(); next();
}); });
@ -2597,9 +2577,7 @@ RPC.prototype.dumpprivkey = function dumpprivkey(args, callback) {
if (!key) if (!key)
return callback(new RPCError('Wallet is locked.')); return callback(new RPCError('Wallet is locked.'));
key = ring.derive(key); callback(null, ring.toSecret());
callback(null, key.toSecret());
}); });
}; };
@ -2642,14 +2620,13 @@ RPC.prototype.dumpwallet = function dumpwallet(args, callback) {
if (!key) if (!key)
return callback(new RPCError('Wallet is locked.')); return callback(new RPCError('Wallet is locked.'));
key = ring.derive(key);
address = ring.getAddress('base58'); address = ring.getAddress('base58');
fmt = '%s %s label= addr=%s'; fmt = '%s %s label= addr=%s';
if (ring.change) if (ring.change)
fmt = '%s %s change=1 addr=%s'; fmt = '%s %s change=1 addr=%s';
str = utils.fmt(fmt, key.toSecret(), time, address); str = utils.fmt(fmt, ring.toSecret(), time, address);
out.push(str); out.push(str);
@ -3826,12 +3803,10 @@ RPC.prototype.signmessage = function signmessage(args, callback) {
if (!key) if (!key)
return callback(new RPCError('Wallet is locked.')); return callback(new RPCError('Wallet is locked.'));
key = ring.derive(key);
msg = new Buffer(RPC.magic + msg, 'utf8'); msg = new Buffer(RPC.magic + msg, 'utf8');
msg = utils.hash256(msg); msg = utils.hash256(msg);
sig = bcoin.ec.sign(msg, key); sig = ring.sign(msg);
callback(null, sig.toString('base64')); callback(null, sig.toString('base64'));
}); });

View File

@ -16,168 +16,82 @@ var BufferWriter = require('./writer');
var scriptTypes = constants.scriptTypes; var scriptTypes = constants.scriptTypes;
/** /**
* Represents a key ring which amounts to an address. Used for {@link Wallet}. * Represents a key ring which amounts to an address.
* @exports KeyRing * @exports KeyRing
* @constructor * @constructor
* @param {Object} options * @param {Object} options
* @param {HDPrivateKey|HDPublicKey|KeyPair|Buffer} options.key * @param {HDPrivateKey|HDPublicKey|Buffer} options.key
* @param {String?} options.name
* @param {Number?} options.account
* @param {Number?} options.change
* @param {Number?} options.index
* @param {String?} options.type - `"pubkeyhash"` or `"multisig"`.
* @param {Buffer[]} options.keys - Shared multisig keys. * @param {Buffer[]} options.keys - Shared multisig keys.
* @param {Number?} options.m - Multisig `m` value. * @param {Number?} options.m - Multisig `m` value.
* @param {Number?} options.n - Multisig `n` value. * @param {Number?} options.n - Multisig `n` value.
* @param {Boolean?} options.witness - Whether witness programs are enabled. * @param {Boolean?} options.witness - Whether witness programs are enabled.
*/ */
function KeyRing(options) { function KeyRing(options, network) {
if (!(this instanceof KeyRing)) if (!(this instanceof KeyRing))
return new KeyRing(options); return new KeyRing(options, network);
this.network = bcoin.network.get(); this.network = bcoin.network.get();
this.type = KeyRing.types.PUBKEYHASH;
this.m = 1;
this.n = 1;
this.witness = false; this.witness = false;
this.publicKey = null;
this.privateKey = null;
this.script = null;
this.wid = 0; this.wid = 0;
this.id = null; this.id = null;
this.name = null; this.name = null;
this.account = 0; this.account = 0;
this.change = 0; this.change = 0;
this.index = 0; this.index = 0;
this.key = null;
this.keys = [];
this._keyHash = null; this._keyHash = null;
this._keyAddress = null; this._keyAddress = null;
this._program = null; this._program = null;
this._programHash = null; this._programHash = null;
this._programAddress = null; this._programAddress = null;
this._script = null;
this._scriptHash160 = null; this._scriptHash160 = null;
this._scriptHash256 = null; this._scriptHash256 = null;
this._scriptAddress = null; this._scriptAddress = null;
this._addressMap = null;
if (options) if (options)
this.fromOptions(options); this.fromOptions(options, network);
} }
/**
* KeyRing types.
* @enum {Number}
* @default
*/
KeyRing.types = {
PUBKEYHASH: 0,
MULTISIG: 1
};
/**
* KeyRing types by value.
* @const {RevMap}
*/
KeyRing.typesByVal = {
0: 'pubkeyhash',
1: 'multisig'
};
/** /**
* Inject properties from options object. * Inject properties from options object.
* @private * @private
* @param {Object} options * @param {Object} options
*/ */
KeyRing.prototype.fromOptions = function fromOptions(options) { KeyRing.prototype.fromOptions = function fromOptions(options, network) {
var i; var key = toKey(options);
assert(options.key); if (Buffer.isBuffer(key))
return this.fromKey(key, network);
key = toKey(options.key);
if (options.privateKey)
key = toKey(options.privateKey);
if (options.publicKey)
key = toKey(options.publicKey);
if (options.network) if (options.network)
this.network = bcoin.network.get(options.network); this.network = bcoin.network.get(options.network);
if (options.type != null) {
if (typeof options.type === 'string') {
this.type = KeyRing.types[options.type.toUpperCase()];
assert(this.type != null);
} else {
assert(typeof options.type === 'number');
this.type = options.type;
assert(KeyRing.typesByVal[this.type]);
}
}
if (options.m != null) {
assert(utils.isNumber(options.m));
this.m = options.m;
}
if (options.n != null) {
assert(utils.isNumber(options.n));
this.n = options.n;
}
if (options.witness != null) { if (options.witness != null) {
assert(typeof options.witness === 'boolean'); assert(typeof options.witness === 'boolean');
this.witness = options.witness; this.witness = options.witness;
} }
if (options.wid) { if (options.keys)
assert(utils.isNumber(options.wid)); return this.fromKeys(key, options.m, options.n, options.keys, this.network);
this.wid = options.wid;
}
if (options.id) { if (options.script)
assert(utils.isName(options.id)); return this.fromScript(key, options.script, this.network);
this.id = options.id;
}
if (options.name) { this.fromKey(key, this.network);
assert(utils.isName(options.name));
this.name = options.name;
}
if (options.account != null) {
assert(utils.isNumber(options.account));
this.account = options.account;
}
if (options.change != null) {
assert(utils.isNumber(options.change));
this.change = options.change;
}
if (options.index != null) {
assert(utils.isNumber(options.index));
this.index = options.index;
}
this.key = options.key;
if (this.key.getPublicKey)
this.key = this.key.getPublicKey();
assert(Buffer.isBuffer(this.key));
if (this.n > 1)
this.type = KeyRing.types.MULTISIG;
if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n');
this.addKey(this.key);
if (options.keys) {
assert(Array.isArray(options.keys));
for (i = 0; i < options.keys.length; i++)
this.addKey(options.keys[i]);
}
return this;
}; };
/** /**
@ -190,6 +104,234 @@ KeyRing.fromOptions = function fromOptions(options) {
return new KeyRing().fromOptions(options); return new KeyRing().fromOptions(options);
}; };
/**
* Inject data from private key.
* @private
* @param {Buffer} privateKey
* @param {Boolean?} compressed
* @param {(NetworkType|Network}) network
*/
KeyRing.prototype.fromPrivate = function fromPrivate(privateKey, network) {
assert(Buffer.isBuffer(privateKey), 'Private key must be a buffer.');
assert(bcoin.ec.privateKeyVerify(privateKey), 'Not a valid private key.');
this.network = bcoin.network.get(network);
this.privateKey = privateKey;
this.publicKey = bcoin.ec.publicKeyCreate(this.privateKey, true);
return this;
};
/**
* Instantiate keyring from a private key.
* @param {Buffer} privateKey
* @param {Boolean?} compressed
* @param {(NetworkType|Network}) network
* @returns {KeyRing}
*/
KeyRing.fromPrivate = function fromPrivate(privateKey, network) {
return new KeyRing().fromPrivate(privateKey, network);
};
/**
* Inject data from public key.
* @private
* @param {Buffer} privateKey
* @param {(NetworkType|Network}) network
*/
KeyRing.prototype.fromPublic = function fromPublic(publicKey, network) {
assert(Buffer.isBuffer(publicKey), 'Public key must be a buffer.');
assert(bcoin.ec.publicKeyVerify(publicKey), 'Not a valid public key.');
this.network = bcoin.network.get(network);
this.publicKey = publicKey;
return this;
};
/**
* Generate a keyring.
* @param {(Network|NetworkType)?} network
* @returns {KeyRing}
*/
KeyRing.generate = function(witness, network) {
var key = new KeyRing();
key.network = bcoin.network.get(network);
key.privateKey = bcoin.ec.generatePrivateKey();
key.publicKey = bcoin.ec.publicKeyCreate(key.privateKey, true);
key.witness = !!witness;
return key;
};
/**
* Instantiate keyring from a public key.
* @param {Buffer} publicKey
* @param {(NetworkType|Network}) network
* @returns {KeyRing}
*/
KeyRing.fromPublic = function fromPublic(publicKey, network) {
return new KeyRing().fromPublic(publicKey, network);
};
/**
* Inject data from public key.
* @private
* @param {Buffer} privateKey
* @param {(NetworkType|Network}) network
*/
KeyRing.prototype.fromKey = function fromKey(key, network) {
assert(Buffer.isBuffer(key), 'Key must be a buffer.');
assert(key.length === 32 || key.length === 33, 'Not a key.');
if (key.length === 33)
return this.fromPublic(key, network);
return this.fromPrivate(key, network);
};
/**
* Instantiate keyring from a public key.
* @param {Buffer} publicKey
* @param {(NetworkType|Network}) network
* @returns {KeyRing}
*/
KeyRing.fromKey = function fromKey(key, network) {
return new KeyRing().fromKey(key, network);
};
/**
* Inject data from public key.
* @private
* @param {Buffer} key
* @param {Number} m
* @param {Number} n
* @param {Buffer[]} keys
* @param {(NetworkType|Network}) network
*/
KeyRing.prototype.fromKeys = function fromKeys(key, m, n, keys, network) {
var script = bcoin.script.fromMultisig(m, n, keys);
this.fromScript(key, script, network);
return this;
};
/**
* Instantiate keyring from keys.
* @param {Buffer} key
* @param {Number} m
* @param {Number} n
* @param {Buffer[]} keys
* @param {(NetworkType|Network}) network
* @returns {KeyRing}
*/
KeyRing.fromKeys = function fromKeys(key, m, n, keys, network) {
return new KeyRing().fromKeys(key, m, n, keys, network);
};
/**
* Inject data from script.
* @private
* @param {Buffer} key
* @param {Script} script
* @param {(NetworkType|Network}) network
*/
KeyRing.prototype.fromScript = function fromScript(key, script, network) {
assert(script instanceof bcoin.script, 'Non-script passed into KeyRing.');
this.fromKey(key, network);
this.script = script;
return this;
};
/**
* Instantiate keyring from script.
* @param {Buffer} key
* @param {Script} script
* @param {(NetworkType|Network}) network
* @returns {KeyRing}
*/
KeyRing.fromScript = function fromScript(key, script, network) {
return new KeyRing().fromScript(key, script, network);
};
/**
* Convert key to a CBitcoinSecret.
* @param {(Network|NetworkType)?} network
* @returns {Base58String}
*/
KeyRing.prototype.toSecret = function toSecret(network) {
var p = new BufferWriter();
assert(this.privateKey, 'Cannot serialize without private key.');
if (!network)
network = this.network;
network = bcoin.network.get(network);
p.writeU8(network.keyPrefix.privkey);
p.writeBytes(this.privateKey);
p.writeU8(1);
p.writeChecksum();
return utils.toBase58(p.render());
};
/**
* Inject properties from serialized CBitcoinSecret.
* @private
* @param {Base58String} secret
*/
KeyRing.prototype.fromSecret = function fromSecret(data) {
var p = new BufferReader(utils.fromBase58(data), true);
var i, prefix, version, type, key, compressed;
version = p.readU8();
for (i = 0; i < network.types.length; i++) {
type = network.types[i];
prefix = network[type].keyPrefix.privkey;
if (version === prefix)
break;
}
assert(i < network.types.length, 'Network not found.');
key = p.readBytes(32);
if (p.left() > 4) {
assert(p.readU8() === 1, 'Bad compression flag.');
compressed = true;
} else {
compressed = false;
}
p.verifyChecksum();
assert(compressed === false, 'Cannot handle uncompressed.');
return this.fromPrivate(key, type);
};
/**
* Instantiate a keyring from a serialized CBitcoinSecret.
* @param {Base58String} secret
* @returns {KeyRing}
*/
KeyRing.fromSecret = function fromSecret(data) {
return new KeyRing().fromSecret(data);
};
/** /**
* Inject properties from account object. * Inject properties from account object.
* @private * @private
@ -201,25 +343,20 @@ KeyRing.fromOptions = function fromOptions(options) {
*/ */
KeyRing.prototype.fromAccount = function fromAccount(account, key, keys, change, index) { KeyRing.prototype.fromAccount = function fromAccount(account, key, keys, change, index) {
var i;
this.network = account.network; this.network = account.network;
this.key = key.publicKey; this.publicKey = key.publicKey;
if (account.n > 1)
this.script = bcoin.script.fromMultisig(account.m, account.n, keys);
this.witness = account.witness;
this.wid = account.wid; this.wid = account.wid;
this.id = account.id; this.id = account.id;
this.name = account.name; this.name = account.name;
this.account = account.accountIndex; this.account = account.accountIndex;
this.change = change; this.change = change;
this.index = index; this.index = index;
this.type = account.type;
this.witness = account.witness;
this.m = account.m;
this.n = account.n;
this.addKey(this.key);
for (i = 0; i < keys.length; i++)
this.addKey(keys[i]);
return this; return this;
}; };
@ -238,36 +375,6 @@ KeyRing.fromAccount = function fromAccount(account, key, keys, change, index) {
return new KeyRing().fromAccount(account, key, keys, change, index); return new KeyRing().fromAccount(account, key, keys, change, index);
}; };
/**
* Add a key to shared keys.
* @param {Buffer} key
*/
KeyRing.prototype.addKey = function addKey(key) {
assert(Buffer.isBuffer(key));
utils.binaryInsert(this.keys, key, utils.cmp, true);
if (this.keys.length > this.n) {
utils.binaryRemove(this.keys, key, utils.cmp);
throw new Error('Cannot add more keys.');
}
};
/**
* Remove a key from shared keys.
* @param {Buffer} key
*/
KeyRing.prototype.removeKey = function removeKey(key) {
assert(Buffer.isBuffer(key));
if (this.keys.length === this.n)
throw new Error('Cannot remove key.');
utils.binaryRemove(this.keys, key, utils.cmp);
};
/** /**
* Get public key. * Get public key.
* @param {String?} enc - `"hex"` or `null`. * @param {String?} enc - `"hex"` or `null`.
@ -276,12 +383,12 @@ KeyRing.prototype.removeKey = function removeKey(key) {
KeyRing.prototype.getPublicKey = function getPublicKey(enc) { KeyRing.prototype.getPublicKey = function getPublicKey(enc) {
if (enc === 'base58') if (enc === 'base58')
return utils.toBase58(this.key); return utils.toBase58(this.publicKey);
if (enc === 'hex') if (enc === 'hex')
return this.key.toString('hex'); return this.publicKey.toString('hex');
return this.key; return this.publicKey;
}; };
/** /**
@ -290,23 +397,7 @@ KeyRing.prototype.getPublicKey = function getPublicKey(enc) {
*/ */
KeyRing.prototype.getScript = function getScript() { KeyRing.prototype.getScript = function getScript() {
var redeem; return this.script;
if (this.type !== KeyRing.types.MULTISIG)
return;
if (!this._script) {
assert(this.keys.length === this.n, 'Not all keys have been added.');
redeem = bcoin.script.fromMultisig(this.m, this.n, this.keys);
if (redeem.getSize() > 520)
throw new Error('Redeem script too large (520 byte limit).');
this._script = redeem;
}
return this._script;
}; };
/** /**
@ -321,14 +412,12 @@ KeyRing.prototype.getProgram = function getProgram() {
return; return;
if (!this._program) { if (!this._program) {
if (this.type === KeyRing.types.PUBKEYHASH) { if (!this.script) {
hash = utils.hash160(this.getPublicKey()); hash = utils.hash160(this.publicKey);
program = bcoin.script.fromProgram(0, hash);
} else if (this.type === KeyRing.types.MULTISIG) {
hash = utils.sha256(this.getScript().toRaw());
program = bcoin.script.fromProgram(0, hash); program = bcoin.script.fromProgram(0, hash);
} else { } else {
assert(false, 'Unknown address type.'); hash = this.script.sha256();
program = bcoin.script.fromProgram(0, hash);
} }
this._program = program; this._program = program;
} }
@ -348,7 +437,7 @@ KeyRing.prototype.getProgramHash = function getProgramHash(enc) {
return; return;
if (!this._programHash) if (!this._programHash)
this._programHash = utils.hash160(this.getProgram().toRaw()); this._programHash = this.getProgram().hash160();
return enc === 'hex' return enc === 'hex'
? this._programHash.toString('hex') ? this._programHash.toString('hex')
@ -398,11 +487,11 @@ KeyRing.prototype.getScriptHash = function getScriptHash(enc) {
*/ */
KeyRing.prototype.getScriptHash160 = function getScriptHash256(enc) { KeyRing.prototype.getScriptHash160 = function getScriptHash256(enc) {
if (this.type !== KeyRing.types.MULTISIG) if (!this.script)
return; return;
if (!this._scriptHash160) if (!this._scriptHash160)
this._scriptHash160 = utils.hash160(this.getScript().toRaw()); this._scriptHash160 = this.script.hash160();
return enc === 'hex' return enc === 'hex'
? this._scriptHash160.toString('hex') ? this._scriptHash160.toString('hex')
@ -416,11 +505,11 @@ KeyRing.prototype.getScriptHash160 = function getScriptHash256(enc) {
*/ */
KeyRing.prototype.getScriptHash256 = function getScriptHash256(enc) { KeyRing.prototype.getScriptHash256 = function getScriptHash256(enc) {
if (this.type !== KeyRing.types.MULTISIG) if (!this.script)
return; return;
if (!this._scriptHash256) if (!this._scriptHash256)
this._scriptHash256 = utils.sha256(this.getScript().toRaw()); this._scriptHash256 = this.script.sha256();
return enc === 'hex' return enc === 'hex'
? this._scriptHash256.toString('hex') ? this._scriptHash256.toString('hex')
@ -436,7 +525,7 @@ KeyRing.prototype.getScriptHash256 = function getScriptHash256(enc) {
KeyRing.prototype.getScriptAddress = function getScriptAddress(enc) { KeyRing.prototype.getScriptAddress = function getScriptAddress(enc) {
var hash, address; var hash, address;
if (this.type !== KeyRing.types.MULTISIG) if (!this.script)
return; return;
if (!this._scriptAddress) { if (!this._scriptAddress) {
@ -464,7 +553,7 @@ KeyRing.prototype.getScriptAddress = function getScriptAddress(enc) {
KeyRing.prototype.getKeyHash = function getKeyHash(enc) { KeyRing.prototype.getKeyHash = function getKeyHash(enc) {
if (!this._keyHash) if (!this._keyHash)
this._keyHash = utils.hash160(this.getPublicKey()); this._keyHash = utils.hash160(this.publicKey);
return enc === 'hex' return enc === 'hex'
? this._keyHash.toString('hex') ? this._keyHash.toString('hex')
@ -516,7 +605,7 @@ KeyRing.prototype.compile = function compile(hash, type, version) {
*/ */
KeyRing.prototype.getHash = function getHash(enc) { KeyRing.prototype.getHash = function getHash(enc) {
if (this.type === KeyRing.types.MULTISIG) if (this.script)
return this.getScriptHash(enc); return this.getScriptHash(enc);
return this.getKeyHash(enc); return this.getKeyHash(enc);
}; };
@ -528,25 +617,33 @@ KeyRing.prototype.getHash = function getHash(enc) {
*/ */
KeyRing.prototype.getAddress = function getAddress(enc) { KeyRing.prototype.getAddress = function getAddress(enc) {
if (this.type === KeyRing.types.MULTISIG) if (this.script)
return this.getScriptAddress(enc); return this.getScriptAddress(enc);
return this.getKeyAddress(enc); return this.getKeyAddress(enc);
}; };
/** /**
* Create the address map for testing txs. * Test an address hash against hash and program hash.
* @returns {AddressMap} * @param {Buffer} hash
* @returns {Boolean}
*/ */
KeyRing.prototype.getAddressMap = function getAddressMap() { KeyRing.prototype.ownHash = function ownHash(hash) {
if (!this._addressMap) { if (!hash)
this._addressMap = {}; return false;
this._addressMap[this.getHash('hex')] = true;
if (this.witness) if (utils.equal(hash, this.keyHash))
this._addressMap[this.getProgramHash('hex')] = true; return true;
if (utils.equal(hash, this.scriptHash))
return true;
if (this.witness) {
if (utils.equal(hash, this.programHash))
return true;
} }
return this._addressMap; return false;
}; };
/** /**
@ -557,8 +654,7 @@ KeyRing.prototype.getAddressMap = function getAddressMap() {
*/ */
KeyRing.prototype.ownInput = function ownInput(tx, index) { KeyRing.prototype.ownInput = function ownInput(tx, index) {
var addressMap = this.getAddressMap(); var input;
var input, hash;
if (tx instanceof bcoin.input) { if (tx instanceof bcoin.input) {
input = tx; input = tx;
@ -567,12 +663,7 @@ KeyRing.prototype.ownInput = function ownInput(tx, index) {
assert(input, 'Input does not exist.'); assert(input, 'Input does not exist.');
} }
hash = input.getHash('hex'); return this.ownHash(input.getHash());
if (!hash)
return false;
return addressMap[hash] === true;
}; };
/** /**
@ -583,8 +674,7 @@ KeyRing.prototype.ownInput = function ownInput(tx, index) {
*/ */
KeyRing.prototype.ownOutput = function ownOutput(tx, index) { KeyRing.prototype.ownOutput = function ownOutput(tx, index) {
var addressMap = this.getAddressMap(); var output;
var output, hash;
if (tx instanceof bcoin.output) { if (tx instanceof bcoin.output) {
output = tx; output = tx;
@ -593,61 +683,53 @@ KeyRing.prototype.ownOutput = function ownOutput(tx, index) {
assert(output, 'Output does not exist.'); assert(output, 'Output does not exist.');
} }
hash = output.getHash('hex'); return this.ownHash(output.getHash());
if (!hash)
return false;
return addressMap[hash] === true;
}; };
/**
* Test a hash against script hashes to
* find the correct redeem script, if any.
* @param {Buffer} hash
* @returns {Script|null}
*/
KeyRing.prototype.getRedeem = function(hash) { KeyRing.prototype.getRedeem = function(hash) {
if (this.program && utils.equal(hash, this.programHash)) if (this.program) {
return this.program; if (utils.equal(hash, this.programHash))
return this.program;
}
if (this.script && utils.equal(hash, this.scriptHash160)) if (this.script) {
return this.script; if (utils.equal(hash, this.scriptHash160))
return this.script;
if (this.script && utils.equal(hash, this.scriptHash256)) if (utils.equal(hash, this.scriptHash256))
return this.script; return this.script;
}
return null;
}; };
/** /**
* Build input scripts templates for a transaction (does not * Sign a message.
* sign, only creates signature slots). Only builds scripts * @param {Buffer} msg
* for inputs that are redeemable by this address. * @returns {Buffer} Signature in DER format.
* @param {MTX} tx
* @param {Number?} index - Index of input. If not present,
* it will attempt to sign all redeemable inputs.
* @returns {Number} Total number of scripts built.
*/ */
KeyRing.prototype.scriptInputs = function scriptInputs(tx) { KeyRing.prototype.sign = function sign(msg) {
return tx.template(this.publicKey, this.script); assert(this.privateKey, 'Cannot sign without private key.');
return bcoin.ec.sign(msg, this.privateKey);
}; };
/** /**
* Build input scripts and sign inputs for a transaction. Only attempts * Verify a message.
* to build/sign inputs that are redeemable by this address. * @param {Buffer} msg
* @param {MTX} tx * @param {Buffer} sig - Signature in DER format.
* @param {HDPrivateKey|KeyPair|Buffer} key - Private key. * @returns {Boolean}
* @returns {Number} Total number of inputs scripts built and signed.
*/ */
KeyRing.prototype.sign = function sign(tx, key) { KeyRing.prototype.verify = function verify(msg, sig) {
return tx.sign(key, this.script); return bcoin.ec.verify(msg, sig, this.publicKey);
};
/**
* Derive to address index.
* @param {HDPrivateKey} key
* @returns {HDPrivateKey}
*/
KeyRing.prototype.derive = function derive(key) {
if (key.isMaster())
key = key.deriveAccount44(this.account);
return key.derive(this.change).derive(this.index);
}; };
/** /**
@ -655,28 +737,20 @@ KeyRing.prototype.derive = function derive(key) {
* @returns {ScriptType} * @returns {ScriptType}
*/ */
KeyRing.prototype.getScriptType = function getScriptType() { KeyRing.prototype.getType = function getType() {
switch (this.type) { if (this.program)
case KeyRing.types.PUBKEYHASH: return this.program.getType();
return this.witness if (this.script)
? scriptTypes.WITNESSPUBKEYHASH return this.script.getType();
: scriptTypes.PUBKEYHASH; return scriptTypes.PUBKEYHASH;
case KeyRing.types.MULTISIG:
return this.witness
? scriptTypes.WITNESSSCRIPTHASH
: scriptTypes.SCRIPTHASH;
default:
assert(false, 'Bad keyring type.');
break;
}
}; };
KeyRing.prototype.__defineGetter__('publicKey', function() { /*
return this.getPublicKey(); * Getters
}); */
KeyRing.prototype.__defineGetter__('script', function() { KeyRing.prototype.__defineGetter__('type', function() {
return this.getScript(); return this.getType();
}); });
KeyRing.prototype.__defineGetter__('scriptHash', function() { KeyRing.prototype.__defineGetter__('scriptHash', function() {
@ -723,6 +797,15 @@ KeyRing.prototype.__defineGetter__('address', function() {
return this.getAddress(); return this.getAddress();
}); });
/**
* Inspect keyring.
* @returns {Object}
*/
KeyRing.prototype.inspect = function inspect() {
return this.toJSON();
};
/** /**
* Convert an KeyRing to a more json-friendly object. * Convert an KeyRing to a more json-friendly object.
* @returns {Object} * @returns {Object}
@ -731,20 +814,16 @@ KeyRing.prototype.__defineGetter__('address', function() {
KeyRing.prototype.toJSON = function toJSON() { KeyRing.prototype.toJSON = function toJSON() {
return { return {
network: this.network.type, network: this.network.type,
type: KeyRing.typesByVal[this.type].toLowerCase(),
m: this.m,
n: this.n,
witness: this.witness, witness: this.witness,
key: this.publicKey.toString('hex'),
script: this.script ? this.script.toRaw().toString('hex') : null,
type: constants.scriptTypesByVal[this.type].toLowerCase(),
wid: this.wid, wid: this.wid,
id: this.id, id: this.id,
name: this.name, name: this.name,
account: this.account, account: this.account,
change: this.change, change: this.change,
index: this.index, index: this.index,
key: this.key.toString('hex'),
keys: this.keys.map(function(key) {
return key.toString('hex');
}),
address: this.getAddress('base58'), address: this.getAddress('base58'),
programAddress: this.getProgramAddress('base58') programAddress: this.getProgramAddress('base58')
}; };
@ -757,39 +836,31 @@ KeyRing.prototype.toJSON = function toJSON() {
*/ */
KeyRing.prototype.fromJSON = function fromJSON(json) { KeyRing.prototype.fromJSON = function fromJSON(json) {
var i;
assert(json); assert(json);
assert(typeof json.network === 'string'); assert(typeof json.network === 'string');
assert(typeof json.type === 'string');
assert(utils.isNumber(json.m));
assert(utils.isNumber(json.n));
assert(typeof json.witness === 'boolean'); assert(typeof json.witness === 'boolean');
assert(typeof json.publicKey === 'string');
assert(!json.script || typeof json.script === 'string');
assert(!json.wid || utils.isNumber(json.wid)); assert(!json.wid || utils.isNumber(json.wid));
assert(!json.id || utils.isName(json.id)); assert(!json.id || utils.isName(json.id));
assert(!json.name || utils.isName(json.name)); assert(!json.name || utils.isName(json.name));
assert(utils.isNumber(json.account)); assert(utils.isNumber(json.account));
assert(utils.isNumber(json.change)); assert(utils.isNumber(json.change));
assert(utils.isNumber(json.index)); assert(utils.isNumber(json.index));
assert(typeof json.key === 'string');
assert(Array.isArray(json.keys));
this.nework = bcoin.network.get(json.network); this.nework = bcoin.network.get(json.network);
this.type = KeyRing.types[json.type.toUpperCase()];
this.m = json.m;
this.n = json.n;
this.witness = json.witness; this.witness = json.witness;
this.publicKey = new Buffer(json.publicKey, 'hex');
if (json.script)
this.script = new Buffer(json.script, 'hex');
this.wid = json.wid; this.wid = json.wid;
this.name = json.name; this.name = json.name;
this.account = json.account; this.account = json.account;
this.change = json.change; this.change = json.change;
this.index = json.index; this.index = json.index;
this.key = new Buffer(json.key, 'hex');
assert(this.type != null);
for (i = 0; i < json.keys.length; i++)
this.keys.push(new Buffer(json.keys[i], 'hex'));
return this; return this;
}; };
@ -814,21 +885,18 @@ KeyRing.prototype.toRaw = function toRaw(writer) {
var i; var i;
p.writeU32(this.network.magic); p.writeU32(this.network.magic);
p.writeU8(this.type);
p.writeU8(this.m);
p.writeU8(this.n);
p.writeU8(this.witness ? 1 : 0); p.writeU8(this.witness ? 1 : 0);
p.writeU32(this.wid); p.writeVarBytes(this.publicKey);
p.writeVarString(this.id, 'utf8');
p.writeVarString(this.name, 'utf8');
p.writeU32(this.account);
p.writeU32(this.change);
p.writeU32(this.index);
p.writeVarBytes(this.key);
p.writeU8(this.keys.length);
for (i = 0; i < this.keys.length; i++) if (this.privateKey)
p.writeVarBytes(this.keys[i]); p.writeVarBytes(this.privateKey);
else
p.writeVarint(0);
if (this.script)
p.writeVarBytes(this.script.toRaw());
else
p.writeVarint(0);
if (!writer) if (!writer)
p = p.render(); p = p.render();
@ -847,24 +915,18 @@ KeyRing.prototype.fromRaw = function fromRaw(data) {
var i, count; var i, count;
this.network = bcoin.network.fromMagic(p.readU32()); this.network = bcoin.network.fromMagic(p.readU32());
this.type = p.readU8();
this.m = p.readU8();
this.n = p.readU8();
this.witness = p.readU8() === 1; this.witness = p.readU8() === 1;
this.wid = p.readU32(); this.publicKey = p.readVarBytes();
this.id = p.readVarString('utf8');
this.name = p.readVarString('utf8');
this.account = p.readU32();
this.change = p.readU32();
this.index = p.readU32();
this.key = p.readVarBytes();
assert(KeyRing.typesByVal[this.type]); this.privateKey = p.readVarBytes();
count = p.readU8(); if (this.privateKey.length === 0)
this.privateKey = null;
for (i = 0; i < count; i++) this.script = p.readVarBytes();
this.keys.push(p.readVarBytes());
if (this.script.length === 0)
this.script = null;
return this; return this;
}; };
@ -887,10 +949,27 @@ KeyRing.fromRaw = function fromRaw(data) {
KeyRing.isKeyRing = function isKeyRing(obj) { KeyRing.isKeyRing = function isKeyRing(obj) {
return obj return obj
&& Array.isArray(obj.keys) && Buffer.isBuffer(obj.publicKey)
&& typeof obj.getAddressMap === 'function'; && typeof obj.toSecret === 'function';
}; };
/*
* Helpers
*/
function toKey(opt) {
if (!opt)
return opt;
if (opt.getPrivateKey)
return opt.getPrivateKey();
if (opt.getPublicKey)
return opt.getPublicKey();
return opt;
}
/* /*
* Expose * Expose
*/ */

View File

@ -211,12 +211,11 @@ MTX.prototype.addOutput = function addOutput(options, value) {
* Build input script (or witness) templates (with * Build input script (or witness) templates (with
* OP_0 in place of signatures). * OP_0 in place of signatures).
* @param {Number} index - Input index. * @param {Number} index - Input index.
* @param {Buffer} key - Public key. * @param {KeyRing} ring
* @param {Script} script - Redeem script.
* @returns {Boolean} Whether the script was able to be built. * @returns {Boolean} Whether the script was able to be built.
*/ */
MTX.prototype.scriptInput = function scriptInput(index, key, script) { MTX.prototype.scriptInput = function scriptInput(index, ring) {
var input = this.inputs[index]; var input = this.inputs[index];
var prev, redeem; var prev, redeem;
@ -233,9 +232,6 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
return true; return true;
} }
if (key.getPublicKey)
key = key.getPublicKey();
// Get the previous output's script // Get the previous output's script
prev = input.coin.script; prev = input.coin.script;
@ -243,7 +239,7 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
// with segwit: figuring out where the redeem script and witness // with segwit: figuring out where the redeem script and witness
// redeem scripts go. // redeem scripts go.
if (prev.isScripthash()) { if (prev.isScripthash()) {
redeem = this._getRedeem(prev.get(1), key, script); redeem = ring.getRedeem(prev.get(1));
if (!redeem) if (!redeem)
return false; return false;
@ -252,12 +248,12 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
if (redeem.isProgram()) { if (redeem.isProgram()) {
// P2WSH nested within pay-to-scripthash. // P2WSH nested within pay-to-scripthash.
if (redeem.isWitnessScripthash()) { if (redeem.isWitnessScripthash()) {
prev = this._getRedeem(redeem.get(1), key, script); prev = ring.getRedeem(redeem.get(1));
if (!prev) if (!prev)
return false; return false;
if (!this.scriptVector(prev, input.witness, key)) if (!this.scriptVector(prev, input.witness, ring))
return false; return false;
input.witness.push(prev.toRaw()); input.witness.push(prev.toRaw());
@ -269,9 +265,9 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
// P2WPKH nested within pay-to-scripthash. // P2WPKH nested within pay-to-scripthash.
if (redeem.isWitnessPubkeyhash()) { if (redeem.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(utils.hash160(key)); prev = Script.fromPubkeyhash(ring.keyHash);
if (!this.scriptVector(prev, input.witness, key)) if (!this.scriptVector(prev, input.witness, ring))
return false; return false;
input.script.push(redeem.toRaw()); input.script.push(redeem.toRaw());
@ -285,7 +281,7 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
} }
// Regular P2SH. // Regular P2SH.
if (!this.scriptVector(redeem, input.script, key)) if (!this.scriptVector(redeem, input.script, ring))
return false; return false;
input.script.push(redeem.toRaw()); input.script.push(redeem.toRaw());
@ -298,12 +294,12 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
if (prev.isProgram()) { if (prev.isProgram()) {
// Bare P2WSH. // Bare P2WSH.
if (prev.isWitnessScripthash()) { if (prev.isWitnessScripthash()) {
redeem = this._getRedeem(prev.get(1), key, script); redeem = ring.getRedeem(prev.get(1));
if (!redeem) if (!redeem)
return false; return false;
if (!this.scriptVector(redeem, input.witness, key)) if (!this.scriptVector(redeem, input.witness, ring))
return false; return false;
input.witness.push(redeem.toRaw()); input.witness.push(redeem.toRaw());
@ -316,7 +312,7 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
if (prev.isWitnessPubkeyhash()) { if (prev.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(prev.get(1)); prev = Script.fromPubkeyhash(prev.get(1));
if (!this.scriptVector(prev, input.witness, key)) if (!this.scriptVector(prev, input.witness, ring))
return false; return false;
input.script.compile(); input.script.compile();
@ -329,7 +325,7 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
} }
// Wow, a normal output! Praise be to Jengus and Gord. // Wow, a normal output! Praise be to Jengus and Gord.
return this.scriptVector(prev, input.script, key); return this.scriptVector(prev, input.script, ring);
}; };
/** /**
@ -337,16 +333,16 @@ MTX.prototype.scriptInput = function scriptInput(index, key, script) {
* based on a previous script. * based on a previous script.
* @param {Script} prev * @param {Script} prev
* @param {Witness|Script} vector * @param {Witness|Script} vector
* @param {Buffer} key * @param {Buffer} ring
* @return {Boolean} * @return {Boolean}
*/ */
MTX.prototype.scriptVector = function scriptVector(prev, vector, key) { MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) {
var i, n; var i, n;
// P2PK // P2PK
if (prev.isPubkey()) { if (prev.isPubkey()) {
if (!utils.equal(prev.get(1), key)) if (!utils.equal(prev.get(1), ring.publicKey))
return false; return false;
vector.set(0, opcodes.OP_0); vector.set(0, opcodes.OP_0);
@ -356,18 +352,18 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, key) {
// P2PKH // P2PKH
if (prev.isPubkeyhash()) { if (prev.isPubkeyhash()) {
if (!utils.equal(prev.get(2), utils.hash160(key))) if (!utils.equal(prev.get(2), ring.keyHash))
return false; return false;
vector.set(0, opcodes.OP_0); vector.set(0, opcodes.OP_0);
vector.set(1, key); vector.set(1, ring.publicKey);
return true; return true;
} }
// Multisig // Multisig
if (prev.isMultisig()) { if (prev.isMultisig()) {
if (prev.indexOf(key) === -1) if (prev.indexOf(ring.publicKey) === -1)
return false; return false;
// Technically we should create m signature slots, // Technically we should create m signature slots,
@ -388,52 +384,6 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, key) {
return false; return false;
}; };
/**
* Calculate a redeem script based on hash.
* Test against passed in redeem script.
* @private
* @param {Buffer} hash - 32 or 20 byte hash.
* @param {Buffer} key - Public key.
* @param {Script} script - Known redeem script.
* @returns {Script|null}
*/
MTX.prototype._getRedeem = function getRedeem(hash, key, script) {
var program;
if (!key)
return;
switch (hash.length) {
case 20:
program = bcoin.script.fromProgram(0, utils.hash160(key));
if (utils.equal(program.hash160(), hash))
return program;
if (!script)
return;
program = script.forWitness();
if (utils.equal(program.hash160(), hash))
return program;
if (utils.equal(script.hash160(), hash))
return script;
break;
case 32:
if (!script)
return;
if (utils.equal(script.sha256(), hash))
return script;
break;
}
};
/** /**
* Sign an input. * Sign an input.
* @param {Number} index - Index of input being signed. * @param {Number} index - Index of input being signed.
@ -454,9 +404,6 @@ MTX.prototype.signInput = function signInput(index, key, type) {
if (!input.coin) if (!input.coin)
return false; return false;
if (key.getPrivateKey)
key = key.getPrivateKey();
// Get the previous output's script // Get the previous output's script
prev = input.coin.script; prev = input.coin.script;
vector = input.script; vector = input.script;
@ -832,26 +779,23 @@ MTX.prototype.isSigned = function isSigned() {
/** /**
* Built input scripts (or witnesses) and sign the inputs. * Built input scripts (or witnesses) and sign the inputs.
* @param {Number} index - Index of input being signed.
* @param {KeyRing} ring - Address used to sign. The address * @param {KeyRing} ring - Address used to sign. The address
* must be able to redeem the coin. * must be able to redeem the coin.
* @param {HDPrivateKey|KeyPair|Buffer} key - Private key.
* @param {SighashType} type
* @returns {Boolean} Whether the input was able to be signed. * @returns {Boolean} Whether the input was able to be signed.
* @throws on unavailable coins.
*/ */
MTX.prototype.template = function template(key, script) { MTX.prototype.template = function template(ring) {
var total = 0; var total = 0;
var i; var i;
if (key.getPublicKey)
key = key.getPublicKey();
for (i = 0; i < this.inputs.length; i++) { for (i = 0; i < this.inputs.length; i++) {
// Build script for input if (!ring.ownInput(this, i))
if (!this.scriptInput(i, key, script))
continue; continue;
// Build script for input
if (!this.scriptInput(i, ring))
continue;
total++; total++;
} }
@ -860,27 +804,32 @@ MTX.prototype.template = function template(key, script) {
/** /**
* Built input scripts (or witnesses) and sign the inputs. * Built input scripts (or witnesses) and sign the inputs.
* @param {Number} index - Index of input being signed.
* @param {KeyRing} ring - Address used to sign. The address * @param {KeyRing} ring - Address used to sign. The address
* must be able to redeem the coin. * must be able to redeem the coin.
* @param {HDPrivateKey|KeyPair|Buffer} key - Private key.
* @param {SighashType} type * @param {SighashType} type
* @returns {Boolean} Whether the input was able to be signed. * @returns {Boolean} Whether the input was able to be signed.
* @throws on unavailable coins.
*/ */
MTX.prototype.sign = function sign(key, script, type) { MTX.prototype.sign = function sign(ring, type) {
var total = 0; var total = 0;
var i, pub; var i, key;
if (key.getPrivateKey) if (Array.isArray(ring)) {
key = key.getPrivateKey(); for (i = 0; i < ring.length; i++)
total += this.sign(ring[i], type);
return total;
}
pub = bcoin.ec.publicKeyCreate(key, true); key = ring.privateKey;
assert(key, 'No private key available.');
for (i = 0; i < this.inputs.length; i++) { for (i = 0; i < this.inputs.length; i++) {
if (!ring.ownInput(this, i))
continue;
// Build script for input // Build script for input
if (!this.scriptInput(i, pub, script)) if (!this.scriptInput(i, ring))
continue; continue;
// Sign input // Sign input
@ -893,6 +842,36 @@ MTX.prototype.sign = function sign(key, script, type) {
return total; return total;
}; };
/**
* Sign the transaction inputs on the worker pool
* (if workers are enabled).
* @param {KeyRing} ring
* @param {SighashType?} type
* @param {Function} callback
* @returns {Boolean} Whether the inputs are valid.
*/
MTX.prototype.signAsync = function signAsync(ring, type, callback) {
var result;
if (typeof type === 'function') {
callback = type;
type = null;
}
if (!bcoin.useWorkers) {
callback = utils.asyncify(callback);
try {
result = this.sign(ring, type);
} catch (e) {
return callback(e);
}
return callback(null, result);
}
bcoin.workerPool.sign(this, ring, type, callback);
};
/** /**
* Test whether the transaction at least * Test whether the transaction at least
* has all script templates built. * has all script templates built.

View File

@ -15,8 +15,6 @@ var assert = utils.assert;
var BufferReader = require('./reader'); var BufferReader = require('./reader');
var BufferWriter = require('./writer'); var BufferWriter = require('./writer');
var TXDB = require('./txdb'); var TXDB = require('./txdb');
var keyTypes = bcoin.keyring.types;
var keyTypesByVal = bcoin.keyring.typesByVal;
/** /**
* BIP44 Wallet * BIP44 Wallet
@ -943,12 +941,12 @@ Wallet.prototype.createTX = function createTX(options, callback, force) {
if (!tx.checkInputs(self.db.height)) if (!tx.checkInputs(self.db.height))
return callback(new Error('CheckInputs failed.')); return callback(new Error('CheckInputs failed.'));
self.scriptInputs(tx, function(err, total) { self.template(tx, function(err, total) {
if (err) if (err)
return callback(err); return callback(err);
if (total === 0) if (total === 0)
return callback(new Error('scriptInputs failed.')); return callback(new Error('template failed.'));
callback(null, tx); callback(null, tx);
}); });
@ -1029,11 +1027,16 @@ Wallet.prototype.resend = function resend(callback) {
* @param {Function} callback - Returns [Error, {@link KeyRing}[]]. * @param {Function} callback - Returns [Error, {@link KeyRing}[]].
*/ */
Wallet.prototype.deriveInputs = function deriveInputs(tx, callback) { Wallet.prototype.deriveInputs = function deriveInputs(tx, master, callback) {
var self = this; var self = this;
var rings = []; var rings = [];
var ring; var ring;
if (typeof master === 'function') {
callback = master;
master = null;
}
this.getInputPaths(tx, function(err, paths) { this.getInputPaths(tx, function(err, paths) {
if (err) if (err)
return callback(err); return callback(err);
@ -1046,7 +1049,7 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx, callback) {
if (!account) if (!account)
return next(); return next();
ring = account.deriveAddress(path.change, path.index); ring = account.deriveAddress(path.change, path.index, master);
rings.push(ring); rings.push(ring);
next(); next();
@ -1088,7 +1091,7 @@ Wallet.prototype.getKeyring = function getKeyring(address, callback) {
if (!account) if (!account)
return callback(); return callback();
ring = account.deriveAddress(path.change, path.index); ring = account.deriveAddress(path.change, path.index, self.master.key);
callback(null, ring); callback(null, ring);
}); });
@ -1365,16 +1368,18 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) {
* (total number of scripts built). * (total number of scripts built).
*/ */
Wallet.prototype.scriptInputs = function scriptInputs(tx, callback) { Wallet.prototype.template = function template(tx, callback) {
var total = 0; var total = 0;
var i; var i, ring;
this.deriveInputs(tx, function(err, rings) { this.deriveInputs(tx, function(err, rings) {
if (err) if (err)
return callback(err); return callback(err);
for (i = 0; i < rings.length; i++) for (i = 0; i < rings.length; i++) {
total += rings[i].scriptInputs(tx); ring = rings[i];
total += tx.template(ring);
}
callback(null, total); callback(null, total);
}); });
@ -1404,15 +1409,15 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
passphrase = options.passphrase; passphrase = options.passphrase;
timeout = options.timeout; timeout = options.timeout;
this.deriveInputs(tx, function(err, rings) { this.unlock(passphrase, timeout, function(err, master) {
if (err) if (err)
return callback(err); return callback(err);
self.unlock(passphrase, timeout, function(err, master) { self.deriveInputs(tx, master, function(err, rings) {
if (err) if (err)
return callback(err); return callback(err);
self.signAsync(rings, master, tx, callback); self.signAsync(rings, tx, callback);
}); });
}); });
}; };
@ -1420,51 +1425,25 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
/** /**
* Sign a transaction asynchronously. * Sign a transaction asynchronously.
* @param {KeyRing[]} rings * @param {KeyRing[]} rings
* @param {HDPrivateKey} master
* @param {MTX} tx * @param {MTX} tx
* @param {Number?} index
* @param {SighashType?} 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.signAsync = function signAsync(rings, master, tx, callback) { Wallet.prototype.signAsync = function signAsync(rings, 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); result = tx.sign(rings);
} catch (e) { } catch (e) {
return callback(e); return callback(e);
} }
return callback(null, result); return callback(null, result);
} }
this.workerPool.sign(rings, master, tx, callback); this.workerPool.sign(tx, rings, null, 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;
}; };
/** /**
@ -2072,7 +2051,7 @@ Wallet.fromJSON = function fromJSON(db, json) {
Wallet.isWallet = function isWallet(obj) { Wallet.isWallet = function isWallet(obj) {
return obj return obj
&& typeof obj.accountDepth === 'number' && typeof obj.accountDepth === 'number'
&& obj.scriptInputs === 'function'; && obj.template === 'function';
}; };
/** /**
@ -2120,7 +2099,7 @@ function Account(db, options) {
this.accountIndex = 0; this.accountIndex = 0;
this.receiveDepth = 0; this.receiveDepth = 0;
this.changeDepth = 0; this.changeDepth = 0;
this.type = keyTypes.PUBKEYHASH; this.type = Account.types.PUBKEYHASH;
this.m = 1; this.m = 1;
this.n = 1; this.n = 1;
this.keys = []; this.keys = [];
@ -2130,6 +2109,27 @@ function Account(db, options) {
this.fromOptions(options); this.fromOptions(options);
} }
/**
* Account types.
* @enum {Number}
* @default
*/
Account.types = {
PUBKEYHASH: 0,
MULTISIG: 1
};
/**
* Account types by value.
* @const {RevMap}
*/
Account.typesByVal = {
0: 'pubkeyhash',
1: 'multisig'
};
/** /**
* Inject properties from options object. * Inject properties from options object.
* @private * @private
@ -2177,12 +2177,12 @@ Account.prototype.fromOptions = function fromOptions(options) {
if (options.type != null) { if (options.type != null) {
if (typeof options.type === 'string') { if (typeof options.type === 'string') {
this.type = keyTypes[options.type.toUpperCase()]; this.type = Account.types[options.type.toUpperCase()];
assert(this.type != null); assert(this.type != null);
} else { } else {
assert(typeof options.type === 'number'); assert(typeof options.type === 'number');
this.type = options.type; this.type = options.type;
assert(keyTypesByVal[this.type]); assert(Account.typesByVal[this.type]);
} }
} }
@ -2202,7 +2202,7 @@ Account.prototype.fromOptions = function fromOptions(options) {
} }
if (this.n > 1) if (this.n > 1)
this.type = keyTypes.MULTISIG; this.type = Account.types.MULTISIG;
if (this.m < 1 || this.m > this.n) if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n'); throw new Error('m ranges between 1 and n');
@ -2384,7 +2384,7 @@ Account.prototype._checkKeys = function _checkKeys(callback) {
var self = this; var self = this;
var ring, hash; var ring, hash;
if (this.initialized || this.type !== keyTypes.MULTISIG) if (this.initialized || this.type !== Account.types.MULTISIG)
return callback(null, false); return callback(null, false);
if (this.keys.length !== this.n - 1) if (this.keys.length !== this.n - 1)
@ -2486,8 +2486,8 @@ Account.prototype.createAddress = function createAddress(change, callback) {
* @returns {KeyRing} * @returns {KeyRing}
*/ */
Account.prototype.deriveReceive = function deriveReceive(index) { Account.prototype.deriveReceive = function deriveReceive(index, master) {
return this.deriveAddress(false, index); return this.deriveAddress(false, index, master);
}; };
/** /**
@ -2496,8 +2496,8 @@ Account.prototype.deriveReceive = function deriveReceive(index) {
* @returns {KeyRing} * @returns {KeyRing}
*/ */
Account.prototype.deriveChange = function deriveChange(index) { Account.prototype.deriveChange = function deriveChange(index, master) {
return this.deriveAddress(true, index); return this.deriveAddress(true, index, master);
}; };
/** /**
@ -2507,13 +2507,20 @@ Account.prototype.deriveChange = function deriveChange(index) {
* @returns {KeyRing} * @returns {KeyRing}
*/ */
Account.prototype.deriveAddress = function deriveAddress(change, index) { Account.prototype.deriveAddress = function deriveAddress(change, index, master) {
var keys = []; var keys = [];
var i, key, shared; var i, key, shared, ring;
change = +change; change = +change;
key = this.accountKey.derive(change).derive(index); if (master) {
key = master.deriveAccount44(this.accountIndex);
key = key.derive(change).derive(index);
} else {
key = this.accountKey.derive(change).derive(index);
}
keys.push(key.publicKey);
for (i = 0; i < this.keys.length; i++) { for (i = 0; i < this.keys.length; i++) {
shared = this.keys[i]; shared = this.keys[i];
@ -2521,7 +2528,12 @@ Account.prototype.deriveAddress = function deriveAddress(change, index) {
keys.push(shared.publicKey); keys.push(shared.publicKey);
} }
return bcoin.keyring.fromAccount(this, key, keys, change, index); ring = bcoin.keyring.fromAccount(this, key, keys, change, index);
if (master)
ring.privateKey = key.privateKey;
return ring;
}; };
/** /**
@ -2607,7 +2619,7 @@ Account.prototype.inspect = function inspect() {
name: this.name, name: this.name,
network: this.network, network: this.network,
initialized: this.initialized, initialized: this.initialized,
type: keyTypesByVal[this.type].toLowerCase(), type: Account.typesByVal[this.type].toLowerCase(),
m: this.m, m: this.m,
n: this.n, n: this.n,
address: this.initialized address: this.initialized
@ -2639,7 +2651,7 @@ Account.prototype.toJSON = function toJSON() {
wid: this.wid, wid: this.wid,
name: this.name, name: this.name,
initialized: this.initialized, initialized: this.initialized,
type: keyTypesByVal[this.type].toLowerCase(), type: Account.typesByVal[this.type].toLowerCase(),
m: this.m, m: this.m,
n: this.n, n: this.n,
witness: this.witness, witness: this.witness,
@ -2688,7 +2700,7 @@ Account.prototype.fromJSON = function fromJSON(json) {
this.wid = json.wid; this.wid = json.wid;
this.name = json.name; this.name = json.name;
this.initialized = json.initialized; this.initialized = json.initialized;
this.type = keyTypes[json.type.toUpperCase()]; this.type = Account.types[json.type.toUpperCase()];
this.m = json.m; this.m = json.m;
this.n = json.n; this.n = json.n;
this.witness = json.witness; this.witness = json.witness;
@ -2763,7 +2775,7 @@ Account.prototype.fromRaw = function fromRaw(data) {
this.changeDepth = p.readU32(); this.changeDepth = p.readU32();
this.accountKey = bcoin.hd.fromRaw(p.readBytes(82)); this.accountKey = bcoin.hd.fromRaw(p.readBytes(82));
assert(keyTypesByVal[this.type]); assert(Account.typesByVal[this.type]);
count = p.readU8(); count = p.readU8();

View File

@ -1673,7 +1673,7 @@ Path.prototype.fromKeyRing = function fromKeyRing(ring) {
this.index = ring.index; this.index = ring.index;
this.version = ring.witness ? 0 : -1; this.version = ring.witness ? 0 : -1;
this.type = ring.getScriptType(); this.type = ring.getType();
this.id = ring.id; this.id = ring.id;
this.hash = ring.getHash('hex'); this.hash = ring.getHash('hex');

View File

@ -248,11 +248,10 @@ Workers.prototype.verify = function verify(tx, flags, callback) {
* @param {Function} callback * @param {Function} callback
*/ */
Workers.prototype.sign = function sign(rings, master, tx, callback) { Workers.prototype.sign = function sign(tx, ring, type, callback) {
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', [tx, ring, type], -1, function(err, result) {
if (err) if (err)
return callback(err); return callback(err);
@ -775,8 +774,8 @@ jobs.verify = function verify(tx, flags) {
* @param {MTX} tx * @param {MTX} tx
*/ */
jobs.sign = function sign(rings, master, tx) { jobs.sign = function sign(tx, ring, type) {
var total = bcoin.wallet.sign(rings, master, tx); var total = tx.sign(ring, type);
var sigs = []; var sigs = [];
var i, input; var i, input;

View File

@ -239,7 +239,7 @@ describe('Wallet', function() {
var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed) var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed)
.addOutput(w, 500); .addOutput(w, 500);
// Script inputs but do not sign // Script inputs but do not sign
w.scriptInputs(fake, function(err) { w.template(fake, function(err) {
assert.ifError(err); assert.ifError(err);
// Fake signature // Fake signature
fake.inputs[0].script.set(0, FAKE_SIG); fake.inputs[0].script.set(0, FAKE_SIG);