{
 "number": 29247,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/29247",
 "title": "CAT in Tapscript (BIP-347)",
 "author": "arminsabouri",
 "author_association": "FIRST_TIME_CONTRIBUTOR",
 "created_at": "2024-01-14T22:40:42Z",
 "updated_at": "2026-09-11T02:36:35Z",
 "age_days": 976,
 "draft": true,
 "labels": [
  "Consensus"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "6ca33cb59e4a1526fb6fd74d0bc84aaa35e3fccd",
 "head_ref": "arm/re-enable-opcat",
 "head_repo": "arminsabouri/bitcoin",
 "head_history": [
  {
   "t": "2024-05-26T22:05:25Z",
   "sha": "e04fb802b278ae4aab2d0d5a63c75ebe0e81560e"
  },
  {
   "t": "2024-05-26T22:06:04Z",
   "sha": "9dbf0811dba75a36b213bb72e8f1e8297a9a12a3"
  },
  {
   "t": "2024-05-27T00:39:47Z",
   "sha": "b9c67da29c1c2c62891ef52fc5832df1012abba3"
  },
  {
   "t": "2024-05-27T18:07:50Z",
   "sha": "f1fd2b6dd8d48a26fda87eb30040116a81e6a5c5"
  },
  {
   "t": "2024-08-27T12:43:51Z",
   "sha": "6b259bdff8f790403600d5c9255f78729a4c343a"
  },
  {
   "t": "2024-08-29T01:50:58Z",
   "sha": "a04c22b47a2b784793f1afbf4534e04f11771356"
  },
  {
   "t": "2024-08-29T03:47:11Z",
   "sha": "3471550367d9a93394e99860a3e580c31ba763fb"
  },
  {
   "t": "2024-08-31T13:13:00Z",
   "sha": "a8bf3f4e6d3cf592cfd6fbefd86e77b6b3de8933"
  },
  {
   "t": "2024-08-31T16:09:36Z",
   "sha": "457a41b144c02a3abd0e9f95222f4da7e7681cf1"
  },
  {
   "t": "2024-08-31T21:06:59Z",
   "sha": "f5807a52a3bb61618606cefd499bf0fe61b40930"
  },
  {
   "t": "2024-09-17T01:09:17Z",
   "sha": "d6a668a5cdf784274a1f29956dae6ac28fd6d125"
  },
  {
   "t": "2024-09-17T11:54:46Z",
   "sha": "6e05acff87be5ca826a3761a83bed88559cadc01"
  },
  {
   "t": "2024-10-29T23:38:10Z",
   "sha": "e24086b08a6ced9ec49b0d88cdb6c6b027fd28d3"
  },
  {
   "t": "2024-11-09T15:30:25Z",
   "sha": "94fcae92aff8d1ec9e1cd2091c383ecbaba2167e"
  },
  {
   "t": "2024-11-15T14:53:36Z",
   "sha": "4e337f7245460a36186d329082eb3451edbcb1a9"
  },
  {
   "t": "2024-12-02T14:01:07Z",
   "sha": "01945a50caa08cb287cf1f378dcc46de36e5f7f2"
  },
  {
   "t": "2024-12-02T15:13:33Z",
   "sha": "7377841a85cc12a960c069b5897a6d13b468158d"
  },
  {
   "t": "2025-05-09T03:10:31Z",
   "sha": "6e01b2817ccfc2f4627b82edf50b38b9f52d5a16"
  },
  {
   "t": "2025-07-11T13:55:56Z",
   "sha": "08302a65338c6f83ba5c818b5513f40ca510786e"
  },
  {
   "t": "2025-07-12T16:21:36Z",
   "sha": "16f5ad2564f4a560a9807552c1ea8773e1d55674"
  },
  {
   "t": "2025-11-30T14:12:53Z",
   "sha": "779da41726165bb98c553e872cdf6bf007f1b4c2"
  },
  {
   "t": "2025-11-30T17:56:28Z",
   "sha": "9a9ecc5a52b9b6b987d832543056b0af3bfb9f2b"
  },
  {
   "t": "2025-12-01T02:11:40Z",
   "sha": "fff6edc3d54caedbfb31c67942e2355dd721ada5"
  },
  {
   "t": "2026-05-16T21:27:36Z",
   "sha": "6ca33cb59e4a1526fb6fd74d0bc84aaa35e3fccd"
  }
 ],
 "additions": 788,
 "deletions": 7,
 "changed_files": 9,
 "commit_count": 3,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": [
    {
     "number": 35788,
     "title": "bitcoin-util: Add evalscript subcommand",
     "author": "ajtowns"
    },
    {
     "number": 29491,
     "title": "[EXPERIMENTAL] Schnorr batch verification for blocks",
     "author": "fjahr"
    }
   ]
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Co1nB3e",
   "EthanHeilman",
   "achow101",
   "ajtowns",
   "awin28522",
   "bigspider",
   "moonsettler",
   "rot13maxi"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-05-16T21:27:36Z",
  "last_reviewer_activity": "2026-04-30T14:03:58Z",
  "last_reviewer": "EthanHeilman",
  "author_silent_days": 123,
  "waiting_on_author_days": 0,
  "days_since_update": 6
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": [
   35788,
   29491
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/script/interpreter.cpp"
 ],
 "body": "# CAT in Tapscript\n\nThis PR provides the necessary code to enable the opcode OP\\_CAT in Tapscript as specified in [BIP-347: OP\\_CAT in Tapscript](https://github.com/bitcoin/bips/blob/master/bip-0347.mediawiki) and [BIN-2024-0001](https://github.com/bitcoin-inquisition/binana/blob/master/2024/BIN-2024-0001.md),\n\n**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.\nIf 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.\nThe PR is not a stand-in for consensus around such decisions.\n\nThis PR includes:\n\n- An implementation of the Tapscript opcode OP\\_CAT along with the flags `SCRIPT_VERIFY_OP_CAT` and `SCRIPT_VERIFY_DISCOURAGE_OP_CAT`.\n- Integration and unit tests which ensure that our CAT  implementation works as expected. This includes ensuring that CAT never uses more that 520 bytes of stack memory. A full description of what these tests cover is given below.\n- An improvement to `src/test/script_tests.cpp` JSON script format enabling the rapid creation of new Tapscript unit tests.\n\n## Activation on Bitcoin-inquisition (signet)\n\n[Bitcoin-inquisition PR 37](https://github.com/bitcoin-inquisition/bitcoin/pull/39) which contained our implementation of OP\\_CAT [was merged into bitcoin-inquisition (signet) on Apr 25 2024](https://github.com/bitcoin-inquisition/bitcoin/pull/39), released as [bitcoin-inquisition release 25.2 on Apr 26 2024.](https://github.com/bitcoin-inquisition/bitcoin/releases/tag/v25.2-inq) and activated in bitcoin-inquisition Apr 30 2024. The bitcoin-inquisition PR was reviewed in the PR review club [(transcript of discussion)](https://bitcoincore.reviews/bitcoin-inquisition-39). Since Apr 30 2024 there have been many OP\\_CAT transactions created and spent on signet.\n\nThe code merged into bitcoin-inquisition differs from this PR, as we have removed the consensus logic which was used to activate it on signet.\n\n## OP\\_CAT Tapscript Implementation\n\nWe 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.\n\nWhen evaluated, the OP\\_CAT instruction:\n\n1. Pops the top two values off the stack,\n2. concatenates the popped values together in stack order,\n3. and then pushes the concatenated value on the top of the stack.\n\nOP\\_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.\n\nSee [BIP 347](https://github.com/bitcoin/bips/blob/master/bip-0347.mediawiki) for a deeper description.\n\n### Errors thrown\n\nIf evaluated OP\\_CAT can throw the following errors:\n\n- If at time of evaluation the stack has fewer than two elements we throw the error: `SCRIPT_ERR_INVALID_STACK_OPERATION`\n- If at time of evaluation the top two stack elements have a combined size greater than `MAX_SCRIPT_ELEMENT_SIZE` (520 bytes) we throw the error: `SCRIPT_ERR_PUSH_SIZE`.\n\n### Script verification flags\n\nWhile 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.\n\n- `SCRIPT_VERIFY_OP_CAT` IF 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.\n\n`SCRIPT_VERIFY_DISCOURAGE_OP_CAT` When set to true, a node receiving any Tapscript transaction containing the opcode OP\\_CAT or OP\\_SUCCESS126 will reject the transaction throwing the error `SCRIPT_ERR_DISCOURAGE_OP_CAT` but not banning the node which relayed the transaction. This prevents nodes from relaying transactions with OP\\_CAT. This  is equivalent to the behavior of `SCRIPT_ERR_DISCOURAGE_OP_CAT = true` when `SCRIPT_VERIFY_OP_CAT = false` as OP\\_CAT is an OP\\_SUCCESS (OP\\_SUCCESS126).\n\nThis is how these two flags are intended to be used to ensure a smooth soft fork.\n\n| Stage | `SCRIPT_VERIFY OP_CAT` | `SCRIPT_VERIFY DISCOURAGE_OP_CAT` | Status |\n| --- | --- | --- | --- |\n| **1.Default** | False | True | This does not represent any change in behavior of the bitcoin-core node. |\n| **2.Upgrading** | True | True | OP\\_CAT is activating. |\n| **3.Upgraded** | True | False | It is clear that OP\\_CAT has been activated and the network has been upgraded. |\n\nThe flag `SCRIPT_ERR_DISCOURAGE_OP_CAT` provides a window of time for the network to fully activate, before nodes will relay or accept transactions containing OP\\_CAT in their mem pools.\n\n## Tests\n\nThis 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:\n\n- That if OP\\_CAT is not activated that there are no changes to bitcoin consensus.\n- That regardless of the activation or non-activation of OP\\_CAT in Tapscript, pre-Tapscript scripts, i.e. Bitcoin scripts, have no changes in behavior.\n- That OP\\_CAT when evaluated throws the expected error when there are less than two elements on the stack.\n- That OP\\_CAT when evaluated in a variety of circumstances and edge cases, successfully concatenates elements of the stack. This includes:\n  - multiple calls to OP\\_CAT in a row,\n  - evaluating OP\\_CAT inside of a IF conditional,\n  - evaluating OP\\_CAT on zero size stack elements, random and large stack elements,\n  - evaluating OP\\_CAT on values being moved to and from the alt stack,\n  - and checking that when evaluated OP\\_CAT concatenates the elements in the expected order.\n\nAll 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.\n\nAdditionally we include three tests outside of the JSON script tests.\n\n- `cat_simple` and `cat_empty_stack` are 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.\n- `cat_dup_test` enumerates all stack element sizes from 1 to 522 bytes and then enumerates up to 10 repetitions of `OP\\_DUP OP\\_CAT`. It then tests if the stack element would exceed 520 bytes and if so did OP\\_CAT throw the error `SCRIPT_ERR_PUSH_SIZE`. This allows us to be certain that OP\\_CAT will not introduce any `OP\\_DUP OP\\_CAT` memory exhaustion attacks.\n\n### Better Tapscript tests in JSON script tests\n\nWhile 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.\nConsider the following pre-tapscript test:\n\n```json\n[\"'aa' 'bb'\", \"CAT 0x4c 0x02 0xaabb EQUAL\", \"P2SH,STRICTENC\", \"DISABLED_OPCODE\", \"CAT disabled\"]\n```\n\nwhereas a Tapscript test for the same script (annotated with comments for better readability) would look like:\n\n```json\n[\n    [\n        \"aa\",\n        \"bb\",\n        \"7e4c02aabb87\", // output script\n        \"c0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d\", // control block\n        0.00000001\n    ],\n    \"\",\n    \"0x51 0x20 0x15048ed3a65748549c27b671936987093cf73a4c9cb18522a74fb9553060ca99\", // Tapscript output\n    \"P2SH,WITNESS,TAPROOT\",\n    \"OK\",\n    \"TAPSCRIPT CATs aa and bb together and checks if EQUAL to aabb\"\n]\n```\n\nComputing 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.\n\nIn this PR we address this issue by adding the following improvements to JSON script tests:\n\n- 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.\n- 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`.\n\nThis results in the following JSON script test which is far easier to write and easier to read.\n\n```json\n[\n    [\n        \"aa\",\n        \"bb\",\n        \"#SCRIPT# CAT\",\n        \"#CONTROLBLOCK#\",\n        0.00000001\n    ],\n    \"\",\n    \"0x51 0x20 #TAPROOTOUTPUT#\",\n    \"P2SH,WITNESS,TAPROOT,OP_CAT\",\n    \"OK\",\n    \"TAPSCRIPT Test of OP_CAT flag by calling CAT on two elements. TAPSCRIPT_OP_CAT flag is set so CAT is executed.\"\n],\n```",
 "commits": [
  {
   "sha": "34af9c3b62b10ca512b28e5c9d980b8e3b6b3866",
   "date": "2026-05-16T21:14:39Z",
   "message": "Re-enable OP_CAT in tapscript\n\nImplement OP_CAT as a new Tapscript op code by redefining the opcode OP_SUCCESS126 (126 in decimal and 0x7e in hexadecimal).\nThis is the same opcode value used by the original OP_CAT.\n\nWhen evaluated, the OP_CAT instruction:\n\nPops the top two values off the stack,\nconcatenates the popped values together in stack order,\nand then pushes the concatenated value on the top of the stack.\nOP_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.\n\nSee [BIP\n347](https://github.com/bitcoin/bips/blob/master/bip-0347.mediawiki) for a deeper description."
  },
  {
   "sha": "b2ed4ff9ea39ef60b7b1ab85bbf0833d30dcd0ae",
   "date": "2026-05-16T21:25:20Z",
   "message": "test: add unit tests for OP_CAT\n\nCo-authored-by: Ethan Heilman <ethan.r.heilman@gmail.com>"
  },
  {
   "sha": "6ca33cb59e4a1526fb6fd74d0bc84aaa35e3fccd",
   "date": "2026-05-16T21:25:25Z",
   "message": "test: add functional test for OP_CAT spends\n\nThe goal of this functional test is to ensure OP_CAT spends are still\ndisabled by default in segwitv0 and legacy spends. Spending such inputs\nshould result in `mandatory-script-verify-flag-failed (Attempted to use a disabled opcode)`.\nWhile spending OP_CAT inputs in tapscript should be discouraged under\nthe default `STANDARD_SCRIPT_VERIFY_FLAGS`."
  }
 ],
 "timeline": [
  {
   "t": "2024-02-01T22:05:09Z",
   "kind": "comment",
   "who": "arminsabouri",
   "assoc": "NONE",
   "text": "Drafting until I get the [inquisition PR](https://github.com/bitcoin-inquisition/bitcoin/pull/39) approved and I can get the builds passing."
  },
  {
   "t": "2024-02-15T10:39:54Z",
   "kind": "comment",
   "who": "moonsettler",
   "assoc": "NONE",
   "text": "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.\n\n**Rationale:**\nIt explicitly disables open ended second order effects by removing the ability to assemble a SIGHASH or CTV template on the stack for more detailed introspection than intended. If such introspection behavior is desired in the future it can be explicitly enabled by specialized and more efficient opcodes.\n\nCombined 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:\n- Merkle inclusion: 2x 20/32 byte hashes\n- LN-symmetry (LNhance): 2x 32 byte hashes\n- Separate sig: 64/72 bytes (0-conf bonds, staking contracts)\n\nCould 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."
  },
  {
   "t": "2024-02-16T09:16:23Z",
   "kind": "comment",
   "who": "bigspider",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nScript 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.\n\nTherefore, 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."
  },
  {
   "t": "2024-05-26T22:05:25Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "e04fb802b278ae4aab2d0d5a63c75ebe0e81560e"
  },
  {
   "t": "2024-05-26T22:06:04Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "9dbf0811dba75a36b213bb72e8f1e8297a9a12a3"
  },
  {
   "t": "2024-05-27T00:39:47Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "b9c67da29c1c2c62891ef52fc5832df1012abba3"
  },
  {
   "t": "2024-05-27T18:07:50Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "f1fd2b6dd8d48a26fda87eb30040116a81e6a5c5"
  },
  {
   "t": "2024-08-27T12:43:51Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "6b259bdff8f790403600d5c9255f78729a4c343a"
  },
  {
   "t": "2024-08-29T01:50:58Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "a04c22b47a2b784793f1afbf4534e04f11771356"
  },
  {
   "t": "2024-08-29T03:47:11Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "3471550367d9a93394e99860a3e580c31ba763fb"
  },
  {
   "t": "2024-08-31T13:13:00Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "a8bf3f4e6d3cf592cfd6fbefd86e77b6b3de8933"
  },
  {
   "t": "2024-08-31T16:09:36Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "457a41b144c02a3abd0e9f95222f4da7e7681cf1"
  },
  {
   "t": "2024-08-31T16:12:14Z",
   "kind": "review",
   "who": "awin28522",
   "assoc": "NONE",
   "state": "APPROVED",
   "commit": "457a41b144c02a3abd0e9f95222f4da7e7681cf1",
   "text": ""
  },
  {
   "t": "2024-08-31T21:06:59Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "f5807a52a3bb61618606cefd499bf0fe61b40930"
  },
  {
   "t": "2024-09-12T19:56:02Z",
   "kind": "review_comment",
   "who": "rot13maxi",
   "assoc": "NONE",
   "path": "src/script/interpreter.cpp",
   "commit": "6ca33cb59e4a1526fb6fd74d0bc84aaa35e3fccd",
   "in_reply_to": null,
   "text": "`SCRIPT_VERIFY_DISCOURAGE_OP_CAT` is acting as a CAT-specific `SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS` that can be turned off while still not relaying transasctions with other OP_SUCCESS opcodes. Since this is the first time we're carving out an OP_SUCCESS and doing this, it would be great to have a test that exercises the combinations of SCRIPT_VERIFY_DISCOURAGE_OP_CAT and SCRIPT_VERIFY_OP_CAT"
  },
  {
   "t": "2024-09-17T01:09:17Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "d6a668a5cdf784274a1f29956dae6ac28fd6d125"
  },
  {
   "t": "2024-09-17T11:54:46Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "6e05acff87be5ca826a3761a83bed88559cadc01"
  },
  {
   "t": "2024-09-17T15:25:30Z",
   "kind": "review_comment",
   "who": "arminsabouri",
   "assoc": "NONE",
   "path": "src/script/interpreter.cpp",
   "commit": "6ca33cb59e4a1526fb6fd74d0bc84aaa35e3fccd",
   "in_reply_to": 1757499656,
   "text": "Great suggestion! Added some additional unit tests to script_tests.json testing different combinations of scripts with and without (SCRIPT_VERIFY_DISCOURAGE_OP_CAT, SCRIPT_VERIFY_OP_CAT and SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS)"
  },
  {
   "t": "2024-10-09T14:19:15Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/script/interpreter.cpp",
   "commit": "6ca33cb59e4a1526fb6fd74d0bc84aaa35e3fccd",
   "in_reply_to": 1757499656,
   "text": "Added to the inquisition PR in https://github.com/bitcoin-inquisition/bitcoin/pull/68"
  },
  {
   "t": "2024-10-15T16:04:00Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "Converting to draft while waiting for consensus on this."
  },
  {
   "t": "2024-10-19T14:55:59Z",
   "kind": "comment",
   "who": "EthanHeilman",
   "assoc": "CONTRIBUTOR",
   "text": "@achow101 What does draft vs non-draft mean in the bitcoin-core github? My understanding was moving from draft to non-draft means code is ready for review. Similar to moving out of WIP.\n\nIs draft/non-draft is being used to signal community consensus or bitcoin-core consensus? If so, I agree this should be a draft."
  },
  {
   "t": "2024-10-19T20:40:08Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "In general, draft means that a PR is not in a state where it could be merged. It indicates to reviewers that they may not want to do any in depth code review of the PR yet.\n\nIn the context of this PR, there does not appear to be consensus for the concept of OP_CAT in TapScript yet, so this PR cannot be merged even if the code is okay. Hence marking it as a draft.\n\nNote that this is a result of a [Kill/Shill/Merge](https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Kill-Shill-Drill) session that occurred at the most recent CoreDev."
  },
  {
   "t": "2024-10-20T14:28:34Z",
   "kind": "comment",
   "who": "EthanHeilman",
   "assoc": "CONTRIBUTOR",
   "text": "@achow101 Thanks, that's helpful for understanding the context. If consensus forms around OP_CAT should I or @0xBEEFCAF3 mark this as no longer as draft? I can't speak for @0xBEEFCAF3 but I would feel slightly uncomfortable making that decision as it would open me up to criticism that I was attempting to manufacture consensus, even if there was consensus. Then again if that is the expectation I suppose we'd have to do that and take the licks. What is the process here?"
  },
  {
   "t": "2024-10-20T15:10:24Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYes. A maintainer may also do so as well."
  },
  {
   "t": "2024-10-29T23:38:10Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "e24086b08a6ced9ec49b0d88cdb6c6b027fd28d3"
  },
  {
   "t": "2024-11-09T15:30:25Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "94fcae92aff8d1ec9e1cd2091c383ecbaba2167e"
  },
  {
   "t": "2024-11-15T14:53:36Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "4e337f7245460a36186d329082eb3451edbcb1a9"
  },
  {
   "t": "2024-12-02T14:01:07Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "01945a50caa08cb287cf1f378dcc46de36e5f7f2"
  },
  {
   "t": "2024-12-02T15:13:33Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "7377841a85cc12a960c069b5897a6d13b468158d"
  },
  {
   "t": "2025-05-07T06:14:29Z",
   "kind": "comment",
   "who": "Co1nB3e",
   "assoc": "NONE",
   "text": "What about SCRIPT_VERIFY OP_CAT = False and SCRIPT_VERIFY DISCOURAGE_OP_CAT = False?\nThis combination is not described in the \"soft fork\" table. Is it part of the tests?"
  },
  {
   "t": "2025-05-07T21:30:22Z",
   "kind": "comment",
   "who": "EthanHeilman",
   "assoc": "CONTRIBUTOR",
   "text": "@AlexSQY That's an interesting question.\nCurrent Bitcoin behavior is `OP_CAT = False` and `DISCOURAGE_OP_CAT = False`. I don't believe we would ever want to set  `DISCOURAGE_OP_CAT = False` while `OP_CAT = False` since a disabled opcode should be discouraged."
  },
  {
   "t": "2025-05-08T14:38:12Z",
   "kind": "comment",
   "who": "Co1nB3e",
   "assoc": "NONE",
   "text": "@EthanHeilman , thanks for the feedback.\nI understand this combination should be discouraged.\nMy concern, perhaps not justified, was about this negative case not being documented/tested, thus wondering about a potential edge case.\nFound out those tapscript test cases but I do not know if \"not set\" is exactly same as \"set to false\" (all those variables set to false by default?):\nhttps://github.com/bitcoin/bitcoin/pull/29247/files#diff-ef0159635ee0fee8864cd9570a04337cabb63e9c49a9e96d7b746194ecaa427d\n\nPS: DISCOURAGE_OP_SUCCESS also introduces more combinations one with the others (2 tested as part of the actual script_tests.json, nominal cases only?)"
  },
  {
   "t": "2025-05-08T14:53:21Z",
   "kind": "comment",
   "who": "EthanHeilman",
   "assoc": "CONTRIBUTOR",
   "text": "@AlexSQY I see your point. Let me see if there is anything that can be done to ensure it is never the case that `OP_CAT = False && DISCOURAGE_OP_CAT = False` since that should never happen outside of tests.\n\nThe behavior is what you see in that test is the expected behavior for `DISCOURAGE_OP_CAT = False` and `OP_CAT = False`,\nsince `OP_CAT = False` the CAT opcode is an OP_SUCCESS so it returns success (`OK`).\n\n@0xBEEFCAF3 What do you think?"
  },
  {
   "t": "2025-05-09T02:49:15Z",
   "kind": "comment",
   "who": "arminsabouri",
   "assoc": "NONE",
   "text": "[quoted text omitted]\n\nBy default, the `SCRIPT_VERIFY_DISCOURAGE_OP_CAT` bit [is set](https://github.com/bitcoin/bitcoin/pull/29247/files#diff-1fc0f6b5081e8ed5dfa8bf230744ad08cc6f4c1147e98552f1f424b0492fe9bdR121), and we do not expose this as a configurable mempool policy. The only practical way for node operators to unset this bit (`SCRIPT_VERIFY_DISCOURAGE_OP_CAT = False`) is by compiling a custom binary.\n\nThat said, @AlexSQY, you're right -- this edge case deserves documentation. If I recall correctly, it\u2019s being tested [here](https://github.com/bitcoin/bitcoin/pull/29247/files#diff-ef0159635ee0fee8864cd9570a04337cabb63e9c49a9e96d7b746194ecaa427dR2541), where neither the discourage bit nor the script verify bit is set. In that case, both are `false`, and `0x7e` just behaves as `OP_SUCCESS`."
  },
  {
   "t": "2025-05-09T03:10:31Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "6e01b2817ccfc2f4627b82edf50b38b9f52d5a16"
  },
  {
   "t": "2025-05-12T15:32:37Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nDISCOURAGE_OP_SUCCESS in current bitcoin covers the behaviour of DISCOURAGE_OP_CAT.\n\nOP_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\".\n\nI think the table should look more like:\n\n| Phase  | Mode | SV_OP_CAT | SV_DISCOURAGE_OP_CAT |\n| ---- | ---- | ---- | ---- |\n| Pre-activation | Transaction relay | :x: (false) | :heavy_check_mark: (true) |\n| Pre-activation | Block validation | :x:  (false) | :x:  (false) |\n| Post-activation | Transaction relay | :heavy_check_mark: (true) | :x:  (false) |\n| Post-activation | Block validation | :heavy_check_mark: (true) | :x:  (false) |"
  },
  {
   "t": "2025-07-11T13:55:56Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "08302a65338c6f83ba5c818b5513f40ca510786e"
  },
  {
   "t": "2025-07-12T16:21:36Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "16f5ad2564f4a560a9807552c1ea8773e1d55674"
  },
  {
   "t": "2025-11-30T14:12:53Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "779da41726165bb98c553e872cdf6bf007f1b4c2"
  },
  {
   "t": "2025-11-30T17:56:28Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "9a9ecc5a52b9b6b987d832543056b0af3bfb9f2b"
  },
  {
   "t": "2025-12-01T02:11:40Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "fff6edc3d54caedbfb31c67942e2355dd721ada5"
  },
  {
   "t": "2026-04-30T14:03:58Z",
   "kind": "comment",
   "who": "EthanHeilman",
   "assoc": "CONTRIBUTOR",
   "text": "@DrahtBot I am still very interested in merging this, however there was unexpected progress in quantum computers and I am temporarily shifting my attention to that. I plan to return this after helping with quantum (hopefully in 2026)."
  },
  {
   "t": "2026-05-16T21:27:36Z",
   "kind": "force_push",
   "who": "arminsabouri",
   "commit": "6ca33cb59e4a1526fb6fd74d0bc84aaa35e3fccd"
  }
 ],
 "labels_log": [
  {
   "t": "2024-01-14T23:27:44Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-01-15T07:49:43Z",
   "action": "labeled",
   "label": "Consensus",
   "who": "ajtowns"
  },
  {
   "t": "2024-05-27T19:28:12Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-08-27T15:16:42Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-08-31T22:26:41Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-09-11T15:55:20Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-09-15T13:11:06Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-09-17T02:33:25Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-09-17T13:22:57Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-10-30T01:11:17Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-11-13T02:13:44Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-11-15T15:50:24Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-11-15T16:22:49Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-04-21T19:39:55Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-09T03:44:02Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-06-16T16:27:52Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-10T09:53:44Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-11T14:59:26Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-12T17:18:13Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-19T16:25:24Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-09-26T11:55:19Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "maflcko"
  },
  {
   "t": "2025-10-07T21:54:54Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-11-30T15:30:22Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-15T17:15:36Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-31T01:08:49Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-16T23:13:05Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-21T15:47:57Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2024-01-14T22:40:47Z",
   "kind": "renamed",
   "who": "DrahtBot",
   "from": "Reenable OP_CAT ",
   "to": "Reenable OP_CAT"
  },
  {
   "t": "2024-02-01T22:04:59Z",
   "kind": "convert_to_draft",
   "who": "arminsabouri"
  },
  {
   "t": "2024-05-27T19:28:48Z",
   "kind": "ready_for_review",
   "who": "arminsabouri"
  },
  {
   "t": "2024-08-29T01:51:35Z",
   "kind": "renamed",
   "who": "arminsabouri",
   "from": "Reenable OP_CAT",
   "to": "CAT in Tapscript (BIP-347)"
  },
  {
   "t": "2024-10-15T16:04:04Z",
   "kind": "convert_to_draft",
   "who": "achow101"
  }
 ],
 "text_chars": 17719,
 "text_tokens_estimate": 4429,
 "changed_paths": [
  "src/policy/policy.h",
  "src/script/interpreter.cpp",
  "src/script/interpreter.h",
  "src/script/script_error.cpp",
  "src/script/script_error.h",
  "src/test/data/script_tests.json",
  "src/test/script_tests.cpp",
  "test/functional/feature_opcat.py",
  "test/functional/test_runner.py"
 ],
 "files": [
  {
   "path": "src/policy/policy.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/script/interpreter.cpp",
   "add": 36,
   "del": 5
  },
  {
   "path": "src/script/interpreter.h",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/script/script_error.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/script/script_error.h",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/test/data/script_tests.json",
   "add": 414,
   "del": 0
  },
  {
   "path": "src/test/script_tests.cpp",
   "add": 79,
   "del": 1
  },
  {
   "path": "test/functional/feature_opcat.py",
   "add": 248,
   "del": 0
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 1,
   "del": 0
  }
 ],
 "test_lines": 743,
 "git": {
  "head": "6ca33cb59e4a1526fb6fd74d0bc84aaa35e3fccd",
  "head_matches_backup": true,
  "base": "ed1795aa17205db6ac15fa7ea1645357740b26ac",
  "commits": [
   {
    "sha": "34af9c3b62",
    "subject": "Re-enable OP_CAT in tapscript",
    "files": 5,
    "add": 46,
    "del": 6
   },
   {
    "sha": "b2ed4ff9ea",
    "subject": "test: add unit tests for OP_CAT",
    "files": 2,
    "add": 493,
    "del": 1
   },
   {
    "sha": "6ca33cb59e",
    "subject": "test: add functional test for OP_CAT spends",
    "files": 2,
    "add": 249,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "c324aecf310f4547",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}