TEE-as-Verifier for BitVM-style bridges: collapsing the canonical
label distribution problem
Context. We’re building Stax, a BTC L2 with a BitVM-class bridge. Equivocation-only protection has been live on signet since
2026-04-23, and we’re now designing the fixed-lie defence for Phase 3. While doing the design pass, we ended up at a framing that
— if it holds up to scrutiny — eliminates an entire class of on-chain machinery (forced-reveal Taproot leaves, Σ-responses, label
distribution channels). We want cryptographer review on the seven questions in §6 before we commit to it.
This post summarises the problem, the framing shift, and seven questions where we want a second opinion. The full design doc is
linked at the end.
- The problem under the standard BitVM-class bridge model
Standard BitVM-style withdrawal flow:
Operator publishes AssertTx claiming the L2 state at withdrawal time, revealing Lamport preimages bit-by-bit.
CSV timer.
Watchtowers, during the CSV window, can publish DisprovalTx to slash the bond. Two attack categories matter here:
(a) Equivocation — operator publishes two AssertTxs with conflicting bits at the same Lamport position.
(b) Fixed lie — operator publishes one AssertTx whose claimed state does not match canonical L2 history.
Equivocation is straightforward and is what BitVM2 / Citrea / Bitlayer ship today: any two conflicting reveals at one position give a
watchtower both Lamport preimages, which slashes the bond.
Fixed-lie is the open problem. No deployed BitVM-class bridge has an on-chain disprove path for it — the standard assumption is “if
the operator only publishes one AssertTx and it’s a lie, off-chain mechanisms (alerts, social) handle it”. This post is about closing that
gap. A naive deposit-time keypair flow has
s_i^b = SHA256(“op-secret” || withdrawal_id || i || b)
i.e. the operator generates their own Lamport preimages. They know both preimages at every bit position. They can therefore
construct an AssertTx claiming any bit pattern — truthful or lying — and the script verifier accepts it. The on-chain machinery has no
independent reference for what the canonical bits should be.
Existing literature on forcing leaves (e.g. Σ-protocol-style challenge-response leaves binding the operator to a public canonical-wire
label table) all assume the canonical labels come from somewhere external to the operator. The standard suggestion is a multi-party
DKG ceremony or a trusted setup. In our analysis, none of the on-chain forcing variants we examined (extending the pubkey, separate
RevealTx, CSV-gated opposite-preimage) actually solve the problem without that external label generator. We documented this in a
separate design doc; see §10.6 of forcing_mechanism_design.md.
So the question becomes: where do canonical labels come from, and who can see them?
2. Two framings of TEE as the label generator
We picked TEE (AWS Nitro Enclaves as default platform) over a multi-party ceremony for solo-team feasibility reasons. Once you
commit to a TEE, two API designs are possible. The choice of API determines the entire on-chain footprint.
2.1 TEE-as-Garbler (the framing we started with)
TEE.generate_circuit(vk) → (commitment_C, label_table T = {(L_i^0, L_i^1)})
TEE.attest() → AttestedCommitment(C, code_hash, public_seed, …)
Labels then have to flow somewhere — to watchtowers, so they can spend forced-reveal Taproot leaves when they detect a lying
AssertTx. This raises a hard sub-problem: how do you distribute labels to watchtowers without leaking them to the operator? In a
permissionless watchtower set, anyone can run a watchtower, so anyone can hold the labels — including the operator via a sock
puppet watchtower. We don’t have a clean answer.
Under TEE-as-Garbler you also need: forced-reveal leaves in the Taproot tree, Σ-response generation in AssertTx witness, watchtower
disputer code that spends those leaves, beacon plumbing, and the label distribution channel itself. That’s the bulk of
forcing_mechanism_design.md Milestones B and C.
2.2 TEE-as-Verifier (the framing we’re proposing)
TEE.generate_keypair_for_withdrawal(withdrawal_id)-> Lamport pubkey (47 KB), attestation
TEE.release_preimages(withdrawal_id, claimed_state s, claimed_proof π)
→ { s_i^{bit_i(public_inputs(s))} } if Groth16Verify(vk, ., π) is VALID
→ error “INVALID” otherwise
The TEE is both garbler and verifier. The operator’s only API is “verify this proof and give me the preimages for its public inputs”.
Crucially:
TEE only releases one preimage per bit position — the one matching the bit value of the claimed state’s public inputs.
TEE never exports the OTHER preimage at any position.
TEE never exports preimages tied to a never-asserted state.
The fixed-lie attack reduces to:Path What happensA: operator submits (state_lie, garbage_proof) TEE Groth16Verify → INVALID → refuses → operator has no preimagesB: operator submits (state_truthful, valid_proof) TEE returns truthful preimages → AssertTx asserts truth, no lie occurredC: operator forges Groth16 proof for state_lie Reduces to Groth16 soundness — same assumption the L2 already trusts
The lie cannot reach Bitcoin in the first place. There is nothing on-chain to catch and slash via forcing leaves.
2.3 What this collapses on-chainComponent TEE-as-Garbler TEE-as-VerifierForced-reveal Taproot leaves needed removedAssertTx Σ-response needed removedWatchtower forced-reveal disputer needed removedCanonical-label distribution channel needed (open problem) removedBeacon plumbing for our Σ-protocol variant needed removedEquivocation Lamport leaves unchanged unchangedTEE attestation infrastructure needed neededregisterGarblerAttestation on bridge contract needed needed
Watchtowers under TEE-as-Verifier need only: TEE attestation digest match, AssertTx detection, equivocation detection (existing),
and an off-chain alert path for the residual TEE-compromise-no-equivocation scenario.
3. Why we think forced-reveal leaves are vestigial under TEE-as-Verifier
Even if you keep the forced-reveal leaves from the original Σ-protocol design (per phase3_tee_secrecy.md §3.1), in the TEE
compromise-no-equivocation scenario the watchtower still cannot spend them. The leaf script is
OP_SHA256 <expected_hash[i]> OP_EQUALVERIFY OP_TRUE
To spend, the watchtower needs the preimage of expected_hash[i]. In the TEE model, preimages are TEE-internal. Watchtowers
never had them. The leaf is unspendable by any party except the TEE and whoever the TEE released preimages to (the operator).
Same outcome as the deferral reasoning recorded in forcing_mechanism_design.md §10.6, different actor. The defence-in
depth argument for keeping the leaves doesn’t survive because the actor with the preimages is the same actor in both scenarios (the
one who submitted to the TEE).
4. Trust statement, honestly
Stax bridge in Phase 3 with TEE-as-Verifier garbler trusts:
(a) Bitcoin honest majority, (b) Celestia DA (operator-withholding closed since 2026-04-22), © ≥1 honest watchtower for
equivocation detection, (d) Groth16 soundness for fixed-lie defence, (e) TEE vendor attestation chain (AWS Nitro by
default; multi-vendor cross-attestation deferred to Phase 3.2c).
Assumption (e) is strictly weaker than a BitVM2 N-of-N ceremony. We accept it for solo-team mainnet feasibility, with the explicit
intent to retire it post-mainnet when the ceremony becomes feasible.
5. Defence-in-depth fallback (if you push back on §3)
If a reviewer believes “fixed-lie at construction” is not a sufficient defence, the fallback is to KEEP forced-reveal leaves AND distribute
canonical labels to watchtowers via an attested side channel (TEE-encrypted under per-watchtower keys, published via Celestia DA).
Cost: ~85 KB pubkey, +Σ-response in AssertTx witness (~3-5 KB), one additional Taproot leaf per cut-and-choose challenge position (cchosen for the desired soundness; c=64 puts ≈1 in 264 chance of an undetected lie), full Σ-protocol disputer + label-distribution
channel implementation, plus a watchtower key registry.
We argue this doesn’t cleanly compose because anyone-can-run-a-watchtower means anyone-can-hold-labels, which collapses back
to operator-via-sock-puppet possessing labels — but we want this rejection challenged.
6. Open questions where we want cryptographer eyes
These are pre-commit-to-implementation blockers. We have not started Phase 3.2b code beyond the attestation infrastructure
(which is correct under either framing).
Q1 — Groth16 soundness as the only fixed-lie defence. Is reducing fixed-lie defence under TEE to “Groth16 soundness + TEE
attestation integrity” acceptable for a mainnet bridge? Zcash Sapling and Filecoin both rely on Groth16 soundness for production
deployments holding multi-billion-dollar value, and the assumption has held under sustained scrutiny. We’re not introducing a new
cryptographic assumption — we want this confirmed in the bridge context where the assertion is on-chain Bitcoin, not on-chain
Ethereum.
Q2 — TEE statefulness for equivocation prevention. Should the TEE internally track “I have already released preimages for
withdrawal_id X under bit pattern P” and refuse to release a second pattern for the same withdrawal? If yes, this is prevention of
equivocation rather than detection, and the equivocation Lamport leaves can be deprecated entirely — collapsing Phase 3 to “TEE
attests, nothing else”. Cost: TEE must persist state durably (Nitro KMS or AMD SEV-SNP equivalent). Race conditions across multiple
TEE instances need handling (see Q4).
Q3 — TEE-to-operator channel encryption. When the TEE returns preimages, the channel to the operator must be encrypted
(otherwise a passive eavesdropper gains the ability to publish AssertTx). Standard answer: enclave-attested TLS — ephemeral keypair
generated inside the enclave, attestation document binds the TLS public key to code_hash, operator authenticates the attestation
before sending the proof. Are there subtleties specific to the BitVM context we’re missing — e.g. session replay across TEE restarts,
key rotation under TEE upgrade, an operator who lies about which session received which preimages?
Q4 — TEE replication for liveness. A single TEE is a SPOF for bridge liveness. Multiple TEE instances (same code_hash, same
master_seed) need a coordination protocol so they don’t release preimages for conflicting states. Is “deterministic master seed
across N TEEs + per-instance state log + take-the-first-replied” safe, or does it open a race that an attacker can exploit? Specifically: if
instance A releases preimages for state P at time t, and instance B releases preimages for state P’ (P’ ≠ P) at time t+ε before A’s state
log replicates, has the operator just been handed both halves at some bit positions?
Q5 — TEE upgrade with active deposits. If we ship a new garbler binary (different code_hash), existing deposits committed to the
old pubkey are stuck unless either (a) the old TEE binary remains operationally available indefinitely, or (b) we ship a migration path
(e.g. cooperative refund flow, then re-deposit under new code_hash). What’s the standard pattern in TEE-using bridge designs? Is (a)
acceptable for a 6-12 month deposit lifetime? Does (b) have a cleaner mechanic than “owner-triggered migration window”?
Q6 — Per-withdrawal vs batch attestation cadence. The on-chain attestation digest covers (code_hash, public_seed,
output_hash). If output_hash is the pubkey commitment for ONE withdrawal, every withdrawal needs its own on-chain digest
registration — this gets expensive at scale. Per-batch would aggregate but loses some granularity. Per-epoch is cheapest but raises
questions about which withdrawals fall under which attestation. What’s the right granularity here, and what’s the failure mode of
getting it wrong?
Q7 — Would forced-reveal leaves ever be useful? §3 of this post argues no, because watchtowers possessing labels means
operator-via-sock-puppet possesses labels (in a permissionless watchtower set). If you can construct a threat model where forced
reveal leaves provide non-trivial defence-in-depth that off-chain alert + attestation revocation does not, we’d like to hear it — that
would push us to the §5 fallback.
7. What we’re asking for
Not a full audit (that’s a Tier 4 spend later). Right now we want:
A reality check on Q1 — is the trust statement in §4 honest, or are we hand-waving past something?
A second opinion on Q2 — does TEE statefulness genuinely subsume equivocation detection, or does the corner case of TEE
state corruption / rollback re-open the door?
Anyone with hands-on AWS Nitro Enclaves experience for Q4 / Q5 — what bites in production that doesn’t show up in design
docs?
If the answers come back unanimously “this is fine”, we ship Phase 3.2b in ~1 week. If they come back with one of the questions
exposing a hole, we do the §5 fallback or re-architect.
8. Links
Full design doc this post summarises: docs/phase3_tee_secrecy.md (~530 lines)
Related: docs/phase3_tee_design.md (TEE platform choice, threat model)
Original problem statement: docs/forcing_mechanism_design.md §10.6
Bridge runs on Bitcoin signet (live since 2026-04-22). TESTNET.md records the equivocation + DisprovalTx flow, the Celestia
DA strict-mode refusal, and a honest deposit/mint, with concrete signet transaction hashes for each.
Comments and DMs welcome. Ethresearch thread is the canonical place for technical responses; we’ll mirror substantive feedback
into the doc with attribution