Skip to content

[PROPOSAL] RFP-021 — Sextant: an Ethereum light client inside a LEZ program, measured on mainnet #224

Open

Description

@edenbd1

RFP ID

RFP-021 — Trustless Ethereum State Attestation for LEZ

Your Project Name

Sextant

Team or Organization Name

Antumbra

Primary Contact

Telegram https://t.me/edenbdn or edenbaud1@gmail.com. Either reaches both of us within a day.

Team Members

Eden Baudin, engineer, privacy & ZK

Nada Ahmadi, engineer, DeFi

  • GitHub: https://github.com/nadaamd
  • LinkedIn: https://www.linkedin.com/in/nada-ahmadi/
  • Role: the Logos core module and its API, the CLI and its dry run, the update-submitting module and its headless daemon (the operator journey), the reference consumer programs and their clients, the IDL, the module catalog.
  • Availability: full time, for the whole grant.
  • ETHGlobal Lisbon 2026, Umbra: 3rd place, Uniswap Foundation "Best Uniswap API Integration" (showcase), an autonomous circuit breaker for DeFi positions, backtested over 48,000 real swaps.
  • Full-stack developer at XRPL Commons (LinkedIn above). Member of DeVinci Blockchain, ESILV.

Continuity. Both of us can build, deploy and drive the program. Every deployment we make ships with a runbook from a clean checkout and a script that re-checks it against the chain, as in antumbra-lez/docs/deploy-v03.md and scripts/verify-onchain.sh. The same discipline applies here: what the program does and why lives in the repository and its decision log, not in one person's head.

Project Summary

Verifiable from a clean clone, from the prototype repository https://github.com/edenbd1/lez-sextant (program sextant_lc, built on LEZ v0.3.0 with RISC Zero 3.0.5; every test runs on real Ethereum mainnet data committed in vectors/mainnet/):

git clone https://github.com/edenbd1/lez-sextant && cd lez-sextant
cargo test --release --manifest-path host/Cargo.toml             # 30 tests: the verification core, natively (mainnet, Hoodi, Sepolia)
cargo test --release --manifest-path executor-tests/Cargo.toml   # 16 tests: the committed binaries (light client, consumer) through lee::V03State
cargo run --release --manifest-path executor-tests/Cargo.toml --bin measure_lez   # gas of every instruction
cargo run --release --manifest-path host/Cargo.toml --bin measure                 # cycles: two libraries, stock vs accelerated
RISC0_DEV_MODE=0 SEGMENT_PO2=18 cargo run --release --manifest-path executor-tests/Cargo.toml --bin prove   # real proofs, per step
scripts/verify-onchain.sh             # the public testnet run, re-checked against the sequencer's RPC (python3 only)
scripts/verify-onchain.sh --manifest evidence/testnet-v2/run.tsv   # the v2 program's run, the same way
  • A real Ethereum chain, followed inside a LEZ program. From a checkpoint at mainnet slot 15,409,152, the program verifies three sync-committee handoffs (periods 1881 to 1884) and a finality update, through lee::V03State, the state machine the v0.3 sequencer applies, under the 10,000,000 gas cap (executor-tests/tests/program.rs). Each update is three transactions of 3.7M to 5.5M gas, each signed by an account the chain has never seen. It then attests, under the finalized execution block 26,197,200, an ERC-20 Transfer log (90,708 gas) and a WETH balance slot (304,267 gas). The committed binary is RISC Zero's reproducible Docker build (ImageID ad6fbedb… since v2 was merged, fa229edd… before), rebuilt and compared byte for byte in CI.
  • Deployed and driven on the public LEZ testnet (v0.3.1), program header A89TcYmWKXrzGkTUzLAxVExAB6HxSD6yf3dvjWMoiFzw (docs/ONCHAIN.md): the full chain of the vectors, to period 1884. Init from the mainnet checkpoint, updates 1 to 4 across their three transactions each (handoffs at periods 1881 to 1883), to the finalized execution block 26,197,200; attestations accepted under the checkpoint's execution block 26,170,219 and under the last header (the Transfer log and the WETH balance slot), and seven refusals. 24 transactions, 17 applied and 7 refused, each charged on chain its pre-checked gas or, refused, its whole limit; e.g. update 2's Begin dcf68b94…. The instance ends with the same 379 bytes as the local v0.3.1 run of the same chain (36 transactions, 22 applied, 14 refused, including the refusals that cost a whole Begin). The verifier reads each verdict from the block's fee summary: 37 checks pass, 0 fail, and a mutation test shows it catches 15 corruptions of the manifest. Two of update 4's transactions were later dropped from the sequencer's hash index by a block rewrite. The verifier finds them by scanning the blocks and reports them under their own status, proven by the final state.
  • A consumer program, built (docs/CONSUMER.md): erc20_receiver credits an ERC-20 Transfer once. In one transaction it reads the header record, spends a nullifier keyed by sextant's statement identifier, and calls Attest as a chained call with five predicates. A Claim costs 171,193 gas including the chained Attest, 1.7% of the cap; a replay is refused (E7391), and a wrong amount, recipient or token is refused by Attest (E7208) with nothing credited. F.6 has its own tests (host/tests/statement.rs): identical header and statement give the same identifier, and different state gives a different one. Executor tests only for now, not deployed.
  • Branch v2: E7206 and Gloas, on Hoodi and Sepolia (docs/DECISIONS.md D7 and D9). The weak-subjectivity rule is implemented: an update signed more than the configured bound after the store's finalized header is refused with E7206, and Init, Begin and Finish declare a timestamp window, so the sequencer's own clock refuses a stale store. Sepolia moved to Gloas on 2026-10-06 and Hoodi follows on 2026-10-26, so the program now knows both light-client header shapes, chosen by a Gloas epoch fixed at Init. Real vectors for both testnets are committed, and one Sepolia instance crosses the fork from a Fulu checkpoint. The rebuilt binary (reproducible, ImageID ad6fbedb…) runs Hoodi, Sepolia and the fork crossing through lee::V03State, every step under 60% of the cap (executor-tests/tests/testnets.rs). It changes the instance's wire format, so it is a new deployment, not an upgrade. It is deployed on the public LEZ testnet as program Ebe8rifvjyvwWUXnk7bsKeTwuyCAFSRMFuPMVHE8CbUB and has run the same mainnet chain to period 1884 on 2026-10-17, before the committed checkpoints stop being usable (2026-10-25): Init, updates 1 to 4, the attestations against both headers and six refusals, 23 transactions (17 applied, 6 refused), e.g. update 4's Finish c016b5b2…. Verifier 36 checks, 0 fail; mutation test 7 of 7 (docs/ONCHAIN.md). v2 is merged into main.
  • The Logos Ethereum core module, read against what Sextant needs (docs/ETH-CORE-MODULE.md). logos-verified-proxy-module serves verified finalized blocks, block receipts and storage values, and its doc-test already follows post-Gloas Sepolia. It exposes neither of the two things the program consumes: the light-client bootstrap, updates and finality update (fetched only internally), and raw eth_getProof nodes (verified, then dropped). Both are reachable through the C library's host-supplied transports, which the module leaves unset; we upstream the three methods (light-client objects, ethGetProof, verifyCheckpoint) as M3 work.
  • The RFP's central feasibility question, answered with numbers (docs/BENCHMARKS.md): verifying one sync-committee update in-program does not fit one transaction: 12.6M cycles with a handoff and 11.2M without, with the cheapest library and RISC Zero's accelerators; 27.0M with the library the existing zkVM light clients build on; 177M to 4,013M without accelerators. It does fit three, each near the 5M block target.
  • Real proofs, never development mode (Apple M4 Pro, CPU): the three update steps of the committed binary prove in 691 s, 544 s and 477 s (receipts 226 to 329 kB); the consumer's whole inclusion-and-predicate step, what a user's device proves in a private transaction, is 56,291 cycles and proves in 48 s.
  • The adversarial cases the RFP names, refused with typed codes (in the test suite and on the local sequencer; five attestation refusals and a step-order refusal also on the testnet): a fabricated chain, a committee never handed off, participation below two thirds, a header not finalized, a tampered execution root, another chain's domain, a duplicate update, a forged signature (E7203 at the final step, nothing moved), a predicate over state the proof does not cover, and on v2 a stale store (E7206) and a mainnet update offered to a Hoodi or Sepolia instance.
  • Design and decision log: README.md, docs/DECISIONS.md.

