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.

CallReturnsA failure appears as
prepareResult<PreparedSync, SyncError>SyncError::Compile
prepare_pinned_d24_a_membershipResult<PreparedSync, PreparedMaterialError>PreparedMaterialError
prove_sync_op, make_jobResult<_, SyncError>SyncError::Compile or SyncError::Witness
prove_sync_op_prepared, make_job_preparedResult<_, SyncError>SyncError::Witness
prove_batchVec<GkrProof>An empty vector when the configuration does not compile
prove_batch_prepared, prove_batch_parallelVec<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_opboolfalse, for every kind of failure
circuit_identity, encode_sync_resultResult<_, EncodeError>EncodeError
wrap_relationOption<WrapStatementV1>None
wrap_sync_opResult<WrappedProof, WrapError>WrapError
compiler::compile, compiler::compile_with_hintsResult<_, CompileError>CompileError::UnsupportedConfig
compiler::validate_unary_gatesResult<(), CompileError>CompileError::NonUnaryGate
compiler::generate_witness, compiler::build_input_vector, compiler::MerklePath::compute_rootResult<_, SmtError>SmtError
compiler::smt_valid_nativeResult<bool, SmtError>Ok(false) when the operation does not hold; SmtError when the input is malformed
primitives::hash::leaf_fold, HashGadget::hash_leafResult<_, HashError>HashError
gkr::evaluate_circuitResult<CircuitWitness<F>, CircuitError>CircuitError
gkr::verifyResult<InputClaim, GkrError>GkrError. Ok is not acceptance: the caller must still check both input claims
sumcheck::verifyResult<Subclaim<F>, SumcheckError>SumcheckError. Ok is not acceptance: the caller must still check the subclaim
sumcheck::MultilinearPoly::from_evalsResult<MultilinearPoly<F>, PolyError>PolyError
wrap::encoding::encode_inner_proofResult<Vec<u8>, EncodeError>EncodeError
wrap::encoding::decode_inner_proofResult<InnerProofEnvelope, DecodeError>DecodeError
wrap::statement::unpack_digestOption<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_prepared and make_job_prepared assert 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. Check request.operation.kind() against prepared.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::prove expects the witness that gkr::evaluate_circuit returns for the same circuit and does not check it.
  • sumcheck::MultilinearPoly::evaluate panics when the point has a different number of variables than the polynomial, and fix_first_var panics when no variable is left.

Working with error values#

  • Every error type implements Debug and Clone. Only the host-only ReceiptRootPolicyError and Risc0RouteBManifestError also implement Display and std::error::Error, so format the others with {:?}.
  • SyncError, GkrError and WrapError do not implement PartialEq. Use match or matches! on them instead of ==.
  • None of the error enums is marked #[non_exhaustive]. An exhaustive match therefore 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:

src/errors.rsRust
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.

VariantMeaningReturned byHandling
Compile(CompileError)The configuration cannot be compiledprepare, prove_sync_op, make_jobFix the configuration. The same configuration fails the same way every time, so do not retry
Witness(SmtError)The request cannot be turned into circuit inputsprove_sync_op, prove_sync_op_prepared, make_job, make_job_preparedReject 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.

VariantMeaningReturned byHandling
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::CompileUse 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 in1compiler::validate_unary_gates only. The compiler's own circuits satisfy the ruleSet in2 equal to in1 for every Lin and Pow3 gate in your circuit

SmtError#

statesync_gkr::compiler::SmtError. It implements From<HashError>.

VariantMeaningReturned byHandling
PathLengthMismatch { expected, found }The path has found siblings and the tree depth is expectedMerklePath::compute_root, compiler::build_input_vector, compiler::generate_witness, compiler::smt_valid_native; through SyncError::WitnessFetch 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 64MerklePath::compute_root, compiler::smt_valid_native. Witness generation does not check the range, so proving succeeds and verification returns falseAllocate keys within the tree's range
LeafEncoding(HashError)A leaf encoding is empty or longer than the leaf boundMerklePath::compute_root, compiler::build_input_vector, compiler::generate_witness, compiler::smt_valid_native; through SyncError::WitnessSee 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.

VariantMeaningReturned byHandling
EncodingTooLong { len, max }The encoding has len field elements and the leaf bound is maxprimitives::hash::leaf_fold, HashGadget::hash_leaf; wrapped in SmtError::LeafEncodingAn 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
EmptyEncodingThe encoding is emptyleaf_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 itPass a non-empty encoding

EncodeError#

statesync_gkr::wrap::encoding::EncodeError.

VariantMeaningReturned byHandling
BadRoundPolyArity { layer, round, got }The round polynomial of round round in layer layer has got coefficients instead of fivewrap::encoding::encode_inner_proof, encode_sync_resultThe prover always emits five coefficients, so the proof was built or changed elsewhere. Encode only proofs returned by the prover
CountOverflowA count does not fit its wire field. Through the facade, the cause is a leaf_max_fields above 65,535encode_inner_proof, encode_sync_result, circuit_identityUse a leaf bound of at most 65,535
MessageTooLongThe encoding would exceed u32::MAX bytesencode_inner_proof, encode_sync_resultMeasured 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.

