diff --git a/Cargo.toml b/Cargo.toml index a5a28cfd8..ac14deab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ keywords = ["SNARK", "cryptography", "proofs"] [workspace] members = [ + "crates/jolt-crypto", "crates/jolt-poly", "crates/jolt-instructions", "crates/jolt-transcript", diff --git a/crates/jolt-crypto/Cargo.toml b/crates/jolt-crypto/Cargo.toml new file mode 100644 index 000000000..14d8cf066 --- /dev/null +++ b/crates/jolt-crypto/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "jolt-crypto" +version = "0.1.0" +authors = ["Jolt Contributors"] +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/a16z/jolt" +description = "Backend-agnostic cryptographic group and commitment primitives for Jolt" +keywords = ["cryptography", "zero-knowledge", "commitment", "elliptic-curve", "zkvm"] +categories = ["cryptography"] + +[lints] +workspace = true + +[features] +default = ["bn254"] +bn254 = ["dep:ark-bn254", "dep:ark-ec", "dep:ark-ff", "dep:ark-serialize", "dep:ark-std", "dep:num-bigint", "dep:num-integer", "dep:num-traits", "dep:rayon"] +dory-pcs = ["dep:dory", "bn254", "jolt-field/dory-pcs"] + +[dependencies] +jolt-field = { path = "../jolt-field" } +jolt-transcript = { path = "../jolt-transcript" } +tracing.workspace = true +serde = { workspace = true, features = ["derive", "alloc"] } +rand_core = { workspace = true } +dory = { workspace = true, optional = true } + +# Arkworks — BN254 backend (internal, gated behind `bn254` feature) +ark-bn254 = { workspace = true, features = ["curve"], optional = true } +ark-ec = { workspace = true, optional = true } +ark-ff = { workspace = true, optional = true } +ark-serialize = { workspace = true, optional = true } +ark-std = { workspace = true, optional = true } +num-bigint = { workspace = true, optional = true } +num-integer = { workspace = true, optional = true } +num-traits = { workspace = true, optional = true } +rayon = { workspace = true, optional = true } + +[dev-dependencies] +ark-std = { workspace = true } +rand_chacha = { workspace = true } +serde_json = { workspace = true, features = ["std"] } +bincode = { workspace = true } +criterion = { workspace = true } + +[[bench]] +name = "crypto" +harness = false diff --git a/crates/jolt-crypto/README.md b/crates/jolt-crypto/README.md new file mode 100644 index 000000000..c383cc130 --- /dev/null +++ b/crates/jolt-crypto/README.md @@ -0,0 +1,66 @@ +# jolt-crypto + +Backend-agnostic cryptographic group and commitment primitives for the Jolt zkVM. + +Part of the [Jolt](https://github.com/a16z/jolt) zkVM. + +## Overview + +This crate defines the core group abstractions (`JoltGroup`, `PairingGroup`) and commitment trait hierarchy (`Commitment`, `JoltCommitment`, `HomomorphicCommitment`) used by the Jolt proving system. It provides a backend-agnostic interface -- the BN254 implementation wraps arkworks internally, but no arkworks types appear in the public API. + +## Commitment Hierarchy + +``` +Commitment (base: just the Output type) + +JoltCommitment (Setup, Commitment types; commit, verify) + +HomomorphicCommitment (linear_combine for Nova folding) +``` + +| Scheme | Message | Output | +|--------|---------|--------| +| Pedersen | `F` (field) | `G` (group) | +| Dory tier-1 | `F` (field) | `G1` | +| Dory tier-2 | `G1` (commitments) | `GT` | + +`Commitment` is the base trait shared with `jolt-openings::CommitmentScheme` -- both extend it, sharing the `Output` associated type. + +## Public API + +### Core Traits + +- **`Commitment`** -- Base trait defining `type Output` with standard bounds. +- **`JoltCommitment`** -- Backend-agnostic vector commitment with `Setup`, `Commitment` associated types. Methods: `capacity()`, `commit()`, `verify()`. Commit takes a blinding factor. +- **`HomomorphicCommitment`** -- Additive homomorphism: `linear_combine(c1, c2, scalar) = c1 + scalar * c2`. Blanket-implemented for `JoltGroup`. +- **`JoltGroup`** -- Cryptographic group with additive notation. Provides `identity()`, `is_identity()`, `double()`, `scalar_mul()`, `msm()`. +- **`PairingGroup`** -- Pairing-friendly group. Associates `ScalarField`, `G1`, `G2`, `GT` (all `JoltGroup`), provides `pairing()` and `multi_pairing()`. + +### Pedersen Commitment + +- **`Pedersen`** -- Generic Pedersen vector commitment. Implements `JoltCommitment`. +- **`PedersenSetup`** -- Setup parameters (generators + blinding generator). + +### BN254 Concrete Types + +- **`Bn254`** -- BN254 pairing curve implementing `PairingGroup`. +- **`Bn254G1`** / **`Bn254G2`** -- G1/G2 group elements implementing `JoltGroup`. +- **`Bn254GT`** -- Target group element (additive notation over `Fq12`). + +## Feature Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `bn254` | **Yes** | Enable BN254 backend via arkworks | +| `dory-pcs` | No | Enable Dory PCS interop (implies `bn254`) | + +## Dependency Position + +``` +jolt-field <- jolt-crypto <- jolt-openings, jolt-sumcheck, jolt-dory, jolt-blindfold + jolt-transcript -> +``` + +## License + +MIT diff --git a/crates/jolt-crypto/REVIEW.md b/crates/jolt-crypto/REVIEW.md new file mode 100644 index 000000000..cbb87f5ee --- /dev/null +++ b/crates/jolt-crypto/REVIEW.md @@ -0,0 +1,94 @@ +# jolt-crypto Review + +**Crate:** jolt-crypto (Level 2) +**LOC:** 4,969 (was ~5,070 — reduced via G1/G2 macro dedup) +**Baseline:** 0 clippy warnings, 128 tests passing +**Rating:** 8.5/10 + +## Overview + +Elliptic curve and commitment abstractions for Jolt. Provides the `JoltGroup`, +`PairingGroup`, `JoltCommitment`, and `HomomorphicCommitment` trait hierarchy +with a BN254 backend. Includes GLV scalar multiplication, batch affine addition, +Pedersen commitments, and Dory interop bridge. Zero arkworks leakage in the +public API — all arkworks types are behind `#[repr(transparent)]` newtypes. + +**Verdict:** Clean trait design with strong encapsulation. The `#[repr(transparent)]` +newtypes effectively hide arkworks internals. Performance-critical paths (GLV, batch +addition, MSM) are well-optimized. The G1/G2 macro dedup eliminated 300+ lines of +identical boilerplate. Well-tested with 128 tests, 14 benchmarks, and 3 fuzz targets. + +--- + +## Findings + +### [CQ-1.1] G1/G2 ~300 LOC identical boilerplate +**File:** `src/arkworks/bn254/g1.rs`, `g2.rs` +**Severity:** MEDIUM +**Finding:** Both files had 157 LOC of nearly identical operator impls, serde, JoltGroup impl. +**Status:** RESOLVED — Created `impl_jolt_group_wrapper!` macro in `mod.rs`. Both files reduced to 8 LOC. + +### [CQ-2.1] Missing compile-time size assertions for repr(transparent) +**File:** `src/arkworks/bn254/g1.rs`, `g2.rs` +**Severity:** HIGH +**Finding:** Unsafe pointer casts between wrapper and inner types rely on `#[repr(transparent)]` layout guarantee but had no compile-time verification. +**Status:** RESOLVED — Added `const _: () = assert!(size_of::() == size_of::());` inside the macro. + +### [CQ-2.2] No safe into_inner() accessor +**File:** `src/arkworks/bn254/g1.rs`, `g2.rs` +**Severity:** LOW +**Finding:** Downstream crates (jolt-dory) use unsafe transmute to extract the inner arkworks type. A safe accessor would reduce unsafe surface area. +**Status:** RESOLVED — Added `pub fn into_inner(self)` to the macro. Downstream migration (jolt-dory scheme.rs ~15 transmutes) deferred. + +### [CQ-3.1] Clippy warnings in dory_interop +**File:** `src/dory_interop.rs` +**Severity:** LOW +**Finding:** 3 redundant closures, 1 needless borrow. +**Status:** RESOLVED — Fixed all 4 warnings. + +### [CQ-4.1] Missing #[must_use] on pure trait methods +**File:** `src/groups.rs`, `src/commitment.rs` +**Severity:** LOW +**Finding:** Pure methods returning values without side effects should be `#[must_use]`. +**Status:** RESOLVED — Added to JoltGroup, JoltCommitment, HomomorphicCommitment, PairingGroup. + +### [CD-1.1] Internal modules exposed in public API +**File:** `src/arkworks/bn254/mod.rs` +**Severity:** LOW +**Finding:** `glv` and `batch_addition` modules are `pub` but are implementation details. +**Status:** RESOLVED — Added `#[doc(hidden)]`. + +### [CD-2.1] batch_addition precondition undocumented +**File:** `src/arkworks/bn254/batch_addition.rs` +**Severity:** LOW +**Finding:** Entry point has subtle preconditions (in-bounds indices, no equal/inverse pairs). +**Status:** RESOLVED — Added doc comment. + +### [CD-3.1] bincode v1 API usage +**File:** `tests/serialization.rs`, `benches/crypto.rs`, `fuzz/fuzz_targets/*.rs` +**Severity:** MEDIUM +**Finding:** Workspace uses bincode v2 but these files used v1 API. +**Status:** RESOLVED — Migrated all files + fuzz Cargo.toml to bincode v2. + +### [CD-4.1] Cargo.toml metadata incomplete +**File:** `Cargo.toml` +**Severity:** LOW +**Finding:** Missing repository, keywords, categories; license was MIT-only. +**Status:** RESOLVED — Updated to dual MIT OR Apache-2.0, added all metadata. + +### [NIT-1.1] append_to_transcript Vec allocation +**File:** `src/arkworks/bn254/g1.rs` (now in macro) +**Severity:** LOW +**Finding:** `ark_serialize::CanonicalSerialize` allocates a fresh Vec on each call. +**Status:** WONTFIX — Acceptable for now; not on hot path. + +--- + +## Summary + +| Category | Pass | Resolved | Wontfix | Total | +|----------|------|----------|---------|-------| +| CQ | 0 | 4 | 0 | 4 | +| CD | 0 | 4 | 0 | 4 | +| NIT | 0 | 0 | 1 | 1 | +| **Total**| **0**| **8** | **1** | **9** | diff --git a/crates/jolt-crypto/benches/crypto.rs b/crates/jolt-crypto/benches/crypto.rs new file mode 100644 index 000000000..48cb85579 --- /dev/null +++ b/crates/jolt-crypto/benches/crypto.rs @@ -0,0 +1,172 @@ +#![allow(unused_results)] + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +use jolt_crypto::{ + Bn254, Bn254G1, Bn254G2, JoltCommitment, JoltGroup, PairingGroup, Pedersen, PedersenSetup, +}; +use jolt_field::{Field, Fr}; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +fn bench_g1_scalar_mul(c: &mut Criterion) { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let g = Bn254::random_g1(&mut rng); + let s = Fr::random(&mut rng); + + c.bench_function("g1_scalar_mul", |b| { + b.iter(|| g.scalar_mul(&s)); + }); +} + +fn bench_g2_scalar_mul(c: &mut Criterion) { + let mut rng = ChaCha20Rng::seed_from_u64(1); + let g = Bn254::g2_generator(); + let s = Fr::random(&mut rng); + + c.bench_function("g2_scalar_mul", |b| { + b.iter(|| g.scalar_mul(&s)); + }); +} + +fn bench_g1_add(c: &mut Criterion) { + let mut rng = ChaCha20Rng::seed_from_u64(2); + let a = Bn254::random_g1(&mut rng); + let b = Bn254::random_g1(&mut rng); + + c.bench_function("g1_add", |b_| { + b_.iter(|| a + b); + }); +} + +fn bench_g1_double(c: &mut Criterion) { + let mut rng = ChaCha20Rng::seed_from_u64(3); + let a = Bn254::random_g1(&mut rng); + + c.bench_function("g1_double", |b| { + b.iter(|| a.double()); + }); +} + +fn bench_g1_msm(c: &mut Criterion) { + let mut group = c.benchmark_group("g1_msm"); + + for size in [4, 16, 64, 256, 1024] { + let mut rng = ChaCha20Rng::seed_from_u64(10); + let bases: Vec = (0..size).map(|_| Bn254::random_g1(&mut rng)).collect(); + let scalars: Vec = (0..size).map(|_| Fr::random(&mut rng)).collect(); + + group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { + b.iter(|| Bn254G1::msm(&bases, &scalars)); + }); + } + group.finish(); +} + +fn bench_g2_msm(c: &mut Criterion) { + let mut group = c.benchmark_group("g2_msm"); + + for size in [4, 16, 64, 256] { + let mut rng = ChaCha20Rng::seed_from_u64(20); + let g = Bn254::g2_generator(); + let bases: Vec = (0..size) + .map(|i| g.scalar_mul(&Fr::from_u64(i as u64 + 1))) + .collect(); + let scalars: Vec = (0..size).map(|_| Fr::random(&mut rng)).collect(); + + group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { + b.iter(|| Bn254G2::msm(&bases, &scalars)); + }); + } + group.finish(); +} + +fn bench_pairing(c: &mut Criterion) { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + + c.bench_function("pairing", |b| { + b.iter(|| Bn254::pairing(&g1, &g2)); + }); +} + +fn bench_multi_pairing(c: &mut Criterion) { + let mut group = c.benchmark_group("multi_pairing"); + + for size in [2, 4, 8, 16] { + let mut rng = ChaCha20Rng::seed_from_u64(30); + let g1s: Vec = (0..size).map(|_| Bn254::random_g1(&mut rng)).collect(); + let g2 = Bn254::g2_generator(); + let g2s: Vec = (0..size) + .map(|i| g2.scalar_mul(&Fr::from_u64(i as u64 + 1))) + .collect(); + + group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { + b.iter(|| Bn254::multi_pairing(&g1s, &g2s)); + }); + } + group.finish(); +} + +fn bench_pedersen_commit(c: &mut Criterion) { + let mut group = c.benchmark_group("pedersen_commit"); + + for size in [4, 16, 64, 256, 1024] { + let mut rng = ChaCha20Rng::seed_from_u64(40); + let gens: Vec = (0..size).map(|_| Bn254::random_g1(&mut rng)).collect(); + let blinding_gen = Bn254::random_g1(&mut rng); + let setup = PedersenSetup::new(gens, blinding_gen); + let values: Vec = (0..size).map(|_| Fr::random(&mut rng)).collect(); + let blinding = Fr::random(&mut rng); + + group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { + b.iter(|| Pedersen::::commit(&setup, &values, &blinding)); + }); + } + group.finish(); +} + +fn bench_gt_scalar_mul(c: &mut Criterion) { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let gt = Bn254::pairing(&g1, &g2); + let mut rng = ChaCha20Rng::seed_from_u64(50); + let s = Fr::random(&mut rng); + + c.bench_function("gt_scalar_mul", |b| { + b.iter(|| gt.scalar_mul(&s)); + }); +} + +fn bench_g1_serde(c: &mut Criterion) { + let mut rng = ChaCha20Rng::seed_from_u64(60); + let g = Bn254::random_g1(&mut rng); + let config = bincode::config::standard(); + let bytes = bincode::serde::encode_to_vec(g, config).unwrap(); + + c.bench_function("g1_serialize_bincode", |b| { + b.iter(|| bincode::serde::encode_to_vec(g, config).unwrap()); + }); + + c.bench_function("g1_deserialize_bincode", |b| { + b.iter(|| { + bincode::serde::decode_from_slice::(&bytes, config).unwrap(); + }); + }); +} + +criterion_group!( + benches, + bench_g1_scalar_mul, + bench_g2_scalar_mul, + bench_g1_add, + bench_g1_double, + bench_g1_msm, + bench_g2_msm, + bench_pairing, + bench_multi_pairing, + bench_pedersen_commit, + bench_gt_scalar_mul, + bench_g1_serde, +); +criterion_main!(benches); diff --git a/crates/jolt-crypto/fuzz/Cargo.toml b/crates/jolt-crypto/fuzz/Cargo.toml new file mode 100644 index 000000000..e34b81c64 --- /dev/null +++ b/crates/jolt-crypto/fuzz/Cargo.toml @@ -0,0 +1,34 @@ +[workspace] + +[package] +name = "jolt-crypto-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +jolt-crypto = { path = ".." } +jolt-field = { path = "../../jolt-field" } +serde_json = "1" +bincode = { version = "2", features = ["serde"] } +rand_chacha = "0.3" +rand_core = "0.6" + +[[bin]] +name = "deser_group" +path = "fuzz_targets/deser_group.rs" +doc = false + +[[bin]] +name = "group_arith" +path = "fuzz_targets/group_arith.rs" +doc = false + +[[bin]] +name = "pedersen_commit" +path = "fuzz_targets/pedersen_commit.rs" +doc = false diff --git a/crates/jolt-crypto/fuzz/fuzz_targets/deser_group.rs b/crates/jolt-crypto/fuzz/fuzz_targets/deser_group.rs new file mode 100644 index 000000000..db8f223eb --- /dev/null +++ b/crates/jolt-crypto/fuzz/fuzz_targets/deser_group.rs @@ -0,0 +1,19 @@ +#![no_main] +use jolt_crypto::{Bn254G1, Bn254G2, Bn254GT, PedersenSetup}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let config = bincode::config::standard(); + + let _ = serde_json::from_slice::(data); + let _ = bincode::serde::decode_from_slice::(data, config); + + let _ = serde_json::from_slice::(data); + let _ = bincode::serde::decode_from_slice::(data, config); + + let _ = serde_json::from_slice::(data); + let _ = bincode::serde::decode_from_slice::(data, config); + + let _ = serde_json::from_slice::>(data); + let _ = bincode::serde::decode_from_slice::, _>(data, config); +}); diff --git a/crates/jolt-crypto/fuzz/fuzz_targets/group_arith.rs b/crates/jolt-crypto/fuzz/fuzz_targets/group_arith.rs new file mode 100644 index 000000000..85fb39168 --- /dev/null +++ b/crates/jolt-crypto/fuzz/fuzz_targets/group_arith.rs @@ -0,0 +1,42 @@ +#![no_main] +use jolt_crypto::{Bn254G1, JoltGroup}; +use jolt_field::{Field, Fr}; +use libfuzzer_sys::fuzz_target; + +fn parse_input(data: &[u8]) -> Option<(Fr, Fr, Bn254G1)> { + if data.len() < 64 { + return None; + } + let s1 = Fr::from_bytes(&data[..32]); + let s2 = Fr::from_bytes(&data[32..64]); + let g = Bn254G1::identity().scalar_mul(&Fr::from_u64(1)); + let p = g.scalar_mul(&s1); + Some((s1, s2, p)) +} + +fuzz_target!(|data: &[u8]| { + let Some((s1, s2, p)) = parse_input(data) else { + return; + }; + + assert_eq!(p + Bn254G1::identity(), p); + assert_eq!(Bn254G1::identity() + p, p); + + assert_eq!(p + (-p), Bn254G1::identity()); + + assert_eq!(p.double(), p + p); + + let sum_scalar = s1 + s2; + let lhs = p.scalar_mul(&sum_scalar); + let rhs = p.scalar_mul(&s1) + p.scalar_mul(&s2); + assert_eq!(lhs, rhs, "scalar mul distributivity failed"); + + assert_eq!(Bn254G1::msm(&[p], &[s2]), p.scalar_mul(&s2)); + + let config = bincode::config::standard(); + if let Ok(bytes) = bincode::serde::encode_to_vec(&p, config) { + let (recovered, _): (Bn254G1, _) = + bincode::serde::decode_from_slice(&bytes, config).expect("round-trip must succeed"); + assert_eq!(p, recovered); + } +}); diff --git a/crates/jolt-crypto/fuzz/fuzz_targets/pedersen_commit.rs b/crates/jolt-crypto/fuzz/fuzz_targets/pedersen_commit.rs new file mode 100644 index 000000000..3c0cf4e1f --- /dev/null +++ b/crates/jolt-crypto/fuzz/fuzz_targets/pedersen_commit.rs @@ -0,0 +1,47 @@ +#![no_main] +use jolt_crypto::{Bn254, Bn254G1, JoltCommitment, JoltGroup, Pedersen, PedersenSetup}; +use jolt_field::{Field, Fr}; +use libfuzzer_sys::fuzz_target; + +/// Fixed small setup (4 generators) — deterministic so we don't waste fuzzer +/// entropy on setup. +fn fixed_setup() -> PedersenSetup { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + let mut rng = ChaCha20Rng::seed_from_u64(0xf022); + let gens: Vec = (0..4).map(|_| Bn254::random_g1(&mut rng)).collect(); + let blinding = Bn254::random_g1(&mut rng); + PedersenSetup::new(gens, blinding) +} + +fuzz_target!(|data: &[u8]| { + // Need 5 * 32 = 160 bytes: 4 values + 1 blinding. + if data.len() < 160 { + return; + } + + let setup = fixed_setup(); + + let values: Vec = (0..4) + .map(|i| Fr::from_bytes(&data[i * 32..(i + 1) * 32])) + .collect(); + let blinding = Fr::from_bytes(&data[128..160]); + + // Commit-verify round-trip + let c = Pedersen::::commit(&setup, &values, &blinding); + assert!( + Pedersen::::verify(&setup, &c, &values, &blinding), + "commit-verify round-trip failed" + ); + + // Homomorphism: C(v, r) + C(0, 0) == C(v, r) + let zeros = vec![Fr::from_u64(0); 4]; + let zero_blind = Fr::from_u64(0); + let c_zero = Pedersen::::commit(&setup, &zeros, &zero_blind); + assert_eq!(c + c_zero, c, "adding zero commitment should be identity"); + + // Commitment to zero values with blinding should equal blinding_gen * blinding + let c_blind_only = Pedersen::::commit(&setup, &zeros, &blinding); + let expected = setup.blinding_generator.scalar_mul(&blinding); + assert_eq!(c_blind_only, expected, "zero-value commitment mismatch"); +}); diff --git a/crates/jolt-crypto/fuzz/rust-toolchain.toml b/crates/jolt-crypto/fuzz/rust-toolchain.toml new file mode 100644 index 000000000..5d56faf9a --- /dev/null +++ b/crates/jolt-crypto/fuzz/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/crates/jolt-crypto/src/arkworks/bn254/batch_addition.rs b/crates/jolt-crypto/src/arkworks/bn254/batch_addition.rs new file mode 100644 index 000000000..2be08783f --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/batch_addition.rs @@ -0,0 +1,237 @@ +//! Batch affine addition for BN254 G1 using Montgomery's inversion trick. + +use ark_bn254::G1Affine; +use ark_ec::CurveGroup; +use rayon::prelude::*; + +use super::Bn254G1; + +/// Performs batch addition of G1 affine points using Montgomery's inversion trick. +#[cfg(test)] +fn batch_g1_additions_affine(bases: &[G1Affine], indices: &[usize]) -> G1Affine { + if indices.is_empty() { + return G1Affine::identity(); + } + + if indices.len() == 1 { + return bases[indices[0]]; + } + + let mut points: Vec = Vec::with_capacity(indices.len()); + points.extend(indices.iter().map(|&i| bases[i])); + + while points.len() > 1 { + let current_len = points.len(); + let pairs_count = current_len / 2; + let has_odd = current_len % 2 == 1; + + let denominators: Vec<_> = (0..pairs_count) + .into_par_iter() + .map(|i| { + let p1 = points[i * 2]; + let p2 = points[i * 2 + 1]; + p2.x - p1.x + }) + .collect(); + + let mut inverses = denominators; + ark_ff::fields::batch_inversion(&mut inverses); + + let mut new_points: Vec = (0..pairs_count) + .into_par_iter() + .zip(inverses.par_iter()) + .map(|(i, inv)| { + let p1 = points[i * 2]; + let p2 = points[i * 2 + 1]; + let lambda = (p2.y - p1.y) * inv; + let x3 = lambda * lambda - p1.x - p2.x; + let y3 = lambda * (p1.x - x3) - p1.y; + G1Affine::new_unchecked(x3, y3) + }) + .collect(); + + if has_odd { + new_points.push(points[current_len - 1]); + } + + points = new_points; + } + + points[0] +} + +/// Performs multiple batch additions of G1 points in parallel, +/// sharing a single batch inversion across all sets per round. +/// +/// Takes `Bn254G1` bases (converted to affine internally) and index sets. +/// Returns one `Bn254G1` per index set — the sum of the selected points. +/// +/// All indices within each set must be in-bounds (`< bases.len()`), and +/// no two points in the same pair may be equal or inverse (the batch +/// inversion denominator `p2.x - p1.x` would be zero). +pub fn batch_g1_additions_multi(bases: &[Bn254G1], indices_sets: &[Vec]) -> Vec { + if indices_sets.is_empty() { + return vec![]; + } + + // SAFETY: Bn254G1 is #[repr(transparent)] over G1Projective — identical layout. + let projective: &[ark_bn254::G1Projective] = unsafe { + std::slice::from_raw_parts( + bases.as_ptr().cast::(), + bases.len(), + ) + }; + let affines = ark_bn254::G1Projective::normalize_batch(projective); + + batch_g1_additions_multi_affine_inner(&affines, indices_sets) + .into_iter() + .map(|a| { + let proj: ark_bn254::G1Projective = a.into(); + Bn254G1::from(proj) + }) + .collect() +} + +/// Same as [`batch_g1_additions_multi`] but operates directly on affine points. +/// +/// Avoids the projective → affine normalization when callers already have affine bases. +/// Returns one `G1Affine` per index set. +pub fn batch_g1_additions_multi_affine( + bases: &[G1Affine], + indices_sets: &[Vec], +) -> Vec { + batch_g1_additions_multi_affine_inner(bases, indices_sets) +} + +fn batch_g1_additions_multi_affine_inner( + affines: &[G1Affine], + indices_sets: &[Vec], +) -> Vec { + if indices_sets.is_empty() { + return vec![]; + } + + let mut working_sets: Vec> = indices_sets + .par_iter() + .map(|indices| { + if indices.is_empty() { + vec![G1Affine::identity()] + } else if indices.len() == 1 { + vec![affines[indices[0]]] + } else { + indices.iter().map(|&i| affines[i]).collect() + } + }) + .collect(); + + loop { + let total_pairs: usize = working_sets.iter().map(|set| set.len() / 2).sum(); + + if total_pairs == 0 { + break; + } + + let mut all_denominators = Vec::with_capacity(total_pairs); + let mut pair_info = Vec::with_capacity(total_pairs); + + for (set_idx, set) in working_sets.iter().enumerate() { + let pairs_in_set = set.len() / 2; + for pair_idx in 0..pairs_in_set { + let p1 = set[pair_idx * 2]; + let p2 = set[pair_idx * 2 + 1]; + all_denominators.push(p2.x - p1.x); + pair_info.push((set_idx, pair_idx)); + } + } + + let mut inverses = all_denominators; + ark_ff::fields::batch_inversion(&mut inverses); + + let mut new_working_sets: Vec> = working_sets + .iter() + .map(|set| Vec::with_capacity(set.len().div_ceil(2))) + .collect(); + + for ((set_idx, pair_idx), inv) in pair_info.iter().zip(inverses.iter()) { + let set = &working_sets[*set_idx]; + let p1 = set[*pair_idx * 2]; + let p2 = set[*pair_idx * 2 + 1]; + let lambda = (p2.y - p1.y) * inv; + let x3 = lambda * lambda - p1.x - p2.x; + let y3 = lambda * (p1.x - x3) - p1.y; + new_working_sets[*set_idx].push(G1Affine::new_unchecked(x3, y3)); + } + + for (set_idx, set) in working_sets.iter().enumerate() { + if set.len() % 2 == 1 { + new_working_sets[set_idx].push(set[set.len() - 1]); + } + } + + working_sets = new_working_sets; + } + + working_sets.into_iter().map(|set| set[0]).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_ec::AffineRepr; + use ark_std::rand::RngCore; + use ark_std::UniformRand; + + #[test] + fn test_batch_addition_correctness() { + let mut rng = ark_std::test_rng(); + let bases: Vec = (0..10).map(|_| G1Affine::rand(&mut rng)).collect(); + let indices = vec![2, 3, 4, 5, 6, 7]; + + let batch_result = batch_g1_additions_affine(&bases, &indices); + + let mut expected = G1Affine::identity(); + for &idx in &indices { + expected = (expected + bases[idx]).into(); + } + assert_eq!(batch_result, expected); + } + + #[test] + fn test_empty_indices() { + let bases: Vec = vec![G1Affine::generator(); 5]; + let result = batch_g1_additions_affine(&bases, &[]); + assert_eq!(result, G1Affine::identity()); + } + + #[test] + fn test_single_index() { + let mut rng = ark_std::test_rng(); + let bases: Vec = (0..5).map(|_| G1Affine::rand(&mut rng)).collect(); + let result = batch_g1_additions_affine(&bases, &[2]); + assert_eq!(result, bases[2]); + } + + #[test] + fn test_batch_additions_multi() { + let mut rng = ark_std::test_rng(); + let base_size = 1000; + let num_batches = 10; + + let projectiles: Vec = (0..base_size) + .map(|_| ark_bn254::G1Projective::rand(&mut rng)) + .collect(); + let jolt_bases: Vec = projectiles.into_iter().map(Bn254G1::from).collect(); + + let indices_sets: Vec> = (0..num_batches) + .map(|_| { + let size = (rng.next_u64() as usize) % 50 + 1; + (0..size) + .map(|_| (rng.next_u64() as usize) % base_size) + .collect() + }) + .collect(); + + let results = batch_g1_additions_multi(&jolt_bases, &indices_sets); + assert_eq!(results.len(), num_batches); + } +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/g1.rs b/crates/jolt-crypto/src/arkworks/bn254/g1.rs new file mode 100644 index 000000000..aa1292138 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/g1.rs @@ -0,0 +1,8 @@ +use ark_bn254::{G1Affine, G1Projective}; + +super::impl_jolt_group_wrapper!( + Bn254G1, + G1Projective, + G1Affine, + "BN254 G1 group element (projective coordinates)." +); diff --git a/crates/jolt-crypto/src/arkworks/bn254/g2.rs b/crates/jolt-crypto/src/arkworks/bn254/g2.rs new file mode 100644 index 000000000..457215eba --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/g2.rs @@ -0,0 +1,8 @@ +use ark_bn254::{G2Affine, G2Projective}; + +super::impl_jolt_group_wrapper!( + Bn254G2, + G2Projective, + G2Affine, + "BN254 G2 group element (projective coordinates)." +); diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/constants.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/constants.rs new file mode 100644 index 000000000..12d1daaf4 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/constants.rs @@ -0,0 +1,62 @@ +#[allow( + clippy::unreadable_literal, + clippy::type_complexity, + clippy::large_const_arrays +)] +mod power_of_2_table { + include!("power_of_2_decompositions.rs"); +} +pub use power_of_2_table::POWER_OF_2_DECOMPOSITIONS; + +use ark_bn254::Fq2; +use ark_ff::MontFp; + +pub struct FrobeniusCoefficients { + pub psi1_coef2: Fq2, + pub psi1_coef3: Fq2, + pub psi2_coef2: Fq2, + pub psi2_coef3: Fq2, + pub psi3_coef2: Fq2, + pub psi3_coef3: Fq2, +} + +pub fn get_frobenius_coefficients() -> FrobeniusCoefficients { + FrobeniusCoefficients { + psi1_coef2: Fq2::new( + MontFp!( + "21575463638280843010398324269430826099269044274347216827212613867836435027261" + ), + MontFp!( + "10307601595873709700152284273816112264069230130616436755625194854815875713954" + ), + ), + psi1_coef3: Fq2::new( + MontFp!("2821565182194536844548159561693502659359617185244120367078079554186484126554"), + MontFp!("3505843767911556378687030309984248845540243509899259641013678093033130930403"), + ), + psi2_coef2: Fq2::new( + MontFp!( + "21888242871839275220042445260109153167277707414472061641714758635765020556616" + ), + MontFp!("0"), + ), + psi2_coef3: Fq2::new( + MontFp!( + "21888242871839275222246405745257275088696311157297823662689037894645226208582" + ), + MontFp!("0"), + ), + psi3_coef2: Fq2::new( + MontFp!("3772000881919853776433695186713858239009073593817195771773381919316419345261"), + MontFp!("2236595495967245188281701248203181795121068902605861227855261137820944008926"), + ), + psi3_coef3: Fq2::new( + MontFp!( + "19066677689644738377698246183563772429336693972053703295610958340458742082029" + ), + MontFp!( + "18382399103927718843559375435273026243156067647398564021675359801612095278180" + ), + ), + } +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/decomp_2d.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/decomp_2d.rs new file mode 100644 index 000000000..55d55b38d --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/decomp_2d.rs @@ -0,0 +1,88 @@ +//! 2D GLV scalar decomposition for BN254 G1. +//! +//! Decomposes a scalar `k` into `k = k0 + k1 * lambda (mod n)` where `lambda` +//! is the GLV endomorphism eigenvalue, halving the bit-length of each component. + +use ark_bn254::{Fq, Fr, G1Projective}; +use ark_ff::{BigInteger, MontFp, PrimeField}; +use num_bigint::{BigInt, BigUint, Sign}; +use num_integer::Integer; +use num_traits::{One, Signed}; +use std::ops::AddAssign; + +/// GLV endomorphism coefficient for BN254 G1: `β` such that `[λ]P = (β·x, y)` +const ENDO_COEFF: Fq = + MontFp!("21888242871839275220042445260109153167277707414472061641714758635765020556616"); + +/// Lattice coefficients for the BN254 GLV decomposition +const SCALAR_DECOMP_COEFFS: [(bool, ::BigInt); 4] = [ + ( + false, + ark_ff::BigInt!("147946756881789319000765030803803410728"), + ), + (true, ark_ff::BigInt!("9931322734385697763")), + (false, ark_ff::BigInt!("9931322734385697763")), + ( + false, + ark_ff::BigInt!("147946756881789319010696353538189108491"), + ), +]; + +/// Decompose a BN254 scalar into two ~128-bit components via GLV lattice reduction. +/// Returns (coefficients, signs) where `signs[i]` = true means positive. +pub fn decompose_scalar_2d(scalar: Fr) -> ([::BigInt; 2], [bool; 2]) { + let scalar_bytes = scalar.into_bigint().to_bytes_be(); + let scalar_bigint = BigInt::from_bytes_be(Sign::Plus, &scalar_bytes); + + let coeff_bigints: [BigInt; 4] = SCALAR_DECOMP_COEFFS.map(|x| { + let bytes = x.1.to_bytes_be(); + let sign = if x.0 { Sign::Plus } else { Sign::Minus }; + BigInt::from_bytes_be(sign, &bytes) + }); + + let [n11, n12, n21, n22] = coeff_bigints; + + let r_bytes = Fr::MODULUS.to_bytes_be(); + let r = BigInt::from_bytes_be(Sign::Plus, &r_bytes); + + // β = (k·n22, -k·n12) / r + let beta_1 = { + let (mut div, rem) = (&scalar_bigint * &n22).div_rem(&r); + if (&rem + &rem) > r { + div.add_assign(BigInt::one()); + } + div + }; + let beta_2 = { + let (mut div, rem) = (&scalar_bigint * &(-&n12)).div_rem(&r); + if (&rem + &rem) > r { + div.add_assign(BigInt::one()); + } + div + }; + + // b = β · N + let b1 = &beta_1 * &n11 + &beta_2 * &n21; + let b2 = &beta_1 * &n12 + &beta_2 * &n22; + + let k1 = &scalar_bigint - b1; + let k1_abs = BigUint::try_from(k1.abs()).unwrap(); + + let k2 = -b2; + let k2_abs = BigUint::try_from(k2.abs()).unwrap(); + + let k1_fr = Fr::from(k1_abs); + let k2_fr = Fr::from(k2_abs); + + let k_bigint = [k1_fr.into_bigint(), k2_fr.into_bigint()]; + let signs = [k1.sign() == Sign::Plus, k2.sign() == Sign::Plus]; + + (k_bigint, signs) +} + +/// Apply the GLV endomorphism to a G1 point: (x, y) → (β·x, y) +pub fn glv_endomorphism(point: &G1Projective) -> G1Projective { + let mut res = *point; + res.x *= ENDO_COEFF; + res +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/decomp_4d.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/decomp_4d.rs new file mode 100644 index 000000000..24b1ab563 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/decomp_4d.rs @@ -0,0 +1,104 @@ +//! 4D GLV scalar decomposition for BN254 G2. +//! +//! Uses a precomputed lookup table of power-of-2 decompositions to split a scalar +//! into four ~64-bit components for the Frobenius-based 4D GLV multiplication. + +use super::constants::POWER_OF_2_DECOMPOSITIONS; +use ark_bn254::Fr; +use ark_ff::{BigInteger, PrimeField}; +use num_bigint::{BigInt, Sign}; + +fn fr_to_bigint(fr: Fr) -> BigInt { + let bytes = fr.into_bigint().to_bytes_be(); + BigInt::from_bytes_be(Sign::Plus, &bytes) +} + +/// Table-based 4-dimensional scalar decomposition. +/// Decomposes k into (k0, k1, k2, k3) such that +/// k ≡ k0 + k1·λ + k2·λ² + k3·λ³ (mod r). +/// Each coefficient is at most ~66 bits. +fn decompose_scalar_table_based(scalar: &BigInt) -> ([u128; 4], [bool; 4]) { + let mut k0 = 0u128; + let mut k1 = 0u128; + let mut k2 = 0u128; + let mut k3 = 0u128; + + let mut temp_scalar = scalar.clone(); + let mut bit_position = 0; + + while temp_scalar > BigInt::from(0) && bit_position < 254 { + if &temp_scalar & BigInt::from(1) == BigInt::from(1) { + let (decomp_k0, decomp_k1, decomp_k2, decomp_k3, neg0, neg1, neg2, neg3) = + POWER_OF_2_DECOMPOSITIONS[bit_position]; + + if neg0 { + k0 = k0.wrapping_sub(decomp_k0); + } else { + k0 = k0.wrapping_add(decomp_k0); + } + if neg1 { + k1 = k1.wrapping_sub(decomp_k1); + } else { + k1 = k1.wrapping_add(decomp_k1); + } + if neg2 { + k2 = k2.wrapping_sub(decomp_k2); + } else { + k2 = k2.wrapping_add(decomp_k2); + } + if neg3 { + k3 = k3.wrapping_sub(decomp_k3); + } else { + k3 = k3.wrapping_add(decomp_k3); + } + } + + temp_scalar >>= 1; + bit_position += 1; + } + + let (final_k0, neg_flag0) = if (k0 as i128) < 0 { + (k0.wrapping_neg(), true) + } else { + (k0, false) + }; + + let (final_k1, neg_flag1) = if (k1 as i128) < 0 { + (k1.wrapping_neg(), true) + } else { + (k1, false) + }; + + let (final_k2, neg_flag2) = if (k2 as i128) < 0 { + (k2.wrapping_neg(), true) + } else { + (k2, false) + }; + + let (final_k3, neg_flag3) = if (k3 as i128) < 0 { + (k3.wrapping_neg(), true) + } else { + (k3, false) + }; + + ( + [final_k0, final_k1, final_k2, final_k3], + [neg_flag0, neg_flag1, neg_flag2, neg_flag3], + ) +} + +/// Decompose a BN254 scalar for 4D GLV multiplication (G2). +/// Returns coefficients as `BigInt` and their sign flags. +pub fn decompose_scalar_4d(scalar: Fr) -> ([::BigInt; 4], [bool; 4]) { + let scalar_bigint = fr_to_bigint(scalar); + let (coeffs_u128, signs) = decompose_scalar_table_based(&scalar_bigint); + + let coeffs = [ + Fr::from(coeffs_u128[0]).into_bigint(), + Fr::from(coeffs_u128[1]).into_bigint(), + Fr::from(coeffs_u128[2]).into_bigint(), + Fr::from(coeffs_u128[3]).into_bigint(), + ]; + + (coeffs, signs) +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/dory_g1.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/dory_g1.rs new file mode 100644 index 000000000..2aa44b87f --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/dory_g1.rs @@ -0,0 +1,41 @@ +//! Vector-scalar operations on G1 using 2D GLV, for Dory inner-product argument rounds. + +use ark_bn254::{Fr, G1Projective}; +use rayon::prelude::*; + +use super::decomp_2d::{decompose_scalar_2d, glv_endomorphism}; +use super::glv_two::shamir_glv_mul_2d; + +/// `v[i] += scalar * generators[i]`, using 2D GLV decomposition. +pub fn vector_add_scalar_mul_g1_online( + v: &mut [G1Projective], + generators: &[G1Projective], + scalar: Fr, +) { + assert_eq!(v.len(), generators.len()); + let (coeffs, signs) = decompose_scalar_2d(scalar); + + v.par_iter_mut() + .zip(generators.par_iter()) + .for_each(|(vi, gen)| { + let bases = [*gen, glv_endomorphism(gen)]; + *vi += shamir_glv_mul_2d(&bases, &coeffs, &signs); + }); +} + +/// `v[i] = scalar * v[i] + gamma[i]`, using 2D GLV decomposition. +pub fn vector_scalar_mul_add_gamma_g1_online( + v: &mut [G1Projective], + scalar: Fr, + gamma: &[G1Projective], +) { + assert_eq!(v.len(), gamma.len()); + let (coeffs, signs) = decompose_scalar_2d(scalar); + + v.par_iter_mut() + .zip(gamma.par_iter()) + .for_each(|(vi, &gamma_i)| { + let bases = [*vi, glv_endomorphism(vi)]; + *vi = shamir_glv_mul_2d(&bases, &coeffs, &signs) + gamma_i; + }); +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/dory_g2.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/dory_g2.rs new file mode 100644 index 000000000..1d70156d4 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/dory_g2.rs @@ -0,0 +1,52 @@ +//! Vector-scalar operations on G2 using 4D GLV with Frobenius, for Dory inner-product argument rounds. + +use ark_bn254::{Fr, G2Projective}; +use rayon::prelude::*; + +use super::decomp_4d::decompose_scalar_4d; +use super::frobenius::frobenius_psi_power_projective; +use super::glv_four::shamir_glv_mul_4d; + +/// `v[i] += scalar * generators[i]`, using 4D GLV decomposition with Frobenius. +pub fn vector_add_scalar_mul_g2_online( + v: &mut [G2Projective], + generators: &[G2Projective], + scalar: Fr, +) { + assert_eq!(v.len(), generators.len()); + let (coeffs, signs) = decompose_scalar_4d(scalar); + + v.par_iter_mut() + .zip(generators.par_iter()) + .for_each(|(vi, gen)| { + let bases = [ + *gen, + frobenius_psi_power_projective(gen, 1), + frobenius_psi_power_projective(gen, 2), + frobenius_psi_power_projective(gen, 3), + ]; + *vi += shamir_glv_mul_4d(&bases, &coeffs, &signs); + }); +} + +/// `v[i] = scalar * v[i] + gamma[i]`, using 4D GLV decomposition with Frobenius. +pub fn vector_scalar_mul_add_gamma_g2_online( + v: &mut [G2Projective], + scalar: Fr, + gamma: &[G2Projective], +) { + assert_eq!(v.len(), gamma.len()); + let (coeffs, signs) = decompose_scalar_4d(scalar); + + v.par_iter_mut() + .zip(gamma.par_iter()) + .for_each(|(vi, &gamma_i)| { + let bases = [ + *vi, + frobenius_psi_power_projective(vi, 1), + frobenius_psi_power_projective(vi, 2), + frobenius_psi_power_projective(vi, 3), + ]; + *vi = shamir_glv_mul_4d(&bases, &coeffs, &signs) + gamma_i; + }); +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/frobenius.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/frobenius.rs new file mode 100644 index 000000000..477750c69 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/frobenius.rs @@ -0,0 +1,40 @@ +use super::constants::get_frobenius_coefficients; +use ark_bn254::G2Projective; +use ark_std::Zero; + +/// Compute the Frobenius endomorphism ψ^k on a BN254 G2 point (projective). +/// Applies conjugation in Fq2 followed by multiplication by precomputed coefficients. +pub fn frobenius_psi_power_projective(p: &G2Projective, k: usize) -> G2Projective { + if p.is_zero() { + return *p; + } + + let mut res = *p; + let coeffs = get_frobenius_coefficients(); + + if (k & 1) == 1 { + let _ = res.x.conjugate_in_place(); + let _ = res.y.conjugate_in_place(); + let _ = res.z.conjugate_in_place(); + } + + match k % 4 { + 0 => res, + 1 => { + res.x *= coeffs.psi1_coef2; + res.y *= coeffs.psi1_coef3; + res + } + 2 => { + res.x *= coeffs.psi2_coef2; + res.y *= coeffs.psi2_coef3; + res + } + 3 => { + res.x *= coeffs.psi3_coef2; + res.y *= coeffs.psi3_coef3; + res + } + _ => unreachable!(), + } +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/glv_four.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/glv_four.rs new file mode 100644 index 000000000..37c3a0fc9 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/glv_four.rs @@ -0,0 +1,64 @@ +//! 4D GLV scalar multiplication for BN254 G2. +//! +//! Uses Strauss-Shamir interleaving with four ~64-bit scalars from the 4D decomposition. +//! The four bases are `P, psi(P), psi^2(P), psi^3(P)` where `psi` is the Frobenius endomorphism. + +use ark_bn254::{Fr, G2Projective}; +use ark_ec::AdditiveGroup; +use ark_ff::{BigInteger, PrimeField}; +use ark_std::Zero; +use rayon::prelude::*; + +use super::decomp_4d::decompose_scalar_4d; +use super::frobenius::frobenius_psi_power_projective; + +/// Online 4D GLV scalar multiplication for BN254 G2. +/// Decomposes scalar into 4 ~66-bit components using Frobenius endomorphism, +/// then uses Strauss-Shamir interleaving. +pub fn glv_four_scalar_mul_online(scalar: Fr, points: &[G2Projective]) -> Vec { + let (coeffs, signs) = decompose_scalar_4d(scalar); + + points + .par_iter() + .map(|point| { + let bases = [ + *point, + frobenius_psi_power_projective(point, 1), + frobenius_psi_power_projective(point, 2), + frobenius_psi_power_projective(point, 3), + ]; + shamir_glv_mul_4d(&bases, &coeffs, &signs) + }) + .collect() +} + +/// Shamir's trick for 4-point scalar multiplication with sign handling. +pub(crate) fn shamir_glv_mul_4d( + bases: &[G2Projective; 4], + coeffs: &[::BigInt; 4], + signs: &[bool; 4], +) -> G2Projective { + let mut result = G2Projective::zero(); + let max_bits = coeffs + .iter() + .map(|c| c.num_bits() as usize) + .max() + .unwrap_or(0); + + for bit_idx in (0..max_bits).rev() { + result = result.double(); + + for (i, (coeff, &base)) in coeffs.iter().zip(bases.iter()).enumerate() { + if coeff.get_bit(bit_idx) { + if signs[i] { + // signs[i] = true means negative in 4D decomposition + result -= base; + } else { + result += base; + } + } + } + } + + result +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/glv_two.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/glv_two.rs new file mode 100644 index 000000000..13018ce04 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/glv_two.rs @@ -0,0 +1,155 @@ +//! 2D GLV scalar multiplication for BN254 G1. +//! +//! Uses Strauss-Shamir interleaving of the two half-length scalars from the +//! 2D decomposition. Includes a precomputed Shamir table variant for fixed-base MSM. + +use ark_bn254::{Fr, G1Projective}; +use ark_ec::AdditiveGroup; +use ark_ff::{BigInteger, PrimeField}; +use ark_std::Zero; +use rayon::prelude::*; + +use super::decomp_2d::{decompose_scalar_2d, glv_endomorphism}; + +/// Shamir's trick for 2-point scalar multiplication with sign handling. +pub(crate) fn shamir_glv_mul_2d( + bases: &[G1Projective; 2], + coeffs: &[::BigInt; 2], + signs: &[bool; 2], +) -> G1Projective { + let mut result = G1Projective::zero(); + let max_bits = coeffs + .iter() + .map(|c| c.num_bits() as usize) + .max() + .unwrap_or(0); + + for bit_idx in (0..max_bits).rev() { + result = result.double(); + + for (i, (coeff, &base)) in coeffs.iter().zip(bases.iter()).enumerate() { + if coeff.get_bit(bit_idx) { + if signs[i] { + result += base; + } else { + result -= base; + } + } + } + } + + result +} + +/// Precomputed Shamir lookup table for 2D GLV: all 16 combinations +/// of [P, λ(P)] with sign bits. +struct PrecomputedShamir2Table { + table: [G1Projective; 16], +} + +impl PrecomputedShamir2Table { + fn new(bases: &[G1Projective; 2]) -> Self { + let mut table = [G1Projective::zero(); 16]; + + table.par_iter_mut().enumerate().for_each(|(idx, point)| { + let point_mask = idx & 0x3; + let sign_mask = idx >> 2; + + *point = G1Projective::zero(); + for (i, &base) in bases.iter().enumerate() { + if (point_mask >> i) & 1 == 1 { + if (sign_mask >> i) & 1 == 1 { + *point -= base; + } else { + *point += base; + } + } + } + }); + + Self { table } + } + + #[inline] + fn get(&self, point_mask: usize, sign_mask: usize) -> G1Projective { + self.table[point_mask | (sign_mask << 2)] + } +} + +/// Shamir's trick using precomputed table. +fn shamir_glv_mul_2d_precomputed( + table: &PrecomputedShamir2Table, + coeffs: &[::BigInt; 2], + signs: &[bool; 2], +) -> G1Projective { + let mut result = G1Projective::zero(); + let max_bits = coeffs + .iter() + .map(|c| c.num_bits() as usize) + .max() + .unwrap_or(0); + + for bit_idx in (0..max_bits).rev() { + result = result.double(); + + let mut point_mask = 0; + let mut sign_mask = 0; + + for (i, coeff) in coeffs.iter().enumerate() { + if coeff.get_bit(bit_idx) { + point_mask |= 1 << i; + if !signs[i] { + sign_mask |= 1 << i; + } + } + } + + if point_mask != 0 { + result += table.get(point_mask, sign_mask); + } + } + + result +} + +struct DecomposedScalar2D { + coeffs: [::BigInt; 2], + signs: [bool; 2], +} + +impl DecomposedScalar2D { + fn from_scalar(scalar: Fr) -> Self { + let (coeffs, signs) = decompose_scalar_2d(scalar); + Self { coeffs, signs } + } +} + +struct FixedBasePrecomputedG1 { + shamir_table: PrecomputedShamir2Table, +} + +impl FixedBasePrecomputedG1 { + fn new(base: &G1Projective) -> Self { + let glv_bases = [*base, glv_endomorphism(base)]; + let shamir_table = PrecomputedShamir2Table::new(&glv_bases); + Self { shamir_table } + } + + fn mul_scalar(&self, scalar: Fr) -> G1Projective { + let decomposed = DecomposedScalar2D::from_scalar(scalar); + shamir_glv_mul_2d_precomputed(&self.shamir_table, &decomposed.coeffs, &decomposed.signs) + } + + fn mul_scalars(&self, scalars: &[Fr]) -> Vec { + scalars + .par_iter() + .map(|&scalar| self.mul_scalar(scalar)) + .collect() + } +} + +/// Fixed-base vector MSM: compute `base * scalars[i]` for all `i`. +pub fn fixed_base_vector_msm_g1(base: &G1Projective, scalars: &[Fr]) -> Vec { + let precomputed = FixedBasePrecomputedG1::new(base); + precomputed.mul_scalars(scalars) +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/mod.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/mod.rs new file mode 100644 index 000000000..84d7c8222 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/mod.rs @@ -0,0 +1,92 @@ +//! BN254-specific GLV optimizations for elliptic curve scalar multiplication. +//! +//! Provides GLV scalar multiplication (2D for G1, 4D for G2 via Frobenius endomorphism) +//! and vector-scalar operations used in Dory's inner-product argument rounds. +//! +//! References: +//! - GLV: Gallant, Lambert, Vanstone. [Faster Point Multiplication on Elliptic Curves](https://link.springer.com/chapter/10.1007/3-540-44647-8_11) (CRYPTO 2001) +//! - Dory: Lee. [Dory: Efficient, Transparent arguments for Generalised Inner Products](https://eprint.iacr.org/2020/1274) + +mod constants; +mod decomp_2d; +mod decomp_4d; +mod frobenius; +pub mod glv_four; +pub mod glv_two; + +pub mod dory_g1; +pub mod dory_g2; + +use ark_bn254::{G1Projective, G2Projective}; +use jolt_field::Fr; + +use super::{field_to_fr, Bn254G1, Bn254G2}; + +/// `v[i] += scalar * generators[i]`, using 2D GLV decomposition + rayon. +#[inline] +pub fn vector_add_scalar_mul_g1(v: &mut [Bn254G1], generators: &[Bn254G1], scalar: Fr) { + // SAFETY: Bn254G1 is #[repr(transparent)] over G1Projective — identical layout. + let v_inner = + unsafe { std::slice::from_raw_parts_mut(v.as_mut_ptr().cast::(), v.len()) }; + // SAFETY: same repr(transparent) guarantee. + let gens_inner = unsafe { + std::slice::from_raw_parts(generators.as_ptr().cast::(), generators.len()) + }; + dory_g1::vector_add_scalar_mul_g1_online(v_inner, gens_inner, field_to_fr(&scalar)); +} + +/// `v[i] = scalar * v[i] + gamma[i]`, using 2D GLV decomposition + rayon. +#[inline] +pub fn vector_scalar_mul_add_gamma_g1(v: &mut [Bn254G1], scalar: Fr, gamma: &[Bn254G1]) { + // SAFETY: Bn254G1 is #[repr(transparent)] over G1Projective — identical layout. + let v_inner = + unsafe { std::slice::from_raw_parts_mut(v.as_mut_ptr().cast::(), v.len()) }; + // SAFETY: same repr(transparent) guarantee. + let gamma_inner = + unsafe { std::slice::from_raw_parts(gamma.as_ptr().cast::(), gamma.len()) }; + dory_g1::vector_scalar_mul_add_gamma_g1_online(v_inner, field_to_fr(&scalar), gamma_inner); +} + +/// Fixed-base MSM: `[base * scalars[0], ..., base * scalars[n-1]]`, using precomputed Shamir table. +#[inline] +pub fn fixed_base_vector_msm_g1(base: &Bn254G1, scalars: &[Fr]) -> Vec { + let ark_scalars: Vec = scalars.iter().map(field_to_fr).collect(); + let inner: G1Projective = (*base).into(); + let results = glv_two::fixed_base_vector_msm_g1(&inner, &ark_scalars); + results.into_iter().map(Bn254G1::from).collect() +} + +/// `v[i] += scalar * generators[i]`, using 4D GLV with Frobenius endomorphism + rayon. +#[inline] +pub fn vector_add_scalar_mul_g2(v: &mut [Bn254G2], generators: &[Bn254G2], scalar: Fr) { + // SAFETY: Bn254G2 is #[repr(transparent)] over G2Projective — identical layout. + let v_inner = + unsafe { std::slice::from_raw_parts_mut(v.as_mut_ptr().cast::(), v.len()) }; + // SAFETY: same repr(transparent) guarantee. + let gens_inner = unsafe { + std::slice::from_raw_parts(generators.as_ptr().cast::(), generators.len()) + }; + dory_g2::vector_add_scalar_mul_g2_online(v_inner, gens_inner, field_to_fr(&scalar)); +} + +/// `v[i] = scalar * v[i] + gamma[i]`, using 4D GLV with Frobenius + rayon. +#[inline] +pub fn vector_scalar_mul_add_gamma_g2(v: &mut [Bn254G2], scalar: Fr, gamma: &[Bn254G2]) { + // SAFETY: Bn254G2 is #[repr(transparent)] over G2Projective — identical layout. + let v_inner = + unsafe { std::slice::from_raw_parts_mut(v.as_mut_ptr().cast::(), v.len()) }; + // SAFETY: same repr(transparent) guarantee. + let gamma_inner = + unsafe { std::slice::from_raw_parts(gamma.as_ptr().cast::(), gamma.len()) }; + dory_g2::vector_scalar_mul_add_gamma_g2_online(v_inner, field_to_fr(&scalar), gamma_inner); +} + +/// Scalar multiplication of multiple G2 points by a single scalar, using 4D GLV. +#[inline] +pub fn glv_four_scalar_mul(scalar: Fr, points: &[Bn254G2]) -> Vec { + // SAFETY: Bn254G2 is #[repr(transparent)] over G2Projective — identical layout. + let points_inner = + unsafe { std::slice::from_raw_parts(points.as_ptr().cast::(), points.len()) }; + let results = glv_four::glv_four_scalar_mul_online(field_to_fr(&scalar), points_inner); + results.into_iter().map(Bn254G2::from).collect() +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/glv/power_of_2_decompositions.rs b/crates/jolt-crypto/src/arkworks/bn254/glv/power_of_2_decompositions.rs new file mode 100644 index 000000000..80daa442d --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/glv/power_of_2_decompositions.rs @@ -0,0 +1,2799 @@ +/// Precomputed 4D decompositions for powers of 2 (2^0 through 2^253) +/// Each entry contains (k0, k1, k2, k3, neg0, neg1, neg2, neg3) +/// Generated by bn254_table.sage +pub const POWER_OF_2_DECOMPOSITIONS: [(u128, u128, u128, u128, bool, bool, bool, bool); 254] = [ + // 2^0 + ( + 0x00000000000000000000000000000001, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^1 + ( + 0x00000000000000000000000000000002, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^2 + ( + 0x00000000000000000000000000000004, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^3 + ( + 0x00000000000000000000000000000008, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^4 + ( + 0x00000000000000000000000000000010, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^5 + ( + 0x00000000000000000000000000000020, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^6 + ( + 0x00000000000000000000000000000040, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^7 + ( + 0x00000000000000000000000000000080, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^8 + ( + 0x00000000000000000000000000000100, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^9 + ( + 0x00000000000000000000000000000200, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^10 + ( + 0x00000000000000000000000000000400, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^11 + ( + 0x00000000000000000000000000000800, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^12 + ( + 0x00000000000000000000000000001000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^13 + ( + 0x00000000000000000000000000002000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^14 + ( + 0x00000000000000000000000000004000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^15 + ( + 0x00000000000000000000000000008000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^16 + ( + 0x00000000000000000000000000010000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^17 + ( + 0x00000000000000000000000000020000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^18 + ( + 0x00000000000000000000000000040000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^19 + ( + 0x00000000000000000000000000080000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^20 + ( + 0x00000000000000000000000000100000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^21 + ( + 0x00000000000000000000000000200000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^22 + ( + 0x00000000000000000000000000400000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^23 + ( + 0x00000000000000000000000000800000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^24 + ( + 0x00000000000000000000000001000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^25 + ( + 0x00000000000000000000000002000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^26 + ( + 0x00000000000000000000000004000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^27 + ( + 0x00000000000000000000000008000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^28 + ( + 0x00000000000000000000000010000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^29 + ( + 0x00000000000000000000000020000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^30 + ( + 0x00000000000000000000000040000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^31 + ( + 0x00000000000000000000000080000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^32 + ( + 0x00000000000000000000000100000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^33 + ( + 0x00000000000000000000000200000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^34 + ( + 0x00000000000000000000000400000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^35 + ( + 0x00000000000000000000000800000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^36 + ( + 0x00000000000000000000001000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^37 + ( + 0x00000000000000000000002000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^38 + ( + 0x00000000000000000000004000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^39 + ( + 0x00000000000000000000008000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^40 + ( + 0x00000000000000000000010000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^41 + ( + 0x00000000000000000000020000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^42 + ( + 0x00000000000000000000040000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^43 + ( + 0x00000000000000000000080000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^44 + ( + 0x00000000000000000000100000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^45 + ( + 0x00000000000000000000200000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^46 + ( + 0x00000000000000000000400000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^47 + ( + 0x00000000000000000000800000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^48 + ( + 0x00000000000000000001000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^49 + ( + 0x00000000000000000002000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^50 + ( + 0x00000000000000000004000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^51 + ( + 0x00000000000000000008000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^52 + ( + 0x00000000000000000010000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^53 + ( + 0x00000000000000000020000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^54 + ( + 0x00000000000000000040000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^55 + ( + 0x00000000000000000080000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^56 + ( + 0x00000000000000000100000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^57 + ( + 0x00000000000000000200000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^58 + ( + 0x00000000000000000400000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^59 + ( + 0x00000000000000000800000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^60 + ( + 0x00000000000000001000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^61 + ( + 0x00000000000000002000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^62 + ( + 0x00000000000000004000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^63 + ( + 0x00000000000000008000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + 0x00000000000000000000000000000000, + false, + false, + false, + false, + ), + // 2^64 + ( + 0x00000000000000009d797039be763ba8, + 0x00000000000000000000000000000001, + 0x00000000000000000000000000000001, + 0x00000000000000000000000000000001, + true, + true, + false, + true, + ), + // 2^65 + ( + 0x000000000000000062868fc64189c458, + 0x00000000000000000000000000000001, + 0x00000000000000000000000000000001, + 0x00000000000000000000000000000001, + false, + true, + false, + true, + ), + // 2^66 + ( + 0x0000000000000000c50d1f8c831388b0, + 0x00000000000000000000000000000002, + 0x00000000000000000000000000000002, + 0x00000000000000000000000000000002, + false, + true, + false, + true, + ), + // 2^67 + ( + 0x0000000000000000135f3120b84f2a48, + 0x00000000000000000000000000000005, + 0x00000000000000000000000000000005, + 0x00000000000000000000000000000005, + true, + true, + false, + true, + ), + // 2^68 + ( + 0x000000000000000026be6241709e5490, + 0x0000000000000000000000000000000a, + 0x0000000000000000000000000000000a, + 0x0000000000000000000000000000000a, + true, + true, + false, + true, + ), + // 2^69 + ( + 0x00000000000000004d7cc482e13ca920, + 0x00000000000000000000000000000014, + 0x00000000000000000000000000000014, + 0x00000000000000000000000000000014, + true, + true, + false, + true, + ), + // 2^70 + ( + 0x00000000000000009af98905c2795240, + 0x00000000000000000000000000000028, + 0x00000000000000000000000000000028, + 0x00000000000000000000000000000028, + true, + true, + false, + true, + ), + // 2^71 + ( + 0x000000000000000067865e2e39839728, + 0x0000000000000000000000000000004f, + 0x0000000000000000000000000000004f, + 0x0000000000000000000000000000004f, + false, + true, + false, + true, + ), + // 2^72 + ( + 0x0000000000000000ce6cb3dd4b6f0d58, + 0x0000000000000000000000000000009f, + 0x0000000000000000000000000000009f, + 0x0000000000000000000000000000009f, + true, + true, + false, + true, + ), + // 2^73 + ( + 0x000000000000000000a0087f279820f8, + 0x0000000000000000000000000000013d, + 0x0000000000000000000000000000013d, + 0x0000000000000000000000000000013d, + false, + true, + false, + true, + ), + // 2^74 + ( + 0x0000000000000000014010fe4f3041f0, + 0x0000000000000000000000000000027a, + 0x0000000000000000000000000000027a, + 0x0000000000000000000000000000027a, + false, + true, + false, + true, + ), + // 2^75 + ( + 0x0000000000000000028021fc9e6083e0, + 0x000000000000000000000000000004f4, + 0x000000000000000000000000000004f4, + 0x000000000000000000000000000004f4, + false, + true, + false, + true, + ), + // 2^76 + ( + 0x0000000000000000050043f93cc107c0, + 0x000000000000000000000000000009e8, + 0x000000000000000000000000000009e8, + 0x000000000000000000000000000009e8, + false, + true, + false, + true, + ), + // 2^77 + ( + 0x00000000000000000a0087f279820f80, + 0x000000000000000000000000000013d0, + 0x000000000000000000000000000013d0, + 0x000000000000000000000000000013d0, + false, + true, + false, + true, + ), + // 2^78 + ( + 0x000000000000000014010fe4f3041f00, + 0x000000000000000000000000000027a0, + 0x000000000000000000000000000027a0, + 0x000000000000000000000000000027a0, + false, + true, + false, + true, + ), + // 2^79 + ( + 0x000000000000000028021fc9e6083e00, + 0x00000000000000000000000000004f40, + 0x00000000000000000000000000004f40, + 0x00000000000000000000000000004f40, + false, + true, + false, + true, + ), + // 2^80 + ( + 0x000000000000000050043f93cc107c00, + 0x00000000000000000000000000009e80, + 0x00000000000000000000000000009e80, + 0x00000000000000000000000000009e80, + false, + true, + false, + true, + ), + // 2^81 + ( + 0x0000000000000000a0087f279820f800, + 0x00000000000000000000000000013d00, + 0x00000000000000000000000000013d00, + 0x00000000000000000000000000013d00, + false, + true, + false, + true, + ), + // 2^82 + ( + 0x00000000000000005d6871ea8e344ba8, + 0x00000000000000000000000000027a01, + 0x00000000000000000000000000027a01, + 0x00000000000000000000000000027a01, + true, + true, + false, + true, + ), + // 2^83 + ( + 0x0000000000000000bad0e3d51c689750, + 0x0000000000000000000000000004f402, + 0x0000000000000000000000000004f402, + 0x0000000000000000000000000004f402, + true, + true, + false, + true, + ), + // 2^84 + ( + 0x000000000000000027d7a88f85a50d08, + 0x0000000000000000000000000009e803, + 0x0000000000000000000000000009e803, + 0x0000000000000000000000000009e803, + false, + true, + false, + true, + ), + // 2^85 + ( + 0x00000000000000004faf511f0b4a1a10, + 0x0000000000000000000000000013d006, + 0x0000000000000000000000000013d006, + 0x0000000000000000000000000013d006, + false, + true, + false, + true, + ), + // 2^86 + ( + 0x00000000000000009f5ea23e16943420, + 0x0000000000000000000000000027a00c, + 0x0000000000000000000000000027a00c, + 0x0000000000000000000000000027a00c, + false, + true, + false, + true, + ), + // 2^87 + ( + 0x00000000000000005ebc2bbd914dd368, + 0x000000000000000000000000004f4019, + 0x000000000000000000000000004f4019, + 0x000000000000000000000000004f4019, + true, + true, + false, + true, + ), + // 2^88 + ( + 0x0000000000000000bd78577b229ba6d0, + 0x000000000000000000000000009e8032, + 0x000000000000000000000000009e8032, + 0x000000000000000000000000009e8032, + true, + true, + false, + true, + ), + // 2^89 + ( + 0x00000000000000002288c143793eee08, + 0x000000000000000000000000013d0063, + 0x000000000000000000000000013d0063, + 0x000000000000000000000000013d0063, + false, + true, + false, + true, + ), + // 2^90 + ( + 0x000000000000000045118286f27ddc10, + 0x000000000000000000000000027a00c6, + 0x000000000000000000000000027a00c6, + 0x000000000000000000000000027a00c6, + false, + true, + false, + true, + ), + // 2^91 + ( + 0x00000000000000008a23050de4fbb820, + 0x00000000000000000000000004f4018c, + 0x00000000000000000000000004f4018c, + 0x00000000000000000000000004f4018c, + false, + true, + false, + true, + ), + // 2^92 + ( + 0x00000000000000008933661df47ecb68, + 0x00000000000000000000000009e80319, + 0x00000000000000000000000009e80319, + 0x00000000000000000000000009e80319, + true, + true, + false, + true, + ), + // 2^93 + ( + 0x00000000000000008b12a3fdd578a4d8, + 0x00000000000000000000000013d00631, + 0x00000000000000000000000013d00631, + 0x00000000000000000000000013d00631, + false, + true, + false, + true, + ), + // 2^94 + ( + 0x00000000000000008754283e1384f1f8, + 0x00000000000000000000000027a00c63, + 0x00000000000000000000000027a00c63, + 0x00000000000000000000000027a00c63, + true, + true, + false, + true, + ), + // 2^95 + ( + 0x00000000000000008ed11fbd976c57b8, + 0x0000000000000000000000004f4018c5, + 0x0000000000000000000000004f4018c5, + 0x0000000000000000000000004f4018c5, + false, + true, + false, + true, + ), + // 2^96 + ( + 0x00000000000000007fd730be8f9d8c38, + 0x0000000000000000000000009e80318b, + 0x0000000000000000000000009e80318b, + 0x0000000000000000000000009e80318b, + true, + true, + false, + true, + ), + // 2^97 + ( + 0x00000000000000009dcb0ebc9f3b2338, + 0x0000000000000000000000013d006315, + 0x0000000000000000000000013d006315, + 0x0000000000000000000000013d006315, + false, + true, + false, + true, + ), + // 2^98 + ( + 0x000000000000000061e352c07ffff538, + 0x0000000000000000000000027a00c62b, + 0x0000000000000000000000027a00c62b, + 0x0000000000000000000000027a00c62b, + true, + true, + false, + true, + ), + // 2^99 + ( + 0x0000000000000000c3c6a580ffffea70, + 0x000000000000000000000004f4018c56, + 0x000000000000000000000004f4018c56, + 0x000000000000000000000004f4018c56, + true, + true, + false, + true, + ), + // 2^100 + ( + 0x000000000000000015ec2537be7666c8, + 0x000000000000000000000009e80318ab, + 0x000000000000000000000009e80318ab, + 0x000000000000000000000009e80318ab, + false, + true, + false, + true, + ), + // 2^101 + ( + 0x00000000000000002bd84a6f7ceccd90, + 0x000000000000000000000013d0063156, + 0x000000000000000000000013d0063156, + 0x000000000000000000000013d0063156, + false, + true, + false, + true, + ), + // 2^102 + ( + 0x000000000000000057b094def9d99b20, + 0x000000000000000000000027a00c62ac, + 0x000000000000000000000027a00c62ac, + 0x000000000000000000000027a00c62ac, + false, + true, + false, + true, + ), + // 2^103 + ( + 0x0000000000000000af6129bdf3b33640, + 0x00000000000000000000004f4018c558, + 0x00000000000000000000004f4018c558, + 0x00000000000000000000004f4018c558, + false, + true, + false, + true, + ), + // 2^104 + ( + 0x00000000000000003eb71cbdd70fcf28, + 0x00000000000000000000009e80318ab1, + 0x00000000000000000000009e80318ab1, + 0x00000000000000000000009e80318ab1, + true, + true, + false, + true, + ), + // 2^105 + ( + 0x00000000000000007d6e397bae1f9e50, + 0x00000000000000000000013d00631562, + 0x00000000000000000000013d00631562, + 0x00000000000000000000013d00631562, + true, + true, + false, + true, + ), + // 2^106 + ( + 0x0000000000000000a29cfd426236ff08, + 0x00000000000000000000027a00c62ac3, + 0x00000000000000000000027a00c62ac3, + 0x00000000000000000000027a00c62ac3, + false, + true, + false, + true, + ), + // 2^107 + ( + 0x0000000000000000583f75b4fa083d98, + 0x0000000000000000000004f4018c5587, + 0x0000000000000000000004f4018c5587, + 0x0000000000000000000004f4018c5587, + true, + true, + false, + true, + ), + // 2^108 + ( + 0x0000000000000000b07eeb69f4107b30, + 0x0000000000000000000009e80318ab0e, + 0x0000000000000000000009e80318ab0e, + 0x0000000000000000000009e80318ab0e, + true, + true, + false, + true, + ), + // 2^109 + ( + 0x00000000000000003c7b9965d6554548, + 0x0000000000000000000013d00631561b, + 0x0000000000000000000013d00631561b, + 0x0000000000000000000013d00631561b, + false, + true, + false, + true, + ), + // 2^110 + ( + 0x000000000000000078f732cbacaa8a90, + 0x0000000000000000000027a00c62ac36, + 0x0000000000000000000027a00c62ac36, + 0x0000000000000000000027a00c62ac36, + false, + true, + false, + true, + ), + // 2^111 + ( + 0x0000000000000000ab8b0aa265212688, + 0x000000000000000000004f4018c5586d, + 0x000000000000000000004f4018c5586d, + 0x000000000000000000004f4018c5586d, + true, + true, + false, + true, + ), + // 2^112 + ( + 0x000000000000000046635af4f433ee98, + 0x000000000000000000009e80318ab0d9, + 0x000000000000000000009e80318ab0d9, + 0x000000000000000000009e80318ab0d9, + false, + true, + false, + true, + ), + // 2^113 + ( + 0x00000000000000008cc6b5e9e867dd30, + 0x000000000000000000013d00631561b2, + 0x000000000000000000013d00631561b2, + 0x000000000000000000013d00631561b2, + false, + true, + false, + true, + ), + // 2^114 + ( + 0x000000000000000083ec0465eda68148, + 0x000000000000000000027a00c62ac365, + 0x000000000000000000027a00c62ac365, + 0x000000000000000000027a00c62ac365, + true, + true, + false, + true, + ), + // 2^115 + ( + 0x000000000000000095a1676de3293918, + 0x00000000000000000004f4018c5586c9, + 0x00000000000000000004f4018c5586c9, + 0x00000000000000000004f4018c5586c9, + false, + true, + false, + true, + ), + // 2^116 + ( + 0x00000000000000007236a15df823c978, + 0x00000000000000000009e80318ab0d93, + 0x00000000000000000009e80318ab0d93, + 0x00000000000000000009e80318ab0d93, + true, + true, + false, + true, + ), + // 2^117 + ( + 0x0000000000000000b90c2d7dce2ea8b8, + 0x00000000000000000013d00631561b25, + 0x00000000000000000013d00631561b25, + 0x00000000000000000013d00631561b25, + false, + true, + false, + true, + ), + // 2^118 + ( + 0x00000000000000002b61153e2218ea38, + 0x00000000000000000027a00c62ac364b, + 0x00000000000000000027a00c62ac364b, + 0x00000000000000000027a00c62ac364b, + true, + true, + false, + true, + ), + // 2^119 + ( + 0x000000000000000056c22a7c4431d470, + 0x0000000000000000004f4018c5586c96, + 0x0000000000000000004f4018c5586c96, + 0x0000000000000000004f4018c5586c96, + true, + true, + false, + true, + ), + // 2^120 + ( + 0x0000000000000000ad8454f88863a8e0, + 0x0000000000000000009e80318ab0d92c, + 0x0000000000000000009e80318ab0d92c, + 0x0000000000000000009e80318ab0d92c, + true, + true, + false, + true, + ), + // 2^121 + ( + 0x00000000000000004270c648adaee9e8, + 0x0000000000000000013d00631561b257, + 0x0000000000000000013d00631561b257, + 0x0000000000000000013d00631561b257, + false, + true, + false, + true, + ), + // 2^122 + ( + 0x000000000000000084e18c915b5dd3d0, + 0x0000000000000000027a00c62ac364ae, + 0x0000000000000000027a00c62ac364ae, + 0x0000000000000000027a00c62ac364ae, + false, + true, + false, + true, + ), + // 2^123 + ( + 0x000000000000000093b6571707ba9408, + 0x000000000000000004f4018c5586c95d, + 0x000000000000000004f4018c5586c95d, + 0x000000000000000004f4018c5586c95d, + true, + true, + false, + true, + ), + // 2^124 + ( + 0x0000000000000000760cc20baf011398, + 0x000000000000000009e80318ab0d92b9, + 0x000000000000000009e80318ab0d92b9, + 0x000000000000000009e80318ab0d92b9, + false, + true, + false, + true, + ), + // 2^125 + ( + 0x0000000000000000278cc6b9cba20096, + 0x000000000000000031198c82f44de47f, + 0x000000000000000031198c82f44de47e, + 0x000000000000000031198c82f44de47e, + true, + false, + true, + false, + ), + // 2^126 + ( + 0x00000000000000003ab997f4fd8e12b8, + 0x000000000000000027a00c62ac364ae5, + 0x000000000000000027a00c62ac364ae5, + 0x000000000000000027a00c62ac364ae5, + false, + true, + false, + true, + ), + // 2^127 + ( + 0x00000000000000009e331ae72e880256, + 0x00000000000000000a5686110e038bd9, + 0x00000000000000000a5686110e038bda, + 0x00000000000000000a5686110e038bda, + true, + true, + false, + true, + ), + // 2^128 + ( + 0x000000000000000061133a6b616636fc, + 0x000000000000000014ad0c221c0717b1, + 0x000000000000000014ad0c221c0717b3, + 0x000000000000000014ad0c221c0717b3, + false, + true, + false, + true, + ), + // 2^129 + ( + 0x0000000000000000517fd5fa66d7b9ce, + 0x00000000000000001b8f7a70125ada8f, + 0x00000000000000001b8f7a70125ada8a, + 0x00000000000000001b8f7a70125ada8a, + true, + false, + true, + false, + ), + // 2^130 + ( + 0x000000000000000070a69edc5bf4b42a, + 0x00000000000000000dca9dd425b354d3, + 0x00000000000000000dca9dd425b354dc, + 0x00000000000000000dca9dd425b354dc, + false, + true, + false, + true, + ), + // 2^131 + ( + 0x000000000000000032590d1871babf72, + 0x00000000000000002954570bff02604b, + 0x00000000000000002954570bff026038, + 0x00000000000000002954570bff026038, + true, + false, + true, + false, + ), + // 2^132 + ( + 0x000000000000000025210b37b15c9500, + 0x0000000000000000372a775096cd534d, + 0x0000000000000000372a775096cd5371, + 0x0000000000000000372a775096cd5371, + false, + true, + false, + true, + ), + // 2^133 + ( + 0x00000000000000003f910ef93218e9e4, + 0x00000000000000001b7e36c767376d49, + 0x00000000000000001b7e36c767376cff, + 0x00000000000000001b7e36c767376cff, + true, + false, + true, + false, + ), + // 2^134 + ( + 0x000000000000000094842cdec57253fe, + 0x00000000000000000ded25257bfa2f5f, + 0x00000000000000000ded25257bfa2ff2, + 0x00000000000000000ded25257bfa2ff2, + false, + true, + false, + true, + ), + // 2^135 + ( + 0x000000000000000015620eec61408036, + 0x0000000000000000290f48695274ab33, + 0x0000000000000000290f48695274aa0c, + 0x0000000000000000290f48695274aa0c, + false, + false, + true, + false, + ), + // 2^136 + ( + 0x00000000000000005f0f078fd2511376, + 0x00000000000000000d34fe1e5a804c74, + 0x00000000000000000d34fe1e5a804a27, + 0x00000000000000000d34fe1e5a804a27, + true, + false, + true, + false, + ), + // 2^137 + ( + 0x000000000000000055883bb1850200da, + 0x00000000000000002a7f967795687109, + 0x00000000000000002a7f9677956875a2, + 0x00000000000000002a7f9677956875a2, + false, + true, + false, + true, + ), + // 2^138 + ( + 0x00000000000000006895d36e1fa02612, + 0x000000000000000010159a3ae067d821, + 0x000000000000000010159a3ae067e154, + 0x000000000000000010159a3ae067e154, + true, + true, + false, + true, + ), + // 2^139 + ( + 0x000000000000000047588173aa6e3842, + 0x000000000000000024be5e3e899959b0, + 0x000000000000000024be5e3e89994749, + 0x000000000000000024be5e3e89994749, + true, + false, + true, + false, + ), + // 2^140 + ( + 0x000000000000000084f547e9d4c7b742, + 0x0000000000000000049329c8c8c9a96f, + 0x0000000000000000049329c8c8c984a2, + 0x0000000000000000049329c8c8c984a2, + false, + false, + true, + false, + ), + // 2^141 + ( + 0x0000000000000000938ee06614e6cd24, + 0x000000000000000009265391919352dd, + 0x00000000000000000926539191930943, + 0x00000000000000000926539191930943, + true, + false, + true, + false, + ), + // 2^142 + ( + 0x0000000000000000765baf6d94a8a160, + 0x0000000000000000124ca7232326a5bb, + 0x0000000000000000124ca72323261287, + 0x0000000000000000124ca72323261287, + false, + false, + true, + false, + ), + // 2^143 + ( + 0x000000000000000062e43972947f2ede, + 0x00000000000000002050446e041bbe7c, + 0x00000000000000002050446e041ce4e3, + 0x00000000000000002050446e041ce4e3, + false, + true, + false, + true, + ), + // 2^144 + ( + 0x00000000000000004dddd7ec00a5ca0a, + 0x0000000000000000044909d842318cf9, + 0x0000000000000000044909d8422f402a, + 0x0000000000000000044909d8422f402a, + true, + false, + true, + false, + ), + // 2^145 + ( + 0x00000000000000009bbbafd8014b9414, + 0x0000000000000000089213b0846319f2, + 0x0000000000000000089213b0845e8054, + 0x0000000000000000089213b0845e8054, + true, + false, + true, + false, + ), + // 2^146 + ( + 0x000000000000000066021089bbdf1380, + 0x00000000000000001124276108c633e5, + 0x00000000000000001124276108bd00a9, + 0x00000000000000001124276108bd00a9, + false, + false, + true, + false, + ), + // 2^147 + ( + 0x00000000000000004230fbaae2ec131e, + 0x000000000000000022a143f238dca228, + 0x000000000000000022a143f238ef089f, + 0x000000000000000022a143f238ef089f, + false, + true, + false, + true, + ), + // 2^148 + ( + 0x00000000000000008f44537b63cc018a, + 0x00000000000000000058f53027503a5f, + 0x00000000000000000058f5302775074e, + 0x00000000000000000058f5302775074e, + true, + true, + false, + true, + ), + // 2^149 + ( + 0x00000000000000007ef0c942f6de3894, + 0x000000000000000000b1ea604ea074bd, + 0x000000000000000000b1ea604eea0e9b, + 0x000000000000000000b1ea604eea0e9b, + false, + true, + false, + true, + ), + // 2^150 + ( + 0x00000000000000009f97ddb3d0b9ca80, + 0x00000000000000000163d4c09d40e97b, + 0x00000000000000000163d4c09dd41d37, + 0x00000000000000000163d4c09dd41d37, + true, + true, + false, + true, + ), + // 2^151 + ( + 0x00000000000000005e49b4d21d02a6a8, + 0x000000000000000002c7a9813a81d2f5, + 0x000000000000000002c7a9813ba83a6d, + 0x000000000000000002c7a9813ba83a6d, + false, + true, + false, + true, + ), + // 2^152 + ( + 0x0000000000000000bc9369a43a054d50, + 0x0000000000000000058f53027503a5ea, + 0x0000000000000000058f5302775074da, + 0x0000000000000000058f5302775074da, + false, + true, + false, + true, + ), + // 2^153 + ( + 0x000000000000000024529cf14a6ba108, + 0x00000000000000000b1ea604ea074bd5, + 0x00000000000000000b1ea604eea0e9b5, + 0x00000000000000000b1ea604eea0e9b5, + true, + true, + false, + true, + ), + // 2^154 + ( + 0x000000000000000048a539e294d74210, + 0x0000000000000000163d4c09d40e97aa, + 0x0000000000000000163d4c09dd41d36a, + 0x0000000000000000163d4c09dd41d36a, + true, + true, + false, + true, + ), + // 2^155 + ( + 0x000000000000000007774e5c94dc703e, + 0x0000000000000000186efaa0a24bda9e, + 0x0000000000000000186efaa08fe5631d, + 0x0000000000000000186efaa08fe5631d, + true, + false, + true, + false, + ), + // 2^156 + ( + 0x00000000000000000eee9cb929b8e07c, + 0x000000000000000030ddf5414497b53c, + 0x000000000000000030ddf5411fcac63a, + 0x000000000000000030ddf5411fcac63a, + true, + false, + true, + false, + ), + // 2^157 + ( + 0x00000000000000006bf5ebf6416052ec, + 0x000000000000000028173ae60ba2a96b, + 0x000000000000000028173ae6553c876d, + 0x000000000000000028173ae6553c876d, + false, + true, + false, + true, + ), + // 2^158 + ( + 0x00000000000000003bba72e4a6e381ee, + 0x00000000000000000b44e317ccdc48e5, + 0x00000000000000000b44e318601004ea, + 0x00000000000000000b44e318601004ea, + true, + true, + false, + true, + ), + // 2^159 + ( + 0x0000000000000000125e3f9f470b1006, + 0x00000000000000002e5fcc84b0b07828, + 0x00000000000000002e5fcc838a49001d, + 0x00000000000000002e5fcc838a49001d, + false, + false, + true, + false, + ), + // 2^160 + ( + 0x00000000000000006516a62a06bbf3d6, + 0x000000000000000017d6065516f7e65e, + 0x000000000000000017d60652ca28f649, + 0x000000000000000017d60652ca28f649, + true, + false, + true, + false, + ), + // 2^161 + ( + 0x00000000000000004978fe7d1c2c401a, + 0x0000000000000000153d860a1c793d35, + 0x0000000000000000153d860eb6171d5e, + 0x0000000000000000153d860eb6171d5e, + false, + true, + false, + true, + ), + // 2^162 + ( + 0x000000000000000080b44dd6f14ba792, + 0x00000000000000001a6e86a011768f87, + 0x00000000000000001a6e8696de3acf34, + 0x00000000000000001a6e8696de3acf34, + true, + false, + true, + false, + ), + // 2^163 + ( + 0x0000000000000000123daf23470cd8a2, + 0x0000000000000000100c8574277beae3, + 0x0000000000000000100c85868df36b88, + 0x0000000000000000100c85868df36b88, + false, + true, + false, + true, + ), + // 2^164 + ( + 0x0000000000000000247b5e468e19b144, + 0x000000000000000020190ae84ef7d5c6, + 0x000000000000000020190b0d1be6d710, + 0x000000000000000020190b0d1be6d710, + false, + true, + false, + true, + ), + // 2^165 + ( + 0x000000000000000048f6bc8d1c336288, + 0x0000000000000000403215d09defab8c, + 0x00000000000000004032161a37cdae20, + 0x00000000000000004032161a37cdae20, + false, + true, + false, + true, + ), + // 2^166 + ( + 0x0000000000000000081a53b1a394b12c, + 0x0000000000000000096ef9c758f2bccb, + 0x0000000000000000096ef9342536b7a1, + 0x0000000000000000096ef9342536b7a1, + false, + false, + true, + false, + ), + // 2^167 + ( + 0x00000000000000001034a76347296258, + 0x000000000000000012ddf38eb1e57996, + 0x000000000000000012ddf2684a6d6f42, + 0x000000000000000012ddf2684a6d6f42, + false, + false, + true, + false, + ), + // 2^168 + ( + 0x000000000000000020694ec68e52c4b0, + 0x000000000000000025bbe71d63caf32c, + 0x000000000000000025bbe4d094dade84, + 0x000000000000000025bbe4d094dade84, + false, + false, + true, + false, + ), + // 2^169 + ( + 0x0000000000000000490087db782c8a82, + 0x0000000000000000068e3b867d2cdc66, + 0x0000000000000000068e36ecdf4cb317, + 0x0000000000000000068e36ecdf4cb317, + true, + false, + true, + false, + ), + // 2^170 + ( + 0x000000000000000092010fb6f0591504, + 0x00000000000000000d1c770cfa59b8cc, + 0x00000000000000000d1c6dd9be99662e, + 0x00000000000000000d1c6dd9be99662e, + true, + false, + true, + false, + ), + // 2^171 + ( + 0x0000000000000000105bd49cb70e0242, + 0x00000000000000002ab0a49a55b59859, + 0x00000000000000002ab0b700cd363d94, + 0x00000000000000002ab0b700cd363d94, + true, + true, + false, + true, + ), + // 2^172 + ( + 0x0000000000000000691b7c2f26b60f5e, + 0x00000000000000001077b680610226c0, + 0x00000000000000001077db4d50037137, + 0x00000000000000001077db4d50037137, + false, + true, + false, + true, + ), + // 2^173 + ( + 0x0000000000000000416f5272dc38090a, + 0x000000000000000023fa25b38864bc71, + 0x000000000000000023f9dc19aa622782, + 0x000000000000000023f9dc19aa622782, + true, + false, + true, + false, + ), + // 2^174 + ( + 0x000000000000000090c7a5eb713415b2, + 0x0000000000000000030ab8b2c6606ef1, + 0x0000000000000000030a257f0a5b4514, + 0x0000000000000000030a257f0a5b4514, + false, + false, + true, + false, + ), + // 2^175 + ( + 0x00000000000000007bea2462dc0e1044, + 0x0000000000000000061571658cc0dde1, + 0x000000000000000006144afe14b68a27, + 0x000000000000000006144afe14b68a27, + true, + false, + true, + false, + ), + // 2^176 + ( + 0x00000000000000001bd2020b7188073e, + 0x000000000000000038beafe930e74e2f, + 0x000000000000000038c0fcb820fbf5a2, + 0x000000000000000038c0fcb820fbf5a2, + false, + true, + false, + true, + ), + // 2^177 + ( + 0x0000000000000000522f2151b1c20568, + 0x00000000000000001855c59633037785, + 0x000000000000000018512bf852da289d, + 0x000000000000000018512bf852da289d, + true, + false, + true, + false, + ), + // 2^178 + ( + 0x00000000000000006f48082dc6201cf6, + 0x0000000000000000143e0787e4621ae7, + 0x000000000000000014473ac3a4b4b8b6, + 0x000000000000000014473ac3a4b4b8b6, + false, + true, + false, + true, + ), + // 2^179 + ( + 0x000000000000000035163a759d63edda, + 0x00000000000000001c6d83a481a4d423, + 0x00000000000000001c5b1d2d00ff9884, + 0x00000000000000001c5b1d2d00ff9884, + true, + false, + true, + false, + ), + // 2^180 + ( + 0x0000000000000000a979d5e5eedc4c12, + 0x00000000000000000c0e8b6b471f61ab, + 0x00000000000000000c33585a4869d8e8, + 0x00000000000000000c33585a4869d8e8, + false, + true, + false, + true, + ), + // 2^181 + ( + 0x00000000000000004a85c46de0bda384, + 0x0000000000000000181d16d68e3ec357, + 0x00000000000000001866b0b490d3b1d1, + 0x00000000000000001866b0b490d3b1d1, + true, + true, + false, + true, + ), + // 2^182 + ( + 0x00000000000000000b3863732ca93326, + 0x000000000000000014af65072deb8344, + 0x0000000000000000141c314b28c1a64f, + 0x0000000000000000141c314b28c1a64f, + true, + false, + true, + false, + ), + // 2^183 + ( + 0x00000000000000001670c6e65952664c, + 0x0000000000000000295eca0e5bd70688, + 0x00000000000000002838629651834c9e, + 0x00000000000000002838629651834c9e, + true, + false, + true, + false, + ), + // 2^184 + ( + 0x0000000000000000180804e797c43d5a, + 0x00000000000000007bff2400278d10c4, + 0x00000000000000000b873278589d8f4c, + 0x00000000000000005070c52ca306993d, + false, + true, + true, + false, + ), + // 2^185 + ( + 0x0000000000000000301009cf2f887ab4, + 0x00000000000000001ba802d0da8a063d, + 0x0000000000000000170e64f0b13b1e97, + 0x0000000000000000170e64f0b13b1e97, + false, + false, + true, + false, + ), + // 2^186 + ( + 0x000000000000000029b311ca35c11e7a, + 0x00000000000000000d998d129554fd78, + 0x000000000000000016ccc8d2e7f2ccc3, + 0x000000000000000016ccc8d2e7f2ccc3, + true, + true, + false, + true, + ), + // 2^187 + ( + 0x0000000000000000366d01d4294fd6ee, + 0x000000000000000029b6788f1fbf0f02, + 0x00000000000000001750010e7a83706b, + 0x00000000000000001750010e7a83706b, + false, + false, + true, + false, + ), + // 2^188 + ( + 0x00000000000000001cf921c042326606, + 0x00000000000000000e835e69f5151412, + 0x0000000000000000164990975562291b, + 0x0000000000000000164990975562291b, + true, + false, + false, + true, + ), + // 2^189 + ( + 0x000000000000000039f243808464cc0c, + 0x00000000000000001d06bcd3ea2a2824, + 0x00000000000000002c93212eaac45236, + 0x00000000000000002c93212eaac45236, + true, + false, + false, + true, + ), + // 2^190 + ( + 0x00000000000000002efaf44cbe608e27, + 0x00000000000000004fc5abc0c07dc39a, + 0x000000000000000030ace30b3f496f77, + 0x0000000000000000143cafa90b1f9a7b, + true, + true, + true, + true, + ), + // 2^191 + ( + 0x0000000000000000190c55e53258125d, + 0x00000000000000002f31609b5e3f96a0, + 0x00000000000000001c7033623429d4fc, + 0x000000000000000028795f52163f34f6, + true, + false, + true, + true, + ), + // 2^192 + ( + 0x000000000000000012d0e6e9e5b8e537, + 0x000000000000000019792e827216234f, + 0x00000000000000000c092befe2155ff9, + 0x000000000000000038e066c46853a9f7, + false, + false, + false, + false, + ), + // 2^193 + ( + 0x000000000000000064315794c9604974, + 0x000000000000000011f735af663cc354, + 0x00000000000000005cfbea940e93c9e3, + 0x00000000000000002cd73ad4863e49fd, + true, + true, + false, + false, + ), + // 2^194 + ( + 0x0000000000000000065a08f34c7a8aec, + 0x000000000000000020fb27557def834a, + 0x000000000000000014c4e2f4c2138a0e, + 0x00000000000000003024afbf88557fe9, + false, + false, + true, + true, + ), + // 2^195 + ( + 0x0000000000000000519da49ae35e1fc9, + 0x000000000000000002f344094e8a035d, + 0x00000000000000001b5fcccac641f5d5, + 0x00000000000000002989c5e984271411, + false, + true, + false, + false, + ), + // 2^196 + ( + 0x00000000000000005e51b6817c5335a1, + 0x00000000000000003f030aa1ad550337, + 0x00000000000000000e29f91ebde51e47, + 0x000000000000000036bf99958c83ebc1, + false, + false, + true, + true, + ), + // 2^197 + ( + 0x000000000000000012194b19e694b292, + 0x00000000000000000bcd10253a280d75, + 0x00000000000000001c53f23d7bca3c8e, + 0x00000000000000002895a076ce9ecd91, + true, + true, + true, + true, + ), + // 2^198 + ( + 0x000000000000000020b6fc807d3fa4cd, + 0x00000000000000005c83b2febeb924db, + 0x00000000000000000c41ae3952d490d5, + 0x000000000000000038a7e47af79478c1, + false, + true, + false, + false, + ), + // 2^199 + ( + 0x0000000000000000037b99b34fe9c058, + 0x000000000000000015b5521f61c8d41e, + 0x00000000000000002c663641a4bfe846, + 0x0000000000000000714fc8f5ef28f181, + true, + false, + true, + false, + ), + // 2^200 + ( + 0x00000000000000003df25f4daa958943, + 0x0000000000000000197eee7586d761b6, + 0x000000000000000013e2d9ceff16c69c, + 0x00000000000000003106b8e54b5244c2, + false, + true, + true, + true, + ), + // 2^201 + ( + 0x00000000000000000dee66cd3fa7015d, + 0x000000000000000011ebb5c93cba4685, + 0x00000000000000001d23df164c3b7cba, + 0x00000000000000001d23df164c3b7f93, + true, + false, + false, + true, + ), + // 2^202 + ( + 0x00000000000000001bdccd9a7f4e02ba, + 0x000000000000000023d76b9279748d0a, + 0x00000000000000003a47be2c9876f974, + 0x00000000000000003a47be2c9876ff26, + true, + false, + false, + true, + ), + // 2^203 + ( + 0x00000000000000007ca32de949050f66, + 0x000000000000000002c54470a8801023, + 0x00000000000000002fa5e9a4e684e8f7, + 0x00000000000000001543a90f63e41596, + true, + false, + false, + false, + ), + // 2^204 + ( + 0x00000000000000001a5feefe979a08f9, + 0x0000000000000000058a88e151002047, + 0x00000000000000002a87521ec7c841f5, + 0x00000000000000002a87521ec7c82b2c, + false, + false, + true, + false, + ), + // 2^205 + ( + 0x00000000000000005513476b659e01f0, + 0x000000000000000039d480f1a868c964, + 0x000000000000000010251189452779f9, + 0x00000000000000001025118945274c67, + true, + true, + true, + false, + ), + // 2^206 + ( + 0x00000000000000002496294613ff19f4, + 0x0000000000000000162a23854400811b, + 0x0000000000000000204a23128a4ef3f2, + 0x0000000000000000249f6fa1c01a7123, + false, + false, + true, + true, + ), + // 2^207 + ( + 0x0000000000000000492c528c27fe33e8, + 0x00000000000000002c54470a88010236, + 0x000000000000000040944625149de7e4, + 0x0000000000000000493edf438034e246, + false, + false, + true, + true, + ), + // 2^208 + ( + 0x00000000000000003c6413048f3eb604, + 0x000000000000000013befb60c598fa7a, + 0x00000000000000004d942bd2b5ff4e0c, + 0x000000000000000008aa991e6b97b0a9, + true, + false, + false, + true, + ), + // 2^209 + ( + 0x000000000000000055f49213c0bdb1cb, + 0x00000000000000001d6b9bf2bf3714fc, + 0x000000000000000033946077733c81bc, + 0x0000000000000000787df32bbda2b290, + false, + true, + true, + false, + ), + // 2^210 + ( + 0x00000000000000002215febeeca94fb5, + 0x00000000000000000a125acecbfadff8, + 0x0000000000000000223f2e3a9c0ff987, + 0x00000000000000006793f72df8c7cc96, + false, + false, + true, + true, + ), + // 2^211 + ( + 0x000000000000000000bd953671166a88, + 0x00000000000000001424b59d97f5bff0, + 0x00000000000000004554c8f35cb220d5, + 0x0000000000000000006b363f12547b58, + true, + false, + false, + true, + ), + // 2^212 + ( + 0x0000000000000000017b2a6ce22cd510, + 0x00000000000000001ca027791a7d8a11, + 0x00000000000000004413263625d6dc2a, + 0x000000000000000045bfff326f1200a2, + true, + true, + true, + true, + ), + // 2^213 + ( + 0x000000000000000047dfe78e0ec2b412, + 0x000000000000000039404ef234fb1422, + 0x000000000000000001acd8fc49245b8f, + 0x0000000000000000433cb9b801171c90, + true, + true, + false, + false, + ), + // 2^214 + ( + 0x00000000000000003efce900c1b5b5b0, + 0x0000000000000000175287842adbeb9f, + 0x00000000000000000359b1f89248b71e, + 0x0000000000000000418fe0bbb7c52f2f, + false, + false, + false, + false, + ), + // 2^215 + ( + 0x000000000000000050c2e61b5bcfb273, + 0x00000000000000002ea50f0855b7d73d, + 0x000000000000000006b363f124916e3c, + 0x00000000000000004b9cf6a56fb0bf76, + true, + false, + false, + true, + ), + // 2^216 + ( + 0x00000000000000002d36ebe6279bb8ee, + 0x00000000000000002c890757e9626568, + 0x00000000000000000d66c7e24922dc77, + 0x000000000000000052505a9694f874fa, + false, + true, + false, + true, + ), + // 2^217 + ( + 0x00000000000000001584451804ce67e9, + 0x000000000000000014287bfb885bc0de, + 0x00000000000000002a1c02efb8235102, + 0x00000000000000006f0595a3ffb33dd0, + false, + true, + true, + false, + ), + // 2^218 + ( + 0x000000000000000019e1088440cc3a1e, + 0x00000000000000002850f7f710b781bd, + 0x0000000000000000359b1f89248b71de, + 0x00000000000000000f4e732b202b5dcd, + true, + true, + false, + false, + ), + // 2^219 + ( + 0x0000000000000000112781abc8d095b5, + 0x00000000000000003931357a73631069, + 0x00000000000000001e9ce6564bbb3026, + 0x0000000000000000264cac5e0a124e58, + false, + false, + true, + true, + ), + // 2^220 + ( + 0x0000000000000000229a8f5cb8c7de87, + 0x00000000000000001770ba73ae0bf311, + 0x00000000000000004c9958bbfd5bb396, + 0x000000000000000007afc607c9bb92be, + true, + true, + false, + true, + ), + // 2^221 + ( + 0x0000000000000000004b8c052726b31d, + 0x00000000000000005af1b08138ba2dc1, + 0x00000000000000000f5f8c0f65e5534a, + 0x000000000000000054491ec3dde02f6e, + true, + false, + false, + true, + ), + // 2^222 + ( + 0x0000000000000000893c0d5e4684ada8, + 0x000000000000000018d9571a6dc6c251, + 0x0000000000000000262a7a957e9e635e, + 0x0000000000000000262a7a95237abef8, + false, + true, + true, + false, + ), + // 2^223 + ( + 0x00000000000000004617c2c8e703d666, + 0x00000000000000001336e47f6edb854e, + 0x0000000000000000076b6276b2d3bcca, + 0x00000000000000003d7e303e4ddc95f3, + true, + false, + true, + true, + ), + // 2^224 + ( + 0x00000000000000004745f2dd839ea2db, + 0x00000000000000001e7bc9b56cb1ff55, + 0x00000000000000003612cdc6e4c1905d, + 0x00000000000000000ed6c4ebf918e7fd, + true, + true, + false, + false, + ), + // 2^225 + ( + 0x000000000000000004b8c052726b31d4, + 0x000000000000000007f1ff4971050b48, + 0x0000000000000000273c08d97f1a16c9, + 0x000000000000000062971c8c3c9ad9eb, + true, + false, + false, + false, + ), + // 2^226 + ( + 0x00000000000000003b78120f6592a64a, + 0x00000000000000000fe3fe92e20a1690, + 0x00000000000000003b5b13b5969de651, + 0x0000000000000000098e7f04660569fe, + false, + false, + true, + true, + ), + // 2^227 + ( + 0x000000000000000012e30149c9acc74e, + 0x00000000000000002521958e8654dcd2, + 0x000000000000000031cc94b6e2d2c2b1, + 0x0000000000000000580690bd1673dded, + true, + true, + true, + true, + ), + // 2^228 + ( + 0x00000000000000001f239020b70f7b54, + 0x000000000000000005599868c240afb2, + 0x00000000000000001eaf96b97b3c7b70, + 0x000000000000000063992956fcbc6beb, + false, + true, + true, + false, + ), + // 2^229 + ( + 0x000000000000000006a27272dc4a1348, + 0x00000000000000000ab330d184815f65, + 0x00000000000000004c73f7f59e591d02, + 0x0000000000000000078a656ee5c245fd, + true, + true, + false, + true, + ), + // 2^230 + ( + 0x00000000000000000d44e4e5b8942691, + 0x00000000000000002f83311141664b28, + 0x000000000000000035d4c831a288e3cf, + 0x000000000000000035d4c7d67ee47df6, + true, + false, + true, + false, + ), + // 2^231 + ( + 0x00000000000000006f495b9d23a9c6c2, + 0x00000000000000002accc34612057d93, + 0x00000000000000001e2995054fc04c43, + 0x00000000000000001e2995bb970917f5, + false, + true, + false, + true, + ), + // 2^232 + ( + 0x000000000000000035139396e2509a42, + 0x000000000000000010aff3d7d9a1f135, + 0x0000000000000000089668a9aae8716a, + 0x00000000000000000896673d1c56da06, + true, + true, + true, + false, + ), + // 2^233 + ( + 0x00000000000000006a27272dc4a13484, + 0x0000000000000000215fe7afb343e26a, + 0x0000000000000000112cd15355d0e2d4, + 0x0000000000000000112cce7a38adb40c, + true, + true, + true, + false, + ), + // 2^234 + ( + 0x00000000000000000591963eaa074b34, + 0x0000000000000000471356092e4a4f0f, + 0x00000000000000002259a2a6aba1c5a8, + 0x0000000000000000228ff5bfd90da1d9, + true, + false, + true, + true, + ), + // 2^235 + ( + 0x0000000000000000500cbf319e77a059, + 0x0000000000000000045386a9c7c28a3b, + 0x0000000000000000451fe01b3d8e8892, + 0x0000000000000000003658cb67b239c0, + true, + false, + false, + true, + ), + // 2^236 + ( + 0x0000000000000000738ccc6decb4e713, + 0x000000000000000008a70d538f851477, + 0x0000000000000000006c9acde64afd41, + 0x0000000000000000006cb196cf647380, + false, + false, + false, + true, + ), + // 2^237 + ( + 0x00000000000000002c8cb1f5503a599f, + 0x0000000000000000339b780d2b5ee104, + 0x000000000000000044105d187dd30f6f, + 0x000000000000000045c2f5e1e931f0f2, + true, + true, + true, + true, + ), + // 2^238 + ( + 0x000000000000000030b9c17df45d60a4, + 0x0000000000000000229c354e3e1451db, + 0x000000000000000001b26b37992bf505, + 0x000000000000000001b2c65b3d91ce01, + false, + false, + false, + true, + ), + // 2^239 + ( + 0x0000000000000000285fa26cac17529a, + 0x0000000000000000004ed7e831bf99c4, + 0x0000000000000000484e69237cc0f3fb, + 0x0000000000000000484f1f6ac58ca5f3, + true, + false, + false, + true, + ), + // 2^240 + ( + 0x00000000000000000bd5b2250dc59b44, + 0x0000000000000000009dafd0637f3389, + 0x000000000000000006c9acde64afd414, + 0x00000000000000003e1e79475421d1ed, + true, + false, + false, + false, + ), + // 2^241 + ( + 0x000000000000000017ab644a1b8b3688, + 0x0000000000000000013b5fa0c6fe6712, + 0x00000000000000000d9359bcc95fa828, + 0x00000000000000007c3cf28ea843a3da, + true, + false, + false, + false, + ), + // 2^242 + ( + 0x00000000000000005a7c5cd45dbba6d4, + 0x0000000000000000476051f5d865d815, + 0x000000000000000029c2df3ab7a9b9a2, + 0x00000000000000006015f8682385ea02, + false, + false, + true, + true, + ), + // 2^243 + ( + 0x00000000000000002b25944026a539c5, + 0x00000000000000003ffc14312e6f6daa, + 0x00000000000000000e9c2bc124ea6953, + 0x00000000000000000e90c74c982f49d0, + false, + true, + true, + false, + ), + // 2^244 + ( + 0x00000000000000003387fce84787a059, + 0x00000000000000003b0e95ae1275d163, + 0x000000000000000027b13b320094374c, + 0x0000000000000000620b214d7ac79d91, + true, + true, + false, + false, + ), + // 2^245 + ( + 0x000000000000000022c32b9805c2d331, + 0x0000000000000000589f8cc0ba4f7b0e, + 0x00000000000000000a78e3afb6bf64a7, + 0x00000000000000000aa67581e9abe2b2, + false, + false, + false, + true, + ), + // 2^246 + ( + 0x0000000000000000009cc47bc11c9c71, + 0x00000000000000001d7d9e9b6a9c27b8, + 0x00000000000000002ff7cb54dcea40a4, + 0x0000000000000000154ceb03d357c564, + false, + true, + true, + true, + ), + // 2^247 + ( + 0x000000000000000043b009bcc82fd10f, + 0x00000000000000004ed7e831bf99c472, + 0x000000000000000029e38ebedafd929b, + 0x00000000000000006f8368bbf11894b9, + true, + false, + false, + true, + ), + // 2^248 + ( + 0x0000000000000000027311ef047271c4, + 0x0000000000000000310ce7b9600794ef, + 0x00000000000000000edd8ac96b921b44, + 0x0000000000000000104a195b02f60b9e, + false, + true, + false, + true, + ), + // 2^249 + ( + 0x00000000000000008eb949469db6f76a, + 0x00000000000000001d303cbe75a61fec, + 0x0000000000000000272e7d217344d369, + 0x000000000000000024555ffe447cf2b5, + false, + true, + true, + false, + ), + // 2^250 + ( + 0x00000000000000003b1d4af8389f42e2, + 0x00000000000000000a8919375f1cca18, + 0x00000000000000000973678e9c209ce0, + 0x00000000000000004128656c0bd82e79, + true, + false, + true, + true, + ), + // 2^251 + ( + 0x00000000000000003151033c26d57bd3, + 0x00000000000000002fd760458c2f75c1, + 0x00000000000000003202c3971227d031, + 0x000000000000000007825a907d21b6f1, + true, + true, + false, + false, + ), + // 2^252 + ( + 0x00000000000000001db873c40341edb5, + 0x00000000000000002a2464dd7c732861, + 0x000000000000000025cd9e3a70827380, + 0x000000000000000035e4dd9350259c10, + true, + false, + true, + true, + ), + // 2^253 + ( + 0x00000000000000000978ab2c43e52e87, + 0x00000000000000000f5f3706ae7d46d1, + 0x000000000000000006b1a9c0969bdd0f, + 0x00000000000000001e096a41f486dbc3, + false, + false, + true, + false, + ), +]; diff --git a/crates/jolt-crypto/src/arkworks/bn254/gt.rs b/crates/jolt-crypto/src/arkworks/bn254/gt.rs new file mode 100644 index 000000000..3515c1c97 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/gt.rs @@ -0,0 +1,199 @@ +use std::fmt::Debug; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use ark_bn254::Fq12; +use ark_ff::{Field as ArkField, PrimeField}; +use jolt_field::Field; + +use jolt_transcript::{AppendToTranscript, Transcript}; + +use crate::JoltGroup; + +use super::field_to_fr; + +/// BN254 target group element (pairing output). +/// +/// GT is mathematically multiplicative (Fq12 multiplication), but we expose it +/// with **additive notation** via `JoltGroup` for uniformity with G1/G2: +/// +/// | JoltGroup operation | GT semantics | +/// |---------------------|---------------------| +/// | `Add` (`+`) | Fq12 multiplication | +/// | `Neg` (`-x`) | Fq12 inverse | +/// | `Sub` (`-`) | Fq12 mul-by-inverse | +/// | `identity()` | `Fq12::ONE` | +/// | `double()` | Fq12 squaring | +/// +/// `Mul`/`MulAssign` are also provided as convenience aliases that map directly +/// to the same Fq12 multiplication, for callers who prefer multiplicative +/// notation in pairing contexts. +#[derive(Clone, Copy, Eq, PartialEq)] +#[repr(transparent)] +pub struct Bn254GT(pub(crate) Fq12); + +impl Debug for Bn254GT { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Bn254GT").field(&self.0).finish() + } +} + +impl From for Bn254GT { + #[inline(always)] + fn from(inner: Fq12) -> Self { + Self(inner) + } +} + +impl From for Fq12 { + #[inline(always)] + fn from(w: Bn254GT) -> Self { + w.0 + } +} + +impl Default for Bn254GT { + #[inline(always)] + fn default() -> Self { + Self(Fq12::ONE) + } +} + +// GT's additive notation maps to Fq12 multiplication by design. +#[allow(clippy::suspicious_arithmetic_impl, clippy::suspicious_op_assign_impl)] +const _: () = { + impl Add for Bn254GT { + type Output = Self; + #[inline(always)] + fn add(self, rhs: Self) -> Self { + Self(self.0 * rhs.0) + } + } + + impl<'a> Add<&'a Bn254GT> for Bn254GT { + type Output = Self; + #[inline(always)] + fn add(self, rhs: &'a Bn254GT) -> Self { + Self(self.0 * rhs.0) + } + } + + impl Sub for Bn254GT { + type Output = Self; + #[inline(always)] + fn sub(self, rhs: Self) -> Self { + Self(self.0 * rhs.0.inverse().expect("GT element has no inverse")) + } + } + + impl<'a> Sub<&'a Bn254GT> for Bn254GT { + type Output = Self; + #[inline(always)] + fn sub(self, rhs: &'a Bn254GT) -> Self { + Self(self.0 * rhs.0.inverse().expect("GT element has no inverse")) + } + } + + impl Neg for Bn254GT { + type Output = Self; + #[inline(always)] + fn neg(self) -> Self { + Self(self.0.inverse().expect("GT element has no inverse")) + } + } + + impl AddAssign for Bn254GT { + #[inline(always)] + fn add_assign(&mut self, rhs: Self) { + self.0 *= rhs.0; + } + } + + impl SubAssign for Bn254GT { + #[inline(always)] + fn sub_assign(&mut self, rhs: Self) { + self.0 *= rhs.0.inverse().expect("GT element has no inverse"); + } + } +}; // end #[allow(clippy::suspicious_*)] + +impl Mul for Bn254GT { + type Output = Self; + #[inline(always)] + fn mul(self, rhs: Self) -> Self { + Self(self.0 * rhs.0) + } +} + +impl MulAssign for Bn254GT { + #[inline(always)] + fn mul_assign(&mut self, rhs: Self) { + self.0 *= rhs.0; + } +} + +impl AppendToTranscript for Bn254GT { + fn append_to_transcript(&self, transcript: &mut T) { + use ark_serialize::CanonicalSerialize; + let mut buf = Vec::new(); + self.0 + .serialize_compressed(&mut buf) + .expect("GT serialization cannot fail"); + transcript.append_bytes(&buf); + } +} + +impl JoltGroup for Bn254GT { + #[inline(always)] + fn identity() -> Self { + Self(Fq12::ONE) + } + + #[inline(always)] + fn is_identity(&self) -> bool { + self.0 == Fq12::ONE + } + + #[inline(always)] + fn double(&self) -> Self { + Self(self.0.square()) + } + + #[inline] + fn scalar_mul(&self, scalar: &F) -> Self { + // GT exponentiation: self^scalar (written additively as scalar * self). + let fr = field_to_fr(scalar); + Self(self.0.pow(fr.into_bigint())) + } + + #[inline] + fn msm(bases: &[Self], scalars: &[F]) -> Self { + debug_assert_eq!(bases.len(), scalars.len()); + // GT "MSM" is Π bases[i]^scalars[i] (written additively as Σ scalars[i] * bases[i]). + let mut acc = Fq12::ONE; + for (base, scalar) in bases.iter().zip(scalars.iter()) { + let fr = field_to_fr(scalar); + acc *= base.0.pow(fr.into_bigint()); + } + Self(acc) + } +} + +impl serde::Serialize for Bn254GT { + fn serialize(&self, serializer: S) -> Result { + use ark_serialize::CanonicalSerialize; + let mut buf = Vec::new(); + self.0 + .serialize_compressed(&mut buf) + .map_err(serde::ser::Error::custom)?; + serializer.serialize_bytes(&buf) + } +} + +impl<'de> serde::Deserialize<'de> for Bn254GT { + fn deserialize>(deserializer: D) -> Result { + use ark_serialize::CanonicalDeserialize; + let buf = >::deserialize(deserializer)?; + let inner = Fq12::deserialize_compressed(&buf[..]).map_err(serde::de::Error::custom)?; + Ok(Self(inner)) + } +} diff --git a/crates/jolt-crypto/src/arkworks/bn254/mod.rs b/crates/jolt-crypto/src/arkworks/bn254/mod.rs new file mode 100644 index 000000000..0175bb0f5 --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/bn254/mod.rs @@ -0,0 +1,257 @@ +//! Concrete BN254 curve implementation. +//! +//! This module wraps the arkworks `ark-bn254` crate behind the generic +//! `JoltGroup` and `PairingGroup` traits. Arkworks types never appear in +//! the public API — all conversions happen internally. + +/// Generates a `#[repr(transparent)]` wrapper over an arkworks projective curve type, +/// with all operator impls, serde, `AppendToTranscript`, `JoltGroup`, compile-time +/// size assertions, and a safe `into_inner` accessor. +macro_rules! impl_jolt_group_wrapper { + ($wrapper:ident, $projective:ty, $affine:ty, $doc:literal) => { + use std::fmt::Debug; + use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; + + use ark_ec::{AdditiveGroup, CurveGroup, VariableBaseMSM}; + use ark_ff::{PrimeField, Zero}; + use jolt_field::Field; + + use jolt_transcript::{AppendToTranscript, Transcript}; + + use crate::JoltGroup; + + use super::field_to_fr; + + #[doc = $doc] + #[derive(Clone, Copy, Default, Eq, PartialEq)] + #[repr(transparent)] + pub struct $wrapper(pub(crate) $projective); + + // SAFETY: $wrapper is #[repr(transparent)] over $projective. + // Unsafe pointer casts in batch_addition and glv rely on this. + const _: () = + assert!(std::mem::size_of::<$wrapper>() == std::mem::size_of::<$projective>()); + + impl $wrapper { + /// Unwraps into the inner arkworks projective type. + #[inline(always)] + pub fn into_inner(self) -> $projective { + self.0 + } + } + + impl Debug for $wrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let affine = self.0.into_affine(); + f.debug_tuple(stringify!($wrapper)).field(&affine).finish() + } + } + + impl From<$projective> for $wrapper { + #[inline(always)] + fn from(inner: $projective) -> Self { + Self(inner) + } + } + + impl From<$wrapper> for $projective { + #[inline(always)] + fn from(w: $wrapper) -> Self { + w.0 + } + } + + impl Add for $wrapper { + type Output = Self; + #[inline(always)] + fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } + } + + impl<'a> Add<&'a $wrapper> for $wrapper { + type Output = Self; + #[inline(always)] + fn add(self, rhs: &'a $wrapper) -> Self { + Self(self.0 + rhs.0) + } + } + + impl Sub for $wrapper { + type Output = Self; + #[inline(always)] + fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0) + } + } + + impl<'a> Sub<&'a $wrapper> for $wrapper { + type Output = Self; + #[inline(always)] + fn sub(self, rhs: &'a $wrapper) -> Self { + Self(self.0 - rhs.0) + } + } + + impl Neg for $wrapper { + type Output = Self; + #[inline(always)] + fn neg(self) -> Self { + Self(-self.0) + } + } + + impl AddAssign for $wrapper { + #[inline(always)] + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } + } + + impl SubAssign for $wrapper { + #[inline(always)] + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + } + } + + impl serde::Serialize for $wrapper { + fn serialize(&self, serializer: S) -> Result { + use ark_serialize::CanonicalSerialize; + let mut buf = Vec::new(); + self.0 + .serialize_compressed(&mut buf) + .map_err(serde::ser::Error::custom)?; + serializer.serialize_bytes(&buf) + } + } + + impl<'de> serde::Deserialize<'de> for $wrapper { + fn deserialize>(deserializer: D) -> Result { + use ark_serialize::CanonicalDeserialize; + let buf = >::deserialize(deserializer)?; + let inner = <$projective>::deserialize_compressed(&buf[..]) + .map_err(serde::de::Error::custom)?; + Ok(Self(inner)) + } + } + + impl AppendToTranscript for $wrapper { + fn append_to_transcript(&self, transcript: &mut T) { + use ark_serialize::CanonicalSerialize; + let mut buf = Vec::new(); + self.0 + .serialize_compressed(&mut buf) + .expect(concat!(stringify!($wrapper), " serialization cannot fail")); + transcript.append_bytes(&buf); + } + } + + impl JoltGroup for $wrapper { + #[inline(always)] + fn identity() -> Self { + Self(<$projective>::zero()) + } + + #[inline(always)] + fn is_identity(&self) -> bool { + self.0.is_zero() + } + + #[inline(always)] + fn double(&self) -> Self { + Self(AdditiveGroup::double(&self.0)) + } + + #[inline] + fn scalar_mul(&self, scalar: &F) -> Self { + Self(self.0 * field_to_fr(scalar)) + } + + #[inline] + fn msm(bases: &[Self], scalars: &[F]) -> Self { + debug_assert_eq!(bases.len(), scalars.len()); + let affines: Vec<$affine> = bases.iter().map(|b| b.0.into_affine()).collect(); + let fr_scalars: Vec = scalars.iter().map(field_to_fr).collect(); + let bigints: Vec<_> = fr_scalars.iter().map(|s| s.into_bigint()).collect(); + Self(<$projective>::msm_bigint(&affines, &bigints)) + } + } + }; +} + +pub(crate) use impl_jolt_group_wrapper; + +mod g1; +mod g2; +mod gt; + +#[doc(hidden)] +pub mod batch_addition; +#[doc(hidden)] +pub mod glv; + +pub use g1::Bn254G1; +pub use g2::Bn254G2; +pub use gt::Bn254GT; + +use ark_bn254::Bn254 as ArkBn254; +use ark_ec::pairing::Pairing; +use ark_ec::CurveGroup; +use ark_ff::PrimeField as _; +use jolt_field::Field; + +use crate::PairingGroup; + +/// BN254 pairing-friendly curve. +#[derive(Clone, Debug, Default)] +pub struct Bn254; + +impl Bn254 { + /// Standard G1 generator. Useful for tests and PCS setup code. + pub fn g1_generator() -> Bn254G1 { + use ark_ec::AffineRepr; + Bn254G1(ark_bn254::G1Affine::generator().into()) + } + + /// Standard G2 generator. Useful for tests and PCS setup code. + pub fn g2_generator() -> Bn254G2 { + use ark_ec::AffineRepr; + Bn254G2(ark_bn254::G2Affine::generator().into()) + } + + /// Samples a uniformly random G1 element. + pub fn random_g1(rng: &mut R) -> Bn254G1 { + use ark_std::UniformRand; + Bn254G1(ark_bn254::G1Projective::rand(rng)) + } +} + +impl PairingGroup for Bn254 { + type ScalarField = jolt_field::Fr; + type G1 = Bn254G1; + type G2 = Bn254G2; + type GT = Bn254GT; + + fn pairing(g1: &Self::G1, g2: &Self::G2) -> Self::GT { + Bn254GT(ArkBn254::pairing(g1.0, g2.0).0) + } + + fn multi_pairing(g1s: &[Self::G1], g2s: &[Self::G2]) -> Self::GT { + debug_assert_eq!(g1s.len(), g2s.len()); + let g1_affines: Vec = g1s.iter().map(|g| g.0.into_affine()).collect(); + let g2_affines: Vec = g2s.iter().map(|g| g.0.into_affine()).collect(); + Bn254GT(ArkBn254::multi_pairing(&g1_affines, &g2_affines).0) + } +} + +/// Converts a generic `Field` element to an arkworks `Fr` via serialization. +/// +/// This is the bridge between jolt-field's backend-agnostic `Field` trait and +/// arkworks' concrete scalar type. The conversion goes through little-endian +/// byte serialization. +#[inline] +pub(crate) fn field_to_fr(f: &F) -> ark_bn254::Fr { + let bytes = f.to_bytes(); + ark_bn254::Fr::from_le_bytes_mod_order(&bytes) +} diff --git a/crates/jolt-crypto/src/arkworks/mod.rs b/crates/jolt-crypto/src/arkworks/mod.rs new file mode 100644 index 000000000..cea64234e --- /dev/null +++ b/crates/jolt-crypto/src/arkworks/mod.rs @@ -0,0 +1,5 @@ +//! Arkworks backend implementations. +//! +//! This module contains group implementations using the arkworks library. + +pub mod bn254; diff --git a/crates/jolt-crypto/src/commitment.rs b/crates/jolt-crypto/src/commitment.rs new file mode 100644 index 000000000..279a55b6c --- /dev/null +++ b/crates/jolt-crypto/src/commitment.rs @@ -0,0 +1,97 @@ +use std::fmt::Debug; + +use jolt_field::Field; +use jolt_transcript::AppendToTranscript; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +/// Base commitment abstraction: defines only the output type. +/// +/// This is the root of the commitment trait hierarchy, shared by both +/// vector commitments ([`JoltCommitment`], `VectorCommitment`) and +/// polynomial commitment schemes (`jolt_openings::CommitmentScheme`). +/// The `Output` associated type is the single piece of connective tissue +/// between these different levels of abstraction. +pub trait Commitment { + /// The commitment value (e.g., a group element, a Merkle root, a lattice vector). + type Output: Clone + Debug + Eq + Send + Sync + 'static + Serialize + DeserializeOwned; +} + +/// Backend-agnostic vector commitment. +/// +/// Abstracts the ability to commit to a vector of field elements with a +/// blinding factor. ZK protocols (BlindFold, sumcheck) should be generic +/// over this trait rather than hardcoded to Pedersen or elliptic curves. +/// +/// The `Setup` associated type represents transparent, shared parameters +/// (e.g., generators from a URS, lattice parameters). Setup data is +/// expected to be derivable from or shared with a polynomial commitment +/// scheme's structured reference string. +pub trait JoltCommitment: Clone + Send + Sync + 'static { + /// Transparent setup parameters (generators, public parameters, etc.). + type Setup: Clone + Send + Sync; + + /// The commitment output (e.g., a group element, a lattice vector). + /// + /// Requires [`AppendToTranscript`] so commitments can be absorbed + /// into Fiat-Shamir transcripts during ZK sumcheck. + type Commitment: Clone + + Copy + + Debug + + Default + + Eq + + Send + + Sync + + 'static + + Serialize + + for<'de> Deserialize<'de> + + AppendToTranscript; + + /// Maximum number of values this setup can commit to. + #[must_use] + fn capacity(setup: &Self::Setup) -> usize; + + /// Commits to `values` with the given `blinding` factor. + /// + /// # Panics + /// + /// May panic if `values.len()` exceeds [`Self::capacity()`]. + #[must_use] + fn commit(setup: &Self::Setup, values: &[F], blinding: &F) -> Self::Commitment; + + /// Returns `true` if `commitment` opens to `(values, blinding)`. + #[must_use] + fn verify( + setup: &Self::Setup, + commitment: &Self::Commitment, + values: &[F], + blinding: &F, + ) -> bool; +} + +/// Additive homomorphism on commitment values over a scalar field `F`. +/// +/// Captures the ability to linearly combine two commitments without +/// knowing the committed values: +/// ```text +/// linear_combine(c1, c2, s) = c1 ⊕ s ⊗ c2 +/// ``` +/// +/// Required by Nova folding for instance-level commitment operations. +/// Not all commitment schemes have this property (e.g., hash-based schemes +/// do not). Pedersen and lattice-based schemes do. +/// +/// Blanket-implemented for [`JoltGroup`](crate::JoltGroup) over any field +/// (via `scalar_mul` + addition). Non-group commitment types (e.g., lattice +/// vectors) can implement this trait directly for their native scalar field. +pub trait HomomorphicCommitment: Clone { + /// Computes `c1 + scalar * c2`. + #[must_use] + fn linear_combine(c1: &Self, c2: &Self, scalar: &F) -> Self; +} + +impl HomomorphicCommitment for G { + #[inline] + fn linear_combine(c1: &G, c2: &G, scalar: &F) -> G { + *c1 + c2.scalar_mul(scalar) + } +} diff --git a/crates/jolt-crypto/src/dory_interop.rs b/crates/jolt-crypto/src/dory_interop.rs new file mode 100644 index 000000000..402b14144 --- /dev/null +++ b/crates/jolt-crypto/src/dory_interop.rs @@ -0,0 +1,465 @@ +//! Implements `dory_pcs` traits for jolt-crypto group types, allowing jolt-dory +//! to call `dory::prove`/`dory::verify` with [`Bn254G1`], [`Bn254G2`], [`Bn254GT`], +//! and [`Bn254`] directly — no arkworks-to-jolt conversion layer needed. + +use std::io::{Read, Write}; + +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +use dory::primitives::arithmetic; +use dory::primitives::serialization::{ + Compress, DoryDeserialize, DorySerialize, SerializationError, Valid, Validate, +}; + +use jolt_field::Fr; + +use crate::arkworks::bn254::{Bn254, Bn254G1, Bn254G2, Bn254GT}; +use crate::{JoltGroup, PairingGroup}; + +#[inline(always)] +fn to_ark_compress(c: Compress) -> ark_serialize::Compress { + match c { + Compress::Yes => ark_serialize::Compress::Yes, + Compress::No => ark_serialize::Compress::No, + } +} + +#[inline(always)] +fn to_ark_validate(v: Validate) -> ark_serialize::Validate { + match v { + Validate::Yes => ark_serialize::Validate::Yes, + Validate::No => ark_serialize::Validate::No, + } +} + +fn ark_err_to_dory(e: ark_serialize::SerializationError) -> SerializationError { + match e { + ark_serialize::SerializationError::IoError(io) => SerializationError::IoError(io), + ark_serialize::SerializationError::InvalidData => { + SerializationError::InvalidData("arkworks: invalid data".into()) + } + ark_serialize::SerializationError::UnexpectedFlags => SerializationError::UnexpectedData, + ark_serialize::SerializationError::NotEnoughSpace => { + SerializationError::InvalidData("arkworks: not enough space".into()) + } + } +} + +macro_rules! impl_dory_serialization { + ($ty:ty, $inner:ty) => { + impl Valid for $ty { + fn check(&self) -> Result<(), SerializationError> { + ark_serialize::Valid::check(&self.0).map_err(ark_err_to_dory) + } + } + + impl DorySerialize for $ty { + fn serialize_with_mode( + &self, + writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + self.0 + .serialize_with_mode(writer, to_ark_compress(compress)) + .map_err(ark_err_to_dory) + } + + fn serialized_size(&self, compress: Compress) -> usize { + self.0.serialized_size(to_ark_compress(compress)) + } + } + + impl DoryDeserialize for $ty { + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + <$inner>::deserialize_with_mode( + reader, + to_ark_compress(compress), + to_ark_validate(validate), + ) + .map(Self) + .map_err(ark_err_to_dory) + } + } + }; +} + +impl_dory_serialization!(Bn254G1, ark_bn254::G1Projective); +impl_dory_serialization!(Bn254G2, ark_bn254::G2Projective); +impl_dory_serialization!(Bn254GT, ark_bn254::Fq12); + +impl std::ops::Mul for Fr { + type Output = Bn254G1; + #[inline(always)] + fn mul(self, rhs: Bn254G1) -> Bn254G1 { + rhs.scalar_mul(&self) + } +} + +impl<'a> std::ops::Mul<&'a Bn254G1> for Fr { + type Output = Bn254G1; + #[inline(always)] + fn mul(self, rhs: &'a Bn254G1) -> Bn254G1 { + rhs.scalar_mul(&self) + } +} + +impl std::ops::Mul for Fr { + type Output = Bn254G2; + #[inline(always)] + fn mul(self, rhs: Bn254G2) -> Bn254G2 { + rhs.scalar_mul(&self) + } +} + +impl<'a> std::ops::Mul<&'a Bn254G2> for Fr { + type Output = Bn254G2; + #[inline(always)] + fn mul(self, rhs: &'a Bn254G2) -> Bn254G2 { + rhs.scalar_mul(&self) + } +} + +impl std::ops::Mul for Fr { + type Output = Bn254GT; + #[inline(always)] + fn mul(self, rhs: Bn254GT) -> Bn254GT { + rhs.scalar_mul(&self) + } +} + +impl<'a> std::ops::Mul<&'a Bn254GT> for Fr { + type Output = Bn254GT; + #[inline(always)] + fn mul(self, rhs: &'a Bn254GT) -> Bn254GT { + rhs.scalar_mul(&self) + } +} + +impl arithmetic::Group for Bn254G1 { + type Scalar = Fr; + + #[inline(always)] + fn identity() -> Self { + JoltGroup::identity() + } + + #[inline(always)] + fn add(&self, rhs: &Self) -> Self { + *self + *rhs + } + + #[inline(always)] + fn neg(&self) -> Self { + std::ops::Neg::neg(*self) + } + + #[inline] + fn scale(&self, k: &Fr) -> Self { + self.scalar_mul(k) + } + + #[inline] + fn random() -> Self { + Bn254::random_g1(&mut rand_core::OsRng) + } +} + +impl arithmetic::Group for Bn254G2 { + type Scalar = Fr; + + #[inline(always)] + fn identity() -> Self { + JoltGroup::identity() + } + + #[inline(always)] + fn add(&self, rhs: &Self) -> Self { + *self + *rhs + } + + #[inline(always)] + fn neg(&self) -> Self { + std::ops::Neg::neg(*self) + } + + #[inline] + fn scale(&self, k: &Fr) -> Self { + self.scalar_mul(k) + } + + #[inline] + fn random() -> Self { + use ark_std::UniformRand; + Bn254G2(ark_bn254::G2Projective::rand(&mut rand_core::OsRng)) + } +} + +impl arithmetic::Group for Bn254GT { + type Scalar = Fr; + + #[inline(always)] + fn identity() -> Self { + JoltGroup::identity() + } + + #[inline(always)] + fn add(&self, rhs: &Self) -> Self { + *self + *rhs + } + + #[inline(always)] + fn neg(&self) -> Self { + std::ops::Neg::neg(*self) + } + + #[inline] + fn scale(&self, k: &Fr) -> Self { + self.scalar_mul(k) + } + + #[inline] + fn random() -> Self { + // Generate a random GT element via pairing(g1^r, g2_gen) + let r_g1 = Bn254::random_g1(&mut rand_core::OsRng); + let g2 = Bn254::g2_generator(); + ::pairing(&r_g1, &g2) + } +} + +impl arithmetic::PairingCurve for Bn254 { + type G1 = Bn254G1; + type G2 = Bn254G2; + type GT = Bn254GT; + + #[inline] + fn pair(p: &Bn254G1, q: &Bn254G2) -> Bn254GT { + ::pairing(p, q) + } + + #[inline] + fn multi_pair(ps: &[Bn254G1], qs: &[Bn254G2]) -> Bn254GT { + ::multi_pairing(ps, qs) + } + + #[inline] + fn multi_pair_g2_setup(ps: &[Bn254G1], qs: &[Bn254G2]) -> Bn254GT { + ::multi_pairing(ps, qs) + } + + #[inline] + fn multi_pair_g1_setup(ps: &[Bn254G1], qs: &[Bn254G2]) -> Bn254GT { + ::multi_pairing(ps, qs) + } +} + +/// GLV-optimized MSM and vector operations for G1 elements. +pub struct OptimizedG1Routines; + +impl arithmetic::DoryRoutines for OptimizedG1Routines { + #[inline] + fn msm(bases: &[Bn254G1], scalars: &[Fr]) -> Bn254G1 { + JoltGroup::msm(bases, scalars) + } + + fn fixed_base_vector_scalar_mul(base: &Bn254G1, scalars: &[Fr]) -> Vec { + crate::arkworks::bn254::glv::fixed_base_vector_msm_g1(base, scalars) + } + + fn fixed_scalar_mul_bases_then_add(bases: &[Bn254G1], vs: &mut [Bn254G1], scalar: &Fr) { + crate::arkworks::bn254::glv::vector_add_scalar_mul_g1(vs, bases, *scalar); + } + + fn fixed_scalar_mul_vs_then_add(vs: &mut [Bn254G1], addends: &[Bn254G1], scalar: &Fr) { + crate::arkworks::bn254::glv::vector_scalar_mul_add_gamma_g1(vs, *scalar, addends); + } +} + +/// GLV-optimized MSM and vector operations for G2 elements. +pub struct OptimizedG2Routines; + +impl arithmetic::DoryRoutines for OptimizedG2Routines { + #[inline] + fn msm(bases: &[Bn254G2], scalars: &[Fr]) -> Bn254G2 { + JoltGroup::msm(bases, scalars) + } + + fn fixed_base_vector_scalar_mul(base: &Bn254G2, scalars: &[Fr]) -> Vec { + scalars + .iter() + .map(|s| { + crate::arkworks::bn254::glv::glv_four_scalar_mul(*s, std::slice::from_ref(base))[0] + }) + .collect() + } + + fn fixed_scalar_mul_bases_then_add(bases: &[Bn254G2], vs: &mut [Bn254G2], scalar: &Fr) { + crate::arkworks::bn254::glv::vector_add_scalar_mul_g2(vs, bases, *scalar); + } + + fn fixed_scalar_mul_vs_then_add(vs: &mut [Bn254G2], addends: &[Bn254G2], scalar: &Fr) { + crate::arkworks::bn254::glv::vector_scalar_mul_add_gamma_g2(vs, *scalar, addends); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use dory::primitives::arithmetic::{ + DoryRoutines, Field as DoryField, Group as DoryGroup, PairingCurve as DoryPairing, + }; + + #[test] + fn g1_identity_and_neg() { + let id = ::identity(); + let g = ::random(); + assert_eq!(DoryGroup::add(&g, &id), g); + assert_eq!(DoryGroup::add(&g, &DoryGroup::neg(&g)), id); + } + + #[test] + fn g2_identity_and_neg() { + let id = ::identity(); + let g = ::random(); + assert_eq!(DoryGroup::add(&g, &id), g); + assert_eq!(DoryGroup::add(&g, &DoryGroup::neg(&g)), id); + } + + #[test] + fn gt_identity_and_neg() { + let id = ::identity(); + let g = ::random(); + assert_eq!(DoryGroup::add(&g, &id), g); + assert_eq!(DoryGroup::add(&g, &DoryGroup::neg(&g)), id); + } + + #[test] + fn g1_scale() { + let g = ::random(); + let two = ::from_u64(2); + let doubled = DoryGroup::scale(&g, &two); + assert_eq!(doubled, DoryGroup::add(&g, &g)); + } + + #[test] + fn pairing_bilinearity() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let a = ::from_u64(3); + let b = ::from_u64(5); + + // e(a*G1, b*G2) == e(G1, G2)^(a*b) + let lhs = + ::pair(&DoryGroup::scale(&g1, &a), &DoryGroup::scale(&g2, &b)); + let rhs = DoryGroup::scale( + &::pair(&g1, &g2), + &DoryField::mul(&a, &b), + ); + assert_eq!(lhs, rhs); + } + + #[test] + fn multi_pair_matches_sequential() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let a = ::from_u64(7); + let b = ::from_u64(11); + + let g1s = vec![DoryGroup::scale(&g1, &a), DoryGroup::scale(&g1, &b)]; + let g2s = vec![g2, g2]; + + let multi = ::multi_pair(&g1s, &g2s); + let sequential = DoryGroup::add( + &::pair(&g1s[0], &g2s[0]), + &::pair(&g1s[1], &g2s[1]), + ); + assert_eq!(multi, sequential); + } + + #[test] + fn g1_msm_matches_jolt() { + let bases: Vec = (0..4).map(|_| ::random()).collect(); + let scalars: Vec = (1..=4).map(::from_u64).collect(); + + let via_routines = OptimizedG1Routines::msm(&bases, &scalars); + let via_jolt = JoltGroup::msm(&bases, &scalars); + assert_eq!(via_routines, via_jolt); + } + + #[test] + fn g1_fixed_base_vector_scalar_mul() { + let base = ::random(); + let scalars: Vec = (1..=3).map(::from_u64).collect(); + let result = OptimizedG1Routines::fixed_base_vector_scalar_mul(&base, &scalars); + for (r, s) in result.iter().zip(scalars.iter()) { + assert_eq!(*r, DoryGroup::scale(&base, s)); + } + } + + #[test] + fn g1_fixed_scalar_mul_bases_then_add() { + let bases: Vec = (0..3).map(|_| ::random()).collect(); + let scalar = ::from_u64(5); + let mut vs: Vec = (0..3).map(|_| ::random()).collect(); + let vs_orig = vs.clone(); + + OptimizedG1Routines::fixed_scalar_mul_bases_then_add(&bases, &mut vs, &scalar); + for i in 0..3 { + let expected = DoryGroup::add(&vs_orig[i], &DoryGroup::scale(&bases[i], &scalar)); + assert_eq!(vs[i], expected); + } + } + + #[test] + fn g2_msm_matches_jolt() { + let bases: Vec = (0..4).map(|_| ::random()).collect(); + let scalars: Vec = (1..=4).map(::from_u64).collect(); + + let via_routines = OptimizedG2Routines::msm(&bases, &scalars); + let via_jolt = JoltGroup::msm(&bases, &scalars); + assert_eq!(via_routines, via_jolt); + } + + #[test] + fn g1_serialization_roundtrip() { + let g = ::random(); + let mut buf = Vec::new(); + DorySerialize::serialize_compressed(&g, &mut buf).unwrap(); + let recovered: Bn254G1 = DoryDeserialize::deserialize_compressed(&buf[..]).unwrap(); + assert_eq!(g, recovered); + } + + #[test] + fn g2_serialization_roundtrip() { + let g = ::random(); + let mut buf = Vec::new(); + DorySerialize::serialize_compressed(&g, &mut buf).unwrap(); + let recovered: Bn254G2 = DoryDeserialize::deserialize_compressed(&buf[..]).unwrap(); + assert_eq!(g, recovered); + } + + #[test] + fn gt_serialization_roundtrip() { + let g = ::random(); + let mut buf = Vec::new(); + DorySerialize::serialize_compressed(&g, &mut buf).unwrap(); + let recovered: Bn254GT = DoryDeserialize::deserialize_compressed(&buf[..]).unwrap(); + assert_eq!(g, recovered); + } + + #[test] + fn fr_mul_g1() { + let g = ::random(); + let s = ::from_u64(42); + let via_mul: Bn254G1 = s * g; + let via_scale = DoryGroup::scale(&g, &s); + assert_eq!(via_mul, via_scale); + + // Reference variant + let via_mul_ref: Bn254G1 = s * g; + assert_eq!(via_mul_ref, via_scale); + } +} diff --git a/crates/jolt-crypto/src/group.rs b/crates/jolt-crypto/src/group.rs new file mode 100644 index 000000000..9268a1bdc --- /dev/null +++ b/crates/jolt-crypto/src/group.rs @@ -0,0 +1,63 @@ +use std::fmt::Debug; +use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; + +use jolt_field::Field; +use jolt_transcript::AppendToTranscript; +use serde::{Deserialize, Serialize}; + +/// Cryptographic group suitable for commitments. +/// +/// Not necessarily an elliptic curve — the trait is intentionally general +/// enough for lattice-based or other algebraic groups. The group operation +/// uses additive notation (`Add`/`Sub`), but this is purely conventional; +/// the underlying algebra may be multiplicative. +/// +/// All elements are `Copy` and thread-safe. Implementors must provide +/// scalar multiplication and multi-scalar multiplication (MSM). +/// +/// Requires [`AppendToTranscript`] so group elements can be absorbed into +/// Fiat-Shamir transcripts (e.g., Pedersen commitments in ZK sumcheck). +pub trait JoltGroup: + Clone + + Copy + + Debug + + Default + + Eq + + Send + + Sync + + 'static + + Add + + Sub + + Neg + + for<'a> Add<&'a Self, Output = Self> + + for<'a> Sub<&'a Self, Output = Self> + + AddAssign + + SubAssign + + Serialize + + for<'de> Deserialize<'de> + + AppendToTranscript +{ + /// Group identity element. + #[must_use] + fn identity() -> Self; + + /// Returns `true` if this element is the identity. + #[must_use] + fn is_identity(&self) -> bool; + + /// Returns `self + self`. + #[must_use] + fn double(&self) -> Self; + + /// Scalar multiplication: `scalar * self`. + #[must_use] + fn scalar_mul(&self, scalar: &F) -> Self; + + /// Multi-scalar multiplication: `Σᵢ scalars[i] * bases[i]`. + /// + /// # Panics + /// + /// Debug-asserts that `bases.len() == scalars.len()`. + #[must_use] + fn msm(bases: &[Self], scalars: &[F]) -> Self; +} diff --git a/crates/jolt-crypto/src/lib.rs b/crates/jolt-crypto/src/lib.rs new file mode 100644 index 000000000..64e970681 --- /dev/null +++ b/crates/jolt-crypto/src/lib.rs @@ -0,0 +1,35 @@ +//! Backend-agnostic cryptographic group and commitment primitives for Jolt. +//! +//! This crate provides the core group abstractions (`JoltGroup`, `PairingGroup`) +//! and a vector commitment trait used throughout the Jolt zkVM. The traits are +//! designed to be backend-agnostic: the BN254 implementation wraps arkworks +//! internally, but no arkworks types appear in the public API. +//! +//! # Crate structure +//! +//! | Module | Purpose | +//! |--------|---------| +//! | `group` | [`JoltGroup`] trait — additive group with scalar multiplication and MSM | +//! | `pairing` | [`PairingGroup`] trait — pairing-friendly group (extends `JoltGroup`) | +//! | `commitment` | [`JoltCommitment`] trait — backend-agnostic vector commitment | +//! | `arkworks` | Arkworks backend implementations (BN254) | + +mod group; +pub use group::JoltGroup; + +mod pairing; +pub use pairing::PairingGroup; + +mod commitment; +pub use commitment::{Commitment, HomomorphicCommitment, JoltCommitment}; + +mod pedersen; +pub use pedersen::{Pedersen, PedersenSetup}; + +#[cfg(feature = "bn254")] +pub mod arkworks; +#[cfg(feature = "bn254")] +pub use arkworks::bn254::{Bn254, Bn254G1, Bn254G2, Bn254GT}; + +#[cfg(feature = "dory-pcs")] +pub mod dory_interop; diff --git a/crates/jolt-crypto/src/pairing.rs b/crates/jolt-crypto/src/pairing.rs new file mode 100644 index 000000000..6346ddd8e --- /dev/null +++ b/crates/jolt-crypto/src/pairing.rs @@ -0,0 +1,28 @@ +use jolt_field::Field; + +use crate::JoltGroup; + +/// Pairing-friendly group for schemes that require bilinear maps (Dory, KZG). +/// +/// Not all groups need this — Pedersen commitments only require `JoltGroup`. +/// The trait is parameterised over four associated types: the scalar field, +/// G1, G2, and the target group GT. +/// +/// G1, G2, and GT all implement `JoltGroup` (additive notation). GT uses +/// additive notation for uniformity, even though the underlying operation +/// is Fq12 multiplication. See `Bn254GT` for the mapping. +pub trait PairingGroup: Clone + Sync + Send + 'static { + /// Scalar field for G1 and G2 (e.g., BN254 Fr). + type ScalarField: Field; + type G1: JoltGroup; + type G2: JoltGroup; + type GT: JoltGroup; + + /// Computes the bilinear pairing `e(g1, g2)`. + #[must_use] + fn pairing(g1: &Self::G1, g2: &Self::G2) -> Self::GT; + + /// Computes the multi-pairing `Π e(g1s[i], g2s[i])`. + #[must_use] + fn multi_pairing(g1s: &[Self::G1], g2s: &[Self::G2]) -> Self::GT; +} diff --git a/crates/jolt-crypto/src/pedersen.rs b/crates/jolt-crypto/src/pedersen.rs new file mode 100644 index 000000000..40d63ae83 --- /dev/null +++ b/crates/jolt-crypto/src/pedersen.rs @@ -0,0 +1,70 @@ +use jolt_field::Field; +use serde::{Deserialize, Serialize}; + +use crate::commitment::JoltCommitment; +use crate::JoltGroup; + +/// Pedersen vector commitment scheme, generic over any `JoltGroup`. +/// +/// Commitment: `C = Σᵢ values[i] * message_generators[i] + blinding * blinding_generator` +/// +/// This provides a blanket `JoltCommitment` implementation for any group +/// that implements `JoltGroup`, so concrete backends (BN254, etc.) inherit +/// Pedersen commitments. +#[derive(Clone, Debug)] +pub struct Pedersen { + _marker: std::marker::PhantomData, +} + +/// Setup parameters for Pedersen commitments: a vector of message generators +/// and a separate blinding generator. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct PedersenSetup { + pub message_generators: Vec, + pub blinding_generator: G, +} + +impl PedersenSetup { + /// Constructs setup from externally-provided generators (e.g., from a PCS URS). + /// + /// # Panics + /// + /// Panics if `message_generators` is empty. + pub fn new(message_generators: Vec, blinding_generator: G) -> Self { + assert!( + !message_generators.is_empty(), + "Pedersen setup requires at least one message generator" + ); + Self { + message_generators, + blinding_generator, + } + } +} + +impl JoltCommitment for Pedersen { + type Setup = PedersenSetup; + type Commitment = G; + + #[inline] + fn capacity(setup: &Self::Setup) -> usize { + setup.message_generators.len() + } + + fn commit(setup: &Self::Setup, values: &[F], blinding: &F) -> G { + assert!( + values.len() <= setup.message_generators.len(), + "values length ({}) exceeds generator count ({})", + values.len(), + setup.message_generators.len(), + ); + let msg = G::msm(&setup.message_generators[..values.len()], values); + let blind = setup.blinding_generator.scalar_mul(blinding); + msg + blind + } + + fn verify(setup: &Self::Setup, commitment: &G, values: &[F], blinding: &F) -> bool { + *commitment == Self::commit(setup, values, blinding) + } +} diff --git a/crates/jolt-crypto/tests/coverage.rs b/crates/jolt-crypto/tests/coverage.rs new file mode 100644 index 000000000..70099809c --- /dev/null +++ b/crates/jolt-crypto/tests/coverage.rs @@ -0,0 +1,553 @@ +//! Targeted coverage tests for jolt-crypto. +//! +//! Covers gaps in G2, GT, GLV (2D/4D), Dory vector ops, fixed-base MSM, +//! HomomorphicCommitment, and Debug/From conversions. + +use jolt_crypto::arkworks::bn254::glv; +use jolt_crypto::{ + Bn254, Bn254G1, Bn254G2, Bn254GT, HomomorphicCommitment, JoltGroup, PairingGroup, +}; +use jolt_field::{Field, Fr}; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +#[test] +fn g2_debug_format_contains_type_name() { + let g = Bn254::g2_generator(); + let debug_str = format!("{:?}", g); + assert!( + debug_str.starts_with("Bn254G2("), + "expected Bn254G2(...), got: {debug_str}" + ); +} + +#[test] +fn g2_commutativity() { + let g = Bn254::g2_generator(); + let two = Fr::from_u64(2); + let three = Fr::from_u64(3); + let a = g.scalar_mul(&two); + let b = g.scalar_mul(&three); + assert_eq!(a + b, b + a); +} + +#[test] +#[allow(clippy::op_ref)] +fn g2_add_ref() { + let g = Bn254::g2_generator(); + let a = g.scalar_mul(&Fr::from_u64(3)); + let b = g.scalar_mul(&Fr::from_u64(5)); + let expected = a + b; + assert_eq!(a + &b, expected); +} + +#[test] +#[allow(clippy::op_ref)] +fn g2_sub_ref() { + let g = Bn254::g2_generator(); + let a = g.scalar_mul(&Fr::from_u64(7)); + let b = g.scalar_mul(&Fr::from_u64(3)); + let expected = a - b; + assert_eq!(a - &b, expected); +} + +#[test] +fn g2_msm_single_element() { + let g = Bn254::g2_generator(); + let s = Fr::from_u64(42); + assert_eq!(Bn254G2::msm(&[g], &[s]), g.scalar_mul(&s)); +} + +#[test] +fn g2_msm_empty() { + let result = Bn254G2::msm(&[], &([] as [Fr; 0])); + assert!(result.is_identity()); +} + +#[test] +fn g2_msm_multiple_random() { + let mut rng = ChaCha20Rng::seed_from_u64(100); + let g = Bn254::g2_generator(); + let points: Vec = (0..5).map(|i| g.scalar_mul(&Fr::from_u64(i + 1))).collect(); + let scalars: Vec = (0..5).map(|_| Fr::random(&mut rng)).collect(); + + let msm_result = Bn254G2::msm(&points, &scalars); + let naive: Bn254G2 = points + .iter() + .zip(scalars.iter()) + .fold(Bn254G2::identity(), |acc, (p, s)| acc + p.scalar_mul(s)); + assert_eq!(msm_result, naive); +} + +#[test] +fn g2_associativity() { + let g = Bn254::g2_generator(); + let a = g.scalar_mul(&Fr::from_u64(3)); + let b = g.scalar_mul(&Fr::from_u64(7)); + let c = g.scalar_mul(&Fr::from_u64(11)); + assert_eq!((a + b) + c, a + (b + c)); +} + +#[test] +fn g2_neg() { + let g = Bn254::g2_generator(); + assert_eq!(g + (-g), Bn254G2::identity()); + assert!((-g + g).is_identity()); +} + +#[test] +fn g2_scalar_mul_distributive() { + let g = Bn254::g2_generator(); + let three = Fr::from_u64(3); + let five = Fr::from_u64(5); + let eight = Fr::from_u64(8); + assert_eq!( + g.scalar_mul(&three) + g.scalar_mul(&five), + g.scalar_mul(&eight) + ); +} + +fn gt_element() -> Bn254GT { + Bn254::pairing(&Bn254::g1_generator(), &Bn254::g2_generator()) +} + +#[test] +fn gt_debug_format_contains_type_name() { + let e = gt_element(); + let debug_str = format!("{:?}", e); + assert!( + debug_str.starts_with("Bn254GT("), + "expected Bn254GT(...), got: {debug_str}" + ); +} + +#[test] +fn gt_identity_is_identity() { + let id = Bn254GT::identity(); + assert!(id.is_identity()); + assert!(!gt_element().is_identity()); +} + +#[test] +fn gt_mul_assign() { + let e = gt_element(); + let mut acc = e; + acc *= e; + // Mul is a convenience alias for Add (both map to Fq12 multiplication) + assert_eq!(acc, e + e); +} + +#[test] +fn gt_sub_assign() { + let e = gt_element(); + let double = e + e; + let mut x = double; + x -= e; + assert_eq!(x, e); +} + +#[test] +#[allow(clippy::op_ref)] +fn gt_add_ref() { + let e = gt_element(); + let e2 = e.scalar_mul(&Fr::from_u64(2)); + let expected = e + e2; + assert_eq!(e + &e2, expected); +} + +#[test] +#[allow(clippy::op_ref)] +fn gt_sub_ref() { + let e = gt_element(); + let e2 = e.scalar_mul(&Fr::from_u64(2)); + let expected = e2 - e; + assert_eq!(e2 - &e, expected); +} + +#[test] +fn gt_neg_of_neg_is_self() { + let e = gt_element(); + assert_eq!(-(-e), e); +} + +#[test] +fn gt_scalar_mul_distributive() { + let e = gt_element(); + let three = Fr::from_u64(3); + let five = Fr::from_u64(5); + let eight = Fr::from_u64(8); + assert_eq!( + e.scalar_mul(&three) + e.scalar_mul(&five), + e.scalar_mul(&eight) + ); +} + +#[test] +fn gt_msm_single_element() { + let e = gt_element(); + let s = Fr::from_u64(7); + assert_eq!(Bn254GT::msm(&[e], &[s]), e.scalar_mul(&s)); +} + +#[test] +fn gt_msm_empty() { + let result = Bn254GT::msm(&[], &([] as [Fr; 0])); + assert!(result.is_identity()); +} + +#[test] +fn gt_associativity() { + let e = gt_element(); + let a = e.scalar_mul(&Fr::from_u64(2)); + let b = e.scalar_mul(&Fr::from_u64(3)); + let c = e.scalar_mul(&Fr::from_u64(5)); + assert_eq!((a + b) + c, a + (b + c)); +} + +#[test] +fn gt_commutativity() { + let e = gt_element(); + let a = e.scalar_mul(&Fr::from_u64(3)); + let b = e.scalar_mul(&Fr::from_u64(7)); + assert_eq!(a + b, b + a); +} + +#[test] +fn gt_double_is_squaring() { + let e = gt_element(); + let s = Fr::from_u64(5); + let es = e.scalar_mul(&s); + assert_eq!(es.double(), es + es); +} + +#[test] +fn gt_mul_matches_add() { + let e = gt_element(); + let a = e.scalar_mul(&Fr::from_u64(3)); + let b = e.scalar_mul(&Fr::from_u64(7)); + assert_eq!(a * b, a + b); +} + +#[test] +fn glv_vector_add_scalar_mul_g1_matches_naive() { + let mut rng = ChaCha20Rng::seed_from_u64(200); + let n = 8; + let generators: Vec = (0..n).map(|_| Bn254::random_g1(&mut rng)).collect(); + let initial: Vec = (0..n).map(|_| Bn254::random_g1(&mut rng)).collect(); + let scalar = Fr::random(&mut rng); + + let mut result = initial.clone(); + glv::vector_add_scalar_mul_g1(&mut result, &generators, scalar); + + for i in 0..n { + let expected = initial[i] + generators[i].scalar_mul(&scalar); + assert_eq!(result[i], expected, "mismatch at index {i}"); + } +} + +#[test] +fn glv_vector_scalar_mul_add_gamma_g1_matches_naive() { + let mut rng = ChaCha20Rng::seed_from_u64(201); + let n = 8; + let gamma: Vec = (0..n).map(|_| Bn254::random_g1(&mut rng)).collect(); + let initial: Vec = (0..n).map(|_| Bn254::random_g1(&mut rng)).collect(); + let scalar = Fr::random(&mut rng); + + let mut result = initial.clone(); + glv::vector_scalar_mul_add_gamma_g1(&mut result, scalar, &gamma); + + for i in 0..n { + let expected = initial[i].scalar_mul(&scalar) + gamma[i]; + assert_eq!(result[i], expected, "mismatch at index {i}"); + } +} + +#[test] +fn glv_vector_add_scalar_mul_g2_matches_naive() { + let n = 4; + let g2 = Bn254::g2_generator(); + let generators: Vec = (1..=n) + .map(|i| g2.scalar_mul(&Fr::from_u64(i as u64))) + .collect(); + let initial: Vec = (10..10 + n) + .map(|i| g2.scalar_mul(&Fr::from_u64(i as u64))) + .collect(); + let scalar = Fr::from_u64(17); + + let mut result = initial.clone(); + glv::vector_add_scalar_mul_g2(&mut result, &generators, scalar); + + for i in 0..n { + let expected = initial[i] + generators[i].scalar_mul(&scalar); + assert_eq!(result[i], expected, "mismatch at index {i}"); + } +} + +#[test] +fn glv_vector_scalar_mul_add_gamma_g2_matches_naive() { + let n = 4; + let g2 = Bn254::g2_generator(); + let gamma: Vec = (1..=n) + .map(|i| g2.scalar_mul(&Fr::from_u64(i as u64))) + .collect(); + let initial: Vec = (10..10 + n) + .map(|i| g2.scalar_mul(&Fr::from_u64(i as u64))) + .collect(); + let scalar = Fr::from_u64(23); + + let mut result = initial.clone(); + glv::vector_scalar_mul_add_gamma_g2(&mut result, scalar, &gamma); + + for i in 0..n { + let expected = initial[i].scalar_mul(&scalar) + gamma[i]; + assert_eq!(result[i], expected, "mismatch at index {i}"); + } +} + +#[test] +fn glv_four_scalar_mul_matches_naive() { + let mut rng = ChaCha20Rng::seed_from_u64(300); + let g2 = Bn254::g2_generator(); + let points: Vec = (0..6) + .map(|_| g2.scalar_mul(&Fr::random(&mut rng))) + .collect(); + let scalar = Fr::random(&mut rng); + + let results = glv::glv_four_scalar_mul(scalar, &points); + + for (i, (result, point)) in results.iter().zip(points.iter()).enumerate() { + let expected = point.scalar_mul(&scalar); + assert_eq!( + *result, expected, + "glv_four_scalar_mul mismatch at index {i}" + ); + } +} + +#[test] +fn glv_four_scalar_mul_identity_scalar() { + let g2 = Bn254::g2_generator(); + let points = vec![g2, g2.scalar_mul(&Fr::from_u64(5))]; + let one = Fr::from_u64(1); + + let results = glv::glv_four_scalar_mul(one, &points); + for (result, point) in results.iter().zip(points.iter()) { + assert_eq!(*result, *point); + } +} + +#[test] +fn glv_four_scalar_mul_zero_scalar() { + let g2 = Bn254::g2_generator(); + let points = vec![g2, g2.double()]; + let zero = Fr::from_u64(0); + + let results = glv::glv_four_scalar_mul(zero, &points); + for result in &results { + assert!(result.is_identity()); + } +} + +#[test] +fn glv_four_scalar_mul_empty() { + let results = glv::glv_four_scalar_mul(Fr::from_u64(42), &[]); + assert!(results.is_empty()); +} + +#[test] +fn glv_fixed_base_vector_msm_g1_matches_naive() { + let mut rng = ChaCha20Rng::seed_from_u64(400); + let base = Bn254::random_g1(&mut rng); + let scalars: Vec = (0..8).map(|_| Fr::random(&mut rng)).collect(); + + let results = glv::fixed_base_vector_msm_g1(&base, &scalars); + + for (i, (result, scalar)) in results.iter().zip(scalars.iter()).enumerate() { + let expected = base.scalar_mul(scalar); + assert_eq!( + *result, expected, + "fixed_base_vector_msm_g1 mismatch at index {i}" + ); + } +} + +#[test] +fn glv_fixed_base_vector_msm_g1_empty() { + let base = Bn254::g1_generator(); + let results = glv::fixed_base_vector_msm_g1(&base, &[]); + assert!(results.is_empty()); +} + +#[test] +fn glv_fixed_base_vector_msm_g1_single() { + let mut rng = ChaCha20Rng::seed_from_u64(401); + let base = Bn254::random_g1(&mut rng); + let scalar = Fr::random(&mut rng); + let results = glv::fixed_base_vector_msm_g1(&base, &[scalar]); + assert_eq!(results.len(), 1); + assert_eq!(results[0], base.scalar_mul(&scalar)); +} + +#[test] +fn glv_vector_add_scalar_mul_g2_random_scalar() { + let mut rng = ChaCha20Rng::seed_from_u64(500); + let g2 = Bn254::g2_generator(); + let n = 4; + let generators: Vec = (0..n) + .map(|_| g2.scalar_mul(&Fr::random(&mut rng))) + .collect(); + let initial: Vec = (0..n) + .map(|_| g2.scalar_mul(&Fr::random(&mut rng))) + .collect(); + let scalar = Fr::random(&mut rng); + + let mut result = initial.clone(); + glv::vector_add_scalar_mul_g2(&mut result, &generators, scalar); + + for i in 0..n { + let expected = initial[i] + generators[i].scalar_mul(&scalar); + assert_eq!(result[i], expected, "mismatch at index {i}"); + } +} + +#[test] +fn glv_vector_scalar_mul_add_gamma_g2_random_scalar() { + let mut rng = ChaCha20Rng::seed_from_u64(501); + let g2 = Bn254::g2_generator(); + let n = 4; + let gamma: Vec = (0..n) + .map(|_| g2.scalar_mul(&Fr::random(&mut rng))) + .collect(); + let initial: Vec = (0..n) + .map(|_| g2.scalar_mul(&Fr::random(&mut rng))) + .collect(); + let scalar = Fr::random(&mut rng); + + let mut result = initial.clone(); + glv::vector_scalar_mul_add_gamma_g2(&mut result, scalar, &gamma); + + for i in 0..n { + let expected = initial[i].scalar_mul(&scalar) + gamma[i]; + assert_eq!(result[i], expected, "mismatch at index {i}"); + } +} + +#[test] +fn homomorphic_commitment_g1_linear_combine() { + let mut rng = ChaCha20Rng::seed_from_u64(600); + let c1 = Bn254::random_g1(&mut rng); + let c2 = Bn254::random_g1(&mut rng); + let scalar = Fr::random(&mut rng); + + let result = >::linear_combine(&c1, &c2, &scalar); + let expected = c1 + c2.scalar_mul(&scalar); + assert_eq!(result, expected); +} + +#[test] +fn homomorphic_commitment_g2_linear_combine() { + let g2 = Bn254::g2_generator(); + let c1 = g2.scalar_mul(&Fr::from_u64(3)); + let c2 = g2.scalar_mul(&Fr::from_u64(7)); + let scalar = Fr::from_u64(5); + + let result = >::linear_combine(&c1, &c2, &scalar); + let expected = c1 + c2.scalar_mul(&scalar); + assert_eq!(result, expected); +} + +#[test] +fn homomorphic_commitment_gt_linear_combine() { + let e = gt_element(); + let c1 = e.scalar_mul(&Fr::from_u64(2)); + let c2 = e.scalar_mul(&Fr::from_u64(3)); + let scalar = Fr::from_u64(4); + + let result = >::linear_combine(&c1, &c2, &scalar); + let expected = c1 + c2.scalar_mul(&scalar); + assert_eq!(result, expected); +} + +#[test] +fn glv_four_scalar_mul_large_random_scalars() { + let mut rng = ChaCha20Rng::seed_from_u64(700); + let g2 = Bn254::g2_generator(); + let points: Vec = (0..3) + .map(|_| g2.scalar_mul(&Fr::random(&mut rng))) + .collect(); + + for _ in 0..5 { + let scalar = Fr::random(&mut rng); + let results = glv::glv_four_scalar_mul(scalar, &points); + for (i, (result, point)) in results.iter().zip(points.iter()).enumerate() { + let expected = point.scalar_mul(&scalar); + assert_eq!(*result, expected, "large scalar mismatch at index {i}"); + } + } +} + +#[test] +fn glv_fixed_base_vector_msm_g1_large_random_scalars() { + let mut rng = ChaCha20Rng::seed_from_u64(701); + let base = Bn254::random_g1(&mut rng); + let scalars: Vec = (0..16).map(|_| Fr::random(&mut rng)).collect(); + + let results = glv::fixed_base_vector_msm_g1(&base, &scalars); + for (i, (result, scalar)) in results.iter().zip(scalars.iter()).enumerate() { + let expected = base.scalar_mul(scalar); + assert_eq!(*result, expected, "large scalar MSM mismatch at index {i}"); + } +} + +#[test] +fn g2_scalar_mul_large_random() { + let mut rng = ChaCha20Rng::seed_from_u64(800); + let g = Bn254::g2_generator(); + let a = Fr::random(&mut rng); + let b = Fr::random(&mut rng); + + // (a * b) * G == a * (b * G) + let ab = a * b; + let lhs = g.scalar_mul(&ab); + let rhs = g.scalar_mul(&b).scalar_mul(&a); + assert_eq!(lhs, rhs); +} + +#[test] +fn g2_scalar_mul_consistency_with_repeated_add() { + let g = Bn254::g2_generator(); + let n = 7u64; + let scalar = Fr::from_u64(n); + let via_scalar_mul = g.scalar_mul(&scalar); + let mut via_add = Bn254G2::identity(); + for _ in 0..n { + via_add += g; + } + assert_eq!(via_scalar_mul, via_add); +} + +#[test] +fn gt_scalar_mul_consistency_with_repeated_add() { + let e = gt_element(); + let n = 5u64; + let scalar = Fr::from_u64(n); + let via_scalar_mul = e.scalar_mul(&scalar); + let mut via_add = Bn254GT::identity(); + for _ in 0..n { + via_add += e; + } + assert_eq!(via_scalar_mul, via_add); +} + +#[test] +fn gt_scalar_mul_large_random() { + let mut rng = ChaCha20Rng::seed_from_u64(801); + let e = gt_element(); + let a = Fr::random(&mut rng); + let b = Fr::random(&mut rng); + + let ab = a * b; + let lhs = e.scalar_mul(&ab); + let rhs = e.scalar_mul(&b).scalar_mul(&a); + assert_eq!(lhs, rhs); +} diff --git a/crates/jolt-crypto/tests/group_laws.rs b/crates/jolt-crypto/tests/group_laws.rs new file mode 100644 index 000000000..e2ac0f0b0 --- /dev/null +++ b/crates/jolt-crypto/tests/group_laws.rs @@ -0,0 +1,221 @@ +//! Algebraic group law tests for BN254 G1 and G2. + +use jolt_crypto::{Bn254, Bn254G1, Bn254G2, JoltGroup}; +use jolt_field::{Field, Fr}; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +fn random_g1(rng: &mut ChaCha20Rng) -> Bn254G1 { + Bn254::random_g1(rng) +} + +#[test] +fn g1_identity() { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let a = random_g1(&mut rng); + assert_eq!(a + Bn254G1::identity(), a); + assert_eq!(Bn254G1::identity() + a, a); +} + +#[test] +fn g1_inverse() { + let mut rng = ChaCha20Rng::seed_from_u64(1); + let a = random_g1(&mut rng); + assert_eq!(a + (-a), Bn254G1::identity()); + assert!(Bn254G1::identity().is_identity()); +} + +#[test] +fn g1_associativity() { + let mut rng = ChaCha20Rng::seed_from_u64(2); + let a = random_g1(&mut rng); + let b = random_g1(&mut rng); + let c = random_g1(&mut rng); + assert_eq!((a + b) + c, a + (b + c)); +} + +#[test] +fn g1_double_equals_add_self() { + let mut rng = ChaCha20Rng::seed_from_u64(3); + let a = random_g1(&mut rng); + assert_eq!(a.double(), a + a); +} + +#[test] +fn g1_scalar_mul_two_equals_double() { + let mut rng = ChaCha20Rng::seed_from_u64(4); + let a = random_g1(&mut rng); + let two = Fr::from_u64(2); + assert_eq!(a.scalar_mul(&two), a.double()); +} + +#[test] +fn g1_scalar_mul_zero_is_identity() { + let mut rng = ChaCha20Rng::seed_from_u64(5); + let a = random_g1(&mut rng); + let zero = Fr::from_u64(0); + assert!(a.scalar_mul(&zero).is_identity()); +} + +#[test] +fn g1_scalar_mul_one_is_identity() { + let mut rng = ChaCha20Rng::seed_from_u64(6); + let a = random_g1(&mut rng); + let one = Fr::from_u64(1); + assert_eq!(a.scalar_mul(&one), a); +} + +#[test] +fn g1_msm_matches_naive() { + let mut rng = ChaCha20Rng::seed_from_u64(7); + let g1 = random_g1(&mut rng); + let g2 = random_g1(&mut rng); + let s1 = Fr::random(&mut rng); + let s2 = Fr::random(&mut rng); + + let msm_result = Bn254G1::msm(&[g1, g2], &[s1, s2]); + let naive = g1.scalar_mul(&s1) + g2.scalar_mul(&s2); + assert_eq!(msm_result, naive); +} + +#[test] +fn g1_sub_and_sub_assign() { + let mut rng = ChaCha20Rng::seed_from_u64(8); + let a = random_g1(&mut rng); + let b = random_g1(&mut rng); + + let diff = a - b; + assert_eq!(diff + b, a); + + let mut c = a; + c -= b; + assert_eq!(c, diff); +} + +#[test] +fn g1_add_assign() { + let mut rng = ChaCha20Rng::seed_from_u64(9); + let a = random_g1(&mut rng); + let b = random_g1(&mut rng); + + let mut c = a; + c += b; + assert_eq!(c, a + b); +} + +#[test] +fn g1_commutativity() { + let mut rng = ChaCha20Rng::seed_from_u64(11); + let a = random_g1(&mut rng); + let b = random_g1(&mut rng); + assert_eq!(a + b, b + a); +} + +#[test] +#[allow(clippy::op_ref)] +fn g1_add_ref() { + let mut rng = ChaCha20Rng::seed_from_u64(12); + let a = random_g1(&mut rng); + let b = random_g1(&mut rng); + let expected = a + b; + assert_eq!(a + &b, expected); +} + +#[test] +#[allow(clippy::op_ref)] +fn g1_sub_ref() { + let mut rng = ChaCha20Rng::seed_from_u64(13); + let a = random_g1(&mut rng); + let b = random_g1(&mut rng); + let expected = a - b; + assert_eq!(a - &b, expected); +} + +#[test] +fn g1_msm_single_element() { + let mut rng = ChaCha20Rng::seed_from_u64(14); + let g = random_g1(&mut rng); + let s = Fr::random(&mut rng); + assert_eq!(Bn254G1::msm(&[g], &[s]), g.scalar_mul(&s)); +} + +#[test] +fn g1_msm_empty() { + let result = Bn254G1::msm(&[], &([] as [Fr; 0])); + assert!(result.is_identity()); +} + +#[test] +fn g1_default_is_identity() { + assert_eq!(Bn254G1::default(), Bn254G1::identity()); +} + +#[test] +fn g2_identity_and_inverse() { + let g = Bn254::g2_generator(); + assert_eq!(g + Bn254G2::identity(), g); + assert_eq!(g + (-g), Bn254G2::identity()); +} + +#[test] +fn g2_double_equals_add_self() { + let g = Bn254::g2_generator(); + assert_eq!(g.double(), g + g); +} + +#[test] +fn g2_scalar_mul_and_msm() { + let mut rng = ChaCha20Rng::seed_from_u64(10); + let g = Bn254::g2_generator(); + let s1 = Fr::random(&mut rng); + let s2 = Fr::random(&mut rng); + + let g2 = g.scalar_mul(&s1); + let msm_result = Bn254G2::msm(&[g, g2], &[s1, s2]); + let naive = g.scalar_mul(&s1) + g2.scalar_mul(&s2); + assert_eq!(msm_result, naive); +} + +#[test] +fn g2_sub_and_sub_assign() { + let g = Bn254::g2_generator(); + let two = Fr::from_u64(2); + let g2 = g.scalar_mul(&two); + + let diff = g2 - g; + assert_eq!(diff + g, g2); + + let mut c = g2; + c -= g; + assert_eq!(c, diff); +} + +#[test] +fn g2_add_assign() { + let g = Bn254::g2_generator(); + let two = Fr::from_u64(2); + let g2 = g.scalar_mul(&two); + + let mut c = g; + c += g; + assert_eq!(c, g2); +} + +#[test] +fn g2_scalar_mul_zero_is_identity() { + let g = Bn254::g2_generator(); + let zero = Fr::from_u64(0); + assert!(g.scalar_mul(&zero).is_identity()); +} + +#[test] +fn g2_scalar_mul_one_is_noop() { + let g = Bn254::g2_generator(); + let one = Fr::from_u64(1); + assert_eq!(g.scalar_mul(&one), g); +} + +#[test] +fn g2_default_is_identity() { + assert_eq!(Bn254G2::default(), Bn254G2::identity()); +} diff --git a/crates/jolt-crypto/tests/pairing.rs b/crates/jolt-crypto/tests/pairing.rs new file mode 100644 index 000000000..1daefca73 --- /dev/null +++ b/crates/jolt-crypto/tests/pairing.rs @@ -0,0 +1,197 @@ +//! Pairing bilinearity and consistency tests for BN254. + +use jolt_crypto::{Bn254, Bn254G2, Bn254GT, JoltGroup, PairingGroup}; +use jolt_field::{Field, Fr}; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +#[test] +fn pairing_bilinearity() { + // e(aG, bH) == e(abG, H) == e(G, abH) + let mut rng = ChaCha20Rng::seed_from_u64(42); + let a = Fr::random(&mut rng); + let b = Fr::random(&mut rng); + + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + + let ab = a * b; + + let lhs = Bn254::pairing(&g1.scalar_mul(&a), &g2.scalar_mul(&b)); + let rhs1 = Bn254::pairing(&g1.scalar_mul(&ab), &g2); + let rhs2 = Bn254::pairing(&g1, &g2.scalar_mul(&ab)); + + assert_eq!(lhs, rhs1, "e(aG, bH) != e(abG, H)"); + assert_eq!(lhs, rhs2, "e(aG, bH) != e(G, abH)"); +} + +#[test] +fn pairing_with_identity_gives_gt_identity() { + let g1 = Bn254::g1_generator(); + let g2_id = Bn254G2::identity(); + + let result = Bn254::pairing(&g1, &g2_id); + assert_eq!( + result, + Bn254GT::identity(), + "e(G, O) should be identity in GT" + ); +} + +#[test] +fn multi_pairing_matches_sum_of_individual() { + // multi_pairing([(a,b), (c,d)]) == e(a,b) + e(c,d) (additive notation) + let mut rng = ChaCha20Rng::seed_from_u64(99); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + + let a = Fr::random(&mut rng); + let b = Fr::random(&mut rng); + + let g1a = g1.scalar_mul(&a); + let g1b = g1.scalar_mul(&b); + + let multi = Bn254::multi_pairing(&[g1a, g1b], &[g2, g2]); + // In additive notation, "sum" is GT addition (which is Fq12 multiplication). + let sum = Bn254::pairing(&g1a, &g2) + Bn254::pairing(&g1b, &g2); + + assert_eq!( + multi, sum, + "multi_pairing should equal sum of individual pairings" + ); +} + +#[test] +fn single_multi_pairing_matches_pairing() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + + let single = Bn254::pairing(&g1, &g2); + let multi = Bn254::multi_pairing(&[g1], &[g2]); + assert_eq!(single, multi); +} + +#[test] +fn generators_are_not_identity() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + assert!(!g1.is_identity()); + assert!(!g2.is_identity()); +} + +#[test] +fn gt_add_identity() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + let id = Bn254GT::identity(); + + assert_eq!(e + id, e, "e + identity should be e"); + assert_eq!(id + e, e, "identity + e should be e"); +} + +#[test] +fn gt_add_assign() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + + let mut acc = Bn254GT::identity(); + acc += e; + assert_eq!(acc, e); + acc += e; + assert_eq!(acc, e + e); +} + +#[test] +fn gt_sub_is_inverse_of_add() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + + assert_eq!(e - e, Bn254GT::identity()); + assert_eq!((e + e) - e, e); +} + +#[test] +fn gt_neg_is_additive_inverse() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + + assert_eq!(e + (-e), Bn254GT::identity()); +} + +#[test] +fn gt_double_equals_add_self() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + + assert_eq!(e.double(), e + e); +} + +#[test] +fn gt_scalar_mul_two_equals_double() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + let two = Fr::from_u64(2); + + assert_eq!(e.scalar_mul(&two), e.double()); +} + +#[test] +fn gt_mul_convenience_matches_add() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + + // Mul and Add should behave identically (both map to Fq12 multiplication). + assert_eq!(e * e, e + e); +} + +#[test] +fn gt_scalar_mul_zero_is_identity() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + let zero = Fr::from_u64(0); + + assert!(e.scalar_mul(&zero).is_identity()); +} + +#[test] +fn gt_scalar_mul_one_is_noop() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let e = Bn254::pairing(&g1, &g2); + let one = Fr::from_u64(1); + + assert_eq!(e.scalar_mul(&one), e); +} + +#[test] +fn gt_msm_matches_naive() { + let mut rng = ChaCha20Rng::seed_from_u64(55); + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + + let s1 = Fr::random(&mut rng); + let s2 = Fr::random(&mut rng); + + let e1 = Bn254::pairing(&g1.scalar_mul(&s1), &g2); + let e2 = Bn254::pairing(&g1.scalar_mul(&s2), &g2); + + let ms1 = Fr::random(&mut rng); + let ms2 = Fr::random(&mut rng); + + let msm_result = Bn254GT::msm(&[e1, e2], &[ms1, ms2]); + let naive = e1.scalar_mul(&ms1) + e2.scalar_mul(&ms2); + assert_eq!(msm_result, naive); +} + +#[test] +fn gt_default_is_identity() { + assert_eq!(Bn254GT::default(), Bn254GT::identity()); +} diff --git a/crates/jolt-crypto/tests/pedersen.rs b/crates/jolt-crypto/tests/pedersen.rs new file mode 100644 index 000000000..fb2dce2d7 --- /dev/null +++ b/crates/jolt-crypto/tests/pedersen.rs @@ -0,0 +1,179 @@ +//! Pedersen commitment scheme tests over BN254 G1. + +use jolt_crypto::{Bn254, Bn254G1, JoltCommitment, JoltGroup, Pedersen, PedersenSetup}; +use jolt_field::{Field, Fr}; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +fn deterministic_setup(count: usize) -> PedersenSetup { + let mut rng = ChaCha20Rng::seed_from_u64(0xdead); + let message_generators: Vec = (0..count).map(|_| Bn254::random_g1(&mut rng)).collect(); + let blinding_generator = Bn254::random_g1(&mut rng); + PedersenSetup::new(message_generators, blinding_generator) +} + +#[test] +fn commit_verify_roundtrip() { + let setup = deterministic_setup(4); + let mut rng = ChaCha20Rng::seed_from_u64(1); + + let values: Vec = (0..4).map(|_| Fr::random(&mut rng)).collect(); + let blinding = Fr::random(&mut rng); + + let commitment = Pedersen::::commit(&setup, &values, &blinding); + assert!(Pedersen::::verify( + &setup, + &commitment, + &values, + &blinding + )); +} + +#[test] +fn wrong_values_rejected() { + let setup = deterministic_setup(4); + let mut rng = ChaCha20Rng::seed_from_u64(2); + + let values: Vec = (0..4).map(|_| Fr::random(&mut rng)).collect(); + let blinding = Fr::random(&mut rng); + + let commitment = Pedersen::::commit(&setup, &values, &blinding); + + let mut wrong_values = values.clone(); + wrong_values[0] += Fr::from_u64(1); + assert!(!Pedersen::::verify( + &setup, + &commitment, + &wrong_values, + &blinding + )); +} + +#[test] +fn wrong_blinding_rejected() { + let setup = deterministic_setup(4); + let mut rng = ChaCha20Rng::seed_from_u64(3); + + let values: Vec = (0..4).map(|_| Fr::random(&mut rng)).collect(); + let blinding = Fr::random(&mut rng); + + let commitment = Pedersen::::commit(&setup, &values, &blinding); + + let wrong_blinding = blinding + Fr::from_u64(1); + assert!(!Pedersen::::verify( + &setup, + &commitment, + &values, + &wrong_blinding + )); +} + +#[test] +fn different_blinding_different_commitment() { + let setup = deterministic_setup(2); + let mut rng = ChaCha20Rng::seed_from_u64(4); + + let values: Vec = (0..2).map(|_| Fr::random(&mut rng)).collect(); + let r1 = Fr::random(&mut rng); + let r2 = Fr::random(&mut rng); + + let c1 = Pedersen::::commit(&setup, &values, &r1); + let c2 = Pedersen::::commit(&setup, &values, &r2); + assert_ne!( + c1, c2, + "different blindings should produce different commitments" + ); +} + +#[test] +fn commitment_is_binding() { + let setup = deterministic_setup(2); + let mut rng = ChaCha20Rng::seed_from_u64(5); + + let v1: Vec = (0..2).map(|_| Fr::random(&mut rng)).collect(); + let v2: Vec = (0..2).map(|_| Fr::random(&mut rng)).collect(); + let blinding = Fr::random(&mut rng); + + let c1 = Pedersen::::commit(&setup, &v1, &blinding); + let c2 = Pedersen::::commit(&setup, &v2, &blinding); + assert_ne!( + c1, c2, + "different messages with same blinding should differ" + ); +} + +#[test] +fn additive_homomorphism() { + // C(m₁, r₁) + C(m₂, r₂) == C(m₁+m₂, r₁+r₂) + let setup = deterministic_setup(3); + let mut rng = ChaCha20Rng::seed_from_u64(6); + + let v1: Vec = (0..3).map(|_| Fr::random(&mut rng)).collect(); + let v2: Vec = (0..3).map(|_| Fr::random(&mut rng)).collect(); + let r1 = Fr::random(&mut rng); + let r2 = Fr::random(&mut rng); + + let c1 = Pedersen::::commit(&setup, &v1, &r1); + let c2 = Pedersen::::commit(&setup, &v2, &r2); + + let v_sum: Vec = v1.iter().zip(v2.iter()).map(|(a, b)| *a + *b).collect(); + let r_sum = r1 + r2; + let c_sum = Pedersen::::commit(&setup, &v_sum, &r_sum); + + assert_eq!(c1 + c2, c_sum, "Pedersen should be additively homomorphic"); +} + +#[test] +fn zero_blinding_commit() { + let setup = deterministic_setup(2); + let mut rng = ChaCha20Rng::seed_from_u64(7); + + let values: Vec = (0..2).map(|_| Fr::random(&mut rng)).collect(); + let zero = Fr::from_u64(0); + + let commitment = Pedersen::::commit(&setup, &values, &zero); + // Without blinding, commitment is purely MSM of message generators. + let expected = Bn254G1::msm(&setup.message_generators[..2], &values); + assert_eq!(commitment, expected); +} + +#[test] +fn capacity_returns_generator_count() { + let setup = deterministic_setup(10); + assert_eq!(Pedersen::::capacity(&setup), 10); +} + +#[test] +#[should_panic(expected = "exceeds generator count")] +fn commit_panics_on_exceeding_capacity() { + let setup = deterministic_setup(2); + let mut rng = ChaCha20Rng::seed_from_u64(9); + + let values: Vec = (0..3).map(|_| Fr::random(&mut rng)).collect(); + let blinding = Fr::random(&mut rng); + + let _ = Pedersen::::commit(&setup, &values, &blinding); +} + +#[test] +#[should_panic(expected = "at least one message generator")] +fn setup_panics_on_empty_generators() { + let _ = PedersenSetup::new(Vec::::new(), Bn254G1::identity()); +} + +#[test] +fn partial_values_uses_prefix_generators() { + let setup = deterministic_setup(8); + let mut rng = ChaCha20Rng::seed_from_u64(8); + + let values: Vec = (0..3).map(|_| Fr::random(&mut rng)).collect(); + let blinding = Fr::random(&mut rng); + + let commitment = Pedersen::::commit(&setup, &values, &blinding); + assert!(Pedersen::::verify( + &setup, + &commitment, + &values, + &blinding + )); +} diff --git a/crates/jolt-crypto/tests/serialization.rs b/crates/jolt-crypto/tests/serialization.rs new file mode 100644 index 000000000..0e28ca1f6 --- /dev/null +++ b/crates/jolt-crypto/tests/serialization.rs @@ -0,0 +1,135 @@ +//! Serialization round-trip tests for all BN254 types. + +use jolt_crypto::{Bn254, Bn254G1, Bn254G2, Bn254GT, JoltGroup, PairingGroup, PedersenSetup}; +use jolt_field::{Field, Fr}; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; + +fn bincode_roundtrip( + val: &T, +) -> T { + let config = bincode::config::standard(); + let bytes = bincode::serde::encode_to_vec(val, config).expect("serialize"); + let (recovered, _): (T, _) = + bincode::serde::decode_from_slice(&bytes, config).expect("deserialize"); + recovered +} + +#[test] +fn g1_json_roundtrip() { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let g = Bn254::random_g1(&mut rng); + let json = serde_json::to_string(&g).expect("serialize"); + let recovered: Bn254G1 = serde_json::from_str(&json).expect("deserialize"); + assert_eq!(g, recovered); +} + +#[test] +fn g1_bincode_roundtrip() { + let mut rng = ChaCha20Rng::seed_from_u64(1); + let g = Bn254::random_g1(&mut rng); + let recovered = bincode_roundtrip(&g); + assert_eq!(g, recovered); +} + +#[test] +fn g1_identity_roundtrip() { + let z = Bn254G1::identity(); + let recovered = bincode_roundtrip(&z); + assert_eq!(z, recovered); + assert!(recovered.is_identity()); +} + +#[test] +fn g2_json_roundtrip() { + let g = Bn254::g2_generator(); + let json = serde_json::to_string(&g).expect("serialize"); + let recovered: Bn254G2 = serde_json::from_str(&json).expect("deserialize"); + assert_eq!(g, recovered); +} + +#[test] +fn g2_bincode_roundtrip() { + let g = Bn254::g2_generator().scalar_mul(&Fr::from_u64(42)); + let recovered = bincode_roundtrip(&g); + assert_eq!(g, recovered); +} + +#[test] +fn gt_json_roundtrip() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let gt = Bn254::pairing(&g1, &g2); + + let json = serde_json::to_string(>).expect("serialize"); + let recovered: Bn254GT = serde_json::from_str(&json).expect("deserialize"); + assert_eq!(gt, recovered); +} + +#[test] +fn gt_bincode_roundtrip() { + let g1 = Bn254::g1_generator(); + let g2 = Bn254::g2_generator(); + let gt = Bn254::pairing(&g1, &g2); + let recovered = bincode_roundtrip(>); + assert_eq!(gt, recovered); +} + +#[test] +fn g1_generator_roundtrip() { + let g = Bn254::g1_generator(); + let recovered = bincode_roundtrip(&g); + assert_eq!(g, recovered); +} + +#[test] +fn multiple_g1_elements_roundtrip() { + let mut rng = ChaCha20Rng::seed_from_u64(100); + let elements: Vec = (0..10).map(|_| Bn254::random_g1(&mut rng)).collect(); + let recovered = bincode_roundtrip(&elements); + assert_eq!(elements, recovered); +} + +#[test] +fn g2_identity_roundtrip() { + let z = Bn254G2::identity(); + let recovered = bincode_roundtrip(&z); + assert_eq!(z, recovered); + assert!(recovered.is_identity()); +} + +#[test] +fn gt_identity_roundtrip() { + let z = Bn254GT::identity(); + let recovered = bincode_roundtrip(&z); + assert_eq!(z, recovered); + assert!(recovered.is_identity()); +} + +#[test] +fn pedersen_setup_bincode_roundtrip() { + let mut rng = ChaCha20Rng::seed_from_u64(200); + let gens: Vec = (0..5).map(|_| Bn254::random_g1(&mut rng)).collect(); + let blinding = Bn254::random_g1(&mut rng); + let setup = PedersenSetup::new(gens, blinding); + + let config = bincode::config::standard(); + let bytes = bincode::serde::encode_to_vec(&setup, config).expect("serialize"); + let (recovered, _): (PedersenSetup, _) = + bincode::serde::decode_from_slice(&bytes, config).expect("deserialize"); + assert_eq!(setup.message_generators, recovered.message_generators); + assert_eq!(setup.blinding_generator, recovered.blinding_generator); +} + +#[test] +fn pedersen_setup_json_roundtrip() { + let mut rng = ChaCha20Rng::seed_from_u64(201); + let gens: Vec = (0..3).map(|_| Bn254::random_g1(&mut rng)).collect(); + let blinding = Bn254::random_g1(&mut rng); + let setup = PedersenSetup::new(gens, blinding); + + let json = serde_json::to_string(&setup).expect("serialize"); + let recovered: PedersenSetup = serde_json::from_str(&json).expect("deserialize"); + assert_eq!(setup.message_generators, recovered.message_generators); + assert_eq!(setup.blinding_generator, recovered.blinding_generator); +}