Skip to content
Oumuamua Labs
Hekate ZK Engine menu

Documentation

Hekate documentation: install the Rust zero-knowledge proof engine, write a first program, then learn its tables, chiplets, LogUp buses and prover.

Updated docs.md
On this page

Hekate proves computations in GF(2^128) using Sumcheck + Brakedown PCS with O(N) prover time and O(N) memory. 100 proven bits of soundness.

§ The Hekate Ecosystem

Hekate is not one crate. The prover is the engine; the math core, hardware chiplets, mobile toolchain, and fuzzer ship separately, to compose as needed.

CrateRole
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- and 64-bit integer arithmetic, RAM, ROM and the bignum modexp the RSA chiplet builds on.
hekate-keccakKeccak-f[1600] chiplet plus SHA-3 / SHAKE, with virtual packing.
hekate-aesAES-128 / AES-256 round-function chiplet (FIPS 197) with an S-box ROM.
hekate-sha2SHA-256 round chiplet (FIPS 180-4). Bit-expanded B32 columns, degree 2, 1, 2, 4, 8 or 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.

§ What It Does

Binary tower field arithmetic, GF(2^8) through GF(2^128), recursive tower extension, hardware-accelerated via PMULL on aarch64. Constant-time by default.

Chiplet architecture, Independent AIR tables (Keccak, AES, RAM, NTT, ML-KEM, ML-DSA) with own traces under one commitment. No column waste, no forced padding. Tables linked by LogUp bus.

Virtual packing, Keccak stores 1600 bits in 25 physical B64 columns instead of 1600 bit columns. Bits expand JIT in registers.

Linear-code commitments, Brakedown PCS: O(N) prover, O(N) memory. MDS Reed-Solomon row code via additive binary-field FFT, exact distance δ = 1 − rate. Merkle tree over encoded columns only (raw trace never hashed, true ZK).

Post-quantum crypto suite, ML-DSA (Dilithium) signature verification, ML-KEM (Kyber) key generation, encapsulation and decapsulation, AES-128/256. AES and Keccak are native to binary fields; lattice arithmetic mod q runs on bit-decomposed carry chains.

§ Hardware Support

ArchitectureStatusInstructions
aarch64ProductionPMULL, NEON
x86_64FallbackSoftware fallback (PCLMULQDQ roadmap)
WASMPlannedSoftware multiply

§ Quick Example

Real 32-bit-integer Fibonacci. The CPU side holds five columns, the two Fibonacci transition constraints and an opcode pin. Every u32 ADD is offloaded to the IntArithmeticChiplet, its own trace and own ZeroCheck under the shared commitment and evaluation argument, and is wired in by a LogUp bus ((val_a, val_b, val_res, opcode) keys; the t-th request meets the t-th response by emit rank). The result reaches the verdict through publish: a boundary pin to the public input on a row whose schedule a fixed column forces, with the transition chain determined from the pinned origins.

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, Col};
use hekate_program::define_columns;

type F = Block128;

define_columns! {
    ProgColumns {
        VAL_A: B32,
        VAL_B: B32,
        VAL_RES: B32,
        OPCODE: B32,
        SELECTOR: Bit,
    }
}

fn build_program(num_rows: usize) -> errors::Result<CircuitProgram<F>> {
    let mut cx = Circuit::<F>::new("Fibonacci", num_rows)?;

    let cpu = cx.schema(&ProgColumns::build_layout());
    let selector = cpu.at(ProgColumns::SELECTOR);
    let a = cpu.at(ProgColumns::VAL_A);
    let b = cpu.at(ProgColumns::VAL_B);
    let res = cpu.at(ProgColumns::VAL_RES);
    let opcode = cpu.at(ProgColumns::OPCODE);

    let cs = cx.cs();
    let col = |c: Col| cs.col(c.index());
    let next = |c: Col| cs.next(c.index());

    cs.constrain(col(selector) * (next(a) + col(b))); // next(a) = b
    cs.constrain(col(selector) * (next(b) + col(res))); // next(b) = res = a + b
    cs.constrain(col(opcode)); // opcode = ADD

    cx.call(
        &IntArithmeticChiplet::service(),
        &[a, b, res, opcode],
        selector,
    )?;

    cx.attach(ChipletDef::from_air(&IntArithmeticChiplet::new(
        32,
        num_rows,
        num_rows - 1,
    )?)?);

    cx.fix(
        selector,
        FixedShape::Cadence {
            stride: 1,
            count: num_rows - 1,
            origin: 0,
            values: vec![F::ONE],
        },
    );

    cx.boundary(a, 0, F::ZERO);
    cx.boundary(b, 0, F::ONE);

    cx.publish(b, num_rows - 1);

    cx.compile()
}

