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:
| 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)
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
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 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
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:
| 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
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()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
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
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
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)
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
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
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
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-statscollects 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_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
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
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_Nconvention.prove_secret_vote.ach— Modernized to newprove(x: Public)syntax.
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
- 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 byprove(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) 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
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
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
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-blockrule forprove(name: Public)syntax highlighting. - LSP:
provepcompletion 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 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
- 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-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
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_checkandIsLtBoundednow generate real KZG proofs. Migrated frommeta.selector()tometa.fixed_column()+meta.lookup_any().
Features
- W003 warning — Warns when comparisons remain unbounded (~761 constraints) with
range_checksuggestion.
0.1.0-beta.9 — 2026-03-15
R1CS export fix, IsLtBounded optimization.
Security Fix
- R1CS export:
write_lc()simplifies LinearCombinations before serialization (fixes snarkjswtns checkfailures).
Features
- IsLtBounded optimization —
bound_inferencepass rewrites comparisons when operands have proven bitwidth. 761 constraints down to 66 for 64-bit comparisons.
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
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>.