https://github.com/achronyme/achronyme
A programming language for zero-knowledge circuits with dual execution: full VM + optimized circuit compilation.
https://github.com/achronyme/achronyme
compiler cryptography dsl rust zero-knowledge zk
Last synced: 3 months ago
JSON representation
A programming language for zero-knowledge circuits with dual execution: full VM + optimized circuit compilation.
- Host: GitHub
- URL: https://github.com/achronyme/achronyme
- Owner: achronyme
- License: gpl-3.0
- Created: 2026-02-21T19:07:18.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-03-04T23:53:20.000Z (4 months ago)
- Last Synced: 2026-03-05T05:02:36.920Z (4 months ago)
- Topics: compiler, cryptography, dsl, rust, zero-knowledge, zk
- Language: Rust
- Homepage: https://achrony.me
- Size: 14.7 MB
- Stars: 5
- Watchers: 4
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Audit: audits/AUDIT-2026-02-21.md
Awesome Lists containing this project
README
# Achronyme
[](https://github.com/achronyme/achronyme/actions/workflows/ci.yml)
A programming language for zero-knowledge circuits.
Write readable code. Decide what gets proven. Same language for general execution and ZK circuit compilation.
## Install
```bash
curl -fsSL https://achrony.me/install.sh | sh
```
This installs the `ach` binary to `~/.local/bin`. Requires Linux or macOS (x86_64 or aarch64).
### Build from source
```bash
git clone https://github.com/achronyme/achronyme.git
cd achronyme
cargo build --release
cargo test --workspace # 2,000+ unit tests
bash test/run_tests.sh # 150+ integration tests
```
---
## Quick Look
### General-purpose execution
```achronyme
let make_counter = fn(init) {
mut n = init
return fn() { n = n + 1; return n }
}
let c = make_counter(0)
print(c()) // 1
print(c()) // 2
```
### ZK circuit
```achronyme
public root
witness leaf
witness path[3]
witness indices[3]
merkle_verify(root, leaf, path, indices)
```
```bash
ach circuit merkle.ach --inputs "root=...,leaf=42,path_0=...,path_1=...,path_2=...,indices_0=0,indices_1=1,indices_2=0"
# → circuit.r1cs + witness.wtns (snarkjs-compatible)
```
### Inline proof generation
```achronyme
let secret = 0p42
let hash = 0p17159... // poseidon(42, 0)
let p = prove {
witness secret
public hash
assert_eq(poseidon(secret, 0), hash)
}
print(proof_json(p)) // Groth16 proof, verifiable on-chain
```
---
## How It Works
Achronyme has two execution modes from the same source:
**VM mode** (`ach run`) — Full language: closures, recursion, GC, arrays, maps, strings, I/O. Code runs like any scripting language.
**Circuit mode** (`ach circuit`) — Compiles to arithmetic constraints over BN254. No loops at runtime, no I/O — everything is unrolled and flattened into a constraint system for zero-knowledge proofs.
The `prove {}` block bridges both: it runs inside the VM, compiles its body as a circuit, generates a witness from captured variables, and produces a cryptographic proof — all in one expression.
```
Source (.ach)
│
├─► Parser (Pratt) → AST
│ │
│ ├─► Bytecode → VM (run mode)
│ │
│ └─► SSA IR → Optimize
│ │
│ ┌───┴───┐
│ ▼ ▼
│ R1CS Plonkish
│ (Groth16) (KZG-PlonK)
│ │ │
│ ▼ ▼
│ .r1cs Gates/Lookups
│ .wtns Copy constraints
│ │ │
│ └───┬───┘
│ ▼
│ Native proof
│
└─► prove { } → compile + witness + verify + proof (inline)
```
---
## Language
### Types
| Type | Examples |
|------|---------|
| Int | `42`, `-7` |
| Bool | `true`, `false` |
| String | `"hello"` |
| List | `[1, 2, 3]` |
| Map | `{"a": 1, "b": 2}` |
| Field | `0p42`, `0pxFF`, `0pb1010` |
| BigInt256 | `0i256xFF`, `0i256d42` |
| BigInt512 | `0i512xFF`, `0i512d100` |
| Function | `fn(x) { x + 1 }` |
| Proof | result of `prove { }` |
| Nil | `nil` |
### Control Flow
```achronyme
if x > 0 { print("positive") } else { print("non-positive") }
while n > 0 { n = n - 1 }
for item in list { print(item) }
for i in 0..10 { print(i) }
```
### Functions and Closures
```achronyme
let add = fn(a, b) { a + b }
let fib = fn fib(n) {
if n < 2 { return n }
return fib(n - 1) + fib(n - 2)
}
// Closures capture environment
let make_adder = fn(x) { fn(y) { x + y } }
let add5 = make_adder(5)
print(add5(3)) // 8
```
### Field Elements
BN254 scalar field. Montgomery form internally. Created with the `0p` prefix:
```achronyme
let a = 0p42
let b = 0pxFF
let c = 0pb1010
let sum = a + b
let prod = a * b
let inv = 0p1 / a
```
### BigInt (VM only)
Fixed-width unsigned integers (256-bit and 512-bit) for cryptographic operations:
```achronyme
let a = 0i256xFF
let b = bigint256(42)
let bits = a.to_bits()
let masked = a.bit_and(b)
```
---
## Circuit Features
### Declarations
```achronyme
public output // public input (instance)
witness secret // private input (witness)
witness arr[4] // witness array (arr_0, arr_1, arr_2, arr_3)
```
### Builtins
| Builtin | Description | R1CS cost | Plonkish cost |
|---------|-------------|-----------|---------------|
| `assert_eq(a, b)` | Enforce equality | 1 | 1 |
| `assert(expr)` | Enforce boolean true | 2 | 2 |
| `poseidon(a, b)` | Poseidon 2-to-1 hash | 361 | 361 |
| `poseidon_many(a, b, c, ...)` | Left-fold Poseidon | 361*(n-1) | 361*(n-1) |
| `mux(cond, a, b)` | Conditional select | 2 | 1 |
| `range_check(x, bits)` | Value fits in N bits | bits+1 | 1 (lookup) |
| `merkle_verify(root, leaf, path, indices)` | Merkle membership proof | ~1090/level | ~1090/level |
| `len(arr)` | Compile-time array length | 0 | 0 |
### Operators in Circuits
| Operation | R1CS | Plonkish |
|-----------|------|----------|
| `+`, `-` | 0 | 0 |
| `*` | 1 | 1 |
| `/` | 2 | 2 |
| `^` (constant exp) | O(log n) | O(log n) |
| `==`, `!=` | 2 | 2 |
| `<`, `<=`, `>`, `>=` | ~760 | ~760 |
| `&&`, `\|\|` | 3 | 3 |
| `!` | 1 | 1 |
### Functions in Circuits
Functions are inlined at each call site. No dynamic dispatch, no recursion.
```achronyme
witness a, b
public out
fn hash_pair(x, y) { poseidon(x, y) }
assert_eq(hash_pair(a, b), out)
```
### Control Flow in Circuits
`if/else` compiles to `mux` (both branches are evaluated). `for` loops are statically unrolled. `while`, `break`, `continue` are rejected at compile time.
```achronyme
witness vals[4]
public total
let sum = vals[0]
let sum = sum + vals[1]
let sum = sum + vals[2]
let sum = sum + vals[3]
assert_eq(sum, total)
```
---
## CLI
```bash
# Run a program
ach run script.ach
# Run with PlonK prove backend
ach run script.ach --prove-backend plonkish
# Compile circuit (in-source declarations)
ach circuit circuit.ach --inputs "x=42,y=7"
# Compile circuit (CLI declarations)
ach circuit circuit.ach --public "out" --witness "a,b" --inputs "out=42,a=6,b=7"
# Plonkish backend
ach circuit circuit.ach --backend plonkish --inputs "x=42,y=7"
# Generate Plonkish proof
ach circuit circuit.ach --backend plonkish --inputs "x=42" --prove
# Generate Solidity verifier contract
ach circuit circuit.ach --inputs "x=42,y=7" --solidity
# Compile to bytecode
ach compile script.ach --output script.achb
# Disassemble
ach disassemble script.ach
```
Output `.r1cs` and `.wtns` files are compatible with snarkjs:
```bash
snarkjs groth16 setup circuit.r1cs pot12_final.ptau circuit.zkey
snarkjs groth16 prove circuit.zkey witness.wtns proof.json public.json
snarkjs groth16 verify verification_key.json public.json proof.json
```
---
## Prove Blocks
`prove {}` compiles a circuit, captures variables from the enclosing scope, generates a witness, and returns a proof — all inline.
```achronyme
let a = 0p6
let b = 0p7
let product = 0p42
let p = prove {
witness a
witness b
public product
assert_eq(a * b, product)
}
```
Variable names inside `public`/`witness` declarations must match `let` bindings in the outer scope. Integer values are automatically promoted to field elements.
The result is a `Proof` object (Groth16 or PlonK depending on `--prove-backend`). Extract components with `proof_json(p)`, `proof_public(p)`, `proof_vkey(p)`. Verify with `verify_proof(p)`.
If no proving backend is available, the block still compiles the circuit, generates the witness, and verifies constraints locally (returns `nil`).
---
## Optimization Passes
The SSA IR runs four optimization passes before constraint generation:
- **Constant folding** — Evaluates arithmetic on known constants at compile time
- **Dead code elimination** — Removes unused instructions
- **Boolean propagation** — Tracks proven-boolean variables, skips redundant enforcement
- **Taint analysis** — Warns about under-constrained or unused inputs
Disable with `--no-optimize`.
---
## Project Structure
```
achronyme/
├── achronyme-parser/ Hand-written Pratt lexer + recursive descent parser
├── ir/ SSA intermediate representation, optimization passes
├── compiler/ Bytecode compiler, R1CS backend, Plonkish backend
├── vm/ Register-based VM (37 opcodes, prototype method dispatch)
├── memory/ Heap, GC, FieldElement (BN254, BLS12-381, Goldilocks), BigInt
├── constraints/ R1CS/Plonkish systems, Poseidon hash, binary export
├── cli/ CLI, native Groth16 (ark-groth16) & PlonK (halo2-KZG)
├── std/ Standard library (NativeModule: parse_int, join, I/O)
├── ach-macros/ Proc-macros: #[ach_native], #[ach_module]
├── docs/ Documentation site (Astro + Starlight, 83 pages, EN/ES)
└── test/
├── vm/ VM/interpreter integration tests
├── circuit/ Circuit compilation tests
├── prove/ Prove block tests
└── run_tests.sh Integration test runner (158 tests)
```
---
## Global Functions
16 global functions are available without imports. Most operations now use [method syntax](#methods).
| Function | Arity | Description |
|----------|-------|-------------|
| `print(...)` | variadic | Print values to stdout |
| `typeof(x)` | 1 | Type name as String |
| `assert(x)` | 1 | Runtime assertion |
| `time()` | 0 | Unix timestamp (ms) |
| `gc_stats()` | 0 | GC statistics as map |
| `poseidon(a, b)` | 2 | Poseidon 2-to-1 hash (BN254) |
| `poseidon_many(a, b, ...)` | variadic | Left-fold Poseidon hash |
| `verify_proof(p)` | 1 | Verify a Groth16 proof |
| `proof_json(p)` | 1 | Extract proof JSON |
| `proof_public(p)` | 1 | Extract public inputs JSON |
| `proof_vkey(p)` | 1 | Extract verifying key JSON |
| `bigint256(x)` | 1 | Construct 256-bit unsigned integer |
| `bigint512(x)` | 1 | Construct 512-bit unsigned integer |
| `from_bits(bits, width)` | 2 | Bit list to BigInt |
| `parse_int(str)` | 1 | Parse string to integer |
| `join(list, sep)` | 2 | Join strings with separator |
## Methods
Values have type-specific methods called with dot syntax: `value.method(args)`.
```achronyme
// String methods
let upper = "hello".to_upper() // "HELLO"
let words = "a,b,c".split(",") // ["a", "b", "c"]
// List methods
let doubled = [1, 2, 3].map(fn(n) { n * 2 }) // [2, 4, 6]
let evens = [1, 2, 3, 4].filter(fn(n) { n % 2 == 0 }) // [2, 4]
// Map methods
let m = {name: "Alice", age: 30}
assert(m.contains_key("name"))
m.set("city", "NYC")
// Int methods
assert((-42).abs() == 42)
assert(2.pow(10) == 1024)
```
**50 methods** across 6 types: Int (6), String (14), List (13), Map (8), Field (2), BigInt (7).
**Static namespaces** provide type-level constants: `Int::MAX`, `Int::MIN`, `Field::ZERO`, `Field::ONE`, `Field::ORDER`, `BigInt::from_bits`.
---
## Status
- 2,125+ unit tests + 158 integration tests
- Cross-validated against snarkjs (independent constraint verification)
- 2 ZK backends: R1CS/Groth16 + Plonkish/KZG-PlonK
- Native in-process proof generation (no external tools)
- snarkjs-compatible binary export
- Solidity verifier contract generation
- Poseidon hash compatible with circomlibjs
- Runtime errors with source line numbers
- [Documentation](https://docs.achrony.me)
## License
GPL-3.0