Skip to content
Oumuamua Labs

Proving Stack · 01 Prove and verify

Hekate ZK Engine

Generate zero-knowledge proofs on the phone that holds the private data.

Write zero-knowledge programs in Rust and prove them in linear time and bounded memory over binary fields, with no trusted setup.

Workload
338 ms
to prove one ML-DSA-65 signature verification
283 ms
to prove ML-KEM-768 encapsulation with an AES-256-CTR payload
205 ms
to prove one RSA-2048 PKCS#1 v1.5 signature verification
910 ms
to prove AES-128 over 31,250 blocks, about 500 KB
52 ms
to prove SHA-256 over 40 blocks
3.20 s
to prove 41,943 Keccak-f[1600] permutations, about 5.7 MB
202 MiB
159 MiB
458 MiB
768 MiB
65 MiB
2,430 MiB
peak memory for that proof
O(N)prover time and memory
100 bitsof proven soundness

Apple M3 Max, public prover build with table-math, zero knowledge on, best of three runs. All benchmarks

Proving without the memory wall

Proving one ML-DSA-65 signature verification peaks at 202 MiB. Hekate streams through the trace, folds in place and discards each segment once it is proved, and it encodes one table's codewords at a time.

01

Binary tower fields

AES and Keccak are native to the field, and arithmetic is constant-time by default. The tower runs from GF(2) to GF(2^128), with PMULL on aarch64.

02

Linear-code commitments

The commitment is hash-based and needs no trusted setup: Brakedown with a Reed-Solomon row code over an additive binary-field FFT. The Merkle tree covers encoded columns only, and the raw trace is never hashed.

03

Chiplets on a LogUp bus

Your program carries columns and rows only for the chiplets it attaches. Keccak, AES, SHA-256, RAM, NTT, ML-KEM and ML-DSA run as independent AIR tables, each with its own trace and height, under one commitment and linked by a LogUp bus.

04

Virtual packing

Keccak commits its 1,600 state bits as 25 physical 64-bit columns, where a layout with one column per bit would need 1,600. The bits expand just in time in registers.

Standard cryptography, ready to prove

Attach a chiplet to your program and prove the operation without revealing the key, the signature or the plaintext. Each chiplet is built to its NIST or IETF specification.

Also included in hekate-gadgets: 32- and 64-bit integer arithmetic, RAM and ROM chiplets.All benchmarks

Define, trace, prove, verify

Four steps from a Rust program to a verified proof, shown on the Fibonacci example that ships with Hekate. You never touch Sumcheck or Brakedown directly.

rust
use hekate_core::errors;
use hekate_gadgets::IntArithmeticChiplet;
use hekate_math::{Block128, TowerField};
use hekate_program::FixedShape;
use hekate_program::chiplet::ChipletDef;
use hekate_program::circuit::{Circuit, CircuitProgram};

type F = Block128;

fn build_program(num_rows: usize) -> errors::Result<CircuitProgram<F>> {
    let chiplet = IntArithmeticChiplet::new(32, num_rows, num_rows - 1)?;
    let layout = chiplet.layout().clone();

    // The arithmetic chiplet's columns
    // and AIR are the program's trace.
    let mut cx = Circuit::<F>::new("FibonacciInt", num_rows)?;
    let arith = cx.mount_unlinked(ChipletDef::from_air(&chiplet)?);

    let cs = cx.cs();

    let s_add = cs.col(layout.s_add);
    let val_b = cs.col(layout.val_b);
    let val_res = cs.col(layout.val_res);
    let next_val_a = cs.next(layout.val_a);
    let next_val_b = cs.next(layout.val_b);

    // The two Fibonacci transitions,
    // on top of the chiplet's AIR.
    cs.constrain(s_add * (next_val_a + val_b));
    cs.constrain(s_add * (next_val_b + val_res));

    cs.assert_zero_when(cs.one() + s_add, val_res);

    // Boundaries pin fib(0) and fib(1)
    cx.boundary(arith.col(layout.val_a), 0, F::ZERO);
    cx.boundary(arith.col(layout.val_b), 0, F::ONE);

    cx.fix(arith.col(layout.s_add), FixedShape::LastRow);

    // Publish exposes the last value
    cx.publish(arith.col(layout.val_b), num_rows - 1);

    cx.compile()
}
use hekate_core::errors;
use hekate_core::trace::{ColumnTrace, TraceColumn};
use hekate_gadgets::{
    ArithmeticOpcode, IntArithmeticLayout, IntArithmeticOp, generate_arithmetic_trace,
};
use hekate_math::{Block32, HardwareField};

