beth is an EVM-compatible Bitcoin sidechain, secured by the
BIP300/BIP301
"drivechain" two-way peg. It's a reth node -- execution
layer only, no consensus or wallet of its own beyond what's described below -- with a custom
consensus module, payload builder, and EVM wiring that implement the sidechain side of the peg:
Bitcoin deposits credited as ETH, ETH withdrawals paid out as Bitcoin, and each sidechain block
committing to a Bitcoin mainchain block via blind merged mining (BMM).
beth doesn't implement the mainchain side of BIP300/301 itself (sidechain proposals/acks,
withdrawal-bundle voting, wallet management) -- that's the job of a separate, chain-agnostic
bip300301_enforcer node, which beth
talks to over gRPC for everything peg-related. beth cannot run meaningfully on its own; it
needs a running enforcer (and, behind that, a regtest/real Bitcoin node) to talk to.
- Deposits (Bitcoin ->
beth): a user sends BTC to the sidechain's mainchain treasury output. The enforcer recognizes it and reports it tobeth. On its next block, every node independently recomputes the sameDepositVault.systemCreditDeposits(...)call from that already-agreed mainchain data -- a "system call" (the same no-transaction, protocol-level-state-write mechanism EIP-4788/2935/7002 use), gated onmsg.sender == SYSTEM_ADDRESS, applied during ordinary block execution. A forged or missing credit is therefore caught by standard state-root validation, with no bespoke consensus check needed. - Withdrawals (
beth-> Bitcoin): a user callsWithdrawalRequestQueue.requestWithdrawal().beth's own payload builder deterministically selects pending requests into a bundle on every block it builds and broadcasts the resulting BIP300 "M6" Bitcoin transaction to the enforcer. Mainchain miners vote the bundle through; once it's confirmed, the enforcer pays it out on L1, and the same system-call mechanism as deposits (WithdrawalRequestQueue.systemReportBundleLifecycle) advances each request's on-chain status. - BMM: every
bethblock'sextraDatacommits to the current Bitcoin mainchain tip. A "critical data transaction" bid for that block's hash gets embedded in the next mainchain block's coinbase (the BIP301 M7 accept message) --beth's consensus module checks the mainchain tip and a small window of recent ancestors for that commitment before accepting the block.
contracts/DepositVault.sol-- holds the sidechain's ETH reserve (genesis-funded); credits deposits via the system call above.contracts/WithdrawalRequestQueue.sol-- queues withdrawal requests and tracks their lifecycle (Pending->Bundled->Confirmed/Refunded); self-funding escrow, not genesis-funded.src/main.rs-- wires the pieces below into a standard reth node (EthereumNode), pointed at an enforcer viaBIP300301_ENFORCER_URL(defaulthttp://127.0.0.1:8080) and a sidechain slot viaBETH_SIDECHAIN_ID(default10).src/consensus.rs-- wraps reth's standard Ethereum consensus with the BIP301 BMM commitment check described above.src/payload.rs-- wraps reth's standard payload builder to stampextraDatawith the current mainchain tip and to select/broadcast withdrawal bundles.src/evm.rs-- wraps reth's standard EVM config to run the deposit-crediting and withdrawal-lifecycle system calls once per block, for both block building and block validation.src/deposit_vault.rs/src/withdrawal_bundle.rs-- the Rust-side logic for each system call (reading pending withdrawals and selecting a bundle; building the deposit-credit calldata).src/enforcer.rs/src/proto.rs-- the gRPC client for the enforcer'sValidatorService, generated from its own.protodefinitions (seebuild.rs).bip300301_enforcer/-- a git submodule vendoringbip300301_enforceritself, pinned to a specific, reviewed commit --build.rscompiles its.protodefinitions directly from here. The same approachthunder-rust(a reference UTXO-based BIP300/301 sidechain) uses. Not initialized by a plaingit clone-- see "Building" below.src/chainspec.rs--beth's own genesis (genesis.json, chain ID300301), embedded at compile time and used by default. Currently activates hardforks through Prague only, deliberately -- see the doc comment there for why (a real, confirmed limitation in this pinned reth version's Osaka/Amsterdam support, not something left undone).
beth's build compiles bip300301_enforcer's
own protobuf definitions directly (see build.rs), from the bip300301_enforcer/ git submodule
vendored in this repo (see .gitmodules) -- not a sibling checkout, and not optional: the
submodule needs to be checked out before cargo build will succeed.
git clone --recurse-submodules <this repo's URL>
# ...or, if you already have a plain (non-recursive) clone:
git submodule update --init
cargo build --releaseNeeds a reasonably recent Rust toolchain (rust-version = "1.95", edition 2024, in Cargo.toml).
beth needs a running bip300301_enforcer (and, behind that, a patched, BIP300/301-aware
Bitcoin node) to talk to before it does anything useful -- it won't do much of interest run on
its own. See scripts/ and in particular scripts/README.md
for everything needed to bring up that companion infrastructure (including building
bip300301_enforcer's actual binary from the same submodule beth compiled its proto
definitions from, so the two can never drift apart), run a full local regtest devnet, and drive
it end to end -- block production, deposits, withdrawals, and testing beth against a real,
unmodified Foundry workflow.