How Bitcoin's Taproot Internal Key Commitment Prevents Script Path Hiding From Being Reversed
You're auditing a wallet. A P2TR output sits in the UTXO set: 32 bytes, a single x-only public key, indistinguishable from any other Taproot spend on the chain. No scripts visible. No conditions exposed. The question that should follow immediately is not aesthetic but legal in character: what stops the party who constructed that output from inventing a script claim against it later, after the fact, once it suits them? The answer is a tight piece of cryptographic machinery called the internal key commitment, and it rewards precise attention.
The Tweak That Ties Everything Together
Taproot outputs are Pay-to-Taproot (P2TR), which means they lock funds to a 32-byte x-only public key called the output key (call it `Q`). That key is not a raw key you generated. It's a modified version of an internal key (`P`), adjusted by a hash that encodes your entire script tree.
The formula is simple enough to write on a napkin:
``` Q = P + hash(P || merkle_root) * G ```
`G` is the elliptic curve generator point. `merkle_root` is the root of a Merkle tree whose leaves are the hashed spending scripts (called tapleaves). The hash function used is tagged SHA-256, specifically the `TapTweak` tag defined in BIP 341, which domain-separates this operation from every other hash in the protocol. That domain separation is not a minor implementation detail; it is what prevents a hash computed in one context from being weaponized in another.
The commitment works because of a fundamental property of elliptic curve arithmetic: if you know `P` and you know `merkle_root`, you can verify that `Q` was constructed correctly. You cannot reverse the relationship to extract a different `merkle_root` that also satisfies the equation, because that would require solving the discrete logarithm problem. The hash output is a scalar, not recoverable from `Q` alone without knowing `P` first.
The output key `Q` is, in a very literal sense, a simultaneous fingerprint of both the internal key and the script tree. Think of it as a wax seal pressed over two documents at once: breaking one breaks the other.
A Concrete Scenario
Imagine Lena and Marco both receive Taproot outputs. Lena's internal key is a 2-of-3 multisig key aggregated via MuSig2, and her script tree contains a single timelocked recovery path. Marco's internal key is a solo key with no script tree at all, so his `merkle_root` is just an empty hash.
Both outputs look identical on-chain. When Lena spends cooperatively, she provides a Schnorr signature against `Q` and nobody ever sees her scripts. When she can't assemble all signers, she reveals the script path: the internal key `P`, the control block (which includes the Merkle proof for the specific script leaf), and the script itself. The verifying node recomputes `Q` from `P` and the merkle root reconstructed from the proof. If it matches the output key in the UTXO set, the spend is valid.
Lena cannot swap in a different script at spend time. The output key already committed to the exact tree she constructed at the outset. Any substitution changes the merkle root, changes the tweak, and produces a `Q` that doesn't match what's on-chain.
The coins don't move.
What People Get Wrong About This
The common misconception is that Taproot's privacy guarantee comes purely from the key-path spend resembling an ordinary signature. True, but incomplete, and the incompleteness matters. The deeper guarantee is that the script path can't be invented after the fact. A counterparty who receives a P2TR output is protected not just by the opacity of an unspent output, but by the mathematical impossibility of the sender retroactively constructing scripts that satisfy the commitment. Those are two different protections, and conflating them understates what BIP 341 actually achieves.
There is a subtler wrinkle. Even a key-only output with no scripts must include a `merkle_root` in the tweak. BIP 341 specifies that when there is no script tree, the tweak uses just `hash(P)` rather than `hash(P || merkle_root)`. The output key therefore encodes the absence of scripts as its own binding commitment. You cannot later claim a secret script tree existed when the output key was computed without one. The protocol treats the null case with the same rigor it applies to the populated case, which is exactly the right design instinct.
One honest caveat, and it applies to essentially all of Bitcoin cryptography: the security of the commitment rests entirely on the collision resistance of SHA-256 and the hardness of the elliptic curve discrete logarithm over secp256k1. Neither has been broken. But if either assumption ever weakened, the binding property weakens with it.
So here is the question worth sitting with: if you received a P2TR output in a contract settlement today, could you verify independently that the output key matches the scripts you agreed to? You can. Recover the internal key and the script tree, recompute `Q` yourself, and check it against the UTXO set. That auditability is the protocol working exactly as designed. The math doesn't care whether you trust the wallet software that built the output. It only cares whether the numbers match, and on that point it is perfectly, inflexibly indifferent.