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

Changelog

Release history for Achronyme.

All notable changes to Achronyme are documented here. Each release bumps all 9 workspace crates to the same version.

0.1.0-beta.17 — 2026-03-28

8.6x VM speedup, bytecode optimizer, ProveIR observability, WASM crate, OuterScope unification, 42% constraint reduction.

Performance: 1315ms → 303ms (8.6x with PGO)

Six VM hot-path optimizations and a three-pass bytecode optimizer bring the 10M-iteration benchmark from 1315ms to 303ms:

OptimizationSpeedupTechnique
Unchecked register access-25%get(idx)get_unchecked(idx) in hot loop
Boxed error payloadsfixResult<Value> from 48 → 16 bytes
Dispatch cache-40%Cache closure→function lookup, skip Arena free-check
Batch GC-7%Check every 1024 instructions, not every instruction
Budget sentinel-7%Option<u64>u64 with u64::MAX sentinel
Fused int check-6%as_int_unchecked() eliminates double tag check
Bytecode optimizer-8%Three compiler passes (see below)
PGO-34%Profile-guided optimization via scripts/build-pgo.sh

Three attempted optimizations were reverted after measurement showed no improvement with PGO (branch reordering, Result removal, specialized integer opcodes), confirming that with PGO the branch predictor already handles hot-path branches optimally — future gains must reduce work, not branches.

Bytecode optimizer (compiler/src/optimizer.rs)

Three peephole passes run on every function’s bytecode after compilation:

  1. Redundant Load EliminationSetGlobal Ra, idx; GetGlobal Rb, idxMove Rb, Ra. Jump targets act as barriers.
  2. Constant Hoisting (LICM) — Loop-invariant LoadConst moved before loop. Re-runs after register promotion to catch newly hoistable constants.
  3. Register Promotion — Globals in call-free innermost loops promoted to local registers. GET_GLOBAL/SET_GLOBAL become Move, with one-time load before the loop and write-back at each exit point.

26 unit tests. The optimizer correctly handles continue (collapsed back-edges), nested loops (innermost-only promotion), and read-only globals (no write-back for let bindings).

ProveIR Display

ach disassemble examples/proof_of_membership.ach
  -- ProveIR block 1 (instruction 0134) --
  Captures:
    key_5                (witness)
    proof_path_0..2      (witness)
    proof_indices_0..2   (witness)
  Public inputs:
    merkle_root: Field
  Body:
    let my_commitment = poseidon($key_5, 0)
    merkle_verify(merkle_root, my_commitment, proof_path, proof_indices)

Human-readable representation of pre-compiled circuit templates. Captured variables prefixed with $ to distinguish them from circuit-local names. Shows captures with usage roles (witness/structure/both), typed inputs, and full circuit body with proper indentation.

Disassembler rewrite

The ach disassemble command now works from compiled artifacts instead of re-parsing source:

  • Prove blocks — Deserialized from bytecode constant pool via ProveIR::from_bytes(). Previously failed with “expected expression, found ..”.
  • Circuit declarationscircuit name(params) { body } extracted from AST with span-based source slicing. Previously failed with “circuit declarations not supported inside circuits”.

New: achronyme-wasm crate

7 of 9 core crates compile to wasm32-unknown-unknown (parser, compiler, vm, ir, memory, constraints, std). The wasm/ crate provides a browser-ready API for the planned playground (0.3.0).

New: Int and String type annotations

let count: Int = 42
let name: String = "hello"

VM-mode type annotations for documentation and future type checking. Joins the existing Field, Bool, Public, Witness annotations.

OuterScope unification

Functions defined before prove {} and inline circuit {} blocks are now accessible inside them — the same behavior that ach circuit files already had, now unified across all compilation paths.

fn double(x) { x * 2 }

prove(expected: Public) {
    assert_eq(double(val), expected)   // ✓ works now
}

New OuterScope struct replaces the ad-hoc preamble prepend hack in compile_circuit(). FnDecl ASTs are registered in the ProveIR compiler’s fn_table before block compilation, so outer functions are inlined identically to locally-defined ones.

ZK constraint optimization: 2,539 → 1,461 (-42.4%)

Three optimizations reduce the proof of membership (depth-3 Merkle) constraint count:

OptimizationBeforeAfterΔ
Conditional swap in merkle_verify2,5391,464-42.3%
Boolean enforcement dedup1,4641,461-0.2%
Total2,5391,461-42.4%

Conditional swap — The merkle_verify builtin now uses 2 Mux + 1 Poseidon per Merkle level (365 constraints) instead of 2 Poseidon + 1 Mux (724 constraints). This matches the standard pattern used by Tornado Cash and Semaphore.

Boolean enforcement dedup — The R1CS backend now tracks which variables have already had v * (1-v) = 0 enforced. When the same condition is used in multiple Mux/And/Or instructions, the check is emitted only once.

New: CSE (Common Sub-expression Elimination) pass

New IR optimization pass that identifies duplicate pure computations and remaps their results to the first occurrence. Covers all pure instructions: arithmetic, Poseidon, Mux, boolean ops, comparisons. Side-effecting instructions (AssertEq, Assert, RangeCheck) are never deduplicated. Handles chained substitutions.

The optimization pipeline is now: constant folding → bound inference → CSE → dead code elimination.

New: Proof of membership example

examples/proof_of_membership.ach — depth-3 Merkle tree with 8 members. Two members prove membership via Poseidon commitments and merkle_verify without revealing identity. 1,461 constraints per proof (Groth16, ~855 bytes).

Fix: BigInt.to_string() and BigInt.to_hex()

BigInt values now support string conversion:

let b = bigint256(42)
print(b.to_string())   // "42"
print(b.to_hex())      // "0x2a"

Test suite: 2,705 tests

2,543 unit/integration tests (cargo test —workspace) + 162 E2E tests (run_tests.sh). Up from 2,125 in beta.16.

PGO build script

./scripts/build-pgo.sh

Builds an optimized binary using profile-guided optimization. Collects profiles from the hot-loop benchmark and the VM test suite, then rebuilds with LLVM PGO. Integrated into CI (release.yml) for native release targets.


0.1.0-beta.16 — 2026-03-25

Hardening, circuit imports, assert messages, TOML inputs.

Hardening

  • Panics → Result: Arena::alloc() returns Result, unreachable!() replaced with Err(InvalidOpcode), ~35 defensive unwrap conversions.
  • Generic errors eliminated: RuntimeError::Unknown and SystemError replaced with specific variants (StaleHeapHandle, StaleUpvalue, IoError, etc.).
  • Lexer robustness: from_utf8().unwrap() replaced with proper error propagation.
  • Flat circuit format removed: Top-level public/witness declarations are a compile error. Use circuit name(param: Type, ...) { body }.
  • Keyword argument validation: Typos in circuit calls produce “did you mean?” suggestions.

assert_eq / assert with custom messages

assert_eq(computed, expected, "commitment mismatch")
assert(is_valid, "eligibility check failed")

Optional third argument (string literal) shown when witness evaluation fails. Falls back to variable names + values when no message is provided.

Circuit imports in ProveIR

import "./hash_lib.ach" as h

circuit main(out: Public, a: Witness, b: Witness) {
    assert_eq(h.my_hash(a, b), out)
}

Circuits can now import functions from other modules. Imported functions are inlined during ProveIR compilation. Supports import ... as alias and selective imports. Circular import detection included.

--input-file for TOML circuit inputs

ach circuit merkle.ach --input-file inputs.toml
root = "7853200120375982..."
leaf = "1"
path = ["2", "3"]
indices = ["0", "1"]

Replaces the --inputs "path_0=2,path_1=3" convention with native TOML arrays. Supports string values, integers, hex ("0xFF"), and negative values. Mutually exclusive with --inputs.

Internal

  • ModuleLoader moved from compiler to ir crate (shared between bytecode compiler and ProveIR).
  • ProveIR format version bumped to v3 (assert message field in CircuitNode::AssertEq/Assert).
  • WASM feasibility verified: 7/9 core crates compile to wasm32-unknown-unknown.

0.1.0-beta.15 — 2026-03-23

Syntax unification, global type metadata, Tornado Cash example.

Breaking: Unified syntax

Six inconsistent syntax patterns consolidated into one coherent design:

// Circuit definitions — params with visibility types
circuit eligibility(root: Public, secret: Witness, path: Witness Field[3]) {
    merkle_verify(root, poseidon(secret, 0), path, indices)
}

// Prove blocks — only public params, witnesses auto-captured
prove withdrawal(root: Public, nullifier_hash: Public) {
    merkle_verify(root, commitment, path, indices)
}

// Keyword arguments for all callables
eligibility(root: root_val, secret: my_secret, path: my_path)
  • TypeAnnotation refactored from enum to struct with visibility, base, array_size fields.
  • Public/Witness are contextual type keywords: root: Public, path: Witness Field[3].
  • Call/CircuitCall unified — single Call node with Vec<CallArg> supports keyword args for all callables.
  • CircuitParam, CircuitVisibility removed from AST.
  • Old syntax removedprove(public: [...]), circuit(public x, witness y) no longer parse.

circuit keyword — reusable circuit definitions

circuit eligibility(root: Public, secret: Witness, path: Witness Field[3], indices: Witness Field[3]) {
    let commitment = poseidon(secret, 0)
    merkle_verify(root, commitment, path, indices)
}
  • ach circuit file.ach requires the circuit name(...) declaration format.
  • import circuit "./path.ach" as name imports standalone circuit files.
  • Circuit calls with keyword arguments: eligibility(root: val, secret: s).

Named prove blocks

prove vote(hash: Public) {
    assert_eq(poseidon(secret, 0), hash)
}
  • prove name(...) at statement level desugars to let name = prove name(...)
  • Proofs are first-class values (TAG_PROOF)

--circuit-stats — circuit constraint profiler

ach circuit merkle.ach --circuit-stats
ach run program.ach --circuit-stats
  • 7 categories: Arithmetic, Assertions, Range checks, Hashes, Comparisons, Boolean ops, Selections
  • ach run --circuit-stats collects stats across all prove blocks at runtime
  • Static cost model matches actual R1CS constraint counts exactly

Global type metadata (GlobalEntry)

Global variables now carry type annotations through the compiler pipeline:

let path: Field[2] = [voter1, n1]   // type_ann preserved in GlobalEntry
prove(root: Public) {
    merkle_verify(root, leaf, path, idx)  // path correctly captured as array
}
  • global_symbols upgraded from HashMap<String, u16> to HashMap<String, GlobalEntry> with index, type_ann, is_mutable fields.
  • compile_prove reads GlobalEntry.type_ann to build enriched OuterScopeEntry for globals.
  • find_array_size searches locals, upvalues, AND globals.
  • Selective imports propagate type_ann from source module.

Prove block array captures

Array variables from VM scope are automatically captured by prove blocks:

let path = [voter1, n1]           // inferred as Field[2]
prove(merkle_root: Public) {
    merkle_verify(merkle_root, commitment, path, indices)
}
  • Array type inferencelet x = [a, b, c] infers Field[3] on immutable bindings.
  • extract_array_ident capture tracking — merkle_verify and array-consuming constructs now correctly mark array element captures for instantiation.
  • VM prove handler auto-expands TAG_LIST captures into scalar entries.

Type annotation warnings

let x: Bool = 0p42          // W006: type mismatch
let a: Field[3] = [1, 2]    // W007: array size mismatch

Examples

  • tornado_mixer.ach — Full Tornado Cash–style private mixer: 4-user deposit tree, Merkle membership proofs, nullifier-based double-spend prevention, recipient binding. 3 Groth16 proofs generated end-to-end.
  • credential_proof.ach — Updated to use real arrays instead of _N convention.
  • prove_secret_vote.ach — Modernized to new prove(x: Public) syntax.

Editor

  • TextMate grammar updated: Public/Witness as visibility modifiers, Field/Bool as types, circuit/prove named definitions, import circuit, ZK builtins separated.
  • Grammar synced to docs site for Shiki highlighting.

Documentation

  • 21 pages updated (EN + ES) to new syntax across tutorials, getting-started, circuits, and zk-concepts.

0.1.0-beta.14 — 2026-03-21

ProveIR pipeline complete (Phases A-H). Prove blocks are now compiled at compile time and serialized into bytecode, eliminating runtime re-parsing.

New syntax

  • prove(public: [...]) — Prove block syntax with witness auto-inference (superseded by prove(name: Public) in beta.15).
let secret = 0p42
let hash = poseidon(secret, 0)

let p = prove(public: [hash]) {
    assert_eq(poseidon(secret, 0), hash)
}

ProveIR pipeline

  • Phase A: ProveIR compiler — AST to pre-compiled circuit templates with SSA desugaring, function inlining, and method lowering.
  • Phase B: Instantiation — unrolls loops, resolves captures, emits flat IR SSA. Includes MAX_INSTANTIATE_ITERATIONS (1M) and AssertEq consistency enforcement for captures used in both structure and constraints.
  • Phase C: Serialization — bincode with ACHP magic header, version byte, 64 MB size limit, and post-deserialization validation.
  • Phase D: ach circuit uses ProveIR instead of IrLowering (IrLowering fallback retained for circuits with imports).
  • Phase E: compile_prove() compiles prove blocks to ProveIR at compile time and stores serialized bytes in the bytecode constant pool via TAG_BYTES.
  • Phase F: VM handle_prove() reads ProveIR bytes from the constant pool, the handler deserializes and instantiates with captured scope values.
  • Phase G: Parser supports prove(name: Public) syntax. Compiler synthesizes PublicDecl statements and validates no mixed syntax.
  • Phase H: Documentation updated (18 pages, EN + ES).

Hardening

  • ImportsNotSupported explicit error variant (replaces fragile string matching for IrLowering fallback detection).
  • Capture reference validation in ProveIR::validate() — rejects ArraySize::Capture and ForRange::WithCapture referencing unknown capture names.
  • Signed-range comparison contract documented on Instruction::IsLt / IsLe.
  • Input validation in execute_prove_ir() — errors instead of silently skipping missing scope values.
  • Upvalue scope fix — compile_prove() includes parent scope locals so prove blocks inside nested functions can reference upvalue-accessible variables.

Infrastructure

  • TAG_BYTES = 14 — new value tag for binary blob constants (GC mark, sweep, recount integrated).
  • BytesInterner — append-only compiler interner for serialized ProveIR.
  • SER_TAG_BYTES — bytecode serializer/loader support for bytes constants in .achb files.
  • Removed source: String from Expr::Prove AST and unused source parameter from Parser::new().

Examples

Three new examples in examples/:

  • private_auction.ach — Sealed-bid auction with Poseidon commitments, range checks, and comparison gadgets.
  • credential_proof.ach — Anonymous credential verification with Merkle membership and age threshold proofs.
  • hash_chain_proof.ach — Iterated Poseidon hashing with functions inside prove blocks.

Editor

  • TextMate grammar: prove-block rule for prove(name: Public) syntax highlighting.
  • LSP: provep completion snippet, updated hover docs.

0.1.0-beta.13 — 2026-03-19

Method dispatch, static namespaces, namespace reorg.

Features

  • Method dispatch (.method()) — 50 prototype methods across 8 types (Int, Field, String, List, Map, BigInt, Bool, Proof). Resolved at compile time via MethodCall opcode (161).
  • Static namespaces (Type::MEMBER) — Int::MAX, Int::MIN, Field::ZERO, Field::ONE, Field::ORDER, BigInt::from_bits. Compiled to LoadConst or GetGlobal.
  • Namespace reorg — Global function count reduced from 43 to 14 builtins + 2 std. Functions like abs, len, min, max, pow, to_string migrated to methods.

Documentation

  • 83 documentation pages (EN + ES) on docs.achrony.me.
  • Map methods reference, method migration guide, static namespace reference.

0.1.0-beta.12 — 2026-03-18

NativeModule trait, standard library, proc-macros.

Features

  • NativeModule trait — Modular native function registration. Each stdlib group implements NativeModule.
  • achronyme-std crate — 16 new native functions: type conversion, math utilities, extended strings, I/O (feature-gated).
  • #[ach_native] / #[ach_module] proc-macros — Automatic NativeFn wrapper generation with arity checks and type-safe argument extraction.
  • FromValue / IntoValue traits — Type-safe conversion between VM Value and Rust types.

0.1.0-beta.11 — 2026-03-16

Project manifest, ach init command.

Features

  • achronyme.toml — Project configuration with walk-up directory search. Supports [project], [circuit], and [output] sections.
  • ach init — Interactive project scaffolding.
  • Config resolution — CLI flags > TOML > defaults.

0.1.0-beta.10 — 2026-03-16

Plonkish lookup fix, W003 warning.

Bug Fix

  • Plonkish range_check and IsLtBounded now generate real KZG proofs. Migrated from meta.selector() to meta.fixed_column() + meta.lookup_any().

Features

  • W003 warning — Warns when comparisons remain unbounded (~761 constraints) with range_check suggestion.

0.1.0-beta.9 — 2026-03-15

R1CS export fix, IsLtBounded optimization.

Security Fix

  • R1CS export: write_lc() simplifies LinearCombinations before serialization (fixes snarkjs wtns check failures).

Features

  • IsLtBounded optimizationbound_inference pass rewrites comparisons when operands have proven bitwidth. 761 constraints down to 66 for 64-bit comparisons.

Benchmark

PrimitiveAchronymeCircomNotes
Poseidon(t=3)36251730% more efficient
IsLt (64-bit)6667Parity

0.1.0-beta.3 — 2026-03-04

Zero-panic hardening, architecture refactoring.

Security (16 fixes)

All .unwrap() and panic! paths replaced with Result propagation across compiler, VM, memory, and constraint backends.

Architecture (13 refactors)

Monolithic files split into focused submodules: vm.rs, field.rs, parser.rs, poseidon.rs, plonkish_backend.rs, lower.rs, eval.rs, Arena<T>.

Navigation