const PHY_VAL_A: usize = 0;
const PHY_VAL_B: usize = 1;

fn generate_trace(num_rows: usize) -> errors::Result<(ColumnTrace, u32)> {
    let layout = IntArithmeticLayout::compute(32);

    let mut a: u32 = 0;
    let mut b: u32 = 1;
    let mut prev_b: u32 = 0;

    // N - 1 ADD ops become the chiplet trace
    let mut ops: Vec<IntArithmeticOp> = Vec::with_capacity(num_rows - 1);

    for _ in 0..num_rows - 1 {
        let sum = a.wrapping_add(b);
        ops.push(IntArithmeticOp::U32 {
            op: ArithmeticOpcode::ADD,
            a,
            b,
        });

        prev_b = b;
        a = b;
        b = sum;
    }

    let final_b = b;
    let final_a = prev_b;

    let mut trace = generate_arithmetic_trace(&ops, &layout, num_rows)?;

    // Patch the padding row: the transition
    // from row N - 2 to N - 1 stays valid.
    if let TraceColumn::B32(col) = &mut trace.columns[PHY_VAL_A] {
        col[num_rows - 1] = Block32::from(final_a).to_hardware();
    }

    if let TraceColumn::B32(col) = &mut trace.columns[PHY_VAL_B] {
        col[num_rows - 1] = Block32::from(final_b).to_hardware();
    }

    Ok((trace, final_b))
}
use hekate_core::config::Config;
use hekate_core::proofs::InnerProof;
use hekate_program::{ProgramInstance, ProgramWitness};
use hekate_prover_sys::prove;
use rand::{TryRng, rngs::SysRng};
use std::error::Error;

fn prove_fibonacci(
    num_rows: usize,
) -> Result<(ProgramInstance<F>, InnerProof<F>), Box<dyn Error>> {
    let (trace, final_b) = generate_trace(num_rows)?;

    // The instance is public;
    // the witness stays on the device.
    let instance = ProgramInstance::new(num_rows, vec![F::from(final_b as u128)]);
    let witness = ProgramWitness::new(trace);
    let program = build_program(num_rows)?;

    // A fresh blinding seed makes the proof zero-knowledge
    let mut seed = [0; 32];
    SysRng.try_fill_bytes(&mut seed)?;

    let proof = prove(
        b"FibonacciIntChiplet",
        &program,
        &instance,
        &witness,
        &Config::prod(),
        seed,
        None,
    )?;

    Ok((instance, proof))
}
use hekate_core::config::Config;
use hekate_core::proofs::InnerProof;
use hekate_crypto::DefaultHasher;
use hekate_crypto::transcript::Transcript;
use hekate_program::ProgramInstance;
use hekate_program::circuit::CircuitProgram;
use hekate_verifier::HekateVerifier;

fn verify_fibonacci(
    pinned_id: &[u8; 32],
    program: &CircuitProgram<F>,
    instance: &ProgramInstance<F>,
    proof: &InnerProof<F>,
) -> bool {
    // Same transcript label and Config as the prover
    let mut transcript = Transcript::<DefaultHasher>::new(b"FibonacciIntChiplet");

    // A proof for another circuit fails
    // against the pinned program id.
    let verdict = HekateVerifier::<F, DefaultHasher>::verify(
        pinned_id,
        program,
        instance,
        proof,
        &mut transcript,
        &Config::prod(),
    );

    matches!(verdict, Ok(true))
}
$ just example fibonacci fibonacci.rs on GitHub

Open where it matters, free to run

Hekate's open code is AGPL-3.0-only; its signed prover binary is free. A product shipped under other terms, open source or closed, needs a commercial license.

Open source · AGPL-3.0-only

Verifier, SDK, chiplets

The verifier, the core SDK and every cryptographic chiplet. Read and audit the code that decides whether a proof is valid.

Free binary

Signed prover

The prover ships as a signed shared library for macOS, Linux, iOS and Android on ARM64, with SHA-256 hashes and Ed25519 and ML-DSA-65 signatures. The AGPL linking exception covers combining it with open code.

