Skip to content

Commit

Permalink
Make SignatureData able to store signatures and scripts
Browse files Browse the repository at this point in the history
In addition to having the scriptSig and scriptWitness, have SignatureData
also be able to store just the signatures (pubkeys mapped to sigs) and
scripts (script ids mapped to scripts).

Also have DataFromTransaction be able to extract signatures and scripts
from the scriptSig and scriptWitness of an input to put them in SignatureData.

Adds a new SignatureChecker which takes a SignatureData and puts pubkeys
and signatures into it when it successfully verifies a signature.

Adds a new field in SignatureData which stores whether the SignatureData
was complete. This allows us to also update the scriptSig and
scriptWitness to the final one when updating a SignatureData with another
one.

Cherry-picked from: 0422beb
  • Loading branch information
achow101 authored and xanimo committed Apr 21, 2024
1 parent c325be4 commit e7891a7
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 62 deletions.
9 changes: 6 additions & 3 deletions src/bench/verify_script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
#if defined(HAVE_CONSENSUS_LIB)
#include "script/bitcoinconsensus.h"
#endif
#include "script/script.h"
#include "script/sign.h"
#include "streams.h"
#include <script/script.h>
#include <script/sign.h>
#include <script/standard.h>
#include <streams.h>

#include <array>

// FIXME: Dedup with BuildCreditingTransaction in test/script_tests.cpp.
static CMutableTransaction BuildCreditingTransaction(const CScript& scriptPubKey)
Expand Down
16 changes: 4 additions & 12 deletions src/bitcoin-tx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
// mergedTx will end up with all the signatures; it
// starts as a clone of the raw tx:
CMutableTransaction mergedTx(txVariants[0]);
const CMutableTransaction txv{tx};
bool fComplete = true;
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
Expand Down Expand Up @@ -615,23 +616,14 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey;
const CAmount& amount = coins->vout[txin.prevout.n].nValue;

SignatureData sigdata;
SignatureData sigdata = DataFromTransaction(mergedTx, i, coins->vout[txin.prevout.n]);
// Only sign SIGHASH_SINGLE if there's a corresponding output:
if (!fHashSingle || (i < mergedTx.vout.size()))
ProduceSignature(keystore, MutableTransactionSignatureCreator(&mergedTx, i, amount, nHashType), prevPubKey, sigdata);

// ... and merge in other signatures:
BOOST_FOREACH(const CTransaction& txv, txVariants)
sigdata = CombineSignatures(prevPubKey, MutableTransactionSignatureChecker(&mergedTx, i, amount), sigdata, DataFromTransaction(txv, i));
UpdateTransaction(mergedTx, i, sigdata);

if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&mergedTx, i, amount)))
fComplete = false;
}

if (fComplete) {
// do nothing... for now
// perhaps store this for later optional JSON output
sigdata = CombineSignatures(prevPubKey, MutableTransactionSignatureChecker(&mergedTx, i, amount), sigdata, DataFromTransaction(txv, i, coins->vout[txin.prevout.n]));
UpdateInput(txin, sigdata);
}

tx = mergedTx;
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,11 +838,11 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
// ... and merge in other signatures:
BOOST_FOREACH(const CMutableTransaction& txv, txVariants) {
if (txv.vin.size() > i) {
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i));
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i, coins->vout[txin.prevout.n]));
}
}

UpdateTransaction(mergedTx, i, sigdata);
UpdateInput(mergedTx.vin[i], sigdata);

ScriptError serror = SCRIPT_ERR_OK;
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
Expand Down
139 changes: 106 additions & 33 deletions src/script/sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ static CScript PushAll(const vector<valtype>& values)

bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata)
{
CScript script = fromPubKey;
if (sigdata.complete) return true;

std::vector<valtype> result;
txnouttype whichType;
bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::SIGVERSION_BASE);
Expand All @@ -153,7 +154,6 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
txnouttype subType;
solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::SIGVERSION_WITNESS_V0);
sigdata.scriptWitness.stack = result;
sigdata.witness = true;
result.clear();
}
else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH)
Expand All @@ -163,7 +163,6 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::SIGVERSION_WITNESS_V0) && subType != TX_SCRIPTHASH && subType != TX_WITNESS_V0_SCRIPTHASH && subType != TX_WITNESS_V0_KEYHASH;
result.push_back(std::vector<unsigned char>(witnessscript.begin(), witnessscript.end()));
sigdata.scriptWitness.stack = result;
sigdata.witness = true;
result.clear();
}

Expand All @@ -173,15 +172,117 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
sigdata.scriptSig = PushAll(result);

// Test solution
return solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker());
sigdata.complete = solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker());
return sigdata.complete;
}

