Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OP_TXHASH and OP_CHECKTXHASHVERIFY opcodes #29050

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

stevenroose
Copy link
Contributor

@stevenroose stevenroose commented Dec 11, 2023

Implementation of OP_TXHASH and OP_CHECKTXHASHVERIFY, as per the draft BIP.

This MR includes a test using the test vectors generated from the reference implementation in the BIP.
The implementation utilizes a caching strategy that hopefully alleviates concerns around resource usage like quadratic hashing.

This MR on purpose does not make any consensus changes yet. Activation of the proposed opcodes will be coordinated in an independent MR, probably combined with other opcodes (like OP_CAT and/or OP_CHECKSIGFROMSTACK).

(I'm not very familiar with checks and lints on here, so I'll try to address problems as they come. I have personally been able to compile and run the tests, but that usually is only the tip of the iceberg.)

@DrahtBot
Copy link
Contributor

DrahtBot commented Dec 11, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.
A summary of reviews will appear here.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #29648 (Remove libbitcoinconsensus by fanquake)
  • #29491 ([DO NOT MERGE] Schnorr batch verification for blocks by fjahr)
  • #29280 (Implement OP_CHECKTEMPLATEVERIFY by reardencode)
  • #29270 (Implement OP_CHECKSIGFROMSTACK(VERIFY) by reardencode)
  • #29269 (Add OP_INTERNALKEY for Tapscript by reardencode)
  • #29247 (Reenable OP_CAT by 0xBEEFCAF3)
  • #29221 (Implement 64 bit arithmetic op codes in the Script interpreter by Christewart)
  • #28923 (script/sign: avoid duplicated signature verification after signing (+introduce signing benchmarks) by theStack)
  • #28806 (rpc: Add script verification flags to getdeploymentinfo by ajtowns)
  • #28710 (Remove the legacy wallet and BDB dependency by achow101)
  • #21283 (Implement BIP 370 PSBTv2 by achow101)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@stevenroose
Copy link
Contributor Author

I added a commit that has a few question marks about which contexts should have their own TxHashCache. I'm not entirely understanding which contexts have the PrecomputedTxData either. The MutableTransactionSignatureCreator is the most troubling, because since the TransactionSignatureChecker takes a ptr to TxHashCache, some cache needs to live outside of the MutableTransactionSignatureCreator while actually that cache could be empty on every call. So it seems like we actually might have to add cache variables to all that stuff. I didn't do it until now, though.

Left some todos with questions where I was unsure.

{
// if flags not enabled; treat as a NOP4
if (!(flags & SCRIPT_VERIFY_TXHASH)) {
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
Copy link
Contributor

@Randy808 Randy808 Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Consider adding curly braces here for clarity (since it's so close to the break)

if (!memcmp(txhash.begin(), stack.back().data(), 32)) {
return set_error(serror, SCRIPT_ERR_CHECKTXHASHVERIFY);
}
// nothing more to do, leave item on stack
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving it on the stack seems inconsistent with the semantics of the other 'VERIFY' opcodes

if (stack.back().size() < 32) {
return set_error(serror, SCRIPT_ERR_CHECKTXHASHVERIFY);
}
tx_field_selector = tx_field_selector.subspan(32);
Copy link
Contributor

@Randy808 Randy808 Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there's nothing wrong with pushing pre-concatenated arguments on the stack to save bytes, but this also seems inconsistent with other opcodes. Not the most important thing in the world but I'm curious what others think

| TXFS_OUTPUTS
| TXFS_CONTROL;

static const unsigned char TXFS_INPUTS_PREVOUTS = 1 << 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good list of options 👍


if (spentOutputs != nullptr && flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_TAPROOT) {
txdata.Init(tx, std::move(spent_outputs));
}

return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, MissingDataBehavior::FAIL), nullptr);
return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, &txhash_cache, MissingDataBehavior::FAIL), nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A working build and green CI may help attract review. The last commit c368d32 doesn't build for me without updating the related fuzz tests.

sample diff

diff --git a/src/test/fuzz/script_assets_test_minimizer.cpp b/src/test/fuzz/script_assets_test_minimizer.cpp
index 511b581f606..7478897e170 100644
--- a/src/test/fuzz/script_assets_test_minimizer.cpp
+++ b/src/test/fuzz/script_assets_test_minimizer.cpp
@@ -7,6 +7,7 @@
 #include <primitives/transaction.h>
 #include <pubkey.h>
 #include <script/interpreter.h>
