Chat about this codebase

AI-powered code exploration

Online

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)

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.

  1. In VS Code, run Remote-Containers: Reopen in Container
  2. Wait for the container to build (uses .devcontainer/Dockerfile)
  3. 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 under lib/
  • 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

  1. Represent inputs as coefficient arrays in a suitable fp-based field.
  2. 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() and cs.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() and verifier.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:

  1. Define and optimize multiplication gates in your QuadCircuit.
  2. Compute a stable circuit identifier for artifact management.
  3. Commit your private witness and randomness with ZkProver::commit.
  4. 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

  1. 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.
  2. 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.
  3. Two-operand scalar multiply

    size_t e = qc.mul(k, a);        // creates k·a
    
  4. 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

  1. Extract private part: W.v_[c_.npub_in…].
  2. Generate per-layer pad via fill_pad(rng).
  3. Call LigeroProver’s commit to write into zkp.com and update tp.

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 into tp.

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 nth root of unity.

Usage

  1. Prepare your field and data

    • Define a Field instance (e.g. a prime‐modulus field).
    • Choose omega such that omega^omega_order == 1 and omega_order is divisible by n.
    • Allocate an array A of length n (pad with zeros if your polynomial degree < n).
  2. 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))
    
  3. 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 of Field::Elt, length n (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 of omega (must satisfy (omega)^(omega_order/n) is a primitive nth 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

  1. Matrix Strategy

    • Defines combinations of os, container_image, cpp_compiler and per-OS install_command.
    • Runs Debian or Fedora inside an Ubuntu runner via container_image.
  2. Dependency Installation

    • Single step running ${{ matrix.install_command }} installs CMake, compilers, Google Test, Benchmark, etc.
  3. Reusable Strings Step

    - name: Set reusable strings
      id: strings
      run: echo "build-output-dir=$GITHUB_WORKSPACE/build" >> "$GITHUB_OUTPUT"
    
  4. 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 under matrix.
  • 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 bytes
  • kPRFInputSize = 16 bytes
  • kPRFOutputSize = 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]);
};
  1. Initialization

    • Supply a 32-byte key (e.g. from rand_bytes).
    • Internally initializes an OpenSSL EVP_CIPHER_CTX for AES-256-ECB.
  2. Eval()

    • Encrypts exactly 16 input bytes → 16 output bytes.
    • Caller must ensure out and in 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.

  1. Install clang-format (v15+).
  2. Run before each commit:
    clang-format -i $(git ls-files '*.h' '*.cpp')
    git diff --exit-code  # ensure no style changes remain
    
  3. Use descriptive names, avoid macros, prefer constexpr and enum 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.

  1. Create build directory and configure:
    cmake -B build -DCMAKE_BUILD_TYPE=Debug
    
  2. Build and run unit tests:
    cmake --build build
    cd build
    ctest --output-on-failure
    
  3. 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

  1. Fork the repo and create a feature branch:
    git checkout -b feat/your-feature
    
  2. Commit with clear messages:
    feat(util/log): support DEBUG log level
    
  3. Ensure all tests pass and code is formatted.
  4. Push and open a PR against main.
  5. In PR description: reference issues (Fixes #123), describe change scope, include screenshots or logs if relevant.
  6. 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, and ALIASES.

Refer to Doxygen manual for advanced settings: https://www.doxygen.nl/manual/config.html