Introducing Achronyme — a language for zero-knowledge proofs. Read the announcement

Error Handling

Runtime errors, circuit errors, taint warnings, and debugging.

Achronyme provides detailed error messages across all execution modes. Errors include source location information when available.

Runtime Errors (VM Mode)

When running with ach run, the VM reports errors with line numbers:

[line 5] in main: DivisionByZero

Error Types

ErrorCause
DivisionByZeroDivision or modulo by zero
IntegerOverflowi60 arithmetic overflow (no silent promotion)
TypeMismatchIncompatible types in operation (e.g., Int + Field)
ArityMismatchWrong number of arguments to a function
AssertionFailedassert(expr) evaluated to false
OutOfBoundsArray index out of range
StackOverflowCall stack exceeded 65,536 entries (likely infinite recursion)
StackUnderflowInternal error — pop from empty stack
InvalidOpcodeCorrupt bytecode — unknown instruction
FunctionNotFoundClosure or function reference is invalid
ProveBlockFailedError during prove {} execution
ProveHandlerNotConfiguredprove {} used without --ptau
SystemErrorInternal VM state corruption

Integer Overflow

Achronyme uses 60-bit signed integers (range: -2^59 to 2^59 - 1). Overflow is always an error:

let big = 576460752303423487   // 2^59 - 1 (max i60)
let overflow = big + 1         // ERROR: IntegerOverflow

To work with larger values, use 0p field literals for BN254 field elements (e.g., 0p42, 0pxFF).

Type Mismatch

Integer and field values cannot be mixed directly:

let x = 42
let y = 0p10
let z = x + y  // ERROR: TypeMismatch

Use 0p literals for explicit conversion: let z = 0p42 + y.

Parse Errors

Syntax errors include line and column information:

parse error at line 3, col 10: expected expression

Circuit Errors (IR Lowering)

When compiling circuits with ach circuit, the IR lowering phase reports errors with source spans:

ErrorCause
UndeclaredVariableVariable used without public/witness declaration
UnsupportedOperationOperation not available in circuit mode (e.g., print)
TypeNotConstrainableNon-field type used in circuit (string, map, etc.)
UnboundedLoopLoop without fixed bounds (circuits require static unrolling)
WrongArgumentCountBuiltin called with wrong arity
DuplicateInputSame variable declared twice
IndexOutOfBoundsArray access beyond array length
ArrayLengthMismatchArray size doesn’t match type annotation
RecursiveFunctionRecursive call detected (not allowed in circuits)
TypeMismatchExpression type doesn’t match annotation
AnnotationMismatchDeclared type conflicts with inferred type

Helpful Messages

Some errors include user-friendly guidance:

// Using a string in a circuit:
"cannot be used in circuits (circuits operate on field elements only)"

// Using a map in a circuit:
"cannot be used in circuits (use arrays instead)"

// Using a decimal:
"field arithmetic is integer-only — use whole numbers"

R1CS Compilation Errors

Errors during constraint generation:

R1CS compilation error: [3:5] undeclared variable 'x'

The [line:col] prefix maps to the source location when available.

IR Evaluation Errors

When using compile_ir_with_witness(), the IR evaluator validates inputs before constraint generation:

ErrorCause
MissingInputRequired circuit input not provided
DivisionByZeroDivision by zero with concrete values
AssertionFailedassert(expr) fails with provided inputs
AssertEqFailedassert_eq(a, b) fails — values don’t match
RangeCheckFailedValue doesn’t fit in declared bit width
NonBooleanMuxConditionmux condition is not 0 or 1
UndefinedVarInternal error — SSA variable not computed

These errors include the concrete values that caused the failure, making debugging easier:

assert_eq failed: x (= 42) != y (= 43)

Taint Analysis Warnings

After circuit compilation, the taint analysis pass checks for potential soundness issues:

UnderConstrained

witness input 'secret' is under-constrained (not in any assert_eq)

A declared input is not used in any constraint. This means a malicious prover could set it to any value without affecting the proof. This is almost always a bug.

UnusedInput

public input 'unused_var' is unused

A declared input is never referenced in the circuit body. Remove the declaration or use the variable.

Prove Block Errors

Errors inside prove {} blocks are categorized by pipeline phase:

PhaseError
IR loweringParsing or AST→IR conversion failed
CompilationConstraint generation failed
VerificationGenerated witness doesn’t satisfy constraints
Proof generationGroth16/PlonK proving failed

Example:

prove block error: IR lowering: undeclared variable 'x'

Debugging Strategies

Check inputs first

Most circuit failures come from wrong input values. The --inputs flag requires exact field-level precision:

ach circuit my_circuit.ach --inputs "a=42,b=7"

Use --no-optimize to isolate issues

If a circuit fails after optimization, try running without it:

ach circuit my_circuit.ach --no-optimize --inputs "a=42,b=7"

Check taint warnings

Always review taint analysis output. An UnderConstrained warning often indicates a missing constraint that could allow a malicious prover to forge proofs.

In VM mode, use print() to inspect values:

let h = poseidon(1, 2)
print("hash:", h)

Verify witness separately

After compiling a circuit, verify the witness with snarkjs:

snarkjs r1cs info circuit.r1cs       # check constraint count
snarkjs wtns check circuit.r1cs witness.wtns  # verify witness

Use --stress-gc for memory issues

If you suspect GC-related bugs:

ach run program.ach --stress-gc

This triggers garbage collection on every allocation, exposing rooting issues.

Navigation