mtx: refactor.

This commit is contained in:
Christopher Jeffrey 2016-08-18 20:03:58 -07:00
parent a42d11cb80
commit 34f59e8a60
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 249 additions and 175 deletions

View File

@ -623,7 +623,7 @@ KeyRing.prototype.getRedeem = function(hash) {
*/
KeyRing.prototype.scriptInputs = function scriptInputs(tx) {
return tx.template(this.publicKey, this.script, this.program);
return tx.template(this.publicKey, this.script);
};
/**
@ -635,7 +635,7 @@ KeyRing.prototype.scriptInputs = function scriptInputs(tx) {
*/
KeyRing.prototype.sign = function sign(tx, key) {
return tx.sign(key, this.script, this.program);
return tx.sign(key, this.script);
};
/**

View File

@ -211,37 +211,16 @@ MTX.prototype.addOutput = function addOutput(options, value) {
* Build input script (or witness) templates (with
* OP_0 in place of signatures).
* @param {Number} index - Input index.
* @param {KeyRing} ring - Address used to build. The address
* must be able to redeem the coin.
* @param {Buffer} key - Public key.
* @param {Script} script - Redeem script.
* @returns {Boolean} Whether the script was able to be built.
* @throws on unavailable coins.
*/
function getRedeem(hash, script, program) {
if (program) {
if (utils.equal(program.hash160(), hash))
return program;
}
MTX.prototype.scriptInput = function scriptInput(index, key, script) {
var input = this.inputs[index];
var prev, redeem;
if (script) {
if (utils.equal(script.hash160(), hash))
return script;
if (utils.equal(script.sha256(), hash))
return script;
}
};
MTX.prototype.buildInput = function buildInput(index, key, script, program) {
var input, prev, redeem;
if (key.getPublicKey)
key = key.getPublicKey();
// Get the input
input = this.inputs[index];
assert(input);
assert(input, 'Input does not exist.');
// We should have previous outputs by now.
if (!input.coin)
@ -249,8 +228,13 @@ MTX.prototype.buildInput = function buildInput(index, key, script, program) {
// Don't bother with any below calculation
// if the output is already templated.
if (input.script.length !== 0 || input.witness.length !== 0)
if (input.script.length !== 0
|| input.witness.length !== 0) {
return true;
}
if (key.getPublicKey)
key = key.getPublicKey();
// Get the previous output's script
prev = input.coin.script;
@ -259,7 +243,7 @@ MTX.prototype.buildInput = function buildInput(index, key, script, program) {
// with segwit: figuring out where the redeem script and witness
// redeem scripts go.
if (prev.isScripthash()) {
redeem = getRedeem(prev.get(1), script, program);
redeem = this._getRedeem(prev.get(1), key, script);
if (!redeem)
return false;
@ -268,22 +252,31 @@ MTX.prototype.buildInput = function buildInput(index, key, script, program) {
if (redeem.isProgram()) {
// P2WSH nested within pay-to-scripthash.
if (redeem.isWitnessScripthash()) {
prev = getRedeem(redeem.get(1), script, program);
prev = this._getRedeem(redeem.get(1), key, script);
if (!prev)
return false;
this.scriptVector(prev, input.witness, key);
if (!this.scriptVector(prev, input.witness, key))
return false;
input.witness.push(prev.toRaw());
input.script.push(redeem.toRaw());
input.script.compile();
return true;
}
// P2WPKH nested within pay-to-scripthash.
if (redeem.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(utils.hash160(key));
this.scriptVector(prev, input.witness, key);
if (!this.scriptVector(prev, input.witness, key))
return false;
input.script.push(redeem.toRaw());
input.script.compile();
return true;
}
@ -292,9 +285,12 @@ MTX.prototype.buildInput = function buildInput(index, key, script, program) {
}
// Regular P2SH.
this.scriptVector(redeem, input.script, key);
if (!this.scriptVector(redeem, input.script, key))
return false;
input.script.push(redeem.toRaw());
input.script.compile();
return true;
}
@ -302,22 +298,29 @@ MTX.prototype.buildInput = function buildInput(index, key, script, program) {
if (prev.isProgram()) {
// Bare P2WSH.
if (prev.isWitnessScripthash()) {
redeem = getRedeem(prev.get(1), script, program);
redeem = this._getRedeem(prev.get(1), key, script);
if (!redeem)
return false;
this.scriptVector(redeem, input.witness, key);
if (!this.scriptVector(redeem, input.witness, key))
return false;
input.witness.push(redeem.toRaw());
input.script.compile();
return true;
}
// Bare P2WPKH.
if (prev.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(prev.get(1));
this.scriptVector(prev, input.witness, key);
if (!this.scriptVector(prev, input.witness, key))
return false;
input.script.compile();
return true;
}
@ -326,10 +329,18 @@ MTX.prototype.buildInput = function buildInput(index, key, script, program) {
}
// Wow, a normal output! Praise be to Jengus and Gord.
this.scriptVector(prev, input.script, key);
return true;
return this.scriptVector(prev, input.script, key);
};
/**
* Build script for a single vector
* based on a previous script.
* @param {Script} prev
* @param {Witness|Script} vector
* @param {Buffer} key
* @return {Boolean}
*/
MTX.prototype.scriptVector = function scriptVector(prev, vector, key) {
var i, n;
@ -338,10 +349,6 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, key) {
if (!utils.equal(prev.get(1), key))
return false;
// Already has a script template (at least)
if (vector.length !== 0)
return true;
vector.set(0, opcodes.OP_0);
return true;
@ -352,10 +359,6 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, key) {
if (!utils.equal(prev.get(2), utils.hash160(key)))
return false;
// Already has a script template (at least)
if (vector.length !== 0)
return true;
vector.set(0, opcodes.OP_0);
vector.set(1, key);
@ -367,10 +370,6 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, key) {
if (prev.indexOf(key) === -1)
return false;
// Already has a script template (at least)
if (vector.length !== 0)
return true;
// Technically we should create m signature slots,
// but we create n signature slots so we can order
// the signatures properly.
@ -386,35 +385,61 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, key) {
return true;
}
if (prev.indexOf(key) === -1)
return false;
return false;
};
// Already has a script template (at least)
if (vector.length !== 0)
return true;
/**
* 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}
*/
// Likely a non-standard scripthash multisig
// input. Determine n value by counting keys.
// Also, only allow nonstandard types for
// scripthash.
vector.set(0, opcodes.OP_0);
MTX.prototype._getRedeem = function getRedeem(hash, key, script) {
var program;
// Fill script with `n` signature slots.
for (i = 0; i < prev.length; i++) {
if (Script.isKey(prev.get(i)))
vector.set(i + 1, opcodes.OP_0);
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.
* @param {Number} index - Index of input being signed.
* @param {KeyRing} ring - Address used to sign. The address
* 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.
* @throws on unavailable coins.
*/
MTX.prototype.signInput = function signInput(index, key, type) {
@ -423,15 +448,15 @@ MTX.prototype.signInput = function signInput(index, key, type) {
var redeem = false;
var prev, vector, sig, result;
assert(input);
if (key.getPrivateKey)
key = key.getPrivateKey();
assert(input, 'Input does not exist.');
// We should have previous outputs by now.
if (!input.coin)
return false;
if (key.getPrivateKey)
key = key.getPrivateKey();
// Get the previous output's script
prev = input.coin.script;
vector = input.script;
@ -465,7 +490,7 @@ MTX.prototype.signInput = function signInput(index, key, type) {
}
// Create our signature.
sig = this.createSignature(index, prev, key, type, version);
sig = this.signature(index, prev, key, type, version);
if (redeem) {
redeem = vector.pop();
@ -478,9 +503,19 @@ MTX.prototype.signInput = function signInput(index, key, type) {
return this.signVector(prev, vector, sig, key);
};
/**
* Add a signature to a vector
* based on a previous script.
* @param {Script} prev
* @param {Witness|Script} vector
* @param {Buffer} sig
* @param {Buffer} key
* @return {Boolean}
*/
MTX.prototype.signVector = function signVector(prev, vector, sig, key) {
var pub = bcoin.ec.publicKeyCreate(key, true);
var keys, i, m, n, signatures, keyIndex;
var i, m, n, keys, keyIndex, total;
// P2PK
if (prev.isPubkey()) {
@ -534,102 +569,109 @@ MTX.prototype.signVector = function signVector(prev, vector, sig, key) {
// Grab `n` value (number of keys).
n = prev.getSmall(prev.length - 2);
} else {
keys = [];
for (i = 0; i < prev.length; i++) {
if (Script.isKey(prev.get(i)))
keys.push(prev.get(i));
if (vector.getSmall(0) !== 0)
throw new Error('Input has not been templated.');
// Too many signature slots. Abort.
if (vector.length - 1 > n)
return false;
// Count the number of current signatures.
total = 0;
for (i = 1; i < vector.length; i++) {
if (Script.isSignature(vector.get(i)))
total++;
}
// We don't know what m is, so
// we can never finalize the signatures.
m = keys.length;
n = keys.length;
}
// Signatures are already finalized.
if (total === m && vector.length - 1 === m)
return true;
if (vector.getSmall(0) !== 0)
throw new Error('Input has not been templated.');
// Add some signature slots for us to use if
// there was for some reason not enough.
while (vector.length - 1 < n)
vector.push(opcodes.OP_0);
// Too many signature slots. Abort.
if (vector.length - 1 > n)
return false;
// Find the key index so we can place
// the signature in the same index.
keyIndex = utils.indexOf(keys, pub);
// Count the number of current signatures.
signatures = 0;
for (i = 1; i < vector.length; i++) {
if (Script.isSignature(vector.get(i)))
signatures++;
}
// Our public key is not in the prev_out
// script. We tried to sign a transaction
// that is not redeemable by us.
if (keyIndex === -1)
return false;
// Offset key index by one to turn it into
// "sig index". Accounts for OP_0 byte at
// the start.
keyIndex++;
// Add our signature to the correct slot
// and increment the total number of
// signatures.
if (keyIndex < vector.length && total < m) {
if (vector.getSmall(keyIndex) === 0) {
vector.set(keyIndex, sig);
total++;
}
}
// All signatures added. Finalize.
if (total >= m) {
// Remove empty slots left over.
for (i = vector.length - 1; i >= 1; i--) {
if (vector.getSmall(i) === 0)
vector.remove(i);
}
// Remove signatures which are not required.
// This should never happen.
while (total > m) {
vector.pop();
total--;
}
// Sanity checks.
assert(total === m);
assert(vector.length - 1 === m);
}
vector.compile();
if (total !== m)
return false;
// Signatures are already finalized.
if (signatures === m && vector.length - 1 === m)
return true;
// Add some signature slots for us to use if
// there was for some reason not enough.
while (vector.length - 1 < n)
vector.push(opcodes.OP_0);
// Find the key index so we can place
// the signature in the same index.
keyIndex = utils.indexOf(keys, pub);
// Our public key is not in the prev_out
// script. We tried to sign a transaction
// that is not redeemable by us.
if (keyIndex === -1)
return false;
// Offset key index by one to turn it into
// "sig index". Accounts for OP_0 byte at
// the start.
keyIndex++;
// Add our signature to the correct slot
// and increment the total number of
// signatures.
if (keyIndex < vector.length && signatures < m) {
if (vector.getSmall(keyIndex) === 0) {
vector.set(keyIndex, sig);
signatures++;
}
}
// All signatures added. Finalize.
if (signatures >= m) {
// Remove empty slots left over.
for (i = vector.length - 1; i >= 1; i--) {
if (vector.getSmall(i) === 0)
vector.remove(i);
}
// Remove signatures which are not required.
// This should never happen.
while (signatures > m) {
vector.pop();
signatures--;
}
// Sanity checks.
assert(signatures === m);
assert(vector.length - 1 === m);
}
vector.compile();
return signatures === m;
return false;
};
MTX.prototype.combineMultisig = function combineMultisig(index, prev, version, script, signature) {
/**
* Combine and sort multisig signatures for script.
* Mimics bitcoind's behavior.
* @param {Number} index
* @param {Script} prev
* @param {Witness|Script} vector
* @param {Number} version
* @param {Buffer} data
* @return {Boolean}
*/
MTX.prototype.combine = function combine(index, prev, vector, version, data) {
var m = prev.getSmall(0);
var sigs = [signature];
var sigs = [];
var map = {};
var result;
var result = false;
var i, j, sig, type, msg, key, pub, res;
for (i = 1; i < script.length; i++) {
sig = script.get(i);
if (data)
sigs.push(data);
for (i = 1; i < vector.length; i++) {
sig = vector.get(i);
if (Script.isSignature(sig))
sigs.push(sig);
}
@ -651,27 +693,29 @@ MTX.prototype.combineMultisig = function combineMultisig(index, prev, version, s
if (res) {
map[pub] = sig;
if (utils.equal(sig, signature))
if (utils.equal(sig, data))
result = true;
break;
}
}
}
script.clear();
script.push(opcodes.OP_0);
vector.clear();
vector.push(opcodes.OP_0);
for (i = 1; i < prev.length - 2; i++) {
key = prev.get(i);
pub = key.toString('hex');
sig = map[pub];
if (sig)
script.push(sig);
vector.push(sig);
}
while (script.length - 1 < m)
script.push(opcodes.OP_0);
while (vector.length - 1 < m)
vector.push(opcodes.OP_0);
vector.compile();
script.compile();
return result;
};
@ -686,7 +730,7 @@ MTX.prototype.combineMultisig = function combineMultisig(index, prev, version, s
* @returns {Buffer} Signature in DER format.
*/
MTX.prototype.createSignature = function createSignature(index, prev, key, type, version) {
MTX.prototype.signature = function signature(index, prev, key, type, version) {
var hash;
if (type == null)
@ -703,7 +747,6 @@ MTX.prototype.createSignature = function createSignature(index, prev, key, type,
return Script.sign(hash, key, type);
};
/**
* Test whether the transaction is fully-signed.
* @returns {Boolean}
@ -754,10 +797,16 @@ MTX.prototype.isSigned = function isSigned() {
if (prev.isPubkey()) {
if (!Script.isSignature(vector.get(0)))
return false;
} else if (prev.isPubkeyhash()) {
continue;
}
if (prev.isPubkeyhash()) {
if (!Script.isSignature(vector.get(0)))
return false;
} else if (prev.isMultisig()) {
continue;
}
if (prev.isMultisig()) {
// Grab `m` value (number of required sigs).
m = prev.getSmall(0);
@ -771,9 +820,11 @@ MTX.prototype.isSigned = function isSigned() {
// of required signatures.
if (len - 1 !== m)
return false;
} else {
return false;
continue;
}
return false;
}
return true;
@ -790,7 +841,7 @@ MTX.prototype.isSigned = function isSigned() {
* @throws on unavailable coins.
*/
MTX.prototype.template = function template(key, script, program, type) {
MTX.prototype.template = function template(key, script) {
var total = 0;
var i;
@ -799,7 +850,7 @@ MTX.prototype.template = function template(key, script, program, type) {
for (i = 0; i < this.inputs.length; i++) {
// Build script for input
if (!this.buildInput(i, key, script, program))
if (!this.scriptInput(i, key, script))
continue;
total++;
}
@ -818,7 +869,7 @@ MTX.prototype.template = function template(key, script, program, type) {
* @throws on unavailable coins.
*/
MTX.prototype.sign = function sign(key, script, program, type) {
MTX.prototype.sign = function sign(key, script, type) {
var total = 0;
var i, pub;
@ -829,7 +880,7 @@ MTX.prototype.sign = function sign(key, script, program, type) {
for (i = 0; i < this.inputs.length; i++) {
// Build script for input
if (!this.buildInput(i, pub, script, program))
if (!this.scriptInput(i, pub, script))
continue;
// Sign input
@ -1465,7 +1516,7 @@ MTX.isMTX = function isMTX(obj) {
return obj
&& Array.isArray(obj.inputs)
&& typeof obj.locktime === 'number'
&& typeof obj.buildInput === 'function';
&& typeof obj.scriptInput === 'function';
};
/*

View File

@ -3085,6 +3085,29 @@ Script.prototype.toProgram = function toProgram() {
return new Program(version, data);
};
/**
* Get the script to the equivalent witness
* program (mimics bitcoind's scriptForWitness).
* @returns {Program|null}
*/
Script.prototype.forWitness = function() {
var hash;
if (this.isProgram())
return this;
if (this.isPubkey()) {
hash = utils.hash160(this.get(0));
return Script.fromProgram(0, hash);
}
if (this.isPubkeyhash())
return Script.fromProgram(0, this.get(2));
return Script.fromProgram(0, this.sha256());
};
/**
* Test whether the output script is
* a pay-to-witness-pubkeyhash program.

View File

@ -879,8 +879,8 @@ Wallet.prototype.fund = function fund(tx, options, callback, force) {
height: self.db.height,
rate: rate,
wallet: self,
m: self.m,
n: self.n
m: account.m,
n: account.n
});
} catch (e) {
return callback(e);