+#include <script/txhash.h>
 #include <serialize.h>
 #include <streams.h>
 #include <univalue.h>
@@ -159,7 +160,8 @@ void Test(const std::string& str)
         tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
         PrecomputedTransactionData txdata;
         txdata.Init(tx, std::vector<CTxOut>(prevouts));
-        MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
+        TxHashCache txhash_cache;
+        MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, &txhash_cache, MissingDataBehavior::ASSERT_FAIL);
         for (const auto flags : ALL_FLAGS) {
             // "final": true tests are valid for all flags. Others are only valid with flags that are
             // a subset of test_flags.
@@ -174,7 +176,8 @@ void Test(const std::string& str)
         tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
         PrecomputedTransactionData txdata;
         txdata.Init(tx, std::vector<CTxOut>(prevouts));
-        MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
+        TxHashCache txhash_cache;
+        MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, &txhash_cache, MissingDataBehavior::ASSERT_FAIL);
         for (const auto flags : ALL_FLAGS) {
             // If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
             if ((flags & test_flags) == test_flags) {
diff --git a/src/test/fuzz/script_flags.cpp b/src/test/fuzz/script_flags.cpp
index accb32f1cc4..d9001fd81a0 100644
--- a/src/test/fuzz/script_flags.cpp
+++ b/src/test/fuzz/script_flags.cpp
@@ -5,6 +5,7 @@
 #include <consensus/amount.h>
 #include <primitives/transaction.h>
 #include <script/interpreter.h>
+#include <script/txhash.h>
 #include <serialize.h>
 #include <streams.h>
 #include <test/fuzz/fuzz.h>
@@ -42,10 +43,11 @@ FUZZ_TARGET(script_flags)
         }
         PrecomputedTransactionData txdata;
         txdata.Init(tx, std::move(spent_outputs));
+        TxHashCache txhash_cache;
 
         for (unsigned i = 0; i < tx.vin.size(); ++i) {
             const CTxOut& prevout = txdata.m_spent_outputs.at(i);
-            const TransactionSignatureChecker checker{&tx, i, prevout.nValue, txdata, MissingDataBehavior::ASSERT_FAIL};
+            const TransactionSignatureChecker checker{&tx, i, prevout.nValue, txdata, &txhash_cache, MissingDataBehavior::ASSERT_FAIL};
 
             ScriptError serror;
             const bool ret = VerifyScript(tx.vin.at(i).scriptSig, prevout.scriptPubKey, &tx.vin.at(i).scriptWitness, verify_flags, checker, &serror);
diff --git a/src/test/fuzz/script_sigcache.cpp b/src/test/fuzz/script_sigcache.cpp
index 5fdbc9e1069..9cc233fed19 100644
--- a/src/test/fuzz/script_sigcache.cpp
+++ b/src/test/fuzz/script_sigcache.cpp
@@ -6,6 +6,7 @@
 #include <key.h>
 #include <pubkey.h>
 #include <script/sigcache.h>
+#include <script/txhash.h>
 #include <test/fuzz/FuzzedDataProvider.h>
 #include <test/fuzz/fuzz.h>
 #include <test/fuzz/util.h>
@@ -36,7 +37,8 @@ FUZZ_TARGET(script_sigcache, .init = initialize_script_sigcache)
     const CAmount amount = ConsumeMoney(fuzzed_data_provider);
     const bool store = fuzzed_data_provider.ConsumeBool();
     PrecomputedTransactionData tx_data;
-    CachingTransactionSignatureChecker caching_transaction_signature_checker{mutable_transaction ? &tx : nullptr, n_in, amount, store, tx_data};
+    TxHashCache txhash_cache;
+    CachingTransactionSignatureChecker caching_transaction_signature_checker{mutable_transaction ? &tx : nullptr, n_in, amount, store, tx_data, &txhash_cache};
     if (fuzzed_data_provider.ConsumeBool()) {
         const auto random_bytes = fuzzed_data_provider.ConsumeBytes<unsigned char>(64);
         const XOnlyPubKey pub_key(ConsumeUInt256(fuzzed_data_provider));

@DrahtBot
Copy link
Contributor

DrahtBot commented Apr 1, 2024

🐙 This pull request conflicts with the target branch and needs rebase.

@achow101 achow101 marked this pull request as draft April 9, 2024 14:28
@achow101
Copy link
Member

achow101 commented Apr 9, 2024

Seems like this should be a draft

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants