Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Summary

<!-- What does this PR do and why? Link to relevant issues. -->

## Changes

<!-- Bulleted list of key changes. -->

-

## Testing

<!-- How was this tested? Which tests cover this change? -->

- [ ] Ran tests for modified crates
- [ ] `cargo clippy` and `cargo fmt` pass

## Security Considerations

<!-- Every change to a zkVM has security implications. Describe them.
Does this affect proof soundness? Verifier correctness? Private inputs? -->

## Breaking Changes

<!-- List any breaking changes to public APIs, proof formats, or serialization. Write "None" if not applicable. -->

None
99 changes: 99 additions & 0 deletions .github/workflows/bench-crates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Benchmark modular crates

on:
push:
branches: [main]
paths:
- "crates/**"
pull_request:
branches: [main]
paths:
- "crates/**"

env:
CARGO_TERM_COLOR: always

concurrency:
group: bench-crates-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
bench:
runs-on: ubuntu-latest
name: Criterion benchmarks
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1

- name: Install critcmp
run: cargo install critcmp --locked

# On main push: save baseline
- name: Run benchmarks (baseline)
if: github.event_name == 'push'
run: >
cargo bench
-p jolt-field
-p jolt-poly
-p jolt-transcript
-p jolt-sumcheck
-p jolt-openings
-- --save-baseline main_run

- name: Upload baseline
if: github.event_name == 'push'
uses: actions/upload-artifact@v4
with:
name: criterion-baseline
path: target/criterion/
retention-days: 30

# On PR: compare against baseline
- name: Download baseline
if: github.event_name == 'pull_request'
uses: dawidd6/action-download-artifact@v6
with:
name: criterion-baseline
path: target/criterion/
branch: main
workflow: bench-crates.yml
if_no_artifact_found: warn

- name: Run benchmarks (PR)
if: github.event_name == 'pull_request'
run: >
cargo bench
-p jolt-field
-p jolt-poly
-p jolt-transcript
-p jolt-sumcheck
-p jolt-openings
-- --save-baseline pr_run

- name: Compare benchmarks
if: github.event_name == 'pull_request'
id: critcmp
run: |
if [ -d "target/criterion/main_run" ]; then
OUTPUT=$(critcmp main_run pr_run --threshold 5 2>&1) || true
echo "comparison<<EOF" >> "$GITHUB_OUTPUT"
echo "$OUTPUT" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
else
echo "comparison=No baseline found — skipping comparison." >> "$GITHUB_OUTPUT"
fi

- name: Post benchmark comparison
if: github.event_name == 'pull_request' && steps.critcmp.outputs.comparison != ''
uses: actions/github-script@v7
with:
script: |
const body = `## Benchmark comparison (crates)\n\n\`\`\`\n${process.env.COMPARISON}\n\`\`\``;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
env:
COMPARISON: ${{ steps.critcmp.outputs.comparison }}
61 changes: 60 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ keywords = ["SNARK", "cryptography", "proofs"]