Trace generation builds the CPU columns and the chiplet trace independently; they meet on the bus.

rust
use hekate_core::trace::{ColumnTrace, TraceBuilder};
use hekate_gadgets::{
    ArithmeticOpcode, IntArithmeticLayout, IntArithmeticOp, generate_arithmetic_trace,
};
use hekate_math::{Bit, Block32};

fn generate_traces(num_rows: usize) -> errors::Result<(ColumnTrace, ColumnTrace, u32)> {
    let num_vars = num_rows.trailing_zeros() as usize;

    let mut tb = TraceBuilder::new(&ProgColumns::build_layout(), num_vars)?;
    let mut ops: Vec<IntArithmeticOp> = Vec::with_capacity(num_rows - 1);

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

    for i in 0..num_rows - 1 {
        let res = a.wrapping_add(b);

        tb.set_b32(ProgColumns::VAL_A, i, Block32::from(a))?;
        tb.set_b32(ProgColumns::VAL_B, i, Block32::from(b))?;
        tb.set_b32(ProgColumns::VAL_RES, i, Block32::from(res))?;
        tb.set_b32(ProgColumns::OPCODE, i, Block32::from(ArithmeticOpcode::ADD as u32))?;
        tb.set_bit(ProgColumns::SELECTOR, i, Bit::ONE)?;

        ops.push(IntArithmeticOp::U32 {
            op: ArithmeticOpcode::ADD,
            a,
            b,
        });

        a = b;
        b = res;
    }

    // Padding row:
    // selector = 0, val_b carries fib[N-1] for the boundary pin.
    tb.set_b32(ProgColumns::VAL_A, num_rows - 1, Block32::from(a))?;
    tb.set_b32(ProgColumns::VAL_B, num_rows - 1, Block32::from(b))?;

    let cpu_trace = tb.build();

    let arith_layout = IntArithmeticLayout::compute(32);
    let arith_trace = generate_arithmetic_trace(&ops, &arith_layout, num_rows)?;

    Ok((cpu_trace, arith_trace, b))
}

The chiplet computes the operation the opcode names, boolean-checks its own selectors and zero-pins shadow columns when its row is idle. With opcode pinned to ADD, the LogUp bus guarantees res = a + b for every row where selector = 1.

Wire up the program, instance, and witness, then prove with hekate-prover-sys and verify with hekate-verifier. The transcript label and Config must match across both sides, the driver builds one config and reuses it. verify returns true only if every Sumcheck round, the LogUp bus sums, and the evaluation openings hold.

rust
use hekate_core::config::Config;
use hekate_crypto::DefaultHasher;
use hekate_crypto::transcript::Transcript;
use hekate_program::{ProgramInstance, ProgramWitness};
use hekate_prover_sys::prove;
use hekate_verifier::HekateVerifier;
use rand::{TryRng, rngs::SysRng};

fn run(num_rows: usize, audited_id: &[u8; 32]) -> Result<bool, Box<dyn core::error::Error>> {
    let (cpu, arith, fib_n) = generate_traces(num_rows)?;

    let program = build_program(num_rows)?;
    let instance = ProgramInstance::new(num_rows, vec![F::from(fib_n as u128)]);
    let witness = ProgramWitness::new(cpu).with_chiplets(vec![arith]);

    let config = Config::default();

    let mut blinding_seed = [0u8; 32];
    SysRng.try_fill_bytes(&mut blinding_seed)?;

    let proof = prove(
        b"Fibonacci",
        &program,
        &instance,
        &witness,
        &config,
        blinding_seed,
        None,
    )?;

    let mut transcript = Transcript::<DefaultHasher>::new(b"Fibonacci");

    // `audited_id` is a constant of the verifying build,
    // printed once by `digest::program_id_hex`.
    Ok(HekateVerifier::<F, DefaultHasher>::verify(
        audited_id,
        &program,
        &instance,
        &proof,
        &mut transcript,
        &config,
    )?)
}

§ Performance

Proving time, verification time, proof size and peak memory per workload, with zero knowledge on and off, are on Benchmarks together with the command that reproduces each run.

§ Next Steps

§ Status

The verifier, core SDK, and cryptographic chiplets are open-source under AGPL-3.0-only. The prover and compression engine stay proprietary, shipped as free signed binaries for macOS, Linux, iOS and Android on ARM64. Linking the two is covered by the prover linking exception.

The workspace is under aggressive development: APIs, ABIs, and cryptographic signatures will break without notice, and it has not been independently audited. Do not deploy to mainnet.

§ Contact

info@oumuamua.dev