<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: aaron.recompile</title>
    <description>The latest articles on DEV Community by aaron.recompile (@aaron_recompile).</description>
    <link>https://dev.to/aaron_recompile</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4012874%2Fe103f2a5-bac7-473d-b1ae-4df57fcee3fb.png</url>
      <title>DEV Community: aaron.recompile</title>
      <link>https://dev.to/aaron_recompile</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aaron_recompile"/>
    <language>en</language>
    <item>
      <title>Why V3 Matters: Bitcoin’s Relay Layer Was Rewritten, and Most People Didn’t Notice</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:48:03 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/why-v3-matters-bitcoins-relay-layer-was-rewritten-and-most-people-didnt-notice-m05</link>
      <guid>https://dev.to/aaron_recompile/why-v3-matters-bitcoins-relay-layer-was-rewritten-and-most-people-didnt-notice-m05</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitcoin is not a state machine.
It is a verifiable sequence of events.

"Event Machine Letters — Protocol Thoughts on Bitcoin’s Architecture" begins here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  1. What triggered this&amp;nbsp;article
&lt;/h2&gt;

&lt;p&gt;On November 20, &lt;strong&gt;t-bast&lt;/strong&gt; quietly announced something that should not have been possible in 2019:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Two fully compatible implementations of 0-fee channels (Eclair + LDK), and the spec is final.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To many, this looks like an incremental Lightning update.&lt;/p&gt;

&lt;p&gt;In reality, it is the public surface area of a &lt;strong&gt;multi-year restructuring&lt;/strong&gt; of Bitcoin Core’s relay layer — work led by developers such as Gloria Zhao, instagibbs, fanquake, Suhas Daftuar, Pieter Wuille, and others.&lt;/p&gt;

&lt;p&gt;Matt Corallo summarized it perfectly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“People underestimate how insanely much work went into restructuring the way Bitcoin Core relays transactions so that protocols like Lightning, Ark, and Spark can be radically simpler.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr2dpzv5uq6teapfl2pa5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr2dpzv5uq6teapfl2pa5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article explains &lt;strong&gt;what actually changed&lt;/strong&gt;, &lt;strong&gt;why it matters&lt;/strong&gt;, and &lt;strong&gt;how V3 transforms Bitcoin’s fee and security model&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The real problem Lightning could not&amp;nbsp;fix
&lt;/h2&gt;

&lt;p&gt;Lightning’s limitations were never just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;routing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;liquidity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTLC abstraction&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;channel factories&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lightning’s biggest unsolved problem was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bitcoin could not reliably propagate multi-transaction packages under real fee pressure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This created three critical weaknesses:&lt;/p&gt;

&lt;h2&gt;
  
  
  (1) Unpredictable fee&amp;nbsp;bumping
&lt;/h2&gt;

&lt;p&gt;CPFP only worked in “clean” mempools.&lt;/p&gt;

&lt;h2&gt;
  
  
  (2) Replacement pinning
&lt;/h2&gt;

&lt;p&gt;Attackers could attach massive children (10,000+ bytes) to inflate absolute fee required for replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  (3) Unsafe HTLC resolution under congestion
&lt;/h2&gt;

&lt;p&gt;Because commitment + HTLC-success/timeout would not reliably relay as a unit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The root cause was in Bitcoin Core’s relay policy, not Lightning.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lightning could never fix this alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. A concrete example: replacement pinning in the old&amp;nbsp;model
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Carol → Bob → Alice&lt;/strong&gt; (0.5 BTC HTLC)&lt;/p&gt;

&lt;p&gt;Bob receives 0.5 BTC from Carol. Bob now tries to cheat Alice by publishing an old commitment transaction.&lt;/p&gt;

&lt;p&gt;Alice must publish a penalty transaction before Bob’s timelock expires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But Bob can pin her:&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bob’s attack&amp;nbsp;(pre-V3)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Broadcasts the old commitment (≈300 bytes)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Immediately attaches a huge CPFP transaction &lt;br&gt;
&amp;nbsp;&lt;em&gt;e.g. 10,000 bytes at 5 sat/vB → 50,000 sats&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now miners see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commitment (300 bytes)
+ child (10,000 bytes)
= 10,300-byte package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bitcoin Core’s rules require:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alice must pay a higher absolute fee than Bob’s entire package.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So if fee rates spike to 50 sat/vB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,300 bytes × 50 sat/vB = 515,000 sats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Alice evaluates:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Save 0.5 BTC? Yes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;But Alice’s wallet may only have &lt;strong&gt;0.001 BTC&lt;/strong&gt; in liquid funds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bob can escalate the attack by attaching &lt;strong&gt;50,000-byte&lt;/strong&gt; children (→ 0.025 BTC cost).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And watchtowers cannot pre-commit to &lt;strong&gt;unbounded fee liabilities&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The real&amp;nbsp;problem
&lt;/h2&gt;

&lt;p&gt;The issue is &lt;em&gt;not&lt;/em&gt; that “0.00515 BTC is too expensive for 0.5 BTC.”&lt;/p&gt;

&lt;p&gt;The problem is that the &lt;strong&gt;required fee is unpredictable and potentially unlimited&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A small node with limited liquid capital can be priced out by a richer attacker. even if the channel’s nominal value is large.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Result:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A rich attacker can price a poor user out of security.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why Lightning disputes were not reliably safe.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Anchors: the missing context most people&amp;nbsp;skip
&lt;/h2&gt;

&lt;p&gt;Lightning attempted to fix fee bumping in 2020 with &lt;strong&gt;BIP 330: anchored commitment transactions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But BIP330 anchors had two major problems:&lt;/p&gt;

&lt;h2&gt;
  
  
  (1) Anchors were 330 sats in&amp;nbsp;value
&lt;/h2&gt;

&lt;p&gt;They required locking value upfront, and the value was too small during fee spikes.&lt;/p&gt;

&lt;h2&gt;
  
  
  (2) Anchors still depended on unconstrained child transactions
&lt;/h2&gt;

&lt;p&gt;So attackers could still attach giant CPFP transactions and restore the same old problem.&lt;/p&gt;

&lt;p&gt;This is why “anchored commitments” did not deliver fully reliable fee bumping.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. What actually changed: V3 + Package Relay + Ephemeral Anchors
&lt;/h2&gt;

&lt;p&gt;Here is the structural shift, summarized clearly:&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature comparison
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funrhawix5nk1j6sl6zc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funrhawix5nk1j6sl6zc6.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These are not mempool tweaks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These are protocol-level invariants.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The core guarantee V3 introduces
&lt;/h2&gt;

&lt;p&gt;Now we can state V3’s contribution precisely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;V3 establishes a hard upper bound on the cost of fee-bumping any package containing a V3 transaction.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mathematically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max package size = parent_size + 1,000 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This directly eliminates the attacker’s ability to magnify absolute fee requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before V3:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cost_to_replace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;attacker_decides&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;unlimited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  After V3:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cost_to_replace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bounded_and_predictable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This transforms Lightning, Ark, Spark, vaults, and all future L2 protocols from:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Best effort under good fee conditions”&lt;/strong&gt; &lt;br&gt;
&amp;nbsp;→ &lt;br&gt;
&amp;nbsp;&lt;strong&gt;“Deterministically secure even under fee spikes”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is not an optimization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is a semantic correction to Bitcoin’s relay layer.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Why Package Relay and P2A are needed&amp;nbsp;together
&lt;/h2&gt;

&lt;p&gt;V3 alone sets size constraints.&lt;/p&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Package Relay&lt;/strong&gt; ensures parent+child propagate together&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ephemeral Anchors&lt;/strong&gt; ensure fee bumping is always possible without UTXO pollution&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The three combine into:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bitcoin’s first dependency-aware fee and relay model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is what enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;0-fee channel opens&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;safe HTLC resolution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;covenant-based vaults&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;off-chain contract rollups&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ark withdrawal reliability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;safe parallel contract trees&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;multi-party protocols with guaranteed escape hatches&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This did not exist before 2024–2025.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Why this work is invisible
&lt;/h2&gt;

&lt;p&gt;When V3, Package Relay, and P2A landed, some people asked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;“Why change RBF rules?”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“Why add a new transaction version?”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“Is this just mempool churn?”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“Why is this needed at all?”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These reactions are understandable — Bitcoin Core’s relay layer is complex, and the benefits appear only indirectly.&lt;/p&gt;

&lt;p&gt;But the context is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These changes remove entire attack classes, not just adjust parameters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They give L2 protocols clear invariants for the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Without them:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;watchtowers cannot price risk&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;protocols cannot promise safety&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;channels cannot open without fees&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTLCs fail during congestion&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;covenants cannot guarantee exit safety&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is infrastructure work — &lt;strong&gt;often unnoticed, always essential.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Real-world impact
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Lightning
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;0-fee channel opens&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;safe HTLC resolution during fee spikes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;predictable penalty enforcement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;simplified implementations (Eclair + LDK now interoperable)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ark
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;reliable vtxo expiry&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;safe connector exits&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;predictable mass-exit fee models&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future protocols
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;covenant vaults&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;channel factories&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;state channels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rollup-style constructions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ANY protocol needing deterministic fee bumping&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The second layer is now operating on a far stronger foundation.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Conclusion: why V3 truly&amp;nbsp;matters
&lt;/h2&gt;

&lt;p&gt;The story is not:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Lightning finally gets 0-fee channels.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The real story is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bitcoin Core finally provides the fee guarantees and relay invariants that Lightning, Ark, Spark, vaults, and future L2 protocols always needed.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;V3, Package Relay, and Ephemeral Anchors are not mempool tweaks.&lt;/p&gt;

&lt;p&gt;They are a &lt;strong&gt;structural repair&lt;/strong&gt; to Bitcoin’s relay architecture.&lt;/p&gt;

&lt;p&gt;Most people didn’t notice.&lt;/p&gt;

&lt;p&gt;But every serious protocol will benefit from it for the next decade.&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/5d4f1b56b5bd" rel="noopener noreferrer"&gt;November 22, 2025&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/why-v3-matters-bitcoins-relay-layer-was-rewritten-and-most-people-didn-t-notice-5d4f1b56b5bd" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
    </item>
    <item>
      <title>Why Counterparty’s Fake-Pubkey Grinding Reveals the Real Boundary Between Bitcoin Consensus and…</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:48:01 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/why-counterpartys-fake-pubkey-grinding-reveals-the-real-boundary-between-bitcoin-consensus-and-56gi</link>
      <guid>https://dev.to/aaron_recompile/why-counterpartys-fake-pubkey-grinding-reveals-the-real-boundary-between-bitcoin-consensus-and-56gi</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitcoin is not a state machine.
It is a verifiable sequence of events.

"Event Machine Letters - Protocol Thoughts on Bitcoin's Architecture" begins here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;While writing Chapter 5 of my book &lt;a href="https://btcstudy.gitbook.io/bitcoin-data-embedding/book/manuscript/ch05_counterparty-multisig" rel="noopener noreferrer"&gt;&lt;em&gt;How People Tried to Push Non-Transaction Data into Bitcoin&lt;/em&gt;&lt;/a&gt;, I fell into a technical rabbit hole: How did Counterparty manage to store structured payloads inside bare multisig outputs?&lt;/p&gt;

&lt;p&gt;The answer turned out to be more interesting than expected — not because of multisig itself, but because Counterparty’s “fake-pubkey grinding” exposes a deep and often misunderstood distinction in Bitcoin:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consensus guarantees possibility.&lt;/strong&gt; &lt;br&gt;
&amp;nbsp;&lt;strong&gt;Policy guarantees sanity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most debates around Bitcoin protocol behavior (including recent ones) come from mixing these two layers together. Counterparty provides a perfect historical case study for why this distinction matters.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Counterparty’s Hack: Multisig as a Data Container
&lt;/h2&gt;

&lt;p&gt;Counterparty (2014) was the first protocol to systematically embed arbitrary data in Bitcoin by encoding payloads inside fake public keys placed in a 1-of-3 bare multisig output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;OP_1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;fake_pubkey1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;fake_pubkey2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;real_pubkey&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;OP_3&lt;/span&gt; &lt;span class="n"&gt;OP_CHECKMULTISIG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The fake pubkeys contained 64 bytes of payload&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The real pubkey ensured the output was theoretically spendable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OP_RETURN carried only the instruction header&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multisig carried the full structured payload&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design allowed Counterparty to break through the ~80-byte OP_RETURN limit and support more complex protocol messages.&lt;/p&gt;

&lt;p&gt;But it also forced Counterparty to confront Bitcoin’s deepest rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Why Fake Pubkeys Must Be Valid EC&amp;nbsp;Points
&lt;/h2&gt;

&lt;p&gt;At first glance, one might think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“If Counterparty never plans to spend these outputs, who cares if the pubkeys are valid?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bitcoin cares — a lot.&lt;/p&gt;

&lt;p&gt;Before a transaction can enter the mempool, Bitcoin Core performs a full consensus-level script validity check. This check ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The script is syntactically valid&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No opcode triggers immediate failure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every pubkey is a valid secp256k1 point&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CHECKMULTISIG would not immediately error&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bitcoin does not allow an output that is provably unspendable due to script error. That would violate consensus rules, not policy.&lt;/p&gt;

&lt;p&gt;Thus:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Even if no one will ever spend the output, fake pubkeys still must be on the secp256k1 curve.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why Counterparty clients had to grind fake pubkeys:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grinding =&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Search for an x-coordinate that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Encodes the payload (high bits), and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Produces a valid curve point (low bits adjusted until valid)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only such points can be accepted into the mempool and relayed across the network.&lt;/p&gt;

&lt;p&gt;Without a valid EC point, the transaction never broadcasts — it fails before policy even enters the picture.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Consensus vs. Policy: What Counterparty Accidentally Taught&amp;nbsp;Us
&lt;/h2&gt;

&lt;p&gt;Counterparty’s architecture forces us to confront the difference between two layers often confused in Bitcoin debates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consensus Layer: Strict Logic, Wide Permission
&lt;/h2&gt;

&lt;p&gt;Consensus rules guarantee:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Every output is theoretically spendable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Script execution will not immediately error&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Public keys are valid&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CHECKMULTISIG would not produce ScriptError&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Script semantics are self-consistent&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consensus does not care:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;whether an output will ever be spent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;whether the pubkey is used as a data container&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;whether the protocol “abuses” Bitcoin&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;whether it’s storing JPEGs or metadata&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;whether users are burning dust forever&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bitcoin consensus is maximalist in a very precise way:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If your pubkey is valid and your script is internally consistent, consensus says “Yes.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is rule-bound but not value-judging — almost Confucian in spirit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy Layer: Pragmatic, Local, Protective
&lt;/h2&gt;

&lt;p&gt;Policy rules — Bitcoin Core’s mempool rules — are not consensus. They exist only to prevent node DoS and spam.&lt;/p&gt;

&lt;p&gt;Policy restricts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;bare multisig relay&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;multisig N ranges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;dust thresholds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OP_RETURN sizes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;nonstandard script forms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;vary between node implementations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;do not affect the blockchain&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;do not define Bitcoin’s consensus&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;only determine whether the transaction is relayed and mined easily&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why Counterparty eventually became marginalized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Consensus allowed it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Policy discouraged it&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bare multisig became a disfavored pattern, even though it remains fully valid at the consensus layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Real Insight: Bitcoin Allows Abuse at Consensus, Discourages It at&amp;nbsp;Policy
&lt;/h2&gt;

&lt;p&gt;Counterparty sits precisely at the intersection of the two:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consensus:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;“That fake pubkey is a valid secp256k1 point? Then this multisig output is fine.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;“Don’t fill the UTXO set with garbage. We may choose not to relay this.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Users:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;“These outputs are dust. We’re never spending them anyway.”&lt;/p&gt;

&lt;p&gt;Thus, Counterparty becomes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consensus permissiveness ×&lt;/strong&gt; &lt;br&gt;
&amp;nbsp;&lt;strong&gt;Policy defensiveness ×&lt;/strong&gt; &lt;br&gt;
&amp;nbsp;&lt;strong&gt;User economic behavior&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s far more than a curiosity of early NFT history. It’s a case study in how Bitcoin’s layered design produces emergent behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Why This Distinction Matters&amp;nbsp;Today
&lt;/h2&gt;

&lt;p&gt;A surprising number of recent Bitcoin debates — OP_RETURN policy changes, Ordinals arguments, BitVM anchoring, and even knothole discussions — stem from the same misunderstanding:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;People mix up consensus rules (what is allowed) with policy rules (what is relayed or encouraged).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Counterparty’s fake-pubkey grinding is the earliest and clearest reminder of this separation.&lt;/p&gt;

&lt;p&gt;Understanding it is essential for understanding Bitcoin’s evolution — and the fights that continue around it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Counterparty wasn’t just a creative hack for storing data in Bitcoin. It was the first protocol to unintentionally illuminate the boundary between &lt;em&gt;can&lt;/em&gt; and &lt;em&gt;should&lt;/em&gt;, between consensus and policy.&lt;/p&gt;

&lt;p&gt;And many misunderstandings in today’s discussions remain unsolved simply because this boundary is still not widely understood.&lt;/p&gt;

&lt;p&gt;If we want to reason clearly about Bitcoin’s future, this distinction is the place to start.&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/3c891f0e7ec9" rel="noopener noreferrer"&gt;November 24, 2025&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/why-counterpartys-fake-pubkey-grinding-reveals-the-real-boundary-between-bitcoin-consensus-and-3c891f0e7ec9" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
    </item>
    <item>
      <title>The Missing Developer Stack of Taproot</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:48:00 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/the-missing-developer-stack-of-taproot-1l4l</link>
      <guid>https://dev.to/aaron_recompile/the-missing-developer-stack-of-taproot-1l4l</guid>
      <description>&lt;p&gt;&lt;em&gt;Why Taproot still lacks the infrastructure developers need&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flujfqbglflpjq5pjgt6f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flujfqbglflpjq5pjgt6f.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction — Taproot is deployed, but underused
&lt;/h2&gt;

&lt;p&gt;Taproot activated in 2021 and introduced one of the most powerful upgrades in Bitcoin’s scripting history.&lt;/p&gt;

&lt;p&gt;It enabled Schnorr signatures, key aggregation, script trees, and meaningfully improved privacy for complex spending conditions. From a protocol perspective, Taproot is complete.&lt;/p&gt;

&lt;p&gt;However, four years after activation, most Taproot outputs on chain are still spent via key-path spends, while script-path usage remains relatively rare.&lt;/p&gt;

&lt;p&gt;This raises an obvious question: if Taproot is so powerful, why are complex script constructions still uncommon?&lt;/p&gt;

&lt;p&gt;The answer is not a limitation of the protocol. It is a limitation of the developer infrastructure around it.&lt;/p&gt;

&lt;p&gt;Taproot currently lacks a coherent developer stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1 — Debugging and Measurement Tools
&lt;/h2&gt;

&lt;p&gt;One of the biggest obstacles developers face when working with Taproot is &lt;em&gt;visibility&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A Taproot script-path spend contains multiple layers of cryptographic commitments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TapLeaf hash
  → Merkle path
    → Control block
      → TapTweak
        → Tweaked output key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understanding or verifying these steps manually is difficult. Today, most tools provide only basic transaction decoding. Bitcoin Core’s &lt;code&gt;decoderawtransaction&lt;/code&gt;, for example, does not reconstruct TapLeaf hashes, Merkle path structure, control block interpretation, or tweak verification.&lt;/p&gt;

&lt;p&gt;As a result, developers often fall back to ad-hoc scripts or manual calculations. This friction discourages experimentation.&lt;/p&gt;

&lt;p&gt;What is missing are Taproot inspection tools: witness analyzers, control block inspectors, commitment reconstruction tools, and chain-level measurement frameworks for understanding how Taproot is being used in practice. Without these, debugging Taproot scripts is unnecessarily opaque — even for developers who understand the protocol.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 2 — Script Engineering Knowledge
&lt;/h2&gt;

&lt;p&gt;Even when developers understand the protocol rules, there is a second, more subtle gap: &lt;em&gt;engineering patterns&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The Taproot BIPs define the system precisely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BIP340&lt;/strong&gt; — Schnorr signatures&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BIP341&lt;/strong&gt; — Taproot construction&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BIP342&lt;/strong&gt; — Tapscript&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But these documents do not answer the practical questions that arise when building real systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How should a complex script tree be structured?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How should leaf ordering be chosen?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What privacy trade-offs emerge from different tree designs?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How should multiple spending conditions be composed efficiently?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are engineering questions, not protocol questions — and the gap is more concrete than it might sound.&lt;/p&gt;

&lt;p&gt;Take leaf ordering as one example. In a Taproot tree with multiple spending conditions, placing the most likely-to-be-used leaf at a lower depth reduces the control block size required at spend time. But leaf position also has privacy implications: a shallow leaf reveals less of the tree than a deep one, and an analyst observing script-path spends over time can make probabilistic inferences about tree structure. How a developer balances these trade-offs is not specified anywhere in the BIPs.&lt;/p&gt;

&lt;p&gt;Or take control block size itself. In a balanced binary tree of depth &lt;em&gt;d&lt;/em&gt;, the control block grows by 32 bytes per level (one hash per Merkle path step). A tree with 8 leaves at depth 3 requires a 3-hash control block; a pathological linear tree of the same 8 leaves can require up to 7 hashes for the deepest leaf. The choice of tree topology is therefore an engineering decision with real on-chain cost and privacy consequences — but there is no reference material telling developers how to think about it.&lt;/p&gt;

&lt;p&gt;In other ecosystems, these answers emerge as design patterns, reference implementations, and cookbook-style documentation. For Taproot, this material is still scarce. As a result, many developers treat Taproot scripts as theoretical capabilities rather than practical building blocks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 3 — Playground Infrastructure
&lt;/h2&gt;

&lt;p&gt;The final missing piece is &lt;em&gt;experimentation infrastructure&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Developers working in other scripting environments typically have access to purpose-built tooling: local deployment environments, transaction simulators, interactive debuggers. Bitcoin development is different by design — Bitcoin Script is not a general-purpose computation system, and the comparison has real limits. But the underlying need is similar: a low-friction way to construct, test, and iterate on script logic before committing to mainnet.&lt;/p&gt;

&lt;p&gt;The typical Taproot development workflow today still looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bitcoind → regtest → RPC calls → manual transaction construction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow is fully capable, but it requires assembling many pieces by hand, and there is no dedicated tooling for Taproot-specific tasks. A focused Taproot development environment could provide script tree construction tools, transaction simulation with witness inspection, reproducible test cases, and interactive control block debugging. The goal is not to replicate Ethereum’s tooling philosophy — it is to give Bitcoin developers the same ability to move quickly and verify their understanding that regtest alone does not provide.&lt;/p&gt;

&lt;p&gt;Such infrastructure would significantly lower the barrier to experimentation and reduce the time between “I have an idea” and “I have a working transaction.”&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This&amp;nbsp;Matters
&lt;/h2&gt;

&lt;p&gt;Taproot was designed to enable a wide range of advanced protocols: vault constructions, DLCs, covenant-like structures, and future contract systems. But protocols do not emerge automatically. They require tools, examples, and experiments.&lt;/p&gt;

&lt;p&gt;The current situation is not a failure of the protocol — it is a gap in the surrounding ecosystem. Developers who might otherwise explore Taproot’s capabilities are slowed down at each layer: they cannot see what is happening inside their transactions, they have no engineering patterns to reason from, and they have no low-friction environment in which to iterate.&lt;/p&gt;

&lt;p&gt;Each of these is a solvable problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Taproot itself is not missing any functionality. What is missing is the developer stack around it.&lt;/p&gt;

&lt;p&gt;Three layers remain underdeveloped:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugging and measurement tools&lt;/strong&gt; — witness analyzers, control block inspectors, commitment reconstruction tools, and chain-level measurement frameworks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Script engineering knowledge&lt;/strong&gt; — concrete patterns for tree design, leaf ordering, privacy trade-offs, and spending condition composition&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Playground infrastructure&lt;/strong&gt; — a focused environment for Taproot experimentation that reduces the gap between understanding the protocol and building with it&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building this stack is likely one of the most important steps for unlocking the full potential of Taproot.&lt;/p&gt;

&lt;p&gt;The protocol is already here. Now the ecosystem needs the tools that allow developers to truly use it.&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/61352dad7f96" rel="noopener noreferrer"&gt;March 11, 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/the-missing-developer-stack-of-taproot-61352dad7f96" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>taproot</category>
    </item>
    <item>
      <title>The Anatomy of Bitcoin Scripts: From P2PKH to Taproot</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:58 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/the-anatomy-of-bitcoin-scripts-from-p2pkh-to-taproot-114k</link>
      <guid>https://dev.to/aaron_recompile/the-anatomy-of-bitcoin-scripts-from-p2pkh-to-taproot-114k</guid>
      <description>&lt;p&gt;Companion notes to my book &lt;strong&gt;&lt;em&gt;Mastering Taproot&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HODLing is the beginning.
But Bitcoin was meant to be programmed.

"Not Just HODLing: Real Bitcoin Script Engineering" starts here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Universal Truth
&lt;/h2&gt;

&lt;p&gt;Every Bitcoin address — from the genesis block to the latest Taproot output — follows one fundamental pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lock:   Commit to a condition
Unlock: Reveal proof that you satisfy it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn’t a feature of SegWit or Taproot. It’s the DNA of Bitcoin itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Evolution of Commit-Reveal
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5yfqqa9kq90le683ysk2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5yfqqa9kq90le683ysk2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The abstraction deepens. The privacy improves. The core logic remains unchanged.&lt;/p&gt;




&lt;h2&gt;
  
  
  Locking Script&amp;nbsp;Formats
&lt;/h2&gt;

