The on-chainDocumentation Index
Fetch the complete documentation index at: https://docs.pfbridge.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Verifier on Stellar Testnet is a small Soroban contract that delegates UltraHonk proof verification to an open-source Rust crate. This page documents the implementation end-to-end so it can be audited independently — which BN254 host functions are called, how proofs are parsed, the verifier pipeline, the trusted setup baked into the binary, and the immutability guarantees on the verification key.
Source layout
The verifier is split across two crates:| Layer | Path | Role |
|---|---|---|
| Soroban contract | contracts/stellar/contracts/verifier/src/lib.rs | Thin wrapper — stores the VK immutably at deployment, exposes verify_proof(public_inputs, proof_bytes). |
| UltraHonk implementation | rs-soroban-ultrahonk crate, pinned at rev d762995d8c181c05fe9a15962793dba6641a79ba (MIT-licensed, separate repo) | The full UltraHonk verifier — proof parsing, Fiat-Shamir transcript, sumcheck, Shplemini opening, BN254 host-function calls. |
rs-soroban-ultrahonk crate, which is open-source and independently auditable.
What the contract exposes
Three entry points, no admin surface:__constructor(vk_bytes)— runs once at deployment, parses the VK (panicking on malformed input), and writes it into instance storage. There is no setter, no upgrade hook, and no role enum; the VK is permanent for the lifetime of the contract address.verify_proof(public_inputs, proof_bytes)— reads the VK from immutable storage, instantiatesUltraHonkVerifierfrom the external crate, and forwards the proof and public inputs to it. ReturnsOk(())on a valid proof, a typedVerifierErrorotherwise.get_vk() -> Option<Bytes>— view accessor so anyone can read the deployed VK and diff it against thebbartifacts in the monorepo’scircuits/directory.
BN254 host functions used
The UltraHonk pairing-based verifier needs three primitive operations on the BN254 curve: scalar arithmetic inFr, multi-scalar multiplication on G1, and a multi-pairing check. All three are exposed by Soroban’s soroban_sdk::crypto::bn254 module as native host functions.
There are exactly two host calls in the verifier, both in ec.rs:
| Operation | Soroban host call | Where it is used |
|---|---|---|
| Scalar arithmetic | Fr::add / mul / inv (host-backed) | Sumcheck rounds, Fiat-Shamir delta computation, Shplemini scalar prep |
| Multi-scalar multiplication | env.crypto().bn254().g1_msm(...) | Shplemini commitment folding (shplemini.rs:208) |
| Pairing product check | env.crypto().bn254().pairing_check(...) | Final Shplonk pairing equation (shplemini.rs:211) |
Verifier pipeline
The top-levelverify in verifier.rs runs six steps in order. A proof must pass all six or verify_proof returns VerifierError::VerificationFailed.
Parse proof bytes
load_proof decodes the proof into PROOF_FIELDS = 456 BN254 scalars (14,592 bytes total). Wrong-length proofs return ProofParseError.Validate public inputs
Public inputs must be a multiple of 32 bytes. The verifier subtracts the 16 fixed pairing-point inputs (
PAIRING_POINTS_SIZE) from the VK’s public_inputs_size and requires the caller-supplied count to match exactly.Generate Fiat–Shamir transcript
generate_transcript hashes proof rounds + public inputs into challenges using keccak as the random oracle. The proof is generated with --oracle_hash keccak, which keeps the on-chain hashing cheap on both EVM and Soroban.Compute public_inputs_delta
compute_public_input_delta folds the public inputs into a single Fr using the transcript’s beta / gamma challenges. This becomes part of the relation parameters for sumcheck.Sumcheck
verify_sumcheck runs the sumcheck protocol over CONST_PROOF_SIZE_LOG_N rounds. Each round checks a degree-bounded polynomial relation against the prover’s committed values.Trusted setup / SRS
The Shplonk pairing check needs the standard KZG SRS — a G2 generator[1]_2 and a [τ]_2 element from a one-shot trusted ceremony. Both are hardcoded as 128-byte affine encodings in ec.rs lines 8–28 (RHS_G2_BYTES and LHS_G2_BYTES).
These are the same SRS values used by Aztec’s Barretenberg bb prover (the prover that produces the proofs the relayer submits) and the same values baked into the auto-generated EVM Solidity verifier at contracts/evm/src/Verifier.sol. Identical SRS on both chains means a single proof produced by bb is acceptable to both verifiers without any rebuilding or re-blinding.
VK immutability
The contract surface is intentionally minimal:- The constructor runs exactly once at deploy. Soroban does not re-run constructors on upgrade, and the contract has no manual
initfunction. - No setter for the VK exists in
lib.rs— there is no function that writes to the VK storage slot after construction. - No admin role is recorded. The contract has no
Addressfield, no role enum, no migration hook. - VK parsing is enforced at deploy time. If
load_vk_from_bytesreturnsNone, the constructor panics, the deploy fails, and the contract address is never reachable. There is no path that produces a deployed-but-unconfigured Verifier.
get_vk(), so anyone can independently confirm the testnet contract is bound to the expected verification key (matching the bb artifacts in circuits/).
Same proof, both chains
UltraHonk proofs have a single canonical encoding — 456 field elements, keccak Fiat-Shamir, BN254 SRS — and both chains’ verifiers consume that same encoding:- Soroban side: the contract above (Soroban WASM, BN254 host functions).
- EVM side:
contracts/evm/src/Verifier.sol, the auto-generated Solidity verifier frombb write_solidity_verifier, which uses Ethereum’secMul/ecAdd/ecPairingprecompiles for the same primitives.
Cost measurement
Thers-soroban-ultrahonk crate ships a cost-measurement harness that submits proofs against a localnet Soroban deployment and records CPU instructions, memory, host-call counts, and read/write entries. It is how we validate that a real ProofBridge proof fits inside Soroban’s transaction resource limits, and how a regression introduced by a future bb version would be caught.
Independent verification checklist
For an auditor asking “is the Soroban verifier actually doing what is claimed?”, the steps are:- Read
contracts/stellar/contracts/verifier/src/lib.rs— confirm it stores the VK once and only forwards to the external crate. (139 lines, no concealed state.) - Pin the external crate at the rev above and read
ultrahonk-soroban-verifier/src/ec.rs— confirm the only host calls areenv.crypto().bn254().g1_msmandenv.crypto().bn254().pairing_check. - Read
verifier.rs— confirm the six-step pipeline above. - Diff
RHS_G2_BYTES/LHS_G2_BYTESagainst the SRS values used by Aztecbband by the EVM verifier — they must match. - Call
get_vk()on the deployed Soroban contract and check the bytes against the VK incircuits/— they must match.
Related references
- Smart contracts — Verifier role and addresses on both chains.
- Zero-knowledge proofs — circuit-level explanation of what is being proved.
- Order hashing — how the order digest fed into public inputs is constructed.
- Off-chain signing — how the prover-side artifacts are produced.