Changelog
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
Section titled “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)
Section titled “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:
| Optimization | Speedup | Technique |
|---|---|---|
| Unchecked register access | -25% | get(idx) → get_unchecked(idx) in hot loop |
| Boxed error payloads | fix | Result<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)
Section titled “Bytecode optimizer (compiler/src/optimizer.rs)”Three peephole passes run on every function’s bytecode after compilation:
- Redundant Load Elimination —
SetGlobal Ra, idx; GetGlobal Rb, idx→Move Rb, Ra. Jump targets act as barriers. - Constant Hoisting (LICM) — Loop-invariant
LoadConstmoved before loop. Re-runs after register promotion to catch newly hoistable constants. - Register Promotion — Globals in call-free innermost loops promoted to local registers.
GET_GLOBAL/SET_GLOBALbecomeMove, 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
Section titled “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
Section titled “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 declarations —
circuit name(params) { body }extracted from AST with span-based source slicing. Previously failed with “circuit declarations not supported inside circuits”.
New: achronyme-wasm crate
Section titled “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
Section titled “New: Int and String type annotations”let count: Int = 42let name: String = "hello"VM-mode type annotations for documentation and future type checking. Joins the existing Field, Bool, Public, Witness annotations.
OuterScope unification
Section titled “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%)
Section titled “ZK constraint optimization: 2,539 → 1,461 (-42.4%)”Three optimizations reduce the proof of membership (depth-3 Merkle) constraint count:
| Optimization | Before | After | Δ |
|---|---|---|---|
Conditional swap in merkle_verify | 2,539 | 1,464 | -42.3% |
| Boolean enforcement dedup | 1,464 | 1,461 | -0.2% |
| Total | 2,539 | 1,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
Section titled “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
Section titled “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()
Section titled “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
Section titled “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
Section titled “PGO build script”./scripts/build-pgo.shBuilds 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
Section titled “0.1.0-beta.16 — 2026-03-25”Hardening, circuit imports, assert messages, TOML inputs.
Hardening
Section titled “Hardening”- Panics → Result:
Arena::alloc()returnsResult,unreachable!()replaced withErr(InvalidOpcode), ~35 defensive unwrap conversions. - Generic errors eliminated:
RuntimeError::UnknownandSystemErrorreplaced with specific variants (StaleHeapHandle,StaleUpvalue,IoError, etc.). - Lexer robustness:
from_utf8().unwrap()replaced with proper error propagation. - Flat circuit format removed: Top-level
public/witnessdeclarations are a compile error. Usecircuit name(param: Type, ...) { body }. - Keyword argument validation: Typos in circuit calls produce “did you mean?” suggestions.
assert_eq / assert with custom messages
Section titled “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
Section titled “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
Section titled “--input-file for TOML circuit inputs”ach circuit merkle.ach --input-file inputs.tomlroot = "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
Section titled “Internal”ModuleLoadermoved fromcompilertoircrate (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
Section titled “0.1.0-beta.15 — 2026-03-23”Syntax unification, global type metadata, Tornado Cash example.
Breaking: Unified syntax
Section titled “Breaking: Unified syntax”Six inconsistent syntax patterns consolidated into one coherent design:
// Circuit definitions — params with visibility typescircuit eligibility(root: Public, secret: Witness, path: Witness Field[3]) { merkle_verify(root, poseidon(secret, 0), path, indices)}
// Prove blocks — only public params, witnesses auto-capturedprove withdrawal(root: Public, nullifier_hash: Public) { merkle_verify(root, commitment, path, indices)}
// Keyword arguments for all callableseligibility(root: root_val, secret: my_secret, path: my_path)TypeAnnotationrefactored from enum to struct withvisibility,base,array_sizefields.Public/Witnessare contextual type keywords:root: Public,path: Witness Field[3].Call/CircuitCallunified — singleCallnode withVec<CallArg>supports keyword args for all callables.CircuitParam,CircuitVisibilityremoved from AST.- Old syntax removed —
prove(public: [...]),circuit(public x, witness y)no longer parse.
circuit keyword — reusable circuit definitions
Section titled “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.achrequires thecircuit name(...)declaration format.import circuit "./path.ach" as nameimports standalone circuit files.- Circuit calls with keyword arguments:
eligibility(root: val, secret: s).
Named prove blocks
Section titled “Named prove blocks”prove vote(hash: Public) { assert_eq(poseidon(secret, 0), hash)}prove name(...)at statement level desugars tolet name = prove name(...)- Proofs are first-class values (
TAG_PROOF)
--circuit-stats — circuit constraint profiler
Section titled “--circuit-stats — circuit constraint profiler”ach circuit merkle.ach --circuit-statsach run program.ach --circuit-stats- 7 categories: Arithmetic, Assertions, Range checks, Hashes, Comparisons, Boolean ops, Selections
ach run --circuit-statscollects stats across all prove blocks at runtime- Static cost model matches actual R1CS constraint counts exactly
Global type metadata (GlobalEntry)
Section titled “Global type metadata (GlobalEntry)”Global variables now carry type annotations through the compiler pipeline:
let path: Field[2] = [voter1, n1] // type_ann preserved in GlobalEntryprove(root: Public) { merkle_verify(root, leaf, path, idx) // path correctly captured as array}global_symbolsupgraded fromHashMap<String, u16>toHashMap<String, GlobalEntry>withindex,type_ann,is_mutablefields.compile_provereadsGlobalEntry.type_annto build enrichedOuterScopeEntryfor globals.find_array_sizesearches locals, upvalues, AND globals.- Selective imports propagate
type_annfrom source module.
Prove block array captures
Section titled “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 inference —
let x = [a, b, c]infersField[3]on immutable bindings. extract_array_identcapture tracking — merkle_verify and array-consuming constructs now correctly mark array element captures for instantiation.- VM prove handler auto-expands
TAG_LISTcaptures into scalar entries.
Type annotation warnings
Section titled “Type annotation warnings”let x: Bool = 0p42 // W006: type mismatchlet a: Field[3] = [1, 2] // W007: array size mismatchExamples
Section titled “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_Nconvention.prove_secret_vote.ach— Modernized to newprove(x: Public)syntax.
Editor
Section titled “Editor”- TextMate grammar updated:
Public/Witnessas visibility modifiers,Field/Boolas types,circuit/provenamed definitions,import circuit, ZK builtins separated. - Grammar synced to docs site for Shiki highlighting.
Documentation
Section titled “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
Section titled “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
Section titled “New syntax”prove(public: [...])— Prove block syntax with witness auto-inference (superseded byprove(name: Public)in beta.15).
let secret = 0p42let hash = poseidon(secret, 0)
let p = prove(public: [hash]) { assert_eq(poseidon(secret, 0), hash)}ProveIR pipeline
Section titled “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) andAssertEqconsistency enforcement for captures used in both structure and constraints. - Phase C: Serialization — bincode with
ACHPmagic header, version byte, 64 MB size limit, and post-deserialization validation. - Phase D:
ach circuituses 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 viaTAG_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 synthesizesPublicDeclstatements and validates no mixed syntax. - Phase H: Documentation updated (18 pages, EN + ES).
Hardening
Section titled “Hardening”ImportsNotSupportedexplicit error variant (replaces fragile string matching for IrLowering fallback detection).- Capture reference validation in
ProveIR::validate()— rejectsArraySize::CaptureandForRange::WithCapturereferencing 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
Section titled “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.achbfiles.- Removed
source: StringfromExpr::ProveAST and unusedsourceparameter fromParser::new().
Examples
Section titled “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
Section titled “Editor”- TextMate grammar:
prove-blockrule forprove(name: Public)syntax highlighting. - LSP:
provepcompletion snippet, updated hover docs.
0.1.0-beta.13 — 2026-03-19
Section titled “0.1.0-beta.13 — 2026-03-19”Method dispatch, static namespaces, namespace reorg.
Features
Section titled “Features”- Method dispatch (
.method()) — 50 prototype methods across 8 types (Int, Field, String, List, Map, BigInt, Bool, Proof). Resolved at compile time viaMethodCallopcode (161). - Static namespaces (
Type::MEMBER) —Int::MAX,Int::MIN,Field::ZERO,Field::ONE,Field::ORDER,BigInt::from_bits. Compiled toLoadConstorGetGlobal. - Namespace reorg — Global function count reduced from 43 to 14 builtins + 2 std. Functions like
abs,len,min,max,pow,to_stringmigrated to methods.
Documentation
Section titled “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
Section titled “0.1.0-beta.12 — 2026-03-18”NativeModule trait, standard library, proc-macros.
Features
Section titled “Features”- NativeModule trait — Modular native function registration. Each stdlib group implements
NativeModule. achronyme-stdcrate — 16 new native functions: type conversion, math utilities, extended strings, I/O (feature-gated).#[ach_native]/#[ach_module]proc-macros — AutomaticNativeFnwrapper generation with arity checks and type-safe argument extraction.FromValue/IntoValuetraits — Type-safe conversion between VMValueand Rust types.
0.1.0-beta.11 — 2026-03-16
Section titled “0.1.0-beta.11 — 2026-03-16”Project manifest, ach init command.
Features
Section titled “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
Section titled “0.1.0-beta.10 — 2026-03-16”Plonkish lookup fix, W003 warning.
Bug Fix
Section titled “Bug Fix”- Plonkish
range_checkandIsLtBoundednow generate real KZG proofs. Migrated frommeta.selector()tometa.fixed_column()+meta.lookup_any().
Features
Section titled “Features”- W003 warning — Warns when comparisons remain unbounded (~761 constraints) with
range_checksuggestion.
0.1.0-beta.9 — 2026-03-15
Section titled “0.1.0-beta.9 — 2026-03-15”R1CS export fix, IsLtBounded optimization.
Security Fix
Section titled “Security Fix”- R1CS export:
write_lc()simplifies LinearCombinations before serialization (fixes snarkjswtns checkfailures).
Features
Section titled “Features”- IsLtBounded optimization —
bound_inferencepass rewrites comparisons when operands have proven bitwidth. 761 constraints down to 66 for 64-bit comparisons.
Benchmark
Section titled “Benchmark”| Primitive | Achronyme | Circom | Notes |
|---|---|---|---|
| Poseidon(t=3) | 362 | 517 | 30% more efficient |
| IsLt (64-bit) | 66 | 67 | Parity |
0.1.0-beta.3 — 2026-03-04
Section titled “0.1.0-beta.3 — 2026-03-04”Zero-panic hardening, architecture refactoring.
Security (16 fixes)
Section titled “Security (16 fixes)”All .unwrap() and panic! paths replaced with Result propagation across compiler, VM, memory, and constraint backends.
Architecture (13 refactors)
Section titled “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>.