&lt;h2&gt;
  
  
  P2PKH
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_DUP OP_HASH160 &amp;lt;20-byte pubkey_hash&amp;gt; OP_EQUALVERIFY OP_CHECKSIG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  P2SH
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_HASH160 &amp;lt;20-byte script_hash&amp;gt; OP_EQUAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  P2WPKH
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_0 &amp;lt;20-byte pubkey_hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  P2WSH
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_0 &amp;lt;32-byte script_hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  P2TR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_1 &amp;lt;32-byte output_pubkey&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How nodes differentiate SegWit types:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;OP_0&lt;/code&gt; + 20 bytes → P2WPKH&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;OP_0&lt;/code&gt; + 32 bytes → P2WSH&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;OP_1&lt;/code&gt; + 32 bytes → P2TR&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Verification Flow
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Universal Pattern
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: Verify Commit — Does the revealed content match what was promised?
Step 2: Execute Script — Do the conditions evaluate to true?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  P2SH Verification
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HASH160(redeem_script) == hash in locking script?
Match → Execute redeem_script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  P2WSH Verification
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHA256(witness_script) == hash in locking script?
Match → Execute witness_script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  P2TR Script Path Verification
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Extract internal_pubkey from control block
2. Compute tapleaf_hash from script
3. Calculate merkle_root using Merkle path
4. tweak = TaggedHash("TapTweak", internal_pubkey || merkle_root)
5. computed_pubkey = internal_pubkey + tweak × G
6. computed_pubkey == output_pubkey in locking script?
7. Match → Execute tapscript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Stack Execution Visualized
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Example 1:&amp;nbsp;P2PKH
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Locking Script:&lt;/strong&gt; &lt;code&gt;OP_DUP OP_HASH160 &amp;lt;pubkey_hash&amp;gt; OP_EQUALVERIFY OP_CHECKSIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unlock Data:&lt;/strong&gt; &lt;code&gt;&amp;lt;signature&amp;gt; &amp;lt;pubkey&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial Stack      OP_DUP         OP_HASH160      PUSH hash
┌─────────┐       ┌─────────┐    ┌─────────┐     ┌─────────┐
│ pubkey  │       │ pubkey  │    │ pk_hash │     │ pk_hash │ ← expected
├─────────┤       ├─────────┤    ├─────────┤     ├─────────┤
│ sig     │       │ pubkey  │    │ pubkey  │     │ pk_hash │ ← computed
└─────────┘       ├─────────┤    ├─────────┤     ├─────────┤
                  │ sig     │    │ sig     │     │ pubkey  │
                  └─────────┘    └─────────┘     ├─────────┤
                                                 │ sig     │
                                                 └─────────┘

OP_EQUALVERIFY     OP_CHECKSIG      Final
┌─────────┐        ┌─────────┐      ┌─────────┐
│ pubkey  │        │ 1(true) │      │ 1(true) │ ✓
├─────────┤        └─────────┘      └─────────┘
│ sig     │
└─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example 2: P2SH with&amp;nbsp;Timelock
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Redeem Script:&lt;/strong&gt; &lt;code&gt;OP_3 OP_CSV OP_DROP OP_DUP OP_HASH160 &amp;lt;hash&amp;gt; OP_EQUALVERIFY OP_CHECKSIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unlock Data:&lt;/strong&gt; &lt;code&gt;&amp;lt;signature&amp;gt; &amp;lt;pubkey&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial Stack      PUSH 3         OP_CSV          OP_DROP
┌─────────┐       ┌─────────┐    ┌─────────┐     ┌─────────┐
│ pubkey  │       │ 3       │    │ 3       │     │ pubkey  │
├─────────┤       ├─────────┤    ├─────────┤     ├─────────┤
│ sig     │       │ pubkey  │    │ pubkey  │     │ sig     │
└─────────┘       ├─────────┤    ├─────────┤     └─────────┘
                  │ sig     │    │ sig     │
                  └─────────┘    └─────────┘
                                 Check nSeq ≥ 3

Continues with standard P2PKH logic...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example 3:&amp;nbsp;P2WPKH
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Locking Script:&lt;/strong&gt; &lt;code&gt;OP_0 &amp;lt;20-byte pubkey_hash&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witness:&lt;/strong&gt; &lt;code&gt;&amp;lt;signature&amp;gt; &amp;lt;pubkey&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Node sees &lt;code&gt;OP_0 + 20 bytes&lt;/code&gt;, implicitly executes P2PKH logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verification: HASH160(pubkey) == hash in locking script? Match → Continue


Execution (implicit P2PKH logic):

Initial Stack      OP_DUP         OP_HASH160      PUSH hash
┌─────────┐       ┌─────────┐    ┌─────────┐     ┌─────────┐
│ pubkey  │       │ pubkey  │    │ pk_hash │     │ pk_hash │ ← expected
├─────────┤       ├─────────┤    ├─────────┤     ├─────────┤
│ sig     │       │ pubkey  │    │ pubkey  │     │ pk_hash │ ← computed
└─────────┘       ├─────────┤    ├─────────┤     ├─────────┤
                  │ sig     │    │ sig     │     │ pubkey  │
                  └─────────┘    └─────────┘     ├─────────┤
                                                 │ sig     │
                                                 └─────────┘
OP_EQUALVERIFY     OP_CHECKSIG      Final
┌─────────┐        ┌─────────┐      ┌─────────┐
│ pubkey  │        │ 1(true) │      │ 1(true) │ ✓
├─────────┤        └─────────┘      └─────────┘
│ sig     │
└─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Difference from P2PKH:&lt;/strong&gt; Identical logic, unlock data moved from scriptSig to witness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 4: P2WSH (2-of-3 Multisig)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Locking Script:&lt;/strong&gt; &lt;code&gt;OP_0 &amp;lt;32-byte script_hash&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witness Script:&lt;/strong&gt; &lt;code&gt;OP_2 &amp;lt;pubkey1&amp;gt; &amp;lt;pubkey2&amp;gt; &amp;lt;pubkey3&amp;gt; OP_3 OP_CHECKMULTISIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witness:&lt;/strong&gt; &lt;code&gt;OP_0 &amp;lt;sig1&amp;gt; &amp;lt;sig2&amp;gt; &amp;lt;witness_script&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verification: SHA256(witness_script) == hash? Match → Execute witness_script


Execution:
Initial Stack (witness data excluding witness_script)
┌─────────┐
│ sig2    │
├─────────┤
│ sig1    │
├─────────┤
│ 0       │ ← CHECKMULTISIG bug placeholder
└─────────┘
PUSH 2         PUSH pk1       PUSH pk2       PUSH pk3
┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│ 2       │    │ pk1     │    │ pk2     │    │ pk3     │
├─────────┤    ├─────────┤    ├─────────┤    ├─────────┤
│ sig2    │    │ 2       │    │ pk1     │    │ pk2     │
├─────────┤    ├─────────┤    ├─────────┤    ├─────────┤
│ sig1    │    │ sig2    │    │ 2       │    │ pk1     │
├─────────┤    ├─────────┤    ├─────────┤    ├─────────┤
│ 0       │    │ sig1    │    │ sig2    │    │ 2       │
└─────────┘    ├─────────┤    ├─────────┤    ├─────────┤
               │ 0       │    │ sig1    │    │ sig2    │
               └─────────┘    ├─────────┤    ├─────────┤
                              │ 0       │    │ sig1    │
                              └─────────┘    ├─────────┤
                                             │ 0       │
                                             └─────────┘



PUSH 3                    OP_CHECKMULTISIG           Final
┌─────────┐               ┌─────────┐                ┌─────────┐
│ 3       │               │ 1(true) │                │ 1(true) │ ✓
├─────────┤               └─────────┘                └─────────┘
│ pk3     │
├─────────┤               Verify: at least 2 of 3
│ pk2     │               signatures are valid
├─────────┤
│ pk1     │
├─────────┤
│ 2       │
├─────────┤
│ sig2    │
├─────────┤
│ sig1    │
├─────────┤
│ 0       │ ← consumed (legacy bug)
└─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The CHECKMULTISIG bug:&lt;/strong&gt; Due to an off-by-one error in the original implementation, an extra stack element is consumed. Hence the dummy &lt;code&gt;OP_0&lt;/code&gt; at the start of witness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 5: Taproot Script&amp;nbsp;Path
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tapscript:&lt;/strong&gt; &lt;code&gt;OP_2 OP_CSV OP_DROP &amp;lt;pubkey&amp;gt; OP_CHECKSIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witness:&lt;/strong&gt; &lt;code&gt;&amp;lt;signature&amp;gt; &amp;lt;script&amp;gt; &amp;lt;control_block&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verification: control_block + script → reconstruct output_pubkey → match locking script

Execution:
Initial Stack      PUSH 2         OP_CSV          OP_DROP
┌─────────┐       ┌─────────┐    ┌─────────┐     ┌─────────┐
│ sig     │       │ 2       │    │ 2       │     │ sig     │
└─────────┘       ├─────────┤    ├─────────┤     └─────────┘
                  │ sig     │    │ sig     │
                  └─────────┘    └─────────┘
                                 Check nSeq ≥ 2



PUSH pubkey        OP_CHECKSIG        Final
┌─────────┐        ┌─────────┐        ┌─────────┐
│ pubkey  │        │ 1(true) │        │ 1(true) │ ✓
├─────────┤        └─────────┘        └─────────┘
│ sig     │
└─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What SegWit Actually&amp;nbsp;Changed
&lt;/h2&gt;

&lt;p&gt;SegWit wasn’t a revolution. It was a relocation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9l3z4kulzs2xsa6xu4jm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9l3z4kulzs2xsa6xu4jm.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Taproot Actually&amp;nbsp;Changed
&lt;/h2&gt;

&lt;p&gt;Taproot optimized the commit-reveal paradigm:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwkegrfdp9olac2o5v1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwkegrfdp9olac2o5v1f.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Control Block Structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1 byte: leaf_version + parity] [32 bytes: internal_pubkey] [32 bytes × N: Merkle path]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verification Formula:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output_pubkey = internal_pubkey + TaggedHash("TapTweak", internal_pubkey || merkle_root) × G
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Every address type is commit-reveal&lt;/strong&gt; — Lock with a hash/pubkey commitment, unlock by revealing the preimage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Locking scripts are routing labels&lt;/strong&gt; — Nodes use the format to determine validation rules; they don’t execute on stack&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verification precedes execution&lt;/strong&gt; — First prove “you have the right to run this script”, then verify “does the script pass”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stack execution is universal&lt;/strong&gt; — Regardless of address type, it’s all push, pop, verify signatures, check timelocks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The trajectory is clear:&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Commitments grow more abstract (pubkey → hash → tweak)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Privacy improves (expose everything → expose only what’s used)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Efficiency increases (fee optimization, verification optimization)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Understanding these primitives is the foundation for everything that follows — Lightning channels, covenant proposals, and whatever comes next.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/4db16924232f" rel="noopener noreferrer"&gt;November 28, 2025&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/the-anatomy-of-bitcoin-scripts-from-p2pkh-to-taproot-4db16924232f" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>taproot</category>
    </item>
    <item>
      <title>Taproot Control Block Deep Analysis &amp; Stack Execution Visualization | Part 2</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:56 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/taproot-control-block-deep-analysis-stack-execution-visualization-part-2-116g</link>
      <guid>https://dev.to/aaron_recompile/taproot-control-block-deep-analysis-stack-execution-visualization-part-2-116g</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HODLing is the beginning.
But Bitcoin was meant to be programmed.

"Not Just HODLing: Real Bitcoin Script Engineering" starts here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqom4hzhg3nigff5496m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqom4hzhg3nigff5496m.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;In &lt;a href="https://medium.com/@aaron.recompile/building-a-4-leaf-taproot-tree-in-python-the-first-complete-implementation-on-bitcoin-testnet-c8b66c331f29" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;, we implemented a complete 4-leaf Taproot tree and successfully executed all spending paths on testnet. But Taproot’s core charm lies not only in implementation, but also in its sophisticated cryptographic mechanisms — how Control Blocks provide Merkle proofs, and how Tapscript executes in the Bitcoin virtual machine.&lt;/p&gt;

&lt;p&gt;This article will deeply analyze Control Block construction principles, progressively verify Merkle paths through real on-chain data, and finally demonstrate stack execution processes through animations. This is not just a technical interpretation, but a cryptographic journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Article Navigation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Merkle Tree Construction Principles&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Control Block Deep Analysis&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Merkle Path Reconstruction Verification&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tweak Mechanism Detailed Explanation&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stack Execution Visualization&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Review of Our 4-Leaf Taproot Tree Structure
&lt;/h2&gt;

&lt;p&gt;Let’s review the balanced Merkle tree we constructed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;               Root Hash
               /         \
         Branch0         Branch1
        /      \        /       \
  Script0   Script1  Script2  Script3
  (Hash)    (Multi)   (CSV)    (Sig)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Functions of the Four Scripts&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Script0&lt;/strong&gt;: SHA256 hash lock — spendable by anyone knowing “helloworld”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Script1&lt;/strong&gt;: 2-of-2 multisig — requires both Alice and Bob signatures&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Script2&lt;/strong&gt;: CSV timelock — Bob can spend after waiting 2 blocks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Script3&lt;/strong&gt;: Simple signature — Bob can spend directly with signature&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Address Information&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Taproot Address&lt;/strong&gt;: &lt;code&gt;tb1pjfdm902y2adr08qnn4tahxjvp6x5selgmvzx63yfqk2hdey02yvqjcr29q&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Internal Public Key&lt;/strong&gt;: &lt;code&gt;50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;All transactions successfully confirmed on Bitcoin testnet&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Merkle Tree Construction Principles: From TapLeaf to&amp;nbsp;Root
&lt;/h2&gt;

&lt;h2&gt;
  
  
  TaggedHash: Bitcoin’s Hash&amp;nbsp;Standard
&lt;/h2&gt;

