Project Overview
Longfellow-zk is a modular C++ library for constructing zero-knowledge proofs over legacy identity standards (e.g., SSNs, passports). It delivers high-performance arithmetic primitives, a flexible circuit builder, and proof engines that enable privacy-preserving verification without exposing sensitive attributes.
Problems Addressed
- Privacy-preserving verification of identity attributes
- Interoperability with existing identity frameworks
- High-throughput cryptographic operations optimized per architecture
High-Level Architecture
Longfellow-zk separates concerns into layered modules:
- Application
- API Layer
- Proof Engines (zk-SNARK, zk-STARK backends with Fiat-Shamir)
- Circuit Builder (R1CS/Arithemetic circuits)
- Algebraic Primitives (multi-precision, prime fields, FFTs, hashing, ECCodes)
- Circuit Builder (R1CS/Arithemetic circuits)
- Proof Engines (zk-SNARK, zk-STARK backends with Fiat-Shamir)
- API Layer
Major Components
- lib/algebra
Core arithmetic: multi-precision integers, prime-field routines (generic and optimized), FFTs, hashing, Reed–Solomon codes. - lib/circuits
R1CS and arithmetic circuit construction, serialization, constraint optimizations. - lib/proofs
Proof generation and verification interfaces, Fiat-Shamir transcript, public input handling. - lib/bench
Benchmarks for field operations and proof pipelines on target architectures.
Build and Dependencies
Longfellow-zk uses CMake (≥3.15) and targets C++17. It optionally integrates GMP for big-integer speedups and OpenSSL for hashing.
Prerequisites
- CMake ≥3.15
- A C++17 compiler (GCC, Clang)
- GMP (optional)
- OpenSSL (for SHA/SHA3)
Build Example
git clone https://github.com/google/longfellow-zk.git
cd longfellow-zk
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON ..
make -j$(nproc)
ctest --output-on-failure
CMake Integration Snippet
# In your CMakeLists.txt
find_package(LongfellowZK REQUIRED CONFIG PATHS /usr/local/lib/cmake/LongfellowZK)
add_executable(my_app src/main.cpp)
target_link_libraries(my_app
PRIVATE
LongfellowZK::algebra
LongfellowZK::circuits
LongfellowZK::proofs
)
Documentation
Generate and browse the full API reference via Doxygen. After building:
- Open
docs/html/index.html
- Use the search box to locate classes, functions, and modules
- Explore detailed hierarchies, examples, and cross-references.
2. Getting Started
Follow these steps to clone, build, and test Longfellow-zk on your workstation or inside VS Code’s dev container.
2.1 Prerequisites
- Git >= 2.30
- CMake >= 3.25
- Docker & VS Code with “Remote – Containers” extension (optional)
- A modern C++ compiler (Clang or GCC)
2.2 Clone the Repository
git clone https://github.com/google/longfellow-zk.git
cd longfellow-zk
2.3 Quick Start with VS Code Dev Container
Longfellow-zk provides a preconfigured Ubuntu 24.04 container with Clang, CMake, Google Test, Benchmark, Zlib, Zstd and more.
- In VS Code, run Remote-Containers: Reopen in Container
- Wait for the container to build (uses
.devcontainer/Dockerfile
) - Your workspace now has all tools and correct architecture flags
Alternatively, use the DevContainer CLI:
# From repo root
devcontainer build --workspace-folder . --image-name longfellow-zk-dev
devcontainer up --workspace-folder .
# Opens a shell inside the container
2.4 Building Locally with CMake
Whether inside or outside the container, build via CMake:
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallel 8
- The root
CMakeLists.txt
picks up all submodules underlib/
- Architecture-specific flags come from
lib/CMakeLists.txt
(e.g.-mpclmul
,-march=armv8-a+crypto
on AArch64)
2.5 Running Tests and Benchmarks
After a successful build, run unit tests and benchmarks from build/
:
# Run all Google Test unit tests
ctest --output-on-failure
# Or invoke the combined main directly:
./proofs/tests/proof_test
# To run benchmarks instead:
./proofs/tests/proof_test --benchmark_filter=.* --benchmark_min_time=0.5
proof_test
is linked against both GTest and Google Benchmark- The first argument starting with
--bench
switches to benchmarks
2.6 Continuous Integration Overview
Longfellow-zk’s GitHub Actions workflow (.github/workflows/cmake-multi-platform.yml
) runs builds and tests across multiple Linux images and compilers:
name: CMake Multi-Platform
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, ubuntu-22.04]
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y cmake ${{ matrix.compiler }}-++ git zlib1g-dev
- name: Configure
run: cmake -S . -B build -DCMAKE_CXX_COMPILER=${{ matrix.compiler }}++ -DCMAKE_BUILD_TYPE=Debug
- name: Build & Test
run: cmake --build build --parallel && cd build && ctest --output-on-failure
- Add platforms or compilers by extending the
matrix
- The same flags in
.devcontainer
ensure parity between local and CI environments
3. Core Concepts
This section introduces the core directories and their fundamental abstractions. Each subsection maps to one lib/
subdirectory and shows how to include, use, and extend its components.
lib/algebra Directory Overview
Purpose
The lib/algebra directory collects low-level arithmetic primitives, finite-field routines and transform algorithms used across the codebase. Before pulling in higher-level modules (e.g. ZK circuits, pairing engines), you’ll often rely on one or more of these headers:
• Field arithmetic
– fp.h, fp2.h, fp_generic.h, fp_p128.h, fp_p256.h
– limb.h, nat.h
• Fast transforms & convolutions
– fft.h, rfft.h, fft_interpolation.h, convolution.h, interpolation.h, twiddle.h
• Polynomial operations & error-correction
– poly.h, reed_solomon.h, permutations.h
• Utilities
– hash.h, bogorng.h (randomness), compare.h, static_string.h, utility.h
• Platform glue
– sysdep.h
Include pattern
All headers live under your project’s include path as algebra/<header>
. For example:
#include <algebra/fp.h>
#include <algebra/fft.h>
#include <algebra/convolution.h>
Practical example: large-integer multiplication via FFT convolution
- Represent inputs as coefficient arrays in a suitable fp-based field.
- Forward-FFT both arrays, multiply pointwise, inverse-FFT the result.
#include <algebra/fp_p256.h>
#include <algebra/fft.h>
#include <algebra/convolution.h>
#include <string.h>
#include <stdlib.h>
/* Multiply two polynomials a and b of degree N–1, result in c of length N */
void poly_mul_fft(const fp_t *a, const fp_t *b, fp_t *c, unsigned N) {
fp_t *A = malloc(sizeof(fp_t)*N);
fp_t *B = malloc(sizeof(fp_t)*N);
for(unsigned i = 0; i < N; i++){ A[i] = a[i]; B[i] = b[i]; }
fft_forward(A, N);
fft_forward(B, N);
for(unsigned i = 0; i < N; i++)
fp_mul(&A[i], &B[i], &A[i]);
fft_inverse(A, N);
memcpy(c, A, sizeof(fp_t)*N);
free(A); free(B);
}
Guidance
- Only pull in the headers you actually use to avoid build-time bloat.
- For bare-metal or cross-compiles, review
sysdep.h
to adjust underlying C standard or platform quirks. - When combining transforms with field arithmetic, ensure your field’s order supports the chosen FFT size (roots of unity).
lib/circuit Directory Overview
Purpose
The lib/circuit directory defines abstractions to build and manipulate arithmetic constraint systems (R1CS/PLONK). Key types include variables, linear combinations and gate definitions.
Key files
• circuit.h (Circuit builder API)
• constraint_system.h (ConstraintSystem class)
• wire.h, gate.h (Gate representations)
• linear_combination.h (LC builder)
• witness.h (Assignment of variables)
Include pattern
#include <circuit/constraint_system.h>
#include <circuit/linear_combination.h>
#include <circuit/gate.h>
Practical example: build a simple multiplication-plus-constant circuit
#include <algebra/fp_p256.h>
#include <circuit/constraint_system.h>
#include <circuit/linear_combination.h>
using namespace longfellow::circuit;
using fp = longfellow::algebra::fp_t;
void build_mul_add(CircuitBuilder<fp>& builder) {
// Allocate variables: a, b, c = a * b + 5
auto a = builder.allocate_input("a");
auto b = builder.allocate_input("b");
auto c = builder.allocate_witness("c");
// Enforce a * b = d
auto d = builder.add_wire("d");
builder.add_gate(Gate::Mul, {a, b}, {}, d);
// Enforce d + 5 = c
auto five = builder.constant(5);
builder.add_gate(Gate::Add, {d, five}, {}, c);
}
int main() {
CircuitBuilder<fp> builder;
build_mul_add(builder);
auto cs = builder.finalize();
// cs now contains gates, variables, and can be input to a prover
}
Guidance
- Use
allocate_input
for public inputs,allocate_witness
for private ones. - Combine
LinearCombination
with custom gates for advanced constraints. - Inspect
cs.num_gates()
andcs.num_variables()
before proof generation.
lib/plonk Directory Overview
Purpose
The lib/plonk directory implements the PLONK proof system: indexing, proving and verification. It orchestrates polynomial commitments, FFT domains, and the Fiat–Shamir transcript.
Key files
• indexer.h (Preprocessing: circuit → proving/verification keys)
• prover.h (Prover API)
• verifier.h (Verifier API)
• proof.h (Serialized proof format)
• types.h (Common PLONK types)
Include pattern
#include <plonk/indexer.h>
#include <plonk/prover.h>
#include <plonk/verifier.h>
Practical example: end-to-end prove & verify
#include <algebra/fp_p256.h>
#include <circuit/constraint_system.h>
#include <plonk/indexer.h>
#include <plonk/prover.h>
#include <plonk/verifier.h>
#include <transcript/merlin_transcript.h>
using fp = longfellow::algebra::fp_t;
int main() {
// 1) Build circuit
CircuitBuilder<fp> builder;
// ... define inputs, gates ...
auto cs = builder.finalize();
// 2) Preprocess: generate keys
auto [pk, vk] = plonk::indexer(cs);
// 3) Prove
merlin::Transcript pt("plonk");
plonk::Prover<fp> prover(cs, pk, pt);
auto proof = prover.create_proof();
// 4) Verify
merlin::Transcript vt("plonk");
vt.append_message("proof", proof.data(), proof.size());
plonk::Verifier<fp> verifier(vk, vt);
bool ok = verifier.verify(proof);
return ok ? 0 : 1;
}
Guidance
- Cache
pk
/vk
for repeated proofs on the same circuit. - Adjust FFT domain size via
pk.domain.log2_size()
for performance. - Inspect
prover.stats()
andverifier.stats()
for bottlenecks.
lib/transcript Directory Overview
Purpose
Implements Fiat–Shamir transcripts to derive randomness and challenges in a non-interactive proof. Supports Merlin and SHA-256 based transcripts.
Key files
• transcript.h (Core interface)
• merlin_transcript.h (Merlin binding)
• sha256_transcript.h (SHA-256 binding)
Include pattern
#include <transcript/merlin_transcript.h>
Practical example: challenge generation
#include <algebra/fp_p256.h>
#include <transcript/merlin_transcript.h>
int main() {
using fp = longfellow::algebra::fp_t;
merlin::Transcript t("example");
// Absorb bytes
uint8_t data[] = {0x01, 0x02, 0x03};
t.append_message("label1", data, sizeof(data));
// Derive scalar challenge in field
fp chal;
t.challenge_scalar("challenge1", chal);
// Use chal in circuit or protocol
}
Guidance
- Always use domain-separated labels (e.g.
"round1"
,"proof"
) - For multi-instance proofs, seed the transcript with unique IDs.
- Merlin transcripts resist length-extension; prefer over raw hashes.
lib/utils Directory Overview
Purpose
Provides miscellaneous utilities: logging, random number generation, configuration and error handling.
Key files
• logger.h (Logging macros & sinks)
• randomness.h (BogorNG, OS RNG wrappers)
• config.h (Parse CLI flags, environment)
• exception.h (Error classes)
Include pattern
#include <utility/logger.h>
#include <utility/randomness.h>
Practical example: initialize logger and RNG
#include <utility/logger.h>
#include <utility/randomness.h>
int main(int argc, char** argv) {
// Initialize logging at INFO level
Logger::init(Logger::Level::Info);
Logger::info("Starting proof generation");
// Create seeded RNG
auto rng = Randomness::create_seeded(0xDEADBEEF);
uint64_t r = rng.next_u64();
Logger::debug("Drawn random 64-bit: {}", r);
// ... rest of application ...
}
Guidance
Use
Logger::debug
,info
,warn
,error
for leveled output.Favor
Randomness::create_seeded
in tests for reproducibility.Centralize configuration in
config.h
to avoid scattered flag parsing. Sure. Please provide:The subsection title or topic you need documented (e.g. “Configuring the OAuth Token Refresh”)
Relevant file names and brief summaries or the key types/functions involved
With that information I’ll generate a concise, example-driven markdown snippet.
5. Building & Using Your Own Proofs
This recipe walks you through constructing a new arithmetic circuit, generating a zk-SNARK proof over private inputs, and verifying it. You will:
- Define and optimize multiplication gates in your
QuadCircuit
. - Compute a stable circuit identifier for artifact management.
- Commit your private witness and randomness with
ZkProver::commit
. - Drive the interactive verification with
ZkVerifier
.
QuadCircuit::mul – Defining Optimized Multiplication Nodes
Purpose
Introduce multiplication gates into your QuadCircuit
with constant-folding, zero/one detection, and CSE.
Usage Patterns
Plain wire–wire product
// a, b are wire indices from qc.input() or earlier ops size_t c = qc.mul(a, b); // creates 1·a·b
- Returns zero-node if either operand is zero.
- Hoists or merges constants and linear terms.
Scaling by a constant
Field::Elt k = qc.f_.of_scalar(17); size_t d = qc.mul(k, a, b); // creates 17·a·b
- k=0 ⇒ zero constant; k=1 or any operand zero ⇒ other operand.
- Nested constants merged: k·(k₁·x) → (k·k₁)·x.
Two-operand scalar multiply
size_t e = qc.mul(k, a); // creates k·a
Forcing a true linear term
size_t lin = qc.linear(a); // marks a as linear size_t f = qc.mul(k, lin); // yields k·a with explicit zero-wire
Under the Hood
- Zero/One shortcuts: special‐case k=0,1 or trivial operands.
- Constant propagation: collapses nested constants.
- CSE: hashes and deduplicates new nodes.
Practical Tips
- Use
qc.mul(a,b)
to leverage quad-term grouping. - Use
qc.linear(op)
only when you need a pinned linear wire. - To scale by a constant, prefer
qc.mul(k,op)
.
Example Circuit Snippet
using QC = QuadCircuit<YourField>;
QC qc(your_field);
// inputs
size_t a = qc.input();
size_t b = qc.input();
// c = 17*a*b
auto seventeen = qc.f_.of_scalar(17);
size_t c = qc.mul(seventeen, a, b);
// d = 5*a as a linear term
auto five = qc.f_.of_scalar(5);
size_t lin_a = qc.linear(a);
size_t d = qc.mul(five, lin_a);
// enforce c + d == 0
size_t sum = qc.add(c, d);
qc.assert0(sum);
Generating Circuit Identifiers (circuit_id)
Purpose
Compute a reproducible 32-byte SHA-256 fingerprint of any Circuit<Field>
instance, capturing its shape and constants.
Signature
template <class Field>
void circuit_id(uint8_t id[32],
const Circuit<Field>& c,
const Field& F);
Usage Example
#include "circuits/compiler/circuit_id.h"
#include "sumcheck/circuit.h"
#include "util/fields/gf2_128.h"
#include <array>
#include <iomanip>
#include <iostream>
int main() {
proofs::Circuit<GF2_128> circuit = /* build or load */;
GF2_128 F;
std::array<uint8_t,32> id_bytes;
proofs::circuit_id(id_bytes.data(), circuit, F);
for(auto b : id_bytes)
std::cout << std::hex << std::setw(2) << std::setfill('0') << int(b);
std::cout << "\n";
}
Practical Guidance
- Call after constructing the circuit and before serialization.
- Use this ID to index prover/verifier keys or optimized artifacts.
- IDs change if you alter gates, ordering or constants.
ZkProver::commit – Committing Witness and Random Pad
Purpose
Bind your private witness and fresh randomness in a Ligero commitment before sumcheck.
Signature
void commit(ZkProof<Field>& zkp,
const Dense<Field>& W,
Transcript& tp,
RandomEngine& rng);
Step-by-Step
- Extract private part:
W.v_[c_.npub_in…]
. - Generate per-layer pad via
fill_pad(rng)
. - Call LigeroProver’s
commit
to write intozkp.com
and updatetp
.
Minimal Usage Example
ZkProver<Fr, MyRSFactory> prover(circuit, Fr::factory(), rs_factory);
ZkProof<Fr> zkp;
Transcript tp;
RandomEngine rng;
// W includes public slots first
Dense<Fr> W = load_witness();
// commit private witness + pad
prover.commit(zkp, W, tp, rng);
// now zkp.com holds the commitment, tp has challenges
Practical Guidance
- Always invoke commit before prove.
- Retain the same
Transcript
for Fiat–Shamir consistency. - The pad is deterministic from
rng
and absorbed intotp
.
Verifying a ZK Proof with ZkVerifier
Purpose
Drive the interactive verification of a zero-knowledge proof using ZkVerifier
.
1. Instantiate the verifier
#include "zk/zk_verifier.h"
#include "zk/zk_proof.h"
#include "arrays/dense.h"
#include "ligero/ligero_param.h"
#include "random/transcript.h"
RSFactory rsf{ /* setup */ };
size_t rate = /* codeword rate */, nreq = /* queries */;
proofs::ZkVerifier<Field,RSFactory>
verifier(circ, rsf, rate, nreq, F);
2. Receive commitments
proofs::ZkProof<Field> zk = /* from prover */;
Transcript transcript;
verifier.recv_commitment(zk, transcript);
3. Perform verification
// public inputs in same order as circ.pub_inputs()
Dense<Field> pub_inputs =
Dense<Field>::wrap(pub_vector.data(), pub_vector.size());
bool ok = verifier.verify(zk, pub_inputs, transcript);
if (!ok) {
// invalid proof
} else {
// proof accepted
}
Behind the scenes, verify
absorbs public inputs via Fiat–Shamir, builds circuit constraints, and invokes LigeroVerifier::verify
for sumcheck and codeword checks.
6. Advanced Topics
FFT Transforms (fftf and fftb)
Provide forward and inverse Fast Fourier Transforms over any finite field satisfying the Field
interface (Elt
type, add
, sub
, mul
, invertf
). Use fftf
for the “engineer’s” forward FFT (negative exponent), and fftb
for the inverse (positive exponent). Both require array length n
to be a power of two and a primitive n
th root of unity.
Usage
Prepare your field and data
- Define a
Field
instance (e.g. a prime‐modulus field). - Choose
omega
such thatomega^omega_order == 1
andomega_order
is divisible byn
. - Allocate an array
A
of lengthn
(pad with zeros if your polynomial degree < n).
- Define a
Forward FFT (point evaluation)
#include "algebra/fft.h" #include "algebra/prime_field.h" using Field = PrimeField<uint64_t>; Field F(modulus); size_t n = 1 << k; // power of two ≥ polynomial degree std::vector<Field::Elt> A(n); // fill A[0..m-1] with coefficients, rest = 0 Field::Elt omega = F.generator(); // primitive element uint64_t omega_order = F.order_of(omega); // In-place forward FFT: A[j] ← P(ω^(-j)) proofs::FFT<Field>::fftf(A.data(), n, omega, omega_order, F); // A now holds evaluations P(1), P(ω), ..., P(ω^(n-1))
Inverse FFT (interpolation)
// Given A[j] = P(ω^j), recover coefficients in place proofs::FFT<Field>::fftb(A.data(), n, omega, omega_order, F); // After fftb, A[i] = n * coeff_i, so divide by n: Field::Elt inv_n = F.invertf(F.from_uint(n)); for (size_t i = 0; i < n; ++i) { F.mul(A[i], inv_n); } // A now contains the original polynomial coefficients
Parameters
A[/*n*/]
: pointer to an array ofField::Elt
, lengthn
(power of two).n
: size of the transform, must be a power of two.omega
: a primitive root of unity in the field.omega_order
: the order ofomega
(must satisfy(omega)^(omega_order/n)
is a primitiven
th root).F
: your field implementation providing:add(a,b)
,sub(a,b)
,mul(a,b)
invertf(x)
CMake Multi-Platform GitHub Actions Workflow
Configure automated builds and tests of this CMake-based repository across multiple Linux distributions, CPU architectures, and compilers using a single matrix-driven workflow.
Essential Components
Matrix Strategy
- Defines combinations of
os
,container_image
,cpp_compiler
and per-OSinstall_command
. - Runs Debian or Fedora inside an Ubuntu runner via
container_image
.
- Defines combinations of
Dependency Installation
- Single step running
${{ matrix.install_command }}
installs CMake, compilers, Google Test, Benchmark, etc.
- Single step running
Reusable Strings Step
- name: Set reusable strings id: strings run: echo "build-output-dir=$GITHUB_WORKSPACE/build" >> "$GITHUB_OUTPUT"
Configure, Build & Test
- name: Configure CMake run: cmake -B ${{ steps.strings.outputs.build-output-dir }} \ -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \ -DCMAKE_BUILD_TYPE=Release \ -S $GITHUB_WORKSPACE - name: Build run: cmake --build ${{ steps.strings.outputs.build-output-dir }} -j - name: Test working-directory: ${{ steps.strings.outputs.build-output-dir }} run: ctest -j
Full Workflow Snippet
name: CMake on multiple platforms
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
container: ${{ matrix.container_image }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04-16core
os_name: "Ubuntu 24.04"
cpp_compiler: clang++
install_command: sudo apt-get update && sudo apt-get install -y clang cmake libssl-dev libzstd-dev
- os_name: "Debian 12"
os: ubuntu-24.04-16core
container_image: debian:12
cpp_compiler: clang++
install_command: apt-get update && apt-get install -y clang cmake libssl-dev libzstd-dev
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: ${{ matrix.install_command }}
- name: Set reusable strings
id: strings
run: echo "build-output-dir=$GITHUB_WORKSPACE/build" >> "$GITHUB_OUTPUT"
- name: Configure CMake
run: cmake -B ${{ steps.strings.outputs.build-output-dir }} \
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \
-DCMAKE_BUILD_TYPE=Release \
-S $GITHUB_WORKSPACE
- name: Build
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} -j
- name: Test
working-directory: ${{ steps.strings.outputs.build-output-dir }}
run: ctest -j
Practical Tips
- To add a distro or compiler, append an
include
entry undermatrix
. - Use
container_image
to test in a clean environment. - Centralize paths via the
strings
step to reduce copy-paste errors.
PRF: Pseudo-Random Function
Provide a deterministic, AES-256-ECB-based PRF interface for deriving fixed-length pseudo-random outputs in protocols (e.g., Fiat–Shamir transcripts).
When to Use
Anytime you need a keyed, collision-resistant mapping F(key, input16) → output16
that behaves like a random oracle over 16-byte blocks.
Key Constants
kPRFKeySize = 32
byteskPRFInputSize = 16
byteskPRFOutputSize = 16
bytes
Interface
class PRF {
public:
explicit PRF(const uint8_t key[kPRFKeySize]);
~PRF();
void Eval(uint8_t out[kPRFOutputSize], const uint8_t in[kPRFInputSize]);
};
Initialization
- Supply a 32-byte key (e.g. from
rand_bytes
). - Internally initializes an OpenSSL
EVP_CIPHER_CTX
for AES-256-ECB.
- Supply a 32-byte key (e.g. from
Eval()
- Encrypts exactly 16 input bytes → 16 output bytes.
- Caller must ensure
out
andin
do not overlap. - Reuses the same key context across calls.
Practical Example
#include "crypto/prf.h"
#include "crypto/random.h"
uint8_t prf_key[kPRFKeySize];
rand_bytes(prf_key, kPRFKeySize);
PRF prf(prf_key);
// Prepare 16-byte input (e.g., counter + padding)
uint8_t input_block[kPRFInputSize] = {0};
uint64_t counter = 42;
memcpy(input_block, &counter, sizeof(counter));
uint8_t output_block[kPRFOutputSize];
prf.Eval(output_block, input_block);
// Split into two 8-byte values
uint64_t a, b;
memcpy(&a, output_block, sizeof(a));
memcpy(&b, output_block + sizeof(a), sizeof(b));
Advanced Patterns
- Chaining outputs
Feed previous output as next input to stretch PRF outputs. - Domain separation
Prefix or suffix your input with constants to avoid collisions.
Common Pitfalls
- Don’t reuse the same 16-byte input under the same key unless deterministic repeats are desired.
- Ensure input and output buffers are distinct.
- Do not use this AES-ECB PRF for variable-length plaintext encryption.
7. Contributing & Development
Provide guidelines for joining development, ensuring consistent code quality and smooth collaboration.
7.1 License Compliance
All contributions fall under Apache License 2.0.
- Include the boilerplate header in new source files:
// Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0
- Do not remove or alter attribution notices.
- See LICENSE for full terms.
7.2 Coding Style
We follow the Google C++ Style Guide and enforce via clang-format.
- Install clang-format (v15+).
- Run before each commit:
clang-format -i $(git ls-files '*.h' '*.cpp') git diff --exit-code # ensure no style changes remain
- Use descriptive names, avoid macros, prefer
constexpr
andenum class
.
7.3 Dev Container
We provide a reproducible VS Code environment in .devcontainer/
.
.devcontainer/Dockerfile
FROM mcr.microsoft.com/devcontainers/cpp:ubuntu-24.04
RUN apt update && \
DEBIAN_FRONTEND=noninteractive apt install -y \
clang cmake git g++ libssl-dev libzstd-dev \
libgtest-dev libbenchmark-dev zlib1g-dev
.devcontainer/devcontainer.json
{
"name": "longfellow-zk-dev",
"build": { "dockerfile": "Dockerfile", "options": ["--platform=linux/amd64"] },
"customizations": {
"vscode": {
"extensions": ["ms-vscode.cmake-tools","ms-vscode.cpptools"],
"settings": {
"terminal.integrated.defaultProfile.linux": "zsh",
"terminal.integrated.profiles.linux": { "zsh": { "path": "/bin/zsh" } }
}
}
}
}
Usage:
- In VS Code, Remote-Containers: Reopen in Container
- Rebuild after
.devcontainer
changes: Remote-Containers: Rebuild Container
7.4 Running the Test Suite
We use Google Test and CTest.
- Create build directory and configure:
cmake -B build -DCMAKE_BUILD_TYPE=Debug
- Build and run unit tests:
cmake --build build cd build ctest --output-on-failure
- Run benchmarks (optional):
./benchmarks/my_benchmark # built under build/benchmarks
7.5 Using the Logging Utility
lib/util/log.h
provides leveled logging.
#include "util/log.h"
int main() {
set_log_level(INFO); // ERROR, WARNING, INFO
log_info("Initializing protocol version {}", 1);
// ...
log_error("Unexpected error: {}", err_msg);
}
- Call
set_log_level()
early (before any modules log). - Use formatting placeholders
{}
like in fmtlib.
7.6 Submitting a Pull Request
- Fork the repo and create a feature branch:
git checkout -b feat/your-feature
- Commit with clear messages:
feat(util/log): support DEBUG log level
- Ensure all tests pass and code is formatted.
- Push and open a PR against
main
. - In PR description: reference issues (
Fixes #123
), describe change scope, include screenshots or logs if relevant. - Address review comments promptly and rebase to resolve conflicts.
Thank you for contributing! We look forward to your improvements.
8. API Reference & Doxygen
Generate and browse the Longfellow ZK API reference using Doxygen. This section points to the auto-generated HTML docs and shows how to customize and regenerate them.
8.1 Input File Configuration
Defines which files and directories Doxygen parses.
Essential Settings:
# Doxyfile excerpt
INPUT = lib ./README.md
RECURSIVE = YES
FILE_PATTERNS = *.c \
*.cc \
*.h \
*.hh \
*.markdown \
*.md
EXCLUDE = third_party/legacy
EXCLUDE_PATTERNS = */test/*
USE_MDFILE_AS_MAINPAGE = README.md
• INPUT: lib
directory and project README
• RECURSIVE: descends into all subfolders
• FILE_PATTERNS: picks C/C++ sources and Markdown
• EXCLUDE / EXCLUDE_PATTERNS: skips legacy code and tests
Practical variants:
- Add custom docs:
INPUT = lib docs guides/README.md
- Omit generated code:
EXCLUDE = lib/generated
- Limit to C++ headers/sources:
FILE_PATTERNS = *.cpp *.hpp
- Skip VCS or temp dirs:
EXCLUDE_PATTERNS = */.git/* */backup/*
8.2 HTML Output & Appearance
Control output location, site title, and navigation.
# Doxyfile excerpt
GENERATE_HTML = YES
HTML_OUTPUT = docs/html
HTML_FILE_EXTENSION = .html
HTML_HEADER = assets/doxygen/custom_header.html
PROJECT_NAME = "Longfellow ZK"
PROJECT_BRIEF = "Zero-knowledge protocols library"
DISABLE_INDEX = NO
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
GENERATE_TREEVIEW = YES
• HTML_OUTPUT: writes into docs/html
• PROJECT_NAME / PROJECT_BRIEF: appears in navbar
• SOURCE_BROWSER = NO: hides raw source tabs
• GENERATE_TREEVIEW: adds collapsible sidebar
8.3 Diagrams & Cross-References
Enable Graphviz diagrams and member links.
# Doxyfile excerpt
HAVE_DOT = YES
DOT_PATH = /usr/local/bin
CLASS_DIAGRAMS = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
DOT_IMAGE_FORMAT = svg
• HAVE_DOT: generates UML and call graphs
• DOT_IMAGE_FORMAT: uses SVG for crisp rendering
• CALL_GRAPH / CALLER_GRAPH: show function relationships
8.4 Regenerating the HTML Docs
Run Doxygen from the repository root whenever you update code or docs.
# Install prerequisites (if needed)
brew install doxygen graphviz # macOS
sudo apt-get install doxygen # Linux
# Generate HTML docs
cd /path/to/google/longfellow-zk
doxygen Doxyfile
On success, updated HTML lives under docs/html
. Commit the docs/html
folder to serve via GitHub Pages or internal web server.
8.5 Browsing & Search
Open docs/html/index.html
in your browser. The top-right search box indexes classes, files, and members. The left sidebar treeview lets you:
- Jump to modules, classes, files
- Expand namespaces and directories
- View detailed member documentation with diagrams
Use browser “Find” (Ctrl+F) for quick keyword lookup.
8.6 Customizing Further
- Change theme colors by editing
assets/doxygen/custom_header.html
. - Include additional Markdown pages via
INPUT
. - Adjust member grouping with
SORT_MEMBER_DOCS
,TAB_SIZE
, andALIASES
.
Refer to Doxygen manual for advanced settings: https://www.doxygen.nl/manual/config.html