Commercial license

Pro and Enterprise

Pro adds the 128-bit build and a reply within two days; Enterprise adds pluggable ZeroCheck kernels, customization and a 24/7 support line.

Hardware support
  • aarch64Production · PMULL, NEON
  • x86_64Software fallback
  • WASMPlanned
Crates
  • hekate-mathBinary tower field arithmetic, constant-time, PMULL on aarch64. The mathematical core.
  • hekate-cryptoHash functions, Merkle trees and the Fiat-Shamir transcript. BLAKE3 by default, SHA-256 or SHA3-256 by feature.
  • hekate-coreConfiguration, execution traces and proof types: Config, TraceBuilder and InnerProof.
  • hekate-programThe circuit DSL: columns, constraints, fixed columns, public inputs, chiplets, LogUp buses and program ids.
  • hekate-sdkPreflight diagnostics and the proof wire format. Preflight names what fails before you spend a proof.
  • hekate-verifierThe verifier, in pure Rust. Checks a program's constraints, pinned cells and bus relations over the committed tables.
  • hekate-prover-sysOpen FFI shim. Links the signed prover cdylib over a stable C ABI; the only crate that can call the prover.
  • hekate-gadgetsBase AIR chiplets and atoms: 32-bit integer arithmetic, RAM, ROM and the bignum modexp the RSA chiplet builds on.
  • hekate-keccakKeccak-f[1600] chiplet plus SHA-3 / SHAKE. Virtual packing, ~16× memory savings.
  • hekate-aesAES-128 / AES-256 round-function chiplet (FIPS 197) with an S-box ROM.
  • hekate-sha2SHA-256 compression chiplet (FIPS 180-4). Bit-expanded B32 columns, degree 2, 1-16 rounds per row.
  • hekate-rsaRSA-2048 PKCS#1 v1.5 signature statements (RFC 8017) over the modexp and SHA-256 chiplets.
  • hekate-pqcML-KEM KeyGen, Encaps and Decaps and ML-DSA verification (FIPS 203 / 204) over NTT, Sampler, Codec tables.
  • hekate-mobileWraps a Rust prover into a signed iOS .xcframework / Android .aar with a typed Swift / Kotlin API.
  • hekate-scribbleTrace-mutation fuzzer. Tampers a valid trace, panics if your constraints miss the tamper.

Next in the Proving Stack

Under active development, not yet audited

APIs, ABIs and cryptographic signatures still change without notice, and the workspace has not been independently audited. Build and experiment with it today; do not deploy it to mainnet yet.

Soundness and Security

Questions

What is Hekate?

Hekate is a zero-knowledge proof system written in Rust. It proves computations over binary tower fields in GF(2^128) with Sumcheck and Brakedown commitments, in linear time and bounded memory: proofs can be generated on phones and edge devices instead of servers.

Does Hekate need a trusted setup?

No. Brakedown commitments are built from a linear code and a Merkle tree: there is no setup ceremony and no secret to destroy.

Which platforms can run the prover?

Signed prover builds ship for macOS, Linux, iOS and Android on ARM64. aarch64 is the production target, accelerated with PMULL and NEON; x86_64 runs a software fallback and WASM is planned. Mobile apps get the prover through the ZK Toolkit for iOS & Android.

Is Hekate open source?

The verifier, the core SDK and the cryptographic chiplets are open source under AGPL-3.0-only. The prover is a free proprietary binary, and a linking exception under AGPL section 7 lets open code call it.

Do I need a commercial license?

Only if your product ships under terms other than AGPL-3.0. An open-source protocol under another license needs one, and a closed-source product does too. A product under AGPL-3.0 uses Hekate free. The Pricing page compares the tiers.

Has Hekate been audited?

Not yet. The workspace has not been independently audited and its APIs still change: treat it as pre-production software.

How large are Hekate proofs?

A few megabytes for typical workloads: about 1.3 MiB for one ML-DSA-65 signature verification with zero knowledge on.

Can I aggregate many proofs into one?

Yes, with Charon ZK Engine. It wraps each Hekate proof into a smaller form and accumulates any number of wrapped proofs into one result that keeps the same size, checked once. Charon will be provided as SaaS.