Skip to content

ASSERT_MY_TAPROOT removed: one identity assert - #63

Merged
EvanWinget merged 7 commits into
mainfrom
taproot-removal
Aug 24, 2026
Merged

ASSERT_MY_TAPROOT removed: one identity assert#63
EvanWinget merged 7 commits into
mainfrom
taproot-removal

Conversation

@EvanWinget

@EvanWinget EvanWinget commented Aug 24, 2026

Copy link
Copy Markdown
Owner

The follow-up PR 62 scheduled: ASSERT_MY_TAPROOT (0x37) leaves the vocabulary, ratified as condition-record decision 29 (decision by Evan, 2026-08-23, at the PR 62 review, the venue decision 28 reserved for this call). The objection register also gains the hardware-signer note from the same review conversation.

Why removal loses nothing. ASSERT_MY_TAPTREE proves the same fact at 200 instead of 1,300,200, and the two capabilities that leave with 0x37 are unreachable: the empty-root form asserts a treeless output on a spend that by definition executes a leaf, and exploiting its tweak-collision tolerance requires a discrete log. PR 62 removed the last caller, so the entry would have carried review burden with no user. CREATE_OUTPUT_TAPROOT and the derivation machinery are untouched.

Read the commits in this order

  1. spec: ASSERT_MY_TAPROOT leaves the vocabulary. The table row and entry go, the self assert family is five conditions, 0x37 joins the 0x34 to 0x36 gap (invalid, not reserved), and the taptree entry restates its agreement fact against ASSERT_MY_SCRIPTPUBKEY over the derived scriptPubKey.
  2. conditions: drop the removed assert's implementation. Constant, dataclass, parse function, dispatch branch, cost row, validation branch, display and name tables. The language has 26 condition constants.
  3. vectors: 0x37 re-pins as a gap. 26 cases leave, gap_0x37_invalid arrives, the all-opcodes total re-pins at 14,405,200 (exactly 1,300,200 lower), and the covenant recreation-missing case re-pins on the taptree assert with an honest identity.
  4. tests: the taptree assert's oracle is the tweak derivation directly. The one substantive test change: the agreement property now pairs ASSERT_MY_TAPTREE with ASSERT_MY_SCRIPTPUBKEY over the scriptPubKey its operands derive, keeping the BIP341 derivation as the identity assert's oracle with no intermediate assert.
  5. docs: decision 29 recorded. Record, C24, comparison table, glossary, plan bullet.
  6. docs: the register anticipates the hardware-signer objection. In scope by request: this PR was asked to carry the removal and this note together. NUMS-keyed spends sign outpoint-bound condition digests, not a BIP341 sighash, and shipping hardware signers refuse arbitrary digests. An xpub-only watcher assembles everything else. Phase 4 owes the integration note.

What a reviewer should scrutinize

  • The oracle chain survives the removal. The condition record's rule 2 column had the taproot assert as the taptree assert's oracle through the agreement invariant. That chain is now direct: the shared BIP341 derivation anchors the taptree assert through the derived-scriptPubKey agreement invariant, stated in VALIDATION.md's invariants, the record, and pinned by the rewritten hypothesis property.
  • The gap is visible. gap_0x37_invalid expects bad_condition_opcode, the same shape as the 0x39 gap vector, so a future revival of 0x37 is a loud re-pin, never an accident.
  • The all-opcodes cost total. Old 15,705,400, new 14,405,200. The conditions payload changed only by deleting the one 0x37 element, checked by decoding both.

Verify independently

.venv/bin/pytest python/tests                # 812 passed
.venv/bin/python tools/run_vectors.py        # 38 files, 1106 cases, 0 failures
ci/lint/lint.sh                              # all checks passed
grep -rn "ASSERT_MY_TAPROOT" spec/ python/ tools/ vectors/   # no hits
grep -rn "ASSERT_MY_TAPROOT" docs/           # historical mentions only: the record, the comparison row, the puzzle docs' saving sentences, the plan

Confidence

High, 9 of 10. The removal surface was enumerated by grep before editing and re-checked after, every gate is green, and the one deliberate semantic artifact (the gap vector) is pinned. The vendored Bitcoin Core oracle files were touched by a formatter mid-session and restored to main's blobs before any commit, git diff main -- tools/oracle is empty. Residual risk sits in wording drift across the spec and record, which review reads catch best.

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.
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.
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.
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.
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.
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.
… 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.
@EvanWinget

Copy link
Copy Markdown
Owner Author

Review pass (code-review skill, high, Fable finders, adversarial verify)

Eight finder angles, nine deduplicated candidates, one verifier each. All nine confirmed at least in substance and all are folded in the review fold-in commit:

  1. COSTS.md still said "the two derivation entries" in the section 10 charge-order sentence. Singular now.
  2. The condition record's rule 5 row still claimed vectors for both derivation entries' width and derivation defects after the assert-side twins were deleted. Restated for the one entry.
  3. The puzzle docs' saving baselines compared against ASSERT_MY_TAPROOT in present tense. Both now name it as the derivation assert decision 29 has since removed.
  4. gap_0x37_invalid sat where the deleted parse cases were, away from the contiguous gap block. Moved: the block now reads 0x34, 0x35, 0x36, 0x37, 0x39.
  5. HONEST_SPKS re-derived tweaks the module already held as SPK constants, contradicting the derived-once comment. The dict is now the single derivation site and the constants read from it.
  6. The cost-invariants menu comment undercounted its generic entries after the taptree entry joined. Reworded.
  7. GOOD_INTERNAL_KEY was dead once its only test left. Deleted.
  8. A vestigial f-string prefix survived in the validation clause after its placeholder left. Plain literal now.
  9. The register's owed-by-phase list missed the new hardware-signer note. Phase 4 now carries it beside the section 5 bullet. The scope half of this finding is answered in the PR body: the note was requested together with the removal.

Final: 812 tests, 1106 vector cases, lint clean, CI green.

@EvanWinget
EvanWinget merged commit b0c9f4a into main Aug 24, 2026
2 checks passed
@EvanWinget
EvanWinget deleted the taproot-removal branch August 24, 2026 04:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant