Skip to content

Commit 17e1d5d

Browse files
committed
refactor(jolt-field): decouple Limbs and SignedBigInt from arkworks
- Fix SignedBigInt PartialEq/Ord inconsistency: +0 and -0 now compare equal, matching the Ord impl (consistent with SignedBigIntHi32) - Remove ark_serialize impls from Limbs, SignedBigInt, SignedBigIntHi32 (unused outside tests) - Move BigInt<->Limbs conversion from limbs.rs into arkworks/mod.rs as From impls, eliminating all arkworks references from limbs.rs and signed/ modules
1 parent 539fbc5 commit 17e1d5d

12 files changed

Lines changed: 56 additions & 475 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -217,55 +217,6 @@ expect_used = "deny"
217217
[workspace.lints.rust]
218218
unused_results = "warn"
219219

220-
[workspace.lints.clippy]
221-
pedantic = { level = "warn", priority = -1 }
222-
223-
# Pedantic overrides — suppressed because they're too noisy for math-heavy ZK code
224-
missing_errors_doc = "allow"
225-
missing_panics_doc = "allow"
226-
must_use_candidate = "allow"
227-
doc_markdown = "allow"
228-
similar_names = "allow"
229-
too_many_lines = "allow"
230-
module_name_repetitions = "allow"
231-
struct_excessive_bools = "allow"
232-
fn_params_excessive_bools = "allow"
233-
items_after_statements = "allow"
234-
uninlined_format_args = "allow"
235-
return_self_not_must_use = "allow"
236-
default_trait_access = "allow"
237-
match_same_arms = "allow"
238-
manual_let_else = "allow"
239-
used_underscore_binding = "allow"
240-
no_effect_underscore_binding = "allow"
241-
needless_pass_by_value = "allow"
242-
trivially_copy_pass_by_ref = "allow"
243-
redundant_closure_for_method_calls = "allow"
244-
unnecessary_wraps = "allow"
245-
if_not_else = "allow"
246-
247-
# Numeric/math code — ZK cryptography uses these patterns pervasively
248-
float_cmp = "allow"
249-
many_single_char_names = "allow"
250-
wildcard_imports = "allow"
251-
inline_always = "allow"
252-
checked_conversions = "allow"
253-
254-
# Cast lints — field arithmetic requires intentional casting
255-
cast_possible_truncation = "allow"
256-
cast_sign_loss = "allow"
257-
cast_precision_loss = "allow"
258-
cast_possible_wrap = "allow"
259-
cast_lossless = "allow"
260-
261-
# Code quality — hard denies to catch AI-generated slop
262-
dbg_macro = "deny"
263-
todo = "deny"
264-
unimplemented = "deny"
265-
print_stdout = "deny"
266-
print_stderr = "deny"
267-
undocumented_unsafe_blocks = "deny"
268-
269220
[workspace.dependencies]
270221
# Cryptography and Math
271222
ark-bn254 = { git = "https://github.com/a16z/arkworks-algebra", branch = "dev/twist-shout", default-features = false }

crates/jolt-field/Cargo.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ categories = ["cryptography"]
1313
workspace = true
1414

1515
[dependencies]
16-
ark-ff = { workspace = true }
17-
ark-serialize = { workspace = true }
16+
ark-ff = { workspace = true, optional = true }
17+
ark-serialize = { workspace = true, optional = true }
1818
ark-bn254 = { workspace = true, features = ["scalar_field"], optional = true }
1919
num-traits = { workspace = true }
2020
serde = { workspace = true, features = ["derive"] }
@@ -24,9 +24,11 @@ rand = { workspace = true }
2424
rand_core = { workspace = true }
2525

2626
[features]
27-
default = ["bn254"]
28-
bn254 = ["dep:ark-bn254"]
27+
default = ["bn254", "dory-pcs"]
28+
arkworks = ["dep:ark-ff", "dep:ark-serialize"]
29+
bn254 = ["dep:ark-bn254", "arkworks"]
2930
dory-pcs = ["dep:dory", "bn254"]
31+
allocative = ["dep:allocative"]
3032

3133
[dev-dependencies]
3234
ark-std = { workspace = true }

crates/jolt-field/benches/field_arith.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
#![allow(unused_results)]
1+
#![expect(unused_results)]
22

3-
use criterion::{black_box, criterion_group, criterion_main, Criterion};
3+
use std::hint::black_box;
4+
5+
use criterion::{criterion_group, criterion_main, Criterion};
46
use jolt_field::{Field, Fr};
57
use rand_chacha::ChaCha20Rng;
68
use rand_core::SeedableRng;

crates/jolt-field/src/arkworks/bn254.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ impl Fr {
291291
/// less than the modulus.
292292
#[inline]
293293
pub fn from_bigint_unchecked(limbs: Limbs<4>) -> Self {
294-
Fr(bn254_ops::from_bigint_unchecked(limbs.to_bigint()))
294+
Fr(bn254_ops::from_bigint_unchecked(limbs.into()))
295295
}
296296

297297
/// Access the internal Montgomery-form limbs.
@@ -315,12 +315,13 @@ impl Field for Fr {
315315

316316
const NUM_BYTES: usize = 32;
317317

318+
#[expect(clippy::expect_used)]
318319
fn to_bytes(&self) -> [u8; 32] {
319320
use ark_serialize::CanonicalSerialize;
320321
let mut buf = [0u8; 32];
321322
self.0
322323
.serialize_compressed(&mut buf[..])
323-
.expect("field serialization should not fail");
324+
.expect("BN254 Fr always serializes to 32 bytes");
324325
buf
325326
}
326327

@@ -406,6 +407,7 @@ impl Field for Fr {
406407
}
407408

408409
#[cfg(test)]
410+
#[expect(clippy::unwrap_used)]
409411
mod tests {
410412
use super::*;
411413
use crate::Field;

crates/jolt-field/src/arkworks/bn254_ops.rs

Lines changed: 4 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@ fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 {
1616
tmp as u64
1717
}
1818

19-
/// a + b * c → (result, carry) (no input carry)
20-
#[inline(always)]
21-
fn mac_no_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 {
22-
let tmp = (a as u128) + (b as u128) * (c as u128);
23-
*carry = (tmp >> 64) as u64;
24-
tmp as u64
25-
}
26-
2719
/// *a += b + carry → new carry
2820
#[inline(always)]
2921
fn adc(a: &mut u64, b: u64, carry: u64) -> u64 {
@@ -45,8 +37,6 @@ const N: usize = 4;
4537
const MODULUS: [u64; N] = <FrConfig as MontConfig<N>>::MODULUS.0;
4638
const INV: u64 = <FrConfig as MontConfig<N>>::INV;
4739
const R: BigInt<N> = <FrConfig as MontConfig<N>>::R;
48-
#[allow(dead_code)]
49-
const R2: BigInt<N> = <FrConfig as MontConfig<N>>::R2;
5040

5141
const MODULUS_HAS_SPARE_BIT: bool = MODULUS[N - 1] >> 63 == 0;
5242
const MODULUS_NUM_SPARE_BITS: u32 = MODULUS[N - 1].leading_zeros();
@@ -124,7 +114,6 @@ const PRECOMP_TABLE_SIZE: usize = 1 << 14;
124114
/// `PRECOMP_TABLE[i]` = Montgomery form of `i` for BN254 Fr.
125115
///
126116
/// Uses `Fp::new()` which converts standard form → Montgomery form at compile time.
127-
#[allow(long_running_const_eval)]
128117
static PRECOMP_TABLE: [Fr; PRECOMP_TABLE_SIZE] = {
129118
let mut table: [Fr; PRECOMP_TABLE_SIZE] =
130119
[Fp::new_unchecked(BigInt([0u64; N])); PRECOMP_TABLE_SIZE];
@@ -165,6 +154,7 @@ fn nplus1_from_low_n_and_top(low_n: [u64; N], top: u64) -> BigInt<5> {
165154
/// Conditional subtraction for Barrett reduction: reduce a 5-limb intermediate
166155
/// that is known to be < 4p down to < p (4 limbs).
167156
#[inline(always)]
157+
#[expect(clippy::unwrap_used)]
168158
fn barrett_cond_subtract(r_tmp: BigInt<5>) -> BigInt<N> {
169159
let (m2_lo, _m2_hi) = MODULUS_TIMES_2;
170160
let (m3_lo, _m3_hi) = MODULUS_TIMES_3;
@@ -268,26 +258,10 @@ fn mul_bigint5_by_u64_in_place(a: &mut BigInt<5>, b: u64) {
268258
// Overflow is discarded (caller ensures result fits in 5 limbs)
269259
}
270260

271-
/// Barrett reduce an L-limb BigInt to a field element.
272-
///
273-
/// Folds from high limb to low, applying the 5→4 kernel at each step.
274-
#[inline(always)]
275-
pub(crate) fn from_barrett_reduce<const L: usize>(unreduced: BigInt<L>) -> Fr {
276-
debug_assert!(L >= N);
277-
let mut acc = BigInt::<N>([0u64; N]);
278-
let mut i = L;
279-
while i > 0 {
280-
i -= 1;
281-
let c5 = nplus1_from_low_and_high(unreduced.0[i], acc.0);
282-
acc = barrett_reduce_5_to_4(c5);
283-
}
284-
Fp::new_unchecked(acc)
285-
}
286-
287261
/// Perform N Montgomery reduction steps on a mutable buffer of L >= 2N limbs.
288262
/// Returns carry from the final step.
289263
#[inline(always)]
290-
#[allow(clippy::needless_range_loop)]
264+
#[expect(clippy::needless_range_loop)]
291265
fn montgomery_reduce_in_place<const L: usize>(limbs: &mut [u64; L]) -> u64 {
292266
debug_assert!(L >= 2 * N);
293267
let mut carry2 = 0u64;
@@ -396,6 +370,7 @@ fn from_unchecked_nplus1(element: BigInt<5>) -> Fr {
396370

397371
/// Barrett reduce BigInt<6> → Fr via two rounds
398372
#[inline(always)]
373+
#[expect(clippy::unwrap_used)]
399374
fn from_unchecked_nplus2(element: BigInt<6>) -> Fr {
400375
// Round 1: reduce top 5 limbs (indices 1..6)
401376
let c1 = BigInt::<5>(element.0[1..6].try_into().unwrap());
@@ -486,74 +461,14 @@ pub(crate) fn from_u128(n: u128) -> Fr {
486461
}
487462
}
488463

489-
/// Multiply by a sparse RHS with exactly 2 non-zero high limbs at positions N-2 and N-1.
490-
///
491-
/// This is used in the Challenge × Field hot path where the challenge value
492-
/// has only its top 2 limbs set (128-bit challenge stored in high position).
493-
///
494-
/// Interleaves multiplication with Montgomery reduction for efficiency.
495-
#[inline(always)]
496-
pub(crate) fn mul_by_hi_2limbs(a: Fr, limb_lo: u64, limb_hi: u64) -> Fr {
497-
let a_limbs = a.0 .0;
498-
let mut r = [0u64; N];
499-
500-
// Process limb at position N-2 (limb_lo), with interleaved Montgomery step
501-
{
502-
let mut carry1 = 0u64;
503-
r[0] = mac_no_carry(r[0], a_limbs[0], limb_lo, &mut carry1);
504-
let k = r[0].wrapping_mul(INV);
505-
let mut carry2 = 0u64;
506-
let _ = mac_no_carry(r[0], k, MODULUS[0], &mut carry2);
507-
for j in 1..N {
508-
let new_rj = mac_with_carry(r[j], a_limbs[j], limb_lo, &mut carry1);
509-
let new_rj_minus_1 = mac_with_carry(new_rj, k, MODULUS[j], &mut carry2);
510-
r[j] = new_rj;
511-
r[j - 1] = new_rj_minus_1;
512-
}
513-
r[N - 1] = carry1.wrapping_add(carry2);
514-
}
515-
516-
// Process limb at position N-1 (limb_hi), with interleaved Montgomery step
517-
{
518-
let mut carry1 = 0u64;
519-
r[0] = mac_no_carry(r[0], a_limbs[0], limb_hi, &mut carry1);
520-
let k = r[0].wrapping_mul(INV);
521-
let mut carry2 = 0u64;
522-
let _ = mac_no_carry(r[0], k, MODULUS[0], &mut carry2);
523-
for j in 1..N {
524-
let new_rj = mac_with_carry(r[j], a_limbs[j], limb_hi, &mut carry1);
525-
let new_rj_minus_1 = mac_with_carry(new_rj, k, MODULUS[j], &mut carry2);
526-
r[j] = new_rj;
527-
r[j - 1] = new_rj_minus_1;
528-
}
529-
r[N - 1] = carry1.wrapping_add(carry2);
530-
}
531-
532-
let mut out = Fp::new_unchecked(BigInt::<N>(r));
533-
if compare_4(out.0 .0, MODULUS) != core::cmp::Ordering::Less {
534-
out.0 = BigInt(sub_4(out.0 .0, MODULUS));
535-
}
536-
out
537-
}
538-
539464
/// Wrap a raw BigInt<4> as Fr without any reduction (caller guarantees it's valid).
540465
#[inline(always)]
541466
pub(crate) fn from_bigint_unchecked(r: BigInt<N>) -> Fr {
542467
Fp::new_unchecked(r)
543468
}
544469

545-
/// Multiply `BigInt<N>` by `u64` and accumulate into `BigInt<5>`.
546-
#[inline(always)]
547-
pub(crate) fn mul_u64_accumulate(acc: &mut BigInt<5>, a: &BigInt<N>, b: u64) {
548-
let mut carry = 0u64;
549-
for i in 0..N {
550-
acc.0[i] = mac_with_carry(acc.0[i], a.0[i], b, &mut carry);
551-
}
552-
let final_carry = adc(&mut acc.0[N], carry, 0);
553-
debug_assert!(final_carry == 0, "overflow in mul_u64_accumulate");
554-
}
555-
556470
#[cfg(test)]
471+
#[expect(clippy::unwrap_used)]
557472
mod tests {
558473
use super::*;
559474
use ark_ff::{PrimeField, UniformRand};
@@ -697,35 +612,6 @@ mod tests {
697612
}
698613
}
699614

700-
#[test]
701-
fn barrett_reduce_correct() {
702-
let mut rng = test_rng();
703-
// Barrett reduce of a product a*b should equal a*b in the field
704-
for _ in 0..200 {
705-
let a = Fr::rand(&mut rng);
706-
let b = Fr::rand(&mut rng);
707-
// Compute unreduced product in 8 limbs
708-
let a_bigint = a.into_bigint();
709-
let b_bigint = b.into_bigint();
710-
let mut prod = BigInt::<8>::zero();
711-
for i in 0..N {
712-
let mut carry = 0u64;
713-
for j in 0..N {
714-
prod.0[i + j] =
715-
mac_with_carry(prod.0[i + j], a_bigint.0[i], b_bigint.0[j], &mut carry);
716-
}
717-
prod.0[i + N] = carry;
718-
}
719-
// Barrett reduce should give the same result as Montgomery reduce
720-
// (both map from standard 8-limb → 4-limb Montgomery)
721-
let reduced = from_barrett_reduce::<8>(prod);
722-
// Verify it's a valid field element by roundtripping
723-
let _ = reduced.into_bigint();
724-
}
725-
// Barrett reduce of zero should give zero
726-
assert_eq!(from_barrett_reduce::<5>(BigInt::<5>::zero()), Fr::zero());
727-
}
728-
729615
#[test]
730616
fn montgomery_reduce_roundtrip() {
731617
let mut rng = test_rng();
@@ -751,23 +637,4 @@ mod tests {
751637
assert_eq!(got, expected, "Montgomery reduce roundtrip mismatch");
752638
}
753639
}
754-
755-
#[test]
756-
fn mul_by_hi_2limbs_correct() {
757-
let mut rng = test_rng();
758-
for _ in 0..200 {
759-
let a = Fr::rand(&mut rng);
760-
let lo: u64 = rng.gen();
761-
let hi: u64 = rng.gen();
762-
// mul_by_hi_2limbs treats [0, 0, lo, hi] as a raw Montgomery-form scalar
763-
let scalar = Fp::new_unchecked(BigInt::new([0, 0, lo, hi]));
764-
let expected = a * scalar;
765-
let got = mul_by_hi_2limbs(a, lo, hi);
766-
assert_eq!(
767-
got, expected,
768-
"mul_by_hi_2limbs mismatch: lo={}, hi={}",
769-
lo, hi
770-
);
771-
}
772-
}
773640
}

crates/jolt-field/src/arkworks/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,24 @@
33
//! Provides the BN254 scalar field (`Fr`) and its low-level arithmetic
44
//! (Montgomery/Barrett reduction, precomputed lookup tables, sparse multiplication).
55
6+
use crate::Limbs;
7+
use ark_ff::BigInt;
8+
69
pub mod bn254;
7-
#[allow(dead_code)]
810
pub(crate) mod bn254_ops;
911
pub mod montgomery_impl;
1012
pub mod wide_accumulator;
13+
14+
impl<const N: usize> From<Limbs<N>> for BigInt<N> {
15+
#[inline]
16+
fn from(limbs: Limbs<N>) -> Self {
17+
BigInt(limbs.0)
18+
}
19+
}
20+
21+
impl<const N: usize> From<BigInt<N>> for Limbs<N> {
22+
#[inline]
23+
fn from(bigint: BigInt<N>) -> Self {
24+
Limbs(bigint.0)
25+
}
26+
}

crates/jolt-field/src/arkworks/wide_accumulator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl FieldAccumulator for WideAccumulator {
5252
// The accumulator holds sum_i (a_i_mont × b_i_mont).
5353
// Montgomery reduction divides by R, yielding the Montgomery form
5454
// of sum_i (a_i × b_i).
55-
let bigint = self.limbs.to_bigint();
55+
let bigint = self.limbs.into();
5656
Fr::from_inner(bn254_ops::from_montgomery_reduce(bigint))
5757
}
5858
}

crates/jolt-field/src/dory_interop.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ impl arithmetic::Field for Fr {
128128
}
129129

130130
#[cfg(test)]
131+
#[expect(clippy::unwrap_used, clippy::expect_used)]
131132
mod tests {
132133
use super::*;
133134
use dory::primitives::arithmetic::Field as DoryField;

0 commit comments

Comments
 (0)