Requested: $60,000 over about 10 weeks of build, within the RFP's 8 to 12, in six milestones paid on acceptance. The external audit is a separate line item (Supportability #8): a $35,000 allowance (not a vendor quote), with Spearbit (Cantina as the alternative), contracted and paid by Logos directly, outside our $60,000 request. Our remediation work is milestone M5.

In two sentences. A LEZ program that runs the Ethereum light client itself, verifying each sync-committee update across three permissionless transactions and recording every finalized header it verifies as an immutable account; any LEZ program then attests an account, a storage slot, a log or a transaction under one of those headers inside its own transaction, for well under a tenth of the budget. No signer, federation or designated operator is trusted for anything: trust enters once, through a checkpoint anyone can check.

Every LEZ program that wants to act on something that happened on Ethereum needs the same thing: to know that a header is a finalized header of the real chain, before any Merkle proof against it means anything. That is a light client, and the expensive part of a light client is BLS12-381: one aggregate signature check per update, with a pairing at its core. The RFP asks whether that fits LEZ's per-transaction budget. We built the light client as a LEZ program and measured it on mainnet's own updates.

What the measurements decided. The library matters more than any trick: with RISC Zero's accelerated blst, the pairing check costs 7.85M cycles; with the accelerated zkcrypto bls12_381 that zkVM light clients are built on, 19.7M, and its final exponentiation alone exceeds a transaction. How the committee's 512 keys are handled matters next: decompressing and checking every key costs 234M cycles; starting from the committee's own aggregate key and subtracting the few that did not sign, from witnessed uncompressed points, costs 0.45M. With both, a whole update costs 11.2M to 12.7M, still above 10M, so the program splits it along the pairing's natural seam into three transactions, each under 5.5M. A BLS12-381 precompile would make the update fit one transaction; this proposal supplies the measurement a follow-on RFP needs, and does not depend on it.

