From a42d11cb8064abf6cca6ca52f21d8ac387c1db5b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 18 Aug 2016 18:27:17 -0700 Subject: [PATCH] mtx: signing work. --- lib/bcoin/keyring.js | 22 +---- lib/bcoin/mtx.js | 217 +++++++++++++++++++++++++++---------------- lib/bcoin/script.js | 24 +++++ 3 files changed, 161 insertions(+), 102 deletions(-) diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index 67877908..efccb1bb 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -623,16 +623,7 @@ KeyRing.prototype.getRedeem = function(hash) { */ KeyRing.prototype.scriptInputs = function scriptInputs(tx) { - var total = 0; - var i, input; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - if (tx.scriptInput(i, this)) - total++; - } - - return total; + return tx.template(this.publicKey, this.script, this.program); }; /** @@ -644,16 +635,7 @@ KeyRing.prototype.scriptInputs = function scriptInputs(tx) { */ KeyRing.prototype.sign = function sign(tx, key) { - var total = 0; - var i, input; - - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - if (tx.sign(i, this, key)) - total++; - } - - return total; + return tx.sign(key, this.script, this.program); }; /** diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 7419dd4e..4ff80089 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -217,9 +217,28 @@ MTX.prototype.addOutput = function addOutput(options, value) { * @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; + if (key.getPublicKey) + key = key.getPublicKey(); + // Get the input input = this.inputs[index]; assert(input); @@ -233,10 +252,6 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) { if (input.script.length !== 0 || input.witness.length !== 0) return true; - // Make sure this coin is ours. - if (!ring.ownOutput(input.coin)) - return false; - // Get the previous output's 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 // redeem scripts go. if (prev.isScripthash()) { - redeem = ring.getRedeem(prev.get(1)); + redeem = getRedeem(prev.get(1), script, program); if (!redeem) return false; @@ -253,10 +268,10 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) { if (redeem.isProgram()) { // P2WSH nested within pay-to-scripthash. if (redeem.isWitnessScripthash()) { - prev = ring.getRedeem(redeem.get(1)); + prev = getRedeem(redeem.get(1), script, program); if (!prev) return false; - this.scriptVector(prev, input.witness, ring); + this.scriptVector(prev, input.witness, key); input.witness.push(prev.toRaw()); input.script.push(redeem.toRaw()); input.script.compile(); @@ -265,8 +280,8 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) { // P2WPKH nested within pay-to-scripthash. if (redeem.isWitnessPubkeyhash()) { - prev = Script.fromPubkeyhash(ring.keyHash); - this.scriptVector(prev, input.witness, ring); + prev = Script.fromPubkeyhash(utils.hash160(key)); + this.scriptVector(prev, input.witness, key); input.script.push(redeem.toRaw()); input.script.compile(); return true; @@ -277,7 +292,7 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) { } // Regular P2SH. - this.scriptVector(redeem, input.script, ring); + this.scriptVector(redeem, input.script, key); input.script.push(redeem.toRaw()); input.script.compile(); return true; @@ -287,12 +302,12 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) { if (prev.isProgram()) { // Bare P2WSH. if (prev.isWitnessScripthash()) { - redeem = ring.getRedeem(prev.get(1)); + redeem = getRedeem(prev.get(1), script, program); if (!redeem) return false; - this.scriptVector(redeem, input.witness, ring); + this.scriptVector(redeem, input.witness, key); input.witness.push(redeem.toRaw()); input.script.compile(); return true; @@ -301,7 +316,7 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) { // Bare P2WPKH. if (prev.isWitnessPubkeyhash()) { prev = Script.fromPubkeyhash(prev.get(1)); - this.scriptVector(prev, input.witness, ring); + this.scriptVector(prev, input.witness, key); input.script.compile(); return true; } @@ -311,16 +326,16 @@ MTX.prototype.scriptInput = function scriptInput(index, ring) { } // Wow, a normal output! Praise be to Jengus and Gord. - this.scriptVector(prev, input.script, ring); + this.scriptVector(prev, input.script, key); return true; }; -MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) { +MTX.prototype.scriptVector = function scriptVector(prev, vector, key) { var i, n; // P2PK if (prev.isPubkey()) { - if (!utils.equal(prev.get(1), ring.publicKey)) + if (!utils.equal(prev.get(1), key)) return false; // Already has a script template (at least) @@ -334,7 +349,7 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) { // P2PKH if (prev.isPubkeyhash()) { - if (!utils.equal(prev.get(2), ring.keyHash)) + if (!utils.equal(prev.get(2), utils.hash160(key))) return false; // Already has a script template (at least) @@ -342,14 +357,14 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) { return true; vector.set(0, opcodes.OP_0); - vector.set(1, ring.publicKey); + vector.set(1, key); return true; } // Multisig if (prev.isMultisig()) { - if (prev.indexOf(ring.publicKey) === -1) + if (prev.indexOf(key) === -1) return false; // Already has a script template (at least) @@ -371,7 +386,7 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) { return true; } - if (prev.indexOf(ring.publicKey) === -1) + if (prev.indexOf(key) === -1) return false; // 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. * @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. */ -MTX.prototype.signInput = function signInput(index, ring, key, type) { +MTX.prototype.signInput = function signInput(index, key, type) { var input = this.inputs[index]; var version = 0; var redeem = false; - var prev, vector, signature, result; + var prev, vector, sig, result; assert(input); + if (key.getPrivateKey) + key = key.getPrivateKey(); + // We should have previous outputs by now. if (!input.coin) return false; - // Make sure this output is ours. - if (!ring.ownOutput(input.coin)) - return false; - // Get the previous output's script prev = input.coin.script; vector = input.script; @@ -479,36 +465,37 @@ MTX.prototype.signInput = function signInput(index, ring, key, type) { } // Create our signature. - signature = this.createSignature(index, prev, key, type, version); + sig = this.createSignature(index, prev, key, type, version); if (redeem) { redeem = vector.pop(); - result = this.signVector(prev, vector, signature, ring); + result = this.signVector(prev, vector, sig, key); vector.push(redeem); vector.compile(); 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; // P2PK if (prev.isPubkey()) { + // Make sure the pubkey is ours. + if (!utils.equal(pub, prev.get(0))) + return false; + // Already signed. if (Script.isSignature(vector.get(0))) return true; - // Make sure the pubkey is ours. - if (!utils.equal(ring.publicKey, prev.get(0))) - return false; - if (vector.getSmall(0) !== 0) throw new Error('Input has not been templated.'); - vector.set(0, signature); + vector.set(0, sig); vector.compile(); return true; @@ -516,18 +503,18 @@ MTX.prototype.signVector = function signVector(prev, vector, signature, ring) { // P2PKH if (prev.isPubkeyhash()) { + // Make sure the pubkey hash is ours. + if (!utils.equal(utils.hash160(pub), prev.get(2))) + return false; + // Already signed. if (Script.isSignature(vector.get(0))) 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))) throw new Error('Input has not been templated.'); - vector.set(0, signature); + vector.set(0, sig); vector.compile(); return true; @@ -586,7 +573,7 @@ MTX.prototype.signVector = function signVector(prev, vector, signature, ring) { // Find the key index so we can place // 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 // script. We tried to sign a transaction @@ -604,7 +591,7 @@ MTX.prototype.signVector = function signVector(prev, vector, signature, ring) { // signatures. if (keyIndex < vector.length && signatures < m) { if (vector.getSmall(keyIndex) === 0) { - vector.set(keyIndex, signature); + vector.set(keyIndex, sig); signatures++; } } @@ -688,6 +675,35 @@ MTX.prototype.combineMultisig = function combineMultisig(index, prev, version, s 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. * @returns {Boolean} @@ -774,19 +790,56 @@ MTX.prototype.isSigned = function isSigned() { * @throws on unavailable coins. */ -MTX.prototype.sign = function sign(index, ring, key, type) { - var input = this.inputs[index]; - assert(input); +MTX.prototype.template = function template(key, script, program, type) { + var total = 0; + var i; - // Build script for input - if (!this.scriptInput(index, ring)) - return false; + if (key.getPublicKey) + key = key.getPublicKey(); - // Sign input - if (!this.signInput(index, ring, key, type)) - return false; + for (i = 0; i < this.inputs.length; i++) { + // Build script for input + 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 && Array.isArray(obj.inputs) && typeof obj.locktime === 'number' - && typeof obj.scriptInput === 'function'; + && typeof obj.buildInput === 'function'; }; /* diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index bf842558..bd989685 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -2846,6 +2846,30 @@ Script.prototype.getAddress = function getAddress() { 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. * @param {Boolean} [minimal=false] - Minimaldata only.