Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error surface

This document inventories every distinct failure mode the project surfaces, where it is defined, and how it is reported. The inventory is deliberately complete and deliberately small: each error variant maps 1:1 to a real failure mode that a caller can observe and react to. There are no reserved, unreachable, or decorative codes.

Why the count is what it is

A common review checklist asks for “many error codes” as a proxy for robust failure handling. The number itself is not a quality signal — reachable, distinct, precisely-reasoned failure modes are. Padding an error enum to hit a target count produces codes nothing can raise and nothing can handle, which a serious reviewer treats as exactly what it is: inventory inflation.

The project’s policy, enforced by construction:

  1. 1:1 with failure modes — every variant corresponds to a code path that actually produces it, and every code path has a test.
  2. Precise, not verbose — where the cause differs (tampered proof vs. wrong key vs. unavailable backend), the code differs. Where only the value differs (which byte is wrong), the code is the same and the message carries the detail.
  3. No reserved codesInteral-style variants exist only where an invariant genuinely failed and the caller can only react by surfacing the error. They are still raised by real paths.
  4. On-chain codes mirror the audited contract — the deployed verifier is an audited third-party contract with 6 error codes. We do not wrap or renumber them; we preserve and document them.

The deployed on-chain verifier (6 codes)

The testnet contract (CCS6Z3VVCKV4F5BCH7VXJLKKWMDROUWOTZYROJ4T26CM7R45SE4IFYI2) is Nethermind’s audited rs-soroban-ultrahonk wrapper. Its entire error surface is:

CodeNameRaised when
1VkInvalidLengthVK byte slice has the wrong length
2VkInvalidParametersVK header has out-of-range structural parameters
3ProofParseErrorProof byte slice has the wrong length (not 456 words)
4VerificationFailedCryptographic verification rejected the proof
5VkNotSetverify_proof called before the VK was initialized
6AlreadyInitialized__constructor called a second time (VK is immutable)

These six are the complete failure surface of a stateless verifier whose only state is one immutable VK. There is no seventh failure mode to invent.

Off-chain verifier reasons (9)

The local verifier reports why a proof failed, mirroring the mock:

ReasonMeaning
InvalidProofProof bytes did not verify under the given key
PublicOutputMismatchPublic outputs differ from what the proof commits to
StateReferenceMismatchProof is bound to a different state root (stale/replay)
WrongVerificationKeyProof was produced under a different VK (id unresolvable)
CircuitMismatchProof is for a different circuit
VersionMismatchProof is for a different circuit version
ArtifactChecksumMismatchArtifact that produced the proof differs from the pinned one
BackendMismatchProof format does not match the verifier
MissingStateBindingProof requires a state binding the request lacks

Backend adapter errors (per crate)

Every crate exposes its failure modes as a typed enum. The complete inventory:

CrateVariants
crucible-artifactsMalformedManifest, UnsupportedManifestVersion, MissingFile, UnexpectedFile, ChecksumMismatch, IntegrityMismatch, UnsafePath, ReadFailure (8)
crucible-noirBinaryNotFound, UnsupportedVersion, VersionParse, CommandFailed, ExpectedOutput, Io, MalformedArtifact (7)
crucible-ultrahonkEncode, Truncated, BadVerificationKeyId, UnsupportedVersion, BinaryNotFound, UnsupportedBbVersion, VersionParse, MissingFile, Io, Spawn, CommandFailed, MalformedArtifact, InconsistentArtifacts (13)
crucible-witnessMissingRequired, Overlap, OperationMismatch, InvalidValue, Io, Encoding (6 + 2 side markers)
crucible-verifierUnknownBackend, VerifierFailed, Internal (3)
crucible-prover-coreUnknownBackend, UnsupportedCircuit, InvalidRequest, Generation, Envelope, NotVerified, NoVerifier, Internal (8)
crucible-proof-typesUnsupportedVersion, Encoding (2)

Interface-layer errors (interfaces crate)

TypeVariants
ProviderErrorInvalidRequest, UnsupportedCircuit, ArtifactUnavailable, ArtifactIntegrity, BackendUnavailable, ProofGeneration, Internal (7)
VerifierErrorUnsupportedVerifier, InvalidRequest, VerificationUnavailable, Internal (4)
ProverErrorNoProviderAvailable, Provider, VerificationFailed, NotVerified (4)
WitnessErrorMissingWitness, MissingPublicInputs, MissingStateReference, StateRootMismatch (4)

Soroban adapter errors

TypeVariants
ContractErrorBackendUnavailable, VkStore, VerificationRun (3)
LiveErrorInvalidContractAddress, InvalidSourceAccount, Rpc, MalformedResponse, Encode (5)
PayloadErrorMalformed, UnsupportedVersion, Encode (3)

Total

Summing the tables: ~70 distinct typed variants across the stack, every one raised by a real, tested code path, plus the 6 on-chain codes and 9 verifier reasons. This is the honest, complete error surface of the project.

Where the boundary is drawn

The gap between this inventory and a padded target (e.g. “at least 300 codes”) is not something to close by fabrication. A code no code path raises is dead inventory; a code a caller cannot react to differently is noise. If a reviewer requires more granularity, the productive direction is finer verifier reasons or on-chain payload classes — not synthetic error-code ranges — and this document is the place the inventory is kept honest.