[workspace]
members = [
"crates/jolt-poly",
"crates/jolt-instructions",
"crates/jolt-transcript",
"crates/jolt-profiling",
"crates/jolt-field",
"jolt-core",
"tracer",
"common",
Expand Down Expand Up @@ -149,6 +154,58 @@ allocative = { git = "https://github.com/facebookexperimental/allocative", rev =
[workspace.metadata.cargo-machete]
ignored = ["jolt-sdk"]


[workspace.lints.clippy]
pedantic = { level = "warn", priority = -1 }

# Pedantic overrides — suppressed because they're too noisy for math-heavy ZK code
missing_errors_doc = "allow"
missing_panics_doc = "allow"
must_use_candidate = "allow"
doc_markdown = "allow"
similar_names = "allow"
too_many_lines = "allow"
module_name_repetitions = "allow"
struct_excessive_bools = "allow"
fn_params_excessive_bools = "allow"
items_after_statements = "allow"
uninlined_format_args = "allow"
return_self_not_must_use = "allow"
default_trait_access = "allow"
match_same_arms = "allow"
manual_let_else = "allow"
used_underscore_binding = "allow"
no_effect_underscore_binding = "allow"
needless_pass_by_value = "allow"
trivially_copy_pass_by_ref = "allow"
redundant_closure_for_method_calls = "allow"
unnecessary_wraps = "allow"
if_not_else = "allow"

# Numeric/math code — ZK cryptography uses these patterns pervasively
float_cmp = "allow"
many_single_char_names = "allow"
wildcard_imports = "allow"
inline_always = "allow"
checked_conversions = "allow"

# Cast lints — field arithmetic requires intentional casting
cast_possible_truncation = "allow"
cast_sign_loss = "allow"
cast_precision_loss = "allow"
cast_possible_wrap = "allow"
cast_lossless = "allow"

# Code quality — hard denies to catch AI-generated slop
dbg_macro = "deny"
todo = "deny"
unimplemented = "deny"
print_stdout = "deny"
print_stderr = "deny"
undocumented_unsafe_blocks = "deny"

[workspace.lints.rust]
unused_results = "warn"
[workspace.dependencies]
# Cryptography and Math
ark-bn254 = { git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout", default-features = false }
Expand All @@ -165,6 +222,8 @@ sha2 = "0.10"
sha3 = "0.10.8"
blake2 = "0.10"
blake3 = { version = "1.5.0" }
light-poseidon = "0.4"
digest = "0.10"
jolt-optimizations = { git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout" }
dory = { package = "dory-pcs", version = "0.3.0", features = ["backends", "cache", "disk-persistence"] }

Expand Down Expand Up @@ -214,7 +273,7 @@ memory-stats = { version = "1.0.0", features = ["always_use_statm"] }
rayon = { version = "^1.8.0" }

# Tracing and Profiling
tracing = { version = "0.1.37", default-features = false }
tracing = { version = "0.1.37", default-features = false, features = ["attributes"] }
tracing-chrome = "0.7.1"
tracing-subscriber = { version = "0.3.20", features = ["fmt", "env-filter"] }
inferno = { version = "0.12.3" }
Expand Down
38 changes: 38 additions & 0 deletions crates/jolt-field/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[package]
name = "jolt-field"
version = "0.1.0"
authors = ["Jolt Contributors"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Field abstractions for the Jolt zkVM"
repository = "https://github.com/a16z/jolt"
keywords = ["SNARK", "cryptography", "finite-fields", "BN254"]
categories = ["cryptography"]

[lints]
workspace = true

[dependencies]
ark-ff = { workspace = true }
ark-serialize = { workspace = true }
ark-bn254 = { workspace = true, features = ["scalar_field"], optional = true }
num-traits = { workspace = true }
serde = { workspace = true, features = ["derive"] }
allocative = { workspace = true, optional = true }
dory = { workspace = true, optional = true }
rand = { workspace = true }
rand_core = { workspace = true }

[features]
default = ["bn254"]
bn254 = ["dep:ark-bn254"]
dory-pcs = ["dep:dory", "bn254"]

[dev-dependencies]
ark-std = { workspace = true }
rand_chacha = { workspace = true }
criterion = { workspace = true }

[[bench]]
name = "field_arith"
harness = false
50 changes: 50 additions & 0 deletions crates/jolt-field/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# jolt-field

Field abstractions for the Jolt zkVM.

Part of the [Jolt](https://github.com/a16z/jolt) zkVM.

## Overview

This crate defines the core `Field` trait and associated types used throughout the Jolt proving system. It provides a backend-agnostic interface over prime-order scalar fields, currently implemented for the BN254 scalar field (`Fr`).

The crate also provides optimized arithmetic primitives -- wide accumulators and fused multiply-add -- all tuned for the BN254 field.

## Public API

### Core Traits

- **`Field`** -- Prime field element abstraction. Elements are `Copy`, thread-safe, serializable. Provides conversions from integer types, random sampling, square, inverse, and bit-width queries.
- **`OptimizedMul<Rhs, Output>`** -- Multiplication with fast-path short-circuits for zero and one.
- **`MontgomeryConstants`** -- Trait providing Montgomery form constants (modulus limbs, R^2, inverse) for wide arithmetic backends.

### Accumulation

- **`FieldAccumulator`** -- Trait for accumulators that defer modular reduction across multiply-add steps.
- **`NaiveAccumulator`** -- Simple accumulator using standard field arithmetic (no deferred reduction).
- **`WideAccumulator`** -- BN254-specific accumulator using unreduced wide limbs for deferred reduction.
- **`Limbs<N>`** -- Fixed-size limb array type used by wide arithmetic internals.

### Types

- **`Fr`** -- BN254 scalar field element (`#[repr(transparent)]` newtype over `ark_bn254::Fr`).

### Signed Integer Types

The `signed` module provides fixed-width signed big integers (`S64`, `S128`, `S192`, `S256`, etc.) with truncating arithmetic.

## Dependency Position

`jolt-field` is a **leaf crate** with no internal Jolt dependencies. All other `jolt-*` crates depend on it.

## Feature Flags

| Flag | Default | Description |
|------|---------|-------------|
| `bn254` | **Yes** | Enable BN254 scalar field implementation |
| `dory-pcs` | No | Enable Dory PCS interop (implies `bn254`) |
| `allocative` | No | Enable memory profiling via the `allocative` crate |

## License

MIT OR Apache-2.0
59 changes: 59 additions & 0 deletions crates/jolt-field/benches/field_arith.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#![allow(unused_results)]

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use jolt_field::{Field, Fr};
use rand_chacha::ChaCha20Rng;
use rand_core::SeedableRng;

fn bench_field_mul(c: &mut Criterion) {
let mut rng = ChaCha20Rng::seed_from_u64(0);
let a: Fr = Field::random(&mut rng);
let b: Fr = Field::random(&mut rng);

c.bench_function("Fr * Fr", |bench| {
bench.iter(|| black_box(a) * black_box(b));
});
}

fn bench_mul_u64(c: &mut Criterion) {
let mut rng = ChaCha20Rng::seed_from_u64(1);
let a: Fr = Field::random(&mut rng);
let n = 0xDEAD_BEEF_CAFE_BABEu64;

c.bench_function("Fr::mul_u64", |bench| {
bench.iter(|| <Fr as Field>::mul_u64(black_box(&a), black_box(n)));
});
}

fn bench_mul_u128(c: &mut Criterion) {
let mut rng = ChaCha20Rng::seed_from_u64(2);
let a: Fr = Field::random(&mut rng);
let n = 0xDEAD_BEEF_CAFE_BABE_1234_5678_9ABC_DEF0u128;

c.bench_function("Fr::mul_u128", |bench| {
bench.iter(|| <Fr as Field>::mul_u128(black_box(&a), black_box(n)));
});
}

fn bench_to_from_bytes(c: &mut Criterion) {
let mut rng = ChaCha20Rng::seed_from_u64(4);
let a: Fr = Field::random(&mut rng);
let bytes = a.to_bytes();

c.bench_function("Fr::to_bytes", |bench| {
bench.iter(|| black_box(a).to_bytes());
});

c.bench_function("Fr::from_bytes", |bench| {
bench.iter(|| <Fr as Field>::from_bytes(black_box(&bytes)));
});
}

criterion_group!(
benches,
bench_field_mul,
bench_mul_u64,
bench_mul_u128,
bench_to_from_bytes,
);
criterion_main!(benches);
Loading
Loading