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

Inline Proofs

Using prove blocks for inline proof generation.

The prove {} block lets you generate zero-knowledge proofs inline, without leaving the VM. It compiles its body as a circuit at compile time via ProveIR, generates a witness from captured variables, and returns a cryptographic proof.

Basic Example

let x: Field = 0p6
let y: Field = 0p7
let product: Field = 0p42

let p = prove(product: Public) {
    assert_eq(x * y, product)
}

print(proof_json(p))

The prove block:

  1. Compiles assert_eq(x * y, product) as a circuit at compile time
  2. Captures x and y from the outer scope as witness inputs (auto-inferred)
  3. Marks product as a public input (declared as product: Public)
  4. Generates a Groth16 proof at runtime
  5. Returns a ProofObject

Witness Auto-Inference

With the prove(name: Public) syntax, you only declare which variables are public — everything else is automatically inferred as a witness:

let secret: Field = 0p42
let hash: Field = 0p17159...

let p = prove(hash: Public) {
    assert_eq(poseidon(secret, 0), hash)
}
  • hash is declared as hash: Publicpublic input (visible to the verifier)
  • secret is referenced in the body but not in the public list → witness (private, auto-inferred)

The verifier learns only that some value hashes to hash, without learning secret.

Explicit Declarations (Classic Syntax)

The explicit witness/public declaration syntax is also supported:

let secret: Field = 0p42
let hash: Field = 0p17159...

let p = prove {
    witness secret    // private — prover knows this
    public hash       // public — verifier sees this
    assert_eq(poseidon(secret, 0), hash)
}

Both syntaxes produce identical circuits. The prove(name: Public) form is more concise; the explicit form gives full control. You cannot mix both in the same block.

Integer Auto-Conversion

Integers captured by prove blocks are automatically converted to field elements. This is the only place where Int→Field conversion happens implicitly:

let a = 10
let b = 20
let sum = 30

prove(sum: Public) {
    assert_eq(a + b, sum)
}

Outside prove blocks, mixing Int and Field raises a TypeMismatch error. Use 0p field literals for explicit conversion.

Accessing Proof Components

A ProofObject contains three JSON components:

let p = prove(y: Public) {
    assert_eq(x, y)
}

// Groth16 proof data (pi_a, pi_b, pi_c)
let proof = proof_json(p)

// Public inputs as decimal string array
let inputs = proof_public(p)

// Verification key (for on-chain verification)
let vkey = proof_vkey(p)

print(proof)
print(inputs)
print(vkey)

Verifying Proofs

Use verify_proof() to verify a proof within the VM:

let p = prove(hash: Public) {
    assert_eq(poseidon(secret, 0), hash)
}

let ok = verify_proof(p)
print(ok)  // true

Arrays and Functions

Prove blocks support arrays, functions, and all circuit-mode features:

let v0: Field = 0p10
let v1: Field = 0p20
let v2: Field = 0p30
let total: Field = 0p60

prove(total: Public) {
    let vals: Field[3] = [v0, v1, v2]
    let acc: Field = vals[0] + vals[1] + vals[2]
    assert_eq(acc, total)
    assert_eq(len(vals), 3)
}

Poseidon in Prove Blocks

Cryptographic builtins work inside prove blocks:

let a: Field = 0p1
let b: Field = 0p2
let h: Field = 0p7853200120776062878684798364095072458815029376092732009249414926327459813530

prove(h: Public) {
    assert_eq(poseidon(a, b), h)
}

Range Checks

Enforce that a value fits within a certain number of bits:

let val: Field = 0p200

prove() {
    range_check(val, 8)   // 0 ≤ val < 256
}

Multiple Prove Blocks

You can use multiple prove blocks in sequence:

let a: Field = 0p3
let b: Field = 0p4
let sum: Field = 0p7
let product: Field = 0p12

prove(sum: Public) {
    assert_eq(a + b, sum)
}

prove(product: Public) {
    assert_eq(a * b, product)
}

Each prove block compiles and proves independently.

Running with Proof Generation

To generate actual Groth16 proofs, provide a Powers of Tau file:

ach run my_program.ach --ptau pot12_final.ptau

Without --ptau, prove blocks still verify constraints but return VerifiedOnly (no cryptographic proof is generated).

The zkey is cached in ~/.achronyme/cache/ for faster subsequent runs.

Backend Selection

By default, prove blocks use the R1CS/Groth16 backend. To use Plonkish/KZG-PlonK:

ach run my_program.ach --prove-backend plonkish
Navigation