-
Notifications
You must be signed in to change notification settings - Fork 38.2k
CAT in Tapscript (BIP-347) #29247
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
base: master
Are you sure you want to change the base?
CAT in Tapscript (BIP-347) #29247
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29247. ReviewsSee the guideline for information on the review process. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
|
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the Possibly this is due to a silent merge conflict (the changes in this pull request being Leave a comment here, if you need help tracking down a confusing failure. Debug: https://github.com/bitcoin/bitcoin/runs/20473822722 |
|
Drafting until I get the inquisition PR approved and I can get the builds passing. |
|
Some of us have been playing with the idea of neutered CAT: instead of the MAX_SCRIPT_ELEMENT_SIZE (520 bytes) the maximum output size would be 80 bytes. Rationale: Combined with CSFS it can be used as a signed datacarrier, for which the 'standard' limit is 80 bytes, it also has to be smaller than 84 bytes which is required for building CTV templates on the stack, and larger than 64/65/72 bytes which are respectively needed for:
Could be a livable compromise between the conservatives that want to preserve certain characteristics of bitcoin and the prometheans who want to give the developers more practical and useful tools to build with. |
Script is already expressive enough to (awkwardly, expensively) compute arbitrary SHA256 hashes on the stack, except that the result would be broken in smaller pieces of at most 4 bytes. With CAT, those can be concatenated to get a single 32-byte result. Therefore, neutering CATs does not achieve the desired result of preventing the CHECKSIG tricks, unless you limit the length of the result to less than 32 bytes - which would also neuter most of the utility of the opcode. |
b9c67da to
f1fd2b6
Compare
f1fd2b6 to
6b259bd
Compare
|
🚧 At least one of the CI tasks failed. HintsMake sure to run all tests locally, according to the documentation. The failure may happen due to a number of reasons, for example:
Leave a comment here, if you need help tracking down a confusing failure. |
6b259bd to
a04c22b
Compare
e3d7533 test: improves tapscript unit tests (Ethan Heilman) 3e16708 test: Ensures test fails if witness is not hex (Ethan Heilman) Pull request description: This commit creates new test utilities for future Taproot script tests within script_tests.json. The key features of this commit are the addition of three new tags: `#SCRIPT#`, `#CONTROLBLOCK#`, and `#TAPROOTOUTPUT#`. These tags streamline the test creation process by eliminating the need to manually generate these components outside the test suite. * `#SCRIPT#`: Parses Tapscript and outputs a byte string of opcodes. * `#CONTROLBLOCK#`: Automatically generates the control block for a given Taproot output. * `#TAPROOTOUTPUT#`: Generates the final Taproot scriptPubKey. This code was originally part of the OP_CAT PR #29247 but was pulled out into a separate PR to reduce the rebase treadmill for the OP_CAT PR. Additionally this PR adds a check to ensure that if the witness data can not be parsed as hex the test fails. Prior to this PR, the test code would fail silently and set the values it couldn't parse as empty stack elements. This fix was suggested by @instagibbs. ## Rationale While writing JSON script tests (script_tests.json) for #29247 we ran into the following problem. The JSON script tests are simple and easy to write for pre-Tapscript scripts, but adding or changing a Tapscript test requires substantial work per test. Consider the following pre-tapscript test: ``` ["'aa' 'bb'", "CAT 0x4c 0x02 0xaabb EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "CAT disabled"] ```` whereas a Tapscript test for the same script (annotated with comments for better readability) would look like: ``` [ [ "aa", "bb", "7e4c02aabb87", // output script "c0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", // control block 0.00000001 ], "", "0x51 0x20 0x15048ed3a65748549c27b671936987093cf73a4c9cb18522a74fb9553060ca99", // Tapscript output "P2SH,WITNESS,TAPROOT", "OK", "TAPSCRIPT CATs aa and bb together and checks if EQUAL to aabb" ] ``` Computing the Tapscript output, such as `0x51 0x20 0x15048ed3a65748549c27b671936987093cf73a4c9cb18522a74fb9553060ca99`, requires writing custom code and running it for each test. The same is true for the Tapscript control block, such as `c0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d`. If a test is changed or updated new outputs and control blocks must be computed. The complexity of doing this is likely the reason that no one has added any Tapscript tests to JSON script tests until this PR. In this PR we address this issue by adding the following improvements to JSON script tests: Adding simple macros ("#SCRIPT# and #CONTROLBLOCK#) that allow the script test parser to automatically generate and inject a valid Tapscript output and control block to be computed automatically from the JSON script. Allowing Tapscript scripts to use the human readable strings like pre-script scripts by marking the location of the script in the witness stack using #SCRIPT#. This transforms the unreadable script 7e4c02aabb87 into #SCRIPT# CAT 0x4c 0x02 0xaabb EQUAL. This results in the following JSON script test which is far easier to write and easier to read. ``` [ [ "aa", "bb", "#SCRIPT# CAT", "#CONTROLBLOCK#", 0.00000001 ], "", "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Test of OP_CAT flag by calling CAT on two elements. TAPSCRIPT_OP_CAT flag is set so CAT is executed." ], ``` ACKs for top commit: instagibbs: reACK e3d7533 sipa: utACK e3d7533 janb84: Re ACK [e3d7533](e3d7533) Tree-SHA512: 948c3ec28a4b2b222c2d77e48918ed19d298b51d64662fc20959073edd9978fc796516a392da9755a7e173f556e3021816dc6ce8eb3ed16bbe0fa6ebc574fd48
|
What about SCRIPT_VERIFY OP_CAT = False and SCRIPT_VERIFY DISCOURAGE_OP_CAT = False? |
|
@AlexSQY That's an interesting question. |
|
@EthanHeilman , thanks for the feedback. PS: DISCOURAGE_OP_SUCCESS also introduces more combinations one with the others (2 tested as part of the actual script_tests.json, nominal cases only?) |
|
@AlexSQY I see your point. Let me see if there is anything that can be done to ensure it is never the case that The behavior is what you see in that test is the expected behavior for @0xBEEFCAF3 What do you think? |
By default, the That said, @AlexSQY, you're right -- this edge case deserves documentation. If I recall correctly, it’s being tested here, where neither the discourage bit nor the script verify bit is set. In that case, both are |
7377841 to
6e01b28
Compare
DISCOURAGE_OP_SUCCESS in current bitcoin covers the behaviour of DISCOURAGE_OP_CAT. OP_CAT=false and DISCOURAGE_OP_CAT=false are used for validating blocks prior to activation of OP_CAT, so this isn't an edge case as far as I can see? OP_CAT=true and DISCOURAGE_OP_CAT=true should be an edge case, but it doesn't make sense of course: "disallow OP_CAT, but if it were allowed, which it's not, enforce its rules". I think the table should look more like:
|
Implement OP_CAT as a new Tapscript op code by redefining the opcode OP_SUCCESS126 (126 in decimal and 0x7e in hexadecimal). This is the same opcode value used by the original OP_CAT. When evaluated, the OP_CAT instruction: Pops the top two values off the stack, concatenates the popped values together in stack order, and then pushes the concatenated value on the top of the stack. OP_CAT fails if there are fewer than two values on the stack or if a concatenated value would have a combined size greater than the maximum script element size of 520 bytes. See [BIP 347](https://github.com/bitcoin/bips/blob/master/bip-0347.mediawiki) for a deeper description.
Co-authored-by: Ethan Heilman <[email protected]>
6e01b28 to
08302a6
Compare
The goal of this functional test is to ensure OP_CAT spends are still disabled by default in segwitv0 and legacy spends. Spending such inputs should result in `mandatory-script-verify-flag-failed (Attempted to use a disabled opcode)`. While spending OP_CAT inputs in tapscript should be discouraged under the default `STANDARD_SCRIPT_VERIFY_FLAGS`.
08302a6 to
16f5ad2
Compare
652424a test: additional test coverage for script_verify_flags (Anthony Towns) 417437e script/verify_flags: extend script_verify_flags to 64 bits (Anthony Towns) 3cbbcb6 script/interpreter: make script_verify_flag_name an ordinary enum (Anthony Towns) bddcade script/verify_flags: make script_verify_flags type safe (Anthony Towns) a5ead12 script/interpreter: introduce script_verify_flags typename (Anthony Towns) 4577fb2 rpc: have getdeploymentinfo report script verify flags (Anthony Towns) a398693 validation: export GetBlockScriptFlags() (Anthony Towns) 5db8cd2 Move mapFlagNames and FormatScriptFlags logic to script/interpreter.h (Anthony Towns) Pull request description: We currently use 21 of 32 possible bits for `SCRIPT_VERIFY_*` flags, with open PRs that may use 8 more (#29247, #31989, #32247, #32453). The mutinynet fork that has included many experimental soft fork features is [already reusing bits here](https://github.com/benthecarman/bitcoin/blob/d4a86277ed8a0712e03fbbce290e9209165e049c/src/script/interpreter.h#L175-L195). Therefore, bump this to 64 bits. In order to make it easier to update this logic in future, this PR also introduces a dedicated type for the script flags, and disables implicit conversion between that type and the underlying integer type. To make verifying that this change doesn't cause flags to disappear, this PR also resurrects the changes from #28806 so that the script flags that are consensus enforced on each block can be queried via getdeploymentinfo. ACKs for top commit: instagibbs: reACK 652424a achow101: ACK 652424a darosior: ACK 652424a theStack: Code-review ACK 652424a 🎏 Tree-SHA512: 7b30152196cdfdef8b9700b571b7d7d4e94d28fbc5c26ea7532788037efc02e4b1d8de392b0b20507badfdc26f5c125f8356a479604a9149b8aae23a7cf5549f
|
🐙 This pull request conflicts with the target branch and needs rebase. |
CAT in Tapscript
This PR provides the necessary code to enable the opcode OP_CAT in Tapscript as specified in BIP-347: OP_CAT in Tapscript and BIN-2024-0001,
Important: This PR does not include miner activation functionality. This means that merging this PR into bitcoin-core will not make OP_CAT functional in Bitcoin.
If this PR is merged it is not a signal of community consensus around activating CAT nor be read as a portent about the activation process or timeline.
The PR is not a stand-in for consensus around such decisions.
This PR includes:
SCRIPT_VERIFY_OP_CATandSCRIPT_VERIFY_DISCOURAGE_OP_CAT.src/test/script_tests.cppJSON script format enabling the rapid creation of new Tapscript unit tests.Activation on Bitcoin-inquisition (signet)
Bitcoin-inquisition PR 37 which contained our implementation of OP_CAT was merged into bitcoin-inquisition (signet) on Apr 25 2024, released as bitcoin-inquisition release 25.2 on Apr 26 2024. and activated in bitcoin-inquisition Apr 30 2024. The bitcoin-inquisition PR was reviewed in the PR review club (transcript of discussion). Since Apr 30 2024 there have been many OP_CAT transactions created and spent on signet.
The code merged into bitcoin-inquisition differs from this PR, as we have removed the consensus logic which was used to activate it on signet.
OP_CAT Tapscript Implementation
We implement OP_CAT as a new Tapscript op code by redefining the opcode OP_SUCCESS126 (126 in decimal and 0x7e in hexadecimal). This is the same opcode value used by the original OP_CAT.
When evaluated, the OP_CAT instruction:
OP_CAT fails if there are fewer than two values on the stack or if a concatenated value would have a combined size greater than the maximum script element size of 520 bytes.
See BIP 347 for a deeper description.
Errors thrown
If evaluated OP_CAT can throw the following errors:
SCRIPT_ERR_INVALID_STACK_OPERATIONMAX_SCRIPT_ELEMENT_SIZE(520 bytes) we throw the error:SCRIPT_ERR_PUSH_SIZE.Script verification flags
While this PR does not contain any miner signaling and activation logic and can not activate OP_CAT, it does contain two flags which future activation logic could set to control activation of OP_CAT.
SCRIPT_VERIFY_OP_CATIF a bitcoin node has this set to true, then it treat OP_CAT enabled for Tapscript. That is, OP_SUCCESS126 will be redefining to OP_CAT in Tapscript. If this was set to true at the consensus level this would cause a soft fork.SCRIPT_VERIFY_DISCOURAGE_OP_CATWhen set to true, a node receiving any Tapscript transaction containing the opcode OP_CAT or OP_SUCCESS126 will reject the transaction throwing the errorSCRIPT_ERR_DISCOURAGE_OP_CATbut not banning the node which relayed the transaction. This prevents nodes from relaying transactions with OP_CAT. This is equivalent to the behavior ofSCRIPT_ERR_DISCOURAGE_OP_CAT = truewhenSCRIPT_VERIFY_OP_CAT = falseas OP_CAT is an OP_SUCCESS (OP_SUCCESS126).This is how these two flags are intended to be used to ensure a smooth soft fork.
SCRIPT_VERIFY OP_CATSCRIPT_VERIFY DISCOURAGE_OP_CATThe flag
SCRIPT_ERR_DISCOURAGE_OP_CATprovides a window of time for the network to fully activate, before nodes will relay or accept transactions containing OP_CAT in their mem pools.Tests
This PR contains a suite of script tests to ensure that OP_CAT functions as expected. In the JSON script tests (
script_tests.json), we test:All of these tests are designed to cover the happy path of OP_CAT, the various errors which OP_CAT can throw and all the corner cases between those two outcomes.
Additionally we include three tests outside of the JSON script tests.
cat_simpleandcat_empty_stackare designed to test OP_CAT outside of the JSON serialization regime. Ensure that we catch bugs that we might miss in the JSON script tests due to a bug introduced at the JSON serialization layer.cat_dup_testenumerates all stack element sizes from 1 to 522 bytes and then enumerates up to 10 repetitions ofOP\_DUP OP\_CAT. It then tests if the stack element would exceed 520 bytes and if so did OP_CAT throw the errorSCRIPT_ERR_PUSH_SIZE. This allows us to be certain that OP_CAT will not introduce anyOP\_DUP OP\_CATmemory exhaustion attacks.Better Tapscript tests in JSON script tests
While writing these JSON script tests (
script_tests.json) we ran into the following problem. The JSON script tests are simple and easy to write for pre-Tapscript scripts, but adding or changing a Tapscript test requires substantial work per test.Consider the following pre-tapscript test:
whereas a Tapscript test for the same script (annotated with comments for better readability) would look like:
[ [ "aa", "bb", "7e4c02aabb87", // output script "c0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", // control block 0.00000001 ], "", "0x51 0x20 0x15048ed3a65748549c27b671936987093cf73a4c9cb18522a74fb9553060ca99", // Tapscript output "P2SH,WITNESS,TAPROOT", "OK", "TAPSCRIPT CATs aa and bb together and checks if EQUAL to aabb" ]Computing the Tapscript output, such as
0x51 0x20 0x15048ed3a65748549c27b671936987093cf73a4c9cb18522a74fb9553060ca99, requires writing custom code and running it for each test. The same is true for the Tapscript control block, such asc0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d. If a test is changed or updated new outputs and control blocks must be computed. The complexity of doing this is likely the reason that no one has added any Tapscript tests to JSON script tests until this PR.In this PR we address this issue by adding the following improvements to JSON script tests:
"#SCRIPT#and#CONTROLBLOCK#) that allow the script test parser to automatically generate and inject a valid Tapscript output and control block to be computed automatically from the JSON script.#SCRIPT#. This transforms the unreadable script7e4c02aabb87into#SCRIPT# CAT 0x4c 0x02 0xaabb EQUAL.This results in the following JSON script test which is far easier to write and easier to read.
[ [ "aa", "bb", "#SCRIPT# CAT", "#CONTROLBLOCK#", 0.00000001 ], "", "0x51 0x20 #TAPROOTOUTPUT#", "P2SH,WITNESS,TAPROOT,OP_CAT", "OK", "TAPSCRIPT Test of OP_CAT flag by calling CAT on two elements. TAPSCRIPT_OP_CAT flag is set so CAT is executed." ],