Architecture · Reference
Proof verification and cryptographic interfaces
The proof-verification environment supplies deterministic cryptographic operations. It does not execute arbitrary user bytecode or own a separate database. Applications provide a proof, verification key, and public inputs—or curve-operation inputs—and must interpret the result according to the calling interface.
The central adoption question is which boundary actually enforces the proof condition. An RPC response reporting a valid proof is an observation from a server. A transaction whose instruction succeeds only after verification is a different enforcement mechanism.
Reference scope: the inspected checkout’s Groth16, BN254 and Token-2022 proof boundaries, including accepted encoding, result interpretation and known native-dispatch limits. Deployed availability is recorded separately in the compatibility reference. For the broader authorization model, read trust boundaries.
Choose an interface by its effect
| Surface | Implemented operation | Meaning of success |
|---|---|---|
| Groth16 RPC | BN254 or BLS12-381 proof verification | Returns a boolean; no consensus state transition |
| BN254 RPC / EVM precompile | Add, multiply, pairing | Returns bytes; a false pairing is successful computation returning zero |
| Dedicated native BN254 ID | Opcode 0 add, 1 multiply, 2 pairing | Accepts any successful computation and discards its output |
| Dedicated native Groth16 ID | System 0 BN254, 1 BLS12-381 | Requires true, but its current framing cannot carry a canonical valid triple |
| Token-2022 ElGamal proof program | Typed confidential-token proof families | Verifies a typed context and participates in transaction proof binding |
| SBF cryptographic syscall | Supported curve operations | Syscall status and output must both be interpreted by the program |
The generic cross-VM target codec supports EVM, WASM, BPF, Move, and SVM. It has no general ZK or Bombil target. Architectural environment names do not automatically create generic dispatch variants. Target codec and RPC, native ZK dispatch.
Groth16 uses canonical compressed components
The verifier is stateless CPU code using arkworks. It receives the verification key from the caller and prepares that key on each call. It does not generate a proof, run a setup ceremony, or select a trusted key from an on-chain registry. The application must bind the key and public inputs to its intended statement.
The proof is A(G1) || B(G2) || C(G1). The key contains four curve elements plus a length-prefixed gamma_abc_g1 vector with one constant element and one element per public input. Inputs are a bare concatenation of canonical scalar encodings, without a vector count prefix.
| Curve | Proof bytes | Key bytes for n public inputs | Public-input bytes |
|---|---|---|---|
| BN254 | 128 | 264 + 32n | 32n |
| BLS12-381 | 192 | 392 + 48n | 32n |
These are arkworks canonical compressed encodings, not Ethereum's uncompressed big-endian BN254 point format. Wire geometry and verifier.
Before deserializing a key, the core verifies that the declared vector count fits the bytes supplied. A short buffer declaring an enormous vector must not trigger an enormous allocation. Core parsing retains its existing tolerance for trailing proof/key bytes; the RPC boundary is deliberately stricter.
The useful read-only RPC contract
eto_zkGroth16Verify([proofHex, vkHex, publicInputsHex, curve?])| Position | Input | Required | Encoding |
|---|---|---|---|
| 0 | proofHex | Yes | Hex of the selected curve’s canonical compressed proof |
| 1 | vkHex | Yes | Hex of the canonical compressed verification key |
| 2 | publicInputsHex | Yes | Hex of concatenated canonical scalars, with no vector-count prefix |
| 3 | curve | No | bn254 by default; bls12_381 or bls12381 for BLS12-381 |
The default curve is bn254; bls12_381 and bls12381 select the other supported curve. Success returns {valid: boolean, curve: string}. Invalid parameter shape, hex, curve, length, or input count returns JSON-RPC -32602; a verifier/deserialization error after structural admission returns -32000.
Each component is capped at 1 MiB. The RPC requires exact proof/key lengths, scalar-aligned public inputs, and at most 1,024 public inputs by default. ETO_ZK_RPC_MAX_PUBLIC_INPUTS changes the RPC count cap. It is an availability control, not an implicit consensus rule. RPC validation.
The BN254, modular-exponentiation, and BLAKE2F read helpers also use a 30-million-gas precompile budget in addition to byte bounds. Limiting work by pairing count or exponentiation cost matters more than accepting a small-looking payload. No latency guarantee follows from these limits. Read budgets.
The native Groth16 framing is not usable for a canonical positive proof
The current native instruction uses system:u8 followed by a payload split into three nearly equal regions: proof, key, inputs. It does not decode the Borsh instruction type also present in the tree, and it does not read a verification-key account.
This cannot represent a canonical valid triple. For n public inputs, the input region needs 32n bytes, but the BN254 key needs 264 additional bytes; the BLS12-381 key needs still more. The key region is never larger than the input region under the implemented split. Padding cannot create the missing key capacity while preserving the required key/input cardinality.
This is a deduction from the current framing and wire-size equations, not a failed attempt to generate a proof. Negative native dispatch tests do not establish a positive native proof route. An application needing transaction-enforced verification must use a separately validated executable path; it must not treat ZK_VERIFY_PROGRAM_ID as usable merely because the verifier helper passes tests. Native split, component sizes.
Pairing false is not a transport failure
BN254 addition takes two uncompressed G1 points; multiplication takes one point and a scalar. Their short inputs are right-padded. Pairing input is a multiple of 192 bytes per G1/G2 pair and returns a 32-byte big-endian one or zero. Empty pairing input returns one by the empty-product rule.
A well-formed false pairing returns Ok(zero). The native BN254 handler currently accepts Ok(_) and discards output, so a successful native transaction does not assert that the pairing equation was true. EVM/SBF callers that receive output must check it explicitly. Curve operations, native result mapping.
Token-2022 proof context has its own lifecycle
The canonical ZkE1Gama1Proof11111111111111111111111111111 program verifies the pinned confidential-token proof formats. It can produce an authenticated persistent context account or verify inline/record-account material. That is separate from Groth16 and BN254 proof verification.
Token instructions can reference proof instructions appearing earlier or later in the same transaction. The transaction-owned proof ledger records the exact instruction index, proof type, instruction bytes, and context bytes consumed. A later successful proof must match that requirement; before outer success, finish() rejects any missing or substituted proof.
Checkpoints roll back child requirements with a failed CPI frame. The ledger is never a cross-transaction proof-result cache. Persistent context accounts instead have their own authority and close lifecycle. Proof binding.
Test the actual caller boundary
Positive Groth16 tests generate real proofs for both curves; RPC tests exercise those proofs with strict framing. Allocation-prefix, wrong-input, and malformed-point tests protect rejection behavior. Token proof tests cover prior/future binding, substitution, and rollback. Dedicated native ZK tests primarily establish dispatch and negative handling.
These tests were inspected, not run during this documentation pass. Before claiming a proof-dependent application works, test a positive and negative vector through its exact caller interface and confirm the application rejects false. Continue with trust boundaries and validation strategy.