Reference
Errors
Every public error type in StateSync-GKR, with its variants, the functions that return it and how to handle it, together with the calls that report failure as a boolean, an option or a short vector instead.
This reference shows how each StateSync-GKR call reports a failure and describes every public error type: what each variant means, which functions return it and what to do about it. The variant names, fields and messages on this page are taken from the 1.1 source.
How failures are reported#
Not every failure is an error value. The facade's verification calls return bool, so a rejected proof never appears as an error type, and two batch calls return a vector without any error channel.
| Call | Returns | A failure appears as |
|---|---|---|
prepare | Result<PreparedSync, SyncError> | SyncError::Compile |
prepare_pinned_d24_a_membership | Result<PreparedSync, PreparedMaterialError> | PreparedMaterialError |
prove_sync_op, make_job | Result<_, SyncError> | SyncError::Compile or SyncError::Witness |
prove_sync_op_prepared, make_job_prepared | Result<_, SyncError> | SyncError::Witness |
prove_batch | Vec<GkrProof> | An empty vector when the configuration does not compile |
prove_batch_prepared, prove_batch_parallel | Vec<GkrProof> | No failure channel; one proof per job, in job order |
verify_sync_op, verify_sync_op_prepared, verify_sync_op_reference, verify_encoded_sync_op | bool | false, for every kind of failure |
circuit_identity, encode_sync_result | Result<_, EncodeError> | EncodeError |
wrap_relation | Option<WrapStatementV1> | None |
wrap_sync_op | Result<WrappedProof, WrapError> | WrapError |
compiler::compile, compiler::compile_with_hints | Result<_, CompileError> | CompileError::UnsupportedConfig |
compiler::validate_unary_gates | Result<(), CompileError> | CompileError::NonUnaryGate |
compiler::generate_witness, compiler::build_input_vector, compiler::MerklePath::compute_root | Result<_, SmtError> | SmtError |
compiler::smt_valid_native | Result<bool, SmtError> | Ok(false) when the operation does not hold; SmtError when the input is malformed |
primitives::hash::leaf_fold, HashGadget::hash_leaf | Result<_, HashError> | HashError |
gkr::evaluate_circuit | Result<CircuitWitness<F>, CircuitError> | CircuitError |
gkr::verify | Result<InputClaim, GkrError> | GkrError. Ok is not acceptance: the caller must still check both input claims |
sumcheck::verify | Result<Subclaim<F>, SumcheckError> | SumcheckError. Ok is not acceptance: the caller must still check the subclaim |
sumcheck::MultilinearPoly::from_evals | Result<MultilinearPoly<F>, PolyError> | PolyError |
wrap::encoding::encode_inner_proof | Result<Vec<u8>, EncodeError> | EncodeError |
wrap::encoding::decode_inner_proof | Result<InnerProofEnvelope, DecodeError> | DecodeError |
wrap::statement::unpack_digest | Option<Digest<BaseField>> | None for a value that is not a packed digest |
The rows without a module path are methods of StateSyncProver. The facade's verification calls return a verdict, so they return bool. The generic gkr::verify and sumcheck::verify return Result because their Ok value still leaves residual claims for the caller, as described in Custom frontends.
To find out why a facade verification call returned false, repeat its first checks yourself: Encoding and transport shows this for received bytes and Troubleshooting for typed results.
Debug assertions and panics#
prove_sync_op_preparedandmake_job_preparedassert in debug builds that the request has the preparation's operation kind. Release builds do not check it, and proving against a preparation of another kind can panic or produce a proof that verification rejects. Checkrequest.operation.kind()againstprepared.kind()first.wrap::commitment::full_circuit_commitment, which preparation and the fresh calls use, panics instead of reducing a structural value at or above the field order.gkr::proveexpects the witness thatgkr::evaluate_circuitreturns for the same circuit and does not check it.sumcheck::MultilinearPoly::evaluatepanics when the point has a different number of variables than the polynomial, andfix_first_varpanics when no variable is left.
Working with error values#
- Every error type implements
DebugandClone. Only the host-onlyReceiptRootPolicyErrorandRisc0RouteBManifestErroralso implementDisplayandstd::error::Error, so format the others with{:?}. SyncError,GkrErrorandWrapErrordo not implementPartialEq. Usematchormatches!on them instead of==.- None of the error enums is marked
#[non_exhaustive]. An exhaustivematchtherefore compiles today and stops compiling if a later version adds a variant, which points you to the new case.
The function below turns every SyncError into a log line. Because the match is exhaustive, the compiler reports any variant it does not handle:
use statesync_gkr::SyncError;
use statesync_gkr::compiler::{CompileError, SmtError};
use statesync_gkr::primitives::hash::HashError;
/// One log line per variant. The match is exhaustive, so the compiler
/// reports any variant that is not handled here.
pub fn describe(error: &SyncError) -> String {
match error {
SyncError::Compile(CompileError::UnsupportedConfig { reason }) => {
format!("configuration rejected: {reason}")
}
SyncError::Compile(CompileError::NonUnaryGate { layer, out }) => {
format!("non-unary gate in layer {layer} at output wire {out}")
}
SyncError::Witness(SmtError::PathLengthMismatch { expected, found }) => {
format!("path has {found} siblings, expected {expected}")
}
SyncError::Witness(SmtError::KeyOutOfRange { key, depth }) => {
format!("key {} is outside a tree of depth {depth}", key.0)
}
SyncError::Witness(SmtError::LeafEncoding(HashError::EncodingTooLong { len, max })) => {
format!("leaf encoding has {len} elements, the bound is {max}")
}
SyncError::Witness(SmtError::LeafEncoding(HashError::EmptyEncoding)) => {
"leaf encoding is empty".to_owned()
}
}
}SyncError#
statesync_gkr::SyncError is the error of the facade's preparation and proving calls.
| Variant | Meaning | Returned by | Handling |
|---|---|---|---|
Compile(CompileError) | The configuration cannot be compiled | prepare, prove_sync_op, make_job | Fix the configuration. The same configuration fails the same way every time, so do not retry |
Witness(SmtError) | The request cannot be turned into circuit inputs | prove_sync_op, prove_sync_op_prepared, make_job, make_job_prepared | Reject that request; other requests are unaffected |
Where a call has no Result, the same conditions show up differently: prove_batch returns an empty vector instead of SyncError::Compile, and the verification calls return false.
CompileError#
statesync_gkr::compiler::CompileError.
| Variant | Meaning | Returned by | Handling |
|---|---|---|---|
UnsupportedConfig { reason } | The configuration is outside what the compiler implements. reason is one of "v0.1 implements strategy A (one data-parallel instance per tree level) only", "tree depth must be positive" and "leaf_max_fields must be >= 10 (tag + 9 keccak limbs)" | compiler::compile, compiler::compile_with_hints; through SyncError::Compile | Use LayerStrategy::A, a depth of at least 1 and a leaf bound of at least 10 (Configuration) |
NonUnaryGate { layer, out } | A Lin or Pow3 gate in layer layer (0 is the output layer) that writes to wire out has an in2 different from its in1 | compiler::validate_unary_gates only. The compiler's own circuits satisfy the rule | Set in2 equal to in1 for every Lin and Pow3 gate in your circuit |
SmtError#
statesync_gkr::compiler::SmtError. It implements From<HashError>.
| Variant | Meaning | Returned by | Handling |
|---|---|---|---|
PathLengthMismatch { expected, found } | The path has found siblings and the tree depth is expected | MerklePath::compute_root, compiler::build_input_vector, compiler::generate_witness, compiler::smt_valid_native; through SyncError::Witness | Fetch the path for the configured depth, leaf-level sibling first |
KeyOutOfRange { key, depth } | The key is at or above 2^depth, checked when the depth is below 64 | MerklePath::compute_root, compiler::smt_valid_native. Witness generation does not check the range, so proving succeeds and verification returns false | Allocate keys within the tree's range |
LeafEncoding(HashError) | A leaf encoding is empty or longer than the leaf bound | MerklePath::compute_root, compiler::build_input_vector, compiler::generate_witness, compiler::smt_valid_native; through SyncError::Witness | See HashError |
compiler::generate_witness also reports a failed circuit evaluation as PathLengthMismatch. In that case expected is the circuit's input width, 2^input_width_bits, rather than the tree depth. A preparation built by prepare for the request's own kind does not produce it; if you see it, report it with a reproduction.
HashError#
statesync_gkr::primitives::hash::HashError, also re-exported as statesync_gkr::primitives::HashError.
| Variant | Meaning | Returned by | Handling |
|---|---|---|---|
EncodingTooLong { len, max } | The encoding has len field elements and the leaf bound is max | primitives::hash::leaf_fold, HashGadget::hash_leaf; wrapped in SmtError::LeafEncoding | An occupied leaf encodes one tag, its sync-state fields and nine identity limbs, so at most max - 10 sync-state fields fit. Shorten the payload, or plan a new tree with a larger bound |
EmptyEncoding | The encoding is empty | leaf_fold and hash_leaf called with an empty slice. LeafState::encode always produces at least the tag, so requests built from leaf states never cause it | Pass a non-empty encoding |
EncodeError#
statesync_gkr::wrap::encoding::EncodeError.
| Variant | Meaning | Returned by | Handling |
|---|---|---|---|
BadRoundPolyArity { layer, round, got } | The round polynomial of round round in layer layer has got coefficients instead of five | wrap::encoding::encode_inner_proof, encode_sync_result | The prover always emits five coefficients, so the proof was built or changed elsewhere. Encode only proofs returned by the prover |
CountOverflow | A count does not fit its wire field. Through the facade, the cause is a leaf_max_fields above 65,535 | encode_inner_proof, encode_sync_result, circuit_identity | Use a leaf bound of at most 65,535 |
MessageTooLong | The encoding would exceed u32::MAX bytes | encode_inner_proof, encode_sync_result | Measured configurations stay far below this limit; check the configuration that produced the proof |
DecodeError#
statesync_gkr::wrap::encoding::DecodeError has one variant for each rejection rule of the decoder. verify_encoded_sync_op and wrap_relation decode internally and turn any DecodeError into false or None; call wrap::encoding::decode_inner_proof yourself to see the variant. Treat every variant as a rejection of the bytes. Wire format gives the exact rule behind each variant.
| Group | Variants and fields | Meaning |
|---|---|---|
| Length and framing | HeaderTooShort { have }, DeclaredLengthMismatch { declared, actual }, Truncated { offset }, TrailingBytes { extra }, OversizedCount { offset } | The buffer is shorter than the header, differs from the declared length, ends inside a field, continues after the last field, or declares a count larger than the remaining bytes allow |
| Format and version | BadMagic, UnsupportedProofEncodingVersion(u16), UnsupportedProtocolVersion(u16), UnsupportedProofKind(u8), UnsupportedCircuitVersion(u16), UnsupportedLeafEncodingVersion(u16), UnsupportedLayerStrategy(u8) | The bytes are not an inner-proof-v1 message, or carry a version, proof kind or layer strategy that this release does not decode. This release decodes only version 1 and strategy A |
| Content | UnknownOpKindTag(u8), OpKindMismatch { identity, statement }, NonCanonicalFieldElement { offset }, BadRoundPolyArity { layer, round, got } | An operation tag above 2, different tags in the identity and the statement, a field element at or above p, or a round polynomial that does not declare five coefficients |
Byte offsets in the fields count from the start of the message.
GkrError#
statesync_gkr::gkr::GkrError, returned by gkr::verify. The facade's verification calls turn it into false.
| Variant | Meaning | Handling |
|---|---|---|
Sumcheck { layer, source } | The sumcheck of layer layer (0 is the output layer) failed; source is the SumcheckError | Reject the proof |
LayerClaimMismatch { layer } | The sumcheck's final claim for layer layer does not match the layer identity rebuilt from the wiring oracle and the prover's two evaluations | Reject the proof. If honest proofs fail this way, check that the wiring oracle describes the same circuit |
ShapeMismatch | The circuit has no layers, the proof has a different number of layer proofs than the circuit has layers, the output layer is too wide for a machine word, or claimed_outputs does not have one entry per output wire | Reject the proof. If honest proofs fail this way, check that prover and verifier use the same circuit and output claim |
An Ok(InputClaim) from gkr::verify is not yet acceptance: compare both residual input claims with the input vector.
SumcheckError#
statesync_gkr::sumcheck::SumcheckError, returned by sumcheck::verify and carried inside GkrError::Sumcheck.
| Variant | Meaning |
|---|---|
DegreeExceeded { round } | The round polynomial of round round exceeds the degree bound. The degree is the coefficient count minus one, so trailing zero coefficients count |
SumMismatch { round } | The round polynomial's values at 0 and 1 do not add up to the running claim |
WrongRoundCount { expected, found } | The proof has found rounds and the instance has expected variables |
Each variant rejects the proof. An Ok(Subclaim) still requires the caller to evaluate the polynomial at subclaim.point and compare it with subclaim.expected_eval.
CircuitError#
statesync_gkr::gkr::CircuitError, returned by gkr::evaluate_circuit.
| Variant | Meaning | Handling |
|---|---|---|
InputWidthMismatch { expected, found } | The input vector has found entries instead of expected, which is 2^input_width_bits | Pad the inputs to the full width with zeros |
WireOutOfRange { layer } | A gate or constant in layer layer refers to a wire outside its own layer or the layer below | Fix the circuit; evaluate_circuit reports the first such layer it reaches, starting from the input side |
PolyError#
statesync_gkr::sumcheck::PolyError, returned by sumcheck::MultilinearPoly::from_evals.
| Variant | Meaning | Handling |
|---|---|---|
NotPowerOfTwo { len } | The evaluation table has len entries, which is zero or not a power of two | Pad the table to a power of two |
WrapError#
statesync_gkr::wrap::WrapError.
| Variant | Meaning | Returned by | Handling |
|---|---|---|---|
BackendUnavailable { backend } | The requested backend is not available in this build. No backend in this source returns it | Implementations of wrap::WrapBackend::wrap | Use a build that includes the backend |
Rejected { reason } | The input was refused. wrap_sync_op uses the reason "inner proof fails the wrap relation" when its own check fails before any backend runs, and "backend echoed a different wrap statement" when the backend's statement differs from the locally computed one. MockWrapBackend uses it for bytes it cannot decode | wrap_sync_op, WrapBackend::wrap | Treat it as final for that input. Check the inner proof with verify_encoded_sync_op first |
MockWrapBackend returns the fixed bytes MOCK-WRAP-NOT-A-PROOF and carries no cryptographic content. The published outer proof covers one fixed depth-24 membership example; see Trust boundaries.
PreparedMaterialError#
statesync_gkr::wrap::prepared::PreparedMaterialError is returned by prepare_pinned_d24_a_membership and by the wrap::prepared functions that build, encode, decode and validate the reviewed prepared material for depth 24, strategy A, the default leaf bound and the Membership kind. PreparedProfileAxis names the axis in two of the variants: Protocol, Circuit, Leaf, Field, Hash or Pcs.
| Group | Variants and fields | Meaning |
|---|---|---|
| Size and structure | LengthOverflow, BudgetExceeded, CountOverflow, UnexpectedEof { offset }, DeclaredLengthMismatch, TrailingBytes { extra } | A size calculation overflowed, a count exceeds the fixed budget or its wire integer, or the bytes end early, disagree with the declared length or continue after it |
| Format and profile | BadMagic, BadDomain, UnsupportedSchema { found }, UnsupportedVersion { axis, found }, WrongProfile { axis, found }, OperationKindMismatch { found }, ConfigMismatch | The bytes are not schema-1 prepared material, a version or profile identifier differs on the named axis, the operation is not Membership, or the depth, leaf bound, configuration profile or strategy differs |
| Canonical form | NonCanonicalEncoding { offset }, NonCanonicalField { offset } | A reserved field, tag or alternative representation is not canonical, or a field element is not a canonical KoalaBear residue |
| Content | InvalidCircuit, CircuitCommitmentMismatch, WiringMismatch, PreparedDigestMismatch | Circuit indices, widths, the unary-gate rule or hint shapes are invalid; stored, recomputed or expected commitments differ; hints or the locally rebuilt wiring differ from the reference; or the framed digest differs from the expected one |
| Generator provenance | ProvenanceMismatch, NonCanonicalProvenance | verify_generator_provenance found a field that differs from the reviewed record, or GeneratorProvenanceV1::to_canonical_text or digest found a control character in a text field |
| Audit binding | RouteIdMismatch, GuestImageMismatch, RouteNotEffective, RouteRevoked, SupersededRoute | PreparedMaterialBindingAuditV1::validate found a stale or substituted route ID, another guest image, a height before the binding's effective height, a revoked binding or a height past its draining cutoff |
prepare_pinned_d24_a_membership checks the prover's configuration first and returns ConfigMismatch unless it is depth 24, leaf bound 31 and strategy A. Every variant is a rejection, and none of these functions falls back to compiling or to unvalidated material. For any other profile, build the preparation with prepare. For the same audit binding, only RouteNotEffective can pass at a later acceptance height.
External route reference models#
The modules wrap::settlement, wrap::risc0_destination_policy and wrap::risc0_route_b_manifest require the default host feature. They are reference models for the recorded external proof route: they fix canonical bytes and fail-closed checks, and they do not authenticate live finality, read a chain or act as deployed contracts. Their errors reject the input they describe.
ContractError#
statesync_gkr::wrap::settlement::ContractError is returned by CanonicalRawStatement::from_statement, from_bytes and action_kind, by ReferenceSettlementConsumer::register_route, set_lifecycle, verify_historical and authorize_new, and by wrap::risc0_destination_policy::validate_receipt_attachment. In the table, a settlement attempt means verify_historical or authorize_new.
| Variant | Meaning | Returned by |
|---|---|---|
InvalidEnvelope | The claim's magic or envelope version is not recognized | A settlement attempt |
NonCanonicalStatement | A raw scalar, header field, digest packing or asset scalar is invalid | from_statement, from_bytes, a settlement attempt |
InvalidActionKind | The raw statement contains an unknown action tag | action_kind, a settlement attempt |
UnknownRoute | The route is not registered | set_lifecycle, a settlement attempt |
InvalidRouteManifest | The route's content, codec, statement or Merkle profile is invalid | register_route, a settlement attempt |
InvalidLifecycle | The lifecycle state and the acceptance policy disagree | register_route, set_lifecycle, authorize_new |
StaleLifecycleRevision | Lifecycle revisions and effective heights must increase | set_lifecycle |
RouteRevoked | The route is revoked for new acceptance | authorize_new |
CheckpointPastCutoff | The checkpoint of a draining route is past its cutoff | authorize_new |
PrimaryRecordMismatch | The claim, primary finality record and route do not bind to each other | A settlement attempt |
PrimaryRecordNotCommitted | The primary finality record is not in the committed state | A settlement attempt |
InvalidPrimaryCertificate | The quorum certificate or one of its signatures is invalid | A settlement attempt |
ApplicationContextMismatch | The source, destination, action, accepted root or preclaim is inconsistent | A settlement attempt |
ProgramVkMismatch | The guest program's verification-key projection is not the route's value | A settlement attempt |
Groth16VkMismatch | The Groth16 verification key is not the route's value | A settlement attempt |
Groth16SetupMismatch | The Groth16 setup identity is not the route's value | A settlement attempt |
ReceiptContextMismatch | The receipt's network, runtime, context or domain is not the route's profile | A settlement attempt |
SourceBlockNotFinalized | The receipt's block has not reached authenticated source finality | A settlement attempt |
StatementLeafMismatch | The receipt's statement leaf differs from the canonical recomputation | A settlement attempt |
InvalidMerklePath | The leaf count, index or path shape is invalid | A settlement attempt, validate_receipt_attachment |
ReceiptRootMismatch | The Merkle path does not reach the authenticated receipt root | A settlement attempt, validate_receipt_attachment |
ClaimIdMismatch | The delivery claim's identity does not match the canonical claim | A settlement attempt |
ClaimAlreadySeen | The same delivery claim was already consumed | authorize_new |
SettlementAlreadyConsumed | The same economic transition was already settled through some route or claim | authorize_new |
RouteAlreadyRegistered | A route with the same immutable content address is already registered | register_route |
Some checks read state that can change between attempts: registered routes, lifecycle entries and the answers of the PrimaryFinalityVerifier and FinalizedReceiptRootSource you supply. SourceBlockNotFinalized, for example, can pass on a later attempt once the receipt's block is finalized. ClaimAlreadySeen and SettlementAlreadyConsumed are the replay checks of authorize_new and do not pass again for the same claim.
ReceiptRootPolicyError#
statesync_gkr::wrap::risc0_destination_policy::ReceiptRootPolicyError implements Display and std::error::Error. It is returned by ReferenceReceiptRootRegistry::new, register and register_signatures, and by recover_evm_signer.
| Variant | Meaning | Checked by |
|---|---|---|
InvalidThreshold | The threshold is zero or exceeds the configured signer count | Policy validation in new and register |
ZeroConfiguredSigner | A configured signer is the zero address, which could alias a failed recovery | Policy validation |
ConfiguredSignersNotSorted | Configured signers are not in strictly ascending order | Policy validation |
DuplicateConfiguredSigner | The configured signer list repeats an identity | Policy validation |
InvalidSignature | The signature bytes are not a valid secp256k1 (r, s) pair | recover_evm_signer, register_signatures |
HighSignatureS | The signature's s is not in the lower half of the curve order | recover_evm_signer, register_signatures |
InvalidRecoveryId | The recovery byte is not 27 or 28 | recover_evm_signer, register_signatures |
SignatureRecoveryFailed | No public key can be recovered from the signature | recover_evm_signer, register_signatures |
ZeroAttester | A recovered or supplied signer is the zero address | recover_evm_signer, register |
AttestersNotSorted | Signer identities are not in strictly ascending order | register |
DuplicateAttester | A signer identity is repeated | register |
UnauthorizedAttester | A signer is not in the configured set | register |
BelowThreshold | Fewer distinct configured signers attested than the threshold requires | register |
AuthorizationReplay | The exact signed authorization is already registered | register |
RootConflict | The coordinate already holds different signed metadata | register |
StaleSignerSetEpoch | The authorization uses a signer-set epoch other than the active one | register |
AuthorityContextMismatch | The source, genesis, runtime, context or domain differs from the policy | register |
InvalidLeafCount | The authorization does not describe exactly one leaf | register |
UnexpectedAuthorizationNonce | A new coordinate does not use the exact next authorization nonce | register |
ArithmeticOverflow | A nonce or transition counter cannot be incremented | register |
register_signatures recovers each signer with recover_evm_signer and then calls register, so it can return every variant.
DestinationReplayError#
statesync_gkr::wrap::risc0_destination_policy::DestinationReplayError is returned by ReferenceDestinationReplayRegistry::consume, which checks both keys before it inserts either.
| Variant | Meaning |
|---|---|
ClaimReplay | The delivery claim was already consumed |
SettlementReplay | The route-independent settlement key was already consumed |
ArithmeticOverflow | The accepted-transition counter cannot be incremented |
Risc0RouteBManifestError#
statesync_gkr::wrap::risc0_route_b_manifest::Risc0RouteBManifestError is a struct that carries a message and implements Display and std::error::Error. Risc0RouteBManifestV1::from_json, canonical_bytes and route_id, and Risc0RouteBManifestVectorV1::from_json, compute and transcript return it when strict parsing, canonical encoding or a vector check fails. Log the message and reject the manifest or vector.
Helpers with string errors#
wrap::risc0_destination_policy::source_runtime_id, which derives a runtime identity from version fields and a code hash, returns Result<Hash32, String>. The methods of ReceiptRootQuorumVectorV1, a strict cross-language test vector, also return string errors: from_json, settlement_raw_statement, replay_keys, verify_negative_cases, negative_case_observations, compute and verify_expected.
Next steps#
- Troubleshooting: symptoms, causes and fixes, including rejected proofs.
- Encoding and transport: diagnose received bytes that fail verification.
- API reference: signatures of every function named on this page.