What already exists, and what does not. We have shipped LEZ programs on both runtime generations and taken four of them (this one, Accord, the vesting program, the auction) to the v0.3 public testnet with a verifier anyone can run. For this RFP, the prototype above is the verification core and the program, tested on real mainnet data through the state machine the sequencer runs. It is deployed on the public LEZ testnet, where it has run Init, the four sync-committee updates of the vectors to period 1884 and consumer attestations against the first and last headers. One reference consumer, erc20_receiver, runs in the executor tests, and the weak-subjectivity rule and Gloas, with Hoodi and Sepolia vectors, are merged from v2 and deployed as a second program that has run the same chain. No instance tracks a live chain yet, and the core module and the update daemon do not exist yet (the prototype's client only drives the committed vectors); the milestone table states, piece by piece, what is built and what is priced as remaining work.

Technical Approach

1. The measurement behind the design (Performance #1, #2, #8)

Every figure is a RISC Zero executor count (the quantity LEZ meters as gas) on the update of sync-committee period 1882 (512 of 512 signed), from docs/BENCHMARKS.md and its raw output. Both libraries run the same code paths in the same guest and agree on every verdict.

Step blst, accelerated zkcrypto, accelerated blst, stock
(a) pairing check 7,849,796 19,725,300 129,951,187
hash_to_curve G2 1,192,728 2,815,078 25,201,148
signature decode + subgroup check 545,356 1,466,435 11,153,316
(b) signing key, 512 keys decompressed and checked 233,602,653 525,079,512 3,826,823,461
(b) signing key, aggregate minus non-signers 448,732 451,573 539,272
(b) same, 342 of 512 signed (the floor) 2,324,517 2,891,995 17,233,901
committee hash_tree_root (512 keys, SHA-256) 1,509,370 9,751,472
(c) whole update with handoff, best path 12,611,850 27,032,756 186,486,933
(c) whole update, finality only 11,225,342 177,238,486

What fits one transaction, and what does not:

  • Fits: the pairing check alone (blst, 78% of the cap), everything else of an update together (4.8M with a handoff, 3.4M without), and every consumer attestation (at most 3%).
  • Does not fit: a whole update with any library (112% to 270% of the cap); any update that decompresses its committee's keys (23 to 55 times the cap); zkcrypto's final exponentiation alone (108%).
  • The precompile delta (P.8): a native BLS12-381 precompile would take the 9.6M cycles of key assembly, hashing to G2, signature decoding and pairing out of an update, leaving about 3M with a handoff, so an update would fit one transaction. A precompile is reachable only from public transactions; updates are public, consumer attestations need no BLS at all, so the private path would not need it. Our documentation states the delta per operation as measured; a precompile's own cost is for the follow-on RFP to specify.

2. Architecture: one program, three kinds of account (F.1, F.7, F.9, F.15)

Account (program PDA) Holds Written by
instance [instance_id] checkpoint, chain configuration (chain id, genesis validators root, fork schedule, participation floor) and the tracked store (finalized header, its execution roots, current and next committee roots) Init once; Finish replaces the store, never the configuration
job sha256("job", instance_id, job_id) one update in progress: the signing key, the message point, the signature, then the Miller product, and digests of the store before and after Begin, Miller; released by Finish
header record sha256("header", instance_id, slot) chain id, slot, beacon block root, block number, execution state root, receipts root (120 bytes) Init, Finish; written once, never modified

Advancing the light client: three transactions, anyone can send any of them.

  1. Begin runs every check but the pairing: ordering and periods, participation against the floor, the supplied committee against the stored root, the finality branch, the execution branch, the next committee's branch, the signing root, hash to G2, signature decoding with its subgroup check. It opens the job. 5.34M to 5.47M gas with a handoff, 3.66M without.
  2. Miller runs the two Miller loops and stores their product. 4.22M.
  3. Finish runs the final exponentiation and, in the same transaction, replaces the store and writes the new header record. 3.77M.

A v0.3 plan cannot read accounts, so Begin carries the store it builds on, and every apply checks a digest of the bytes it is given; a job's id binds the store it starts from and the update, so a job built against a store that has since moved can never land (E7293), and two operators racing one update converge on one job. Splitting in three rather than two keeps every step near the 5M block target instead of filling 80% of a block (decision D2); a two-step variant is a one-instruction change if Logos prefers it.

Attesting: inside the consumer's transaction. Attest { instance, slot, query } reads one header record, verifies a Merkle-Patricia proof against its state or receipts root, evaluates the predicates, and refuses otherwise. A consumer program calls it as a chained call in its own transaction, by a constant ProgramId, so verification and the action it authorises land or fail together, and nothing computed in an earlier transaction is acted on (F.7). Whether consumers instead run the same inclusion code in their own apply through a v0.3 inspect guard is settled in M2, with both measured (D5).

Instances (F.14, F.15). One immutable program, any number of instances: each Init names a fresh instance_id and fixes its chain. In the tests the same binary hosts two instances with different chain ids side by side, a second Init cannot touch the first, and a query naming one chain is refused against the other's record (the chain id is in every record and every query).

3. The light client (F.1, F.2, F.9, F.10, F.13)

The rules are the consensus specs' (Altair sync-protocol.md, finalized updates only, Electra generalized indices that mainnet uses since May 2025), checked on mainnet's own updates. Init verifies the bootstrap: the header must hash to the checkpoint, its execution payload must sit under its body root, the current committee under its state root (F.9). There is no instruction that touches the configuration or checkpoint afterwards, whoever calls (F.13); a second Init at the same id is refused. An update is accepted only if it is finalized (F.2): the finalized header is in the attested state through the finality branch, and the signature over the attested header is by the committee the store holds, with at least the configured participation (two thirds of 512 by default; the program refuses a floor below that).

The signing key (decision D3). The submitter supplies uncompressed points; the program checks each is on the curve and compresses to the committed bytes, starts from the committee's own aggregate key, committed in the same SSZ root, and subtracts the non-signers. No subgroup check is needed: each key's bytes are bound, through a verified committee root, to a beacon state that admitted the key only after verifying its proof of possession at deposit.

Weak subjectivity (F.10). Built on branch v2 (D9): Begin refuses (E7206) an update whose signature slot is more than the instance's weak-subjectivity bound past its finalized header, and Init, Begin and Finish declare a timestamp window that closes at that bound, so the sequencer's clock refuses a stale store too. Attest declares none, so recorded headers stay attestable (R.3). Such an instance cannot be refreshed, by construction; it is replaced by a fresh Init. The consumer documentation says when (the instance's last record is older than the period), how to detect it (the Advanced events stop, or Begin returns E7206) and how to migrate (bind to the new instance's id after checking its checkpoint).

Checkpoint verification (F.9). The core module exposes verifyCheckpoint(instance): it reads the instance's checkpoint and bootstrap header from LEZ, fetches the same block root at the same slot through the Logos Ethereum core module (logos-verified-proxy-module), configured with several independent beacon endpoints, and reports agreement, any disagreement, and the checkpoint's age against the weak-subjectivity period. We contribute verifyCheckpoint upstream to the Logos Ethereum core module in M3, as for raw updates (§13); sextant_module calls it.

Forks (D7). The signing domain's fork version comes from the configuration in the prototype; M1 moves to reading it from the attested beacon state's own fork field, which is safe (a wrong version only makes an honest signature fail) and spares a re-initialisation at a scheduled fork. Gloas, which changes the light-client header, is already live on Sepolia (2026-10-06) and reaches Hoodi on 2026-10-26. Branch v2 handles both shapes: the Gloas epoch is fixed at Init, the Electra or Gloas generalized indices follow the header's slot, and a Gloas header comes with its RLP execution header, kept only if its keccak is the proven execution_block_hash. One Sepolia instance crosses the fork in the tests. A future container-changing fork still needs a new program version, released before activation, with the migration documented as Verification Security #3 requires.

4. Inclusion, predicates, statements (F.3 to F.6)

  • An account and a storage slot: eth_getProof nodes against the record's state root (WETH's balanceOf slot of a mainnet pool: 9 + 7 nodes, 187,776 cycles in the guest, 304,267 gas through the program).
  • An event log by its position in a receipt: a receipts-trie proof against the receipts root (1,242 bytes, 47,737 cycles; 90,708 gas through the program). The prototype builds the proof locally from the block's full receipts and checks the trie root against the block's (scripts/receipt_proof.py), so a provider learns only which block was fetched. A log from a reverted transaction proves nothing and is refused.
  • A transaction (M2), for native ether sent to a contract with no event: the transaction under the payload's transactions_root, its receipt's success status, and the sender recovered from its secp256k1 signature with RISC Zero's accelerated k256, the crate our multisig program already uses for in-guest signature checks.
  • Predicates: =, ≠, <, ≤, >, ≥ over 32-byte words a proof exposes (balance, nonce, slot value, log address, topic i, data word i), several per query, all over the same header (soft RFP-001: Program Authority PoC #4). A consumer expresses its predicate in the instruction; the module is never modified (F.4).
  • Chain binding (F.5): every header record carries the instance's chain id, every query states the chain it expects, and a mismatch is E7209. The signature domain itself binds the genesis validators root, so a testnet update cannot verify on a mainnet instance (another_chain_is_refused). On v2, Hoodi and Sepolia instances refuse mainnet updates and the reverse, and a Hoodi or Sepolia statement is refused by a mainnet consumer. M2 adds the test the RFP names: the same contract address on mainnet and a testnet.
  • Statement identifier (F.6, D6): sha256 over the chain and the attested state itself: for an event, its block, transaction index, log index and content; for state, the account, the slot and the values, so identical state yields the same identifier whichever finalized block it was read at. The consumer keys its nullifier on it. Tested on its own: f6_the_statement_identifier_is_identical_for_the_same_header_and_statement and f6_the_statement_identifier_differs_for_different_state.

5. Privacy (F.8) and the private path (Performance #4)

Attest writes nothing and emits nothing: the verified statement is the consumer's to expose, so nothing in the event or state diff names the consuming account or transaction. The one public trace of an attestation in a private transaction is which header record it reads. Consumers therefore attest against the latest record and reach their event's block through the beacon state's block_roots and historical_summaries (soft #3, M2), so the record read reveals only that the event is older than it, which the submission time already reveals. Header records are immutable, so a private transaction's result does not depend on anything an update can change (R.11).

What a user's device proves is the consumer step: 56,291 cycles for a log attestation, proved in 48 s on an Apple M4 Pro CPU with one segment and a 226 kB succinct receipt. The privacy-preserving transaction that composes it is measured end to end in M2, wall-clock and peak memory, on a mid-range laptop and on the minimum we declare (16 GB RAM, 8 cores, CPU proving). On our 24 GB machine with other applications open, proving an update step at the default 2^20-cycle segments, then at 2^19, drove the system 13 to 14 GB into swap; at 2^18 the steps proved without growing it, and they prove at 2^18 on the committed binary in 691 s, 544 s and 477 s. The client therefore proves with a segment size chosen from available memory.

6. Permissionless operation and every off-chain participant (F.11, F.12, R.8 to R.10, Verification Security #6)

No step checks who signs it; the executor tests sign every step with an account the chain has never seen. An update is accepted on its validity alone.

Participant Needed for Safety or liveness Why it cannot block an attestation
Update submitter (our daemon or anyone's) advancing the instance liveness only: it cannot insert a header the committee did not sign anyone can run one from any endpoint, with LEZ gas and no standing on Ethereum
Beacon API provider update material liveness only: every byte is verified in-program the material is public and served by every consensus client
Execution RPC provider eth_getProof, block receipts liveness only: every proof is verified against a recorded root any archive or full node serves it; receipts can come whole, by block
Checkpoint source Init safety, once: a wrong checkpoint means a wrong chain checked by anyone through verifyCheckpoint before relying on an instance; a bad instance is abandoned, not repaired
LEZ sequencer inclusion liveness, as for every LEZ program not specific to this design

The submitting daemon. Before each step it re-reads the instance and the job and sends nothing that another operator already applied (R.9); a duplicate is refused cleanly by the program anyway (E7293, E7294, R.8). It defers by a delay drawn per update from a per-operator seed, within a window the operator configures (default 0 to 2 LEZ blocks per step), so over many updates the first submitter and the gas it pays rotate across operators, and it submits promptly when the delay elapses and nobody acted (R.10). The deferral's effect on latency, at most the window per step, is documented with the operator guide.

7. Module, CLI, daemon, IDL, consumers (Usability #1 to #6)

  • Logos core module sextant_module: produce an attestation (fetch through the Logos Ethereum core module, build the proof, the query and the transaction), verify one off-chain, submit an update's three steps, read an instance's checkpoint and configuration, verifyCheckpoint. Its C ABI sits on sextant-core, the crate the program runs, so types, digests and checks cannot drift between the program and its clients. What it can take from the Logos Ethereum core module today, and the three methods we add upstream, are in docs/ETH-CORE-MODULE.md.
  • CLI on the Logos core headless framework: attest (chain, contract, slot | log, predicate), verify --dry-run (the same core, the same typed codes as the program, U.2), update, instance show.
  • Update-submitting module and headless daemon: follows Ethereum through logos-verified-proxy-module (consensus and execution endpoints configurable), detects when the instance is behind by comparing its finalized slot with the beacon's, submits the outstanding periods in order, logs structured JSON, resumes from the instance's own state after a restart (R.4, R.5), stops cleanly on a signal. Its operator journey (install, configure, run, monitor) is documented, with what an operator pays per period, and the statement that nobody is obliged to operate and that liveness rests on interested parties.
  • Typed errors (U.5): all fifteen verification refusals plus the program's own are stable codes with an actionable message (programs/sextant/src/lib.rs): header not finalized E7202, signature invalid E7203, handoff not verified E7204, below participation E7205, checkpoint stale E7206, inclusion invalid E7207, predicate false E7208, chain mismatch E7209, malformed input E7201, already applied E7210 and E7293.
  • IDL in SPEL's JSON shape. SPEL does not support v0.3, so, as in our other v0.3 programs, it is maintained in SPEL's format with a CI check that decodes the program's own borsh vectors using only the IDL; it switches to generated output when SPEL supports the target runtime.
  • Reference consumers (U.6), each with a nullifier keyed on the statement id: erc20_receiver (a Transfer of token T to contract C: token, sender, recipient, amount from the log; built, 171,193 gas a claim) and ether_receiver (native ether to contract C: sender, recipient, amount from the transaction proof).

8. Reliability (R.1 to R.12)

Tested today, through the program on mainnet data: deterministic verdicts from committed vectors (R.1); a rejected update consumes nothing and leaves the instance able to retry (a_bad_signature_is_refused_at_finish_and_nothing_moves, R.2); header records are written once, so an attestation that verified keeps verifying (R.3); a stalled instance resumes when anyone submits the missing periods, in order, with no gap (a_stalled_instance_resumes_without_repair, R.7); a duplicate is refused cleanly, not double-applied (a_competing_operator_is_refused_cleanly, R.8); attestations read only immutable records, so they are independent of one another and of updates (R.11). Delivered with the daemon and CI (M3, M4): restart and RPC-failure recovery from the instance's own state, never re-verifying from the checkpoint and never accepting an unverified handoff (R.4, R.5); the contention check and deferral (R.9, R.10); CI green on the default branch with every job executed (R.12). Reuse (R.6) is decision D8: the consensus-specs algorithm and blst unchanged; r0vm-helios and Helios assessed and not vendored, because their output is a proof for an EVM contract and their types bring a client's dependency tree into a LEZ program; the light-client logic we need is about 360 lines, tested on mainnet data; M0 writes the assessment up with its audit-surface consequences.

9. Cost, latency and storage (Performance #2 to #9)

  • Per action (P.2): cycles and gas for Init, each update step with and without a handoff, and each statement type, from the test suite; proof time and receipt size per action with real proving (today: Begin 691 s and 329 kB, Miller 544 s and 227 kB, Finish 477 s and 226 kB, Attest 48 s and 226 kB), restated on testnet 0.4 in M4.
  • Amortisation (P.3): the instance must ingest at least one update per sync-committee period (27.3 hours) to follow handoffs: about 13.4M gas. Fresher headers cost about 11.6M each: one an hour is about 279M gas a day, shared by every consumer reading those headers, while each attestation pays only its own 0.09M to 0.3M.
  • Latency (P.5): Ethereum finality (two to three epochs, 13 to 19 minutes), then the next finality update (seconds after the attested block), then the deferral and three LEZ blocks.
  • Off-chain resources (P.6): the daemon verifies nothing itself and holds no state beyond the instance's; measured CPU, memory and bandwidth are published with it.
  • Storage (P.7, P.9): the instance holds at most 411 bytes (379 when no next committee is known; 24 more on v2); a job's 1,137 bytes are released at Finish; header records grow by 120 bytes each: 38 kB a year at one per period, about 1 MB a year at one an hour, 5 MB over five years. No action reads more than one record or depends on how many exist.

10. Verification Security, Supportability and the trust document

  • VS Missing Required License Files #1 and Some suggestions #2 are named tests on mainnet data: a_fabricated_chain_is_refused builds a self-consistent header whose execution branch and finality branch both verify, and is refused on the signature; and each case of VS Some suggestions #2 has its test, through the program in executor-tests/tests/program.rs.
  • VS RFP-001 & 002: Program Authorities PoC #3: no verification is reached through a ProgramId held in account state; the consumer documentation states the same rule for consumers, and the migration path to a new program version.
  • VS RFP-001: Program Authority PoC #4, the trust-assumptions document (S.6): a colluding or compromised supermajority of one sync committee (342 of 512 validators, drawn at random weighted by stake) can sign a header off the canonical chain, and sync-committee signatures are not slashable today, so the bound is the stake needed to hold two thirds of a committee, not stake burnt. Consumers bound their exposure with per-period caps and pause authorities (RFP-022's pattern), and, for large values, by acting only once a later header signed by the next committee is recorded, so that two successive committees would have to collude. The document also covers the checkpoint, what an attestation does and does not prove, what each participant above can observe (an RPC provider sees which proofs are asked for; receipts can be fetched by block), and when guarantees degrade.
  • S.1 to S.3: the program on LEZ testnet 0.4, tracking a public Ethereum testnet (Sepolia or Hoodi), end-to-end tests against a standalone sequencer and an Ethereum test network in CI, integration documentation for consumers with the Recommended Consumer Pattern (S.4): a nullifier on the statement id, the chain-binding check, handling of every typed code, and, when no attestation can be produced, refusing the action rather than falling back to an unverified input. The user and developer docs state the trustless model and that every off-chain participant has only a liveness role (VS.5).
  • S.9, S.10: the modules released to our own catalog from the Logos template with logos-modules-release-action; Logos Forum already ships from a catalog of our own. S.11: the decision log, nine entries.

11. Soft requirements

Recorded-statement account (#1): an AttestAndRecord that shares Attest's core and writes the statement to a public account, documented as trusting whoever wrote it; batching (#2): several queries against one record in one call; historical state (#3): block_roots and historical_summaries proofs, which the privacy design above relies on; compound predicates (#4): built; operator incentive (#5): an optional tip account any party can fund, paid by Finish to the submitter of the step that completes the update, open to anyone.

12. Audit programme (Supportability #8)

  • Firm: Spearbit (Cantina as the alternative), engaged for the consensus and inclusion verification logic: sextant-core, the program shell, and the accelerated layer of RISC Zero's blst fork as it is used.
  • Budget line item: a $35,000 allowance, not a vendor quote, contracted and paid by Logos directly to the firm, outside our $60,000 request. About 1,700 lines of Rust plus the fork's acceleration layer: about 1.5 auditor-weeks at tier-1 rates, review and a re-check of the remediation. We ask the firm for a scoping estimate during M0 and raise any gap with Logos at the M0 gate.
  • Timeline: booked during M1, run on the code frozen at the end of M4, remediation re-checked in M5. No production deployment of the software is recommended before that.
  • Publication: the report and our remediation notes are published in the repository.

13. Risks, stated

  • The cost could move. A fork, a LEZ cost change or a new committee size could push a step over the cap. Each step today is at most 55% of it; from M1 a CI gate fails if any step's measured gas crosses 70%, and a fourth step is a contained change.
  • The private path. The consumer step proves in under a minute; the composed privacy-preserving transaction is measured in M2. If it is too slow on the declared minimum, the shortfall is documented by component, as P.1 allows.
  • The Logos Ethereum core module. It does not expose light-client objects or eth_getProof nodes today (docs/ETH-CORE-MODULE.md). We contribute those methods upstream in M3, through the library's host-supplied transports. Until they land, the daemon fetches from the endpoints the module is configured with and cross-checks against its verified finalized block before submitting anything.
  • Testnet 0.4 breaks programs. The logic is in sextant-core, which does not depend on the runtime; the shell is about 320 lines and is the only part that ports (M4). Until 0.4 ships, milestones are accepted on 0.3.
  • Gloas. Handled on v2, ahead of Hoodi's fork on 2026-10-26. Because the wire format changed, v2 is a new deployment, and the committed checkpoints stop being usable for Init from 2026-10-25 (the staleness window), so it was redeployed before then: the v2 program ran the chain to period 1884 on 2026-10-17.

14. Requirement traceability

Requirement Where Milestone
F.1 light client in the program §2, §3 M1
F.2 finalized headers only §3 M1
F.3 account, storage slot, log §4 M2
F.4 predicates, consumer-expressed §4 M2
F.5 chain binding, mainnet and testnet §2, §4 M2
F.6 statement identifier §4 M2
F.7 in-consumer verification, testnet §2 M1, M2
F.8 no new correlation signal §5 M2
F.9, F.13 immutable configuration, verifyCheckpoint §3 M1, M3
F.10 weak subjectivity and replacement §3 M1
F.11, F.12 permissionless; participants §6 M1, M3
F.14, F.15 any chain; many instances §2 M1
U.1 to U.3 module, CLI, daemon §7 M3
U.4 IDL §7 M3
U.5 typed errors §7 M1, M3
U.6 reference consumers §7 M2
R.1 to R.12 §8 M1 to M4
P.1, P.2, P.8 cost per action, precompile delta §1, §9 M0, M4
P.3, P.5 to P.7, P.9 amortisation, latency, resources, storage §9 M4
P.4 private path on laptops §5 M2
S.1 to S.3 testnet 0.4, e2e CI, docs §10 M4
S.4 Recommended Consumer Pattern §10 M2, M4
S.5 to S.7 CLI and operator docs, trust document, operator role §6, §10 M3, M4
S.8 audit: Spearbit (Cantina alternative); a $35,000 allowance (not a quote), paid by Logos directly, outside our request; report published §12, milestone table M5 (audit after M4)
S.9, S.10 own catalog, release action §10 M4
S.11 decision log §10 M0 onward
VS.1 to VS.6 §6, §10 M1, M2
Soft #1 to #5 §11 M2, M3

Milestones, Payout and Timeline

Six milestones, about 10 weeks of build, then remediation once the audit reports. Two tracks: Eden on the program and the measurements (M0 → M1 → M2), Nada on the module, CLI and daemon (M3, from week 2), joined in M4. Each milestone says what is built today, with the test or file that shows it, and what remains; the payouts price what remains. Every milestone is accepted on evidence anyone can re-run. Start and availability. We have one other open proposal in this program (RFP-005). If both are funded, this one starts when that proposal's first milestone is accepted, about five weeks after signing, so each runs with the full-time team it states; otherwise it starts within two weeks of signing.

# Deliverable Effort Status today Payout
M0 Reuse assessment, measurements, design, threat model 1 wk partial: the cycle and gas tables, eight decisions, the program $5,000
M1 Light-client program on testnet 3 wk partial: Init, the three update steps and header records on mainnet data; deployed on the public testnet with four updates applied; E7206 and Gloas, deployed and run to period 1884 $15,000
M2 Attestation path, private path, reference consumers 3 wk partial: Attest for storage slots and logs, predicates, statement ids, erc20_receiver $13,000
M3 Logos core module, CLI, update daemon, IDL 3 wk (parallel) partial: the decoders and witness builder the module wraps $13,000
M4 Testnet 0.4, end-to-end CI, benchmarks, documentation, catalog 2 wk to build $10,000
M5 Audit remediation and re-verification 1 wk, after the audit to build $4,000
Six milestones, paid on acceptance about 10 wk + M5 $60,000
External audit, Spearbit (Cantina alternative): separate line item, contracted and paid by Logos directly, not part of our request after M4 $35,000 allowance

M0: Reuse assessment, measurements, design, threat model. 1 week, $5,000
The reuse assessment against r0vm-helios, SP1 Helios and Helios, stating what is reused, rewritten, and the audit-surface consequence (R.6); the cycle tables re-run on a quiet machine; real-proving times per step; the threat model and the outline of the trust-assumptions document; the decision log current. Accepted when the harness reproduces the published tables from a clean clone and the documents are merged.
Status today: partial. docs/BENCHMARKS.md with raw outputs: the library and key-handling comparisons, gas per instruction, real proofs of every step; decisions D1 to D9 in docs/DECISIONS.md. Remaining: the written assessment, the threat model, the audit scoping estimate, the proofs re-run on a quiet machine.

M1: Light-client program on testnet. 3 weeks, $15,000
Init, Begin, Miller, Finish, header records, events (LP-0012) for every step and every advance; the weak-subjectivity rule (F.10); fork versions from the beacon state (D7); every refusal code; deployed on the public LEZ testnet with an instance tracking a public Ethereum testnet, advanced over several periods by fresh accounts. Accepted when the verifier script passes against the deployment from a clean clone and F.1, F.2, F.9 to F.15, VS.1, VS.2 and VS.6 each have a named test or transaction in the traceability table.
Status today: partial. The program runs a real mainnet chain through lee::V03State: 3 handoffs and a finality update from a checkpoint, every step under 55% of the cap, signed by fresh accounts; 19 host tests and 11 tests of the committed binaries, including the fabricated chain, the duplicate, the stalled instance and the forged signature. The reproducible binary is deployed on the public LEZ testnet with its manifest and a verifier that passes from a clean clone: Init, updates 1 to 4 to period 1884, four attestations accepted and seven refusals, 24 transactions (docs/ONCHAIN.md). Merged from branch v2 and deployed as a second program (the same chain to period 1884, 23 transactions, verifier 36/0): the staleness rule (E7206, typed and by the sequencer's clock), Gloas light-client headers, and Hoodi, Sepolia and a Sepolia fork crossing through the rebuilt binary (30 host tests, 16 executor tests). Remaining: fork versions from state, and an instance tracking a public Ethereum testnet advanced over several periods on the LEZ testnet.

M2: Attestation path, private path, reference consumers. 3 weeks, $13,000
Transaction proofs (native ether); historical headers through block_roots and historical_summaries; the chained-call versus inspect decision (D5), measured; the same-address mainnet/testnet test (F.5); erc20_receiver and ether_receiver with nullifiers on testnet; an attestation inside a privacy-preserving transaction, with wall-clock and peak memory on a mid-range laptop and on the declared minimum; the recorded-statement path and batching (soft #1, #2). Accepted when both consumers mint against real testnet deposits exactly once each and the private-path table is published.
Status today: partial. Attest verifies a WETH storage slot and an ERC-20 Transfer log under a mainnet finalized block through the program, with predicates and refusals (attestations_verify_and_refuse_with_typed_codes), and the consumer step proves in 48 s; on the public testnet four attestations were accepted, under the checkpoint's header and under the header of block 26,197,200, and five refused. erc20_receiver credits a Transfer once through a chained Attest and a nullifier on the statement identifier, with its replay and wrong-predicate refusals, in the executor tests (docs/CONSUMER.md). Remaining: everything else above, including ether_receiver and both consumers on the testnet.

M3: Logos core module, CLI, update daemon, IDL. 3 weeks, $13,000
sextant_module with its API documentation (U.1); the CLI with its dry run returning the program's codes (U.2); the update-submitting module and headless daemon on the Logos Ethereum core module, with contention checks, per-operator deferral, structured logs, clean shutdown and its operator guide (U.3, R.4, R.5, R.9, R.10, S.7); the IDL with its drift check (U.4). Accepted when two daemons run against the same testnet instance for a week of periods and every update is applied once, with no double spend of gas beyond contention.
Status today: partial. The host crate decodes beacon-API and JSON-RPC data into the program's types and builds key witnesses and receipt proofs; the prototype's client (cli/) deploys the program and sends every instruction with a pre-check through lee::V03State that returns the program's codes, from the committed vectors only; what the Logos Ethereum core module serves and lacks is written up, with the upstream changes (docs/ETH-CORE-MODULE.md); the module, the user-facing CLI and the daemon are to build.

M4: Testnet 0.4, end-to-end CI, benchmarks, documentation, catalog. 2 weeks, $10,000
The port to testnet 0.4 and the lifecycle re-driven there; end-to-end tests against a standalone sequencer and an Ethereum test network in CI; every Performance item restated with real proving on 0.4; the consumer integration guide with the Recommended Consumer Pattern, CLI and operator docs, the trust-assumptions document; the modules in our own catalog. The code is frozen for the audit at the end of M4. Accepted when CI is green on the default branch with every job executed and the verifier passes against the 0.4 deployment.
Status today: to build.

M5: Audit remediation and re-verification. 1 week, after the audit reports, $4,000
Every finding fixed or explained, re-verified on testnet, the report published with the codebase. Accepted when the report is in the repository with every finding resolved or explained.
Status today: to build.

Total Requested Budget (USD)

$60,000

Relevant Experience

This RFP's prototype: https://github.com/edenbd1/lez-sextant, described above: the verification core and the LEZ v0.3 program, on real mainnet data, with the measurements this proposal is designed around.

Accord, an M-of-N multisig on LEZ v0.3: https://github.com/edenbd1/lez-accord. It verifies BIP-340 signatures inside the program with RISC Zero's accelerated k256, measured through lee::V03State (about 275,000 cycles per signature) and with real proving, and it runs on the public v0.3 testnet with a verifier anyone can run. The measurement method, the accelerated-crate discipline and the executor harness of this proposal come from it.

A vesting program on LEZ v0.3: https://github.com/edenbd1/antumbra-lez. Ported to v0.3's plan-and-apply model within a day of the v0.3.0 release; custody through program PDAs, claims into private accounts, a reproducible build checked in CI, and a verifier that re-checks the whole testnet run from a clean clone.

A collateral auction on LEZ v0.3: https://github.com/edenbd1/antumbra-auction. Integer-exact auction arithmetic checked against 3,011 differential vectors, its cycle figures checked in CI, and its lifecycle run on the public v0.3 testnet.

λPrize LP-0005, Private Balance Attestation: won. logos-co/lambda-prize#64, merged 2026-08-02. RISC0 threshold proofs over shielded accounts, verified on chain and off chain over Logos Messaging: an attestation primitive, the same shape as this RFP's output.

Logos Forum: https://github.com/edenbd1/lp-0026-forum, a serverless forum module for Basecamp over Logos Delivery and Storage, published in its own catalog (https://github.com/edenbd1/logos-forum-catalog) and run as a permanent node on the public network. The module packaging, the headless daemon pattern and the catalog path of this proposal are ones we already ship.

How this maps to the recommended team profile.

The RFP asks for Where it already exists
Ethereum consensus and the light-client protocol the prototype: bootstrap, three committee handoffs and finality on mainnet's own updates, Electra indices; Hoodi, Sepolia and Gloas on v2
BLS12-381 aggregate signatures the prototype: FastAggregateVerify on two libraries, stock and accelerated, key handling measured four ways
Merkle-Patricia proofs against state and receipts roots the prototype: account, storage slot and receipt-log proofs on a mainnet block, and a receipts-trie builder checked against the block's root
LEZ program development and on-chain proof verification LP-0005 on v0.2; Sextant, Accord, the vesting program and the auction on v0.3, all four on the public v0.3 testnet
Security of consensus verification the fabricated-chain, handoff, participation, replay and finality tests above, on real data
Libraries consumed by other teams sextant-core shared by program, host and clients; Logos modules and a catalog we already maintain

Post-Delivery Plan

Maintained by the two of us for twelve months from final delivery:

  • Security issues acknowledged within 48 hours, with a fix or a documented mitigation; consumers will mint against this, so this one is not best-effort.
  • Platform and Ethereum tracking: kept building and deployed on the current LEZ testnet across releases, re-driven after every reset; Ethereum forks followed, with a new program version and a migration note shipped before any fork that changes light-client containers.
  • An instance kept advancing on the testnet for the twelve months, by our own daemon, so consumers have one to build against; it is one operator among any number, not a privileged one.
  • Public issues answered on GitHub.
  • Handover: MIT OR Apache-2.0, no private build step, a deployment anyone can verify from the committed artifact, and a decision log that explains every choice.

Permissions and Consent

  • I/We confirm Logos may contact me using the primary contact information provided above for follow-ups and next steps.
  • I/We consent to Logos using information from this proposal publicly such as blogs case studies social posts or analytical reporting. Redactions can be requested at any time.

Program Requirements

  • I/We have read and agree to the Logos RFP Terms and Conditions and I understand that no Grant is awarded and no right to payment arises unless a Grant Agreement is executed.
  • I/We understand that RFP specifications are proposals rather than instructions, that Logos makes no representations as to their legal or regulatory treatment, and that we are responsible for assessing what we build, deploy or operate and for complying with the laws that apply to us.
  • I/We understand this project must be open-sourced under the MIT and Apache 2.0 Licenses unless explicitly approved otherwise.
  • I/We are prepared to deliver milestone-based outcomes.

Activity

  1. edenbd1 commented on Oct 16, 2026

    @edenbd1
    Author

    Demo film, in my own voice

    sextant-rfp021-demo.mp4

    4 min 32, narrated by me, subtitled. Everything on screen runs from a clean clone of edenbd1/lez-sextant at the reproducible build (ImageID fa229edd…):

    • the measurement behind the RFP's open question: one BLS pairing check costs 7.8M cycles, a whole update about 12.5M, against the 10M cap per transaction
    • the three-step split (Begin, Miller, Finish), each step under about half of the cap, bound together through a program-owned job account
    • the committed binary through lee::V03State on real mainnet data: three sync-committee handoffs to a finalized block, and every attack refused (tampered signature, fabricated chain, participation below two-thirds, replay, foreign chain), 8 executor tests and 17 host tests
    • the program on the public LEZ testnet (A89TcYmW…), re-checked from a clean clone by verify-onchain.sh: 24 checks, 0 failures, 6 applied and 6 refused transactions
    • what a consumer pays: about 90,000 cycles for an ERC-20 Transfer log, about 300,000 for a WETH balance slot
    • real proofs, not dev mode: each step proves in around ten minutes on a laptop
  2. edenbd1 commented on Oct 17, 2026

    @edenbd1
    Author

    Update, 2026-10-17. The proposal body is revised. What changed in the prototype (https://github.com/edenbd1/lez-sextant):

    • The full chain on the public LEZ testnet. Updates 1 to 4 now run to period 1884 and execution block 26,197,200, with attestations against the last header and another chain's query refused (E7209). That is 24 transactions, 17 applied and 7 refused, each charged its pre-checked gas or its whole limit. scripts/verify-onchain.sh passes 37 checks from a clean clone, and its mutation test catches 15 corruptions of the manifest (docs/ONCHAIN.md).
    • A consumer program. erc20_receiver credits an ERC-20 Transfer once, through a chained Attest and a nullifier keyed by the statement identifier. A claim costs 171,193 gas, 1.7% of the cap. F.6 has its own tests (docs/CONSUMER.md).
    • Branch v2. It adds E7206 (weak subjectivity, enforced both by the update's own slots and by the sequencer's clock) and Gloas light-client headers. Sepolia is already on Gloas, and Hoodi forks on 2026-10-26. Hoodi and Sepolia vectors run through the rebuilt binary (ImageID ad6fbedb…). It is not deployed yet; the redeploy plan is in the repo.
    • The Logos Ethereum core module, read against what the program needs (docs/ETH-CORE-MODULE.md). It does not expose light-client objects or eth_getProof nodes, and we will add both upstream in M3.

    Budget and milestones are unchanged.

  3. edenbd1 commented on Oct 17, 2026

    @edenbd1
    Author

    Update, 2026-10-17 (evening). The v2 program is deployed and has run the full chain on the public LEZ testnet, and v2 is merged into main (https://github.com/edenbd1/lez-sextant).

    • Program Ebe8rifvjyvwWUXnk7bsKeTwuyCAFSRMFuPMVHE8CbUB (reproducible build, ImageID ad6fbedb…, with E7206 and Gloas), instance mainnet-v2-10171005: Init from the mainnet checkpoint, updates 1 to 4 to period 1884 and the finalized execution block 26,197,200, attestations against the checkpoint's header and the last one, and six refusals with typed codes (among them another chain's query, E7209, and update 4's Finish sent twice, E7294). 23 transactions, 17 applied and 6 refused, each charged its pre-checked gas or its whole limit; e.g. update 4's Finish c016b5b2….
    • scripts/verify-onchain.sh --manifest evidence/testnet-v2/run.tsv passes 36 checks, 0 fail; its mutation test catches 7 of 7 corruptions (docs/ONCHAIN.md).
    • This ran before the committed checkpoints leave the staleness window on 2026-10-25, as the redeploy plan required. The earlier program A89TcYmW… and its run stay as they are.

    The proposal body is updated to match. Budget and milestones are unchanged.

  4. edenbd1 commented on Oct 18, 2026

    @edenbd1
    Author

    Update, 2026-10-18. The v2 program (Ebe8rifv…) now also runs Ethereum testnet instances on the public LEZ testnet, beside the mainnet one (https://github.com/edenbd1/lez-sextant).

    • Sepolia, post-Gloas (instance Pi3R6DjZpELFNYJk453aVDtwquGzxcjK8AYPkcA74Sx): Init from the committed checkpoint, the vector chain to period 1388 (execution block 11,929,520; updates 1 to 4, each with Gloas light-client headers), attestations against the checkpoint's and the last header (a log and a storage slot), and three refusals: the same proofs asked as a mainnet statement (E7209, twice) and a false predicate (E7208). 19 transactions, 16 applied and 3 refused; e.g. update 4's Finish c72f9411…. Verifier 24 checks, 0 fail; mutation test 6 of 6.
    • Hoodi (instance 7LxJDWx4ycNiTD79NjVmwV137BvW2eXtyfau9s9Sy1K6): Init, a log attested against the checkpoint, E7209 and E7208 refusals, and update 1 with its committee handoff to period 505 (35c74651…). 7 transactions, verifier 11/0, mutation test 6 of 6.
    • The remaining Hoodi updates and the Sepolia instance that crosses the Gloas fork from a Fulu checkpoint ran in full on a local v0.3.1 sequencer with the same binary (evidence/local/chain-*); on the testnet they are limited by fees, not by the program.

    Each manifest re-checks from a clean clone: scripts/verify-onchain.sh --manifest evidence/testnet-v2-sepolia/run.tsv (or -hoodi). Details in docs/ONCHAIN.md. Budget and milestones are unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

RFP-021Proposals for RFP-021proposalProposal submitted for an RFP

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions