mtx: signing work.

This commit is contained in:
Christopher Jeffrey 2016-08-18 18:27:17 -07:00
parent b6c8362c63
commit a42d11cb80
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 161 additions and 102 deletions

View File

@ -623,16 +623,7 @@ KeyRing.prototype.getRedeem = function(hash) {
*/ */
KeyRing.prototype.scriptInputs = function scriptInputs(tx) { KeyRing.prototype.scriptInputs = function scriptInputs(tx) {
var total = 0; return tx.template(this.publicKey, this.script, this.program);
var i, input;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (tx.scriptInput(i, this))
total++;
}
return total;
}; };
/** /**
@ -644,16 +635,7 @@ KeyRing.prototype.scriptInputs = function scriptInputs(tx) {
*/ */
KeyRing.prototype.sign = function sign(tx, key) { KeyRing.prototype.sign = function sign(tx, key) {
var total = 0; return tx.sign(key, this.script, this.program);
var i, input;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (tx.sign(i, this, key))
total++;
}
return total;
}; };
/** /**

View File

@ -217,9 +217,28 @@ MTX.prototype.addOutput = function addOutput(options, value) {
* @throws on unavailable coins. * @throws on unavailable coins.
*/ */
MTX.prototype.scriptInput = function scriptInput(index, ring) { function getRedeem(hash, script, program) {
if (program) {
if (utils.equal(program.hash160(), hash))
return program;
}
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; var input, prev, redeem;
if (key.getPublicKey)
key = key.getPublicKey();
// Get the input // Get the input
input = this.inputs[index]; input = this.inputs[index];
assert(input); assert(input);
@ -233,10 +252,6 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
if (input.script.length !== 0 || input.witness.length !== 0) if (input.script.length !== 0 || input.witness.length !== 0)
return true; return true;
// Make sure this coin is ours.
if (!ring.ownOutput(input.coin))
return false;
// Get the previous output's script // Get the previous output's script
prev = input.coin.script; prev = input.coin.script;
@ -244,7 +259,7 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
// 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 = ring.getRedeem(prev.get(1)); redeem = getRedeem(prev.get(1), script, program);
if (!redeem) if (!redeem)
return false; return false;
@ -253,10 +268,10 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
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 = ring.getRedeem(redeem.get(1)); prev = getRedeem(redeem.get(1), script, program);
if (!prev) if (!prev)
return false; return false;
this.scriptVector(prev, input.witness, ring); this.scriptVector(prev, input.witness, key);
input.witness.push(prev.toRaw()); input.witness.push(prev.toRaw());
input.script.push(redeem.toRaw()); input.script.push(redeem.toRaw());
input.script.compile(); input.script.compile();
@ -265,8 +280,8 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
// P2WPKH nested within pay-to-scripthash. // P2WPKH nested within pay-to-scripthash.
if (redeem.isWitnessPubkeyhash()) { if (redeem.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(ring.keyHash); prev = Script.fromPubkeyhash(utils.hash160(key));
this.scriptVector(prev, input.witness, ring); this.scriptVector(prev, input.witness, key);
input.script.push(redeem.toRaw()); input.script.push(redeem.toRaw());
input.script.compile(); input.script.compile();
return true; return true;
@ -277,7 +292,7 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
} }
// Regular P2SH. // Regular P2SH.
this.scriptVector(redeem, input.script, ring); this.scriptVector(redeem, input.script, key);
input.script.push(redeem.toRaw()); input.script.push(redeem.toRaw());
input.script.compile(); input.script.compile();
return true; return true;
@ -287,12 +302,12 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
if (prev.isProgram()) { if (prev.isProgram()) {
// Bare P2WSH. // Bare P2WSH.
if (prev.isWitnessScripthash()) { if (prev.isWitnessScripthash()) {
redeem = ring.getRedeem(prev.get(1)); redeem = getRedeem(prev.get(1), script, program);
if (!redeem) if (!redeem)
return false; return false;
this.scriptVector(redeem, input.witness, ring); this.scriptVector(redeem, input.witness, key);
input.witness.push(redeem.toRaw()); input.witness.push(redeem.toRaw());
input.script.compile(); input.script.compile();
return true; return true;
@ -301,7 +316,7 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
// Bare P2WPKH. // Bare P2WPKH.
if (prev.isWitnessPubkeyhash()) { if (prev.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(prev.get(1)); prev = Script.fromPubkeyhash(prev.get(1));
this.scriptVector(prev, input.witness, ring); this.scriptVector(prev, input.witness, key);
input.script.compile(); input.script.compile();
return true; return true;
} }
@ -311,16 +326,16 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) {
} }
// Wow, a normal output! Praise be to Jengus and Gord. // Wow, a normal output! Praise be to Jengus and Gord.
this.scriptVector(prev, input.script, ring); this.scriptVector(prev, input.script, key);
return true; return true;
}; };
MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) { MTX.prototype.scriptVector = function scriptVector(prev, vector, key) {
var i, n; var i, n;
// P2PK // P2PK
if (prev.isPubkey()) { if (prev.isPubkey()) {
if (!utils.equal(prev.get(1), ring.publicKey)) if (!utils.equal(prev.get(1), key))
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
@ -334,7 +349,7 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) {
// P2PKH // P2PKH
if (prev.isPubkeyhash()) { if (prev.isPubkeyhash()) {
if (!utils.equal(prev.get(2), ring.keyHash)) if (!utils.equal(prev.get(2), utils.hash160(key)))
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
@ -342,14 +357,14 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) {
return true; return true;
vector.set(0, opcodes.OP_0); vector.set(0, opcodes.OP_0);
vector.set(1, ring.publicKey); vector.set(1, key);
return true; return true;
} }
// Multisig // Multisig
if (prev.isMultisig()) { if (prev.isMultisig()) {
if (prev.indexOf(ring.publicKey) === -1) if (prev.indexOf(key) === -1)
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
@ -371,7 +386,7 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) {
return true; return true;
} }
if (prev.indexOf(ring.publicKey) === -1) if (prev.indexOf(key) === -1)
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
@ -391,34 +406,6 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) {
} }
}; };
/**
* Create a signature suitable for inserting into scriptSigs/witnesses.
* @param {Number} index - Index of input being signed.
* @param {Script} prev - Previous output script or redeem script
* (in the case of witnesspubkeyhash, this should be the generated
* p2pkh script).
* @param {SighashType} type
* @param {Number} version - Sighash version (0=legacy, 1=segwit).
* @returns {Buffer} Signature in DER format.
*/
MTX.prototype.createSignature = function createSignature(index, prev, key, type, version) {
var hash;
if (type == null)
type = constants.hashType.ALL;
if (typeof type === 'string')
type = constants.hashType[type.toUpperCase()];
// Get the hash of the current tx, minus the other
// inputs, plus the sighash type.
hash = this.signatureHash(index, prev, type, version);
// Sign the transaction with our one input
return Script.sign(hash, key, type);
};
/** /**
* Sign an input. * Sign an input.
* @param {Number} index - Index of input being signed. * @param {Number} index - Index of input being signed.
@ -430,22 +417,21 @@ MTX.prototype.createSignature = function createSignature(index, prev, key, type,
* @throws on unavailable coins. * @throws on unavailable coins.
*/ */
MTX.prototype.signInput = function signInput(index, ring, key, type) { MTX.prototype.signInput = function signInput(index, key, type) {
var input = this.inputs[index]; var input = this.inputs[index];
var version = 0; var version = 0;
var redeem = false; var redeem = false;
var prev, vector, signature, result; var prev, vector, sig, result;
assert(input); assert(input);
if (key.getPrivateKey)
key = key.getPrivateKey();
// We should have previous outputs by now. // We should have previous outputs by now.
if (!input.coin) if (!input.coin)
return false; return false;
// Make sure this output is ours.
if (!ring.ownOutput(input.coin))
return false;
// Get the previous output's script // Get the previous output's script
prev = input.coin.script; prev = input.coin.script;
vector = input.script; vector = input.script;
@ -479,36 +465,37 @@ MTX.prototype.signInput = function signInput(index, ring, key, type) {
} }
// Create our signature. // Create our signature.
signature = this.createSignature(index, prev, key, type, version); sig = this.createSignature(index, prev, key, type, version);
if (redeem) { if (redeem) {
redeem = vector.pop(); redeem = vector.pop();
result = this.signVector(prev, vector, signature, ring); result = this.signVector(prev, vector, sig, key);
vector.push(redeem); vector.push(redeem);
vector.compile(); vector.compile();
return result; return result;
} }
return this.signVector(prev, vector, signature, ring); return this.signVector(prev, vector, sig, key);
}; };
MTX.prototype.signVector = function signVector(prev, vector, signature, ring) { MTX.prototype.signVector = function signVector(prev, vector, sig, key) {
var pub = bcoin.ec.publicKeyCreate(key, true);
var keys, i, m, n, signatures, keyIndex; var keys, i, m, n, signatures, keyIndex;
// P2PK // P2PK
if (prev.isPubkey()) { if (prev.isPubkey()) {
// Make sure the pubkey is ours.
if (!utils.equal(pub, prev.get(0)))
return false;
// Already signed. // Already signed.
if (Script.isSignature(vector.get(0))) if (Script.isSignature(vector.get(0)))
return true; return true;
// Make sure the pubkey is ours.
if (!utils.equal(ring.publicKey, prev.get(0)))
return false;
if (vector.getSmall(0) !== 0) if (vector.getSmall(0) !== 0)
throw new Error('Input has not been templated.'); throw new Error('Input has not been templated.');
vector.set(0, signature); vector.set(0, sig);
vector.compile(); vector.compile();
return true; return true;
@ -516,18 +503,18 @@ MTX.prototype.signVector = function signVector(prev, vector, signature, ring) {
// P2PKH // P2PKH
if (prev.isPubkeyhash()) { if (prev.isPubkeyhash()) {
// Make sure the pubkey hash is ours.
if (!utils.equal(utils.hash160(pub), prev.get(2)))
return false;
// Already signed. // Already signed.
if (Script.isSignature(vector.get(0))) if (Script.isSignature(vector.get(0)))
return true; return true;
// Make sure the pubkey hash is ours.
if (!utils.equal(ring.keyHash, prev.get(2)))
return false;
if (!Script.isKey(vector.get(1))) if (!Script.isKey(vector.get(1)))
throw new Error('Input has not been templated.'); throw new Error('Input has not been templated.');
vector.set(0, signature); vector.set(0, sig);
vector.compile(); vector.compile();
return true; return true;
@ -586,7 +573,7 @@ MTX.prototype.signVector = function signVector(prev, vector, signature, ring) {
// Find the key index so we can place // Find the key index so we can place
// the signature in the same index. // the signature in the same index.
keyIndex = utils.indexOf(keys, ring.publicKey); keyIndex = utils.indexOf(keys, pub);
// Our public key is not in the prev_out // Our public key is not in the prev_out
// script. We tried to sign a transaction // script. We tried to sign a transaction
@ -604,7 +591,7 @@ MTX.prototype.signVector = function signVector(prev, vector, signature, ring) {
// signatures. // signatures.
if (keyIndex < vector.length && signatures < m) { if (keyIndex < vector.length && signatures < m) {
if (vector.getSmall(keyIndex) === 0) { if (vector.getSmall(keyIndex) === 0) {
vector.set(keyIndex, signature); vector.set(keyIndex, sig);
signatures++; signatures++;
} }
} }
@ -688,6 +675,35 @@ MTX.prototype.combineMultisig = function combineMultisig(index, prev, version, s
return result; return result;
}; };
/**
* Create a signature suitable for inserting into scriptSigs/witnesses.
* @param {Number} index - Index of input being signed.
* @param {Script} prev - Previous output script or redeem script
* (in the case of witnesspubkeyhash, this should be the generated
* p2pkh script).
* @param {SighashType} type
* @param {Number} version - Sighash version (0=legacy, 1=segwit).
* @returns {Buffer} Signature in DER format.
*/
MTX.prototype.createSignature = function createSignature(index, prev, key, type, version) {
var hash;
if (type == null)
type = constants.hashType.ALL;
if (typeof type === 'string')
type = constants.hashType[type.toUpperCase()];
// Get the hash of the current tx, minus the other
// inputs, plus the sighash type.
hash = this.signatureHash(index, prev, type, version);
// Sign the transaction with our one input
return Script.sign(hash, key, type);
};
/** /**
* Test whether the transaction is fully-signed. * Test whether the transaction is fully-signed.
* @returns {Boolean} * @returns {Boolean}
@ -774,19 +790,56 @@ MTX.prototype.isSigned = function isSigned() {
* @throws on unavailable coins. * @throws on unavailable coins.
*/ */
MTX.prototype.sign = function sign(index, ring, key, type) { MTX.prototype.template = function template(key, script, program, type) {
var input = this.inputs[index]; var total = 0;
assert(input); var i;
// Build script for input if (key.getPublicKey)
if (!this.scriptInput(index, ring)) key = key.getPublicKey();
return false;
// Sign input for (i = 0; i < this.inputs.length; i++) {
if (!this.signInput(index, ring, key, type)) // Build script for input
return false; if (!this.buildInput(i, key, script, program))
continue;
total++;
}
return true; return total;
};
/**
* 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
* 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.sign = function sign(key, script, program, type) {
var total = 0;
var i, pub;
if (key.getPrivateKey)
key = key.getPrivateKey();
pub = bcoin.ec.publicKeyCreate(key, true);
for (i = 0; i < this.inputs.length; i++) {
// Build script for input
if (!this.buildInput(i, pub, script, program))
continue;
// Sign input
if (!this.signInput(i, key, type))
continue;
total++;
}
return total;
}; };
/** /**
@ -1412,7 +1465,7 @@ MTX.isMTX = function isMTX(obj) {
return obj return obj
&& Array.isArray(obj.inputs) && Array.isArray(obj.inputs)
&& typeof obj.locktime === 'number' && typeof obj.locktime === 'number'
&& typeof obj.scriptInput === 'function'; && typeof obj.buildInput === 'function';
}; };
/* /*

View File

@ -2846,6 +2846,30 @@ Script.prototype.getAddress = function getAddress() {
return bcoin.address.fromScript(this); return bcoin.address.fromScript(this);
}; };
/**
* Get the hash160 of the raw script.
* @param {Buffer}
*/
Script.prototype.hash160 = function hash160(enc) {
var hash = utils.hash160(this.toRaw());
if (enc === 'hex')
hash = hash.toString('hex');
return hash;
};
/**
* Get the sha256 of the raw script.
* @param {Buffer}
*/
Script.prototype.sha256 = function sha256(enc) {
var hash = utils.sha256(this.toRaw());
if (enc === 'hex')
hash = hash.toString('hex');
return hash;
};
/** /**
* Test whether the output script is pay-to-pubkey. * Test whether the output script is pay-to-pubkey.
* @param {Boolean} [minimal=false] - Minimaldata only. * @param {Boolean} [minimal=false] - Minimaldata only.