From f7afd5a5f393652bdfbef3bd6baf0eba05e0764e Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 23 Aug 2026 20:49:02 -0700 Subject: [PATCH 1/7] spec: ASSERT_MY_TAPROOT leaves the vocabulary 0x37 is removed from the vocabulary table and its entry deleted. The self assert family is five conditions at 0x30 to 0x33 and 0x38, and 0x37 joins 0x34 to 0x36 as a visible gap inside the family block, invalid rather than reserved under the section 1 tier rule. The taptree entry now states its agreement fact against ASSERT_MY_SCRIPTPUBKEY over the scriptPubKey its operands derive, and its empty-root contrast against CREATE_OUTPUT_TAPROOT. COSTS.md's generic row covers every self assert, the tweak row prices CREATE_OUTPUT_TAPROOT alone, and VALIDATION.md's charge order and invariants speak of one derivation entry. Ratified as condition-record decision 29 (decision by Evan, 2026-08-23, at the PR 62 review, the venue decision 28 scheduled): ASSERT_MY_TAPTREE subsumes the removed assert on every input base consensus admits, the tweak-collision exemption aside, and PR 62 removed its last caller. The two capabilities removed with it are unreachable, an always-false empty-root form and a collision tolerance behind a discrete log. --- spec/CONDITIONS.md | 56 +++++++++++++--------------------------------- spec/COSTS.md | 18 +++++++-------- spec/VALIDATION.md | 23 +++++++++---------- 3 files changed, 33 insertions(+), 64 deletions(-) diff --git a/spec/CONDITIONS.md b/spec/CONDITIONS.md index d63ee7b..24e657c 100644 --- a/spec/CONDITIONS.md +++ b/spec/CONDITIONS.md @@ -82,7 +82,6 @@ block without a vocabulary entry are invalid, not reserved: | `0x31` | `ASSERT_MY_TXID` | | `0x32` | `ASSERT_MY_SCRIPTPUBKEY` | | `0x33` | `ASSERT_MY_AMOUNT` | -| `0x37` | `ASSERT_MY_TAPROOT` | | `0x38` | `ASSERT_MY_TAPTREE` | | `0x40` | `ANNOUNCE` | | `0x41` | `ASSERT_ANNOUNCEMENT` | @@ -458,9 +457,9 @@ charged after every argument check. **Validation rule.** The assert clause of VALIDATION.md (claims and asserts, rule 2). Stage 4. -### Self asserts (`0x30` to `0x33`, `0x37`, `0x38`) +### Self asserts (`0x30` to `0x33`, `0x38`) -The six conditions of this family assert facts of the spending +The five conditions of this family assert facts of the spending input's own prevout data and execution identity: the outpoint it consumes, the creating txid, the spent scriptPubKey, the amount, and the internal key and merkle root its control block carries. @@ -565,32 +564,6 @@ charged after every argument check. **Validation rule.** The assert clause of VALIDATION.md (claims and asserts, rule 2). Stage 2. -### ASSERT_MY_TAPROOT (`0x37`) - -`(0x37 internal_key merkle_root)` - -**Semantics.** Claims nothing. Derives `spk` from `internal_key` -and `merkle_root` by the derivation stated in the -CREATE_OUTPUT_TAPROOT entry, including its `bad_condition_arg` -failures, then asserts that the spent output's scriptPubKey -equals `spk` byte-exact (`unsatisfied_scriptpubkey_assert`). - -A satisfied assert proves the spent output is the taproot output -of `internal_key` tweaked with `merkle_root`. - -**Arguments.** `internal_key` is an atom of exactly 32 bytes and -must satisfy the point derivation (`bad_condition_arg`). -`merkle_root` is an atom of exactly 0 or exactly 32 bytes -(`bad_condition_arg`). The empty atom means the output commits to -no script tree. Exactly two arguments, both atoms. - -**Cost.** `CONDITION_GENERIC_COST + TAPROOT_TWEAK_COST` = -1,300,200 (COSTS.md section 10), charged after every argument -check and before the point derivation. - -**Validation rule.** The assert clause of VALIDATION.md -(claims and asserts, rule 2), after the derivation above. Stage 2. - ### ASSERT_MY_TAPTREE (`0x38`) `(0x38 internal_key merkle_root)` @@ -604,15 +577,15 @@ consensus has already lifted the key, tweaked it with the root, and checked the result against the spent scriptPubKey. A satisfied assert proves the spent output is the taproot output -of `internal_key` tweaked with `merkle_root`, the same fact -ASSERT_MY_TAPROOT proves by derivation. On an input whose spent -scriptPubKey is the taproot output of its `internalKey` tweaked -with its `merkleRoot`, the shape of every input base consensus -admits, the two are satisfied together and fail together, each -with its own error, except where two distinct operand pairs -derive one output key. The transaction view does not require that -shape, so on a view input whose scriptPubKey and identity -disagree only this assert reads the identity. +of `internal_key` tweaked with `merkle_root`. On an input whose +spent scriptPubKey is the taproot output of its `internalKey` +tweaked with its `merkleRoot`, the shape of every input base +consensus admits, this assert over an operand pair and an +ASSERT_MY_SCRIPTPUBKEY carrying the scriptPubKey that pair +derives are satisfied together, except where two distinct operand +pairs derive one output key. The transaction view does not +require that shape, so on a view input whose scriptPubKey and +identity disagree only this assert reads the identity. **Arguments.** `internal_key` is an atom of exactly 32 bytes (`bad_condition_arg`). It is not checked to lift to a curve @@ -621,8 +594,8 @@ an operand that does not lift never matches and fails as unsatisfied. `merkle_root` is an atom of exactly 32 bytes (`bad_condition_arg`). Never empty: a BitLisp spend always executes a leaf of some tree, so there is no no-tree case to -encode, unlike ASSERT_MY_TAPROOT's `merkle_root` operand. Exactly -two arguments, both atoms. +encode, unlike CREATE_OUTPUT_TAPROOT's `merkle_root` operand. +Exactly two arguments, both atoms. **Cost.** `CONDITION_GENERIC_COST` = 200 (COSTS.md section 10), charged after every argument check. @@ -668,7 +641,8 @@ then the appended execution-identity operands: merkle root (`bad_condition_arg`). Never empty: a BitLisp counterpart always executes a leaf of some tree, so there is no no-tree case to encode, the domain ASSERT_MY_TAPTREE's - `merkle_root` operand shares and ASSERT_MY_TAPROOT's does not. + `merkle_root` operand shares and CREATE_OUTPUT_TAPROOT's does + not. `message`, `namespace`, and `payload` are atoms of 0 to 1024 bytes (`bad_condition_arg`). diff --git a/spec/COSTS.md b/spec/COSTS.md index ccd3480..f60b6e6 100644 --- a/spec/COSTS.md +++ b/spec/COSTS.md @@ -354,21 +354,19 @@ implementation. | Constant | Value | Charged for | | --- | --- | --- | -| `CONDITION_GENERIC_COST` | 200 (PROVISIONAL) | Each time assert, each self assert except ASSERT_MY_TAPROOT, each RESERVE_FEE, each SEAL and SEAL_OUTPUTS | +| `CONDITION_GENERIC_COST` | 200 (PROVISIONAL) | Each time assert, each self assert, each RESERVE_FEE, each SEAL and SEAL_OUTPUTS | | `CONDITION_MESSAGE_COST` | 700 (PROVISIONAL) | Each ANNOUNCE, ASSERT_ANNOUNCEMENT, ASSURE, and REQUIRE | | `CONDITION_SIG_ASSERT_COST` | 1,300,000 (PROVISIONAL) | Each signature assert | -| `TAPROOT_TWEAK_COST` | 1,300,000 (PROVISIONAL) | The point derivation, once per CREATE_OUTPUT_TAPROOT and ASSERT_MY_TAPROOT | +| `TAPROOT_TWEAK_COST` | 1,300,000 (PROVISIONAL) | The point derivation, once per CREATE_OUTPUT_TAPROOT | | `CREATE_OUTPUT_COST` | 1,350,000 (PROVISIONAL) | Each output claim | -The derivation entries charge sums: CREATE_OUTPUT_TAPROOT charges -`CREATE_OUTPUT_COST + TAPROOT_TWEAK_COST = 2,650,000` and -ASSERT_MY_TAPROOT charges -`CONDITION_GENERIC_COST + TAPROOT_TWEAK_COST = 1,300,200`, each as +The derivation entry charges a sum: CREATE_OUTPUT_TAPROOT +charges `CREATE_OUTPUT_COST + TAPROOT_TWEAK_COST = 2,650,000` as one charge before its derivation runs. ASSERT_MY_TAPTREE proves -what ASSERT_MY_TAPROOT proves by reading the internal key and -merkle root base consensus already authenticated from the control -block, so it performs no derivation and charges -`CONDITION_GENERIC_COST` alone. A reserved condition +its identity fact by reading the internal key and merkle root +base consensus already authenticated from the control block, so +it performs no derivation and charges `CONDITION_GENERIC_COST` +alone. A reserved condition charges exactly its declared cost, floor 500 (VALIDATION.md rule 6). There is no per-spend constant: this table charges conditions only, and any fixed per-input overhead belongs to the weight diff --git a/spec/VALIDATION.md b/spec/VALIDATION.md index eea8fab..9e70fc1 100644 --- a/spec/VALIDATION.md +++ b/spec/VALIDATION.md @@ -506,12 +506,11 @@ condition's own encoding precedes its charge: the condition's own list-shape check, the opcode tier check, then the arity and argument checks its vocabulary entry states, in the order the entry states them. The condition's whole cost then lands as one -checked charge. The two derivation entries, -CREATE_OUTPUT_TAPROOT and ASSERT_MY_TAPROOT, run their point -derivation after the charge: their width checks are argument -checks and precede it, and their derivation failures follow it, -so a derivation defect is reported only when the budget covers -the charge. A reserved condition charges exactly its declared +checked charge. The derivation entry CREATE_OUTPUT_TAPROOT runs +its point derivation after the charge: its width checks are +argument checks and precede it, and its derivation failures +follow it, so a derivation defect is reported only when the +budget covers the charge. A reserved condition charges exactly its declared cost, after the declared cost's own checks including rule 6's floor. @@ -705,18 +704,16 @@ it enforceable, and lands with that rule: - A lone self assert is satisfied exactly when each operand equals the field it reads, failing otherwise with its field's error (self asserts). -- ASSERT_MY_TAPROOT and an ASSERT_MY_SCRIPTPUBKEY carrying its - derived scriptPubKey produce identical outcomes on every input - (self asserts). - An ASSERT_MY_TAPTREE's outcome is unchanged by the spent scriptPubKey: it reads the execution identity and nothing else (self asserts). - On an input whose spent scriptPubKey is the taproot output of its `internalKey` tweaked with its `merkleRoot`, the shape of - every input base consensus admits, ASSERT_MY_TAPTREE and - ASSERT_MY_TAPROOT over equal operands are satisfied together and - fail together, each with its own error, except where two - distinct operand pairs derive one output key (self asserts). + every input base consensus admits, ASSERT_MY_TAPTREE over an + operand pair and an ASSERT_MY_SCRIPTPUBKEY carrying the + scriptPubKey that pair derives are satisfied together, except + where two distinct operand pairs derive one output key (self + asserts). - Adding a balanced ASSURE and REQUIRE pair to a valid transaction keeps it valid, and adding either half alone invalidates it (rule 3). From 411bad5c3869b160e7867b4a17ec0fdf4d6d7387 Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 23 Aug 2026 20:49:02 -0700 Subject: [PATCH 2/7] conditions: drop the removed assert's implementation The opcode constant, the dataclass, the parse function, the dispatch branch, and the cost row leave python/bitlisp, and the validation assert clause keeps only ASSERT_MY_SCRIPTPUBKEY on the spent-script comparison. The runner's display table, the vector runner's pinned JSON forms, and the compiler's condition-constant list drop the name, the language now 26 condition constants. The taproot derivation helpers stay: CREATE_OUTPUT_TAPROOT owns them. Authorized by spec/CONDITIONS.md sections 1 and 2 as amended in the previous commit, condition-record decision 29. --- docs/lang/language.md | 2 +- python/bitlisp/conditions.py | 48 +++++--------------------------- python/bitlisp/validation.py | 10 ++----- python/bitlisp_tools/compiler.py | 1 - python/bitlisp_tools/runner.py | 2 -- tools/run_vectors.py | 13 +-------- 6 files changed, 11 insertions(+), 65 deletions(-) diff --git a/docs/lang/language.md b/docs/lang/language.md index a4b0927..bd273d1 100644 --- a/docs/lang/language.md +++ b/docs/lang/language.md @@ -165,7 +165,7 @@ end in `.blib`, though nothing enforces an extension. Every condition name of `CONDITIONS.md` section 2 is a built-in constant whose value is its one-byte opcode, `CREATE_OUTPUT` -through `SEAL_OUTPUTS`, 27 names. They inline like any constant, +through `SEAL_OUTPUTS`, 26 names. They inline like any constant, cannot be redefined, and need no declaration: ``` diff --git a/python/bitlisp/conditions.py b/python/bitlisp/conditions.py index 4437818..827b795 100644 --- a/python/bitlisp/conditions.py +++ b/python/bitlisp/conditions.py @@ -52,7 +52,6 @@ ASSERT_MY_TXID = 0x31 ASSERT_MY_SCRIPTPUBKEY = 0x32 ASSERT_MY_AMOUNT = 0x33 -ASSERT_MY_TAPROOT = 0x37 ASSERT_MY_TAPTREE = 0x38 ANNOUNCE = 0x40 ASSERT_ANNOUNCEMENT = 0x41 @@ -182,7 +181,6 @@ ASSERT_MY_TXID: CONDITION_GENERIC_COST, ASSERT_MY_SCRIPTPUBKEY: CONDITION_GENERIC_COST, ASSERT_MY_AMOUNT: CONDITION_GENERIC_COST, - ASSERT_MY_TAPROOT: CONDITION_GENERIC_COST + TAPROOT_TWEAK_COST, ASSERT_MY_TAPTREE: CONDITION_GENERIC_COST, ANNOUNCE: CONDITION_MESSAGE_COST, ASSERT_ANNOUNCEMENT: CONDITION_MESSAGE_COST, @@ -300,32 +298,15 @@ class AssertMyAmount: opcode = ASSERT_MY_AMOUNT -@dataclass(frozen=True) -class AssertMyTaproot: - """Asserts the spent output is the taproot output of these - components. - - script_pubkey is computed from internal_key and merkle_root at - parse time, the same derivation CreateOutputTaproot claims - with, then compared byte-exact against the spent scriptPubKey. - """ - - internal_key: bytes - merkle_root: bytes - script_pubkey: bytes - - opcode = ASSERT_MY_TAPROOT - - @dataclass(frozen=True) class AssertMyTaptree: """Asserts the input's execution identity: the internal key and merkle root its control block carries, each compared byte-exact. - Proves what AssertMyTaproot proves without the derivation: base - consensus has already tweaked this key by this root and checked - the result against the spent scriptPubKey. The internal key is - width-checked only. The field it is compared against always + Base consensus has already lifted this key, tweaked it by this + root, and checked the result against the spent scriptPubKey, so + no derivation runs here. The internal key is width-checked + only. The field it is compared against always lifts to a curve point, so an operand that does not lift never matches and fails as unsatisfied. """ @@ -624,24 +605,11 @@ def _parse_assert_my_amount(args): return AssertMyAmount(amount) -def _parse_assert_my_taproot(args, meter): - if len(args) != 2: - raise BitLispError( - "bad_condition_arity", - f"ASSERT_MY_TAPROOT takes 2 arguments, got {len(args)}", - ) - internal_key, merkle_root = args - _check_taproot_components(internal_key, merkle_root) - meter.charge(CONDITION_COSTS[ASSERT_MY_TAPROOT]) - script_pubkey = _derive_taproot_spk(internal_key, merkle_root) - return AssertMyTaproot(internal_key, merkle_root, script_pubkey) - - def _parse_assert_my_taptree(args): """Two atom operands of exactly 32 bytes each, the internal key - then the merkle root. The root is never empty here, unlike the - taproot assert's: a BitLisp spend always executes a leaf of some - tree.""" + then the merkle root. The root is never empty here, unlike + CREATE_OUTPUT_TAPROOT's: a BitLisp spend always executes a leaf + of some tree.""" if len(args) != 2: raise BitLispError( "bad_condition_arity", @@ -836,8 +804,6 @@ def _parse_condition(node, meter): return condition if opcode == CREATE_OUTPUT_TAPROOT: return _parse_create_output_taproot(args, meter) - if opcode == ASSERT_MY_TAPROOT: - return _parse_assert_my_taproot(args, meter) condition = _parse_assigned(opcode, args) meter.charge(CONDITION_COSTS[opcode]) return condition diff --git a/python/bitlisp/validation.py b/python/bitlisp/validation.py index 43c0f54..c15ea21 100644 --- a/python/bitlisp/validation.py +++ b/python/bitlisp/validation.py @@ -66,7 +66,6 @@ AssertMyAmount, AssertMyOutpoint, AssertMyScriptPubKey, - AssertMyTaproot, AssertMyTaptree, AssertMyTxid, AssertSequenceHeight, @@ -211,16 +210,11 @@ def check_self_asserts(tx): f"ASSERT_MY_TXID demands {cond.txid.hex()}, " f"the creating txid is {tx_input.txid.hex()}", ) - elif isinstance(cond, (AssertMyScriptPubKey, AssertMyTaproot)): + elif isinstance(cond, AssertMyScriptPubKey): if cond.script_pubkey != tx_input.script_pubkey: - name = ( - "ASSERT_MY_TAPROOT" - if isinstance(cond, AssertMyTaproot) - else "ASSERT_MY_SCRIPTPUBKEY" - ) raise BitLispError( "unsatisfied_scriptpubkey_assert", - f"{name} demands " + f"ASSERT_MY_SCRIPTPUBKEY demands " f"{cond.script_pubkey.hex() or '(empty)'}, the spent " f"scriptPubKey is " f"{tx_input.script_pubkey.hex() or '(empty)'}", diff --git a/python/bitlisp_tools/compiler.py b/python/bitlisp_tools/compiler.py index 06cf8f2..0d340b6 100644 --- a/python/bitlisp_tools/compiler.py +++ b/python/bitlisp_tools/compiler.py @@ -141,7 +141,6 @@ "ASSERT_MY_TXID", "ASSERT_MY_SCRIPTPUBKEY", "ASSERT_MY_AMOUNT", - "ASSERT_MY_TAPROOT", "ASSERT_MY_TAPTREE", "ANNOUNCE", "ASSERT_ANNOUNCEMENT", diff --git a/python/bitlisp_tools/runner.py b/python/bitlisp_tools/runner.py index 0aca8e6..45bf5e2 100644 --- a/python/bitlisp_tools/runner.py +++ b/python/bitlisp_tools/runner.py @@ -28,7 +28,6 @@ AssertMyAmount, AssertMyOutpoint, AssertMyScriptPubKey, - AssertMyTaproot, AssertMyTaptree, AssertMyTxid, AssertSequenceHeight, @@ -221,7 +220,6 @@ def run_spend(program, solution, tx, input_index=0, max_cost=DEFAULT_MAX_COST): AssertMyTxid: "ASSERT_MY_TXID", AssertMyScriptPubKey: "ASSERT_MY_SCRIPTPUBKEY", AssertMyAmount: "ASSERT_MY_AMOUNT", - AssertMyTaproot: "ASSERT_MY_TAPROOT", AssertMyTaptree: "ASSERT_MY_TAPTREE", Announce: "ANNOUNCE", AssertAnnouncement: "ASSERT_ANNOUNCEMENT", diff --git a/tools/run_vectors.py b/tools/run_vectors.py index a9f37e2..29bb224 100755 --- a/tools/run_vectors.py +++ b/tools/run_vectors.py @@ -145,7 +145,6 @@ def _condition_json(cond): AssertMyAmount, AssertMyOutpoint, AssertMyScriptPubKey, - AssertMyTaproot, AssertMyTaptree, AssertMyTxid, AssertSequenceHeight, @@ -198,13 +197,6 @@ def _condition_json(cond): return {"opcode": cond.opcode, "script_pubkey": cond.script_pubkey.hex()} if isinstance(cond, AssertMyAmount): return {"opcode": cond.opcode, "amount": cond.amount} - if isinstance(cond, AssertMyTaproot): - return { - "opcode": cond.opcode, - "internal_key": cond.internal_key.hex(), - "merkle_root": cond.merkle_root.hex(), - "script_pubkey": cond.script_pubkey.hex(), - } if isinstance(cond, AssertMyTaptree): return { "opcode": cond.opcode, @@ -281,10 +273,7 @@ def run_conditions_case(case): The self asserts pin {"opcode"} plus their operand under its entry's name ("outpoint", "txid", "script_pubkey", "amount"), - amounts as integers, bytes as hex. ASSERT_MY_TAPROOT pins - {"opcode", "internal_key", "merkle_root", "script_pubkey"} with - script_pubkey the derived taproot script, exactly as - CREATE_OUTPUT_TAPROOT pins it. ASSERT_MY_TAPTREE pins + amounts as integers, bytes as hex. ASSERT_MY_TAPTREE pins {"opcode", "internal_key", "merkle_root"}, its two operands and nothing derived. From a9e70a5de1d232c88371d0d2ba2472c044ba4be1 Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 23 Aug 2026 20:49:02 -0700 Subject: [PATCH 3/7] vectors: 0x37 re-pins as a gap The removed assert's parse, cost, and validation cases leave the corpus: 11 parse cases, 6 cost cases, and 9 validation cases. gap_0x37_invalid pins the opcode as bad_condition_opcode beside the 0x39 gap vector, so the removal is visible and a revival is a deliberate re-pin. The all-assigned-opcodes total re-pins at 14,405,200 with its boundary twin, exactly the removed 1,300,200 lower, the condition list itself losing only the 0x37 element. The covenant recreation-missing case re-pins on the taptree assert with an honest identity as taptree_covenant_recreation_missing, beside the existing taptree_covenant_recreate, so the missing-recreation failure stays pinned, and the verify-then-recreate acceptance it duplicated is already pinned by that taptree case. Spec citation: CONDITIONS.md sections 1 and 2 as amended, condition-record decision 29. Corpus: 38 files, 1106 cases. --- vectors/conditions/costs.json | 79 +------- vectors/conditions/self-asserts.json | 97 +--------- vectors/validation/self-asserts.json | 260 +-------------------------- 3 files changed, 12 insertions(+), 424 deletions(-) diff --git a/vectors/conditions/costs.json b/vectors/conditions/costs.json index 07e8020..b73d6fc 100644 --- a/vectors/conditions/costs.json +++ b/vectors/conditions/costs.json @@ -378,25 +378,10 @@ "cost": 2650000 } }, - { - "name": "assert_my_taproot_costs_1300200", - "conditions": "ffff37ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff808080", - "expect": { - "parsed": [ - { - "opcode": 55, - "internal_key": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "merkle_root": "", - "script_pubkey": "5120da4710964f7852695de2da025290e24af6d8c281de5a0b902b7135fd9fd74d21" - } - ], - "cost": 1300200 - } - }, { "name": "all_assigned_opcodes_total", - "conditions": "ffff01ff51ff8203e880ffff02ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff80ff8203e880ffff10ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff11ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff12ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff13ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff14ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff15ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff16ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff17ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff20ff830c350080ffff21ff8423c3460080ffff22ff0a80ffff23ff0a80ffff30ffa400000000000000000000000000000000000000000000000000000000000000000000000080ffff31ffa0111111111111111111111111111111111111111111111111111111111111111180ffff32ff5180ffff33ff8203e880ffff37ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff8080ffff38ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffa0111111111111111111111111111111111111111111111111111111111111111180ffff40ff826e73ff877061796c6f616480ffff41ff80ff826e73ff877061796c6f616480ffff42ff80ff836d736780ffff43ff80ff836d736780ffff50ff8203e880ffff60ffa0111111111111111111111111111111111111111111111111111111111111111180ffff61ffa011111111111111111111111111111111111111111111111111111111111111118080", - "max_cost": 15705400, + "conditions": "ffff01ff51ff8203e880ffff02ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff80ff8203e880ffff10ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff11ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff12ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff13ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff14ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff15ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff16ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff17ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff20ff830c350080ffff21ff8423c3460080ffff22ff0a80ffff23ff0a80ffff30ffa400000000000000000000000000000000000000000000000000000000000000000000000080ffff31ffa0111111111111111111111111111111111111111111111111111111111111111180ffff32ff5180ffff33ff8203e880ffff38ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffa0111111111111111111111111111111111111111111111111111111111111111180ffff40ff826e73ff877061796c6f616480ffff41ff80ff826e73ff877061796c6f616480ffff42ff80ff836d736780ffff43ff80ff836d736780ffff50ff8203e880ffff60ffa0111111111111111111111111111111111111111111111111111111111111111180ffff61ffa011111111111111111111111111111111111111111111111111111111111111118080", + "max_cost": 14405200, "expect": { "parsed": [ { @@ -491,12 +476,6 @@ "opcode": 51, "amount": 1000 }, - { - "opcode": 55, - "internal_key": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "merkle_root": "", - "script_pubkey": "5120da4710964f7852695de2da025290e24af6d8c281de5a0b902b7135fd9fd74d21" - }, { "opcode": 56, "internal_key": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", @@ -547,13 +526,13 @@ "outputs_hash": "1111111111111111111111111111111111111111111111111111111111111111" } ], - "cost": 15705400 + "cost": 14405200 } }, { "name": "all_assigned_opcodes_one_below_bursts", - "conditions": "ffff01ff51ff8203e880ffff02ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff80ff8203e880ffff10ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff11ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff12ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff13ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff14ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff15ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff16ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff17ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff20ff830c350080ffff21ff8423c3460080ffff22ff0a80ffff23ff0a80ffff30ffa400000000000000000000000000000000000000000000000000000000000000000000000080ffff31ffa0111111111111111111111111111111111111111111111111111111111111111180ffff32ff5180ffff33ff8203e880ffff37ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff8080ffff38ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffa0111111111111111111111111111111111111111111111111111111111111111180ffff40ff826e73ff877061796c6f616480ffff41ff80ff826e73ff877061796c6f616480ffff42ff80ff836d736780ffff43ff80ff836d736780ffff50ff8203e880ffff60ffa0111111111111111111111111111111111111111111111111111111111111111180ffff61ffa011111111111111111111111111111111111111111111111111111111111111118080", - "max_cost": 15705399, + "conditions": "ffff01ff51ff8203e880ffff02ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff80ff8203e880ffff10ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff11ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff12ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff13ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff14ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff15ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff16ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff17ffa00202020202020202020202020202020202020202020202020202020202020202ff836d7367ffc0400303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030380ffff20ff830c350080ffff21ff8423c3460080ffff22ff0a80ffff23ff0a80ffff30ffa400000000000000000000000000000000000000000000000000000000000000000000000080ffff31ffa0111111111111111111111111111111111111111111111111111111111111111180ffff32ff5180ffff33ff8203e880ffff38ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffa0111111111111111111111111111111111111111111111111111111111111111180ffff40ff826e73ff877061796c6f616480ffff41ff80ff826e73ff877061796c6f616480ffff42ff80ff836d736780ffff43ff80ff836d736780ffff50ff8203e880ffff60ffa0111111111111111111111111111111111111111111111111111111111111111180ffff61ffa011111111111111111111111111111111111111111111111111111111111111118080", + "max_cost": 14405199, "expect": { "error": "cost_exceeded" } @@ -675,30 +654,6 @@ "error": "cost_exceeded" } }, - { - "name": "assert_my_taproot_exact_budget_passes", - "conditions": "ffff37ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff808080", - "max_cost": 1300200, - "expect": { - "parsed": [ - { - "opcode": 55, - "internal_key": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "merkle_root": "", - "script_pubkey": "5120da4710964f7852695de2da025290e24af6d8c281de5a0b902b7135fd9fd74d21" - } - ], - "cost": 1300200 - } - }, - { - "name": "assert_my_taproot_one_below_bursts", - "conditions": "ffff37ffa079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff808080", - "max_cost": 1300199, - "expect": { - "error": "cost_exceeded" - } - }, { "name": "assert_my_taptree_exact_budget_passes", "conditions": "ffff38ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", @@ -795,22 +750,6 @@ "error": "cost_exceeded" } }, - { - "name": "assert_taproot_derivation_defect_reported_when_paid", - "conditions": "ffff37ffa0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808080", - "max_cost": 1300200, - "expect": { - "error": "bad_condition_arg" - } - }, - { - "name": "assert_taproot_derivation_unpaid_bursts_first", - "conditions": "ffff37ffa0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808080", - "max_cost": 1300199, - "expect": { - "error": "cost_exceeded" - } - }, { "name": "taproot_width_defect_wins_over_zero_budget", "conditions": "ffff02ff9f11111111111111111111111111111111111111111111111111111111111111ff80ff018080", @@ -819,14 +758,6 @@ "error": "bad_condition_arg" } }, - { - "name": "assert_taproot_width_defect_wins_over_zero_budget", - "conditions": "ffff37ff9f11111111111111111111111111111111111111111111111111111111111111ff808080", - "max_cost": 0, - "expect": { - "error": "bad_condition_arg" - } - }, { "name": "reserved_charges_declared_cost", "conditions": "ffff8180ff8202588080", diff --git a/vectors/conditions/self-asserts.json b/vectors/conditions/self-asserts.json index ba440b4..20cf3ad 100644 --- a/vectors/conditions/self-asserts.json +++ b/vectors/conditions/self-asserts.json @@ -177,101 +177,10 @@ } }, { - "name": "taproot_parses_empty_root", - "conditions": "ffff37ffa0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961dff808080", + "name": "gap_0x37_invalid", + "conditions": "ffff37ffa011111111111111111111111111111111111111111111111111111111111111118080", "expect": { - "parsed": [ - { - "opcode": 55, - "internal_key": "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", - "merkle_root": "", - "script_pubkey": "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" - } - ] - } - }, - { - "name": "taproot_parses_with_root", - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "expect": { - "parsed": [ - { - "opcode": 55, - "internal_key": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", - "merkle_root": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", - "script_pubkey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" - } - ] - } - }, - { - "name": "taproot_internal_key_not_on_curve", - "conditions": "ffff37ffa00000000000000000000000000000000000000000000000000000000000000000ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "expect": { - "error": "bad_condition_arg" - } - }, - { - "name": "taproot_internal_key_31_bytes", - "conditions": "ffff37ff9f187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cfffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "expect": { - "error": "bad_condition_arg" - } - }, - { - "name": "taproot_root_31_bytes", - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ff9f5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b8080", - "expect": { - "error": "bad_condition_arg" - } - }, - { - "name": "taproot_arity_one", - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf278080", - "expect": { - "error": "bad_condition_arity" - } - }, - { - "name": "taproot_arity_three", - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21ff018080", - "expect": { - "error": "bad_condition_arity" - } - }, - { - "name": "taproot_internal_key_33_bytes", - "conditions": "ffff37ffa1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf2700ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "expect": { - "error": "bad_condition_arg" - } - }, - { - "name": "taproot_internal_key_not_canonical", - "conditions": "ffff37ffa0fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "expect": { - "error": "bad_condition_arg" - } - }, - { - "name": "taproot_root_1_byte", - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ff018080", - "expect": { - "error": "bad_condition_arg" - } - }, - { - "name": "taproot_internal_key_is_pair", - "conditions": "ffff37ffffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf2780ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "expect": { - "error": "bad_condition_arg" - } - }, - { - "name": "taproot_root_is_pair", - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21808080", - "expect": { - "error": "bad_condition_arg" + "error": "bad_condition_opcode" } }, { diff --git a/vectors/validation/self-asserts.json b/vectors/validation/self-asserts.json index 1123e9f..b1d9c57 100644 --- a/vectors/validation/self-asserts.json +++ b/vectors/validation/self-asserts.json @@ -591,202 +591,6 @@ "error": "unsatisfied_scriptpubkey_assert" } }, - { - "name": "taproot_satisfied_bip341_tree", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - "amount": 50000, - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" - } - ], - "outputs": [ - { - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 40000 - } - ] - }, - "expect": { - "valid": true - } - }, - { - "name": "taproot_satisfied_bip341_no_tree", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", - "amount": 50000, - "conditions": "ffff37ffa0d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961dff808080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" - } - ], - "outputs": [ - { - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 40000 - } - ] - }, - "expect": { - "valid": true - } - }, - { - "name": "taproot_wrong_root", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - "amount": 50000, - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa099999999999999999999999999999999999999999999999999999999999999998080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" - } - ], - "outputs": [ - { - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 40000 - } - ] - }, - "expect": { - "error": "unsatisfied_scriptpubkey_assert" - } - }, - { - "name": "taproot_vs_non_taproot_prevout", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 50000, - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" - } - ], - "outputs": [ - { - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 40000 - } - ] - }, - "expect": { - "error": "unsatisfied_scriptpubkey_assert" - } - }, - { - "name": "taproot_prefix_vs_p2wsh_sharing_key", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "0020147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - "amount": 50000, - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" - } - ], - "outputs": [ - { - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 40000 - } - ] - }, - "expect": { - "error": "unsatisfied_scriptpubkey_assert" - } - }, - { - "name": "taproot_vs_bare_key_prevout", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - "amount": 50000, - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" - } - ], - "outputs": [ - { - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 40000 - } - ] - }, - "expect": { - "error": "unsatisfied_scriptpubkey_assert" - } - }, - { - "name": "taproot_nums_internal_key_satisfied", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "5120e7f7a8924eaeafddb971a1d05801ce9b0ec566963a21e04f7d52bef66ee593ec", - "amount": 50000, - "conditions": "ffff37ffa050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" - } - ], - "outputs": [ - { - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 40000 - } - ] - }, - "expect": { - "valid": true - } - }, { "name": "taptree_satisfied", "tx": { @@ -927,34 +731,6 @@ "valid": true } }, - { - "name": "taptree_agrees_with_taproot_on_honest_spk", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - "amount": 50000, - "conditions": "ffff38ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b2180ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", - "internal_key": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" - } - ], - "outputs": [ - { - "script_pubkey": "0014bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "amount": 40000 - } - ] - }, - "expect": { - "valid": true - } - }, { "name": "taptree_covenant_recreate", "tx": { @@ -1088,7 +864,7 @@ } }, { - "name": "covenant_verify_then_recreate", + "name": "taptree_covenant_recreation_missing", "tx": { "version": 2, "locktime": 0, @@ -1098,38 +874,10 @@ "index": 1, "script_pubkey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", "amount": 50000, - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b2180ffff02ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21ff83009c408080", - "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" - } - ], - "outputs": [ - { - "script_pubkey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - "amount": 40000 - } - ] - }, - "expect": { - "valid": true - } - }, - { - "name": "covenant_recreation_missing", - "tx": { - "version": 2, - "locktime": 0, - "inputs": [ - { - "txid": "1111111111111111111111111111111111111111111111111111111111111111", - "index": 1, - "script_pubkey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - "amount": 50000, - "conditions": "ffff37ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b2180ffff02ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21ff83009c408080", + "conditions": "ffff38ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b2180ffff02ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21ff83009c408080", "tapleaf": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", - "merkle_root": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", - "internal_key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c" + "merkle_root": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + "internal_key": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" } ], "outputs": [ From 33e856edb05d4010a59a5d7e726156579f6cf2af Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 23 Aug 2026 20:49:02 -0700 Subject: [PATCH 4/7] tests: the taptree assert's oracle is the tweak derivation directly The self-assert suite replaces the taproot-agreement property with agreement between ASSERT_MY_TAPTREE and ASSERT_MY_SCRIPTPUBKEY over the scriptPubKey its operands derive on an honest input: the same oracle chain, the BIP341 tweak derivation still anchoring the identity assert, with the removed assert no longer the intermediate. The duplicate-invariants pool, the cost-invariants menu, the cost-table pins, the runner render pin, and the compiler's transcribed spec table drop the removed entries, the cost menu keeping an identity assert through a taptree entry. --- python/tests/test_compiler.py | 3 +- python/tests/test_condition_costs.py | 11 ---- python/tests/test_cost_invariants.py | 8 +-- python/tests/test_duplicate_invariants.py | 18 ++----- python/tests/test_runner.py | 10 ---- python/tests/test_self_assert_invariants.py | 60 ++++++--------------- 6 files changed, 24 insertions(+), 86 deletions(-) diff --git a/python/tests/test_compiler.py b/python/tests/test_compiler.py index 0284f45..328d3eb 100644 --- a/python/tests/test_compiler.py +++ b/python/tests/test_compiler.py @@ -79,7 +79,6 @@ def _defs(*declarations): (0x31, "ASSERT_MY_TXID"), (0x32, "ASSERT_MY_SCRIPTPUBKEY"), (0x33, "ASSERT_MY_AMOUNT"), - (0x37, "ASSERT_MY_TAPROOT"), (0x38, "ASSERT_MY_TAPTREE"), (0x40, "ANNOUNCE"), (0x41, "ASSERT_ANNOUNCEMENT"), @@ -92,7 +91,7 @@ def _defs(*declarations): def test_condition_constants_match_spec_table(): - assert len(CONDITION_TABLE_ROWS) == 27 + assert len(CONDITION_TABLE_ROWS) == 26 assert CONDITION_CONSTANTS == { name: int_to_atom(opcode) for opcode, name in CONDITION_TABLE_ROWS } diff --git a/python/tests/test_condition_costs.py b/python/tests/test_condition_costs.py index 440159b..ca83191 100644 --- a/python/tests/test_condition_costs.py +++ b/python/tests/test_condition_costs.py @@ -18,7 +18,6 @@ from bitlisp.conditions import ( # noqa: E402 ANNOUNCE, ASSERT_MY_AMOUNT, - ASSERT_MY_TAPROOT, ASSERT_MY_TAPTREE, ASSERT_SIG_RAW, ASSURE, @@ -64,7 +63,6 @@ def test_cost_table_values_match_spec(): assert CONDITION_COSTS[ASSERT_SIG_RAW] == 1_300_000 assert CONDITION_COSTS[CREATE_OUTPUT] == 1_350_000 assert CONDITION_COSTS[CREATE_OUTPUT_TAPROOT] == 2_650_000 - assert CONDITION_COSTS[ASSERT_MY_TAPROOT] == 1_300_200 assert CONDITION_COSTS[ASSERT_MY_TAPTREE] == 200 @@ -129,15 +127,6 @@ def test_derivation_defect_reported_only_when_paid(): assert excinfo.value.code == "cost_exceeded" -def test_taproot_assert_charges_before_deriving(): - node = clist(cond(ASSERT_MY_TAPROOT, GOOD_INTERNAL_KEY, b"")) - cost, _ = parse_conditions(node, max_cost=1_300_200) - assert cost == 1_300_200 - with pytest.raises(BitLispError) as excinfo: - parse_conditions(node, max_cost=1_300_199) - assert excinfo.value.code == "cost_exceeded" - - def test_reserved_charges_exactly_the_declared_cost(): node = clist(cond(0x80, int_to_atom(600))) cost, parsed = parse_conditions(node, max_cost=600) diff --git a/python/tests/test_cost_invariants.py b/python/tests/test_cost_invariants.py index 7c158a0..be82ec8 100644 --- a/python/tests/test_cost_invariants.py +++ b/python/tests/test_cost_invariants.py @@ -20,9 +20,9 @@ from bitlisp import BitLispError, condition_cost, parse_conditions # noqa: E402 from bitlisp.sexp import NIL, int_to_atom # noqa: E402 -# The taproot menu entries derive a point per parse in pure Python, -# so these properties run over few examples with no deadline, the -# convention of the other EC-heavy invariant suites. +# The taproot output claim derives a point per parse in pure +# Python, so these properties run over few examples with no +# deadline, the convention of the other EC-heavy invariant suites. EXAMPLES = settings(max_examples=25, deadline=None) GOOD_KEY = bytes.fromhex( @@ -50,7 +50,7 @@ def cond(opcode, *args): cond(0x16, b"\x02" * 32, b"msg", b"\x03" * 64), cond(0x01, b"\x51", int_to_atom(1000)), cond(0x02, GOOD_KEY, b"", int_to_atom(1000)), - cond(0x37, GOOD_KEY, b""), + cond(0x38, GOOD_KEY, b"\x11" * 32), cond(0x80, int_to_atom(600)), ) diff --git a/python/tests/test_duplicate_invariants.py b/python/tests/test_duplicate_invariants.py index 8bd0c02..1a2b90e 100644 --- a/python/tests/test_duplicate_invariants.py +++ b/python/tests/test_duplicate_invariants.py @@ -42,11 +42,9 @@ AssertMyAmount, AssertMyOutpoint, AssertMyScriptPubKey, - AssertMyTaproot, AssertMyTaptree, AssertMyTxid, ) -from bitlisp.secp256k1 import taproot_output_key from hypothesis import given from hypothesis import strategies as st from support import ( @@ -65,7 +63,6 @@ AssertMyTxid, AssertMyScriptPubKey, AssertMyAmount, - AssertMyTaproot, AssertMyTaptree, Announce, AssertAnnouncement, @@ -83,12 +80,6 @@ def _outpoint(txid): return txid + (0).to_bytes(4, "little") -_TAPROOT_IK = bytes.fromhex( - "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" -) -_TAPROOT_SPK = b"\x51\x20" + taproot_output_key(_TAPROOT_IK, b"") - - def _pool(own_txid, own_script, other_txid, other_script): """Condition candidates for one input. Announcer specifiers, requirer specifiers, and payloads collide across the pools of @@ -96,11 +87,9 @@ def _pool(own_txid, own_script, other_txid, other_script): every landed rule, and the never-announced payload keeps the announcement error path exercised. The self asserts pair each input's own values with the other input's, so satisfied and - failing asserts are both dense, the taproot assert always - fails here (neither input script is a taproot script), keeping - a failing assert's idempotence exercised, and the taptree - asserts pair the identity build_tx installs with a wrong - root.""" + failing asserts are both dense, and the taptree asserts pair + the identity build_tx installs with a wrong root, so a failing + assert's idempotence stays exercised.""" return ( CreateOutput(SCRIPT_A, 1), CreateOutput(SCRIPT_B, 1), @@ -112,7 +101,6 @@ def _pool(own_txid, own_script, other_txid, other_script): AssertMyScriptPubKey(other_script), AssertMyAmount(0), AssertMyAmount(1), - AssertMyTaproot(_TAPROOT_IK, b"", _TAPROOT_SPK), AssertMyTaptree(FILLER_INTERNAL_KEY, FILLER_MERKLE_ROOT), AssertMyTaptree(FILLER_INTERNAL_KEY, FILLER_TAPLEAF), AssertLocktimeHeight(600), diff --git a/python/tests/test_runner.py b/python/tests/test_runner.py index 8c6924e..44d672b 100644 --- a/python/tests/test_runner.py +++ b/python/tests/test_runner.py @@ -29,7 +29,6 @@ AssertMyAmount, AssertMyOutpoint, AssertMyScriptPubKey, - AssertMyTaproot, AssertMyTaptree, AssertMyTxid, AssertSequenceHeight, @@ -112,15 +111,6 @@ def _input(**overrides): (AssertMyTxid(b"\x22" * 32), "ASSERT_MY_TXID txid=0x" + "22" * 32), (AssertMyScriptPubKey(b""), "ASSERT_MY_SCRIPTPUBKEY script_pubkey=0x"), (AssertMyAmount(0), "ASSERT_MY_AMOUNT amount=0"), - ( - AssertMyTaproot(b"\x02" * 32, b"\x04" * 32, bytes.fromhex(SPK_TAPROOT)), - "ASSERT_MY_TAPROOT internal_key=0x" - + "02" * 32 - + " merkle_root=0x" - + "04" * 32 - + " script_pubkey=0x" - + SPK_TAPROOT, - ), ( AssertMyTaptree(b"\x02" * 32, b"\x04" * 32), "ASSERT_MY_TAPTREE internal_key=0x" + "02" * 32 + " merkle_root=0x" + "04" * 32, diff --git a/python/tests/test_self_assert_invariants.py b/python/tests/test_self_assert_invariants.py index 6fe2542..b93a170 100644 --- a/python/tests/test_self_assert_invariants.py +++ b/python/tests/test_self_assert_invariants.py @@ -4,10 +4,10 @@ data, so the family's defining property is environment independence: the outcome is a pure function of (conditions, own prevout data) and nothing else in the transaction can change it. The other properties -pin the txid-outpoint subset relation, the taproot assert's -equivalence to a plain script assert over the derived scriptPubKey, -and the taptree assert's indifference to the scriptPubKey plus its -agreement with the taproot assert on an honest input. Field values +pin the txid-outpoint subset relation and the taptree assert's +indifference to the scriptPubKey plus its agreement, on an honest +input, with a plain script assert over the scriptPubKey its +operands derive. Field values and operands are drawn from small colliding pools so satisfied and unsatisfied asserts are both dense. """ @@ -23,7 +23,6 @@ AssertMyAmount, AssertMyOutpoint, AssertMyScriptPubKey, - AssertMyTaproot, AssertMyTaptree, AssertMyTxid, ) @@ -67,21 +66,15 @@ def _outpoint(txid, index): return txid + index.to_bytes(4, "little") -def _taproot(internal_key, merkle_root): - spk = b"\x51\x20" + taproot_output_key(internal_key, merkle_root) - return AssertMyTaproot(internal_key, merkle_root, spk) - - # Derived once: the tweak is the one expensive step in this module, # and the pools are fixed, so no property recomputes it per example. -TAPROOT_ASSERTS = {pair: _taproot(*pair) for pair in (*IDENTITIES, (IK, b""))} +HONEST_SPKS = {pair: b"\x51\x20" + taproot_output_key(*pair) for pair in IDENTITIES} self_asserts = st.one_of( st.tuples(txids, indexes).map(lambda t: AssertMyOutpoint(_outpoint(*t))), txids.map(AssertMyTxid), scripts.map(AssertMyScriptPubKey), amounts.map(AssertMyAmount), - st.sampled_from(tuple(TAPROOT_ASSERTS.values())), identities.map(lambda t: AssertMyTaptree(*t)), ) cond_lists = st.lists(self_asserts, max_size=4) @@ -180,7 +173,7 @@ def test_single_assert_outcome_matches_field_equality( elif isinstance(cond, AssertMyTxid): satisfied = cond.txid == txid error = "unsatisfied_outpoint_assert" - elif isinstance(cond, (AssertMyScriptPubKey, AssertMyTaproot)): + elif isinstance(cond, AssertMyScriptPubKey): satisfied = cond.script_pubkey == script error = "unsatisfied_scriptpubkey_assert" elif isinstance(cond, AssertMyTaptree): @@ -208,43 +201,22 @@ def test_taptree_assert_ignores_scriptpubkey( @given(txids, indexes, amounts, identities, identities, environments) -def test_taptree_assert_agrees_with_taproot_assert_on_honest_input( +def test_taptree_assert_agrees_with_derived_script_assert_on_honest_input( txid, index, amount, identity, operands, env ): """On an input whose scriptPubKey is the taproot output of its own identity, the shape every input base consensus admits has, - ASSERT_MY_TAPTREE and ASSERT_MY_TAPROOT over equal operands are - satisfied together and fail together. The pools hold no two - pairs deriving one output key, so the collision exemption never - fires here.""" - honest = TAPROOT_ASSERTS[identity].script_pubkey + ASSERT_MY_TAPTREE over an operand pair and ASSERT_MY_SCRIPTPUBKEY + over the scriptPubKey that pair derives are satisfied together: + the BIP341 tweak derivation is the taptree assert's oracle. The + pools hold no two pairs deriving one output key, so the + collision exemption never fires here.""" + honest = HONEST_SPKS[identity] taptree = AssertMyTaptree(*operands) - taproot = TAPROOT_ASSERTS[operands] + plain = AssertMyScriptPubKey(HONEST_SPKS[operands]) got_taptree = outcome( build_tx(txid, index, honest, amount, [taptree], env, identity) ) - got_taproot = outcome( - build_tx(txid, index, honest, amount, [taproot], env, identity) - ) - assert (got_taptree is None) == (got_taproot is None) + got_plain = outcome(build_tx(txid, index, honest, amount, [plain], env, identity)) + assert (got_taptree is None) == (got_plain is None) assert (got_taptree is None) == (operands == identity) - - -@given( - txids, - indexes, - scripts, - amounts, - st.sampled_from(((IK, ROOT), (IK, b""), (NUMS, ROOT))), - environments, -) -def test_taproot_assert_equals_script_assert_on_derived_spk( - txid, index, script, amount, components, env -): - """ASSERT_MY_TAPROOT behaves exactly like ASSERT_MY_SCRIPTPUBKEY - over the scriptPubKey its components derive.""" - taproot = _taproot(*components) - plain = AssertMyScriptPubKey(taproot.script_pubkey) - got_taproot = outcome(build_tx(txid, index, script, amount, [taproot], env)) - got_plain = outcome(build_tx(txid, index, script, amount, [plain], env)) - assert got_taproot == got_plain From 42ad7455ea0e36227423ada11935bcb6bc9f878b Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 23 Aug 2026 20:49:02 -0700 Subject: [PATCH 5/7] docs: decision 29 recorded The condition record gains decision 29 and updates divergence C24 and the rule 2 oracle column, the comparison table marks the own-taproot-components row removed, the glossary drops the removed row and restates the taptree row without it, and the execution plan's amendment bullet records the call. --- docs/condition-comparison.md | 2 +- docs/condition-record.md | 22 ++++++++++++++++++++-- docs/execution-plan.md | 2 +- docs/glossary.md | 3 +-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/condition-comparison.md b/docs/condition-comparison.md index a204b39..747656c 100644 --- a/docs/condition-comparison.md +++ b/docs/condition-comparison.md @@ -100,7 +100,7 @@ delegate to. | own parent | `ASSERT_MY_PARENT_ID` 71 | `ASSERT_MY_TXID` `0x31`, normative, the txid half of the outpoint | | own program | `ASSERT_MY_PUZZLEHASH` 72 | `ASSERT_MY_SCRIPTPUBKEY` `0x32`, normative, raw script bytes | | own amount | `ASSERT_MY_AMOUNT` 73 | `ASSERT_MY_AMOUNT` `0x33`, normative | -| own taproot components | absent | `ASSERT_MY_TAPROOT` `0x37`, normative, the D-CC2 mirror | +| own taproot components | absent | removed (decision 29): `ASSERT_MY_TAPTREE` `0x38` subsumes the derivation assert, `0x37` now a visible gap | | own taptree | absent | `ASSERT_MY_TAPTREE` `0x38`, normative, reads the control block's internal key and merkle root at the generic cost (decision 28) | | own birth time or height | `ASSERT_MY_BIRTH_SECONDS` 74, `ASSERT_MY_BIRTH_HEIGHT` 75 | declined, a chain read outside the transaction view (C13) | | ephemerality | `ASSERT_EPHEMERAL` 76, the coin was created in the same block it is spent | declined, structurally inexpressible within one transaction (C14) | diff --git a/docs/condition-record.md b/docs/condition-record.md index b9e95b6..c382736 100644 --- a/docs/condition-record.md +++ b/docs/condition-record.md @@ -38,7 +38,7 @@ Section 4 registers the rules that have no external reference at all. | C21 | per-spend base cost | 450,000 (SPEND_COST) charged for each coin spend under the hard fork 2 pricing flag, before any condition's own cost | no per-spend constant, conditions alone charge | A Bitcoin input's fixed overhead, prevout fetch and per-input iteration, is work base consensus performs and prices in weight for every input of every transaction, so a cost-unit constant would charge it twice. Declined with a falsifier pre-registered: the Phase 4 measurement pass isolates per-spend overhead Chia-style, n spends of k conditions against nk conditions in fewer spends, and a measured non-byte-proportional overhead introduces the constant then, a tightening that is cheap before publication. Ratified 2026-08-09, decision 25. | `conditions/costs.json` totals contain no per-list constant | | C22 | signature-condition cost | 1,200,000 (AGG_SIG_COST) per occurrence in every regime, pricing one BLS pairing toward the bundle aggregate | 1,300,000 (CONDITION_SIG_ASSERT_COST), equal to the VM's SECP_VERIFY_COST, PROVISIONAL | Both layers of BitLisp price the same BIP340 verification, so the condition constant ties to the operator constant rather than to Chia's BLS figure: one Phase 4 measurement settles both, and a program can never buy the same verification cheaper in one layer than the other. Chia's magnitude corroborates the range, their pairing being work of the same order. Ratified 2026-08-09, decision 25. | `conditions/costs.json` signature cases | | C23 | execution-identity addressing | none: puzzle hash is both the coin's script commitment and the executing program, one field carries both meanings | the specifier table composes the executing leaf's tapleaf hash (bit 3) and the spending path's merkle root (bit 4) onto the prevout rows, commitment values 0 to 31, the mode packed as assurer times 32 plus requirer, both fields validator-filled from the control block. The pair itself is renamed ASSURE and REQUIRE (decision 27) | Taproot splits Chia's one identity into two, and the C9 mapping carried only the script commitment, so program-to-program trust had no faithful addressing field. Internal-key addressing declined as attacker-satisfiable (no possession proof in BIP341). Grafted-leaf and moved-root caveats recorded in decision 26 and in rule 3's author guidance. Ratified 2026-08-18, decision 26, landed 2026-08-20. | `validation/messages.json` identity cases, `conditions/messages.json` composed-mode cases | -| C24 | the cheap identity assert | none: ASSERT_MY_PUZZLEHASH reads the one identity field, and no derive-versus-read split exists because a puzzle hash is a plain tree hash | ASSERT_MY_TAPTREE, a self assert over the control block's internal key and merkle root at the generic cost, the transaction view's identity widened to a triple. ASSERT_MY_TAPROOT, the derivation assert, stays beside it pending the puzzle rework | Taproot's identity is a tweak preimage the control block reveals and base consensus authenticates, so the assert reads it at 200 where the derivation assert re-derives it at a point multiplication, the largest single cost in both landed puzzles. Internal key read by self asserts only: decision 26's attacker-satisfiable objection concerns a counterpart's key, not the spending input's own. Ratified 2026-08-22, decision 28. | `validation/self-asserts.json` taptree cases, `conditions/self-asserts.json` and `conditions/costs.json` taptree cases | +| C24 | the cheap identity assert | none: ASSERT_MY_PUZZLEHASH reads the one identity field, and no derive-versus-read split exists because a puzzle hash is a plain tree hash | ASSERT_MY_TAPTREE, a self assert over the control block's internal key and merkle root at the generic cost, the transaction view's identity widened to a triple. ASSERT_MY_TAPROOT, the derivation assert it subsumes, removed once the puzzle rework landed (decision 29) | Taproot's identity is a tweak preimage the control block reveals and base consensus authenticates, so the assert reads it at 200 where the derivation assert re-derives it at a point multiplication, the largest single cost in both landed puzzles. Internal key read by self asserts only: decision 26's attacker-satisfiable objection concerns a counterpart's key, not the spending input's own. Ratified 2026-08-22, decision 28. | `validation/self-asserts.json` taptree cases, `conditions/self-asserts.json` and `conditions/costs.json` taptree cases | ## 2. Reference provenance @@ -1591,6 +1591,24 @@ Section 4 registers the rules that have no external reference at all. PR, once the measured saving is recorded (decision by Evan, 2026-08-22). +29. **ASSERT_MY_TAPROOT removed.** RATIFIED (decision by Evan, + 2026-08-23, at the puzzle rework PR's review, the venue + decision 28 scheduled). The derivation assert at 0x37 leaves + the vocabulary. ASSERT_MY_TAPTREE subsumes it on every input + base consensus admits, the tweak-collision exemption aside, + and the puzzle rework removed its last caller, so the entry + would have carried review burden with no user. The two + capabilities removed with it are unreachable: the empty-root + form asserts a treeless output on a spend that by definition + executes a leaf, and exploiting the collision tolerance + requires a discrete log. 0x37 joins 0x34 to 0x36 as a visible + gap in the self assert block, invalid rather than reserved, + pinned by a gap vector. The taptree assert's oracle substitute + becomes the BIP341 tweak derivation directly, through the + derived-scriptPubKey agreement invariant over honest inputs, + with no intermediate assert. CREATE_OUTPUT_TAPROOT and the + derivation machinery are untouched. + ## 4. Novel-layer register The validation rules have no external reference: no deployed system @@ -1600,7 +1618,7 @@ for an oracle, per ground rule 4: | rule | status | oracle substitute | | --- | --- | --- | | 1. Injective multiset output matching | normative | hypothesis invariant suite (injectivity, reorder invariance, monotonicity, metamorphic mutations) plus the adversarial corpus in `vectors/validation/`, opening with the duplicate-CREATE_COIN theft vector | -| 2. Mixed-transaction rule | normative | `vectors/validation/mixed-transaction.json`: five acceptance vectors (mixed, plain-only, unclaimed slots, merge, surplus capture) and one rule 1 boundary rejection, plus the addition-monotonicity, merge, and plain-only invariants. The time assert family checks under this rule's assert clause: `vectors/validation/time-asserts.json` with BIP 65 and BIP 68 field semantics as the double reference, plus the operand-monotonicity and boundary-flip invariants. The self assert family checks under the same clause: `vectors/validation/self-asserts.json` with the probe corpus translated to prevout equality cases, the BIP341 tweak derivation shared with CREATE_OUTPUT_TAPROOT as the taproot assert's oracle, the taproot assert in turn as the taptree assert's oracle through the agreement invariant over honest inputs, plus the outpoint-implies-txid, recombination-invariance, and scriptPubKey-indifference invariants. The seal family checks under the same clause with no Chia reference at all (divergence C20): `vectors/validation/seals.json` with the grafted-output interception regression pair, the sealed-merge rejection case, and the fee-input-addition acceptance pinning what SEAL_OUTPUTS permits, the vendored Bitcoin Core framework as the serialization oracle for the txid and outputs-hash derivations, plus the operand byte-flip, sealed-merge, SEAL-implies-SEAL_OUTPUTS, and outputs-only-dependence invariants | +| 2. Mixed-transaction rule | normative | `vectors/validation/mixed-transaction.json`: five acceptance vectors (mixed, plain-only, unclaimed slots, merge, surplus capture) and one rule 1 boundary rejection, plus the addition-monotonicity, merge, and plain-only invariants. The time assert family checks under this rule's assert clause: `vectors/validation/time-asserts.json` with BIP 65 and BIP 68 field semantics as the double reference, plus the operand-monotonicity and boundary-flip invariants. The self assert family checks under the same clause: `vectors/validation/self-asserts.json` with the probe corpus translated to prevout equality cases, the BIP341 tweak derivation shared with CREATE_OUTPUT_TAPROOT as the taptree assert's oracle through the derived-scriptPubKey agreement invariant over honest inputs, plus the outpoint-implies-txid, recombination-invariance, and scriptPubKey-indifference invariants. The seal family checks under the same clause with no Chia reference at all (divergence C20): `vectors/validation/seals.json` with the grafted-output interception regression pair, the sealed-merge rejection case, and the fee-input-addition acceptance pinning what SEAL_OUTPUTS permits, the vendored Bitcoin Core framework as the serialization oracle for the txid and outputs-hash derivations, plus the operand byte-flip, sealed-merge, SEAL-implies-SEAL_OUTPUTS, and outputs-only-dependence invariants | | 3. Message scoping | normative | `vectors/validation/messages.json` and `vectors/validation/announcements.json`: the probe corpus translated from the chia_rs oracle (balance, multiplicity, mode-key, self-send, order cases) plus adversarial wrong-address and forgery cases, and the balanced-pair, announcement-monotonicity, and byte-flip invariants | | 4. Duplicates and multiplicity | normative | `vectors/validation/duplicates.json`: the strictest-wins oracle tests translated to identical and differing time asserts within one input, identical asserts across two and three inputs including the diverging final-sequence counterexample, ANNOUNCE duplication within an input and copies across inputs including the new-fact flip, duplicated announcement asserts at loose and script commitments, and duplicated reserved conditions on both sides of the cost floor, plus the identical-signature-triple and copied-triple-across-inputs cases from the signature assert unit, plus the in-place seal duplication and copied-seal-across-inputs cases from the seal unit, plus the in-place duplication-invariance invariant. The counted-sort boundaries stay pinned where they landed: duplicate claims in `vectors/validation/create-output.json`, duplicate message halves in `vectors/validation/messages.json`. Chia's remaining dedup tests are mempool spend-dedup machinery, declined in decision 19 | | 5. Per-condition costing | normative | `vectors/conditions/costs.json`: every assigned opcode pinned individually at its cost line plus the all-opcodes sum with its boundary twin, exact-budget boundary pairs for every tier (the inclusive budget passing at equality, bursting one below), the charge-order cases (encoding defects win over cost_exceeded within a condition under a zero budget, an earlier condition's charge precedes a later condition's checks and the tail's shape check, width defects of both derivation entries win under a zero budget, derivation defects of both entries reported only when the charge is covered), reserved declared-cost accounting with the floor check winning over the budget, per-occurrence charging of identical conditions, the mixed-family total, and the COSTS.md worked example, plus the cost-conservation, reorder-invariance, append-additivity, per-occurrence, and inclusive-boundary invariants. No oracle: the deployed CHIP-0049 cost table anchors the magnitudes (section 2) and every constant, all PROVISIONAL, is re-priced by the Phase 4 measurement pass | diff --git a/docs/execution-plan.md b/docs/execution-plan.md index b2d8979..63ea83c 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -62,7 +62,7 @@ descriptions, and commit messages* **Done 2026-08-09:** 184 tests, 984 vector cases, lint and diff harness clean at a fresh seed. Owed forward: the fresh-reader predictability exercise transfers to Phase 5 hostile-review preparation. - [x] **Amendment, execution-identity commitment (2026-08-18, decision by Evan, skeleton change under ground rule 6):** landed 2026-08-20. The rule 3 specifier table gains two execution-identity fields, the executing leaf's tapleaf hash and the spending path's merkle root, carried on the transaction view's BitLisp input. Commitment values widen to 5 bits, bit 3 composing the tapleaf and bit 4 the merkle root onto values 0 to 7, the mode repacking as assurer times 32 plus requirer, announcements sharing the table. Taproot splits Chia's puzzle-hash identity into the coin's script commitment and the executing program, and the C9 mapping carried only the first. Internal-key addressing is declined on soundness: the control block proves no possession of the internal key, so an IPK specifier is attacker-satisfiable. Landed with the same-PR rename of the addressed pair to ASSURE and REQUIRE, the halves following as assurer and requirer (decision by Evan, 2026-08-20). Decisions 26 and 27 plus the grafted-leaf and moved-root caveats recorded in `docs/condition-record.md`, divergence row C23, packed-mode constants re-encoded corpus-wide including the vault's, whose instances re-pinned. -- [x] **Amendment, the cheap identity assert (2026-08-22, decision by Evan, skeleton change under ground rule 6):** `ASSERT_MY_TAPTREE internal_key merkle_root`, a self assert at the generic condition cost reading the internal key and merkle root base consensus authenticated from the control block, the transaction view's execution identity gaining the internal key for self asserts only. It proves what ASSERT_MY_TAPROOT proves at 200 instead of 1,300,200, the largest single cost in every puzzle spend. The execution-identity amendment's objection to internal-key addressing (attacker-satisfiable) does not apply to an assert over the spending input's own key, to be stated in the condition record. Acceptance: the vault and the singleton reworked onto it, re-pinned, the saving measured in their docs. Sequenced first after PR 59. Spec, implementation, vectors, and decision 28 landed 2026-08-22 at opcode 0x38 (internal key operand width-checked only, the root never empty, the view's identity a triple), the rework and the fate of ASSERT_MY_TAPROOT following in their own PR (decision by Evan, 2026-08-22). Rework landed 2026-08-23: both puzzles and their corpus on 0x38, the saving measured at 1,300,000 per spend in the puzzle docs, the fate of 0x37 put to Evan at the rework PR's review. +- [x] **Amendment, the cheap identity assert (2026-08-22, decision by Evan, skeleton change under ground rule 6):** `ASSERT_MY_TAPTREE internal_key merkle_root`, a self assert at the generic condition cost reading the internal key and merkle root base consensus authenticated from the control block, the transaction view's execution identity gaining the internal key for self asserts only. It proves what ASSERT_MY_TAPROOT proves at 200 instead of 1,300,200, the largest single cost in every puzzle spend. The execution-identity amendment's objection to internal-key addressing (attacker-satisfiable) does not apply to an assert over the spending input's own key, to be stated in the condition record. Acceptance: the vault and the singleton reworked onto it, re-pinned, the saving measured in their docs. Sequenced first after PR 59. Spec, implementation, vectors, and decision 28 landed 2026-08-22 at opcode 0x38 (internal key operand width-checked only, the root never empty, the view's identity a triple), the rework and the fate of ASSERT_MY_TAPROOT following in their own PR (decision by Evan, 2026-08-22). Rework landed 2026-08-23: both puzzles and their corpus on 0x38, the saving measured at 1,300,000 per spend in the puzzle docs, the fate of 0x37 put to Evan at the rework PR's review and decided there: ASSERT_MY_TAPROOT removed in its own PR (decision by Evan, 2026-08-23, condition-record decision 29). **Claude Code fit:** strong. Design decisions stay in Fable 5 sessions and land in spec prose first; Claude Code implements and generates vectors. diff --git a/docs/glossary.md b/docs/glossary.md index d9e5d81..841e3ec 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -28,8 +28,7 @@ A term enters this table in the same PR that introduces it. | ASSERT_MY_TXID | asserts the txid half of the input's outpoint | ASSERT_MY_PARENT_ID | a deliberate strict subset of ASSERT_MY_OUTPOINT, the index left unconstrained | | ASSERT_MY_SCRIPTPUBKEY | asserts the spent output's scriptPubKey | ASSERT_MY_PUZZLEHASH | raw script bytes per divergence C1, empty script allowed as prevout content | | ASSERT_MY_AMOUNT | asserts the spent output's amount | ASSERT_MY_AMOUNT | numeric equality in satoshis | -| ASSERT_MY_TAPROOT | verifies the spent scriptPubKey against the BIP341 tweak of its components | none | the assert-side mirror of CREATE_OUTPUT_TAPROOT, the self-propagation covenant primitive | -| ASSERT_MY_TAPTREE | asserts the internal key and merkle root the control block carries | ASSERT_MY_PUZZLEHASH is the nearest, reading the one identity field | proves what ASSERT_MY_TAPROOT proves by reading the identity base consensus authenticated rather than re-deriving it, at the generic cost (condition-record decision 28, divergence C24) | +| ASSERT_MY_TAPTREE | asserts the internal key and merkle root the control block carries | ASSERT_MY_PUZZLEHASH is the nearest, reading the one identity field | proves the spent output is the taproot output of its operands by reading the identity base consensus authenticated, no derivation, the self-propagation covenant primitive beside CREATE_OUTPUT_TAPROOT (condition-record decisions 28 and 29, divergence C24) | | satisfier | compare miniscript satisfactions, which are spender-side | none | the transaction resource assigned to a claim, one per claim. A miniscript satisfaction is what the spender gives a script, a satisfier is what the transaction gives a claim | | composition guarantee | batching and coinjoin practice, no consensus equivalent | spend bundle aggregation, offers | two valid transactions with disjoint outpoints, same-typed locktimes, and no seal condition concatenate into a valid transaction (VALIDATION.md preamble, condition-record decisions 14, 15, and 24) | | spend | input plus its witness | coin spend | one input's evaluation and conditions | From 07924b93a901e831f0ef16af835e90d8db47e046 Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 23 Aug 2026 20:49:02 -0700 Subject: [PATCH 6/7] docs: the register anticipates the hardware-signer objection A NUMS-keyed construction has no key path, so every spend is a script-path execution whose authorizations sign BitLisp's outpoint-bound condition digests rather than a BIP341 sighash. Shipping hardware signers refuse arbitrary 32-byte digests, so a hardware-backed wallet cannot sign these spends until firmware understands the digest scheme, even though an xpub-only watcher assembles everything else. Raised while weighing the xpub wallet flow at the PR 62 review, owed to Phase 4. --- docs/objection-register.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/objection-register.md b/docs/objection-register.md index 2d1a024..108f21e 100644 --- a/docs/objection-register.md +++ b/docs/objection-register.md @@ -149,6 +149,14 @@ essay states them first. composition whose upstream record O35 calls thin, and Bitcoin's positional identity (O25) qualifies it further. The essay must not lean on it harder than 4.4 allows. +- Every spend of a NUMS-keyed construction is a script-path + execution whose authorizations sign BitLisp's outpoint-bound + condition digests, not a BIP341 sighash. Shipping hardware + signers refuse to sign arbitrary 32-byte digests, so a + hardware-backed wallet cannot sign these spends until device + firmware understands the digest scheme, even though an + xpub-only watcher can assemble everything else. Phase 4 owes + the integration note. - The four design obligations were set by the same author who designs against them (the correlated-blind-spot item on the plan's risk watch-list). External implementers arrive only after Phase 5, From 3fee7a8246ecdc8560b7a3f2afd92d1d0192871d Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 23 Aug 2026 21:03:53 -0700 Subject: [PATCH 7/7] review fold-ins: singular derivation prose, gap block contiguous, one tweak site Nine findings from the review pass, all verified. The spec's charge-order sentence and the record's rule 5 row go singular with the removal. The puzzle docs' saving baselines name the removed assert as removed. gap_0x37_invalid moves into the contiguous gap block so an auditor reading the block sees every gap. The self-assert suite derives each honest scriptPubKey once, restoring the derived-once comment's truth. The cost-invariants menu comment counts its generic entries honestly, the dead GOOD_INTERNAL_KEY constant and a vestigial f-string prefix go, and the register's Phase 4 owed list carries the hardware-signer integration note beside its section 5 bullet. --- docs/condition-record.md | 2 +- docs/objection-register.md | 3 +++ docs/puzzles/singleton.md | 3 ++- docs/puzzles/vault.md | 3 ++- python/bitlisp/validation.py | 2 +- python/tests/test_condition_costs.py | 4 ---- python/tests/test_cost_invariants.py | 5 +++-- python/tests/test_self_assert_invariants.py | 17 ++++++++--------- spec/COSTS.md | 2 +- vectors/conditions/self-asserts.json | 14 +++++++------- 10 files changed, 28 insertions(+), 27 deletions(-) diff --git a/docs/condition-record.md b/docs/condition-record.md index c382736..af74c71 100644 --- a/docs/condition-record.md +++ b/docs/condition-record.md @@ -1621,7 +1621,7 @@ for an oracle, per ground rule 4: | 2. Mixed-transaction rule | normative | `vectors/validation/mixed-transaction.json`: five acceptance vectors (mixed, plain-only, unclaimed slots, merge, surplus capture) and one rule 1 boundary rejection, plus the addition-monotonicity, merge, and plain-only invariants. The time assert family checks under this rule's assert clause: `vectors/validation/time-asserts.json` with BIP 65 and BIP 68 field semantics as the double reference, plus the operand-monotonicity and boundary-flip invariants. The self assert family checks under the same clause: `vectors/validation/self-asserts.json` with the probe corpus translated to prevout equality cases, the BIP341 tweak derivation shared with CREATE_OUTPUT_TAPROOT as the taptree assert's oracle through the derived-scriptPubKey agreement invariant over honest inputs, plus the outpoint-implies-txid, recombination-invariance, and scriptPubKey-indifference invariants. The seal family checks under the same clause with no Chia reference at all (divergence C20): `vectors/validation/seals.json` with the grafted-output interception regression pair, the sealed-merge rejection case, and the fee-input-addition acceptance pinning what SEAL_OUTPUTS permits, the vendored Bitcoin Core framework as the serialization oracle for the txid and outputs-hash derivations, plus the operand byte-flip, sealed-merge, SEAL-implies-SEAL_OUTPUTS, and outputs-only-dependence invariants | | 3. Message scoping | normative | `vectors/validation/messages.json` and `vectors/validation/announcements.json`: the probe corpus translated from the chia_rs oracle (balance, multiplicity, mode-key, self-send, order cases) plus adversarial wrong-address and forgery cases, and the balanced-pair, announcement-monotonicity, and byte-flip invariants | | 4. Duplicates and multiplicity | normative | `vectors/validation/duplicates.json`: the strictest-wins oracle tests translated to identical and differing time asserts within one input, identical asserts across two and three inputs including the diverging final-sequence counterexample, ANNOUNCE duplication within an input and copies across inputs including the new-fact flip, duplicated announcement asserts at loose and script commitments, and duplicated reserved conditions on both sides of the cost floor, plus the identical-signature-triple and copied-triple-across-inputs cases from the signature assert unit, plus the in-place seal duplication and copied-seal-across-inputs cases from the seal unit, plus the in-place duplication-invariance invariant. The counted-sort boundaries stay pinned where they landed: duplicate claims in `vectors/validation/create-output.json`, duplicate message halves in `vectors/validation/messages.json`. Chia's remaining dedup tests are mempool spend-dedup machinery, declined in decision 19 | -| 5. Per-condition costing | normative | `vectors/conditions/costs.json`: every assigned opcode pinned individually at its cost line plus the all-opcodes sum with its boundary twin, exact-budget boundary pairs for every tier (the inclusive budget passing at equality, bursting one below), the charge-order cases (encoding defects win over cost_exceeded within a condition under a zero budget, an earlier condition's charge precedes a later condition's checks and the tail's shape check, width defects of both derivation entries win under a zero budget, derivation defects of both entries reported only when the charge is covered), reserved declared-cost accounting with the floor check winning over the budget, per-occurrence charging of identical conditions, the mixed-family total, and the COSTS.md worked example, plus the cost-conservation, reorder-invariance, append-additivity, per-occurrence, and inclusive-boundary invariants. No oracle: the deployed CHIP-0049 cost table anchors the magnitudes (section 2) and every constant, all PROVISIONAL, is re-priced by the Phase 4 measurement pass | +| 5. Per-condition costing | normative | `vectors/conditions/costs.json`: every assigned opcode pinned individually at its cost line plus the all-opcodes sum with its boundary twin, exact-budget boundary pairs for every tier (the inclusive budget passing at equality, bursting one below), the charge-order cases (encoding defects win over cost_exceeded within a condition under a zero budget, an earlier condition's charge precedes a later condition's checks and the tail's shape check, the derivation entry's width defect winning under a zero budget and its derivation defect reported only when the charge is covered), reserved declared-cost accounting with the floor check winning over the budget, per-occurrence charging of identical conditions, the mixed-family total, and the COSTS.md worked example, plus the cost-conservation, reorder-invariance, append-additivity, per-occurrence, and inclusive-boundary invariants. No oracle: the deployed CHIP-0049 cost table anchors the magnitudes (section 2) and every constant, all PROVISIONAL, is re-priced by the Phase 4 measurement pass | | 6. Reserved conditions | normative | encoding vectors in `vectors/conditions/`, every error path pinned | | 7. The fee reserve | normative | `vectors/validation/reserve-fee.json`: the probe corpus translated from the chia_rs oracle (within-spend and cross-input accumulation, boundary equality, one-short rejection, zero reserve, a reserve stack no fee can reach) plus the fee-theft grafted-output regression vector, the surplus-capture acceptance vector pinning what the reserve does not protect, the above-2^32 and off-boundary separating cases from the review's mutation pass, and the operand-monotonicity, split, and boundary invariants | | 8. Signature asserts | normative | `vectors/validation/signature-asserts.json`: satisfied and failing triples for every variant with signatures produced by the vendored Bitcoin Core framework signer (the recorded `secp_verify` signing oracle), the fixed-message rewrite regression pair pinning the decision 23 footgun, variant-separation cases pinning txid against outpoint, raw against bound in both directions, and each single-field variant against the two-field variant extending it (exhaustive pair separation lives in the hypothesis invariant), raw-mode replay acceptance pinning what RAW does not protect, plus the own-data-only, operand byte-flip, and variant-separation invariants. The BIP340 official vectors bind the verification relation itself through the shared `secp_verify` implementation | diff --git a/docs/objection-register.md b/docs/objection-register.md index 108f21e..54c9828 100644 --- a/docs/objection-register.md +++ b/docs/objection-register.md @@ -119,6 +119,9 @@ Phase 4: - O20, the measured cost schedule and the weight mapping. - O21, derived versus committed budget. - O28, the witness-byte cost of computed-over-context claims. +- The hardware-signer digest scheme (section 5): whether and how a + signing device learns the outpoint-bound condition digests, the + integration note. Phase 5: - O6, the covenant vocabulary and the per-construct termination diff --git a/docs/puzzles/singleton.md b/docs/puzzles/singleton.md index b6a1911..e9fa892 100644 --- a/docs/puzzles/singleton.md +++ b/docs/puzzles/singleton.md @@ -578,7 +578,8 @@ million for the taptree assert, the signature assert, the seal, and the two output claims. Through the single-spend runner the first spend totals 4,167,737, a later generation 4,245,698, and the ending spend 4,262,797, each exactly 1,300,000 below the same -spend under ASSERT_MY_TAPROOT: the taptree assert reads the +spend under ASSERT_MY_TAPROOT, the derivation assert decision +29 has since removed: the taptree assert reads the identity base consensus authenticated from the control block instead of re-deriving the coin's scriptPubKey. diff --git a/docs/puzzles/vault.md b/docs/puzzles/vault.md index f192e5e..c3238df 100644 --- a/docs/puzzles/vault.md +++ b/docs/puzzles/vault.md @@ -353,7 +353,8 @@ spend with no revault costs 4,054,131 and one with a revault claim 6,704,696, a keyless recovery 1,407,940, a matured withdrawal 49,468, a consolidation leader with two followers 1,418,898, and a follower 57,496. Each is exactly 1,300,000 below -the same spend under ASSERT_MY_TAPROOT, the point multiplication +the same spend under ASSERT_MY_TAPROOT, the derivation assert decision 29 +has since removed, the point multiplication the taptree assert does not run because base consensus already authenticated the identity it reads. The condition charge itself is 200 against 1,300,200, and a minimal spend emitting one diff --git a/python/bitlisp/validation.py b/python/bitlisp/validation.py index c15ea21..0cebad4 100644 --- a/python/bitlisp/validation.py +++ b/python/bitlisp/validation.py @@ -214,7 +214,7 @@ def check_self_asserts(tx): if cond.script_pubkey != tx_input.script_pubkey: raise BitLispError( "unsatisfied_scriptpubkey_assert", - f"ASSERT_MY_SCRIPTPUBKEY demands " + "ASSERT_MY_SCRIPTPUBKEY demands " f"{cond.script_pubkey.hex() or '(empty)'}, the spent " f"scriptPubKey is " f"{tx_input.script_pubkey.hex() or '(empty)'}", diff --git a/python/tests/test_condition_costs.py b/python/tests/test_condition_costs.py index ca83191..7268a94 100644 --- a/python/tests/test_condition_costs.py +++ b/python/tests/test_condition_costs.py @@ -32,10 +32,6 @@ # An x above the field prime lifts to no curve point, so this key # passes every width check and fails the derivation. BAD_INTERNAL_KEY = b"\xff" * 32 -# A generator-point x, always liftable. -GOOD_INTERNAL_KEY = bytes.fromhex( - "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" -) def clist(*items): diff --git a/python/tests/test_cost_invariants.py b/python/tests/test_cost_invariants.py index be82ec8..1b34c17 100644 --- a/python/tests/test_cost_invariants.py +++ b/python/tests/test_cost_invariants.py @@ -41,8 +41,9 @@ def cond(opcode, *args): return clist(bytes([opcode]), *args) -# One well-formed condition per cost tier, plus a second generic -# and a reserved declaration off the floor. +# One well-formed condition per cost tier, the generic tier drawn +# three times (an amount assert, a time assert, and the identity +# assert), and a reserved declaration off the floor. MENU = ( cond(0x33, int_to_atom(1000)), cond(0x20, int_to_atom(800_000)), diff --git a/python/tests/test_self_assert_invariants.py b/python/tests/test_self_assert_invariants.py index b93a170..87013da 100644 --- a/python/tests/test_self_assert_invariants.py +++ b/python/tests/test_self_assert_invariants.py @@ -45,9 +45,15 @@ # draws one: the shared corpus filler no assert in the pools names. FILLER_IDENTITY = (FILLER_INTERNAL_KEY, FILLER_MERKLE_ROOT) -SPK_IK_ROOT = b"\x51\x20" + taproot_output_key(IK, ROOT) +# (internal key, merkle root) pairs, drawn both as the input's own +# identity and as taptree operands, so the two collide often. +IDENTITIES = ((IK, ROOT), (IK, ROOT_B), (NUMS, ROOT)) +# Derived once: the tweak is the one expensive step in this module, +# and the pools are fixed, so nothing recomputes it per example. +HONEST_SPKS = {pair: b"\x51\x20" + taproot_output_key(*pair) for pair in IDENTITIES} +SPK_IK_ROOT = HONEST_SPKS[(IK, ROOT)] SPK_IK_PLAIN = b"\x51\x20" + taproot_output_key(IK, b"") -SPK_NUMS_ROOT = b"\x51\x20" + taproot_output_key(NUMS, ROOT) +SPK_NUMS_ROOT = HONEST_SPKS[(NUMS, ROOT)] SPK_P2WSH_TWIN = b"\x00\x20" + SPK_IK_ROOT[2:] txids = st.sampled_from((TXID_A, TXID_B)) @@ -56,9 +62,6 @@ (b"", b"\x51", SPK_IK_ROOT, SPK_IK_PLAIN, SPK_NUMS_ROOT, SPK_P2WSH_TWIN) ) amounts = st.sampled_from((0, 1, 50_000, 50_000 + 2**32, 2_100_000_000_000_000)) -# (internal key, merkle root) pairs, drawn both as the input's own -# identity and as taptree operands, so the two collide often. -IDENTITIES = ((IK, ROOT), (IK, ROOT_B), (NUMS, ROOT)) identities = st.sampled_from(IDENTITIES) @@ -66,10 +69,6 @@ def _outpoint(txid, index): return txid + index.to_bytes(4, "little") -# Derived once: the tweak is the one expensive step in this module, -# and the pools are fixed, so no property recomputes it per example. -HONEST_SPKS = {pair: b"\x51\x20" + taproot_output_key(*pair) for pair in IDENTITIES} - self_asserts = st.one_of( st.tuples(txids, indexes).map(lambda t: AssertMyOutpoint(_outpoint(*t))), txids.map(AssertMyTxid), diff --git a/spec/COSTS.md b/spec/COSTS.md index f60b6e6..7a3c06e 100644 --- a/spec/COSTS.md +++ b/spec/COSTS.md @@ -333,7 +333,7 @@ Condition costs accrue on the same per-input budget as evaluation cost, under the accounting and charge order of VALIDATION.md rule 5: for each condition in list order, every encoding check first, then the condition's whole cost as one checked charge, then, for -the two derivation entries, the point derivation. The budget is +the derivation entry, the point derivation. The budget is inclusive and bursting it raises `cost_exceeded`, the VM budget semantics unchanged. diff --git a/vectors/conditions/self-asserts.json b/vectors/conditions/self-asserts.json index 20cf3ad..432e8e4 100644 --- a/vectors/conditions/self-asserts.json +++ b/vectors/conditions/self-asserts.json @@ -176,13 +176,6 @@ "error": "bad_condition_arg" } }, - { - "name": "gap_0x37_invalid", - "conditions": "ffff37ffa011111111111111111111111111111111111111111111111111111111111111118080", - "expect": { - "error": "bad_condition_opcode" - } - }, { "name": "taptree_parses", "conditions": "ffff38ffa0187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27ffa05b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b218080", @@ -314,6 +307,13 @@ "error": "bad_condition_opcode" } }, + { + "name": "gap_0x37_invalid", + "conditions": "ffff37ffa011111111111111111111111111111111111111111111111111111111111111118080", + "expect": { + "error": "bad_condition_opcode" + } + }, { "name": "gap_0x39_invalid", "conditions": "ffff39ffa011111111111111111111111111111111111111111111111111111111111111118080",