&lt;p&gt;Taproot uses TaggedHash to prevent hash collision attacks between different protocols:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TaggedHash(tag, data) = SHA256(SHA256(tag) || SHA256(tag) || data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dual tagging mechanism ensures that Taproot hashes don’t accidentally collide with hashes from other Bitcoin protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  TapLeaf Hash Calculation
&lt;/h2&gt;

&lt;p&gt;Each script is first converted to a TapLeaf hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TapLeaf_Hash = TaggedHash("TapLeaf", version || script_length || script_data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Actual calculation using Script1 (multisig) as an example&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Script1 raw data (71 bytes)
script1_data = 002050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3ba2084b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5ba5287

// TapLeaf calculation
Script1_Hash = TaggedHash("TapLeaf", 0xC0 || 0x47 || script1_data)
             = 63cb9e4776a1cbb195c5cf0cbdbb3110d308969353680e38ec5f446336b60def
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;0xC0&lt;/code&gt;: Tapscript version identifier&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;0x47&lt;/code&gt;: Script length (71 bytes in hexadecimal)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;script1_data&lt;/code&gt;: The actual script bytecode&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TapBranch Hash Calculation
&lt;/h2&gt;

&lt;p&gt;Adjacent TapLeaf hashes are combined into TapBranch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TapBranch_Hash = TaggedHash("TapBranch", left_hash || right_hash)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Branch0 calculation (Script0 + Script1)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Script0_Hash = fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659e
Script1_Hash = 63cb9e4776a1cbb195c5cf0cbdbb3110d308969353680e38ec5f446336b60def

Branch0 = TaggedHash("TapBranch", Script0_Hash || Script1_Hash)
        = 2faaa677cb6ad6a74bf7025e4cd03d2a82c7fb8e3c277916d7751078105cf9df
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Branch1 calculation (Script2 + Script3)&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;First calculate Script2 and Script3 TapLeaf hashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Script2 (CSV timelock) - 20 bytes
script2_data = 52b2752084b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5ac
Script2_Hash = TaggedHash("TapLeaf", 0xC0 || 0x14 || script2_data)
             = 593d543a01c2c3c16c950ed97dfb3f3a1025b4b66323ed6b2814a1fb61d8e4b9
// Script3 (simple signature) - 34 bytes  
script3_data = 2084b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5ac
Script3_Hash = TaggedHash("TapLeaf", 0xC0 || 0x22 || script3_data)
             = d6ac4c0133faaf95feb8e5656367d882f250e23b3295cafcbc465779960d1210
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then calculate Branch1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Branch1 = TaggedHash("TapBranch", Script2_Hash || Script3_Hash)
        = TaggedHash("TapBranch", 593d543a... || d6ac4c01...)
        = da55197526f26fa309563b7a3551ca945c046e5b7ada957e59160d4d27f299e3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Root Calculation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Root = TaggedHash("TapBranch", Branch0 || Branch1)
     = d6ac4c0133faaf95feb8e5656367d882f250e23b3295cafcbc465779960d1210
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Root will be used together with the internal public key to generate the final Taproot address.&lt;/p&gt;




&lt;h2&gt;
  
  
  Control Block Deep Analysis: Core of Cryptographic Proof
&lt;/h2&gt;

&lt;p&gt;Control Block is the core of Taproot script path spending — it provides cryptographic proof that a specific script is indeed included in the Taproot address commitment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Script1 Multisig Transaction as&amp;nbsp;Example
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Transaction ID&lt;/strong&gt;: &lt;code&gt;1951a3be0f05df377b1789223f6da66ed39c781aaf39ace0bf98c3beb7e604a1&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Control Block Raw Data Deconstruction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Control Block Original Data&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659eda55197526f26fa309563b7a3551ca945c046e5b7ada957e59160d4d27f299e3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Field Breakdown Visualization
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🔴 Version + Parity (1&amp;nbsp;byte)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bytes: c0
Position: [0]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Analysis&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;c0&lt;/code&gt; = &lt;code&gt;11000000&lt;/code&gt; (binary)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;First 7 bits: &lt;code&gt;1100000&lt;/code&gt; → Tapscript version identifier&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Last 1 bit: &lt;code&gt;0&lt;/code&gt; → y-coordinate is even&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Specify script interpretation version and elliptic curve point y-coordinate parity&lt;/p&gt;

&lt;h2&gt;
  
  
  🔵 Internal PubKey (32&amp;nbsp;bytes)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bytes: 50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3
Position: [1-32]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Analysis&lt;/strong&gt;: Alice’s internal public key, using x-only format (x-coordinate only)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Key commitment binding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Used for final Taproot address Tweak calculation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Proves Control Block corresponds to specific internal key&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🟡 Sibling Hash (32&amp;nbsp;bytes)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bytes: fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659e
Position: [33-64]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Analysis&lt;/strong&gt;: Script0 (hash lock script) TapLeaf hash&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merkle Proof Role&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Branch0
   ┌─────┴─────┐
Script0     Script1 ← Current script to prove
   ↑           
   └─ Provide sibling node hash for Branch0 reconstruction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Provide Script1’s sibling node hash for Branch0 reconstruction&lt;/p&gt;

&lt;h2&gt;
  
  
  🟢 Branch Hash (32&amp;nbsp;bytes)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bytes: da55197526f26fa309563b7a3551ca945c046e5b7ada957e59160d4d27f299e3
Position: [65-96]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Analysis&lt;/strong&gt;: Complete Branch1 hash (Script2+Script3 combination)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merkle Proof Role&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      Root
    ┌───┴───┐
Branch0   Branch1 ← Provide entire opposite branch
  ↑         
  └─ Just reconstructed branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Provide opposite branch hash for final Root reconstruction&lt;/p&gt;




&lt;h2&gt;
  
  
  Proof Path&amp;nbsp;Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Script1 Proof Path: Script1 → Branch0 → Root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required Data:
✓ Script1 content (obtained from witness)
✓ Script0 hash (🟡 Sibling Hash)
✓ Branch1 hash (🟢 Branch Hash)
✓ Internal public key (🔵 Internal PubKey)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reconstruction Process:
1. Script1 + Script0 hash → Branch0
2. Branch0 + Branch1 hash → Root
3. Root + Internal public key → Verify Taproot address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Minimal Disclosure Principle
&lt;/h2&gt;

&lt;p&gt;Control Block design embodies the &lt;strong&gt;minimal disclosure&lt;/strong&gt; cryptographic principle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hidden Information&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;❌ Script0 specific content (only provides hash)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;❌ Script2 and Script3 specific content (only provides combined hash)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;❌ Complete structure of entire script tree&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Exposed Information&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Script1 content (required for execution)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Minimal Merkle proof path&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Necessary key commitment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design ensures only the executed script path is exposed, while all other possible spending conditions remain completely hidden, achieving Taproot’s privacy protection goals.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reconstructing Merkle Path: Complete Proof from Script1 to&amp;nbsp;Root
&lt;/h2&gt;

&lt;p&gt;Now we will demonstrate step by step how to reconstruct the complete Merkle Root from Control Block information, thereby proving Script1 is indeed included in this Taproot address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Reconstruct Branch0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Known Information&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Script1 content (obtained from witness)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Script0 hash (obtained from Control Block’s 🟡Sibling Hash field)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Calculation Process&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Script0_Hash = fe78d8523ce9603014b28739a51ef826f791aa17511e617af6dc96a8f10f659e
Script1_Hash = TaggedHash("TapLeaf", 0xC0 || 0x47 || script1_data)
             = 63cb9e4776a1cbb195c5cf0cbdbb3110d308969353680e38ec5f446336b60def
Branch0 = TaggedHash("TapBranch", fe78d852... || 63cb9e47...)
        = 2faaa677cb6ad6a74bf7025e4cd03d2a82c7fb8e3c277916d7751078105cf9df
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Insight&lt;/strong&gt;: We don’t need to know Script0’s specific content, only its hash value to reconstruct Branch0. This is exactly how MAST privacy works — unused script paths remain completely hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Reconstruct Root
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Known Information&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Branch0 (just calculated)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Branch1 hash (obtained from Control Block’s 🟢Branch Hash field)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Calculation Process&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Branch1 = da55197526f26fa309563b7a3551ca945c046e5b7ada957e59160d4d27f299e3

Root = TaggedHash("TapBranch", 2faaa677... || da551975...)
     = d6ac4c0133faaf95feb8e5656367d882f250e23b3295cafcbc465779960d1210
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verification Result&lt;/strong&gt;: This calculated Root must be completely identical to the Root used during address generation, otherwise verification fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mathematical Principles of Merkle&amp;nbsp;Path
&lt;/h2&gt;

&lt;p&gt;Control Block essentially provides a &lt;strong&gt;Merkle inclusion proof&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Proof Path: Script1 → Branch0 → Root
Required Data: Script1 itself + Script0 hash + Branch1 hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The elegance of this design lies in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minimal Disclosure&lt;/strong&gt;: Only exposes necessary information on execution path&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficient Verification&lt;/strong&gt;: O(log n) complexity proof verification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy Protection&lt;/strong&gt;: Unused scripts completely hidden&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  From Root to Taproot Address: Complete Tweak Verification Process
&lt;/h2&gt;

&lt;p&gt;Now that we have reconstructed the Root, we need to verify this Root indeed corresponds to our Taproot address. This is achieved through Taproot’s Tweak mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tweak Mechanism Principles
&lt;/h2&gt;

&lt;p&gt;Taproot addresses are generated by “tweaking” the internal public key, with the formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tweak = TaggedHash("TapTweak", internal_pubkey || merkle_root)
taproot_pubkey = internal_pubkey + tweak × G (elliptic curve point addition)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design ensures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commitment Binding&lt;/strong&gt;: Address uniquely corresponds to a specific script tree&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key Derivation&lt;/strong&gt;: Those with internal private key can derive tweaked private key&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verification Transparency&lt;/strong&gt;: Anyone can verify correspondence between address and script tree&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Calculate Tweak&amp;nbsp;Value
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Input Parameters&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Internal Public Key&lt;/strong&gt;: &lt;code&gt;50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Merkle Root&lt;/strong&gt;: &lt;code&gt;d6ac4c0133faaf95feb8e5656367d882f250e23b3295cafcbc465779960d1210&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Calculation Process&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tweak_input = 50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3 || 
              d6ac4c0133faaf95feb8e5656367d882f250e23b3295cafcbc465779960d1210

tweak = TaggedHash("TapTweak", tweak_input)
      = SHA256(SHA256("TapTweak") || SHA256("TapTweak") || tweak_input)
      = 41e4a46b999a3afcc3f3c7a6e5bf5f96e950c7be3a22cd0b4a4c1e5fdd027788
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Elliptic Curve Point Operations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Internal Public Key Point&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal_point = secp256k1_point(50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tweak Point&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tweak_scalar = 41e4a46b999a3afcc3f3c7a6e5bf5f96e950c7be3a22cd0b4a4c1e5fdd027788
tweak_point = tweak_scalar × G  (G is secp256k1 generator point)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Taproot Public Key&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;taproot_point = internal_point + tweak_point
taproot_pubkey = x_coordinate(taproot_point)  // Take x-coordinate as x-only public key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Final Verification
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Calculation Result&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;taproot_pubkey = 925bb2bd44575a379c139d57db9a4c0e8d4867e8db046d4489059576e48f5118
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Address Conversion&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Bech32m encoding
taproot_address = bech32m_encode("tb", 1, taproot_pubkey)
                = tb1pjfdm902y2adr08qnn4tahxjvp6x5selgmvzx63yfqk2hdey02yvqjcr29q
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;✅ Verification Successful&lt;/strong&gt;: This result completely matches our used Taproot address!&lt;/p&gt;

&lt;p&gt;This mathematical verification proves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Script1 is indeed committed in the Merkle tree&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Merkle Root correctly corresponds to our Taproot address&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Control Block provides valid cryptographic proofComparative Analysis of Other Script Paths’ Control Blocks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s quickly compare the Control Block structures of the other three scripts, observing differences in Merkle proof paths:&lt;/p&gt;

&lt;h2&gt;
  
  
  Script0 (Hash Lock) — Complete&amp;nbsp;TXID
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TXID&lt;/strong&gt;: &lt;code&gt;1ba4835fca1c94e7eb0016ce37c6de2545d07d84a97436f8db999f33a6fd6845&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d363cb9e4776a1cbb195c5cf0cbdbb3110d308969353680e38ec5f446336b60defda55197526f26fa309563b7a3551ca945c046e5b7ada957e59160d4d27f299e3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Fields&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🟡 &lt;strong&gt;Sibling Hash&lt;/strong&gt;: &lt;code&gt;63cb9e47...&lt;/code&gt; (Script1's hash, because Script0 needs Script1 as sibling)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🟢 &lt;strong&gt;Branch Hash&lt;/strong&gt;: &lt;code&gt;da551975...&lt;/code&gt; (Branch1 hash, same as Script1)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Script2 (CSV Timelock) — Complete&amp;nbsp;TXID
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TXID&lt;/strong&gt;: &lt;code&gt;98361ab2c19aa0063f7572cfd0f66cb890b403d2dd12029426613b40d17f41ee&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d32faaa677cb6ad6a74bf7025e4cd03d2a82c7fb8e3c277916d7751078105cf9dfd6ac4c0133faaf95feb8e5656367d882f250e23b3295cafcbc465779960d1210
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Field Breakdown&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🟡 &lt;strong&gt;Sibling Hash&lt;/strong&gt;: &lt;code&gt;593d543a...1fb61d8e4b9&lt;/code&gt; (Script3's hash!)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🟢 &lt;strong&gt;Branch Hash&lt;/strong&gt;: &lt;code&gt;d6ac4c01...779960d1210&lt;/code&gt; (Branch0 hash!)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Script2’s Proof Path&lt;/strong&gt;: &lt;code&gt;Script2 → Branch1 → Root&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Needs Script3 hash as sibling to reconstruct Branch1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Needs Branch0 hash to reconstruct Root&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Script3 (Simple Signature) — Complete&amp;nbsp;TXID
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TXID&lt;/strong&gt;: &lt;code&gt;1af46d4c71e121783c3c7195f4b45025a1f38b73fc8898d2546fc33b4c6c71b9&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3593d543a01c2c3c16c950ed97dfb3f3a1025b4b66323ed6b2814a1fb61d8e4b9d6ac4c0133faaf95feb8e5656367d882f250e23b3295cafcbc465779960d1210
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Field Breakdown&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🟡 &lt;strong&gt;Sibling Hash&lt;/strong&gt;: &lt;code&gt;593d543a...1fb61d8e4b9&lt;/code&gt; (Script2's hash)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🟢 &lt;strong&gt;Branch Hash&lt;/strong&gt;: &lt;code&gt;d6ac4c01...779960d1210&lt;/code&gt; (Branch0 hash)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Script3’s Proof Path&lt;/strong&gt;: &lt;code&gt;Script3 → Branch1 → Root&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Needs Script2 hash as sibling to reconstruct Branch1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Needs Branch0 hash to reconstruct Root&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Control Block Pattern&amp;nbsp;Summary
&lt;/h2&gt;

&lt;p&gt;Script Tree Position Sibling Hash Branch Hash Proof Path Script0 Branch0 left Script1 hash Branch1 hash Script0→Branch0→Root Script1 Branch0 right Script0 hash Branch1 hash Script1→Branch0→Root Script2 Branch1 left Script3 hash Branch0 hash Script2→Branch1→Root Script3 Branch1 right Script2 hash Branch0 hash Script3→Branch1→Root&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Insights&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Same-branch scripts&lt;/strong&gt;: Are sibling nodes to each other (Script0↔Script1, Script2↔Script3)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-branch proof&lt;/strong&gt;: Requires hash of entire opposite branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proof minimization&lt;/strong&gt;: Each Control Block contains only minimum information needed to reconstruct Root&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Script1 Multisig Stack Execution Deep&amp;nbsp;Analysis
&lt;/h2&gt;

&lt;p&gt;Since we have cryptographically verified that Script1 is indeed in this Taproot address, now let’s deeply analyze its execution process in the Bitcoin virtual machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete Script1 Bytecode&amp;nbsp;Analysis
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Raw Bytecode (71 bytes)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;002050be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3ba2084b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5ba5287
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Byte-level Breakdown&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00                    OP_0 (initialize counter)
20                    OP_PUSHBYTES_32 (push 32 bytes)
50be5fc4...26bb4d3   Alice's x-only public key (32 bytes)
ba                    OP_CHECKSIGADD (verify signature and increment counter)
20                    OP_PUSHBYTES_32 (push 32 bytes)  
84b59516...eef63af5  Bob's x-only public key (32 bytes)
ba                    OP_CHECKSIGADD (verify signature and increment counter)
52                    OP_2 (push number 2)
87                    OP_EQUAL (check equality)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the standard Tapscript 2-of-2 multisig pattern, using the efficient &lt;code&gt;OP_CHECKSIGADD&lt;/code&gt; opcode introduced in BIP 342.&lt;/p&gt;

&lt;h2&gt;
  
  
  Witness Data Structure and Consumption Order
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Transaction ID&lt;/strong&gt;: &lt;code&gt;1951a3be0f05df377b1789223f6da66ed39c781aaf39ace0bf98c3beb7e604a1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-execution Preparation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Control Block Verification&lt;/strong&gt;: Script1 is indeed in Merkle Root ✅&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 0: Witness Data Pushed onto Execution Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bob signature enters stack first&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │         Bob Signature       │ │ ← Stack bottom
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Alice signature enters stack second&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │        Alice Signature      │ │ ← Stack top
│ ├─────────────────────────────┤ │
│ │         Bob Signature       │ │ ← Stack bottom
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness[0] = Bob signature (enters stack first)
witness[1] = Alice signature (enters stack second)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Detailed Witness Data&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0] Bob Signature (64 bytes):
    31fa0ca7929dac01b908349326183dd7a0f752475d42f11dc2cd0075110ca2a4c
    255f3e310dfc0800e69609c872254241dcf827847e5b64821cefa6c6db575bc

[1] Alice Signature (64 bytes):
    22272de665b998668ae9e97cb72d9814d362ae101ee878caee04da0d2a7efb14
    e8bcdd7eb8082fad30864ec7f22bce6fb2d2178764a0b2f5427346e4b5821fa0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Script1 Execution Process
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Script1 Bytecode Breakdown&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_0 → OP_PUSHBYTES_32(Alice pubkey) → OP_CHECKSIGADD → 
OP_PUSHBYTES_32(Bob pubkey) → OP_CHECKSIGADD → OP_2 → OP_EQUAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1:&amp;nbsp;OP_0
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │             0               │ │ ← Newly pushed
│ ├─────────────────────────────┤ │
│ │        Alice Signature      │ │
│ ├─────────────────────────────┤ │
│ │         Bob Signature       │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: OP_PUSHBYTES_32 (Alice Public&amp;nbsp;Key)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │        Alice_PubKey         │ │ ← Newly pushed
│ │    50be5fc4...126bb4d3       │ │
│ ├─────────────────────────────┤ │
│ │             0               │ │
│ ├─────────────────────────────┤ │
│ │        Alice Signature      │ │
│ ├─────────────────────────────┤ │
│ │         Bob Signature       │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: OP_CHECKSIGADD (Verify&amp;nbsp;Alice)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pop Alice public key, consume Alice signature, push 1 after verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │             1               │ │ ← Verification successful
│ ├─────────────────────────────┤ │
│ │   ❌Alice Signature(consumed)│ │
│ ├─────────────────────────────┤ │
│ │         Bob Signature       │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: OP_PUSHBYTES_32 (Bob Public&amp;nbsp;Key)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │         Bob_PubKey          │ │ ← Newly pushed
│ │    84b59516...eef63af5       │ │
│ ├─────────────────────────────┤ │
│ │             1               │ │
│ ├─────────────────────────────┤ │
│ │   ❌Alice Signature(consumed)│ │
│ ├─────────────────────────────┤ │
│ │         Bob Signature       │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: OP_CHECKSIGADD (Verify&amp;nbsp;Bob)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pop Bob public key, consume Bob signature, push 2 after verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │             2               │ │ ← Verification successful
│ ├─────────────────────────────┤ │
│ │   ❌Alice Signature(consumed)│ │
│ ├─────────────────────────────┤ │
│ │   ❌Bob Signature(consumed)  │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6:&amp;nbsp;OP_2
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │             2               │ │ ← Newly pushed (required count)
│ ├─────────────────────────────┤ │
│ │             2               │ │ ← Actual count
│ ├─────────────────────────────┤ │
│ │   ❌Alice Signature(consumed)│ │
│ ├─────────────────────────────┤ │
│ │   ❌Bob Signature(consumed)  │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7:&amp;nbsp;OP_EQUAL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pop both 2s for comparison, push TRUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────┐
│         Execution Stack          │
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │            TRUE             │ │ ← Comparison result
│ ├─────────────────────────────┤ │
│ │   ❌Alice Signature(consumed)│ │
│ ├─────────────────────────────┤ │
│ │   ❌Bob Signature(consumed)  │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Insights
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Execution Order&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Bob signature enters stack first (witness[0])&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alice signature enters stack second (witness[1])&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Script processes in Alice→Bob order, but Alice signature at stack top is consumed first&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This perfectly explains the witness array index relationship&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;LIFO Consumption Logic&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Stack entry order: Bob → Alice&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consumption order: Alice → Bob (last in, first out)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Script execution order: Alice pubkey → Alice signature consumption → Bob pubkey → Bob signature consumption&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary: Complete Cryptographic Loop of Taproot Mechanism
&lt;/h2&gt;

&lt;p&gt;Through this in-depth analysis, we have completely verified Taproot’s cryptographic mechanisms:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Merkle Tree Construction
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use TaggedHash to build collision-resistant hash trees&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TapLeaf hashes commit individual scripts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TapBranch hashes combine subtrees&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Root commits the entire script set&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Control Block Interpretation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🔴 Version Field&lt;/strong&gt;: Specifies script version and coordinate parity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🔵 Internal Public Key&lt;/strong&gt;: Provides key commitment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🟡🟢 Merkle Proof&lt;/strong&gt;: Provides minimized inclusion proof path&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Real Data Verification
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Reconstruct complete Merkle Root from Control Block&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify Root’s correspondence to address through Tweak mechanism&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prove specific script is indeed committed in Taproot address&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Script Stack Execution
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;LIFO consumption mechanism of witness data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Efficient signature verification in Tapscript&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stack state changes of multisig logic&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is Taproot’s complete cryptographic loop — from script commitment to Merkle proof, from address generation to stack execution, every step embodies sophisticated cryptographic design.&lt;/p&gt;




&lt;h2&gt;
  
  
  Looking Forward to Part&amp;nbsp;3
&lt;/h2&gt;

&lt;p&gt;In Part 3, we will build an interactive visualization tool that allows readers to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Adjust script content in real-time and observe Merkle tree changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simulate different Control Block constructions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compare costs and privacy characteristics of various spending paths&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explore unbalanced trees and optimization strategies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned for this visual feast of Taproot technology!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is part of the “Not Just HODLing: Real Bitcoin Script Engineering” series. Previous articles covered &lt;em&gt;[*CSV timelocks with P2SH&lt;/em&gt;](&lt;a href="https://medium.com/@aaron.recompile/how-i-built-a-time-locked-bitcoin-script-with-csv-and-p2sh-c48c0389709d" rel="noopener noreferrer"&gt;https://medium.com/@aaron.recompile/how-i-built-a-time-locked-bitcoin-script-with-csv-and-p2sh-c48c0389709d&lt;/a&gt;)&lt;/em&gt; and &lt;em&gt;[*basic Taproot implementation&lt;/em&gt;](&lt;a href="https://medium.com/@aaron.recompile/a-guide-to-creating-taproot-scripts-with-python-bitcoinutils-e088633bc2a7)*.*" rel="noopener noreferrer"&gt;https://medium.com/@aaron.recompile/a-guide-to-creating-taproot-scripts-with-python-bitcoinutils-e088633bc2a7)*.*&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/5ff10f98032c" rel="noopener noreferrer"&gt;July 7, 2025&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/taproot-control-block-deep-analysis-stack-execution-visualization-5ff10f98032c" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>taproot</category>
    </item>
    <item>
      <title>SIGHASH_ANYPREVOUT on Signet: When Signatures Stop Binding to UTXOs</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:54 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/sighashanyprevout-on-signet-when-signatures-stop-binding-to-utxos-3f56</link>
      <guid>https://dev.to/aaron_recompile/sighashanyprevout-on-signet-when-signatures-stop-binding-to-utxos-3f56</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Standard CHECKSIG asks: did you sign for *&lt;/em&gt;&lt;strong&gt;this UTXO&lt;/strong&gt;*&lt;em&gt;?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ANYPREVOUT asks: did you sign for *&lt;/em&gt;&lt;strong&gt;this kind of UTXO&lt;/strong&gt;*&lt;em&gt;?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A signature normally says: “I authorize spending this exact coin.” &lt;br&gt;
ANYPREVOUT says: “I authorize spending any coin that looks like this.”&lt;/p&gt;

&lt;p&gt;Same Schnorr math. Different preimage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F69n8nsqp8grx18gtylmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F69n8nsqp8grx18gtylmg.png"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitcoin is not designed top-down.  
It is discovered through execution.

"Run the Future" — starts here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Same private key, same tapscript leaf (&lt;code&gt;&amp;lt;0x01||xonly&amp;gt; OP_CHECKSIG&lt;/code&gt;, 35 bytes), two different UTXOs. Sign once, copy the witness verbatim onto the second transaction — no re-signing. Both pass Inquisition consensus validation and confirm in the same block. The mechanism: BIP 118’s sighash preimage omits &lt;code&gt;sha_prevouts&lt;/code&gt;, so the outpoint vanishes from the digest. The signature binds not to “which UTXO” but to “what shape of UTXO.”&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What Signatures Actually Are, and How Bitcoin Uses&amp;nbsp;Them
&lt;/h2&gt;

&lt;p&gt;Schnorr signatures (BIP 340) have nothing inherently to do with Bitcoin. They simply sign a message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sig = Sign(privkey, message)
Verify(pubkey, message, sig) → true / false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;message&lt;/code&gt; can be anything — a text string, a file hash, a chat message. A signature proves: &lt;strong&gt;the holder of this private key endorsed this message.&lt;/strong&gt; No relation to transactions whatsoever.&lt;/p&gt;

&lt;p&gt;Bitcoin took this general-purpose tool and did something specific: &lt;strong&gt;it dictated what the message must be.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a script executes &lt;code&gt;OP_CHECKSIG&lt;/code&gt;, the node does not let you pass in your own message. It automatically assembles a byte string from the current transaction’s fields — input sources, output destinations, amounts, timelocks — called the &lt;strong&gt;sighash preimage&lt;/strong&gt;, then hashes it to produce a 32-byte &lt;strong&gt;digest&lt;/strong&gt;. This digest is the message being signed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;General signing:  you choose the message → sign → verify
CHECKSIG:         consensus rules choose the message → you just sign → node rebuilds the message to verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So CHECKSIG is not just “verify a signature” — it turns signatures from general authorization into transaction authorization.&lt;/strong&gt; You are not signing “I agree.” You are signing “I agree to spend this money, from here, to there.” The strength of the signature depends on which fields are stuffed into the sighash preimage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where sighash comes&amp;nbsp;from
&lt;/h2&gt;

&lt;p&gt;The sighash mechanism has evolved through several generations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Legacy (pre-SegWit)&lt;/strong&gt;: Serializes the entire transaction, strips different parts depending on sighash type, double SHA256. The infamous O(n²) problem originated here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BIP 143 (SegWit v0)&lt;/strong&gt;: Redesigned the preimage format — SHA256 prevouts, sequences, and outputs once each, then plug those 32-byte hashes into the preimage. Solved O(n²).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;BIP 341 / 342 (Taproot / Tapscript)&lt;/strong&gt;: Built on BIP 143 with &lt;code&gt;TaggedHash("TapSighash",&amp;nbsp;...)&lt;/code&gt; for domain separation, added &lt;code&gt;spend_type&lt;/code&gt;, &lt;code&gt;tapleaf_hash&lt;/code&gt;, &lt;code&gt;key_version&lt;/code&gt;, and other fields.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every generation adjusts the same question: &lt;strong&gt;which fields go into the sighash preimage?&lt;/strong&gt; More fields means tighter binding; fewer fields means more flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sighash type: choosing how much to&amp;nbsp;bind
&lt;/h2&gt;

&lt;p&gt;Even within the same sighash generation, the signer can choose which fields to bind via &lt;strong&gt;sighash type&lt;/strong&gt;. The standard options:&lt;/p&gt;

&lt;p&gt;sighash typeBinds all inputsBinds all outputs&lt;code&gt;SIGHASH_ALL&lt;/code&gt; (0x01)YesYes&lt;code&gt;SIGHASH_NONE&lt;/code&gt; (0x02)Yes*&lt;em&gt;No&lt;/em&gt;&lt;em&gt;&lt;code&gt;SIGHASH_SINGLE&lt;/code&gt; (0x03)YesSame-index output only&lt;code&gt;SIGHASH_ANYONECANPAY&lt;/code&gt; (0x80, combinable)&lt;/em&gt;&lt;em&gt;Current input only&lt;/em&gt;*Depends on the above three&lt;/p&gt;

&lt;p&gt;These all operate on the &lt;strong&gt;output side&lt;/strong&gt; — relaxing “where the money goes.” But they share one invariant: &lt;strong&gt;the input side never relaxes.&lt;/strong&gt; The input you sign is permanently bound to a specific &lt;code&gt;txid:vout&lt;/code&gt;. Once signed, that signature can only spend that one UTXO. Swap it, and the signature fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is exactly what BIP 118 changes.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What problem BIP 118&amp;nbsp;solves
&lt;/h2&gt;

&lt;p&gt;Scenario: you and a counterparty opened a Lightning channel and went through 100 state updates. The counterparty cheats by broadcasting the old state from round 50. You need your round-100 update transaction to override it.&lt;/p&gt;

&lt;p&gt;But your round-100 update transaction was signed during round 100 — at that time you had no idea whether the counterparty would broadcast round 50, let alone what txid round 50 would have on-chain. Under standard CHECKSIG, the signature is bound to the prevout’s &lt;code&gt;txid:vout&lt;/code&gt;. You cannot pre-sign a “spend any future old state” transaction.&lt;/p&gt;

&lt;p&gt;BIP 118 (SIGHASH_ANYPREVOUT) solves this: &lt;strong&gt;remove **`&lt;/strong&gt;sha_prevouts*&lt;em&gt;`&lt;/em&gt;* from the sighash preimage.** The signature no longer binds to a specific outpoint. As long as the UTXO’s amount and script match, the same signature works. You can pre-sign at round 100 a transaction that spends “any shape-matching old state” — whether the counterparty broadcasts round 50 or round 73.&lt;/p&gt;

&lt;p&gt;This is the foundation of Eltoo / LN-Symmetry.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. SigMsg vs Msg118: What Exactly Was&amp;nbsp;Removed
&lt;/h2&gt;

&lt;p&gt;Standard Tapscript (BIP 342) &lt;code&gt;SigMsg&lt;/code&gt; assembles these fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SigMsg = hash_type
       || nVersion || nLocktime
       || sha_prevouts          ← SHA256(txid₁||vout₁ || txid₂||vout₂ || ...)
       || sha_amounts           ← SHA256(amount₁ || amount₂ || ...)
       || sha_scriptpubkeys     ← SHA256(spk₁ || spk₂ || ...)
       || sha_sequences         ← SHA256(seq₁ || seq₂ || ...)
       || sha_outputs           ← SHA256(all outputs)
       || spend_type
       || input_amount          ← current input's amount
       || input_scriptpubkey    ← current input's scriptPubKey
       || input_index
       || ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sha_prevouts&lt;/code&gt; hashes every input’s &lt;code&gt;txid:vout&lt;/code&gt; into the digest. Change one prevout, the hash changes, the digest changes, signature verification fails. &lt;strong&gt;This is the “bind to UTXO” mechanism — not some abstract design, but a line of bytes in the preimage.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;BIP 118’s &lt;code&gt;Msg118&lt;/code&gt; removes these fields:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqmhp9uvdj2lfi863hub3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqmhp9uvdj2lfi863hub3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the subtlety: BIP 342 includes both the global &lt;code&gt;sha_amounts&lt;/code&gt; (hash of all input amounts) and the per-input &lt;code&gt;input_amount&lt;/code&gt;. BIP 118 keeps only the latter — if the global were retained, you would still indirectly bind to other inputs’ information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: the digest contains no information about which outpoint is being spent. As long as the new UTXO has the same amount and same scriptPubKey, &lt;code&gt;Msg118&lt;/code&gt; produces the identical byte string, the identical digest, and signature verification passes.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Three Tiers of Sighash: Package&amp;nbsp;Pickup
&lt;/h2&gt;

&lt;p&gt;BIP 118 defines two new sighash flags. Together with standard BIP 342, there are three tiers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn0cbtsd9exwnhrzlcib1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn0cbtsd9exwnhrzlcib1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Package pickup analogy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standard CHECKSIG (0x01)&lt;/strong&gt;: The pickup slip says “collect package YT1234567890.” Only that one package — the signature is valid only for that tracking number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ANYPREVOUT (0x41)&lt;/strong&gt;: The pickup slip says “collect any 5 kg package addressed to Zhang San.” Three qualifying packages at the station — this slip works for any of them. But it must be 5 kg, addressed to Zhang San.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ANYPREVOUTANYSCRIPT (0xC1)&lt;/strong&gt;: The pickup slip just says “Zhang San is here to pick up.” Any package will do, regardless of weight or sender, as long as the station accepts Zhang San’s ID.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This experiment uses 0x41 (the middle tier), which is also the tier Eltoo / LN-Symmetry actually needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. How the Node Switches: the 0x01 Public Key&amp;nbsp;Prefix
&lt;/h2&gt;

&lt;p&gt;Standard tapscript uses 32-byte x-only public keys. BIP 118 stipulates: &lt;strong&gt;if the public key in a tapscript is 33 bytes and the first byte is **`&lt;/strong&gt;0x01**&lt;code&gt;, the node uses BIP 118’s &lt;/code&gt;Msg118 + Ext118&lt;code&gt; to compute the digest when executing &lt;/code&gt;OP_CHECKSIG&lt;code&gt;, instead of standard BIP 342’s &lt;/code&gt;SigMsg`.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Standard tapscript:  &amp;lt;32-byte xonly pubkey&amp;gt; OP_CHECKSIG   → uses BIP 342 SigMsg
BIP 118:             &amp;lt;0x01 || 32-byte xonly pubkey&amp;gt; OP_CHECKSIG → uses Msg118 + Ext118
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an opt-in mechanism — it does not affect any existing tapscript. Only when you explicitly use a &lt;code&gt;0x01&lt;/code&gt;-prefixed public key does BIP 118 sighash logic activate.&lt;/p&gt;

&lt;p&gt;Additionally, &lt;code&gt;key_version = 0x01&lt;/code&gt; in &lt;code&gt;Ext118&lt;/code&gt; (BIP 342 uses &lt;code&gt;0x00&lt;/code&gt;) isolates the sighash domain: even if the public key bytes are identical, a BIP 118 signature cannot replay on a standard BIP 342 key. The two worlds do not interfere.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Commit Phase: Building an APO-only P2TR Address with&amp;nbsp;btcaaron
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_wif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEMO_KEY_WIF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;TapTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bip118_checksig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apo_rebind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron API Walkthrough
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**.bip118_checksig(key, label="apo_rebind")**&lt;/code&gt; is a convenience wrapper around&amp;nbsp;&lt;code&gt;.custom()&lt;/code&gt;. It is equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;apo_pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apo_pubkey_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xonly_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 0x01 || xonly (33 bytes)
&lt;/span&gt;&lt;span class="n"&gt;leaf_script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RawScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;build_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;push_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apo_pk&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;OP_CHECKSIG&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;TapTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;leaf_script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apo_rebind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The leaf script is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;0x01 || xonly_pubkey&amp;gt; OP_CHECKSIG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;35 bytes (&lt;code&gt;OP_PUSHBYTES_33&lt;/code&gt; 1 byte + &lt;code&gt;0x01||xonly&lt;/code&gt; 33 bytes + &lt;code&gt;OP_CHECKSIG&lt;/code&gt; 1 byte). The &lt;code&gt;0x01&lt;/code&gt; prefix tells the Inquisition node to use BIP 118 sighash. &lt;code&gt;apo_pubkey_bytes()&lt;/code&gt; prepends the &lt;code&gt;0x01&lt;/code&gt; byte to the x-only public key. The subsequent&amp;nbsp;&lt;code&gt;.build()&lt;/code&gt; →&amp;nbsp;&lt;code&gt;.spend()&lt;/code&gt; →&amp;nbsp;&lt;code&gt;.sign()&lt;/code&gt; /&amp;nbsp;&lt;code&gt;.unlock_with()&lt;/code&gt; flow is identical to all prior experiments.&lt;/p&gt;

&lt;p&gt;Tapscript bytecode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;21                                                                &amp;lt;- OP_PUSHBYTES_33
01ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8  &amp;lt;- 0x01 || xonly pubkey
ac                                                                &amp;lt;- OP_CHECKSIG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Funding Two Identical UTXOs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;txid1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wallet_send_sats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rpc_wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;txid2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wallet_send_sats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rpc_wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two independent wallet transfers, 50,000 sats each, sent to the same P2TR address. Two UTXOs with the same amount and scriptPubKey, but different txids.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Reveal Phase: Sign Once, Spend&amp;nbsp;Twice
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Sign the first UTXO normally
&lt;/span&gt;&lt;span class="n"&gt;tx1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apo_rebind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_utxo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u1_txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u1_vout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;47_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# ← internally calls key.sign_taproot_script_bip118()
&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Extract the witness stack
&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;witnesses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;   &lt;span class="c1"&gt;# [sig, leaf_script, control_block]
# Attach the same witness to the second transaction - no re-signing
&lt;/span&gt;&lt;span class="n"&gt;tx2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apo_rebind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_utxo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u2_txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u2_vout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;47_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock_with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;broadcast_or_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;broadcast_or_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron Signing API Walkthrough
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**.sign(key)**&lt;/code&gt;, upon detecting that the current leaf uses a BIP 118 public key, internally calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign_taproot_script_bip118&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;txin_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;utxo_scripts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;scriptPubKey&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;amounts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;tapleaf_script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;hash_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mh"&gt;0x41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# SIGHASH_ALL | SIGHASH_ANYPREVOUT
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method calls &lt;code&gt;bip118_sighash()&lt;/code&gt; to construct &lt;code&gt;Msg118 || Ext118&lt;/code&gt;, computes &lt;code&gt;TaggedHash("TapSighash", 0x00 || msg || ext)&lt;/code&gt; to get a 32-byte digest, then produces a BIP 340 Schnorr signature, appends sighash byte &lt;code&gt;0x41&lt;/code&gt;, returning 65 bytes (130 hex chars).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**.unlock_with([sig, leaf, cb])**&lt;/code&gt; does not sign — it directly uses the three provided hex strings as the witness stack. Because &lt;code&gt;Msg118&lt;/code&gt; does not contain the outpoint, the witness from the first spend is equally valid on the second.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Transaction Structure and On-chain&amp;nbsp;Evidence
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Funding: Two Independent Wallet Transfers
&lt;/h2&gt;

&lt;p&gt;TxIDAmountFunding A&lt;code&gt;[4b64…344a](https://mempool.space/signet/tx/4b6451082fe4349fdb2acad6bf0964c6cfd8c9cbf5161806fc342b051dee344a?showDetails=true)&lt;/code&gt;50,000 satsFunding B&lt;code&gt;[543c…a5eb](https://mempool.space/signet/tx/543c97f777ea04624392f6a1547146d6e98996b38aec0794f6b79e3275f6a5eb?showDetails=true)&lt;/code&gt;50,000 sats&lt;/p&gt;

&lt;p&gt;Both pay to: &lt;code&gt;tb1prpa4a7wc0v5ghesn8pqhz7uw6lccd5u52rgzcfdqv3kwd3ptapcqnt93g2&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reveal: The Rebinding Pair
&lt;/h2&gt;

&lt;p&gt;TxIDPrevoutOutputSpend A (signed)&lt;code&gt;[03c0…f3a4](https://mempool.space/signet/tx/03c0577c1d47da32804d098187644d0eee18b448aded2f427cd02193c070f3a4?showDetails=true)4b64…344a:0&lt;/code&gt;47,000 satsSpend B (reused witness)&lt;code&gt;[4609…1a43](https://mempool.space/signet/tx/46091190c74d8fd4b39be67a2e945a19b021850e7f8d9e378f5eb11722ae1a43?showDetails=true)543c…a5eb:0&lt;/code&gt;47,000 sats&lt;/p&gt;

&lt;p&gt;Both confirmed in Inquisition signet block 298,280. 3,000 sats fee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Witness Comparison
&lt;/h2&gt;

&lt;p&gt;Decoding both spend transactions on mempool.space, the witness stacks are byte-for-byte identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0] = 902004a5...b5fa04064bdc41   &amp;lt;- Schnorr signature + sighash byte 0x41 (65 bytes)
Witness[1] = 2101ff1f...9986b8ac         &amp;lt;- tapscript (35 bytes)
Witness[2] = c1ff1f9fa3...9986b8         &amp;lt;- control block (33 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Witness[0]&lt;/strong&gt;: 64-byte Schnorr signature with &lt;code&gt;0x41&lt;/code&gt; (&lt;code&gt;SIGHASH_ALL | SIGHASH_ANYPREVOUT&lt;/code&gt;) appended, totaling 65 bytes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witness[1]&lt;/strong&gt;: &lt;code&gt;21&lt;/code&gt; (OP_PUSHBYTES_33) + &lt;code&gt;01||xonly_pubkey&lt;/code&gt; (33 bytes) + &lt;code&gt;ac&lt;/code&gt; (OP_CHECKSIG) = 35 bytes. The BIP 118 leaf script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witness[2]&lt;/strong&gt;: Control block starts with &lt;code&gt;c1&lt;/code&gt;. &lt;code&gt;c1&lt;/code&gt; = leaf version &lt;code&gt;0xc0&lt;/code&gt; + parity bit 1. The parity bit is determined by the Y-coordinate parity of the output key Q after TapTweak computation — &lt;code&gt;c0&lt;/code&gt; for even, &lt;code&gt;c1&lt;/code&gt; for odd; it cannot be chosen in advance. Followed by 32-byte internal public key, totaling 33 bytes.&lt;/p&gt;

&lt;p&gt;Different inputs, same witness, both confirmed. &lt;strong&gt;This is rebinding.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Negative Test
&lt;/h2&gt;

&lt;p&gt;Modifying Spend B’s output address or amount causes script verification failure. &lt;code&gt;sha_outputs&lt;/code&gt; remains in &lt;code&gt;Msg118&lt;/code&gt; (&lt;code&gt;SIGHASH_ALL&lt;/code&gt; mode) — the signature commits to where the money goes. &lt;strong&gt;Rebinding lets you swap the input, not the destination.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Rebuilding the Address with RootScope
&lt;/h2&gt;

&lt;p&gt;The tapscript is 35 bytes: &lt;code&gt;OP_PUSHBYTES_33&lt;/code&gt; (&lt;code&gt;0x21&lt;/code&gt;) + &lt;code&gt;0x01||xonly_pubkey&lt;/code&gt; (33 bytes) + &lt;code&gt;OP_CHECKSIG&lt;/code&gt; (&lt;code&gt;0xac&lt;/code&gt;). The derivation chain is identical to the single-leaf TapTree used in all prior experiments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Derivation Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TapLeaf hash = tagged_hash("TapLeaf",  0xc0 || compact_size(35) || script_bytes)
               (leaf version 0xc0, script length 35, script = 21 01||xonly ac)
Merkle root  = TapLeaf hash
Tweak t      = tagged_hash("TapTweak",  internal_pubkey || Merkle root)
Output key Q = internal_pubkey + t*G
P2TR address = Bech32m("tb", Q.x_only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;script_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2101ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8ac&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tapleaf_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# compact_size(35) = 0x23, single byte since 35 &amp;lt; 253
&lt;/span&gt;    &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xc0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merkle_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tapleaf_hash&lt;/span&gt;
&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_x_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tweak_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_p2tr_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Expected: tb1prpa4a7wc0v5ghesn8pqhz7uw6lccd5u52rgzcfdqv3kwd3ptapcqnt93g2  [OK]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  RootScope Visualization
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internal key
  ff1f9fa3...9986b8
        |
    TapLeaf (0xc0)
    Script: 2101&amp;lt;01||xonly&amp;gt;ac  (35 bytes)
                 ^--- 0x01 prefix triggers BIP 118 sighash
        |
    TapLeaf hash
        |   (no sibling -&amp;gt; single-leaf tree)
    Merkle root
        |
    TapTweak
        |
    Output key Q  (parity: odd -&amp;gt; control block byte = c1)
        |
    P2TR address
    tb1prpa4a7wc0v5ghesn8pqhz7uw6lccd5u52rgzcfdqv3kwd3ptapcqnt93g2  [OK]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The control block starts with &lt;code&gt;c1&lt;/code&gt; — the same odd-parity result as the IK+CSFS experiment, but for a different reason: this 35-byte BIP 118 script produces a different TapLeaf hash, and after tweaking, the output key Q’s Y-coordinate happens to be odd. Same &lt;code&gt;internal_pubkey&lt;/code&gt; (&lt;code&gt;ff1f9fa3...9986b8&lt;/code&gt;) as all prior experiments, different tapscript, different Merkle root, different TapTweak, different address.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Cross-validation: Two Independent Implementations Agree on One&amp;nbsp;Spec
&lt;/h2&gt;

&lt;p&gt;Two confirmed transactions mean two independent implementations agreed on the digest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Signing side&lt;/strong&gt; (Python, &lt;code&gt;btcaaron/bip118.py&lt;/code&gt;): constructs &lt;code&gt;Msg118 || Ext118&lt;/code&gt;, &lt;code&gt;TaggedHash("TapSighash",&amp;nbsp;...)&lt;/code&gt;, BIP 340 signature&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation side&lt;/strong&gt; (C++, Bitcoin Inquisition): reconstructs the same digest from the transaction, verifies the Schnorr signature&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bitcoin Core’s wallet and CLI do not support constructing BIP 118 signatures — there is no &lt;code&gt;signrawtransaction --sighash=anyprevout&lt;/code&gt;. Signing must be done externally. &lt;code&gt;btcaaron/bip118.py&lt;/code&gt; is a Python implementation independent of Core’s test framework, building &lt;code&gt;Msg118&lt;/code&gt; / &lt;code&gt;Ext118&lt;/code&gt; byte assembly from the &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki" rel="noopener noreferrer"&gt;BIP 118 specification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On-chain confirmation is the strictest test: if my implementation and Inquisition’s C++ disagree on any byte, the transaction gets rejected.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Why This Matters for&amp;nbsp;Eltoo
&lt;/h2&gt;

&lt;p&gt;ANYPREVOUT is the infrastructure for &lt;a href="https://blockstream.com/eltoo.pdf" rel="noopener noreferrer"&gt;Eltoo (LN-Symmetry)&lt;/a&gt;. In Eltoo, channel state update transactions need to be able to spend &lt;strong&gt;any prior state’s&lt;/strong&gt; UTXO:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Counterparty cheats by broadcasting old State v2 (txid: aaaa...)
You need your State v5 update transaction to override it
But State v5 was pre-signed during round 5 — you didn't know State v2 would ever go on-chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;0x41 makes this possible: &lt;strong&gt;when pre-signing, you don’t need to know the txid — just that the amount and scriptPubKey match.&lt;/strong&gt; Because all rounds’ state UTXOs share the same “lock” (same amount + same script), the signature you pre-signed works against any round.&lt;/p&gt;

&lt;p&gt;Building on this signing code, I also ran a three-round Eltoo-style state chain (APO updates + CTV settlement), with all six transactions confirmed on the same signet — details at the &lt;a href="https://delvingbitcoin.org/t/bip-118-signing-from-scratch-on-chain-rebinding-proof/" rel="noopener noreferrer"&gt;BIP 118 implementation post on Delving Bitcoin&lt;/a&gt; and the &lt;a href="https://delvingbitcoin.org/t/challenge-covenants-for-braidpool/1370/2" rel="noopener noreferrer"&gt;Braidpool covenant challenge reply&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series Position
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsqixm0rfw86iht4d28as.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsqixm0rfw86iht4d28as.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What We&amp;nbsp;Did
&lt;/h2&gt;

&lt;p&gt;We ran an on-chain BIP 118 SIGHASH_ANYPREVOUT experiment: a 35-byte tapscript (&lt;code&gt;&amp;lt;0x01||xonly&amp;gt; OP_CHECKSIG&lt;/code&gt;), two UTXOs with the same amount and scriptPubKey. After signing the first UTXO, we copied the full witness — 65-byte Schnorr signature (sighash byte &lt;code&gt;0x41&lt;/code&gt;), 35-byte leaf script, 33-byte control block — verbatim onto the second transaction. No re-signing. Both broadcast, both confirmed in Inquisition signet block 298,280.&lt;/p&gt;

&lt;p&gt;Signing was done with &lt;code&gt;btcaaron&lt;/code&gt;’s &lt;code&gt;Key.sign_taproot_script_bip118()&lt;/code&gt;, which internally calls the independently implemented &lt;code&gt;Msg118&lt;/code&gt; / &lt;code&gt;Ext118&lt;/code&gt; / &lt;code&gt;TaggedHash("TapSighash",&amp;nbsp;...)&lt;/code&gt; construction in &lt;code&gt;bip118.py&lt;/code&gt;. This is a Python implementation independent of Bitcoin Core’s test framework — on-chain confirmation means it agrees byte-for-byte with Inquisition’s C++ consensus engine on the BIP 118 specification.&lt;/p&gt;

&lt;p&gt;Negative testing confirmed the boundary: modifying the second transaction’s output address or amount causes script verification failure. Under &lt;code&gt;SIGHASH_ALL&lt;/code&gt;, &lt;code&gt;sha_outputs&lt;/code&gt; remains in &lt;code&gt;Msg118&lt;/code&gt;. Rebinding relaxes “which UTXO to spend,” not “where the money goes.”&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;BIP 118 specification: &lt;a href="https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki" rel="noopener noreferrer"&gt;github.com/bitcoin/bips/bip-0118&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;btcaaron toolkit: &lt;a href="https://github.com/aaron-recompile/btcaaron" rel="noopener noreferrer"&gt;github.com/aaron-recompile/btcaaron&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;BIP 118 sighash implementation: &lt;code&gt;[btcaaron/bip118.py](https://github.com/aaron-recompile/btcaaron/blob/main/btcaaron/bip118.py)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rebinding demo script: &lt;code&gt;[examples/braidpool/rca_taptree_smoke.py](https://github.com/aaron-recompile/btcaaron/blob/main/examples/braidpool/rca_taptree_smoke.py)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bitcoin Inquisition: &lt;a href="https://github.com/bitcoin-inquisition/" rel="noopener noreferrer"&gt;github.com/bitcoin-inquisition&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RootScope visualization tool: &lt;a href="https://github.com/aaron-recompile/rootscope" rel="noopener noreferrer"&gt;github.com/aaron-recompile/rootscope&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Signet | Bitcoin Inquisition 29.2 | Funding *&lt;code&gt;[*4b64…344a*](https://mempool.space/signet/tx/4b6451082fe4349fdb2acad6bf0964c6cfd8c9cbf5161806fc342b051dee344a?showDetails=true)&lt;/code&gt;&lt;/em&gt; &lt;em&gt;&lt;code&gt;[*543c…a5eb*](https://mempool.space/signet/tx/543c97f777ea04624392f6a1547146d6e98996b38aec0794f6b79e3275f6a5eb?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Spend A &lt;em&gt;&lt;code&gt;[*03c0…f3a4*](https://mempool.space/signet/tx/03c0577c1d47da32804d098187644d0eee18b448aded2f427cd02193c070f3a4?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Spend B &lt;em&gt;&lt;code&gt;[*4609…1a43*](https://mempool.space/signet/tx/46091190c74d8fd4b39be67a2e945a19b021850e7f8d9e378f5eb11722ae1a43?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Confirmed in block 298,280*&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/eed4fc475668" rel="noopener noreferrer"&gt;April 11, 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/sighash-anyprevout-on-signet-when-signatures-stop-binding-to-utxos-eed4fc475668" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
    </item>
    <item>
      <title>RootScope: A Tool for Reconstructing Taproot Script Paths — Step by Step</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:53 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/rootscope-a-tool-for-reconstructing-taproot-script-paths-step-by-step-7ai</link>
      <guid>https://dev.to/aaron_recompile/rootscope-a-tool-for-reconstructing-taproot-script-paths-step-by-step-7ai</guid>
      <description>&lt;h3&gt;
  
  
  When you spend from a Taproot script path, the hash chain is deterministic. Reconstructing it by hand is a&amp;nbsp;trap.
&lt;/h3&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr06bqlxg1d01pwnei0a5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr06bqlxg1d01pwnei0a5.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Taproot is elegant on paper. Spend from a script path, and the witness contains everything needed to prove your script was committed: the script itself, the control block, and the Merkle path up to the root. The math is clean.&lt;/p&gt;

&lt;p&gt;The problem is &lt;em&gt;inspection&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Given a real transaction, try to manually verify that the reconstructed Merkle root matches the output key. You’ll need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Parse the control block byte by byte&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compute &lt;code&gt;TaggedHash("TapLeaf", version || compact_size(len) || script)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Walk the sibling hashes in the control block up to the root&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compute &lt;code&gt;TaggedHash("TapTweak", internal_pubkey || root)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do the elliptic curve point addition on secp256k1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bech32m-encode the result&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check that it matches the output address&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Miss one step — wrong hash tag, wrong byte order, parity bit ignored — and you get a silent mismatch. No error message. Just a wrong address.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;RootScope&lt;/strong&gt; to do this deterministically and show every step.&lt;/p&gt;




&lt;h2&gt;
  
  
  What RootScope Does
&lt;/h2&gt;

&lt;p&gt;Given a &lt;code&gt;script&lt;/code&gt; and &lt;code&gt;control block&lt;/code&gt;, RootScope computes the complete reconstruction chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TapLeaf hash
  → TapBranch step 1
  → TapBranch step 2
  → ...
  → Merkle root
  → TapTweak
  → tweaked output key
  → bech32m address
  → ✓ matches expected address (optional check)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each intermediate value is shown. Nothing is hidden.&lt;/p&gt;

&lt;p&gt;There’s also a &lt;code&gt;fetch-witness&lt;/code&gt; helper: give it a &lt;code&gt;txid + vin&lt;/code&gt;, and it resolves the witness from a block explorer and prefills the inputs. No manual hex-copying from a mempool explorer.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/aaron-recompile/rootscope" rel="noopener noreferrer"&gt;github.com/aaron-recompile/rootscope&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hash Chain, Concretely
&lt;/h2&gt;

&lt;p&gt;BIP 341 defines three tagged hashes used in Taproot construction:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TapLeaf:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TapLeaf_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TaggedHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;leafVersion&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;compact_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TapBranch&lt;/strong&gt; (each step up the Merkle path):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TapBranch_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TaggedHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapBranch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; are the lexicographically-sorted pair — the control block provides sibling hashes in order, so sorting happens at construction time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TapTweak:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TaggedHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;taproot_pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lift_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final output key is the x-coordinate of that point. Parity check against the control block’s low bit completes the verification.&lt;/p&gt;

&lt;p&gt;RootScope implements all of this in pure Python — &lt;code&gt;hashlib&lt;/code&gt; only, no external crypto libraries. &lt;code&gt;crypto.py&lt;/code&gt; re-implements &lt;code&gt;tagged_hash&lt;/code&gt;, secp256k1 &lt;code&gt;lift_x&lt;/code&gt;, &lt;code&gt;point_add&lt;/code&gt;, &lt;code&gt;point_mul&lt;/code&gt;, and &lt;code&gt;bech32m_encode/decode&lt;/code&gt; from scratch. Every function is independently testable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validation
&lt;/h2&gt;

&lt;p&gt;Before touching mainnet data, I ran the full BIP 341 test vectors.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wallet-test-vectors.json&lt;/code&gt; contains 6 test cases with 12 distinct script-path spending paths — including unbalanced trees (case 3) and mixed-depth paths (cases 5 and 6). All 12 pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make bip341-vectors
&lt;span class="go"&gt;Running BIP341 test vectors...
 [PASS] Case 1 - path 0/0
 [PASS] Case 1 - path 0/1
 [PASS] Case 2 - path 0/0
&lt;/span&gt;&lt;span class="c"&gt; ...
&lt;/span&gt;&lt;span class="go"&gt; [PASS] Case 6 - path 0/1/0


12 / 12 PASS
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional vectors from &lt;a href="https://github.com/aaron-recompile/mastering-taproot" rel="noopener noreferrer"&gt;&lt;em&gt;Mastering Taproot&lt;/em&gt;&lt;/a&gt; cover single-leaf, dual-leaf (balanced), and the first documented 4-leaf Taproot tree — including the full unbalanced variant from Chapter 8.&lt;/p&gt;

&lt;p&gt;Control block guard checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;(len - 33) % 32 == 0&lt;/code&gt; — control block length must be valid&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;depth ≤ 128 — as specified in BIP 341&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;output-key parity check against the &lt;code&gt;version &amp;amp; 0x01&lt;/code&gt; bit&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quick repro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aaron-recompile/rootscope
&lt;span class="nb"&gt;cd &lt;/span&gt;rootscope
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; backend/requirements.txt

make bip341-vectors
./.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; backend.cli tx &lt;span class="se"&gt;\&lt;/span&gt;
  f1de8d3b3894a7cc0efffa7332bf7236ad089195b9d642121f4844f13e01f1e0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vin&lt;/span&gt; 0 &lt;span class="nt"&gt;--network&lt;/span&gt; mainnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Deeper the Tree, the More the Control Block&amp;nbsp;Reveals
&lt;/h2&gt;

&lt;p&gt;Most mainnet script-path spends use single-leaf trees — 33-byte control blocks with no Merkle path at all. That’s the on-chain reality. But it’s not what Taproot was designed for. The interesting structures are in the &lt;a href="https://github.com/aaron-recompile/mastering-taproot" rel="noopener noreferrer"&gt;&lt;em&gt;Mastering Taproot&lt;/em&gt;&lt;/a&gt; book vectors and the BIP 341 test cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Depth progression: single-leaf to 4-leaf&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The book’s three chapters correspond to three tree shapes. Control block length grows linearly with depth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chapter 06 — single leaf (depth=0)
control block = 1 + 32 = 33 bytes

internal_key
       │
     leaf_A  ← script here, no sibling hashes in control block
Chapter 07 - dual-leaf balanced (depth=1)
control block = 1 + 32 + 32 = 65 bytes


  internal_key
       │
    Branch
   ┌───┴───┐
 leaf_A  leaf_B  ← reveal A, control block carries hash of B
Chapter 08 - 4-leaf balanced (depth=2)
control block = 1 + 32 + 32 + 32 = 97 bytes


  internal_key
       │
      Root
   ┌───┴────┐
Branch0   Branch1
┌──┴──┐  ┌──┴──┐
S0   S1  S2   S3  ← reveal S1: control block carries [S0_hash, Branch1_hash]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each additional depth level adds 32 bytes. Verification complexity is O(depth).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unbalanced trees: two different proofs, same output address&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the most counterintuitive part of Taproot’s design: &lt;strong&gt;different leaves in the same tree can have different control block depths, but all resolve to the same output address.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unbalanced 3-leaf tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      Root
    ┌──┴──┐
  Branch  leaf_C  ← depth=1, control block carries Branch_hash
  ┌─┴─┐
leaf_A leaf_B     ← depth=2, control block carries [leaf_B_hash, leaf_C_hash]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revealing leaf_A (depth=2):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;control_block = version | parity
              + internal_pubkey       (32 bytes)
              + leaf_B_hash           (32 bytes, sibling)
              + leaf_C_hash           (32 bytes, sibling one level up)
total = 97 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revealing leaf_C (depth=1):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;control_block = version | parity
              + internal_pubkey       (32 bytes)
              + Branch_AB_hash        (32 bytes, entire left subtree)
total = 65 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two paths, identical Merkle root, identical output address. RootScope reconstructs both correctly and produces consistent results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BIP 341 Case 3: non-standard leaf version&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Test case 3 in &lt;code&gt;wallet-test-vectors.json&lt;/code&gt; includes a path with leaf version &lt;code&gt;0xfa&lt;/code&gt; — not the standard Tapscript &lt;code&gt;0xc0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;control_block &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"faee4fe085983462a184015d1f782d6a5f8b9c2b..."&lt;/span&gt;
&lt;span class="c"&gt;#                ^^&lt;/span&gt;
&lt;span class="c"&gt;#                0xfa = non-standard leaf version, low bit = parity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script for this path is the raw bytes &lt;code&gt;06424950333431&lt;/code&gt; — ASCII "BIP341". From the perspective of address verification, the leaf version only affects the TapLeaf hash computation. TapBranch and TapTweak are unaffected. RootScope handles this correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Open Questions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Depth distribution at scale.&lt;/strong&gt; Does depth &amp;gt; 0 appear more often in protocol-specific spending — Lightning, RGB, covenant constructions? Are there real multi-leaf trees in the wild beyond ordinals infrastructure?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Template labeling.&lt;/strong&gt; The current pipeline identifies templates by script structure. Automatic classification is possible but risks false positives. Is conservative, opt-in labeling better for a tool like this?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dataset integration.&lt;/strong&gt; Are there public datasets of script-path spends — academic datasets, mempool.space exports, or other tooling outputs — that would plug directly into the batch pipeline?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Repo: &lt;a href="https://github.com/aaron-recompile/rootscope" rel="noopener noreferrer"&gt;github.com/aaron-recompile/rootscope&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Related: &lt;em&gt;[*Building a 4-Leaf Taproot Tree in Python&lt;/em&gt;](&lt;a href="https://medium.com/@aaron.recompile/building-a-4-leaf-taproot-tree-in-python-the-first-complete-implementation-on-bitcoin-testnet-3a9b2c8e7f1d" rel="noopener noreferrer"&gt;https://medium.com/@aaron.recompile/building-a-4-leaf-taproot-tree-in-python-the-first-complete-implementation-on-bitcoin-testnet-3a9b2c8e7f1d&lt;/a&gt;)&lt;/em&gt; · &lt;em&gt;[*Taproot Control Block Deep Analysis&lt;/em&gt;](&lt;a href="https://medium.com/@aaron.recompile/taproot-control-block-deep-analysis-stack-execution-visualization-5ff10f98032c" rel="noopener noreferrer"&gt;https://medium.com/@aaron.recompile/taproot-control-block-deep-analysis-stack-execution-visualization-5ff10f98032c&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/106012af54b9" rel="noopener noreferrer"&gt;March 9, 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/rootscope-a-tool-for-reconstructing-taproot-script-paths-step-by-step-106012af54b9" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>taproot</category>
    </item>
    <item>
      <title>OP_INTERNALKEY + OP_CHECKSIGFROMSTACK on Signet — Identity-Bound Authorization</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:51 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/opinternalkey-opchecksigfromstack-on-signet-identity-bound-authorization-3nkg</link>
      <guid>https://dev.to/aaron_recompile/opinternalkey-opchecksigfromstack-on-signet-identity-bound-authorization-3nkg</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitcoin is not designed top-down.  
It is discovered through execution.

"Run the Future" — starts here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Pure CSFS asks: did someone sign this? IK + CSFS asks: did the *owner&lt;/em&gt; sign this?*&lt;/p&gt;

&lt;p&gt;Bitcoin does not enforce identity declarations. It enforces authorization structures.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Two opcodes, two bytes (&lt;code&gt;cbcc&lt;/code&gt;), zero pubkey in the witness. OP_INTERNALKEY pushes the UTXO's internal key from Taproot execution context; OP_CHECKSIGFROMSTACK verifies that the key holder signed the message. Authorization is bound to UTXO identity at creation time, not declared at spend time. One structural consequence: a fixed message is replayable across UTXOs with the same internal key — the next experiment (CAT+CSFS) closes that window.&lt;/p&gt;

&lt;p&gt;The distinction matters more than it looks.&lt;/p&gt;

&lt;p&gt;In the standalone CSFS experiment, the witness supplies three items: signature, message, and pubkey. Any pubkey can walk in. If you have a key that matches the sig-message pair, you pass. The script has no opinion about who that key belongs to.&lt;/p&gt;

&lt;p&gt;OP_INTERNALKEY changes the question. It is a single opcode that pushes the x-only internal pubkey of the current Taproot context onto the stack. The script no longer accepts a key from the witness — it reads the key from the UTXO itself. The authorized signer is whoever created this specific UTXO, not whoever happens to have a matching key.&lt;/p&gt;

&lt;p&gt;Combined as &lt;code&gt;OP_INTERNALKEY OP_CHECKSIGFROMSTACK&lt;/code&gt;, two bytes, this produces an authorization primitive that is bound to a specific Taproot identity: the internal key holder must have signed the message. The witness shrinks from three items to two — signature and message only. The pubkey never appears in the witness. It arrives at runtime from the execution context.&lt;/p&gt;

&lt;p&gt;This post runs the first combo experiment: &lt;code&gt;cbcc&lt;/code&gt; on Signet, with a commit-reveal cycle through Bitcoin Inquisition 29.2. The witness is two items. The stack execution is three steps. Both transactions are confirmed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv6qrisvowa1d43jjxj0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv6qrisvowa1d43jjxj0m.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same verification primitive. Different binding surface.&lt;/p&gt;

&lt;p&gt;Bitcoin changes when the binding surface changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The IK+CSFS&amp;nbsp;Contract
&lt;/h2&gt;

&lt;p&gt;Compare pure CSFS to the combo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pure CSFS (OP_CHECKSIGFROMSTACK):
  witness  = [sig, message, pubkey]
  script   = OP_CHECKSIGFROMSTACK
  pubkey source: witness (caller-supplied)


IK + CSFS (OP_INTERNALKEY OP_CHECKSIGFROMSTACK):
  witness  = [sig, message]
  script   = OP_INTERNALKEY OP_CHECKSIGFROMSTACK
  pubkey source: Taproot context (UTXO-bound)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the IK+CSFS version, the pubkey is never in the witness. It is pushed by OP_INTERNALKEY from the validated Taproot execution context — the internal key used to construct the TapTree the spender is revealing.&lt;/p&gt;

&lt;p&gt;This matters because of how the Taproot verifier works. When a script-path spend occurs, the control block in the witness carries the candidate internal key &lt;code&gt;P&lt;/code&gt; and a Merkle path. The verifier reconstructs the output key &lt;code&gt;Q' = P + tweak·G&lt;/code&gt; and checks &lt;code&gt;Q' == Q&lt;/code&gt; against the UTXO. Only after this consistency check passes does OP_INTERNALKEY expose the validated &lt;code&gt;P&lt;/code&gt; to the execution stack. There is no way to inject a different key through the witness.&lt;/p&gt;

&lt;p&gt;The result: the signer identity is bound to this UTXO’s creation. The same two-byte script can be used in any TapTree — but each instance enforces a different authorized signer, because each instance has a different internal key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contract&amp;nbsp;Design
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Signer offline:
  message = SHA256("authorized:group_combo_ik_csfs:v1")
           = c4820b82...313ebc
  sig     = Schnorr.sign(secret_key, message)
           = 5a5a5107...a1fdd37

At spend time, witness provides:
  Witness[0] = sig       (64 bytes)
  Witness[1] = message   (32 bytes)
Script (two bytes):
  0xcb = OP_INTERNALKEY           &amp;lt;- push internal key from Taproot context
  0xcc = OP_CHECKSIGFROMSTACK     &amp;lt;- verify sig(message, internal_key)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tapscript bytecode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cb   &amp;lt;- OP_INTERNALKEY
cc   &amp;lt;- OP_CHECKSIGFROMSTACK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two bytes. No key in the script, no key in the witness. The authorized identity is implicit in the UTXO’s Taproot construction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Commit Phase: Building the Taproot Address with&amp;nbsp;btcaaron
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MESSAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authorized:group_combo_ik_csfs:v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_wif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEMO_KEY_WIF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;leaf_script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RawScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;build_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OP_INTERNALKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OP_CHECKSIGFROMSTACK&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;TapTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;leaf_script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ik_csfs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron API Highlights
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**build_script(OP_INTERNALKEY, OP_CHECKSIGFROMSTACK)**&lt;/code&gt; assembles the two-byte tapscript &lt;code&gt;cbcc&lt;/code&gt;. Both opcodes are imported from &lt;code&gt;experiments.opcodes&lt;/code&gt; as raw bytes — they are proposed opcodes, not standard btcaaron primitives, so they live in the experiment layer rather than the library.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**TapTree(...).custom(script, label).build()**&lt;/code&gt; derives the P2TR address via the same TapLeaf → Merkle root → TapTweak → output key chain as all previous experiments. Two-byte script, same derivation depth as any other single-leaf tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Funding the&amp;nbsp;Address
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;txid = fund_address(addr, FUND_TXID_FILE, fund_sats=50_000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reveal Phase: Spending with Sig and Message&amp;nbsp;Only
&lt;/h2&gt;

&lt;p&gt;The witness is two items. No pubkey.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_make_witness&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wif_secret_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEMO_KEY_WIF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PrivateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schnorr_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MESSAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;MESSAGE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ik_csfs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_utxo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fee_sats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_make_witness&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reveal_txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;broadcast_or_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron Spend&amp;nbsp;API
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**_make_witness()**&lt;/code&gt; returns &lt;code&gt;[sig, message]&lt;/code&gt; in that order. &lt;code&gt;sig&lt;/code&gt; is pushed first (lands at stack bottom), &lt;code&gt;message&lt;/code&gt; is pushed second (lands at stack top). No pubkey item — OP_INTERNALKEY will supply it at runtime.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**pk.schnorr_sign(MESSAGE, "", raw=True)**&lt;/code&gt; signs the 32-byte digest using BIP340 Schnorr with a deterministic nonce.&lt;/p&gt;

&lt;p&gt;The final witness layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0] = 5a5a5107...a1fdd37    &amp;lt;- Schnorr sig (64 bytes)
Witness[1] = c4820b82...313ebc     &amp;lt;- message = SHA256("authorized:...") (32 bytes)
Witness[2] = cbcc                  &amp;lt;- tapscript (OP_INTERNALKEY OP_CHECKSIGFROMSTACK, 2 bytes)
Witness[3] = c1ff1f9fa3...9986b8   &amp;lt;- control block (33 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Transactions, OP_RETURN_203 + OP_RETURN_204, and Stack&amp;nbsp;Trace
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Commit: what lands&amp;nbsp;on-chain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[9930e922...0ea8](https://mempool.space/signet/tx/9930e922036a80d04a96a4b08f15838bcb880ce2a4be91da0b24af1484e10ea8?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OUTPUTS (relevant)
  tb1p...    0.00050000 sBTC  &amp;lt;- IK+CSFS lock UTXO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard P2TR output. Nothing about the two-byte script is visible externally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reveal: what lands&amp;nbsp;on-chain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[8d0b2156...dd8f](https://mempool.space/signet/tx/8d0b2156e9425afe64cabf3c906da255b6b86c51cb8968f828d5253fc261dd8f?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fee        : 500 sats
Features   : SegWit | Taproot | RBF

INPUT
  (IK+CSFS lock UTXO)    0.00050000 sBTC
OUTPUT
  (change address)       0.00049500 sBTC  (V1_P2TR)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The witness as shown by mempool.space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0]  5a5a5107...a1fdd37
            &amp;lt;- Schnorr sig (64 bytes)

Witness[1]  c4820b82...313ebc
            &amp;lt;- SHA256("authorized:group_combo_ik_csfs:v1") (32 bytes)
Witness[2]  cbcc
            &amp;lt;- tapscript
Witness[3]  c1 ff1f9fa3...9986b8
            &amp;lt;- control block (leaf version 0xc0 + parity 1 + internal pubkey)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;P2TR tapscript decoded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_RETURN_203
OP_RETURN_204
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The OP_RETURN_203 + OP_RETURN_204 labels — and what they actually&amp;nbsp;mean
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;(Readers familiar with the OP_SUCCESS soft fork upgrade pattern can skip this section.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0xcb&lt;/code&gt; = 203 decimal. &lt;code&gt;0xcc&lt;/code&gt; = 204 decimal. Both fall in BIP342's OP_SUCCESS range (opcodes 187–254 are OP_SUCCESS in Tapscript). On standard nodes, any OP_SUCCESS opcode causes the tapscript to pass immediately without evaluating anything further. On Bitcoin Inquisition 29.2, &lt;code&gt;0xcb&lt;/code&gt; executes as OP_INTERNALKEY and &lt;code&gt;0xcc&lt;/code&gt; as OP_CHECKSIGFROMSTACK.&lt;/p&gt;

&lt;p&gt;mempool.space, running standard software, labels both as &lt;code&gt;OP_RETURN_203&lt;/code&gt; and &lt;code&gt;OP_RETURN_204&lt;/code&gt; — its naming convention for unrecognized opcodes, not OP_RETURN the data-embedding opcode. When two OP_SUCCESS opcodes appear in the same script, a non-upgraded node passes on the first one without reaching the second. Inquisition nodes execute both sequentially in full.&lt;/p&gt;

&lt;p&gt;This is the same soft fork upgrade mechanism as the standalone CSFS experiment. The new rules turn “always pass” opcodes into real constraints; old nodes see OP_SUCCESS and accept the block because they have no rule to reject it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack execution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Executing&lt;/strong&gt;: &lt;code&gt;OP_INTERNALKEY OP_CHECKSIGFROMSTACK&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Taproot interpreter strips Witness&lt;a href="https://dev.toscript"&gt;2&lt;/a&gt; and Witness&lt;a href="https://dev.tocontrol%20block"&gt;3&lt;/a&gt;, loading Witness[0..1] as the initial execution stack.&lt;/p&gt;




&lt;h3&gt;
  
  
  Initial State: two witness items loaded as execution stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| c4820b82...313ebc                          |  &amp;lt;- message  (Witness[1], top)
| 5a5a5107...a1fdd37                         |  &amp;lt;- sig      (Witness[0], bottom)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two items. No pubkey anywhere on the stack yet.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1 — OP_INTERNALKEY: push internal key from Taproot&amp;nbsp;context
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| ff1f9fa3...9986b8                          |  &amp;lt;- internal key (32 bytes, from context)
| c4820b82...313ebc                          |  &amp;lt;- message
| 5a5a5107...a1fdd37                         |  &amp;lt;- sig
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OP_INTERNALKEY reads the validated internal key from the Taproot execution context — the &lt;code&gt;P&lt;/code&gt; that was checked against the UTXO's output key &lt;code&gt;Q&lt;/code&gt; when the control block was processed. This value was never in the witness. It comes from the UTXO's construction history.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2 — OP_CHECKSIGFROMSTACK: pop pubkey, message, sig — verify — push&amp;nbsp;result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 01                                         |  &amp;lt;- TRUE
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OP_CSFS pops top-first: internal key, then message, then sig. Executes BIP340 Schnorr verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verify: Schnorr(sig=5a5a5107...a1fdd37,
                msg=c4820b82...313ebc,
                pub=ff1f9fa3...9986b8)
result: VALID -&amp;gt; push 01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Script terminates with a single truthy value. The spend is valid.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why the witness has two items instead of&amp;nbsp;three
&lt;/h3&gt;

&lt;p&gt;In the standalone CSFS experiment, the spender supplies &lt;code&gt;[sig, message, pubkey]&lt;/code&gt; — three explicit items. The pubkey is caller-supplied: whoever constructs the reveal transaction chooses which key to present. This works fine for verifying against an external key like an oracle or a co-signer, but it means the script has no binding to the UTXO's specific identity.&lt;/p&gt;

&lt;p&gt;IK+CSFS replaces that third item with the execution context. The spender cannot choose a different pubkey. They cannot substitute a key that matches their own private key unless their private key happens to be the TapTree’s internal key. The authorization is not externally presented — it is structurally enforced by the UTXO’s Taproot construction.&lt;/p&gt;

&lt;p&gt;The practical pattern: a cold key holder pre-signs authorization tickets &lt;code&gt;(sig, message)&lt;/code&gt; offline. A hot-side executor broadcasts those tickets without ever holding the cold private key. The script enforces that only the internal key holder's signatures are valid — and the internal key is locked into the UTXO at creation time, not declared at spend time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Address Reconstruction with RootScope
&lt;/h2&gt;

&lt;p&gt;The tapscript is two bytes: &lt;code&gt;cb&lt;/code&gt; (OP_INTERNALKEY) and &lt;code&gt;cc&lt;/code&gt; (OP_CHECKSIGFROMSTACK). The derivation chain is identical to any other single-leaf TapTree.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Derivation Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TapLeaf hash = tagged_hash("TapLeaf",  0xc0 || 0x02 || 0xcb || 0xcc)
               (leaf version 0xc0, script length 2, script bytes)
Merkle root  = TapLeaf hash
tweak t      = tagged_hash("TapTweak",  internal_pubkey || merkle_root)
output key Q = internal_pubkey + t*G
P2TR address = Bech32m("tb", Q.x_only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;script_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xcb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xcc&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;tapleaf_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xc0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merkle_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tapleaf_hash&lt;/span&gt;
&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_x_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tweak_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_p2tr_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Expected: tb1p...  [OK]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same &lt;code&gt;internal_pubkey&lt;/code&gt; (&lt;code&gt;ff1f9fa3...9986b8&lt;/code&gt;) from all four solo experiments. Same key, two-byte tapscript, different Merkle root, different TapTweak, different address.&lt;/p&gt;

&lt;h2&gt;
  
  
  RootScope Visual&amp;nbsp;Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internal Key
  ff1f9fa3...9986b8  &amp;lt;----- also the key OP_INTERNALKEY pushes at runtime
        |
    TapLeaf (0xc0)
    script: cb cc  (2 bytes)
               ^--- OP_INTERNALKEY pushes this key from context
        |
    TapLeaf Hash
        |   (no siblings -&amp;gt; single-leaf tree)
    Merkle Root
        |
    TapTweak
        |
    Output Key Q  (parity: odd -&amp;gt; control block byte = c1)
        |
    P2TR Address
    tb1p...  [OK]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The control block opens with &lt;code&gt;c1&lt;/code&gt;, not &lt;code&gt;c0&lt;/code&gt; as in the CAT, CSFS, and CTV experiments. The different parity reflects a different TapTweak value — this two-byte script produces a different Merkle root than any of the 34-byte or 1-byte scripts, and the tweak lands on an odd-parity point.&lt;/p&gt;

&lt;p&gt;There is a deeper connection here worth making explicit. RootScope performs forward derivation: given &lt;code&gt;P&lt;/code&gt; and the script, compute &lt;code&gt;Q&lt;/code&gt;. The Taproot verifier during control block processing performs the same computation in reverse: given &lt;code&gt;Q&lt;/code&gt; from the UTXO and candidate &lt;code&gt;P&lt;/code&gt; from the control block, verify that &lt;code&gt;P → Q&lt;/code&gt; holds under the same formula. The math is identical; only what is known and what is being verified differs.&lt;/p&gt;

&lt;p&gt;This is why OP_INTERNALKEY can be trusted. It does not reach into the witness and grab a key — it surfaces a value that was already validated by the control block check before script execution began. RootScope’s derivation chain is not just an address verification tool; it is a visualization of the trust chain that makes OP_INTERNALKEY meaningful. The key it pushes onto the stack is the same key that satisfies &lt;code&gt;Q = P + t·G&lt;/code&gt; for this specific UTXO. That relationship, computed forward by RootScope and verified backward by the node, is the foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where IK+CSFS Sits in the&amp;nbsp;Series
&lt;/h2&gt;

&lt;p&gt;The four solo experiments each isolated one opcode. The combo experiments start combining them. This is the first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fga7rec86ilmzvnwh9gey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fga7rec86ilmzvnwh9gey.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The progression from CSFS alone to IK+CSFS is the progression from “someone signed this” to “the owner signed this.” OP_INTERNALKEY does one thing: it replaces a witness-supplied public key with the UTXO-bound Taproot identity. That single substitution turns a generic signature check into an ownership-gated authorization.&lt;/p&gt;

&lt;p&gt;Next combo: CAT + CSFS — building the signed message dynamically from stack components rather than pre-committing it at script creation time.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Boundary Condition: Signature Replay
&lt;/h2&gt;

&lt;p&gt;This experiment uses a fixed message constant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MESSAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authorized:group_combo_ik_csfs:v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once a successful reveal transaction is on-chain, the &lt;code&gt;(sig, message)&lt;/code&gt; pair is permanently visible. If a second UTXO existed with the same &lt;code&gt;cbcc&lt;/code&gt; script and the same internal key, that pair could be replayed against it — no private key required. The control block would validate, OP_INTERNALKEY would push the same &lt;code&gt;P&lt;/code&gt;, and OP_CSFS would verify the same signature against the same message. The spend would pass.&lt;/p&gt;

&lt;p&gt;This is not a flaw in IK+CSFS as a primitive. It is a consequence of using a fixed message in a contract that does not bind the authorization to a specific UTXO. The fix is straightforward: include UTXO-specific data in the message — the txid, vout, amount, or destination — so each signature is only valid for one spend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MESSAGE = SHA256(txid || vout || amount || destination_scriptpubkey)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is precisely what the next experiment addresses. CAT + CSFS composes the message dynamically at execution time from stack components. Each signature becomes specific to the spend it authorizes. The replay window closes. IK+CSFS establishes &lt;em&gt;who&lt;/em&gt; can authorize; CAT+CSFS establishes &lt;em&gt;what&lt;/em&gt; is being authorized and binds the two together.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We&amp;nbsp;Built
&lt;/h2&gt;

&lt;p&gt;We ran the first combo experiment: a two-byte tapscript that pairs OP_INTERNALKEY with OP_CHECKSIGFROMSTACK. The design is minimal — OP_INTERNALKEY pushes the UTXO’s internal key, then OP_CSFS verifies that the message &lt;code&gt;SHA256("authorized:group_combo_ik_csfs:v1")&lt;/code&gt; was signed by that key. The witness supplies only the signature and the message. The pubkey never appears in the witness; it arrives from the Taproot execution context.&lt;/p&gt;

&lt;p&gt;We built the TapTree with &lt;code&gt;btcaaron&lt;/code&gt;'s &lt;code&gt;build_script(OP_INTERNALKEY, OP_CHECKSIGFROMSTACK)&lt;/code&gt;, assembled the two-byte leaf, and derived the Signet address. The witness was constructed with &lt;code&gt;_make_witness()&lt;/code&gt;, which signs the message offline using the same key that controls the TapTree's internal key path. The spend used&amp;nbsp;&lt;code&gt;.unlock_with([sig, message])&lt;/code&gt; — two items, no pubkey.&lt;/p&gt;

&lt;p&gt;Both transactions confirmed on Signet. The trace file records the exact witness bytes. The stack execution is three steps: load two witness items, push the internal key via OP_INTERNALKEY, verify and push TRUE via OP_CSFS.&lt;/p&gt;

&lt;p&gt;RootScope confirmed the address derivation: same internal key as all prior experiments, two-byte tapscript, different Merkle root, different output key, different address. The control block opens with &lt;code&gt;c1&lt;/code&gt; rather than &lt;code&gt;c0&lt;/code&gt; — the odd-parity outcome of this specific tweak combination.&lt;/p&gt;

&lt;p&gt;The real applications of this combination emerge in multi-leaf trees and vault constructions. A leaf that uses IK+CSFS alongside a CTV leaf can enforce both who authorized a spend and exactly where the funds go, with neither condition visible from the P2TR address alone.&lt;/p&gt;

&lt;p&gt;Run the future. Execute first. Formalize later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Evidence Index
&lt;/h2&gt;

&lt;p&gt;Everything needed to reproduce this experiment end-to-end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Script hex        cbcc
                  (cb = OP_INTERNALKEY, cc = OP_CHECKSIGFROMSTACK)

Internal pubkey   ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8
Message           c4820b82d8856e9d8df36a9138d20149a9cd2f5b83d2e33d1b7e026abc313ebc
                  (SHA256("authorized:group_combo_ik_csfs:v1"))
Commit TxID       9930e922036a80d04a96a4b08f15838bcb880ce2a4be91da0b24af1484e10ea8
Reveal TxID       8d0b2156e9425afe64cabf3c906da255b6b86c51cb8968f828d5253fc261dd8f
Control block     c1ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8
                  (c1 = leaf version 0xc0 + odd parity)
Experiment code   experiments/group_opcodes/code/combo_ik_csfs.py
Trace output      experiments/group_opcodes/outputs/combo_ik_csfs_trace.json
Network           Signet | Bitcoin Inquisition 29.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bitcoin Inquisition: &lt;a href="https://github.com/bitcoin-inquisition/" rel="noopener noreferrer"&gt;github.com/bitcoin-inquisition&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;btcaaron toolkit: &lt;a href="https://github.com/aaron-recompile/btcaaron" rel="noopener noreferrer"&gt;github.com/aaron-recompile/btcaaron&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RootScope visualizer: &lt;a href="https://github.com/aaron-recompile/rootscope" rel="noopener noreferrer"&gt;github.com/aaron-recompile/rootscope&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Signet | Bitcoin Inquisition 29.2 | Commit *&lt;code&gt;[*9930e922...0ea8*](https://mempool.space/signet/tx/9930e922036a80d04a96a4b08f15838bcb880ce2a4be91da0b24af1484e10ea8?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Reveal *&lt;code&gt;[*8d0b2156...dd8f*](https://mempool.space/signet/tx/8d0b2156e9425afe64cabf3c906da255b6b86c51cb8968f828d5253fc261dd8f?showDetails=true)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/04f0440557bc" rel="noopener noreferrer"&gt;March 21, 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/op-internalkey-op-checksigfromstack-on-signet-identity-bound-authorization-04f0440557bc" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
    </item>
    <item>
      <title>OP_CHECKTEMPLATEVERIFY on Signet — Locking Outputs at UTXO Creation Time</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:49 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/opchecktemplateverify-on-signet-locking-outputs-at-utxo-creation-time-33ph</link>
      <guid>https://dev.to/aaron_recompile/opchecktemplateverify-on-signet-locking-outputs-at-utxo-creation-time-33ph</guid>
      <description>&lt;p&gt;With OP_CAT you assemble data. With OP_CSFS you authorize it. With OP_CTV you enforce it.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo685694bq108ahdlk7pz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo685694bq108ahdlk7pz.png"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitcoin is not designed top-down.  
It is discovered through execution.

"Run the Future" — starts here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Constraining the Future, Not the&amp;nbsp;Present
&lt;/h2&gt;

&lt;p&gt;OP_CHECKSIG proves that the &lt;em&gt;current&lt;/em&gt; key holder authorized &lt;em&gt;this&lt;/em&gt; spend. OP_CSFS proves that &lt;em&gt;someone&lt;/em&gt; authorized &lt;em&gt;some statement&lt;/em&gt;. &lt;strong&gt;OP_CHECKTEMPLATEVERIFY (BIP119)&lt;/strong&gt; does something different from both: it proves that the &lt;em&gt;spending transaction itself&lt;/em&gt; matches a template committed at UTXO creation time.&lt;/p&gt;

&lt;p&gt;The script does not look at a witness. It does not verify a signature. It computes a hash over the spending transaction’s structure — version, locktime, input count, sequences, output count, outputs — and checks whether that hash matches the one baked into the locking script. If the outputs deviate by a single satoshi or a single byte, the hash fails and the spend is rejected.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;output covenant by pre-commitment&lt;/strong&gt;. The creator of the UTXO decides, at funding time, exactly where and how much the money can go. The spender has no discretion over the outputs; they can only construct a transaction that reproduces the template exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BIP119&lt;/strong&gt; assigns opcode &lt;code&gt;0xb3&lt;/code&gt; to OP_CTV. On standard nodes &lt;code&gt;0xb3&lt;/code&gt; is OP_NOP4 — it does nothing, so any spend passes. &lt;strong&gt;Bitcoin Inquisition 29.2&lt;/strong&gt; activates BIP119 on Signet, turning OP_NOP4 into the real constraint. Same soft fork upgrade pattern as OP_CAT (BIP347, &lt;code&gt;0x7e&lt;/code&gt;) and OP_CSFS (BIP348, &lt;code&gt;0xcc&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This post runs a minimal CTV experiment on Signet: commit to a template that sends exactly 49,500 sats to a fixed address, then spend by providing an empty witness and a matching transaction. The spending transaction proves itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CTV&amp;nbsp;Contract
&lt;/h2&gt;

&lt;p&gt;The locking script is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_PUSHBYTES_32 &amp;lt;template_hash&amp;gt;
OP_NOP4                          &amp;lt;- OP_CTV on Inquisition (0xb3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template hash commits to the spending transaction structure. At spend time, the script:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Pushes &lt;code&gt;&amp;lt;template_hash&amp;gt;&lt;/code&gt; onto the stack (from the script itself)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OP_CTV pops it, computes &lt;code&gt;DefaultCheckTemplateVerifyHash(spending_tx, input_index)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Checks: &lt;code&gt;computed_hash == template_hash&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If equal, script succeeds&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The witness is empty.&lt;/strong&gt; The spender provides no data — no signature, no preimage, no pubkey. The spending transaction itself is the proof. This is the fundamental contrast with every other script we have seen in this series:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_CAT experiment:
  witness  -&amp;gt; ["hello", "world"]     (data for stack computation)
  script   -&amp;gt; OP_CAT OP_SHA256 &amp;lt;hash&amp;gt; OP_EQUAL

OP_CSFS experiment:
  witness  -&amp;gt; [sig, message, pubkey] (authorization proof)
  script   -&amp;gt; OP_CHECKSIGFROMSTACK

OP_CTV experiment:
  witness  -&amp;gt; []                     (nothing)
  script   -&amp;gt; &amp;lt;template_hash&amp;gt; OP_CTV (spending tx proves itself)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Template Hash: What Gets Committed
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DefaultCheckTemplateVerifyHash&lt;/code&gt; is defined in BIP119 as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHA256(
  nVersion          (4 bytes, little-endian)
  nLockTime         (4 bytes, little-endian)
  -- scriptSigs hash omitted for native segwit (all empty) --
  nInputs           (4 bytes, little-endian)
  SHA256(sequences) (32 bytes)
  nOutputs          (4 bytes, little-endian)
  SHA256(outputs)   (32 bytes)
  input_index       (4 bytes, little-endian)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For our experiment, the committed values are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nVersion          = 2
nLockTime         = 0
nInputs           = 1
sequences         = [0xffffffff]
nOutputs          = 1
outputs           = [49500 sats -&amp;gt; tb1p32g0c5...wsxlfd5s]
input_index       = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Computed template hash: &lt;code&gt;f4ceec28ec225e022e059355819726668dca411b9239ee4ef65379dc7bd2d8e08&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Every field that could affect the spending transaction’s character is committed. The output address and amount cannot change. The sequence cannot change. The version and locktime cannot change. The only free variable is the input itself — which is implicitly determined by spending the committed UTXO.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contract&amp;nbsp;Design
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;At UTXO creation time:
  compute template_hash = SHA256(version=2 || locktime=0 ||
                                 nInputs=1 || SHA256(seq=0xffffffff) ||
                                 nOutputs=1 || SHA256(49500 sats to tb1p32g0c5...wsxlfd5s) ||
                                 input_index=0)
                        = f4ceec28...d8e08


Lock script:
  OP_PUSHBYTES_32 f4ceec28...d8e08
  OP_NOP4  (= OP_CTV on Inquisition)
At spend time:
  witness  = []   &amp;lt;- empty
  spending tx must reproduce:
    version=2, locktime=0, sequence=0xffffffff,
    exactly one output: 49500 sats -&amp;gt; tb1p32g0c5...wsxlfd5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tapscript bytecode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20     &amp;lt;- OP_PUSHBYTES_32
f4ceec28ec225e022e059355819726668dca411b9239ee4ef65379dc7bd2d8e08
b3     &amp;lt;- OP_NOP4 / OP_CTV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Commit Phase: Building the Taproot Address with&amp;nbsp;btcaaron
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;btcaaron&lt;/code&gt; provides &lt;code&gt;inq_ctv_program_for_output()&lt;/code&gt; which handles the template hash computation and TapTree construction in a single call. The caller provides the destination output; the function derives everything else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FUND_SATS&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50_000&lt;/span&gt;
&lt;span class="n"&gt;FEE_SATS&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;span class="n"&gt;OUTPUT_SATS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FUND_SATS&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;FEE_SATS&lt;/span&gt;   &lt;span class="c1"&gt;# 49_500、
&lt;/span&gt;
&lt;span class="n"&gt;change_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;default_change_address&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;script_pubkey_hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc_wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getaddressinfo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scriptPubKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inq_ctv_program_for_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;internal_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output_sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OUTPUT_SATS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output_script_pubkey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;script_pubkey_hex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron API Highlights
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**inq_ctv_program_for_output(internal_key, output_sats, output_script_pubkey, network, sequence)**&lt;/code&gt; wraps the full CTV setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Serializes the output: &lt;code&gt;value (8 bytes LE) || compact_size(len(scriptpubkey)) || scriptpubkey&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Computes &lt;code&gt;DefaultCheckTemplateVerifyHash&lt;/code&gt; over the template fields above&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Builds the tapscript: &lt;code&gt;OP_PUSHBYTES_32 &amp;lt;hash&amp;gt; OP_NOP4&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Constructs a single-leaf TapTree and derives the Bech32m address&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Returns &lt;code&gt;(program, template_hash)&lt;/code&gt; — the program carries the spend builder, the hash is for inspection&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The caller must save &lt;code&gt;change_addr&lt;/code&gt; to disk — it is needed at spend time to reconstruct the exact same template hash. If &lt;code&gt;change_addr&lt;/code&gt; changes, the template hash changes, and the spend fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Funding the&amp;nbsp;Address
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fund_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FUND_TXID_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fund_sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FUND_SATS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reveal Phase: Spending with an Empty&amp;nbsp;Witness
&lt;/h2&gt;

&lt;p&gt;The spend is architecturally the simplest of the three experiments. The spender provides no data. They only need to reconstruct the identical template (same &lt;code&gt;change_addr&lt;/code&gt;, same amounts) and submit a matching transaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_build_ctv_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_utxo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT_SATS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock_with&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;          &lt;span class="c1"&gt;# &amp;lt;- empty: no witness data
&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reveal_txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;broadcast_or_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;**.unlock_with([])**&lt;/code&gt; passes an empty list. The final witness contains only two items: the tapscript and the control block. There is nothing before them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**.sequence(0xFFFFFFFF)**&lt;/code&gt; must be set explicitly to match the committed template. Any other sequence value produces a different hash and fails the CTV check.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**.to(change_addr, OUTPUT_SATS)**&lt;/code&gt; must reproduce the exact scriptpubkey committed in the template. Amount and address must be identical.&lt;/p&gt;

&lt;p&gt;The final witness layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0] = 20 f4ceec28...d8e08 b3     &amp;lt;- tapscript (34 bytes)
Witness[1] = c0 ff1f9fa3...9986b8       &amp;lt;- control block (33 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Witness[2], no Witness[3]. Two items total.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transactions, OP_NOP4, and Stack&amp;nbsp;Trace
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Commit: what lands&amp;nbsp;on-chain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[d083e038...69b6681f](https://mempool.space/signet/tx/d083e038393afb711a6f8c11499131e4f856bb3fae898b74e43aa3bb69b6681f?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INPUT
  tb1pqzxvhv...lsycut3t    1.99999816 sBTC  (key-path spend, 64-byte Schnorr sig)

OUTPUTS
  tb1pg5quycyy...yspzja7g  1.99949661 sBTC  &amp;lt;- change
  tb1pvqhal44...5q6g6avk   0.00050000 sBTC  &amp;lt;- CTV lock UTXO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CTV lock output &lt;code&gt;tb1pvqhal44...5q6g6avk&lt;/code&gt; is a standard P2TR address. No observer can distinguish it from a key-path address. Its previous output script (visible in the reveal tx) confirms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Previous output script:
  OP_PUSHNUM_1
  OP_PUSHBYTES_32  602fdfd6...042728    &amp;lt;- P2TR output key
Previous output type: V1_P2TR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reveal: witness data and stack execution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[2789983e...86a244db](https://mempool.space/signet/tx/2789983e7f01997ef1fa04a718ea83d388e813cd2ded3116a5acb1a986a244db?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timestamp  : 2026-03-15 23:47:15 UTC
Fee        : 500 sats  (4.46 sat/vB)
Features   : SegWit | Taproot  (no RBF -- see below)

INPUT
  tb1pvqhal44...5q6g6avk   0.00050000 sBTC
OUTPUT
  tb1p32g0c5...wsxlfd5s    0.00049500 sBTC  (V1_P2TR)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The witness as shown by mempool.space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0]  20f4ceec28ec225e022e059355819726668dca411b9239ee4ef65379dc7bd2d8e08b3
            &amp;lt;- tapscript: OP_PUSHBYTES_32 &amp;lt;template_hash&amp;gt; OP_NOP4

Witness[1]  c0ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8
            &amp;lt;- control block (leaf version 0xc0 + internal pubkey)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two witness items. Nothing else.&lt;/p&gt;

&lt;p&gt;P2TR tapscript decoded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_PUSHBYTES_32  f4ceec28ec225e022e059355819726668dca411b9239ee4ef65379dc7bd2d8e08
OP_NOP4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The OP_NOP4 label — same pattern, different opcode
&lt;/h2&gt;

&lt;p&gt;mempool.space decodes &lt;code&gt;0xb3&lt;/code&gt; and displays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P2TR tapscript   OP_PUSHBYTES_32 f4ceec28...d8e08  OP_NOP4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0xb3&lt;/code&gt; = 179 decimal. On standard nodes, opcode 179 is OP_NOP4 — a no-op that leaves the stack unchanged. Under BIP342, NOPs in Tapscript are treated differently from OP_SUCCESS: they do not cause immediate pass, but they also do not fail. A standard node executes &lt;code&gt;OP_PUSHBYTES_32 &amp;lt;hash&amp;gt;&lt;/code&gt; (stack now has one item) and then &lt;code&gt;OP_NOP4&lt;/code&gt; (does nothing), leaving the hash on the stack. The script terminates with a non-empty stack — which in Tapscript counts as success. So old nodes accept this spend without ever checking the template.&lt;/p&gt;

&lt;p&gt;On Bitcoin Inquisition 29.2 with BIP119 active, the same &lt;code&gt;0xb3&lt;/code&gt; is OP_CTV. It pops the hash from the stack, computes &lt;code&gt;DefaultCheckTemplateVerifyHash&lt;/code&gt; from the spending transaction, and checks equality. Only a transaction with the exact matching outputs passes.&lt;/p&gt;

&lt;p&gt;This is subtly different from the CSFS case. OP_CSFS (&lt;code&gt;0xcc&lt;/code&gt;) is an OP_SUCCESS opcode — old nodes immediately pass without executing anything. OP_NOP4 (&lt;code&gt;0xb3&lt;/code&gt;) is a NOP — old nodes execute the full script but do nothing at the NOP step. The end result is the same (old nodes always accept), but the upgrade mechanism differs. BIP119 could have chosen OP_SUCCESS for a cleaner soft fork; instead it chose a NOP to preserve the property that old nodes still execute and validate the non-CTV parts of the script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack execution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Executing&lt;/strong&gt;: &lt;code&gt;OP_PUSHBYTES_32 &amp;lt;template_hash&amp;gt; OP_NOP4/OP_CTV&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Taproot interpreter strips Witness&lt;a href="https://dev.toscript"&gt;0&lt;/a&gt; and Witness&lt;a href="https://dev.tocontrol%20block"&gt;1&lt;/a&gt;. The remaining witness items become the initial execution stack — and there are none.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial State: empty&amp;nbsp;stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
|                  (empty)                   |
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No witness data. The spending transaction is implicitly available to OP_CTV, but it is not on the stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — OP_PUSHBYTES_32: push template hash from&amp;nbsp;script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| f4ceec28...d8e08                           |  &amp;lt;- template_hash (32 bytes)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script itself supplies the committed hash. The spender provided nothing — this value came from the locking script, fixed at UTXO creation time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — OP_CTV (0xb3): pop hash, verify spending tx, push&amp;nbsp;result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 01                                         |  &amp;lt;- TRUE
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OP_CTV pops &lt;code&gt;f4ceec28...d8e08&lt;/code&gt; and computes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultCheckTemplateVerifyHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spending_tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
             &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;          &lt;span class="n"&gt;nLockTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
             &lt;span class="n"&gt;nInputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;          &lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mh"&gt;0xffffffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="n"&gt;nOutputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;         &lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;49500&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;tb1p32g0c5&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;wsxlfd5s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="n"&gt;input_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
           &lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f4ceec28&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;d8e08&lt;/span&gt;

&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;template_hash&lt;/span&gt;  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TRUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The spending transaction’s structure exactly reproduces the committed template. Script terminates with TRUE. Spend is valid.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why nSequence = 0xffffffff and no&amp;nbsp;RBF
&lt;/h2&gt;

&lt;p&gt;The reveal transaction features show &lt;strong&gt;no RBF flag&lt;/strong&gt;. This is not an accident.&lt;/p&gt;

&lt;p&gt;nSequence = &lt;code&gt;0xffffffff&lt;/code&gt; is committed in the template hash. If you tried to replace the transaction (RBF) with a different fee by changing the fee rate, you would need to either reduce the output amount or add a second input. Reducing the output amount changes the outputs hash — the CTV check fails. Adding a second input changes nInputs — the CTV check fails. There is no fee-bumping path that leaves the template hash intact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CPFP is the practical solution.&lt;/strong&gt; After the reveal transaction confirms and sends 49,500 sats to &lt;code&gt;tb1p32g0c5...wsxlfd5s&lt;/code&gt;, that output can spend those sats with a high-fee child transaction. Miners who see the child's fee incentive will mine the parent (reveal) too. This is why the Delving Bitcoin post noted: &lt;em&gt;"CTV: parent template constraints make direct replacement less flexible; CPFP is the practical accelerator."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the experiment, we set the fee to 500 sats and waited for natural confirmation. At 4.46 sat/vB on Signet, this took hours — the EXPERIMENT_MATRIX records a reveal-to-confirm window of ~338,814 seconds (~3.9 days) for the earlier CTV run. For production CTV vaults, fee planning at template creation time is critical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Address Reconstruction with RootScope
&lt;/h2&gt;

&lt;p&gt;The tapscript is 34 bytes: one &lt;code&gt;OP_PUSHBYTES_32&lt;/code&gt; (0x20), the 32-byte template hash, and &lt;code&gt;OP_NOP4&lt;/code&gt; (0xb3).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Derivation Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TapLeaf&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mh"&gt;0xc0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mh"&gt;0x22&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mh"&gt;0x20&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;template_hash&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mh"&gt;0xb3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;leaf&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OP_PUSHBYTES_32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OP_NOP4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Merkle&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TapLeaf&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;
&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;
&lt;span class="n"&gt;P2TR&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Bech32m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x_only&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;template_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;f4ceec28ec225e022e059355819726668dca411b9239ee4ef65379dc7bd2d8e08&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;script_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;template_hash&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xb3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;tapleaf_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xc0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merkle_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tapleaf_hash&lt;/span&gt;
&lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_x_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tweak_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_p2tr_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Expected: tb1pvqhal44...5q6g6avk  [OK]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: the same &lt;code&gt;internal_pubkey&lt;/code&gt; (&lt;code&gt;ff1f9fa3...9986b8&lt;/code&gt;) appears in all three experiments. It is the x-only pubkey of &lt;code&gt;key = Key.from_wif(DEMO_KEY_WIF)&lt;/code&gt;, the key that controls all three TapTree key paths. The three experiments share one key but embed completely different tapscripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  RootScope Visual&amp;nbsp;Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internal Key
  ff1f9fa3...9986b8
        |
    TapLeaf (0xc0)
    script: 20 f4ceec28...d8e08 b3  (34 bytes)
        |
    TapLeaf Hash
        |   (no siblings -&amp;gt; single-leaf tree)
    Merkle Root
        |
    TapTweak
        |
    Output Key Q
        |
    P2TR Address
    tb1pvqhal44...5q6g6avk   [OK]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Series Complete: CAT, CSFS,&amp;nbsp;CTV
&lt;/h2&gt;

&lt;p&gt;This is the third and final post in the series. All three opcodes are now confirmed on Signet.&lt;/p&gt;

&lt;p&gt;Opcode BIP Legacy opcode What it constrains Witness Stack at start OP_CAT 347 OP_SUCCESS Stack composition [part1, part2] 2 items from witness OP_CSFS 348 OP_SUCCESS Signature scope [sig, message, pubkey] 3 items from witness OP_CTV 119 OP_NOP4 Output template [] empty&lt;/p&gt;

&lt;p&gt;The three opcodes represent three different answers to the question “what should Bitcoin Script be able to prove?”:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CAT&lt;/strong&gt; says: the stack should be able to assemble arbitrary byte strings. It gives Script a serialization primitive. Without CAT, you cannot construct dynamic messages or concatenate transaction fields. With CAT, you can build any 32-byte value from components available on the stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSFS&lt;/strong&gt; says: Script should be able to verify that a specific key holder pre-authorized a specific statement. It decouples authorization from the spending transaction. Without CSFS, a key can only authorize one transaction. With CSFS, a key can authorize any statement — an oracle reading, an output template, a channel state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CTV&lt;/strong&gt; says: Script should be able to constrain the spending transaction to a fixed template, without requiring any runtime authorization. It is deterministic covenant enforcement. Without CTV, you need a signature to authorize every spend. With CTV, the UTXO is self-enforcing — anyone can spend it, but only to the committed destination.&lt;/p&gt;

&lt;p&gt;The three together form a covenant design space. A vault that allows emergency recovery, time-locked inheritance, and fee optimization might use all three: CAT to serialize the output template at runtime, CSFS to verify the vault manager’s pre-authorization, and CTV to enforce the final destination unconditionally.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We&amp;nbsp;Built
&lt;/h2&gt;

&lt;p&gt;We constructed a CTV output that commits exactly 49,500 sats to a fixed destination. The locking script is 34 bytes: a pushed template hash followed by OP_NOP4/OP_CTV. The spending witness is empty — the transaction structure is its own proof.&lt;/p&gt;

&lt;p&gt;We used &lt;code&gt;btcaaron&lt;/code&gt;'s &lt;code&gt;inq_ctv_program_for_output()&lt;/code&gt; to derive the template hash and build the TapTree in one call. The spend used&amp;nbsp;&lt;code&gt;.unlock_with([])&lt;/code&gt; and&amp;nbsp;&lt;code&gt;.sequence(0xFFFFFFFF)&lt;/code&gt; — no data supplied, but the transaction structure must match exactly. RootScope verified the address reconstruction: same internal key as the CAT and CSFS experiments, different 34-byte tapscript, different output key, different address.&lt;/p&gt;

&lt;p&gt;The OP_NOP4 label in mempool.space reflects a different soft fork path than CSFS’s OP_SUCCESS: old nodes execute the full script but treat the NOP as inert, leaving the hash on the stack and accepting. Inquisition nodes enforce the actual CTV semantics. Both paths lead to old-node acceptance — only upgraded miners can reject an invalid template spend.&lt;/p&gt;

&lt;p&gt;The reveal transaction has no RBF flag and no viable fee-bumping path short of CPFP. This is not an implementation detail; it is a direct consequence of the template hash committing to nSequence and outputs. Fee planning belongs at UTXO creation time, not at spend time.&lt;/p&gt;

&lt;p&gt;All three transactions — CAT, CSFS, CTV — are permanently confirmed on Signet and reproducible end-to-end using the tools below.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bitcoin Inquisition: &lt;a href="https://github.com/bitcoin-inquisition/" rel="noopener noreferrer"&gt;github.com/bitcoin-inquisition&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;btcaaron toolkit: &lt;a href="https://github.com/aaron-recompile/btcaaron" rel="noopener noreferrer"&gt;github.com/aaron-recompile/btcaaron&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RootScope visualizer: &lt;a href="https://github.com/aaron-recompile/rootscope" rel="noopener noreferrer"&gt;github.com/aaron-recompile/rootscope&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Signet | Bitcoin Inquisition 29.2 | Commit *&lt;code&gt;[*d083e038...69b6681f*](https://mempool.space/signet/tx/d083e038393afb711a6f8c11499131e4f856bb3fae898b74e43aa3bb69b6681f?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Reveal *&lt;code&gt;[*2789983e...86a244db*](https://mempool.space/signet/tx/2789983e7f01997ef1fa04a718ea83d388e813cd2ded3116a5acb1a986a244db?showDetails=true)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/1d623fbe3899" rel="noopener noreferrer"&gt;March 19, 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/op-checktemplateverify-on-signet-locking-outputs-at-utxo-creation-time-1d623fbe3899" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
    </item>
    <item>
      <title>OP_CHECKSIGFROMSTACK on Signet — Sign Anything, Verify on Stack</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:48 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/opchecksigfromstack-on-signet-sign-anything-verify-on-stack-4k08</link>
      <guid>https://dev.to/aaron_recompile/opchecksigfromstack-on-signet-sign-anything-verify-on-stack-4k08</guid>
      <description>&lt;p&gt;Satoshi disabled OP_CAT in 2010. OP_CSFS was never even enabled. We ran it on-chain in 2026.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04uma64qttbp3g14tl61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04uma64qttbp3g14tl61.png"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitcoin is not designed top-down.  
It is discovered through execution.

"Run the Future" — starts here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Decoupling the Signature from the Transaction
&lt;/h2&gt;

&lt;p&gt;Every Bitcoin signature you have ever seen signs the same thing: a hash of the current transaction. OP_CHECKSIG, OP_CHECKMULTISIG, Schnorr — they all implicitly bind the signature to the spending transaction. The message is not a parameter; it is fixed by consensus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OP_CHECKSIGFROMSTACK&lt;/strong&gt; (BIP348) breaks that assumption. It takes three explicit stack items — a signature, a message, and a public key — and verifies the signature over that arbitrary message. The message can be anything: a SHA256 digest of a string, a serialized output template, a price oracle feed, a cross-input commitment. The transaction is irrelevant.&lt;/p&gt;

&lt;p&gt;This changes what Bitcoin Script can express. OP_CHECKSIG can only attest that the signer authorized &lt;em&gt;this&lt;/em&gt; spend. OP_CSFS can attest that the signer authorized &lt;em&gt;any statement&lt;/em&gt;, and that statement can be checked against other stack values produced by introspection. Combined with OP_CAT (previous post), it becomes possible to serialize transaction fields, hash them, sign them in advance, and enforce the result as a covenant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BIP348&lt;/strong&gt; assigns opcode &lt;code&gt;0xcc&lt;/code&gt; to OP_CHECKSIGFROMSTACK. &lt;strong&gt;Bitcoin Inquisition 29.2&lt;/strong&gt; activates it on Signet alongside BIP347 (OP_CAT) and BIP119 (OP_CTV), making all three available for combined experimentation.&lt;/p&gt;

&lt;p&gt;This post runs a minimal CSFS experiment on Signet: sign the digest of &lt;code&gt;"hello world"&lt;/code&gt; offline, embed the pubkey in the tapscript, and let the script verify the signature at spend time. The message has nothing to do with the transaction.&lt;/p&gt;

&lt;p&gt;One thing to note upfront: OP_CSFS reveal transactions are non-standard under legacy mempool policy. They will not appear in standard Signet mempools before confirmation. After Inquisition mines the block, the transaction becomes visible on public explorers. “Not in the mempool” is a policy observation, not a consensus rejection.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CSFS&amp;nbsp;Contract
&lt;/h2&gt;

&lt;p&gt;OP_CHECKSIGFROMSTACK in its simplest form is a single opcode. The script is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_CHECKSIGFROMSTACK   (0xcc)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire locking script. The spending witness must supply, in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[sig]      &amp;lt;- Schnorr signature over the committed message
[message]  &amp;lt;- the 32-byte message that was signed
[pubkey]   &amp;lt;- x-only public key of the signer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script pops all three and verifies. If the signature is valid over the message under the pubkey, the result is 1 (TRUE).&lt;/p&gt;

&lt;p&gt;Compare this to OP_CHECKSIG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_CHECKSIG behavior:
  message  = implicit (current tx sighash)
  pubkey   = from script
  sig      = from witness
  -&amp;gt; verifies sig(tx_hash, pubkey)

OP_CHECKSIGFROMSTACK behavior:
  message  = explicit (from witness stack)
  pubkey   = explicit (from witness stack)
  sig      = explicit (from witness stack)
  -&amp;gt; verifies sig(message, pubkey)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction is not in scope. This is the core design shift.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contract&amp;nbsp;Design
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Signer offline:
  message = SHA256("hello world")
           = b94d27b9...efcde9
  sig     = Schnorr.sign(secret_key, message)
           = ab69f346...76912

At spend time, witness provides:
  Witness[0] = sig       (64 bytes)
  Witness[1] = message   (32 bytes)
  Witness[2] = pubkey    (32 bytes)
Script (one byte):
  0xcc = OP_CHECKSIGFROMSTACK
       -&amp;gt; pops pubkey, message, sig
       -&amp;gt; verifies sig over message under pubkey
       -&amp;gt; pushes TRUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tapscript bytecode is exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cc   &amp;lt;- OP_CHECKSIGFROMSTACK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One byte. No hash commitment in the script itself — the pubkey alone identifies the authorized signer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Commit Phase: Building the Taproot Address with&amp;nbsp;btcaaron
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;btcaaron&lt;/code&gt; ships &lt;code&gt;inq_csfs_script()&lt;/code&gt; which returns the single-byte &lt;code&gt;0xcc&lt;/code&gt; tapscript. The commit phase is identical in structure to the CAT experiment: build a single-leaf TapTree, derive the address, fund it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MESSAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;leaf_script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inq_csfs_script&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_wif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEMO_KEY_WIF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tap_tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;TapTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;leaf_script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csfs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tap_tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron API Highlights
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**inq_csfs_script()**&lt;/code&gt; returns a &lt;code&gt;RawScript&lt;/code&gt; containing the single byte &lt;code&gt;0xcc&lt;/code&gt;. This is Inquisition's opcode assignment for OP_CHECKSIGFROMSTACK under BIP348. On a standard node the same byte is an OP_SUCCESS opcode — more on this below.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**TapTree(...).custom(script, label).build()**&lt;/code&gt; derives the P2TR address via the same TapLeaf -&amp;gt; Merkle root -&amp;gt; TapTweak -&amp;gt; output key chain described in the previous post. The script being one byte makes no difference to the derivation logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Funding the&amp;nbsp;Address
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc_wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sendtoaddress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tap_tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FUND_SATS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reveal Phase: Spending with an Offline Signature
&lt;/h2&gt;

&lt;p&gt;The reveal transaction constructs the witness by signing the message offline with the &lt;code&gt;secp256k1&lt;/code&gt; library, then submitting all three items — sig, message, pubkey — to the script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_make_witness&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wif_secret_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEMO_KEY_WIF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PrivateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schnorr_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MESSAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pub_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pubkey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;MESSAGE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;pub_x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csfs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_utxo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fee_sats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_make_witness&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;reveal_txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;broadcast_or_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron Spend&amp;nbsp;API
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**_make_witness()**&lt;/code&gt; produces three items in the order CSFS expects: signature first (deepest on stack), then message, then pubkey (topmost). The Taproot interpreter will load these as the initial execution stack before running the tapscript.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**pk.schnorr_sign(MESSAGE, "", raw=True)**&lt;/code&gt; signs the 32-byte message digest directly using BIP340 Schnorr. The second argument (&lt;code&gt;""&lt;/code&gt;) is the auxiliary randomness string; passing empty uses a deterministic nonce.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**.unlock_with([...])**&lt;/code&gt; injects all three items before the script and control block, exactly as in the CAT experiment. The final witness layout is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0] = ab69f346...76912      &amp;lt;- Schnorr sig (64 bytes)
Witness[1] = b94d27b9...efcde9     &amp;lt;- message = SHA256("hello world") (32 bytes)
Witness[2] = ff1f9fa3...9986b8     &amp;lt;- x-only pubkey (32 bytes)
Witness[3] = cc                    &amp;lt;- tapscript (OP_CHECKSIGFROMSTACK, 1 byte)
Witness[4] = c0ff1f9f...9986b8     &amp;lt;- control block (33 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Transactions, OP_RETURN_204, and Stack&amp;nbsp;Trace
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Commit: what lands&amp;nbsp;on-chain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[2d68a0e6...133803fe](https://mempool.space/signet/tx/2d68a0e621533c2f7391159f4c2e252f409c1cec0ec681ff9c6b11cb133803fe?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INPUT
  tb1p...                          0.00050000 sBTC  (funding source)

OUTPUTS
  tb1p822a0z...fqp6x5up            0.00050000 sBTC  &amp;lt;- CSFS lock UTXO
  (change)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output &lt;code&gt;tb1p822a0z...fqp6x5up&lt;/code&gt; is a standard P2TR address. No observer can tell that the script path is a single-byte OP_CSFS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reveal: what mempool.space shows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[cc1b6d35...85195f97](https://mempool.space/signet/tx/cc1b6d352f75348b6a52c7f5c68fc5caea2512423e08011e8f69a9bb85195f97?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INPUT
  tb1p822a0z...fqp6x5up            0.00050000 sBTC

OUTPUT
  tb1pghctez...es5gqyqw            0.00049500 sBTC  (V1_P2TR)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The witness data as shown by mempool.space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0]  ab69f3465e011994ae9d33650a75fa8b0d5f333d...76912
            &amp;lt;- Schnorr sig (64 bytes)

Witness[1]  b94d27b9934d3e08a52e52d7da7dabfac484efe3...efcde9
            &amp;lt;- SHA256("hello world") (32 bytes)
Witness[2]  ff1f9fa326a9438227e6aa25030ccf89bcb8ce53...9986b8
            &amp;lt;- x-only pubkey (32 bytes)
Witness[3]  cc
            &amp;lt;- tapscript
Witness[4]  c0 ff1f9fa3...9986b8
            &amp;lt;- control block (leaf version 0xc0 + internal pubkey)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The OP_RETURN_204 label — and what it actually&amp;nbsp;means
&lt;/h2&gt;

&lt;p&gt;mempool.space decodes the tapscript &lt;code&gt;cc&lt;/code&gt; and displays it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P2TR tapscript   OP_RETURN_204
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This label requires explanation. &lt;code&gt;0xcc&lt;/code&gt; = 204 decimal. On nodes that have &lt;em&gt;not&lt;/em&gt; activated BIP348, opcode 204 falls in the OP_SUCCESS range defined by BIP342 (opcodes 187–254 are OP_SUCCESS in Tapscript). An OP_SUCCESS opcode causes the tapscript to pass immediately — no further evaluation. So a non-upgraded node reads &lt;code&gt;cc&lt;/code&gt;, recognizes it as OP_SUCCESS, and marks the script valid without checking the signature at all.&lt;/p&gt;

&lt;p&gt;On Bitcoin Inquisition 29.2, which has BIP348 activated, the same byte &lt;code&gt;cc&lt;/code&gt; is interpreted as OP_CHECKSIGFROMSTACK. The node actually pops pubkey, message, and sig off the stack and performs the Schnorr verification.&lt;/p&gt;

&lt;p&gt;This is the standard soft fork upgrade mechanism. The new rule turns an “always pass” opcode into a real constraint. Miners running Inquisition enforce the new semantics; legacy nodes see OP_SUCCESS and accept the block because they have no rule to reject it. mempool.space, running standard software, therefore labels it &lt;code&gt;OP_RETURN_204&lt;/code&gt; — their display name for "opcode 204, meaning unknown." The word "RETURN" here is not OP_RETURN the opcode; it is a mempool.space naming convention for unrecognized opcodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack execution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Executing&lt;/strong&gt;: &lt;code&gt;OP_CHECKSIGFROMSTACK&lt;/code&gt; (single opcode, &lt;code&gt;0xcc&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;The Taproot interpreter strips Witness&lt;a href="https://dev.toscript"&gt;3&lt;/a&gt; and Witness&lt;a href="https://dev.tocontrol%20block"&gt;4&lt;/a&gt;, leaving Witness[0..2] as the execution stack:&lt;/p&gt;




&lt;h3&gt;
  
  
  Initial State: three witness items&amp;nbsp;loaded
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| ff1f9fa3...9986b8                          |  &amp;lt;- pubkey    (Witness[2], top)
| b94d27b9...efcde9                          |  &amp;lt;- message   (Witness[1])
| ab69f346...76912                           |  &amp;lt;- sig       (Witness[0], bottom)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three explicit inputs. The transaction itself is not on the stack and plays no role in what follows.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1 — OP_CHECKSIGFROMSTACK: pop pubkey, message, sig — verify — push&amp;nbsp;result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 01                                         |  &amp;lt;- TRUE
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OP_CSFS pops all three items (top-first: pubkey, then message, then sig) and runs BIP340 Schnorr verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verify: Schnorr(sig=ab69f346...76912,
                msg=b94d27b9...efcde9,
                pub=ff1f9fa3...9986b8)

result: VALID -&amp;gt; push 01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Script terminates with a single truthy value. The spend is valid.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why does the sig not cover the transaction?
&lt;/h3&gt;

&lt;p&gt;With OP_CHECKSIG, the signer must know the transaction being spent — the signature is computed over the sighash, which commits to inputs, outputs, and amounts. If the outputs change, the signature breaks.&lt;/p&gt;

&lt;p&gt;With OP_CSFS, the signer committed to &lt;code&gt;SHA256("hello world")&lt;/code&gt; — a message that has nothing to do with the transaction. The signature was computed before the transaction existed. This is the delegation primitive: a key holder can pre-authorize an action by signing a statement, and anyone who obtains that signature can construct a transaction that passes the CSFS check, regardless of what the outputs look like. Combined with OP_CTV (next post), you can constrain both the authorization (CSFS) and the outputs (CTV) simultaneously.&lt;/p&gt;




&lt;h2&gt;
  
  
  Address Reconstruction with RootScope
&lt;/h2&gt;

&lt;p&gt;The tapscript is a single byte: &lt;code&gt;0xcc&lt;/code&gt;. That makes the derivation maximally transparent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Derivation Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TapLeaf hash = tagged_hash("TapLeaf",  0xc0 || 0x01 || 0xcc)
               (leaf version 0xc0, script length 1, script byte 0xcc)
Merkle root  = TapLeaf hash
tweak t      = tagged_hash("TapTweak",  internal_pubkey || merkle_root)
output key Q = internal_pubkey + t*G
P2TR address = Bech32m("tb", Q.x_only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;script_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xcc&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;   &lt;span class="c1"&gt;# OP_CHECKSIGFROMSTACK, 1 byte
&lt;/span&gt;
&lt;span class="n"&gt;tapleaf_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xc0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merkle_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tapleaf_hash&lt;/span&gt;
&lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_x_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tweak_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_p2tr_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Expected: tb1p822a0z...fqp6x5up  [OK]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: the internal pubkey in the control block (&lt;code&gt;ff1f9fa3...9986b8&lt;/code&gt;) is the &lt;strong&gt;same&lt;/strong&gt; key provided in the witness as the CSFS pubkey. This is intentional for the demo — the same key controls both the Taproot key path and the CSFS verification. In production designs these would typically be different keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  RootScope Visual&amp;nbsp;Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internal Key
  ff1f9fa3...9986b8
        |
    TapLeaf (0xc0)
    script: cc  (1 byte)
        |
    TapLeaf Hash
        |   (no siblings -&amp;gt; single-leaf tree)
    Merkle Root
        |
    TapTweak
        |
    Output Key Q
        |
    P2TR Address
    tb1p822a0z...fqp6x5up   [OK]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one-byte script produces the same derivation depth as any other single-leaf tree. The address gives no indication of the script’s size or content.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where CSFS Sits in the CAT / CSFS / CTV&amp;nbsp;Series
&lt;/h2&gt;

&lt;p&gt;This is the second post in a three-part series. Each opcode in the series constrains a different layer of a transaction:&lt;/p&gt;

&lt;p&gt;Opcode BIP What it constrains Mechanism Post OP_CAT 347 Stack composition Concatenate two elements; enables serialization [1] OP_CSFS 348 Signature scope Verify a signature over any message, not just tx [2] OP_CTV 119 Output template Commit the spend tx to a pre-defined hash [3]&lt;/p&gt;

&lt;p&gt;OP_CAT operates purely on the stack and knows nothing about transactions. OP_CSFS understands signatures and keys, but deliberately decouples the message from the transaction. OP_CTV goes furthest — it commits the entire spending transaction to a hash baked into the locking script, requiring exact output reproduction.&lt;/p&gt;

&lt;p&gt;The three together form a covenant toolkit. A realistic pattern: use CAT to serialize transaction output data onto the stack, use CSFS to verify that an authorized party signed that output template, and use CTV to enforce the template is reproduced exactly. Each opcode handles one layer; none of them alone is sufficient.&lt;/p&gt;

&lt;p&gt;Next post: OP_CHECKTEMPLATEVERIFY on Signet — committing to outputs at UTXO creation time, and why CPFP is the practical fee-bumping strategy when the output template is fixed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We&amp;nbsp;Built
&lt;/h2&gt;

&lt;p&gt;We designed the simplest possible CSFS contract: a one-byte tapscript (&lt;code&gt;0xcc&lt;/code&gt;) that verifies a Schnorr signature over an arbitrary 32-byte message. The message — &lt;code&gt;SHA256("hello world")&lt;/code&gt; — was signed offline before the transaction existed. At spend time, the witness supplies the sig, message, and pubkey explicitly; OP_CSFS verifies without consulting the transaction sighash at all.&lt;/p&gt;

&lt;p&gt;We used &lt;code&gt;btcaaron&lt;/code&gt;'s &lt;code&gt;inq_csfs_script()&lt;/code&gt; to get the tapscript byte, built a single-leaf TapTree with &lt;code&gt;TapTree(...).custom(...).build()&lt;/code&gt;, and constructed the witness with &lt;code&gt;_make_witness()&lt;/code&gt; +&amp;nbsp;&lt;code&gt;.unlock_with()&lt;/code&gt;. RootScope verified the address reconstruction: internal key + one-byte script -&amp;gt; TapLeaf hash -&amp;gt; TapTweak -&amp;gt; output key -&amp;gt; &lt;code&gt;tb1p822a0z...fqp6x5up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The key thing mempool.space reveals: the decoded tapscript reads &lt;code&gt;OP_RETURN_204&lt;/code&gt;, not &lt;code&gt;OP_CHECKSIGFROMSTACK&lt;/code&gt;. That is not a bug in the explorer. It is the soft fork mechanism in action — non-upgraded nodes see OP_SUCCESS and pass the script unconditionally; Inquisition nodes see the actual CSFS semantics and enforce the signature check. The same upgrade pattern governs Taproot, SegWit, and every future Bitcoin soft fork.&lt;/p&gt;

&lt;p&gt;Both transactions are permanently confirmed on Signet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bitcoin Inquisition: &lt;a href="https://github.com/bitcoin-inquisition/" rel="noopener noreferrer"&gt;github.com/bitcoin-inquisition&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;btcaaron toolkit: &lt;a href="https://github.com/aaron-recompile/btcaaron" rel="noopener noreferrer"&gt;github.com/aaron-recompile/btcaaron&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RootScope visualizer: &lt;a href="https://github.com/aaron-recompile/rootscope" rel="noopener noreferrer"&gt;github.com/aaron-recompile/rootscope&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Signet | Bitcoin Inquisition 29.2 | Commit *&lt;code&gt;[*2d68a0e6...133803fe*](https://mempool.space/signet/tx/2d68a0e621533c2f7391159f4c2e252f409c1cec0ec681ff9c6b11cb133803fe?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Reveal *&lt;code&gt;[*cc1b6d35...85195f97*](https://mempool.space/signet/tx/cc1b6d352f75348b6a52c7f5c68fc5caea2512423e08011e8f69a9bb85195f97?showDetails=true)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/9cf70ab07583" rel="noopener noreferrer"&gt;March 17, 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/op-checksigfromstack-on-signet-sign-anything-verify-on-stack-9cf70ab07583" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
    </item>
    <item>
      <title>OP_CAT + OP_CHECKSIGFROMSTACK on Signet — Dynamic Message, Oracle Authorization</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:46 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/opcat-opchecksigfromstack-on-signet-dynamic-message-oracle-authorization-201e</link>
      <guid>https://dev.to/aaron_recompile/opcat-opchecksigfromstack-on-signet-dynamic-message-oracle-authorization-201e</guid>
      <description>&lt;h2&gt;
  
  
  OP_CAT + OP_CHECKSIGFROMSTACK on Signet — Dynamic Message, Oracle Authorization
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;IK + CSFS asks: did the owner sign this? CAT + CSFS asks: did the oracle sign what the stack assembled?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The interesting question is not what an opcode does. &lt;br&gt;
It is what emerges when they compose.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The interesting question is not what an opcode does.
It is what emerges when they compose.

Bitcoin is not designed top-down.
It is discovered through execution.
"Run the Future" - continues here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Four opcodes, 36 bytes (&lt;code&gt;7ea820...cc&lt;/code&gt;). OP_CAT assembles the signed message on-chain from two witness fragments. OP_SHA256 fixes it to a 32-byte digest. An oracle pubkey hardcoded in the script authorizes exactly that digest via OP_CHECKSIGFROMSTACK. The signer never saw the transaction — they only signed a price quote. The witness carries the raw material; the script determines what counts as authorization.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr6j0weojjyt1tv4d63rb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr6j0weojjyt1tv4d63rb.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  From Identity to&amp;nbsp;Message
&lt;/h2&gt;

&lt;p&gt;The previous combo experiment established one axis of the binding surface: &lt;em&gt;who&lt;/em&gt; is authorized. OP_INTERNALKEY pulls the UTXO’s internal key from the Taproot execution context; OP_CHECKSIGFROMSTACK verifies that this specific key holder signed the message. The pubkey moves from the witness into the structure of the UTXO itself.&lt;/p&gt;

&lt;p&gt;This experiment shifts to a different axis: &lt;em&gt;what is being authorized&lt;/em&gt;, and specifically, &lt;em&gt;how the authorized message is constructed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In every previous CSFS experiment — solo or combined — the witness hands the message in whole. It is a static 32-byte blob: &lt;code&gt;SHA256("hello world")&lt;/code&gt;, &lt;code&gt;SHA256("authorized:group_combo_ik_csfs:v1")&lt;/code&gt;. The signer commits to a pre-known string, and the verifier checks it. The message content is determined before the script runs.&lt;/p&gt;

&lt;p&gt;OP_CAT changes that. Combined with OP_SHA256 and OP_CSFS, it makes the message a runtime product of execution. The witness provides raw fragments — two byte strings that the script assembles, hashes, and then checks a signature against. The oracle never sees the assembled form; they sign semantic components. The final authorization target is constructed on-chain.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;oracle-gate pattern&lt;/strong&gt;: the script commits to an authorized signer (the oracle pubkey), but not to the authorized message. The message is determined by what the spender supplies. The oracle attests to a statement structure — “a price reading of the form &lt;code&gt;ASSET=VALUE&lt;/code&gt;" — and any spend that presents matching fragments with a valid oracle signature passes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Binding surface evolution:
  CSFS          pubkey: witness    message: witness (static whole)
  IK + CSFS     pubkey: context    message: witness (static whole)
  CAT + CSFS    pubkey: script     message: assembled on-chain from witness parts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pubkey is now in the script itself, hardcoded at UTXO creation time. The message is not pre-committed anywhere — it emerges from execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CAT+CSFS&amp;nbsp;Contract
&lt;/h2&gt;

&lt;p&gt;The locking script is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_CAT
OP_SHA256
OP_PUSHBYTES_32 &amp;lt;oracle_pubkey&amp;gt;
OP_CHECKSIGFROMSTACK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The oracle signs a message offline. The spender provides two fragments and the signature. The script assembles the fragments, hashes them, pushes the oracle pubkey, and verifies.&lt;/p&gt;

&lt;p&gt;For this experiment: &lt;code&gt;PART1 = "BTC_USD="&lt;/code&gt;, &lt;code&gt;PART2 = "100000"&lt;/code&gt;. The oracle attests to a price reading.&lt;/p&gt;

&lt;p&gt;Compare the three CSFS variants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Standalone CSFS:
  witness  = [sig, message, pubkey]
  script   = OP_CHECKSIGFROMSTACK

IK + CSFS:
  witness  = [sig, message]
  script   = OP_INTERNALKEY OP_CHECKSIGFROMSTACK
CAT + CSFS:
  witness  = [sig, PART1, PART2]
  script   = OP_CAT OP_SHA256 &amp;lt;oracle_pubkey&amp;gt; OP_CHECKSIGFROMSTACK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the CAT+CSFS version, the message is absent from the witness as a unit. It does not exist until OP_CAT runs. The oracle pubkey is not in the witness at all — it is embedded in the script, committed at UTXO creation time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contract&amp;nbsp;Design
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Oracle offline:
  FULL_MESSAGE = PART1 || PART2 = "BTC_USD=100000"
  MESSAGE_HASH = SHA256("BTC_USD=100000")
               = d97200147349b678a5f38c5e5cf95181476b4961ef6566570f88c3ea9b4ccc73
  sig          = Schnorr.sign(oracle_key, MESSAGE_HASH)

At spend time, witness provides:
  Witness[0] = sig    (64 bytes)
  Witness[1] = PART1  "BTC_USD=" (8 bytes)
  Witness[2] = PART2  "100000"   (6 bytes)
Script constructs the message on-chain:
  CAT    -&amp;gt; "BTC_USD=100000"
  SHA256 -&amp;gt; d97200...ccc73   (same hash oracle signed)
  PUSH oracle_pk
  CSFS   -&amp;gt; verify sig(MESSAGE_HASH, oracle_pk) -&amp;gt; TRUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tapscript bytecode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;7e                                               &amp;lt;- OP_CAT
a8                                               &amp;lt;- OP_SHA256
20                                               &amp;lt;- OP_PUSHBYTES_32
ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8
cc                                               &amp;lt;- OP_CHECKSIGFROMSTACK (OP_RETURN_204 on standard nodes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;36 bytes. The oracle pubkey is the only constant embedded in the script — the message structure is implicit in the oracle’s signing convention, not enforced by the bytecode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on the demo key.&lt;/strong&gt; In this experiment, &lt;code&gt;ORACLE_PUBKEY&lt;/code&gt; is derived from the same WIF as the TapTree's internal key — the oracle and the UTXO creator are the same entity. This is a deliberate simplification to keep the experiment self-contained. In production the two would be distinct: the internal key held by whoever deploys the contract, the oracle pubkey belonging to an independent data provider. The script mechanism is identical either way. When the two keys are the same, the oracle gate reduces to self-authorization. When they differ, it becomes a genuine third-party attestation gate — which is the pattern worth building toward.&lt;/p&gt;




&lt;h2&gt;
  
  
  Commit Phase: Building the Taproot Address with&amp;nbsp;btcaaron
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PART1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTC_USD=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PART2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;FULL_MESSAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PART1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;PART2&lt;/span&gt;
&lt;span class="n"&gt;MESSAGE_HASH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FULL_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_wif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEMO_KEY_WIF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ORACLE_PUBKEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xonly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;leaf_script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RawScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;build_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;OP_CAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OP_SHA256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;push_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ORACLE_PUBKEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;OP_CHECKSIGFROMSTACK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;TapTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;leaf_script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cat_csfs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron API Highlights
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**build_script(OP_CAT, OP_SHA256, push_bytes(ORACLE_PUBKEY), OP_CHECKSIGFROMSTACK)**&lt;/code&gt; assembles the 36-byte tapscript. &lt;code&gt;OP_CAT&lt;/code&gt; and &lt;code&gt;OP_CHECKSIGFROMSTACK&lt;/code&gt; are imported from &lt;code&gt;experiments.opcodes&lt;/code&gt; as raw bytes &lt;code&gt;0x7e&lt;/code&gt; and &lt;code&gt;0xcc&lt;/code&gt; — proposed opcodes defined in the experiment layer rather than btcaaron's standard primitives. &lt;code&gt;push_bytes(ORACLE_PUBKEY)&lt;/code&gt; encodes the 32-byte oracle pubkey with the &lt;code&gt;0x20&lt;/code&gt; length prefix.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**TapTree(...).custom(script, label).build()**&lt;/code&gt; derives the P2TR address via the same TapLeaf → Merkle root → TapTweak → output key chain as all previous experiments. A 36-byte script produces the same derivation structure as any other single-leaf tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Funding the&amp;nbsp;Address
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fund_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FUND_TXID_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fund_sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reveal Phase: Spending with Sig and Fragments
&lt;/h2&gt;

&lt;p&gt;The witness is three items: the oracle signature, and the two fragments that the script will assemble on-chain. The assembled message never appears in the witness — it is a runtime product.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_make_witness&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wif_secret_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEMO_KEY_WIF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PrivateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schnorr_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MESSAGE_HASH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;PART1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;PART2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cat_csfs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_utxo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fee_sats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_make_witness&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reveal_txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;broadcast_or_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron Spend&amp;nbsp;API
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**_make_witness()**&lt;/code&gt; returns &lt;code&gt;[sig, PART1, PART2]&lt;/code&gt;. Elements are pushed in list order, so &lt;code&gt;sig&lt;/code&gt; lands at the bottom of the execution stack, &lt;code&gt;PART1&lt;/code&gt; in the middle, and &lt;code&gt;PART2&lt;/code&gt; at the top. This is what OP_CAT expects: it pops the top two elements and concatenates as &lt;code&gt;bottom || top&lt;/code&gt;, producing &lt;code&gt;PART1 || PART2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**pk.schnorr_sign(MESSAGE_HASH, "", raw=True)**&lt;/code&gt; signs the 32-byte digest of the fully assembled message. The oracle signs a pre-computed hash — they never interact with the transaction or the fragments individually.&lt;/p&gt;

&lt;p&gt;The final witness layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0] = e3e78a8f...83cfde       &amp;lt;- Schnorr sig (64 bytes)
Witness[1] = 4254435f5553443d        &amp;lt;- PART1: "BTC_USD=" (8 bytes)
Witness[2] = 313030303030            &amp;lt;- PART2: "100000" (6 bytes)
Witness[3] = 7ea820ff1f9f...9986b8cc &amp;lt;- tapscript (36 bytes)
Witness[4] = c1ff1f9fa3...9986b8     &amp;lt;- control block (33 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Transactions, OP_RETURN_204, and Stack&amp;nbsp;Trace
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Commit: what lands&amp;nbsp;on-chain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[926c40c1...008f](https://mempool.space/signet/tx/926c40c1b72edb904a3bb7bf96795f351a6be597fc1aeab1390c15e0133b008f?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timestamp     : 2026-03-21 12:24:18 UTC
Confirmed     : after 9 minutes
Fee           : 155 sats  (1.01 sat/vB)
Features      : SegWit | Taproot | RBF

INPUT
  tb1p2nmwxkf...psdvg7ky    0.00132441 sBTC
OUTPUTS
  tb1p7vd6g2a...nscrqdhy    0.00082286 sBTC  &amp;lt;- change
  tb1peaj5tna...csd84jel    0.00050000 sBTC  &amp;lt;- CAT+CSFS lock UTXO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output &lt;code&gt;tb1peaj5tna...csd84jel&lt;/code&gt; is a standard P2TR address. No observer can tell from the outside that a 36-byte oracle-gate script is committed in the script path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reveal: what mempool.space shows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[75db54de...65ca2](https://mempool.space/signet/tx/75db54dea1f125174699710bd5b517ae13b6e09b3293a0b0f463ea5561a65ca2?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timestamp  : 2026-03-21 12:24:18 UTC
Fee        : 500 sats  (3.77 sat/vB)
Features   : SegWit | Taproot | RBF

INPUT
  tb1peaj5tna...csd84jel    0.00050000 sBTC
OUTPUT
  tb1p0tmlqak...xqcuvjzl    0.00049500 sBTC  (V1_P2TR)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The witness as shown by mempool.space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0]  e3e78a8f93d0349a4c20694ed93a4593a0179615667d
            a0078658973ee8d939bf146c574398ed3af179b560306b
            2f8508aa33598b262d783b786c709dcd83cfde
            &amp;lt;- Schnorr sig (64 bytes)

Witness[1]  4254435f5553443d
            &amp;lt;- "BTC_USD=" (8 bytes)
Witness[2]  313030303030
            &amp;lt;- "100000" (6 bytes)
Witness[3]  7ea820ff1f9fa326a9438227e6aa25030ccf89bcb8ce
            53db4f78dbce6146499d9986b8cc
            &amp;lt;- tapscript: OP_CAT OP_SHA256 OP_PUSHBYTES_32 &amp;lt;oracle_pk&amp;gt; OP_RETURN_204
Witness[4]  c1ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db
            4f78dbce6146499d9986b8
            &amp;lt;- control block (leaf version 0xc0 + odd parity + internal pubkey)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;nSequence for this input is &lt;code&gt;0xfffffffd&lt;/code&gt; — RBF is enabled. This differs from the CTV experiment where &lt;code&gt;0xffffffff&lt;/code&gt; was required by the template hash commitment. Here the output template is unconstrained; the spender is free to replace the transaction to adjust fees.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OP_RETURN_204 label
&lt;/h2&gt;

&lt;p&gt;mempool.space decodes the tapscript and displays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P2TR tapscript   OP_CAT  OP_SHA256  OP_PUSHBYTES_32 ff1f9f...9986b8  OP_RETURN_204
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;OP_RETURN_204&lt;/code&gt; is the explorer's label for opcode &lt;code&gt;0xcc&lt;/code&gt; = 204 decimal. On standard nodes without BIP348 active, 204 falls in the OP_SUCCESS range (BIP342 opcodes 187–254), causing the tapscript to pass immediately without checking the signature. On Bitcoin Inquisition 29.2 with BIP348 active, the same byte is OP_CHECKSIGFROMSTACK — the node pops pubkey, message, and sig from the stack and runs BIP340 Schnorr verification.&lt;/p&gt;

&lt;p&gt;Notably, &lt;code&gt;OP_CAT&lt;/code&gt; and &lt;code&gt;OP_SHA256&lt;/code&gt; are shown correctly by mempool.space — they are recognized opcodes on standard nodes (CAT via BIP347, SHA256 via existing script support). Only the final &lt;code&gt;0xcc&lt;/code&gt; is unknown to the standard decoder. The word "RETURN" in &lt;code&gt;OP_RETURN_204&lt;/code&gt; is not OP_RETURN the data-embedding opcode; it is mempool.space's naming convention for unrecognized opcodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack execution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Executing&lt;/strong&gt;: &lt;code&gt;OP_CAT OP_SHA256 OP_PUSHBYTES_32 &amp;lt;oracle_pubkey&amp;gt; OP_CHECKSIGFROMSTACK&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Taproot interpreter strips Witness&lt;a href="https://dev.toscript"&gt;3&lt;/a&gt; and Witness&lt;a href="https://dev.tocontrol%20block"&gt;4&lt;/a&gt;, loading Witness[0..2] as the initial execution stack.&lt;/p&gt;




&lt;h3&gt;
  
  
  Initial State: three witness items&amp;nbsp;loaded
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 313030303030                               |  &amp;lt;- "100000"   (Witness[2], PART2, top)
| 4254435f5553443d                           |  &amp;lt;- "BTC_USD=" (Witness[1], PART1)
| e3e78a8f...83cfde                          |  &amp;lt;- Schnorr sig (Witness[0], bottom)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two fragments and a signature. The assembled message does not exist yet. The oracle pubkey is not on the stack — it is waiting in the script.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1 — OP_CAT: pop top two, push concatenation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 4254435f5553443d313030303030               |  &amp;lt;- "BTC_USD=100000" (14 bytes)
| e3e78a8f...83cfde                          |  &amp;lt;- sig (still on stack)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OP_CAT pops &lt;code&gt;"100000"&lt;/code&gt; (top), then &lt;code&gt;"BTC_USD="&lt;/code&gt; (next), and concatenates as &lt;code&gt;bottom || top&lt;/code&gt;. The full price-reading string materializes on-chain for the first time. BIP347's 520-byte ceiling is nowhere near approached — 14 bytes is well within bounds.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2 — OP_SHA256: pop top, push its&amp;nbsp;SHA256
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| d97200147349b678...b4ccc73                 |  &amp;lt;- SHA256("BTC_USD=100000") (32 bytes)
| e3e78a8f...83cfde                          |  &amp;lt;- sig
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the exact 32-byte digest the oracle signed offline. The hash is deterministic — given the same PART1 and PART2, it always produces the same MESSAGE_HASH.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3 — OP_PUSHBYTES_32: push oracle pubkey from&amp;nbsp;script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| ff1f9fa3...9986b8                          |  &amp;lt;- oracle_pubkey (from script)
| d97200147349b678...b4ccc73                 |  &amp;lt;- MESSAGE_HASH
| e3e78a8f...83cfde                          |  &amp;lt;- sig
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The oracle pubkey arrives from the locking script, not the witness. It was committed at UTXO creation time. The spender cannot substitute a different key.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4 — OP_CHECKSIGFROMSTACK: pop pubkey, message, sig — verify — push&amp;nbsp;result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 01                                         |  &amp;lt;- TRUE
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OP_CSFS pops all three items (top-first: pubkey → message → sig) and runs BIP340 Schnorr verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verify: Schnorr(sig  = e3e78a8f...83cfde,
                msg  = d97200147349b678...b4ccc73,
                pub  = ff1f9fa3...9986b8)
result: VALID -&amp;gt; push 01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Script terminates with a single truthy value. The spend is valid.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why the sig doesn’t cover the transaction
&lt;/h3&gt;

&lt;p&gt;The oracle signed &lt;code&gt;SHA256("BTC_USD=100000")&lt;/code&gt; — computed before the UTXO existed. The signature does not bind to input amounts, output addresses, or sequence numbers. Anyone who obtains this oracle signature can construct a spend with any output structure and pass the CSFS check, provided they also supply the matching fragments. This is not a flaw — it is the oracle pattern. The oracle attests to a fact about the world; what happens to funds after that attestation is a separate contract design question, typically addressed by combining CAT+CSFS with OP_CTV to lock the output destination alongside the oracle condition.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Structural Boundary: Replay
&lt;/h2&gt;

&lt;p&gt;In this experiment the oracle signs the string &lt;code&gt;"BTC_USD=100000"&lt;/code&gt;. Once the reveal transaction is on-chain, the &lt;code&gt;(sig, PART1, PART2)&lt;/code&gt; triple is permanently visible in the witness. If a second UTXO existed with the same &lt;code&gt;7ea820...cc&lt;/code&gt; script — same oracle pubkey — that triple could be replayed against it. The fragments match, the hash matches, the sig verifies — the spend passes.&lt;/p&gt;

&lt;p&gt;This is an oracle-gate contract behaving exactly as designed. The oracle attested to a real-world fact at a point in time. The attestation is inherently transferable to any UTXO that accepts it. If you want each oracle signature to be valid for exactly one spend, include UTXO-specific data in the fragments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PART1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTC_USD=100000|txid=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PART2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 32 bytes of the input UTXO's txid
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;SHA256(PART1 || PART2)&lt;/code&gt; commits to a specific UTXO. The oracle signs a UTXO-bound attestation. Replay is structurally closed.&lt;/p&gt;

&lt;p&gt;IK+CSFS established &lt;em&gt;who&lt;/em&gt; can authorize. CAT+CSFS establishes &lt;em&gt;what&lt;/em&gt; the authorization covers and hands the message construction to the execution stack. Combining all three — IK to bind signer identity, CAT to build a spend-specific message, CSFS to verify — produces authorization with no replay surface at any layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Address Reconstruction with RootScope
&lt;/h2&gt;

&lt;p&gt;The tapscript is 36 bytes: &lt;code&gt;7e&lt;/code&gt; (OP_CAT) + &lt;code&gt;a8&lt;/code&gt; (OP_SHA256) + &lt;code&gt;20&lt;/code&gt; (OP_PUSHBYTES_32) + 32-byte oracle pubkey + &lt;code&gt;cc&lt;/code&gt; (OP_CHECKSIGFROMSTACK).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Derivation Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TapLeaf&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mh"&gt;0xc0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mh"&gt;0x24&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;leaf&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="mh"&gt;0xc0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Merkle&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TapLeaf&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;
&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;
&lt;span class="n"&gt;P2TR&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Bech32m&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x_only&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ORACLE_PUBKEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;script_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0x7e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xa8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ORACLE_PUBKEY&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xcc&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;tapleaf_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xc0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merkle_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tapleaf_hash&lt;/span&gt;
&lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ff1f9fa326a9438227e6aa25030ccf89bcb8ce53db4f78dbce6146499d9986b8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_x_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tweak_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_p2tr_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Expected: tb1peaj5tna...csd84jel  [OK]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: the same &lt;code&gt;internal_pubkey&lt;/code&gt; (&lt;code&gt;ff1f9fa3...9986b8&lt;/code&gt;) appears as both the TapTree construction key and the oracle pubkey embedded in the script. In this demo, the oracle and the UTXO creator are the same entity. In production these would typically be distinct — the internal key held by the contract operator, the oracle pubkey by an independent data provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  RootScope Visual&amp;nbsp;Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internal Key
  ff1f9fa3...9986b8
        |
    TapLeaf (0xc0)
    script: 7e a8 20 ff1f9fa3...9986b8 cc  (36 bytes)
                     ^--- oracle pubkey hardcoded in script
        |
    TapLeaf Hash
        |   (no siblings -&amp;gt; single-leaf tree)
    Merkle Root
        |
    TapTweak
        |
    Output Key Q  (parity: odd -&amp;gt; control block byte = c1)
        |
    P2TR Address
    tb1peaj5tna...csd84jel   [OK]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The control block opens with &lt;code&gt;c1&lt;/code&gt;, consistent with the IK+CSFS experiment and different from the CAT, CSFS, and CTV solo experiments which all produced &lt;code&gt;c0&lt;/code&gt;. Same internal key, 36-byte script, different Merkle root, different TapTweak value, different parity outcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where CAT+CSFS Sits in the&amp;nbsp;Series
&lt;/h2&gt;

&lt;p&gt;Opcode(s) Script Witness Pubkey source Message source What it proves OP_CAT &lt;code&gt;7ea820...87&lt;/code&gt; [part1, part2] n/a assembled Preimage reassembly OP_CSFS &lt;code&gt;cc&lt;/code&gt; [sig, msg, pubkey] witness witness (static whole) Someone with given key signed this OP_CTV &lt;code&gt;20...b3&lt;/code&gt; [] n/a n/a Spending tx matches template OP_INTERNALKEY &lt;code&gt;cb20...87&lt;/code&gt; [] context n/a Internal key equals expected IK+CSFS &lt;code&gt;cbcc&lt;/code&gt; [sig, msg] context (UTXO) witness (static whole) UTXO owner signed the message &lt;strong&gt;CAT+CSFS&lt;/strong&gt; &lt;code&gt;**7ea820...cc**&lt;/code&gt; &lt;strong&gt;[sig, part1, part2]&lt;/strong&gt; &lt;strong&gt;script&lt;/strong&gt; &lt;strong&gt;assembled on-chain&lt;/strong&gt; &lt;strong&gt;Oracle signed what the stack built&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The series now spans two levels: solo experiments that characterize individual primitives, and combo experiments that explore how composition shifts the authorization surface. The next direction: combining all three axes — IK to bind signer identity, CAT to assemble a spend-specific message, CSFS to verify — producing authorization with no replay surface at any layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We&amp;nbsp;Built
&lt;/h2&gt;

&lt;p&gt;We ran the second combo experiment: a 36-byte tapscript that pairs OP_CAT and OP_SHA256 with a hardcoded oracle pubkey and OP_CHECKSIGFROMSTACK. The oracle signed &lt;code&gt;SHA256("BTC_USD=100000")&lt;/code&gt; offline — a price attestation with no knowledge of the transaction it would eventually authorize. At spend time, the witness supplied the signature and two fragments separately; the script assembled them on-chain, hashed the result, and verified the oracle's signature against the computed digest.&lt;/p&gt;

&lt;p&gt;We built the TapTree with &lt;code&gt;btcaaron&lt;/code&gt;'s &lt;code&gt;build_script(OP_CAT, OP_SHA256, push_bytes(ORACLE_PUBKEY), OP_CHECKSIGFROMSTACK)&lt;/code&gt;, constructed the single-leaf TapTree, and derived the Signet address. The witness was &lt;code&gt;[sig, PART1, PART2]&lt;/code&gt; — three items, no pre-assembled message, no pubkey. The spend used&amp;nbsp;&lt;code&gt;.unlock_with(_make_witness())&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both transactions confirmed on Signet at 2026–03–21 12:24:18 UTC. RootScope verified the address derivation: the oracle pubkey appears in two roles — once as the TapTree’s internal key feeding the TapTweak derivation, once as a literal constant inside the 36-byte script. Same key, two roles, different derivation paths. Control block opens with &lt;code&gt;c1&lt;/code&gt;: same odd-parity outcome as the IK+CSFS experiment, arising from a different Merkle root produced by the 36-byte script.&lt;/p&gt;

&lt;p&gt;The replay boundary is real and deliberate: this oracle attestation is transferable to any UTXO that accepts the same oracle signature. Closing it requires encoding UTXO-specific data in the assembled fragments. That is the design space this experiment opens — not a limitation to apologize for, but a primitive boundary to build from.&lt;/p&gt;

&lt;p&gt;One more boundary worth naming: in this demo the oracle pubkey and the TapTree’s internal key are the same key. The oracle is the contract creator. Separating them — an independent oracle pubkey embedded in the script, a different internal key controlling the key path — is the production design. The mechanism is identical; only the trust model changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bitcoin Inquisition: &lt;a href="https://github.com/bitcoin-inquisition/" rel="noopener noreferrer"&gt;github.com/bitcoin-inquisition&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;btcaaron toolkit: &lt;a href="https://github.com/aaron-recompile/btcaaron" rel="noopener noreferrer"&gt;github.com/aaron-recompile/btcaaron&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RootScope visualizer: &lt;a href="https://github.com/aaron-recompile/rootscope" rel="noopener noreferrer"&gt;github.com/aaron-recompile/rootscope&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Signet | Bitcoin Inquisition 29.2 | Commit *&lt;code&gt;[*926c40c1...008f*](https://mempool.space/signet/tx/926c40c1b72edb904a3bb7bf96795f351a6be597fc1aeab1390c15e0133b008f?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Reveal &lt;em&gt;&lt;code&gt;[*75db54de...65ca2*](https://mempool.space/signet/tx/75db54dea1f125174699710bd5b517ae13b6e09b3293a0b0f463ea5561a65ca2?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Confirmed 2026-03-21*&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/8c73e1ef5353" rel="noopener noreferrer"&gt;March 27, 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/op-cat-op-checksigfromstack-on-signet-dynamic-message-oracle-authorization-8c73e1ef5353" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
    </item>
    <item>
      <title>OP_CAT on Signet — Concatenation, Commitment, and Bitcoin Inquisition</title>
      <dc:creator>aaron.recompile</dc:creator>
      <pubDate>Fri, 03 Jul 2026 02:47:44 +0000</pubDate>
      <link>https://dev.to/aaron_recompile/opcat-on-signet-concatenation-commitment-and-bitcoin-inquisition-3m66</link>
      <guid>https://dev.to/aaron_recompile/opcat-on-signet-concatenation-commitment-and-bitcoin-inquisition-3m66</guid>
      <description>&lt;p&gt;Satoshi disabled it in 2010. We ran it on-chain in 2026.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3y7xmjiw6gs5ruzebw7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3y7xmjiw6gs5ruzebw7v.png"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitcoin is not designed top-down.  
It is discovered through execution.

"Run the Future" — starts here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  From Satoshi’s Disabling to Protocol&amp;nbsp;Revival
&lt;/h2&gt;

&lt;p&gt;OP_CAT is one of Bitcoin’s most storied disabled opcodes. In 2010, Satoshi Nakamoto silently removed it along with a handful of others, citing concerns about unbounded memory growth — a concatenated loop could theoretically produce a stack element of arbitrary size, opening a vector for denial-of-service attacks against nodes.&lt;/p&gt;

&lt;p&gt;The irony is that OP_CAT is perhaps &lt;em&gt;the&lt;/em&gt; most expressive primitive in Bitcoin Script. A single opcode — pop two stack elements, push their concatenation — unlocks a surprising range of constructions: introspection, covenants, STARK proof verification, cross-input signature binding. For over a decade, it sat dormant while developers found increasingly exotic workarounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BIP347&lt;/strong&gt; (proposed 2024) formalizes OP_CAT’s return under Tapscript, with a strict 520-byte limit on concatenated outputs. &lt;strong&gt;Bitcoin Inquisition 29.2&lt;/strong&gt; ships BIP347 as a soft fork active on Signet, making Signet the canonical testbed for OP_CAT experiments before any mainnet activation.&lt;/p&gt;

&lt;p&gt;This post walks through a complete commit-reveal experiment conducted on Signet, implemented with the &lt;code&gt;btcaaron&lt;/code&gt; toolkit and reconstructed visually with RootScope. The core contract is intentionally minimal: a &lt;strong&gt;split-preimage hash lock&lt;/strong&gt; that proves OP_CAT's basic functionality while demonstrating the full Taproot script-path lifecycle.&lt;/p&gt;

&lt;p&gt;One thing worth noting upfront: before block confirmation, reveal transactions may be absent from standard Signet mempools — not because of a network fork, but because of policy. Inquisition nodes accept the non-standard spend, mine it, and make it visible on public explorers post-confirmation. “Not seen yet” does not mean the network rejected it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Split-Preimage Contract
&lt;/h2&gt;

&lt;p&gt;The classical hash-lock script commits to a preimage and requires the spender to reveal it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_SHA256 &amp;lt;hash&amp;gt; OP_EQUALVERIFY OP_TRUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The preimage is presented as a single stack element. OP_CAT introduces a twist: we can commit to a preimage that the spender must &lt;em&gt;reconstruct on the fly&lt;/em&gt; by providing two separate pieces and concatenating them on-chain. This is the &lt;strong&gt;split-preimage&lt;/strong&gt; pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OP_CAT
OP_SHA256
OP_PUSHBYTES_32 &amp;lt;SHA256(part1 || part2)&amp;gt;
OP_EQUAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our experiment: &lt;code&gt;part1 = "hello"&lt;/code&gt;, &lt;code&gt;part2 = "world"&lt;/code&gt;, and the expected hash is &lt;code&gt;SHA256("helloworld")&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters beyond the toy example.&lt;/strong&gt; The split-preimage pattern is the embryo of more sophisticated constructions. When you control what gets concatenated — for instance, a transaction field extracted via introspection alongside a known constant — you can enforce covenant behavior. OP_CAT turns the stack into a serialization engine. But we walk before we run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contract&amp;nbsp;Design
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Secret split:  "hello" (0x68656c6c6f)  ||  "world" (0x776f726c64)
                                   |
                               OP_CAT
                                   |
                    "helloworld" (0x68656c6c6f776f726c64)
                                   |
                               OP_SHA256
                                   |
          936a185c...8f8f07af   (SHA256 of "helloworld")
                                   |
                         compare with committed hash
                                   |
                               OP_EQUAL -&amp;gt; TRUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tapscript bytecode is exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;7e                                               &amp;lt;- OP_CAT
a8                                               &amp;lt;- OP_SHA256
20                                               &amp;lt;- OP_PUSHBYTES_32
936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af
87                                               &amp;lt;- OP_EQUAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Commit Phase: Building the Taproot Address with&amp;nbsp;btcaaron
&lt;/h2&gt;

&lt;p&gt;The commit transaction funds a Taproot output whose script path encodes the OP_CAT hash lock. We construct it as a &lt;strong&gt;single-leaf TapTree&lt;/strong&gt; with the internal key controlling the key path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Computing the Committed Hash
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;part1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;part2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;committed_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;part2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Constructing the OP_CAT&amp;nbsp;Script
&lt;/h2&gt;

&lt;p&gt;OP_CAT is opcode &lt;code&gt;0x7e&lt;/code&gt; — disabled on mainnet, activated on Signet via BIP347. We define it explicitly and pass it to &lt;code&gt;btcaaron&lt;/code&gt;'s script builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;OP_CAT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0x7E&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;script_hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;OP_CAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OP_SHA256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;push_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;committed_hash&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;OP_EQUAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cat_leaf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RawScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tap_tree&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;TapTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cat_leaf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;btcaaron API Highlights&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**TapTree(internal_key, network)**&lt;/code&gt; initialises a Taproot tree builder. The &lt;code&gt;internal_key&lt;/code&gt; is used as the key-path spend key and as the basis for the P2TR address derivation tweak.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**.custom(script, label)**&lt;/code&gt; attaches a raw script leaf to the tree under a human-readable label. The label is used later by&amp;nbsp;&lt;code&gt;.spend(label)&lt;/code&gt; to identify which leaf's control block to construct.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**.build()**&lt;/code&gt; finalises the tree: computes TapLeaf hashes, assembles the Merkle root (for a single leaf, the Merkle root &lt;em&gt;is&lt;/em&gt; the TapLeaf hash), applies the BIP341 tweak &lt;code&gt;Q = P + t*G&lt;/code&gt; where &lt;code&gt;t = tagged_hash("TapTweak", P || merkle_root)&lt;/code&gt;, and derives the Bech32m Signet address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Funding the&amp;nbsp;Address
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc_wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sendtoaddress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tap_tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FUND_SATS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reveal Phase: Spending with the Split&amp;nbsp;Preimage
&lt;/h2&gt;

&lt;p&gt;The reveal transaction spends the UTXO by supplying &lt;code&gt;"hello"&lt;/code&gt; and &lt;code&gt;"world"&lt;/code&gt; as separate witness stack elements, letting OP_CAT reassemble them on-chain before the hash check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tap_tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_utxo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fund_txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FUND_SATS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT_SATS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock_with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;part1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c1"&gt;# "68656c6c6f"  &amp;lt;- pushed first -&amp;gt; sits at stack bottom
&lt;/span&gt;        &lt;span class="n"&gt;part2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c1"&gt;# "776f726c64"  &amp;lt;- pushed second -&amp;gt; sits at stack top
&lt;/span&gt;    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reveal_txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sendrawtransaction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  btcaaron Spend&amp;nbsp;API
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;**.spend(label)**&lt;/code&gt; looks up the named leaf in the tree, constructs its control block (internal pubkey + optional Merkle path), and returns a transaction builder pre-configured for script-path spending.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**.unlock_with([...])**&lt;/code&gt; injects raw witness stack elements &lt;em&gt;before&lt;/em&gt; the script and control block. Order matters: elements are pushed in list order, so the &lt;em&gt;first&lt;/em&gt; element ends up deepest on the execution stack. In our case, &lt;code&gt;"hello"&lt;/code&gt; lands at the bottom and &lt;code&gt;"world"&lt;/code&gt; sits on top — exactly what OP_CAT expects.&lt;/p&gt;

&lt;p&gt;The final witness for our single input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0] = 68656c6c6f                  &amp;lt;- "hello"
Witness[1] = 776f726c64                  &amp;lt;- "world"
Witness[2] = 7ea820...8f8f07af87         &amp;lt;- tapscript (OP_CAT script)
Witness[3] = c050be5f...126bb4d3         &amp;lt;- control block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Transactions and Stack&amp;nbsp;Trace
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Commit: what lands&amp;nbsp;on-chain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[084d5a9c...ecaada09](https://mempool.space/signet/tx/084d5a9c6a8c176c24edc0a8b7ce54ed65808a326367d8a9299b4460ecaada09?showDetails=true)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timestamp     : 2026-02-12 09:52:48 UTC
Fee           : 155 sats  (1.01 sat/vB)
Features      : SegWit | Taproot | RBF

INPUT
  tb1pvzghpa...sqkluxcm    0.00250812 sBTC
OUTPUTS
  tb1p7lcpdk...8snhqmnc    0.00050000 sBTC  &amp;lt;- CAT lock UTXO
  tb1pmfwqkg...jsk55q8v    0.00200657 sBTC  &amp;lt;- change
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output &lt;code&gt;tb1p7lcpdk...8snhqmnc&lt;/code&gt; is a standard P2TR address. An external observer sees nothing unusual — no hint that an OP_CAT script lurks in the script path. The commitment is invisible until revealed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reveal: witness data and stack execution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TxID&lt;/strong&gt;: &lt;code&gt;[00072d4a...b2d05656](https://mempool.space/signet/tx/00072d4aa354b5987eb8f2ffec440db7467b0581c5e845a6a0ef6999b2d05656?showDetails=true)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Taproot interpreter strips the last two witness items (script + control block) and uses the remaining items as the initial execution stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Witness[0]  68656c6c6f
            &amp;lt;- "hello"

Witness[1]  776f726c64
            &amp;lt;- "world"
Witness[2]  7e a8 20 936a185c...8f8f07af 87
            &amp;lt;- tapscript: OP_CAT OP_SHA256 OP_PUSHBYTES_32 &amp;lt;hash&amp;gt; OP_EQUAL
Witness[3]  c0 50be5fc4...126bb4d3
            &amp;lt;- control block (leaf version 0xc0 + internal pubkey, no Merkle path)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 33-byte control block confirms a single-leaf TapTree: no sibling hash, because the TapLeaf hash &lt;em&gt;is&lt;/em&gt; the Merkle root.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executing&lt;/strong&gt;: &lt;code&gt;OP_CAT OP_SHA256 OP_PUSHBYTES_32 &amp;lt;committed_hash&amp;gt; OP_EQUAL&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial State: Witness[0] and Witness[1] loaded as execution stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 776f726c64                                 |  &amp;lt;- "world"  (Witness[1], top)
| 68656c6c6f                                 |  &amp;lt;- "hello"  (Witness[0], bottom)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two separate byte strings. Neither alone matches the preimage hash; only their concatenation does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — OP_CAT: pop top two, push concatenation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 68656c6c6f776f726c64                       |  &amp;lt;- "helloworld" (10 bytes)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OP_CAT pops &lt;code&gt;"world"&lt;/code&gt; then &lt;code&gt;"hello"&lt;/code&gt; (top-first) and concatenates as &lt;code&gt;bottom || top&lt;/code&gt;. BIP347 enforces a 520-byte ceiling on the result — our 10-byte string is well within bounds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — OP_SHA256: pop top, push its&amp;nbsp;SHA256
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 936a185c...8f8f07af                        |  &amp;lt;- SHA256("helloworld") (32 bytes)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hash is deterministic. The spender cannot influence it without controlling the preimage composition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — OP_PUSHBYTES_32: push the committed hash from&amp;nbsp;script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 936a185c...8f8f07af                        |  &amp;lt;- committed_hash (from script)
| 936a185c...8f8f07af                        |  &amp;lt;- computed_hash  (from Step 2)
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script carries the expected hash as a 32-byte constant embedded at compile time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — OP_EQUAL: compare, push&amp;nbsp;result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------+
| 01                                         |  &amp;lt;- TRUE
+--------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both hashes match. Script terminates with a single truthy value — spend is valid. Output destination: &lt;code&gt;tb1p4ug2v8...zqwnmkrp 0.00049500 sBTC (V1_P2TR)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not OP_EQUALVERIFY +&amp;nbsp;OP_TRUE?
&lt;/h3&gt;

&lt;p&gt;Both produce the same consensus result here. &lt;code&gt;OP_EQUAL&lt;/code&gt; leaves the boolean on the stack, while &lt;code&gt;OP_EQUALVERIFY&lt;/code&gt; consumes it and aborts on failure. For a single-path terminal opcode, &lt;code&gt;OP_EQUAL&lt;/code&gt; is idiomatic — clearer intent, one fewer byte. The on-chain bytecode (&lt;code&gt;87&lt;/code&gt;) confirms this choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Address Reconstruction with RootScope
&lt;/h2&gt;

&lt;p&gt;The real payoff of understanding Taproot internals is being able to &lt;strong&gt;independently reconstruct the P2TR address&lt;/strong&gt; from its constituent parts, without trusting any external source. RootScope walks through the full derivation and renders each step visually.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Derivation Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TapLeaf hash = tagged_hash("TapLeaf",  0xc0 || compact_size(len(script)) || script)
Merkle root  = TapLeaf hash              &amp;lt;- single leaf: no TapBranch needed
tweak t      = tagged_hash("TapTweak",  internal_pubkey || merkle_root)
output key Q = internal_pubkey + t*G    &amp;lt;- elliptic curve point addition
P2TR address = Bech32m("tb", Q.x_only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tapleaf_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapLeaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xc0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;script_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merkle_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tapleaf_hash&lt;/span&gt;

&lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tweak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tagged_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TapTweak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;internal_pubkey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_x_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_pubkey&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tweak_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_p2tr_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Expected: tb1p7lcpdk...8snhqmnc  [OK]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  RootScope Visual&amp;nbsp;Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internal Key
  50be5fc4...126bb4d3
        |
    TapLeaf (0xc0)
    script: 7ea820...07af87
        |
    TapLeaf Hash
        |   (no siblings -&amp;gt; single-leaf tree)
    Merkle Root
        |
    TapTweak
        |
    Output Key Q
        |
    P2TR Address
    tb1p7lcpdk...8snhqmnc   [OK]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For multi-leaf trees, RootScope renders the full binary tree, highlights the Merkle proof path for any selected leaf, and shows where sibling hashes appear in the control block — indispensable for debugging spend failures caused by mismatched script indexes or incorrect tree ordering.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Comes Next: CAT, CSFS, and&amp;nbsp;CTV
&lt;/h2&gt;

&lt;p&gt;This post is the first in a three-part series covering the opcode proposals currently co-activated on Signet via Bitcoin Inquisition. Each targets a different layer of the transaction:&lt;/p&gt;

&lt;p&gt;Opcode BIP What it constrains Mechanism OP_CAT 347 Stack composition Concatenate two elements; enables serialization OP_CSFS 348 Signature scope Check a signature against an arbitrary message OP_CTV 119 Output template Commit the spend tx to a pre-defined hash&lt;/p&gt;

&lt;p&gt;OP_CAT, as shown here, operates purely on the stack. It does not know what a transaction looks like — it just concatenates bytes. That is precisely why covenant designs need to combine it with introspection: CAT reassembles serialized transaction fields, and something else checks them.&lt;/p&gt;

&lt;p&gt;OP_CSFS goes one step further: it decouples the message being signed from the transaction itself. A signature can be validated against any data on the stack, not just the current transaction hash. This opens the door to delegation schemes and cross-input commitments.&lt;/p&gt;

&lt;p&gt;OP_CTV takes the opposite approach: instead of building up constraints from scratch, it commits the spending transaction to a pre-computed hash at UTXO creation time. The spender cannot deviate from the template — output addresses, amounts, and sequence numbers are all locked in. No introspection needed; the commitment is baked into the script.&lt;/p&gt;

&lt;p&gt;The three opcodes are complementary. A covenant that validates a transaction’s output structure might use CTV for the simple fixed-template case, or CAT + CSFS for a more dynamic one where the template is computed at runtime. Next post: OP_CSFS on Signet — what it takes to sign something other than the current transaction.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We&amp;nbsp;Built
&lt;/h2&gt;

&lt;p&gt;We designed a split-preimage hash lock under Taproot’s script path, implemented it with &lt;code&gt;btcaaron&lt;/code&gt;'s &lt;code&gt;TapTree / RawScript /&amp;nbsp;.custom() /&amp;nbsp;.unlock_with()&lt;/code&gt; chain, and broadcast both the commit and reveal transactions on Signet. The reveal witness puts &lt;code&gt;"hello"&lt;/code&gt; and &lt;code&gt;"world"&lt;/code&gt; on the stack as two separate items; OP_CAT reassembles them before the SHA256 check fires.&lt;/p&gt;

&lt;p&gt;We then used RootScope to walk the derivation in reverse: starting from the raw script bytes and the internal key, we recomputed the TapLeaf hash, the Merkle root, the TapTweak, and the output key — arriving at the same &lt;code&gt;tb1p7lcpdk...8snhqmnc&lt;/code&gt; that received the commit funds. That chain of trust, from script to address, is what makes Taproot's script-path commitments verifiable and auditable without any external authority.&lt;/p&gt;

&lt;p&gt;Both transactions are permanently confirmed on Signet. The experiment is reproducible end-to-end using the tools linked below.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bitcoin Inquisition: &lt;a href="https://github.com/bitcoin-inquisition/" rel="noopener noreferrer"&gt;github.com/bitcoin-inquisition&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;btcaaron toolkit: &lt;a href="https://github.com/aaron-recompile/btcaaron" rel="noopener noreferrer"&gt;github.com/aaron-recompile/btcaaron&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RootScope visualizer: &lt;a href="https://github.com/aaron-recompile/rootscope" rel="noopener noreferrer"&gt;github.com/aaron-recompile/rootscope&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Signet | Bitcoin Inquisition | Commit *&lt;code&gt;[*084d5a9c...ecaada09*](https://mempool.space/signet/tx/084d5a9c6a8c176c24edc0a8b7ce54ed65808a326367d8a9299b4460ecaada09?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Reveal &lt;em&gt;&lt;code&gt;[*00072d4a...b2d05656*](https://mempool.space/signet/tx/00072d4aa354b5987eb8f2ffec440db7467b0581c5e845a6a0ef6999b2d05656?showDetails=true)&lt;/code&gt;&lt;/em&gt; | Confirmed 2026-02-12*&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@aaron.recompile" rel="noopener noreferrer"&gt;Aaron Recompile&lt;/a&gt; on &lt;a href="https://medium.com/p/ed34a07866d6" rel="noopener noreferrer"&gt;March 16, 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@aaron.recompile/op-cat-on-signet-concatenation-commitment-and-bitcoin-inquisition-ed34a07866d6" rel="noopener noreferrer"&gt;Canonical link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exported from &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on July 3, 2026.&lt;/p&gt;

</description>
      <category>bitcoin</category>
    </item>
  </channel>
</rss>
