This repository presents a proof-of-concept bridge using Simplicity and zero-knowledge proofs to bridge ETH from Ethereum Sepolia to Liquid testnet, with the ability to return SepETH to Sepolia.
The concept was inspired by recent research showing that Groth16 proofs can be verified on Liquid using Simplicity.
This allowed us to adapt and reuse the code for verifying Ethereum's consensus to enable a trustless experience, subject to certain assumptions, between Ethereum Sepolia and Liquid.
At its heart, the mechanism is simple: prove to Liquid that something happened on Ethereum and take an action (in our case, issue wrapped SepETH backed by SepETH locked in a contract on Sepolia).
And vice versa: prove to Sepolia that an action happened on Liquid and take an action (in our case, unlock the previously deposited SepETH).
For more details, see concept.
Read the design assumptions to understand trust assumptions. Each deployment's own trust roots are in its record under instances/, created when it is deployed.
On Sepolia, the contract responsible for managing deposits is BridgeEscrow. Then a prover runs the SP1 program off-chain, starting from a trusted weak-subjectivity checkpoint, to prove Sepolia finality and the escrow's deposit state. The Simplicity update covenant verifies this proof on Liquid.
On Liquid, the Simplicity covenants control a reissuance token for wrapped SepETH. When the user provides the necessary proofs, namely the deposit inclusion proof and proof that the deposit has not already been claimed, the claim covenant allows them to issue the exact amount of wrapped SepETH to the Liquid recipient recorded in the deposit.
The operation above concludes the first direction of bridging assets from Ethereum to Liquid.
The redemption of SepETH starts with burning wrapped SepETH on Liquid using the bridge's specific burn format, which names the chain, escrow and Sepolia recipient. Then the Solidity contract verifies that the burn transaction was included in a Liquid block, using signed block headers and the required confirmations. After the proof is verified and accepted, the corresponding amount of previously deposited SepETH is released. The same burn cannot be redeemed twice.
This concludes the other and final bridge direction that this proof of concept is meant to demonstrate. Both directions rely on the documented trust assumptions.
For the full manual test, including generating a proof locally, install the following tools and apps.
Skip anything you already have. CI uses Rust 1.95.0 and Node.js 22.
The recipes also use the bash and curl commands supplied with macOS.
| Dependency | Used for |
|---|---|
| Xcode Command Line Tools | Git, C/C++ compilers, linker, make and the macOS SDK |
| Rust and Cargo, managed by rustup | Building the bridge tools and SP1 CLI |
| Python 3.9 or later | Fetching pinned dependencies and assembling transactions |
| Node.js 22.13 or later in the 22.x series, with npm | Instance checks, store checks and the local UI |
| just | Running the commands below |
Go and Protocol Buffers (protoc) |
Building the SP1 prover host |
| CMake, LLVM/libclang, pkg-config and OpenSSL 3 | Native dependencies of the prover host |
| Docker Desktop | Building the pinned guest ELF |
| Google Chrome with Humid 1.1.3 and an Ethereum wallet extension | Reviewing and signing the Liquid and Sepolia transactions |
Enable Liquid testnet in Humid and Sepolia in the Ethereum wallet. You need tL-BTC for Liquid fees and SepETH for the deposit and Ethereum gas. Keep Docker running for just prover, which builds the pinned guest ELF. The current just prove uses the native CPU prover.
Local proof generation can exhaust a 16 GB machine. You can try the experimental just prove low-memory profile or generate the proof on a larger machine and keep wallet signing local. The profile is not a RAM cap or verified 16 GB support. See proving with limited RAM for the limits and steps.
These steps finish an existing deposit made through the website to the current bridge instance. Use the deposit number shown in Activity. The paths below assume the default BRIDGE_INSTANCE and BRIDGE_WORK settings.
- Clone the repository and run the following commands from its root directory.
- Export the two RPC URLs:
export SEPOLIA_EXECUTION_RPC=... SEPOLIA_CONSENSUS_RPC=.... The execution RPC must serveeth_getLogs,eth_callandeth_getProofat finalized blocks; the consensus RPC must serve Sepolia Beacon API light-client updates. - Run
just deps tools prover check-elfto install the project dependencies and build the tools needed for the manual steps. - Run
just status <deposit number>to recover the live state, read finalized deposits and print the next steps. Wait if the deposit is not final yet; skip to step 8 if it is proven and unclaimed. A claimed deposit needs no further claim. - For a final but unproven deposit, run
just inputs execute prove. This proves Ethereum finality and the escrow's deposit accumulator at a finalized block; the claim later proves this deposit's inclusion. Ifstatusreports a missing light-client store, runjust reconstructfirst. If you need a new proof andtarget/takeover/proof.jsonalready exists, move it and its matchingproof.store.jsonaside first. - Open Send a proof update on the website and load
target/takeover/proof.browser.json. Prepare a fee coin with Humid, wait for confirmation, then build, review and send the update. The browser constructs the transaction; no CLI transaction assembly is needed. - Keep and optionally share
proof.store.json, included in the proof bundle and available to download on the website. Loading it does not publish it. Without a shared store, the next prover must recover it from retained Beacon data or saved inputs; recovery can fail if neither is available. See store recovery. - Once the update confirms, open Claim a proven deposit, enter your deposit number and prepare a separate fee coin. Build, review and send the claim with Humid. This pays the deposit's original Liquid recipient, whoever pays the fee. Each update or claim spends its entire selected fee coin.
- If another claim changes the state before your update is sent, run
just proof-bundleand reload the bundle; no new proof is needed. If another update makes it stale, runjust status <deposit number>first: your deposit may already be proven. Rebuild stale claims directly in the browser.
To redeem SepETH, follow the instructions in the Redeem tab on the website.
For more detail, troubleshooting and alternative workflows, see the operator guide.