GroupVariants and fieldsMeaning
Length and framingHeaderTooShort { 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 versionBadMagic, 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
ContentUnknownOpKindTag(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.

VariantMeaningHandling
Sumcheck { layer, source }The sumcheck of layer layer (0 is the output layer) failed; source is the SumcheckErrorReject 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 evaluationsReject the proof. If honest proofs fail this way, check that the wiring oracle describes the same circuit
ShapeMismatchThe 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 wireReject 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.

VariantMeaning
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.

VariantMeaningHandling
InputWidthMismatch { expected, found }The input vector has found entries instead of expected, which is 2^input_width_bitsPad 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 belowFix 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.

VariantMeaningHandling
NotPowerOfTwo { len }The evaluation table has len entries, which is zero or not a power of twoPad the table to a power of two

WrapError#

statesync_gkr::wrap::WrapError.

VariantMeaningReturned byHandling
BackendUnavailable { backend }The requested backend is not available in this build. No backend in this source returns itImplementations of wrap::WrapBackend::wrapUse 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 decodewrap_sync_op, WrapBackend::wrapTreat 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.

GroupVariants and fieldsMeaning
Size and structureLengthOverflow, 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 profileBadMagic, BadDomain, UnsupportedSchema { found }, UnsupportedVersion { axis, found }, WrongProfile { axis, found }, OperationKindMismatch { found }, ConfigMismatchThe 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 formNonCanonicalEncoding { offset }, NonCanonicalField { offset }A reserved field, tag or alternative representation is not canonical, or a field element is not a canonical KoalaBear residue
ContentInvalidCircuit, CircuitCommitmentMismatch, WiringMismatch, PreparedDigestMismatchCircuit 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 provenanceProvenanceMismatch, NonCanonicalProvenanceverify_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 bindingRouteIdMismatch, GuestImageMismatch, RouteNotEffective, RouteRevoked, SupersededRoutePreparedMaterialBindingAuditV1::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.

VariantMeaningReturned by
InvalidEnvelopeThe claim's magic or envelope version is not recognizedA settlement attempt
NonCanonicalStatementA raw scalar, header field, digest packing or asset scalar is invalidfrom_statement, from_bytes, a settlement attempt
InvalidActionKindThe raw statement contains an unknown action tagaction_kind, a settlement attempt
UnknownRouteThe route is not registeredset_lifecycle, a settlement attempt
InvalidRouteManifestThe route's content, codec, statement or Merkle profile is invalidregister_route, a settlement attempt
InvalidLifecycleThe lifecycle state and the acceptance policy disagreeregister_route, set_lifecycle, authorize_new
StaleLifecycleRevisionLifecycle revisions and effective heights must increaseset_lifecycle
RouteRevokedThe route is revoked for new acceptanceauthorize_new
CheckpointPastCutoffThe checkpoint of a draining route is past its cutoffauthorize_new
PrimaryRecordMismatchThe claim, primary finality record and route do not bind to each otherA settlement attempt
PrimaryRecordNotCommittedThe primary finality record is not in the committed stateA settlement attempt
InvalidPrimaryCertificateThe quorum certificate or one of its signatures is invalidA settlement attempt
ApplicationContextMismatchThe source, destination, action, accepted root or preclaim is inconsistentA settlement attempt
ProgramVkMismatchThe guest program's verification-key projection is not the route's valueA settlement attempt
Groth16VkMismatchThe Groth16 verification key is not the route's valueA settlement attempt
Groth16SetupMismatchThe Groth16 setup identity is not the route's valueA settlement attempt
ReceiptContextMismatchThe receipt's network, runtime, context or domain is not the route's profileA settlement attempt
SourceBlockNotFinalizedThe receipt's block has not reached authenticated source finalityA settlement attempt
StatementLeafMismatchThe receipt's statement leaf differs from the canonical recomputationA settlement attempt
InvalidMerklePathThe leaf count, index or path shape is invalidA settlement attempt, validate_receipt_attachment
ReceiptRootMismatchThe Merkle path does not reach the authenticated receipt rootA settlement attempt, validate_receipt_attachment
ClaimIdMismatchThe delivery claim's identity does not match the canonical claimA settlement attempt
ClaimAlreadySeenThe same delivery claim was already consumedauthorize_new
SettlementAlreadyConsumedThe same economic transition was already settled through some route or claimauthorize_new
RouteAlreadyRegisteredA route with the same immutable content address is already registeredregister_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.

VariantMeaningChecked by
InvalidThresholdThe threshold is zero or exceeds the configured signer countPolicy validation in new and register
ZeroConfiguredSignerA configured signer is the zero address, which could alias a failed recoveryPolicy validation
ConfiguredSignersNotSortedConfigured signers are not in strictly ascending orderPolicy validation
DuplicateConfiguredSignerThe configured signer list repeats an identityPolicy validation
InvalidSignatureThe signature bytes are not a valid secp256k1 (r, s) pairrecover_evm_signer, register_signatures
HighSignatureSThe signature's s is not in the lower half of the curve orderrecover_evm_signer, register_signatures
InvalidRecoveryIdThe recovery byte is not 27 or 28recover_evm_signer, register_signatures
SignatureRecoveryFailedNo public key can be recovered from the signaturerecover_evm_signer, register_signatures
ZeroAttesterA recovered or supplied signer is the zero addressrecover_evm_signer, register
AttestersNotSortedSigner identities are not in strictly ascending orderregister
DuplicateAttesterA signer identity is repeatedregister
UnauthorizedAttesterA signer is not in the configured setregister
BelowThresholdFewer distinct configured signers attested than the threshold requiresregister
AuthorizationReplayThe exact signed authorization is already registeredregister
RootConflictThe coordinate already holds different signed metadataregister
StaleSignerSetEpochThe authorization uses a signer-set epoch other than the active oneregister
AuthorityContextMismatchThe source, genesis, runtime, context or domain differs from the policyregister
InvalidLeafCountThe authorization does not describe exactly one leafregister
UnexpectedAuthorizationNonceA new coordinate does not use the exact next authorization nonceregister
ArithmeticOverflowA nonce or transition counter cannot be incrementedregister

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.

VariantMeaning
ClaimReplayThe delivery claim was already consumed
SettlementReplayThe route-independent settlement key was already consumed
ArithmeticOverflowThe 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#