> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pfbridge.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Soroban verifier internals

> How the ProofBridge Verifier contract on Stellar (Soroban) verifies UltraHonk proofs — exact BN254 host functions called, the verifier pipeline, the SRS used, and the open-source crate that implements the inner loop.

The on-chain `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`](https://github.com/Explore-Beyond-Innovations/ProofBridge/blob/main/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`](https://github.com/Explore-Beyond-Innovations/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. |

The contract crate is 139 lines. Every cryptographic operation lives in the external `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, instantiates `UltraHonkVerifier` from the external crate, and forwards the proof and public inputs to it. Returns `Ok(())` on a valid proof, a typed `VerifierError` otherwise.
* `get_vk() -> Option<Bytes>` — view accessor so anyone can read the deployed VK and diff it against the `bb` artifacts in the monorepo's `circuits/` directory.

## BN254 host functions used

The UltraHonk pairing-based verifier needs three primitive operations on the BN254 curve: scalar arithmetic in `Fr`, 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`](https://github.com/Explore-Beyond-Innovations/rs-soroban-ultrahonk/blob/d762995d8c181c05fe9a15962793dba6641a79ba/ultrahonk-soroban-verifier/src/ec.rs):

```rust theme={null}
// ec.rs:71 — G1 multi-scalar multiplication
env.crypto().bn254().g1_msm(points, filtered_scalars)

// ec.rs:83 — final Shplonk pairing equation
env.crypto().bn254().pairing_check(g1s, g2s)
```

| 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`](https://github.com/Explore-Beyond-Innovations/rs-soroban-ultrahonk/blob/d762995d8c181c05fe9a15962793dba6641a79ba/ultrahonk-soroban-verifier/src/shplemini.rs#L208))   |
| Pairing product check       | `env.crypto().bn254().pairing_check(...)` | Final Shplonk pairing equation ([`shplemini.rs:211`](https://github.com/Explore-Beyond-Innovations/rs-soroban-ultrahonk/blob/d762995d8c181c05fe9a15962793dba6641a79ba/ultrahonk-soroban-verifier/src/shplemini.rs#L211)) |

Every BN254 group operation that would otherwise have to be reimplemented in WASM is handled by these host calls. The verifier crate never implements its own elliptic-curve arithmetic.

## Verifier pipeline

The top-level `verify` in [`verifier.rs`](https://github.com/Explore-Beyond-Innovations/rs-soroban-ultrahonk/blob/d762995d8c181c05fe9a15962793dba6641a79ba/ultrahonk-soroban-verifier/src/verifier.rs) runs six steps in order. A proof must pass all six or `verify_proof` returns `VerifierError::VerificationFailed`.

<Steps>
  <Step title="Parse proof bytes">
    `load_proof` decodes the proof into `PROOF_FIELDS = 456` BN254 scalars (14,592 bytes total). Wrong-length proofs return `ProofParseError`.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Shplemini opening">
    `verify_shplemini` constructs two G1 points `P0` and `P1` via `g1_msm` (the host MSM), then calls `pairing_check(P0, P1)` (the host pairing). If `e(P0, [1]_2) · e(P1, [τ]_2) == 1`, the proof is valid.
  </Step>
</Steps>

## 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](https://github.com/Explore-Beyond-Innovations/rs-soroban-ultrahonk/blob/d762995d8c181c05fe9a15962793dba6641a79ba/ultrahonk-soroban-verifier/src/ec.rs#L8) (`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.

<Tip>
  If the SRS ever needed to change (e.g., new prover version), it would require a fresh deployment of the Verifier contract on every chain — there is no on-chain mechanism to rotate it. This is by design: a mutable SRS would mean a mutable trust anchor.
</Tip>

## 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 `init` function.
* **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 `Address` field, no role enum, no migration hook.
* **VK parsing is enforced at deploy time.** If `load_vk_from_bytes` returns `None`, the constructor panics, the deploy fails, and the contract address is never reachable. There is no path that produces a deployed-but-unconfigured Verifier.

The VK can be read at any time via `get_vk()`, so anyone can independently confirm the testnet contract is bound to the expected verification key (matching the `bb` artifacts in [`circuits/`](https://github.com/Explore-Beyond-Innovations/ProofBridge/tree/main/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`](https://github.com/Explore-Beyond-Innovations/ProofBridge/blob/main/contracts/evm/src/Verifier.sol), the auto-generated Solidity verifier from `bb write_solidity_verifier`, which uses Ethereum's `ecMul` / `ecAdd` / `ecPairing` precompiles for the same primitives.

Because both verifiers consume the same proof bytes against the same VK and the same SRS, a settled cross-chain trade is gated by **two independent on-chain verifications of the same cryptographic statement** — one on Sepolia, one on Stellar Testnet.

## Cost measurement

The `rs-soroban-ultrahonk` crate ships a [cost-measurement harness](https://github.com/Explore-Beyond-Innovations/rs-soroban-ultrahonk/tree/d762995d8c181c05fe9a15962793dba6641a79ba/scripts/measure_ultrahonk_costs) 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:

1. Read [`contracts/stellar/contracts/verifier/src/lib.rs`](https://github.com/Explore-Beyond-Innovations/ProofBridge/blob/main/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.)
2. Pin the external crate at the rev above and read [`ultrahonk-soroban-verifier/src/ec.rs`](https://github.com/Explore-Beyond-Innovations/rs-soroban-ultrahonk/blob/d762995d8c181c05fe9a15962793dba6641a79ba/ultrahonk-soroban-verifier/src/ec.rs) — confirm the only host calls are `env.crypto().bn254().g1_msm` and `env.crypto().bn254().pairing_check`.
3. Read [`verifier.rs`](https://github.com/Explore-Beyond-Innovations/rs-soroban-ultrahonk/blob/d762995d8c181c05fe9a15962793dba6641a79ba/ultrahonk-soroban-verifier/src/verifier.rs) — confirm the six-step pipeline above.
4. Diff `RHS_G2_BYTES` / `LHS_G2_BYTES` against the SRS values used by Aztec `bb` and by the EVM verifier — they must match.
5. Call `get_vk()` on the deployed Soroban contract and check the bytes against the VK in `circuits/` — they must match.

## Related references

* [Smart contracts](/reference/smart-contracts) — Verifier role and addresses on both chains.
* [Zero-knowledge proofs](/concepts/zero-knowledge-proofs) — circuit-level explanation of what is being proved.
* [Order hashing](/concepts/order-hashing) — how the order digest fed into public inputs is constructed.
* [Off-chain signing](/reference/off-chain-signing) — how the prover-side artifacts are produced.