class SignatureExtractorChecker final : public BaseSignatureChecker
{
private:
SignatureData& sigdata;
BaseSignatureChecker& checker;

public:
SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : sigdata(sigdata), checker(checker) {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
};

bool SignatureExtractorChecker::CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
{
if (checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion)) {
CPubKey pubkey(vchPubKey);
sigdata.signatures.emplace(pubkey.GetID(), SigPair(pubkey, scriptSig));
return true;
}
return false;
}

namespace
{
struct Stacks
{
std::vector<valtype> script;
std::vector<valtype> witness;

Stacks() {}
explicit Stacks(const std::vector<valtype>& scriptSigStack_) : script(scriptSigStack_), witness() {}
explicit Stacks(const SignatureData& data) : witness(data.scriptWitness.stack) {
EvalScript(script, data.scriptSig, SCRIPT_VERIFY_STRICTENC, BaseSignatureChecker(), SigVersion::SIGVERSION_BASE);
}

SignatureData Output() const {
SignatureData result;
result.scriptSig = PushAll(script);
result.scriptWitness.stack = witness;
return result;
}
};
}

SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn)
// Extracts signatures and scripts from incomplete scriptSigs. Please do not extend this, use PSBT instead
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout)
{
SignatureData data;
assert(tx.vin.size() > nIn);
data.scriptSig = tx.vin[nIn].scriptSig;
data.scriptWitness = tx.vin[nIn].scriptWitness;
Stacks stack(data);

// Get signatures
MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue);
SignatureExtractorChecker extractor_checker(data, tx_checker);
if (VerifyScript(data.scriptSig, txout.scriptPubKey, &data.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, extractor_checker)) {
data.complete = true;
return data;
}

// Get scripts
txnouttype script_type;
std::vector<std::vector<unsigned char>> solutions;
Solver(txout.scriptPubKey, script_type, solutions);
SigVersion sigversion = SigVersion::SIGVERSION_BASE;
CScript next_script = txout.scriptPubKey;

if (script_type == TX_SCRIPTHASH && !stack.script.empty() && !stack.script.back().empty()) {
// Get the redeemScript
CScript redeem_script(stack.script.back().begin(), stack.script.back().end());
data.redeem_script = redeem_script;
next_script = std::move(redeem_script);

// Get redeemScript type
Solver(next_script, script_type, solutions);
stack.script.pop_back();
}
if (script_type == TX_WITNESS_V0_SCRIPTHASH && !stack.witness.empty() && !stack.witness.back().empty()) {
// Get the witnessScript
CScript witness_script(stack.witness.back().begin(), stack.witness.back().end());
data.witness_script = witness_script;
next_script = std::move(witness_script);

// Get witnessScript type
Solver(next_script, script_type, solutions);
stack.witness.pop_back();
stack.script = std::move(stack.witness);
stack.witness.clear();
sigversion = SigVersion::SIGVERSION_WITNESS_V0;
}
if (script_type == TX_MULTISIG && !stack.script.empty()) {
// Build a map of pubkey -> signature by matching sigs to pubkeys:
assert(solutions.size() > 1);
unsigned int num_pubkeys = solutions.size()-2;
unsigned int last_success_key = 0;
for (const valtype& sig : stack.script) {
for (unsigned int i = last_success_key; i < num_pubkeys; ++i) {
const valtype& pubkey = solutions[i+1];
// We either have a signature for this pubkey, or we have found a signature and it is valid
if (data.signatures.count(CPubKey(pubkey).GetID()) || extractor_checker.CheckSig(sig, pubkey, next_script, sigversion)) {
last_success_key = i + 1;
break;
}
}
}
}

return data;
}

Expand Down Expand Up @@ -275,28 +376,6 @@ static vector<valtype> CombineMultisig(const CScript& scriptPubKey, const BaseSi
return result;
}

namespace
{
struct Stacks
{
std::vector<valtype> script;
std::vector<valtype> witness;

Stacks() {}
explicit Stacks(const std::vector<valtype>& scriptSigStack_) : script(scriptSigStack_), witness() {}
explicit Stacks(const SignatureData& data) : witness(data.scriptWitness.stack) {
EvalScript(script, data.scriptSig, SCRIPT_VERIFY_STRICTENC, BaseSignatureChecker(), SIGVERSION_BASE);
}

SignatureData Output() const {
SignatureData result;
result.scriptSig = PushAll(script);
result.scriptWitness.stack = witness;
return result;
}
};
}

static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker,
const txnouttype txType, const vector<valtype>& vSolutions,
Stacks sigs1, Stacks sigs2, SigVersion sigversion)
Expand Down Expand Up @@ -492,9 +571,6 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const
if (!witness_script.empty()) {
sigdata.witness_script = witness_script;
}
for (const auto& key_pair : hd_keypaths) {
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair.first);
}
}

