From ee3d00107f16f2af985d9733e4ffb26a764f74ba Mon Sep 17 00:00:00 2001 From: Kenny Joseph Date: Thu, 12 Oct 2023 15:19:12 -0400 Subject: [PATCH 1/2] fix sig validation for sigtype > 1 --- packages/bitcore-lib-ltc/index.js | 1 + .../bitcore-lib-ltc/lib/transaction/input/publickeyhash.js | 2 +- packages/bitcore-lib/index.js | 1 + packages/bitcore-lib/lib/transaction/input/publickeyhash.js | 2 +- packages/bitcore-lib/lib/transaction/sighashwitness.js | 6 +++--- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/bitcore-lib-ltc/index.js b/packages/bitcore-lib-ltc/index.js index cecbaee619f..326d6dc7631 100644 --- a/packages/bitcore-lib-ltc/index.js +++ b/packages/bitcore-lib-ltc/index.js @@ -68,3 +68,4 @@ litecore.deps._ = require('lodash'); // Internal usage, exposed for testing/advanced tweaking litecore.Transaction.sighash = require('./lib/transaction/sighash'); +litecore.Transaction.sighashwitness = require('./lib/transaction/sighashwitness'); diff --git a/packages/bitcore-lib-ltc/lib/transaction/input/publickeyhash.js b/packages/bitcore-lib-ltc/lib/transaction/input/publickeyhash.js index fc819d02a78..379c9c1b9d5 100644 --- a/packages/bitcore-lib-ltc/lib/transaction/input/publickeyhash.js +++ b/packages/bitcore-lib-ltc/lib/transaction/input/publickeyhash.js @@ -157,7 +157,7 @@ PublicKeyHashInput.prototype.isValidSignature = function(transaction, signature, // FIXME: Refactor signature so this is not necessary signature.signature.nhashtype = signature.sigtype; if (this.output.script.isWitnessPublicKeyHashOut() || this.output.script.isScriptHashOut()) { - var scriptCode = this.getScriptCode(); + var scriptCode = this.getScriptCode(signature.publicKey); var satoshisBuffer = this.getSatoshisBuffer(); return SighashWitness.verify( transaction, diff --git a/packages/bitcore-lib/index.js b/packages/bitcore-lib/index.js index 4cbe6bf2ac6..8b8c8eac2e9 100644 --- a/packages/bitcore-lib/index.js +++ b/packages/bitcore-lib/index.js @@ -68,3 +68,4 @@ bitcore.deps._ = require('lodash'); // Internal usage, exposed for testing/advanced tweaking bitcore.Transaction.sighash = require('./lib/transaction/sighash'); +bitcore.Transaction.sighashwitness = require('./lib/transaction/sighashwitness'); diff --git a/packages/bitcore-lib/lib/transaction/input/publickeyhash.js b/packages/bitcore-lib/lib/transaction/input/publickeyhash.js index 374440472a1..1129711e3dd 100644 --- a/packages/bitcore-lib/lib/transaction/input/publickeyhash.js +++ b/packages/bitcore-lib/lib/transaction/input/publickeyhash.js @@ -158,7 +158,7 @@ PublicKeyHashInput.prototype.isValidSignature = function(transaction, signature, // FIXME: Refactor signature so this is not necessary signature.signature.nhashtype = signature.sigtype; if (this.output.script.isWitnessPublicKeyHashOut() || this.output.script.isScriptHashOut()) { - var scriptCode = this.getScriptCode(); + var scriptCode = this.getScriptCode(signature.publicKey); var satoshisBuffer = this.getSatoshisBuffer(); return SighashWitness.verify( transaction, diff --git a/packages/bitcore-lib/lib/transaction/sighashwitness.js b/packages/bitcore-lib/lib/transaction/sighashwitness.js index 96ee142b0dd..c03dcb1774d 100644 --- a/packages/bitcore-lib/lib/transaction/sighashwitness.js +++ b/packages/bitcore-lib/lib/transaction/sighashwitness.js @@ -28,9 +28,9 @@ var _ = require('lodash'); var sighash = function sighash(transaction, sighashType, inputNumber, scriptCode, satoshisBuffer) { /* jshint maxstatements: 50 */ - var hashPrevouts; - var hashSequence; - var hashOutputs; + var hashPrevouts = Buffer.alloc(32); + var hashSequence = Buffer.alloc(32); + var hashOutputs = Buffer.alloc(32); if (!(sighashType & Signature.SIGHASH_ANYONECANPAY)) { var buffers = []; From c4ba5d49fa9230e8d695df766c43385ca591c20e Mon Sep 17 00:00:00 2001 From: Kenny Joseph Date: Mon, 16 Oct 2023 13:51:44 -0400 Subject: [PATCH 2/2] add sig extraction for BTC --- .../lib/transaction/input/input.js | 184 +++++++++++++++- .../lib/transaction/transaction.js | 13 ++ packages/bitcore-lib/test/crypto/signature.js | 2 +- .../test/transaction/input/multisig.js | 81 +++++++ .../transaction/input/multisigscripthash.js | 160 ++++++++++++++ .../test/transaction/input/publickey.js | 71 ++++++- .../test/transaction/input/publickeyhash.js | 200 +++++++++++++++++- 7 files changed, 695 insertions(+), 16 deletions(-) diff --git a/packages/bitcore-lib/lib/transaction/input/input.js b/packages/bitcore-lib/lib/transaction/input/input.js index 7ea866bf0f6..2a5b8931183 100644 --- a/packages/bitcore-lib/lib/transaction/input/input.js +++ b/packages/bitcore-lib/lib/transaction/input/input.js @@ -9,7 +9,13 @@ var BufferUtil = require('../../util/buffer'); var JSUtil = require('../../util/js'); var Script = require('../../script'); var Sighash = require('../sighash'); +var SighashWitness = require('../sighashwitness'); var Output = require('../output'); +const TransactionSignature = require('../signature'); +const Signature = require('../../crypto/signature'); +const PublicKey = require('../../publickey'); +const OpCode = require('../../opcode'); +const BN = require('../../crypto/bn'); var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1; var DEFAULT_SEQNUMBER = MAXINT; @@ -161,6 +167,154 @@ Input.prototype.getSignatures = function() { ); }; +/** + * + * @param {Transaction} transaction + * @param {number} inputIndex The index of the input in the transaction + * @param {Script|Buffer|string} scriptPubKey (optional) required for PublicKeyIn or MultisigIn input that does not have the output attached to it. + * @param {number|BN} satoshis (optional) required for PayToWitnessScriptHash input + * @returns {Array} + */ +Input.prototype.extractSignatures = function(transaction, inputIndex, scriptPubKey, satoshis) { + $.checkArgument(JSUtil.isNaturalNumber(inputIndex), 'inputIndex is not a natural number'); + $.checkState(this.script, 'Missing input script'); + + if (this.script.isPublicKeyIn()) { + const sig = Signature.fromTxFormat(this.script.chunks[0].buf); + if (!this.output) { + $.checkArgument(scriptPubKey, 'scriptPubKey is required when the input is not a full UTXO'); + this.output = { script: new Script(scriptPubKey) }; + } + const publicKey = this.output.script.chunks[0] && this.output.script.chunks[0].buf; + $.checkArgument(publicKey, 'No public key found from UTXO scriptPubKey'); + return [new TransactionSignature({ + signature: sig, + publicKey: new PublicKey(publicKey), + sigtype: sig.nhashtype, + inputIndex, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex + })]; + } else if (this.script.isPublicKeyHashIn()) { + const sig = Signature.fromTxFormat(this.script.chunks[0].buf); + return [new TransactionSignature({ + signature: sig, + publicKey: this.script.chunks[1].buf, + sigtype: sig.nhashtype, + inputIndex, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex + })]; + } else if (this.hasWitnesses()) { + const witnessSigs = []; + const lastWitness = Script.fromBuffer(this.witnesses[this.witnesses.length - 1]); + if (lastWitness.toBuffer().length > 72 && lastWitness.chunks.at(-1).opcodenum === OpCode.OP_CHECKMULTISIG) { + // multisig + $.checkArgument((this.output && this.output._satoshisBN) || satoshis, 'Missing required satoshis parameter'); + const satoshisBuffer = new BufferWriter().writeUInt64LEBN(this.output ? this.output._satoshisBN :new BN(satoshis)).toBuffer(); + const scriptCode = new BufferWriter().writeVarintNum(lastWitness.toBuffer().length).write(lastWitness.toBuffer()).concat(); + const numKeys = parseInt(OpCode(lastWitness.chunks.at(-2).opcodenum).toString().replace('OP_', '')); + const publicKeys = lastWitness.chunks.slice(-2 - numKeys, -2).map(c => c.buf); + for (const sigBuf of this.witnesses.slice(1, -1)) { + const sig = Signature.fromTxFormat(sigBuf); + let pkFound = false; + for (const pk of publicKeys) { + const txSig = new TransactionSignature({ + signature: sig, + publicKey: pk, + sigtype: sig.nhashtype, + inputIndex, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex + }); + if (this.isValidSignature(transaction, txSig, 'ecdsa', { scriptCode, satoshisBuffer })) { + witnessSigs.push(txSig); + pkFound = true; + break; + } + } + $.checkState(pkFound, 'No public key found for multisig signature'); + } + } else { + for (let i = 0; i < this.witnesses.length; i = i + 2) { + const sig = Signature.fromTxFormat(this.witnesses[i]); + witnessSigs.push(new TransactionSignature({ + signature: sig, + publicKey: this.witnesses[i+1], + sigtype: sig.nhashtype, + inputIndex, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex + })); + } + } + return witnessSigs; + } else if (this.script.isMultisigIn()) { + $.checkArgument(transaction, 'Missing transaction parameter'); + if (!this.output) { + $.checkArgument(scriptPubKey, 'scriptPubKey is required when the input is not a full UTXO'); + this.output = { script: new Script(scriptPubKey) }; + } + const publicKeys = this.output.script.chunks.filter(c => c.opcodenum === 33).map(c => c.buf); + $.checkArgument(publicKeys.length > 0, 'No public keys found from UTXO scriptPubKey'); + const sigs = []; + for (const chunk of this.script.chunks.slice(1)) { + const sig = Signature.fromTxFormat(chunk.buf); + let pkFound = false; + for (const pk of publicKeys) { + const txSig = new TransactionSignature({ + signature: sig, + publicKey: pk, + sigtype: sig.nhashtype, + inputIndex, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex + }); + if (this.isValidSignature(transaction, txSig, 'ecdsa')) { + sigs.push(txSig); + pkFound = true; + break; + } + } + $.checkState(pkFound, 'No public key found for multisig signature'); + } + return sigs; + } else if (this.script.isScriptHashIn()) { + $.checkArgument(transaction, 'Missing transaction parameter'); + const redeemScript = Script.fromBuffer(this.script.chunks[this.script.chunks.length - 1].buf); + if (!this.output) { // if this is a generic Input + this.output = { script: redeemScript }; + } + const publicKeys = redeemScript.chunks.filter(c => c.opcodenum === 33).map(c => c.buf); + const sigs = []; + for (const chunk of this.script.chunks.slice(1, -1)) { + const sig = Signature.fromTxFormat(chunk.buf); + let pkFound = false; + for (const pk of publicKeys) { + const txSig = new TransactionSignature({ + signature: sig, + publicKey: pk, + sigtype: sig.nhashtype, + inputIndex, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex + }); + if (this.isValidSignature(transaction, txSig, 'ecdsa')) { + sigs.push(txSig); + pkFound = true; + break; + } + } + $.checkState(pkFound, 'No public key found for multisig signature'); + } + return sigs; + + } + + // Unsigned input + return []; +}; + Input.prototype.getSatoshisBuffer = function() { $.checkState(this.output instanceof Output); $.checkState(this.output._satoshisBN); @@ -199,18 +353,30 @@ Input.prototype.setWitnesses = function(witnesses) { this.witnesses = witnesses; }; -Input.prototype.isValidSignature = function(transaction, signature, signingMethod) { +Input.prototype.isValidSignature = function(transaction, signature, signingMethod, witnessScriptHash) { // FIXME: Refactor signature so this is not necessary signingMethod = signingMethod || 'ecdsa'; signature.signature.nhashtype = signature.sigtype; - return Sighash.verify( - transaction, - signature.signature, - signature.publicKey, - signature.inputIndex, - this.output.script, - signingMethod - ); + if (witnessScriptHash) { + return SighashWitness.verify( + transaction, + signature.signature, + signature.publicKey, + signature.inputIndex, + witnessScriptHash.scriptCode, + witnessScriptHash.satoshisBuffer, + signingMethod + ); + } else { + return Sighash.verify( + transaction, + signature.signature, + signature.publicKey, + signature.inputIndex, + this.output.script, + signingMethod + ); + } }; /** diff --git a/packages/bitcore-lib/lib/transaction/transaction.js b/packages/bitcore-lib/lib/transaction/transaction.js index 3d61478c329..203885486f7 100644 --- a/packages/bitcore-lib/lib/transaction/transaction.js +++ b/packages/bitcore-lib/lib/transaction/transaction.js @@ -1205,6 +1205,19 @@ Transaction.prototype.getSignatures = function(privKey, sigtype, signingMethod) return results; }; +/** + * Returns the signature from a signed transaction input + * @param {number} inputIndex Index of the input to extract the signature from + * @param {Script|Buffer|string} scriptPubKey (optional) required for PublicKeyIn or MultisigIn input that does not have the output attached to it. + * @returns {Array} + */ +Transaction.prototype.extractSignatures = function(inputIndex, scriptPubKey, satoshis) { + $.checkArgument(JSUtil.isNaturalNumber(inputIndex), 'inputIndex must be a natural number'); + $.checkArgument(inputIndex >= 0 && inputIndex < this.inputs.length, 'inputIndex is out of range'); + const input = this.inputs[inputIndex]; + return input.extractSignatures(this, inputIndex, scriptPubKey, satoshis); +}; + /** * Add a signature to the transaction * diff --git a/packages/bitcore-lib/test/crypto/signature.js b/packages/bitcore-lib/test/crypto/signature.js index 0f65f3c3da6..afc6e76701a 100644 --- a/packages/bitcore-lib/test/crypto/signature.js +++ b/packages/bitcore-lib/test/crypto/signature.js @@ -235,7 +235,7 @@ describe('Signature', function() { }); - describe('@isTxDER', function() { + describe('#isTxDER', function() { it('should know this is a DER signature', function() { var sighex = '3042021e17cfe77536c3fb0526bd1a72d7a8e0973f463add210be14063c8a9c37632022061bfa677f825ded82ba0863fb0c46ca1388dd3e647f6a93c038168b59d131a5101'; var sigbuf = Buffer.from(sighex, 'hex'); diff --git a/packages/bitcore-lib/test/transaction/input/multisig.js b/packages/bitcore-lib/test/transaction/input/multisig.js index 16a63b0d3c5..56a7b265332 100644 --- a/packages/bitcore-lib/test/transaction/input/multisig.js +++ b/packages/bitcore-lib/test/transaction/input/multisig.js @@ -174,4 +174,85 @@ describe('MultiSigInput', function() { transaction.inputs[0].isValidSignature(transaction, transaction.inputs[0].signatures[0]).should.be.true; transaction.inputs[0].isValidSignature(transaction, transaction.inputs[0].signatures[2]).should.be.true; }); + + it('should validate a SIGHASH_ALL signature', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2]); // defaults SIGHASH_ALL + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_NONE signature', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_NONE); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_NONE); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_SINGLE signature', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_SINGLE); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_SINGLE); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_ANYONECANPAY signature', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_ANYONECANPAY); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ANYONECANPAY); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a signature from a raw tx', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2]); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const anonymousTx = new Transaction(transaction.uncheckedSerialize()); + const sigs = anonymousTx.extractSignatures(0, output.script); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); }); diff --git a/packages/bitcore-lib/test/transaction/input/multisigscripthash.js b/packages/bitcore-lib/test/transaction/input/multisigscripthash.js index 1b87718733e..4998960e72e 100644 --- a/packages/bitcore-lib/test/transaction/input/multisigscripthash.js +++ b/packages/bitcore-lib/test/transaction/input/multisigscripthash.js @@ -153,6 +153,86 @@ describe('MultiSigScriptHashInput', function() { var satoshisBuffer = input.getSatoshisBuffer(); satoshisBuffer.toString('hex').should.equal('40420f0000000000'); }); + it('should validate a SIGHASH_ALL signature', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2]); // defaults SIGHASH_ALL + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_NONE signature', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_NONE); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_NONE); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_SINGLE signature', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_SINGLE); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_SINGLE); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_ANYONECANPAY signature', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_ANYONECANPAY); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ANYONECANPAY); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a signature from a raw tx', function() { + const transaction = new Transaction() + .from(output, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2]); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const anonymousTx = new Transaction(transaction.uncheckedSerialize()); + const sigs = anonymousTx.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); describe('P2WSH', function() { it('can count missing signatures', function() { @@ -252,6 +332,86 @@ describe('MultiSigScriptHashInput', function() { var satoshisBuffer = input.getSatoshisBuffer(); satoshisBuffer.toString('hex').should.equal('40420f0000000000'); }); + it('should validate a SIGHASH_ALL signature', function() { + const transaction = new Transaction() + .from(witnessOutput, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2]); // defaults SIGHASH_ALL + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_NONE signature', function() { + const transaction = new Transaction() + .from(witnessOutput, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_NONE); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_NONE); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_SINGLE signature', function() { + const transaction = new Transaction() + .from(witnessOutput, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_SINGLE); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_SINGLE); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a SIGHASH_ANYONECANPAY signature', function() { + const transaction = new Transaction() + .from(witnessOutput, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2], Signature.SIGHASH_ANYONECANPAY); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const sigs = transaction.extractSignatures(0); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ANYONECANPAY); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); + + it('should validate a signature from a raw tx', function() { + const transaction = new Transaction() + .from(witnessOutput, [public1, public2, public3], 2) + .to(address, 1000000) + .sign([privateKey1, privateKey2]); + const input = transaction.inputs[0]; + input.isFullySigned().should.equal(true); + + const anonymousTx = new Transaction(transaction.uncheckedSerialize()); + const sigs = anonymousTx.extractSignatures(0, null, input.output.satoshis); + sigs.length.should.equal(2); + for (const sig of sigs) { + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + } + }); }); }); diff --git a/packages/bitcore-lib/test/transaction/input/publickey.js b/packages/bitcore-lib/test/transaction/input/publickey.js index 5d6b366c150..8ae108ce555 100644 --- a/packages/bitcore-lib/test/transaction/input/publickey.js +++ b/packages/bitcore-lib/test/transaction/input/publickey.js @@ -1,9 +1,11 @@ 'use strict'; -var should = require('chai').should(); -var bitcore = require('../../..'); -var Transaction = bitcore.Transaction; -var PrivateKey = bitcore.PrivateKey; +const should = require('chai').should(); +const bitcore = require('../../..'); +const Script = bitcore.Script; +const Signature = bitcore.crypto.Signature; +const Transaction = bitcore.Transaction; +const PrivateKey = bitcore.PrivateKey; describe('PublicKeyInput', function() { @@ -68,4 +70,65 @@ describe('PublicKeyInput', function() { signatures.length.should.equal(0); }); + it('should validate a SIGHASH_ALL signature', function() { + const tx = new Transaction() + .from(utxo) + .to(destKey.toAddress(), 10000) + .sign(privateKey); // SIGHASH_ALL by default + const input = tx.inputs[0]; + input.isFullySigned().should.equal(true); + const sig = tx.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(tx, sig).should.equal(true); + }); + + it('should validate a SIGHASH_NONE signature', function() { + const tx = new Transaction() + .from(utxo) + .to(destKey.toAddress(), 10000) + .sign(privateKey, Signature.SIGHASH_NONE); + const input = tx.inputs[0]; + input.isFullySigned().should.equal(true); + const sig = tx.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_NONE); + input.isValidSignature(tx, sig).should.equal(true); + }); + + it('should validate a SIGHASH_SINGLE signature', function() { + const tx = new Transaction() + .from(utxo) + .to(destKey.toAddress(), 10000) + .sign(privateKey, Signature.SIGHASH_SINGLE); + const input = tx.inputs[0]; + input.isFullySigned().should.equal(true); + const sig = tx.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_SINGLE); + input.isValidSignature(tx, sig).should.equal(true); + }); + + it('should validate a SIGHASH_ANYONECANPAY signature', function() { + const tx = new Transaction() + .from(utxo) + .to(destKey.toAddress(), 10000) + .sign(privateKey, Signature.SIGHASH_ANYONECANPAY); + const input = tx.inputs[0]; + input.isFullySigned().should.equal(true); + const sig = tx.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ANYONECANPAY); + input.isValidSignature(tx, sig).should.equal(true); + }); + + it('should validate a signature from a raw tx', function() { + const tx = new Transaction() + .from(utxo) + .to(destKey.toAddress(), 10000) + .sign(privateKey); + const input = tx.inputs[0]; + input.isFullySigned().should.equal(true); + const anonymousTx = new Transaction(tx.uncheckedSerialize()); + const sig = anonymousTx.extractSignatures(0, utxo.scriptPubKey)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(tx, sig).should.equal(true); + }); + }); diff --git a/packages/bitcore-lib/test/transaction/input/publickeyhash.js b/packages/bitcore-lib/test/transaction/input/publickeyhash.js index f733604f02b..61551dd2885 100644 --- a/packages/bitcore-lib/test/transaction/input/publickeyhash.js +++ b/packages/bitcore-lib/test/transaction/input/publickeyhash.js @@ -81,6 +81,72 @@ describe('PublicKeyHashInput', function() { signatures.length.should.equal(0); }); + it('should validate a SIGHASH_ALL signature', function() { + const transaction = new Transaction() + .from(output) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey); // SIGHASH_ALL by default + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_NONE signature', function() { + const transaction = new Transaction() + .from(output) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_NONE); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_NONE); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_SINGLE signature', function() { + const transaction = new Transaction() + .from(output) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_SINGLE); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_SINGLE); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_ANYONECANPAY signature', function() { + const transaction = new Transaction() + .from(output) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_ANYONECANPAY); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ANYONECANPAY); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a signature from a raw tx', function() { + const transaction = new Transaction() + .from(output) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey); + input.isFullySigned().should.equal(true); + const anonymousTx = new Transaction(transaction.uncheckedSerialize()); + const sig = anonymousTx.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + }); + describe('P2WPKH', function () { it('can count missing signatures', function() { var transaction = new Transaction() @@ -119,7 +185,7 @@ describe('PublicKeyHashInput', function() { }); it('will get the scriptCode', function() { var transaction = new Transaction() - .from(wrappedOutput) + .from(witnessOutput) .to(address, 1000000); var input = transaction.inputs[0]; var scriptCode = input.getScriptCode(publicKey); @@ -127,12 +193,77 @@ describe('PublicKeyHashInput', function() { }); it('will get the satoshis buffer', function() { var transaction = new Transaction() - .from(wrappedOutput) + .from(witnessOutput) .to(address, 1000000); var input = transaction.inputs[0]; var satoshisBuffer = input.getSatoshisBuffer(); satoshisBuffer.toString('hex').should.equal('40420f0000000000'); }); + it('should validate a SIGHASH_ALL signature', function() { + const transaction = new Transaction() + .from(witnessOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey); // SIGHASH_ALL by default + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_NONE signature', function() { + const transaction = new Transaction() + .from(witnessOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_NONE); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_NONE); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_SINGLE signature', function() { + const transaction = new Transaction() + .from(witnessOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_SINGLE); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_SINGLE); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_ANYONECANPAY signature', function() { + const transaction = new Transaction() + .from(witnessOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_ANYONECANPAY); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ANYONECANPAY); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a signature from a raw tx', function() { + const transaction = new Transaction() + .from(witnessOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey); + input.isFullySigned().should.equal(true); + const anonymousTx = new Transaction(transaction.uncheckedSerialize()); + const sig = anonymousTx.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + }); }); describe('P2SH-wrapped-P2WPKH', function () { @@ -187,5 +318,70 @@ describe('PublicKeyHashInput', function() { var satoshisBuffer = input.getSatoshisBuffer(); satoshisBuffer.toString('hex').should.equal('40420f0000000000'); }); + it('should validate a SIGHASH_ALL signature', function() { + const transaction = new Transaction() + .from(wrappedOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey); // SIGHASH_ALL by default + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_NONE signature', function() { + const transaction = new Transaction() + .from(wrappedOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_NONE); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_NONE); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_SINGLE signature', function() { + const transaction = new Transaction() + .from(wrappedOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_SINGLE); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_SINGLE); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a SIGHASH_ANYONECANPAY signature', function() { + const transaction = new Transaction() + .from(wrappedOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey, Signature.SIGHASH_ANYONECANPAY); + input.isFullySigned().should.equal(true); + const sig = transaction.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ANYONECANPAY); + input.isValidSignature(transaction, sig).should.equal(true); + }); + + it('should validate a signature from a raw tx', function() { + const transaction = new Transaction() + .from(wrappedOutput) + .to(address, 1000000); + const input = transaction.inputs[0]; + + transaction.sign(privateKey); + input.isFullySigned().should.equal(true); + const anonymousTx = new Transaction(transaction.uncheckedSerialize()); + const sig = anonymousTx.extractSignatures(0)[0]; + sig.sigtype.should.equal(Signature.SIGHASH_ALL); + input.isValidSignature(transaction, sig).should.equal(true); + }); }); });