A general-purpose VOLE-in-the-Head (VOLEitH) zero-knowledge proof library in C.
libtalos_voleith lets you prove knowledge of a private witness satisfying any Boolean or byte-oriented circuit, producing non-interactive proofs that any party can verify with the public inputs alone. Security rests entirely on symmetric-key primitives (AES, SHAKE-128/256), giving post-quantum security with no elliptic curve assumptions.
The underlying protocol is extracted and generalized from the FAEST v2.0 specification (NIST PQC Round 2 additional signatures submission). FAEST uses VOLEitH to prove knowledge of an AES key; this library exposes the same machinery for arbitrary circuits.
A VOLE-in-the-Head proof lets a prover demonstrate that they know a private
witness w such that C(w, x) = 0 for some public circuit C and public
instance x, without revealing anything about w beyond that fact. The
protocol works by having the prover simulate a VOLE (Vector Oblivious Linear
Evaluation) correlation "in their head", commit to it via a GGM tree, and then
run the QuickSilver line-point zero-knowledge protocol over the committed
correlation. The Fiat-Shamir transform (instantiated with SHAKE) compresses
the resulting interactive protocol into a single non-interactive proof blob.
The result is:
- Non-interactive: no back-and-forth with the verifier.
- Publicly verifiable: anyone with the circuit and public instance can verify.
- Post-quantum: security relies only on AES and SHAKE, not on number-theoretic assumptions that fall to Shor's algorithm.
- General-purpose: any circuit expressible in XOR/AND (bit-level) or XOR/affine/square/multiply (GF(2^8)) can be proved. Unlike SNARK systems, no trusted setup is required.
The trade-off vs. SNARKs is proof size: VOLEitH proofs are kilobytes (5-17 KB depending on circuit and security level), not hundreds of bytes. The benefits are no trusted setup, post-quantum security, and prover times measured in milliseconds.
| Standard | Used for |
|---|---|
| FAEST v2.0 (NIST PQC Round 2 additional signatures) | VOLEitH protocol, QuickSilver proof system, GGM vector commitment, ConvertToVOLE, parameter sets |
| FIPS 197 (AES) | AES-128 and AES-256 encryption - both as standard-eval primitive (for the PRG) and as Boolean / GF(2^8) circuit |
| FIPS 202 (SHA-3 / SHAKE) | SHAKE-128 and SHAKE-256 for Fiat-Shamir transform, commitment hashing, and challenge derivation |
| NIST SP 800-38A | AES-ECB validation vectors |
| NIST SP 800-108r1 | KDF in Counter Mode (KDF-CTR) using AES-CMAC as PRF |
| RFC 4493 | AES-CMAC subkey derivation, padding, and CBC-MAC chaining |
| Grøstl (SHA-3 finalist) | Grøstl-256 / Grøstl-512 hash, as standard-eval primitive and as a wide-node Merkle hash circuit |
| Hirose double-block-length (FSE 2006) | AES-256-keyed Hirose iteration, as a 32-byte / 2¹²⁸-CR Merkle node hash |
| RFC 6962 | Leaf / internal-node domain-separation prefix for the Grøstl Merkle circuit |
All protocol code is a clean-room implementation from the FAEST v2.0
specification. No source from the FAEST reference implementation (faest-ref)
or any other VOLEitH library was copied; faest-ref is used only as a test
oracle for known-answer cross-validation.
- Post-quantum secure. No discrete log, no pairing, no elliptic curves. Security reduces to the difficulty of breaking AES and SHAKE.
- Non-interactive. Proofs are produced without any interaction with the verifier. The Fiat-Shamir transform (instantiated with SHAKE) converts the interactive protocol into a standalone proof blob.
- Publicly verifiable. Anyone with the circuit description and public instance values can verify a proof.
- Zero-knowledge. The proof reveals nothing about the witness beyond the fact that a satisfying assignment exists.
- Clean-room implementation. All protocol code is derived from the FAEST v2.0 specification. No faest-ref code was copied.
The library offers two views of the same underlying protocol, suited to different circuit styles.
- Bit-level GF(2) QuickSilver (
include/voleith.h). Each wire carries one bit; gates are XOR / AND / NOT. XOR and NOT are free (linear in the VOLE correlation); AND gates determine proof cost. Natural representation for circuits that are inherently bitwise (bitfield manipulation, comparison logic, custom Boolean functions). Seeexamples/example_aes.cfor a minimal usage example. - Element-level GF(2⁸) QuickSilver (
include/voleith_gf8.h). Each wire carries one byte; gates are XOR / affine linear map / squaring (all free) and GF(2⁸) multiply (one VOLE slot). Byte-oriented computations (AES, CMAC, KDF, Merkle hashing) are about 8× more compact here than in the bit-level variant because witness and multiplication-gate counts shrink by a factor of 8.
Use the GF(2⁸) variant for any circuit built from AES, CMAC, KDF, or Merkle hashing. The bit-level variant is appropriate when the computation is inherently bitwise and does not compose with the byte-oriented building blocks.
A minimal GF(2⁸) usage sketch:
#include "voleith_gf8.h"
voleith_gf8_circuit_t *c = voleith_gf8_circuit_new();
gf8_wire_id key_byte = voleith_gf8_add_witness(c); // private
gf8_wire_id pub_byte = voleith_gf8_add_instance(c); // public
gf8_wire_id xored = voleith_gf8_add_xor(c, key_byte, pub_byte); // free
gf8_wire_id prod = voleith_gf8_add_mul(c, key_byte, pub_byte); // 1 VOLE slot
voleith_gf8_assert_zero(c, xored);
voleith_proof_t proof;
voleith_gf8_prove(&proof, &voleith_params_em_128f,
c, witness_bytes, instance_bytes, fs_seed, fs_seed_len);
voleith_gf8_verify(&proof, &voleith_params_em_128f,
c, instance_bytes, fs_seed, fs_seed_len);For the parallel bit-level form (voleith_circuit_*,
voleith_prove / voleith_verify) and runnable side-by-side examples
exercising both variants over the same statement (AES-128 key knowledge),
see examples/example_aes.c and examples/example_aes_gf8.c.
| Gate | Bit-level cost | GF(2^8) cost |
|---|---|---|
| XOR / NOT | 0 AND gates | 0 mul gates |
| Affine linear map (MixColumns, basis change, etc.) | -- | 0 mul gates |
| Squaring in GF(2^8) | -- | 0 mul gates |
| AND | 1 AND gate | -- |
| GF(2^8) multiply | -- | 1 mul gate |
Only AND gates (bit-level) or GF(2^8) multiply gates (element-level) contribute to proof size and prover computation. All other operations are absorbed into the VOLE linear homomorphism at no cost.
Both variants ship ready-to-compose sub-circuits for the common building blocks. Each appends gates to a caller-supplied circuit and returns wire IDs for further composition.
| Building block | Functions | Notes |
|---|---|---|
| AES-128 / AES-256 encryption | aes128_circuit / aes256_circuit (bit-level), aes128_gf8_circuit / aes256_gf8_circuit (GF(2⁸)) |
Canright (2005) tower-field S-box. |
| AES-CMAC (RFC 4493) | aes_cmac_circuit / aes_cmac_gf8_circuit |
128- or 256-bit key. |
| KDF-CTR (NIST SP 800-108r1 §4.1) | kdf_ctr_cmac_circuit / kdf_ctr_cmac_gf8_circuit |
AES-CMAC as PRF; 32-bit BE counter wired as circuit constants. |
| Merkle path (Davies-Meyer / CMAC nodes, 16-byte, 2⁶⁴ CR) | merkle_circuit / merkle_gf8_circuit |
Public-dir and secret-dir variants. |
| Merkle path (Grøstl wide nodes) | merkle_grostl_gf8_circuit |
Four node variants (GROSTL_{256, 256_T27, 512, 512_T59}) covering 2¹⁰⁸ to 2²⁵⁶ CR. Public-dir and secret-dir. |
| Merkle path (any hash, hash-agnostic) | merkle_vt_gf8_path_circuit (+ _secret_dir) |
Generic body parameterised by voleith_node_hash_vt; ships with vts for AES-DM, AES-128-CMAC, the four Grøstl variants, the two fixed-input Grøstl variants (grostl256_fixed / grostl512_fixed: full 2¹²⁸ / 2²⁵⁶ CR at single-compression cost), and Hirose-AES-256. |
| Indexed Merkle non-membership | indexed_merkle_circuit / indexed_merkle_gf8_nonmember_circuit / indexed_merkle_grostl_gf8_nonmember_circuit |
DM/CMAC or Grøstl-node; public-dir and secret-dir. |
| Indexed Merkle non-membership (any hash, hash-agnostic) | merkle_vt_gf8_indexed_nonmember_circuit (+ _secret_dir) |
Same vt coverage as the generic Merkle path. |
| Ring signatures (RSv1) | voleith_rsv1_sign / _verify, voleith_rs_membership_build_circuit, voleith_ring_sig_pack / _unpack |
Anonymous-member signature over a published ring with optional revocation. Parameterised over any voleith_node_hash_vt; composes the OWF leaf hash, the secret-dir Merkle path, and the secret-dir indexed-Merkle non-member branch into one circuit. |
| Ring signatures (composable V2/V3/V4) | voleith_rs_sign / _verify, voleith_rs_build_circuit, voleith_rs_sig_pack / _unpack |
Superset of RSv1 with independently-enableable modules: V2 linkable nullifier T = AES-CMAC(sk, scope) (+ optional in-circuit spent-set) , V3 hidden-attribute predicates (EQ / RANGE over OWF(sk || attributes)), V4 claimable commitment C = H(id || rand). One composed Fiat-Shamir transcript with a module-bitmap domain tag; "VRSC" wire format. See docs/RING_SIGNATURES_DESIGN.md. |
| Ring signatures (forward-secure V6) | voleith_rs_epoch_keygen / _sign, voleith_rs_epoch_state_advance, voleith_rs_epoch_derive_sk |
Composable module (bit 5) adding per-identity epoch key evolution: a key captured at epoch t cannot sign for any earlier epoch. Epoch tree walked in-circuit with public directions (bits of t) via the free scale-by-instance gate; GGM key schedule with erasure lives out of circuit. Versioned forward-secure state. See docs/RING_SIGNATURES_DESIGN.md. |
| Ring signatures (designated opener V5) | voleith_rs_opener_seal / voleith_rs_opener_verify (+ the enable_opener config path) |
Composable module (bit 6) adding post-quantum code-based traceability: a designated opener holding a QC-MDPC key recovers the signer's identity from a per-signature tag prim_id || s || tag_ct, while every other verifier learns only that some member signed. Syndrome-based (no trapdoor); split-custody deployment. Opens QuickSilver at degree 16-18 via the sparse syndrome + less-than gadgets. See docs/RING_SIGNATURES_DESIGN.md. |
| Bounded-range assertion | assert_in_range_gf8 |
Constrains low <= value <= high (inclusive) over little-endian byte-vector wires; builds on the indexed-Merkle comparison routine. |
For each building block, see docs/CIRCUIT_DESIGN.md:
concrete AND-gate / mul-slot cost formulas, the public-dir-vs-secret-dir
choice, the Grøstl _T27 / _T59 truncation rationale, the Hirose-AES-256
construction, the voleith_node_hash_vt interface, the indexed-Merkle
non-membership trust assumption (and the record-array validator that
catches the common operational foot-guns), and worked gate-count examples.
The ring-signature protocols (RSv1, the composable V2/V3/V4 superset, the
forward-secure V6 module, and the designated-opener V5 module)
and their Fiat-Shamir message-binding construction are in
docs/RING_SIGNATURES_DESIGN.md; the proof
system and layered architecture are in docs/DESIGN.md.
Each building block has at least one runnable example in examples/ (see
the Examples table below).
Besides circuits built programmatically through the API, the library can
parse circuits in Bristol Fashion, the boolean-circuit file format used
as the standard comparison baseline across the MPC/ZK ecosystem (AES, DES,
SHA-256, adders, comparators, multipliers, etc.). parsers/bristol.h
exposes voleith_bristol_parse_file / voleith_bristol_parse_buffer, which
read a Bristol file and build a bit-level voleith_circuit_t that feeds
directly into voleith_prove / voleith_verify.
Because Bristol has no witness-vs-instance distinction, the caller supplies a
per-input-value role array (WITNESS or INSTANCE); outputs are returned as
bare wire IDs for the caller to constrain. Supported gates are XOR, AND,
INV, EQ, and EQW; MAND and the older pre-Fashion format are detected and
rejected. A small corpus of canonical circuits (AES-128, AES-256, 64-bit
negate, 64×64 multiply) is vendored under tests/data/bristol/ for
cross-validation, and examples/example_bristol_aes128.c proves AES-128 key
knowledge from the parsed Bristol circuit.
See docs/DESIGN.md → "Bristol Fashion Circuit Parser"
for the format, the role-assignment and ownership model, the single-pass
parse algorithm and its validation invariants, the full error-code list, and
the test corpus.
For GF(2⁸) element-level circuits the library also reads Shipshape
(.ship), a native text format designed for this proof system. Unlike
Bristol, a Shipshape file is self-describing: it declares its own WITNESS /
INSTANCE / CONST wires, offers the full Tier 1 gate set with sugar
(SUM, FROBENIUS_K, MUX, INV, ASSERT_*), supports user/*
subcircuit definitions and inlining, and calls a frozen Tier 2a
stdlib/crypto/* registry of cryptographic primitives (AES-128/256, AES-CMAC,
Grøstl-256/512) that lower to the hand-written C builders byte-for-byte.
parsers/shipshape.h exposes voleith_shipshape_parse_file /
voleith_shipshape_parse_buffer, which build a voleith_gf8_circuit_t.
parsers/shipshape_witness.h then generates the full witness from just the
external input (the generic Tier 1 evaluator completes gadget-internal
witnesses such as the INV inverses), and the result feeds
voleith_gf8_prove_v2 / voleith_gf8_verify_v2. Circuit identity is the
16-byte voleith_gf8_circuit_fingerprint: any two conformant parsers lower a
file to a byte-identical circuit and the same fingerprint, so a proof binds
to its .ship source through the metadata header with no extra machinery.
A worked corpus lives under tests/data/shipshape/ (AES and CMAC key
knowledge, public- and secret-direction Merkle paths), and
examples/example_shipshape_parse_prove.c runs the full parse to witness to
prove to verify pipeline on one of them.
The format version is semver MAJOR.MINOR: a new Tier 1 opcode that leaves
existing files valid and fingerprint-identical is an additive MINOR bump. The
scale-by-instance gate ships this way as SCALE_INSTANCE, the first
.shipshape 1.1 opcode (a file must declare 1.1 to use it); existing
.shipshape 1 files are unaffected. This is independent of the stdlib
(crypto-vN) axis.
The additive stdlib crypto-v2 registry extends crypto-v1 with three
hash-parametric crypto extensions (secret-direction Merkle path, indexed-Merkle
non-membership, and ring-signature membership), each selectable by node-hash
type via a bracket selector (path_secret[H], nonmember_secret[H],
ring_sig/v1[H]). See docs/specs/SHIPSHAPE_SPEC.md §7.7
for the format and docs/DESIGN.md for the rationale.
Three runnable .ship examples exercise these constructions over the 128-bit
hirose_fixed_32 node hash: example_shipshape_anon_membership (anonymous
group membership), example_shipshape_kvac (a membership plus indexed-Merkle
revocation credential lifecycle), and example_shipshape_ring_sig (a ring
signature with Fiat-Shamir message binding). A registered witness backend can
fill each construction's witness natively: an opt-in, fail-closed prover-side
speed-up over the generic evaluator (see docs/DESIGN.md).
See docs/specs/SHIPSHAPE_SPEC.md for the
format and witness layout (§2.4), and docs/DESIGN.md for
the design rationale.
Beyond the proof system, the library ships a plaintext erasure-coding layer and
the zero-knowledge statements built on top of it, for verifiable distributed
storage and network coding. These live in erasure/ (a sibling to vole/,
depending only on core/) and do no I/O: the transport and the ledger / tracker
are the consuming application's job.
| Capability | Functions | Notes |
|---|---|---|
| Reed-Solomon (storage) | voleith_rs_encode / _decode / _repair, voleith_rs_encode_indices |
Systematic (n, k) over GF(2⁸); any k of n chunks rebuild the blob (MDS). Cauchy or Vandermonde generator. encode_indices is the decode-once / encode-many healer primitive. |
| RLNC (transport) | voleith_rlnc_* encode / recode / decode |
Random linear network coding over a new GF(2¹⁶) field (core/field16.h); coded symbols carry a coefficient vector and generation id; decode at rank k with rank-progress reporting. |
| RLNC membership (in circuit) | rlnc_gf16_circuit |
Proves a coded symbol belongs to a committed generation (y = c · X) on the native GF(2¹⁶) prover. Public-coefficient and both-secret (data-blind) orientations. |
| RLNC rank certificate (in circuit) | rlnc_gf16_cert_circuit |
Knowledge-of-inverse full-rank statement (C · C⁻¹ = I): proves a hidden coefficient matrix is invertible without revealing it. Standalone; it does not bind C to any particular packet set. |
| Confidential RLNC (paper 2) | voleith_confrlnc_*, rlnc_confidential_gf16_circuit |
Secret-coefficient + secret-partial-permutation codec (Brahimi-Merazka) with a ZK encoding-correctness statement (AS-Waksman permutation gadget, voleith_perm_gf16_circuit). Weak/computational security; not a substitute for AEAD. |
| Chunk membership certificate | voleith_rs_chunk_cert_prove / _verify (+ _secret_dir) |
A non-interactive proof that a chunk is a genuine member of a dataset under its root R, via an FWK-blinded chunk Merkle tree (the FWK is never revealed). Public-index and secret-index variants. |
| Dataset binding and wire format | voleith_rs_compute_R / _verify_R, voleith_rs_metadata_serialize / _parse, voleith_rs_descriptor_serialize / _parse, voleith_rs_chunk_header_serialize / _parse |
R = H(merkle_root ‖ H(serialize(metadata))) binds the tree and dataset parameters together; the descriptor and per-chunk header are the canonical on-the-wire envelopes (design §6.10). |
| Retriever and consistency helpers | voleith_rs_retriever_*, voleith_rs_check_consistency, voleith_rs_recover_index |
Local decision primitives: verify, dedup by recovered index, "do I have k distinct yet", decode, and the plaintext re-encode-and-compare consistency check. |
The native GF(2¹⁶) proving stack (proof/gf16_prover.c / gf16_verifier.c /
gf16_circuit.c / gf16_proof.{c,h}) mirrors the GF(2⁸) stack one element per
VOLE slot, for fastest verification on the high-throughput network-coding path.
See docs/ERASURE_CODES_DESIGN.md for the codec
construction, the dataset commitment and wire format (§6.7 / §6.10), the library
boundary (§7.0), and the test-oracle strategy (Jerasure 2.0 + GF-Complete,
oracle-only and never linked). Runnable examples: example_rs_chunk_membership,
example_rs_heal, example_rs_wire, example_rlnc_gf16,
example_rlnc_gf16_private_vector, and example_rlnc_confidential.
voleith_prove / voleith_verify and voleith_gf8_prove /
voleith_gf8_verify run the complete Fiat-Shamir non-interactive protocol in
one call. The fs_seed parameter is a caller-supplied domain separator that
binds the proof to its application context.
A two-phase (shared-transcript) API is also exposed in the GF(2⁸) variant
(voleith_gf8_prove_commit / _respond, voleith_gf8_verify_reconstruct /
_respond) for hybrid protocols that interleave a VOLEitH proof with a
classical credential scheme on a shared Fiat-Shamir transcript. The split
point is at chall_1, the first FS challenge derived from the BAVC (GGM
tree) commitment, so the challenge can incorporate elements from an outer
protocol (e.g. a Pedersen commitment) before the proof responds to it. See
docs/DESIGN.md → "Two-Phase Fiat-Shamir"
for the rationale and protocol-level pseudocode.
Six FAEST-EM parameter sets at three security levels (128 / 192 / 256-bit)
with two GGM-tree depth choices each: f ("fast", shallower tree, optimised
for prover speed) and s ("small", deeper tree, ~25% smaller proof at
roughly 7× more PRG work during GGM expansion). voleith_params_em_128f
through voleith_params_em_256s.
The f variants are strongly recommended for most applications; pick s
only when proof bytes on the wire are the binding constraint.
Concrete proof sizes by parameter set (GF(2⁸) AES-128 circuit, ℓ = 216),
the f-vs-s trade-off in detail, and what "EM" names (the
leaf-commitment parameter family, not an Even-Mansour OWF; the AES-128
example proves standard AES-128, the FAEST-128f statement): see
docs/DESIGN.md → "Parameter Sets".
See docs/DESIGN.md for the full technical design,
including protocol layering, two-variant rationale, soundness-critical paths,
parameter-set sizing trade-offs, the FAEST norm-trick analysis, the indexed
Merkle trust model, and the future-work roadmap.
- Constant-time comparisons. All secret-dependent equality checks use
voleith_const_memcmp(), nevermemcmp. - Secure zeroing. All contexts holding key material, VOLE correlations,
witness data, or transient cryptographic state are zeroed on free with
voleith_secure_zero()(explicit_bzeroon Linux/BSD,memset_son macOS, volatile-pointer loop otherwise). - No secret-dependent branches or table lookups in any circuit path. The AES S-box uses a purely algebraic tower-field decomposition.
- Constant-time field arithmetic requires GCC or Clang. The software
GF(2^k) multiply keeps its masked, branch-free reduction constant-time with
an inline-asm optimizer barrier. The build hard-errors (
#errorincore/field_scalar.c) on compilers that lack it (e.g. MSVC) rather than silently emitting a variable-time path, so the constant-time guarantees here apply to GCC/Clang targets only. - The constant-time guarantee is tied to the default build. The dudect
evidence holds for the tested compiler and flags only. Do not add
-fltowithout re-running the timing suite and re-checking the disassembly: LTO can inline both voleith's own optimizer barrier and ichor's out-of-line CT barriers (ichor_ct_mask64and friends) and silently reintroduce a secret-dependent branch. This matters most on the designated-opener path, whose constant-time property rests entirely on ichor's primitives and has no voleith-side dudect target of its own. Re-verifying after any toolchain change is the builder's responsibility. VOLEITH_LEGACY_VERIFYis security-relevant. This CMake option (defaultON) letsvoleith_verifyaccept pre-header "legacy" proofs. The legacy path does not bind circuit/parameter identity (it skips the headercheck_identitystep), so any deployment that also accepts v1 (headered) proofs should build with-DVOLEITH_LEGACY_VERIFY=OFFto avoid a downgrade-shaped surface.- Soundness-critical paths implemented exactly per spec. The QuickSilver multiplication check, VOLEHash, and Fiat-Shamir transcript composition are not optimised in any way that deviates from the FAEST v2.0 specification.
- Parameter validation at the API boundary. Every public entry point calls
voleith_params_validate()before any work. - Provers reject invalid witnesses upfront. Both proof-system variants fail fast when the witness violates a circuit constraint, rather than publishing a proof the verifier would later catch.
See docs/DESIGN.md for
the full security-architecture write-up.
One library binary contains every compiled-in backend; ctest runs each test
twice on every host, once with hardware dispatch (<NAME>) and once with the
software floor forced via ICHOR_FORCE_BACKEND (<NAME>_sw). Both paths
are validated against the same known-answer vectors from multiple independent
sources:
- AES primitive: FIPS 197 (Appendices A and B), NIST SP 800-38A Appendix F.1, NIST CAVP AESVS (GFSbox, KeySbox, VarKey, VarTxt).
- AES-CMAC: RFC 4493 Examples 1-4, NIST CAVP CMAC vectors (partial / complete blocks, K1 / K2 paths, truncated tags).
- KDF-CTR(AES-CMAC): NIST CAVS 14.4 (4 vectors, AES-128 / AES-256).
- Grøstl-256 / Grøstl-512: NIST Grøstl ShortMsgKAT and LongMsgKAT known-answer vectors plus the Monte Carlo Test; the GF(2⁸) circuit is cross-checked against the software hash on every test run.
- GF(2^k) arithmetic:
faest-refAppendix A.1 known-answer vectors for every field size from GF(2^8) to GF(2^256). - PRG / GGM tree / vector commitment / AES circuit: cross-validated
against
faest-reftest vectors at FAEST-EM-128F. - Proof system: round-trip valid proofs, invalid-witness rejection, per-section tamper detection, cross-seed replay protection, instance binding, two-phase commit/respond consistency.
The full test-vector inventory is in
docs/DESIGN.md.
Requirements: CMake 3.16+, a C17 compiler (GCC or Clang), Linux or macOS
(x86_64 or aarch64). No third-party dependencies. The shared layer-0
symmetric primitives (AES, SHAKE/SHA3, Grøstl) come from libtalos_ichor,
a first-party Talos library bundled as a git submodule under third_party/
and built as part of this project; clone with --recursive (or run
git submodule update --init) so it is present. The default build is a
single-binary fat library that compiles every available backend (AES-NI,
ARMv8 Crypto, CLMUL, PMULL, plus portable constant-time fallbacks) and
selects among them at runtime based on the host CPU. There is no per-host
build step.
git clone --recursive <repo-url>
cmake -B build
cmake --build build -j$(nproc)
ctest --test-dir build/tests --output-on-failureBuild options. The first four are lean-build opt-outs: omit a hardware backend to shrink the binary or target a deployment that will never see the corresponding ISA. The portable bitsliced AES backend and constant-time scalar field backend are always compiled as the unconditional dispatch floor.
| Option | Default | Description |
|---|---|---|
VOLEITH_AES_NI |
ON | Compile the x86_64 AES-NI backend |
VOLEITH_ARMV8_AES |
ON | Compile the aarch64 ARMv8 Crypto AES backend |
VOLEITH_CLMUL |
ON | Compile the x86_64 CLMUL field-multiply backend |
VOLEITH_PMULL |
ON | Compile the aarch64 PMULL field-multiply backend |
VOLEITH_BUILD_SHARED |
OFF | Build shared library (libtalos_voleith.so/.dylib) in addition to the static library |
VOLEITH_SANITIZE |
OFF | Instrument the library, tests, and examples with ASan + UBSan (dev/CI; see Sanitizer builds) |
A lean build deployed to a hardware-capable host emits a one-shot stderr
notice naming the missing backend and the configure flag that re-enables
it (suppressible with VOLEITH_QUIET=1 in the environment). The dispatch
machinery, lean-build trade-offs, the ICHOR_FORCE_BACKEND testing
override, and the constant-time guarantees across every compiled-in
backend are documented in
docs/DESIGN.md → Runtime Hardware Dispatch.
-DVOLEITH_SANITIZE=ON instruments the library, tests, and examples with
AddressSanitizer and UndefinedBehaviorSanitizer (GCC or Clang). Use a
dedicated build directory so the instrumented binaries never land in a
release artifact:
cmake -B build-san -DVOLEITH_SANITIZE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build-san -j$(nproc)
cd build-san
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
ASAN_OPTIONS=abort_on_error=1:detect_leaks=1 \
ctest --output-on-failureNotes:
- The build aborts on the first UBSan finding (
-fno-sanitize-recover=all), so a violation fails the offendingctestcase instead of printing a warning the run otherwise ignores. - Each test is registered twice (hardware dispatch and an
ICHOR_FORCE_BACKENDsoftware-forced_swvariant), so a singlectestrun sanitizes both the hardware AES-NI / CLMUL paths and the portable constant-time floor. - LeakSanitizer (bundled with ASan) is on by default. Some test harnesses leak
scratch allocations on an early failed check; for a first pass focused on
memory-safety and UB, add
detect_leaks=0toASAN_OPTIONS, then re-run with leak detection on. VOLEITH_SANITIZEis ignored whenVOLEITH_FUZZ=ON, which already applies the same sanitizers plus libFuzzer. Always configure into a fresh build directory; reusing one with different flags picks up a stale CMake cache.
Prove and verify knowledge of an AES-128 key satisfying AES(k, plaintext) = ciphertext:
#include "voleith_gf8.h"
#include "circuits/aes_gf8_circuit.h"
int main(void) {
voleith_gf8_circuit_t *c = voleith_gf8_circuit_new();
gf8_wire_id key[16], pt[16], ct_expected[16], ct[16];
for (int i = 0; i < 16; i++) key[i] = voleith_gf8_add_witness(c);
for (int i = 0; i < 16; i++) pt[i] = voleith_gf8_add_instance(c);
for (int i = 0; i < 16; i++) ct_expected[i] = voleith_gf8_add_instance(c);
aes128_gf8_circuit(c, key, pt, ct);
for (int i = 0; i < 16; i++)
voleith_gf8_assert_equal(c, ct[i], ct_expected[i]);
uint8_t witness[16 + 200]; /* key + AES inversion witnesses */
uint8_t instance[32]; /* plaintext || ciphertext */
/* ... populate witness and instance ... */
voleith_proof_t proof;
const uint8_t fs_seed[] = "my-application-domain-v1";
voleith_gf8_prove(&proof, &voleith_params_em_128f, c,
witness, instance, fs_seed, sizeof(fs_seed) - 1);
int ok = voleith_gf8_verify(&proof, &voleith_params_em_128f, c,
instance, fs_seed, sizeof(fs_seed) - 1);
voleith_proof_free(&proof);
voleith_gf8_circuit_free(c);
return ok == 0 ? 0 : 1;
}See examples/example_aes_gf8.c for the complete runnable version including
witness construction with aes_gf8_build_witness.
Runnable example programs in examples/ exercise every circuit building
block in both proof-system variants, plus the Bristol Fashion parser:
| Example | What it proves |
|---|---|
example_aes.c / example_aes_gf8.c |
Knowledge of an AES-128 / AES-256 key |
example_aes_cmac.c / example_aes_cmac_gf8.c |
Knowledge of a CMAC key producing a given tag |
example_kdf.c / example_kdf_gf8.c |
Correct KDF-CTR(AES-CMAC) derivation from a secret key |
example_merkle.c / example_merkle_gf8.c |
Membership of a secret leaf at a public position in a Merkle tree (AES-DM / CMAC node hash) |
example_merkle_grostl_gf8.c |
The same, with a wide-node Grøstl hash, plus prove/verify timing |
example_merkle_hirose_gf8.c |
The same at 2¹²⁸ CR via Hirose-AES-256 fixed-32 leaf through the vt-driven merkle_vt_gf8_path_circuit, plus prove/verify timing - direct apples-to-apples cost comparison against the AES-DM and Grøstl Merkle examples |
example_indexed_merkle.c / example_indexed_merkle_gf8.c |
Non-membership of a value in an indexed Merkle tree |
example_indexed_merkle_grostl_gf8.c |
Non-membership with wide-node Grøstl-256 T27 nodes (2¹⁰⁸ collision resistance) |
example_kvac_pq.c / example_kvac_pq_gf8.c |
Signal-style anonymous group membership credential (AES-DM trees) |
example_kvac_pq_gf8_depth12.c |
The above at depth 12 (4,096 group members) |
example_kvac_pq_grostl_gf8.c |
The same KVAC statement over Grøstl-256 T27 trees with a hidden leaf index (secret-dir) |
example_kvac_pq_grostl_gf8_depth12.c |
The Grøstl KVAC at depth 12, with prove/verify timing |
example_hirose_gf8.c |
Knowledge of (G, H, M) producing a given Hirose-AES-256 iteration output (the bare iteration primitive) |
example_hirose_leaf_gf8.c |
Knowledge of a 32-byte preimage under the Hirose-AES-256 fixed-32 leaf hash (the leaf side of the Hirose Merkle node vt) |
example_hirose_inode_gf8.c |
Knowledge of children (L, R) under the Hirose-AES-256 inode hash (the inode side of the Hirose Merkle node vt) |
example_bristol_aes128.c |
AES-128 key knowledge from a circuit loaded via the Bristol Fashion parser |
example_rs_v2_linkable_gf8.c |
Composable ring signature with a linkable nullifier: same signer+scope links, different scope does not |
example_rs_v3_attribute_gf8.c |
Composable ring signature proving a hidden attribute is in a public range (age in [18,120]) |
example_rs_v4_claimable_gf8.c |
Composable ring signature with a claimable commitment: sign anonymously, later claim authorship |
example_rs_v6_forward_secure_gf8.c |
Forward-secure ring signature: sign at epoch 0, advance, sign at epoch 5, retired-epoch refusal, verifier epoch-window policy |
example_rs_v5_designated_opener_gf8.c |
Designated-opener ring signature: enroll with the opener id in the leaf, sign anonymously, verify, then a designated opener traces the signature back to the signer (split-custody narrative) |
example_rs_composite_gf8.c |
All composable modules in one proof (membership + revocation + nullifier + spent-set + attribute + commitment) |
example_rs_chunk_membership.c |
RS storage use case end-to-end: FWK-blinded chunk membership certificate, retriever verify/dedup/decode, and the capability-3 consistency check |
example_rs_heal.c |
Healer repair flow: decode a dataset once from k survivors, then re-encode several lost chunks bit-identically (digests unchanged) |
example_rs_wire.c |
The RS dataset on the wire: serialize the descriptor and per-chunk packet, then parse them back, recompute R, and verify the certificate from the bytes |
Each example builds the circuit, generates a valid witness, produces a proof, verifies it, and prints circuit statistics (AND-gate count, ell, proof size) plus PASS/FAIL. After building, run them from the build directory:
./build/examples/example_aes_gf8
./build/examples/example_kvac_pq_gf8_depth12Three examples (example_merkle_gf8, example_merkle_hirose_gf8, and
example_merkle_grostl_gf8) include a small wall-clock benchmark in addition
to the correctness check. They run a few warmup iterations, then time 25
prove and 100 verify calls, and report min / median / mean / max in
milliseconds. All three use the same depth, leaf index, and benchmark
methodology, so running all three on the same host gives a direct
apples-to-apples cost comparison across the three node-hash families.
To minimise scheduler noise, pin the run to a single core with taskset(1):
taskset -c 0 ./build/examples/example_merkle_gf8
taskset -c 0 ./build/examples/example_merkle_hirose_gf8
taskset -c 0 ./build/examples/example_merkle_grostl_gf8The minimum is the cleanest estimate of intrinsic cost (timing noise on a
loaded OS is one-sided slow); the median is the typical run. Quiesce other
CPU-heavy processes first. Disabling CPU frequency scaling
(cpupower frequency-set -g performance on Linux) and SMT/Hyper-Threading
sibling load further reduces variance if you need it.
See docs/DESIGN.md → Performance Benchmarking for the methodology and for the pattern to instrument your own circuits the same way.
Protocol specification (FAEST v2.0), academic papers (VOLEitH, QuickSilver,
half-tree, Hirose, Grøstl), standards documents (FIPS 197, FIPS 202, NIST SP
800-38A / 800-108r1, RFC 4493, RFC 6962), and reference implementations
(faest-ref, used as a test oracle only): see
docs/DESIGN.md → References.
The "Standards and specifications implemented" table near the top of this README lists every standard the library implements, with a one-line role description per standard.
This project is licensed under the GNU Affero General Public License, version 3.0 (AGPL-3.0-only). See LICENSE for the full text.
This library is a clean-room implementation derived from the FAEST v2.0
specification. The FAEST reference implementation (faest-ref, MIT licensed)
was used only as a test oracle to generate known-answer vectors; no source code
was copied from it.
Bug reports and security issues should be filed on the project repository. Security-sensitive reports should be sent privately to the maintainers before public disclosure.