void PSBTInput::FromSignatureData(const SignatureData& sigdata)
Expand Down Expand Up @@ -561,9 +637,6 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
if (!witness_script.empty()) {
sigdata.witness_script = witness_script;
}
for (const auto& key_pair : hd_keypaths) {
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair.first);
}
}

void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
Expand Down
10 changes: 5 additions & 5 deletions src/script/sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,16 @@ extern const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR;

typedef std::pair<CPubKey, std::vector<unsigned char>> SigPair;

// This struct contains information from a transaction input and also contains signatures for that input.
// The information contained here can be used to create a signature and is also filled by ProduceSignature
// in order to construct final scriptSigs and scriptWitnesses.
struct SignatureData {
bool complete = false; ///< Stores whether the scriptSig and scriptWitness are complete
bool witness = false; ///< Stores whether the input this SigData corresponds to is a witness input
CScript scriptSig; ///< The scriptSig of an input. Contains complete signatures or the traditional partial signatures format
CScript redeem_script; ///< The redeemScript (if any) for the input
CScript witness_script; ///< The witnessScript (if any) for the input. witnessScripts are used in P2WSH outputs.
CScriptWitness scriptWitness; ///< The scriptWitness of an input. Contains complete signatures or the traditional partial signatures format. scriptWitness is part of a transaction input per BIP 144.
std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness.
std::map<CKeyID, CPubKey> misc_pubkeys;

SignatureData() {}
explicit SignatureData(const CScript& script) : scriptSig(script) {}
Expand Down Expand Up @@ -649,9 +650,8 @@ bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom,
/** Combine two script signatures using a generic signature checker, intelligently, possibly with OP_0 placeholders. */
SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker, const SignatureData& scriptSig1, const SignatureData& scriptSig2);

/** Extract signature data from a transaction, and insert it. */
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn);
void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data);
/** Extract signature data from a transaction input, and insert it. */
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout);
void UpdateInput(CTxIn& input, const SignatureData& data);

/* Check whether we know how to sign for an output like this, assuming we
Expand Down
8 changes: 4 additions & 4 deletions src/test/transaction_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ BOOST_AUTO_TEST_CASE(test_witness)
CreateCreditAndSpend(keystore2, scriptMulti, output2, input2, false);
CheckWithFlag(output2, input2, 0, false);
BOOST_CHECK(*output1 == *output2);
UpdateTransaction(input1, 0, CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0), DataFromTransaction(input2, 0)));
UpdateInput(input1.vin[0], CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0, output1->vout[0]), DataFromTransaction(input2, 0, output1->vout[0])));
CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true);

// P2SH 2-of-2 multisig
Expand All @@ -639,7 +639,7 @@ BOOST_AUTO_TEST_CASE(test_witness)
CheckWithFlag(output2, input2, 0, true);
CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH, false);
BOOST_CHECK(*output1 == *output2);
UpdateTransaction(input1, 0, CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0), DataFromTransaction(input2, 0)));
UpdateInput(input1.vin[0], CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0, output1->vout[0]), DataFromTransaction(input2, 0, output1->vout[0])));
CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH, true);
CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true);

Expand All @@ -651,7 +651,7 @@ BOOST_AUTO_TEST_CASE(test_witness)
CheckWithFlag(output2, input2, 0, true);
CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false);
BOOST_CHECK(*output1 == *output2);
UpdateTransaction(input1, 0, CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0), DataFromTransaction(input2, 0)));
UpdateInput(input1.vin[0], CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0, output1->vout[0]), DataFromTransaction(input2, 0, output1->vout[0])));
CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true);
CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true);

Expand All @@ -663,7 +663,7 @@ BOOST_AUTO_TEST_CASE(test_witness)
CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH, true);
CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false);
BOOST_CHECK(*output1 == *output2);
UpdateTransaction(input1, 0, CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0), DataFromTransaction(input2, 0)));
UpdateInput(input1.vin[0], CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0, output1->vout[0]), DataFromTransaction(input2, 0, output1->vout[0])));
CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true);
CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true);
}
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3248,7 +3248,7 @@ UniValue bumpfee(const JSONRPCRequest& request)
if (!ProduceSignature(*pwallet, TransactionSignatureCreator(&txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
}
UpdateTransaction(tx, nIn, sigdata);
UpdateInput(tx.vin[nIn], sigdata);
nIn++;
}

Expand Down
2 changes: 1 addition & 1 deletion src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2779,7 +2779,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
strFailReason = _("Signing transaction failed");
return false;
} else {
UpdateTransaction(txNew, nIn, sigdata);
UpdateInput(txNew.vin[nIn], sigdata);
}

nIn++;
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins
{
return false;
} else {
UpdateTransaction(txNew, nIn, sigdata);
UpdateInput(txNew.vin[nIn], sigdata);
}

nIn++;
Expand Down

0 comments on commit e7891a7

Please sign in to comment.