From 7b499287f213bf9de9961561a378c6c63543ef2d Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:13:25 -0700 Subject: [PATCH 01/47] placeholder for SmallFp --- ff/src/fields/models/small_fp/mod.rs | 1 + 1 file changed, 1 insertion(+) create mode 100644 ff/src/fields/models/small_fp/mod.rs diff --git a/ff/src/fields/models/small_fp/mod.rs b/ff/src/fields/models/small_fp/mod.rs new file mode 100644 index 000000000..d045a6460 --- /dev/null +++ b/ff/src/fields/models/small_fp/mod.rs @@ -0,0 +1 @@ +// We can drop SmallFpConfig https://github.com/compsec-epfl/efficient-sumcheck/blob/pr-77/src/fields/small_fp_backend.rs#L16 here and try to mirror the current layout as much as makes sense. \ No newline at end of file From daa99f71a5b7646552411d1e070dfa0f5503b9e4 Mon Sep 17 00:00:00 2001 From: benbencik Date: Thu, 25 Sep 2025 00:13:31 +0200 Subject: [PATCH 02/47] migrate smallfp implementation from blendy --- ff-macros/src/lib.rs | 29 ++ ff-macros/src/small_fp/mod.rs | 53 +++ ff-macros/src/small_fp/montgomery_backend.rs | 160 +++++++++ ff-macros/src/small_fp/standard_backend.rs | 135 ++++++++ ff-macros/src/small_fp/utils.rs | 125 +++++++ ff/src/fields/models/mod.rs | 3 + ff/src/fields/models/small_fp/field.rs | 186 +++++++++++ ff/src/fields/models/small_fp/from.rs | 142 ++++++++ ff/src/fields/models/small_fp/mod.rs | 8 +- ff/src/fields/models/small_fp/ops.rs | 306 +++++++++++++++++ ff/src/fields/models/small_fp/serialize.rs | 101 ++++++ .../models/small_fp/small_fp_backend.rs | 314 ++++++++++++++++++ 12 files changed, 1561 insertions(+), 1 deletion(-) create mode 100644 ff-macros/src/small_fp/mod.rs create mode 100644 ff-macros/src/small_fp/montgomery_backend.rs create mode 100644 ff-macros/src/small_fp/standard_backend.rs create mode 100644 ff-macros/src/small_fp/utils.rs create mode 100644 ff/src/fields/models/small_fp/field.rs create mode 100644 ff/src/fields/models/small_fp/from.rs create mode 100644 ff/src/fields/models/small_fp/ops.rs create mode 100644 ff/src/fields/models/small_fp/serialize.rs create mode 100644 ff/src/fields/models/small_fp/small_fp_backend.rs diff --git a/ff-macros/src/lib.rs b/ff-macros/src/lib.rs index 41e19f0bd..1efd9c4e2 100644 --- a/ff-macros/src/lib.rs +++ b/ff-macros/src/lib.rs @@ -12,6 +12,7 @@ use proc_macro::TokenStream; use syn::{Expr, ExprLit, Item, ItemFn, Lit, Meta}; mod montgomery; +mod small_fp; mod unroll; pub(crate) mod utils; @@ -74,6 +75,34 @@ pub fn mont_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream { .into() } +/// Derive the `SmallFpConfig` trait for small prime fields. +/// +/// The attributes available to this macro are: +/// * `modulus`: Specify the prime modulus underlying this prime field. +/// * `generator`: Specify the generator of the multiplicative subgroup. +/// * `backend`: Specify either "standard" or "montgomery" backend. +#[proc_macro_derive(SmallFpConfig, attributes(modulus, generator, backend))] +pub fn small_fp_config(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + + let modulus: u128 = fetch_attr("modulus", &ast.attrs) + .expect("Please supply a modulus attribute") + .parse() + .expect("Modulus should be a number"); + + let generator: u128 = fetch_attr("generator", &ast.attrs) + .expect("Please supply a generator attribute") + .parse() + .expect("Generator should be a number"); + + let backend: String = fetch_attr("backend", &ast.attrs) + .expect("Please supply a backend attribute") + .parse() + .expect("Backend should be a string"); + + small_fp::small_fp_config_helper(modulus, generator, backend, ast.ident).into() +} + const ARG_MSG: &str = "Failed to parse unroll threshold; must be a positive integer"; /// Attribute used to unroll for loops found inside a function block. diff --git a/ff-macros/src/small_fp/mod.rs b/ff-macros/src/small_fp/mod.rs new file mode 100644 index 000000000..50205bde8 --- /dev/null +++ b/ff-macros/src/small_fp/mod.rs @@ -0,0 +1,53 @@ +mod montgomery_backend; +mod standard_backend; +mod utils; + +use quote::quote; + +pub(crate) fn small_fp_config_helper( + modulus: u128, + generator: u128, + backend: String, + config_name: proc_macro2::Ident, +) -> proc_macro2::TokenStream { + let (ty, _suffix) = { + let u8_max = u128::from(u8::MAX); + let u16_max = u128::from(u16::MAX); + let u32_max = u128::from(u32::MAX); + let u64_max = u128::from(u64::MAX); + + if modulus <= u8_max { + (quote! { u8 }, "u8") + } else if modulus <= u16_max { + (quote! { u16 }, "u16") + } else if modulus <= u32_max { + (quote! { u32 }, "u32") + } else if modulus <= u64_max { + (quote! { u64 }, "u64") + } else { + (quote! { u128 }, "u128") + } + }; + + let backend_impl = match backend.as_str() { + "standard" => standard_backend::backend_impl(ty.clone(), modulus, generator), + "montgomery" => montgomery_backend::backend_impl(ty.clone(), modulus, generator), + _ => panic!("Unknown backend type: {}", backend), + }; + + let new_impl = match backend.as_str() { + "standard" => standard_backend::new(), + "montgomery" => montgomery_backend::new(modulus, ty), + _ => panic!("Unknown backend type: {}", backend), + }; + + quote! { + impl SmallFpConfig for #config_name { + #backend_impl + } + + impl #config_name { + #new_impl + } + } +} diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs new file mode 100644 index 000000000..e2738fa33 --- /dev/null +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -0,0 +1,160 @@ +use super::*; +use crate::small_fp::utils::{ + compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, +}; + +pub(crate) fn backend_impl( + ty: proc_macro2::TokenStream, + modulus: u128, + generator: u128, +) -> proc_macro2::TokenStream { + let k_bits = 128 - modulus.leading_zeros(); + let r: u128 = 1u128 << k_bits; + let r_mod_n = r % modulus; + let r_mask = r - 1; + + let n_prime = mod_inverse_pow2(modulus, k_bits); + let one_mont = r_mod_n; + let generator_mont = (generator % modulus) * (r_mod_n % modulus) % modulus; + + let two_adicity = compute_two_adicity(modulus); + let two_adic_root = compute_two_adic_root_of_unity(modulus, generator, two_adicity); + let two_adic_root_mont = (two_adic_root * r_mod_n) % modulus; + + let (from_bigint_impl, into_bigint_impl) = + generate_montgomery_bigint_casts(modulus, k_bits, r_mod_n); + + quote! { + type T = #ty; + const MODULUS: Self::T = #modulus as Self::T; + const MODULUS_128: u128 = #modulus; + const GENERATOR: SmallFp = SmallFp::new(#generator_mont as Self::T); + const ZERO: SmallFp = SmallFp::new(0 as Self::T); + const ONE: SmallFp = SmallFp::new(#one_mont as Self::T); + const NEG_ONE: SmallFp = SmallFp::new(((Self::MODULUS - 1) as u128 * #r_mod_n % #modulus) as Self::T); + + + const TWO_ADICITY: u32 = #two_adicity; + const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::new(#two_adic_root_mont as Self::T); + + const SQRT_PRECOMP: Option>> = None; + + fn add_assign(a: &mut SmallFp, b: &SmallFp) { + a.value = match a.value.overflowing_add(b.value) { + (val, false) => val % Self::MODULUS, + (val, true) => (Self::T::MAX - Self::MODULUS + 1 + val) % Self::MODULUS, + }; + } + + fn sub_assign(a: &mut SmallFp, b: &SmallFp) { + if a.value >= b.value { + a.value -= b.value; + } else { + a.value = Self::MODULUS - (b.value - a.value); + } + } + + fn double_in_place(a: &mut SmallFp) { + let tmp = *a; + Self::add_assign(a, &tmp); + } + + fn neg_in_place(a: &mut SmallFp) { + if a.value != (0 as Self::T) { + a.value = Self::MODULUS - a.value; + } + } + + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + let a_u128 = a.value as u128; + let b_u128 = b.value as u128; + + let t = a_u128.wrapping_mul(b_u128); + let m = t.wrapping_mul(#n_prime) & #r_mask; + let mn = (m as u128).wrapping_mul(#modulus); + + let t_plus_mn = t.wrapping_add(mn); + let mut u = t_plus_mn >> #k_bits; + + if u >= #modulus { + u -= #modulus; + } + a.value = u as Self::T; + } + + fn sum_of_products( + a: &[SmallFp; T], + b: &[SmallFp; T],) -> SmallFp { + let mut acc = SmallFp::new(0 as Self::T); + for (x, y) in a.iter().zip(b.iter()) { + let mut prod = *x; + Self::mul_assign(&mut prod, y); + Self::add_assign(&mut acc, &prod); + } + acc + } + + fn square_in_place(a: &mut SmallFp) { + let tmp = *a; + Self::mul_assign(a, &tmp); + } + + fn inverse(a: &SmallFp) -> Option> { + if a.value == 0 { + return None; + } + + let mut result = Self::ONE; + let mut base = *a; + let mut exp = Self::MODULUS - 2; + + while exp > 0 { + if exp & 1 == 1 { + Self::mul_assign(&mut result, &base); + } + + let mut sq = base; + Self::mul_assign(&mut sq, &base); + base = sq; + exp >>= 1; + } + + Some(result) + } + + #from_bigint_impl + + #into_bigint_impl + } +} + +fn mod_inverse_pow2(n: u128, bits: u32) -> u128 { + let mut inv = 1u128; + for _ in 0..bits { + inv = inv.wrapping_mul(2u128.wrapping_sub(n.wrapping_mul(inv))); + } + inv.wrapping_neg() +} + +pub(crate) fn new(modulus: u128, _ty: proc_macro2::TokenStream) -> proc_macro2::TokenStream { + let k_bits = 128 - modulus.leading_zeros(); + let r: u128 = 1u128 << k_bits; + let r2 = (r * r) % modulus; + + quote! { + pub fn new(value: ::T) -> SmallFp { + let reduced_value = value % ::MODULUS; + let mut tmp = SmallFp::new(reduced_value); + let r2_elem = SmallFp::new(#r2 as ::T); + ::mul_assign(&mut tmp, &r2_elem); + tmp + } + + pub fn exit(a: &mut SmallFp) { + let mut tmp = *a; + let one = SmallFp::new(1 as ::T); + ::mul_assign(&mut tmp, &one); + a.value = tmp.value; + } + } +} diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs new file mode 100644 index 000000000..846f3894f --- /dev/null +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -0,0 +1,135 @@ +use super::*; +use crate::small_fp::utils::{ + compute_two_adic_root_of_unity, compute_two_adicity, generate_bigint_casts, +}; + +pub(crate) fn backend_impl( + ty: proc_macro2::TokenStream, + modulus: u128, + generator: u128, +) -> proc_macro2::TokenStream { + let two_adicity = compute_two_adicity(modulus); + let two_adic_root_of_unity = compute_two_adic_root_of_unity(modulus, generator, two_adicity); + + let (from_bigint_impl, into_bigint_impl) = generate_bigint_casts(modulus); + + quote! { + type T = #ty; + const MODULUS: Self::T = #modulus as Self::T; + const MODULUS_128: u128 = #modulus; + const GENERATOR: SmallFp = SmallFp::new(#generator as Self::T); + const ZERO: SmallFp = SmallFp::new(0 as Self::T); + const ONE: SmallFp = SmallFp::new(1 as Self::T); + const NEG_ONE: SmallFp = SmallFp::new((Self::MODULUS - 1) as Self::T); + + const TWO_ADICITY: u32 = #two_adicity; + const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::new(#two_adic_root_of_unity as Self::T); + const SQRT_PRECOMP: Option>> = None; + + fn add_assign(a: &mut SmallFp, b: &SmallFp) { + a.value = match a.value.overflowing_add(b.value) { + (val, false) => val % Self::MODULUS, + (val, true) => (Self::T::MAX - Self::MODULUS + 1 + val) % Self::MODULUS, + }; + } + + fn sub_assign(a: &mut SmallFp, b: &SmallFp) { + if a.value >= b.value { + a.value -= b.value; + } else { + a.value = Self::MODULUS - (b.value - a.value); + } + } + + fn double_in_place(a: &mut SmallFp) { + //* Note: This might be faster using bitshifts + let tmp = *a; + Self::add_assign(a, &tmp); + } + + fn neg_in_place(a: &mut SmallFp) { + if a.value != (0 as Self::T) { + a.value = Self::MODULUS - a.value; + } + } + + // TODO: this could be done faster + // a = a1*C + a0, b = b1*C + b0 + // a*b = a1*b1*C^2 + (a1*b0 + a0*b1)*C + a0*b0 + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + let a_128 = (a.value as u128) % #modulus; + let b_128 = (b.value as u128) % #modulus; + let mod_add = |x: u128, y: u128| -> u128 { + if x >= #modulus - y { + x - (#modulus - y) + } else { + x + y + } + }; + a.value = match a_128.overflowing_mul(b_128) { + (val, false) => (val % #modulus) as Self::T, + (_, true) => { + let mut result = 0u128; + let mut base = a_128 % #modulus; + let mut exp = b_128; + while exp > 0 { + if exp & 1 == 1 { + result = mod_add(result, base); + } + base = mod_add(base, base); + exp >>= 1; + } + result as Self::T + } + }; + } + + fn sum_of_products( + a: &[SmallFp; T], + b: &[SmallFp; T],) -> SmallFp { + let mut acc = SmallFp::new(0 as Self::T); + for (x, y) in a.iter().zip(b.iter()) { + let mut prod = *x; + Self::mul_assign(&mut prod, y); + Self::add_assign(&mut acc, &prod); + } + acc + } + + fn square_in_place(a: &mut SmallFp) { + let tmp = *a; + Self::mul_assign(a, &tmp); + } + + fn inverse(a: &SmallFp) -> Option> { + if a.value == 0 { + return None; + } + let mut base = *a; + let mut exp = Self::MODULUS - 2; + let mut acc = Self::ONE; + while exp > 0 { + if (exp & 1) == 1 { + Self::mul_assign(&mut acc, &base); + } + let mut sq = base; + Self::mul_assign(&mut sq, &base); + base = sq; + exp >>= 1; + } + Some(acc) + } + + #from_bigint_impl + + #into_bigint_impl + } +} + +pub(crate) fn new() -> proc_macro2::TokenStream { + quote! { + pub fn new(value: ::T) -> SmallFp { + SmallFp::new(value % ::MODULUS) + } + } +} diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs new file mode 100644 index 000000000..99a13990a --- /dev/null +++ b/ff-macros/src/small_fp/utils.rs @@ -0,0 +1,125 @@ +use super::*; + +// Compute the largest integer `s` such that `N - 1 = 2**s * t` for odd `t`. +pub(crate) const fn compute_two_adicity(modulus: u128) -> u32 { + assert!(modulus % 2 == 1, "Modulus must be odd"); + assert!(modulus > 1, "Modulus must be greater than 1"); + + let mut n_minus_1 = modulus - 1; + let mut two_adicity = 0; + + while n_minus_1 % 2 == 0 { + n_minus_1 /= 2; + two_adicity += 1; + } + two_adicity +} + +const fn mod_add(x: u128, y: u128, modulus: u128) -> u128 { + if x >= modulus - y { + x - (modulus - y) + } else { + x + y + } +} + +const fn safe_mul_const(a: u128, b: u128, modulus: u128) -> u128 { + match a.overflowing_mul(b) { + (val, false) => val % modulus, + (_, true) => { + let mut result = 0u128; + let mut base = a % modulus; + let mut exp = b; + + while exp > 0 { + if exp & 1 == 1 { + result = mod_add(result, base, modulus); + } + base = mod_add(base, base, modulus); + exp >>= 1; + } + result + }, + } +} + +// Two adicity root of unity `w` is defined as `w = g^((N-1)/2^s)` where `s` is two adidcity +// Therefore `w^(2^s) = 1 mod N` +pub(crate) const fn compute_two_adic_root_of_unity( + modulus: u128, + generator: u128, + two_adicity: u32, +) -> u128 { + let mut exp = (modulus - 1) >> two_adicity; + let mut base = generator % modulus; + let mut result = 1u128; + + while exp > 0 { + if exp & 1 == 1 { + result = safe_mul_const(result, base, modulus); + } + base = safe_mul_const(base, base, modulus); + exp /= 2; + } + result +} + +pub(crate) fn generate_bigint_casts( + modulus: u128, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + ( + quote! { + fn from_bigint(a: BigInt<2>) -> Option> { + let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); + if val > Self::MODULUS_128 { + None + } else { + let reduced_val = val % #modulus; + Some(SmallFp::new(reduced_val as Self::T)) + } + } + }, + quote! { + fn into_bigint(a: SmallFp) -> BigInt<2> { + let val = a.value as u128; + let lo = val as u64; + let hi = (val >> 64) as u64; + ark_ff::BigInt([lo, hi]) + } + }, + ) +} + +pub(crate) fn generate_montgomery_bigint_casts( + modulus: u128, + _k_bits: u32, + r_mod_n: u128, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let r2 = (r_mod_n * r_mod_n) % modulus; + ( + quote! { + //* Convert from standard representation to Montgomery space + fn from_bigint(a: BigInt<2>) -> Option> { + let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); + if val > Self::MODULUS_128 { + None + } else { + let reduced_val = val % #modulus; + let mut tmp = SmallFp::new(reduced_val as Self::T); + let r2_elem = SmallFp::new(#r2 as Self::T); + ::mul_assign(&mut tmp, &r2_elem); + Some(tmp) + } + } + }, + quote! { + //* Convert from Montgomery space to standard representation + fn into_bigint(a: SmallFp) -> BigInt<2> { + let mut tmp = a; + let one = SmallFp::new(1 as Self::T); + ::mul_assign(&mut tmp, &one); + ark_ff::BigInt([tmp.value as u64, 0]) + } + }, + ) +} diff --git a/ff/src/fields/models/mod.rs b/ff/src/fields/models/mod.rs index 20b0225f3..30943ba6c 100644 --- a/ff/src/fields/models/mod.rs +++ b/ff/src/fields/models/mod.rs @@ -1,6 +1,9 @@ pub mod fp; pub use self::fp::*; +pub mod small_fp; +pub use self::small_fp::*; + pub mod fp2; pub use self::fp2::*; diff --git a/ff/src/fields/models/small_fp/field.rs b/ff/src/fields/models/small_fp/field.rs new file mode 100644 index 000000000..4581546bb --- /dev/null +++ b/ff/src/fields/models/small_fp/field.rs @@ -0,0 +1,186 @@ +use crate::fields::models::small_fp::small_fp_backend::{SmallFp, SmallFpConfig}; +use crate::{Field, LegendreSymbol, One, PrimeField, SqrtPrecomputation, Zero}; +use ark_serialize::{buffer_byte_size, CanonicalDeserialize, EmptyFlags, Flags}; +use core::iter; + +impl Field for SmallFp

{ + type BasePrimeField = Self; + + const SQRT_PRECOMP: Option> = P::SQRT_PRECOMP; + const ONE: Self = P::ONE; + const NEG_ONE: Self = P::NEG_ONE; + + fn extension_degree() -> u64 { + 1 + } + + fn from_base_prime_field(elem: Self::BasePrimeField) -> Self { + elem + } + + fn to_base_prime_field_elements(&self) -> impl Iterator { + iter::once(*self) + } + + fn from_base_prime_field_elems( + elems: impl IntoIterator, + ) -> Option { + let mut iter = elems.into_iter(); + let first = iter.next()?; + if iter.next().is_some() { + None + } else { + Some(first) + } + } + + #[inline] + fn characteristic() -> &'static [u64] { + &Self::MODULUS.as_ref() + } + + #[inline] + fn sum_of_products(a: &[Self; T], b: &[Self; T]) -> Self { + P::sum_of_products(a, b) + } + + #[inline] + fn from_random_bytes_with_flags(bytes: &[u8]) -> Option<(Self, F)> { + if F::BIT_SIZE > 8 { + None + } else { + let shave_bits = Self::num_bits_to_shave(); + let mut result_bytes: crate::const_helpers::SerBuffer<2> = + crate::const_helpers::SerBuffer::zeroed(); + // Copy the input into a temporary buffer. + result_bytes.copy_from_u8_slice(bytes); + // This mask retains everything in the last limb + // that is below `P::MODULUS_BIT_SIZE`. + let last_limb_mask = + (u64::MAX.checked_shr(shave_bits as u32).unwrap_or(0)).to_le_bytes(); + let mut last_bytes_mask = [0u8; 9]; + last_bytes_mask[..8].copy_from_slice(&last_limb_mask); + + // Length of the buffer containing the field element and the flag. + let output_byte_size = buffer_byte_size(Self::MODULUS_BIT_SIZE as usize + F::BIT_SIZE); + // Location of the flag is the last byte of the serialized + // form of the field element. + let flag_location = output_byte_size - 1; + + // At which byte is the flag located in the last limb? + let flag_location_in_last_limb = + flag_location.saturating_sub(8 * (P::NUM_BIG_INT_LIMBS - 1)); + + // Take all but the last 9 bytes. + let last_bytes = result_bytes.last_n_plus_1_bytes_mut(); + + // The mask only has the last `F::BIT_SIZE` bits set + let flags_mask = u8::MAX.checked_shl(8 - (F::BIT_SIZE as u32)).unwrap_or(0); + + // Mask away the remaining bytes, and try to reconstruct the + // flag + let mut flags: u8 = 0; + for (i, (b, m)) in last_bytes.zip(&last_bytes_mask).enumerate() { + if i == flag_location_in_last_limb { + flags = *b & flags_mask + } + *b &= m; + } + Self::deserialize_compressed(&result_bytes.as_slice()[..(P::NUM_BIG_INT_LIMBS * 8)]) + .ok() + .and_then(|f| F::from_u8(flags).map(|flag| (f, flag))) + } + } + + #[inline] + fn square(&self) -> Self { + let mut temp = *self; + temp.square_in_place(); + temp + } + + fn square_in_place(&mut self) -> &mut Self { + P::square_in_place(self); + self + } + + #[inline] + fn inverse(&self) -> Option { + P::inverse(self) + } + + fn inverse_in_place(&mut self) -> Option<&mut Self> { + self.inverse().map(|inverse| { + *self = inverse; + self + }) + } + + /// The Frobenius map has no effect in a prime field. + #[inline] + fn frobenius_map_in_place(&mut self, _: usize) {} + + #[inline] + fn legendre(&self) -> LegendreSymbol { + // s = self^((MODULUS - 1) // 2) + let s = self.pow(Self::MODULUS_MINUS_ONE_DIV_TWO); + if s.is_zero() { + LegendreSymbol::Zero + } else if s.is_one() { + LegendreSymbol::QuadraticResidue + } else { + LegendreSymbol::QuadraticNonResidue + } + } + + fn mul_by_base_prime_field(&self, elem: &Self::BasePrimeField) -> Self { + *self * elem + } + + fn from_random_bytes(bytes: &[u8]) -> Option { + Self::from_random_bytes_with_flags::(bytes).map(|f| f.0) + } + + fn sqrt(&self) -> Option { + match Self::SQRT_PRECOMP { + Some(tv) => tv.sqrt(self), + None => ark_std::unimplemented!(), + } + } + + fn sqrt_in_place(&mut self) -> Option<&mut Self> { + (*self).sqrt().map(|sqrt| { + *self = sqrt; + self + }) + } + + fn frobenius_map(&self, power: usize) -> Self { + let mut this = *self; + this.frobenius_map_in_place(power); + this + } + + fn pow>(&self, exp: S) -> Self { + let mut res = Self::one(); + + for i in crate::BitIteratorBE::without_leading_zeros(exp) { + res.square_in_place(); + + if i { + res *= self; + } + } + res + } + + fn pow_with_table>(powers_of_2: &[Self], exp: S) -> Option { + let mut res = Self::one(); + for (pow, bit) in crate::BitIteratorLE::without_trailing_zeros(exp).enumerate() { + if bit { + res *= powers_of_2.get(pow)?; + } + } + Some(res) + } +} diff --git a/ff/src/fields/models/small_fp/from.rs b/ff/src/fields/models/small_fp/from.rs new file mode 100644 index 000000000..c420fc780 --- /dev/null +++ b/ff/src/fields/models/small_fp/from.rs @@ -0,0 +1,142 @@ +use crate::fields::models::small_fp::small_fp_backend::{SmallFp, SmallFpConfig}; +use crate::{BigInt, PrimeField}; + +impl From for SmallFp

{ + fn from(other: u128) -> Self { + let bigint = BigInt::<2>::new([other as u64, (other >> 64) as u64]); + Self::from_bigint(bigint).unwrap() + } +} + +impl From for SmallFp

{ + fn from(other: i128) -> Self { + let abs = other.unsigned_abs().into(); + if other.is_positive() { + abs + } else { + -abs + } + } +} + +impl From for SmallFp

{ + fn from(other: bool) -> Self { + if other == true { + P::ONE + } else { + P::ZERO + } + } +} + +impl From for SmallFp

{ + fn from(other: u64) -> Self { + Self::from(other as u128) + } +} + +impl From for SmallFp

{ + fn from(other: i64) -> Self { + let abs = other.unsigned_abs().into(); + if other.is_positive() { + abs + } else { + -abs + } + } +} + +impl From for SmallFp

{ + fn from(other: u32) -> Self { + Self::from(other as u128) + } +} + +impl From for SmallFp

{ + fn from(other: i32) -> Self { + let abs = other.unsigned_abs().into(); + if other.is_positive() { + abs + } else { + -abs + } + } +} + +impl From for SmallFp

{ + fn from(other: u16) -> Self { + let other_as_t = match P::T::try_from(other.into()) { + Ok(val) => val, + Err(_) => { + let modulus_as_u128: u128 = P::MODULUS.into(); + let reduced = (other as u128) % modulus_as_u128; + P::T::try_from(reduced).unwrap_or_else(|_| panic!("Reduced value should fit in T")) + }, + }; + let val = other_as_t % P::MODULUS; + SmallFp::new(val) + } +} + +impl From for SmallFp

{ + fn from(other: i16) -> Self { + let abs = other.unsigned_abs().into(); + if other.is_positive() { + abs + } else { + -abs + } + } +} + +impl From for SmallFp

{ + fn from(other: u8) -> Self { + let other_as_t = match P::T::try_from(other.into()) { + Ok(val) => val, + Err(_) => { + let modulus_as_u128: u128 = P::MODULUS.into(); + let reduced = (other as u128) % modulus_as_u128; + P::T::try_from(reduced).unwrap_or_else(|_| panic!("Reduced value should fit in T")) + }, + }; + let val = other_as_t % P::MODULUS; + SmallFp::new(val) + } +} + +impl From for SmallFp

{ + fn from(other: i8) -> Self { + let abs = other.unsigned_abs().into(); + if other.is_positive() { + abs + } else { + -abs + } + } +} + +impl From for SmallFp

{ + #[inline] + fn from(val: num_bigint::BigUint) -> SmallFp

{ + SmallFp::from_le_bytes_mod_order(&val.to_bytes_le()) + } +} + +impl From> for num_bigint::BigUint { + #[inline(always)] + fn from(other: SmallFp

) -> Self { + other.into_bigint().into() + } +} + +impl From> for BigInt<2> { + fn from(fp: SmallFp

) -> Self { + fp.into_bigint() + } +} + +impl From> for SmallFp

{ + fn from(int: BigInt<2>) -> Self { + Self::from_bigint(int).unwrap() + } +} diff --git a/ff/src/fields/models/small_fp/mod.rs b/ff/src/fields/models/small_fp/mod.rs index d045a6460..8f5a9bfff 100644 --- a/ff/src/fields/models/small_fp/mod.rs +++ b/ff/src/fields/models/small_fp/mod.rs @@ -1 +1,7 @@ -// We can drop SmallFpConfig https://github.com/compsec-epfl/efficient-sumcheck/blob/pr-77/src/fields/small_fp_backend.rs#L16 here and try to mirror the current layout as much as makes sense. \ No newline at end of file +pub mod field; +pub mod from; +pub mod ops; +pub mod serialize; +pub mod small_fp_backend; + +pub use small_fp_backend::{SmallFp, SmallFpConfig}; diff --git a/ff/src/fields/models/small_fp/ops.rs b/ff/src/fields/models/small_fp/ops.rs new file mode 100644 index 000000000..b7e08b65b --- /dev/null +++ b/ff/src/fields/models/small_fp/ops.rs @@ -0,0 +1,306 @@ +use crate::fields::models::small_fp::small_fp_backend::{SmallFp, SmallFpConfig}; +use crate::{Field, One, Zero}; +use ark_std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +impl Neg for SmallFp

{ + type Output = Self; + #[inline] + fn neg(mut self) -> Self { + P::neg_in_place(&mut self); + self + } +} + +impl Add<&SmallFp

> for SmallFp

{ + type Output = Self; + + #[inline] + fn add(mut self, other: &Self) -> Self { + self += other; + self + } +} + +impl Sub<&SmallFp

> for SmallFp

{ + type Output = Self; + + #[inline] + fn sub(mut self, other: &Self) -> Self { + self -= other; + self + } +} + +impl Mul<&SmallFp

> for SmallFp

{ + type Output = Self; + + #[inline] + fn mul(mut self, other: &Self) -> Self { + self *= other; + self + } +} + +impl Div<&SmallFp

> for SmallFp

{ + type Output = Self; + + /// Returns `self * other.inverse()` if `other.inverse()` is `Some`, and + /// panics otherwise. + #[inline] + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(mut self, other: &Self) -> Self { + match other.inverse() { + Some(inv) => { + self *= &inv; + self + }, + None => panic!("Division by zero in finite field"), + } + } +} + +impl<'b, P: SmallFpConfig> Add<&'b SmallFp

> for &SmallFp

{ + type Output = SmallFp

; + + #[inline] + fn add(self, other: &'b SmallFp

) -> SmallFp

{ + let mut result = *self; + result += other; + result + } +} + +impl Sub<&SmallFp

> for &SmallFp

{ + type Output = SmallFp

; + + #[inline] + fn sub(self, other: &SmallFp

) -> SmallFp

{ + let mut result = *self; + result -= other; + result + } +} + +impl Mul<&SmallFp

> for &SmallFp

{ + type Output = SmallFp

; + + #[inline] + fn mul(self, other: &SmallFp

) -> SmallFp

{ + let mut result = *self; + result *= other; + result + } +} + +impl Div<&SmallFp

> for &SmallFp

{ + type Output = SmallFp

; + + #[inline] + fn div(self, other: &SmallFp

) -> SmallFp

{ + let mut result = *self; + result.div_assign(other); + result + } +} + +impl AddAssign<&Self> for SmallFp

{ + #[inline] + fn add_assign(&mut self, other: &Self) { + P::add_assign(self, other) + } +} + +impl SubAssign<&Self> for SmallFp

{ + #[inline] + fn sub_assign(&mut self, other: &Self) { + P::sub_assign(self, other); + } +} + +impl core::ops::Add for SmallFp

{ + type Output = Self; + + #[inline] + fn add(mut self, other: Self) -> Self { + self += &other; + self + } +} + +impl<'a, P: SmallFpConfig> core::ops::Add<&'a mut Self> for SmallFp

{ + type Output = Self; + + #[inline] + fn add(mut self, other: &'a mut Self) -> Self { + self += &*other; + self + } +} + +impl core::ops::Sub for SmallFp

{ + type Output = Self; + + #[inline] + fn sub(mut self, other: Self) -> Self { + self -= &other; + self + } +} + +impl<'a, P: SmallFpConfig> core::ops::Sub<&'a mut Self> for SmallFp

{ + type Output = Self; + + #[inline] + fn sub(mut self, other: &'a mut Self) -> Self { + self -= &*other; + self + } +} + +impl core::iter::Sum for SmallFp

{ + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), core::ops::Add::add) + } +} + +impl<'a, P: SmallFpConfig> core::iter::Sum<&'a Self> for SmallFp

{ + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), core::ops::Add::add) + } +} + +impl core::ops::AddAssign for SmallFp

{ + #[inline(always)] + fn add_assign(&mut self, other: Self) { + *self += &other + } +} + +impl core::ops::SubAssign for SmallFp

{ + #[inline(always)] + fn sub_assign(&mut self, other: Self) { + *self -= &other + } +} + +impl<'a, P: SmallFpConfig> core::ops::AddAssign<&'a mut Self> for SmallFp

{ + #[inline(always)] + fn add_assign(&mut self, other: &'a mut Self) { + *self += &*other + } +} + +impl<'a, P: SmallFpConfig> core::ops::SubAssign<&'a mut Self> for SmallFp

{ + #[inline(always)] + fn sub_assign(&mut self, other: &'a mut Self) { + *self -= &*other + } +} + +impl MulAssign<&Self> for SmallFp

{ + fn mul_assign(&mut self, other: &Self) { + P::mul_assign(self, other) + } +} + +/// Computes `self *= other.inverse()` if `other.inverse()` is `Some`, and +/// panics otherwise. +impl DivAssign<&Self> for SmallFp

{ + #[inline(always)] + fn div_assign(&mut self, other: &Self) { + match other.inverse() { + Some(inv) => { + *self *= &inv; + }, + None => panic!("Division by zero in finite field"), + } + } +} + +impl core::ops::Mul for SmallFp

{ + type Output = Self; + + #[inline(always)] + fn mul(mut self, other: Self) -> Self { + self *= &other; + self + } +} + +impl core::ops::Div for SmallFp

{ + type Output = Self; + + #[inline(always)] + fn div(mut self, other: Self) -> Self { + self.div_assign(&other); + self + } +} + +impl<'a, P: SmallFpConfig> core::ops::Mul<&'a mut Self> for SmallFp

{ + type Output = Self; + + #[inline(always)] + fn mul(mut self, other: &'a mut Self) -> Self { + self *= &*other; + self + } +} + +impl<'a, P: SmallFpConfig> core::ops::Div<&'a mut Self> for SmallFp

{ + type Output = Self; + + #[inline(always)] + fn div(mut self, other: &'a mut Self) -> Self { + self.div_assign(&*other); + self + } +} + +impl core::iter::Product for SmallFp

{ + fn product>(iter: I) -> Self { + iter.fold(Self::one(), core::ops::Mul::mul) + } +} + +impl<'a, P: SmallFpConfig> core::iter::Product<&'a Self> for SmallFp

{ + fn product>(iter: I) -> Self { + iter.fold(Self::one(), Mul::mul) + } +} + +impl core::ops::MulAssign for SmallFp

{ + #[inline(always)] + fn mul_assign(&mut self, other: Self) { + *self *= &other + } +} + +impl<'a, P: SmallFpConfig> core::ops::DivAssign<&'a mut Self> for SmallFp

{ + #[inline(always)] + fn div_assign(&mut self, other: &'a mut Self) { + self.div_assign(&*other) + } +} + +impl<'a, P: SmallFpConfig> core::ops::MulAssign<&'a mut Self> for SmallFp

{ + #[inline(always)] + fn mul_assign(&mut self, other: &'a mut Self) { + *self *= &*other + } +} + +impl core::ops::DivAssign for SmallFp

{ + #[inline(always)] + fn div_assign(&mut self, other: Self) { + self.div_assign(&other) + } +} + +impl zeroize::Zeroize for SmallFp

{ + // The phantom data does not contain element-specific data + // and thus does not need to be zeroized. + fn zeroize(&mut self) { + self.value = P::ZERO.value; + } +} diff --git a/ff/src/fields/models/small_fp/serialize.rs b/ff/src/fields/models/small_fp/serialize.rs new file mode 100644 index 000000000..dd18b7460 --- /dev/null +++ b/ff/src/fields/models/small_fp/serialize.rs @@ -0,0 +1,101 @@ +use crate::fields::models::small_fp::small_fp_backend::{SmallFp, SmallFpConfig}; +use crate::{PrimeField, Zero}; +use ark_serialize::{ + buffer_byte_size, CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize, + CanonicalSerializeWithFlags, Compress, EmptyFlags, Flags, SerializationError, Valid, Validate, +}; + +impl CanonicalSerializeWithFlags for SmallFp

{ + fn serialize_with_flags( + &self, + writer: W, + flags: F, + ) -> Result<(), SerializationError> { + // All reasonable `Flags` should be less than 8 bits in size + // (256 values are enough for anyone!) + if F::BIT_SIZE > 8 { + return Err(SerializationError::NotEnoughSpace); + } + + // Calculate the number of bytes required to represent a field element + // serialized with `flags`. If `F::BIT_SIZE < 8`, + // this is at most `N * 8 + 1` + let output_byte_size = buffer_byte_size(Self::MODULUS_BIT_SIZE as usize + F::BIT_SIZE); + + // Write out `self` to a temporary buffer. + // The size of the buffer is $byte_size + 1 because `F::BIT_SIZE` + // is at most 8 bits. + let mut bytes = crate::const_helpers::SerBuffer::zeroed(); + bytes.copy_from_u64_slice(&self.into_bigint().0); + // Mask out the bits of the last byte that correspond to the flag. + bytes[output_byte_size - 1] |= flags.u8_bitmask(); + + bytes.write_up_to(writer, output_byte_size)?; + Ok(()) + } + + // Let `m = 8 * n` for some `n` be the smallest multiple of 8 greater + // than `P::MODULUS_BIT_SIZE`. + // If `(m - P::MODULUS_BIT_SIZE) >= F::BIT_SIZE` , then this method returns `n`; + // otherwise, it returns `n + 1`. + fn serialized_size_with_flags(&self) -> usize { + buffer_byte_size(Self::MODULUS_BIT_SIZE as usize + F::BIT_SIZE) + } +} + +impl CanonicalSerialize for SmallFp

{ + #[inline] + fn serialize_with_mode( + &self, + writer: W, + _compress: Compress, + ) -> Result<(), SerializationError> { + self.serialize_with_flags(writer, EmptyFlags) + } + + #[inline] + fn serialized_size(&self, _compress: Compress) -> usize { + self.serialized_size_with_flags::() + } +} + +impl CanonicalDeserializeWithFlags for SmallFp

{ + fn deserialize_with_flags( + reader: R, + ) -> Result<(Self, F), SerializationError> { + // All reasonable `Flags` should be less than 8 bits in size + // (256 values are enough for anyone!) + if F::BIT_SIZE > 8 { + return Err(SerializationError::NotEnoughSpace); + } + // Calculate the number of bytes required to represent a field element + // serialized with `flags`. + let output_byte_size = Self::zero().serialized_size_with_flags::(); + + let mut masked_bytes = crate::const_helpers::SerBuffer::zeroed(); + masked_bytes.read_exact_up_to(reader, output_byte_size)?; + let flags = F::from_u8_remove_flags(&mut masked_bytes[output_byte_size - 1]) + .ok_or(SerializationError::UnexpectedFlags)?; + + let self_integer = masked_bytes.to_bigint(); + Self::from_bigint(self_integer) + .map(|v| (v, flags)) + .ok_or(SerializationError::InvalidData) + } +} + +impl Valid for SmallFp

{ + fn check(&self) -> Result<(), SerializationError> { + Ok(()) + } +} + +impl CanonicalDeserialize for SmallFp

{ + fn deserialize_with_mode( + reader: R, + _compress: Compress, + _validate: Validate, + ) -> Result { + Self::deserialize_with_flags::(reader).map(|(r, _)| r) + } +} diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs new file mode 100644 index 000000000..17003472d --- /dev/null +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -0,0 +1,314 @@ +use crate::{AdditiveGroup, BigInt, FftField, One, PrimeField, SqrtPrecomputation, Zero}; +use ark_std::{ + cmp::*, + fmt::{Display, Formatter, Result as FmtResult}, + hash::Hash, + marker::PhantomData, + str::FromStr, +}; +use educe::Educe; +use num_traits::Unsigned; + +/// A trait that specifies the configuration of a prime field. +/// Also specifies how to perform arithmetic on field elements. +pub trait SmallFpConfig: Send + Sync + 'static + Sized { + type T: Copy + + Default + + PartialEq + + Eq + + Hash + + Sync + + Send + + PartialOrd + + Display + + Unsigned + + core::fmt::Debug + + core::ops::Add + + core::ops::Sub + + core::ops::Mul + + core::ops::Div + + core::ops::Rem + + Into + + TryFrom; + + /// The modulus of the field. + const MODULUS: Self::T; + const MODULUS_128: u128; + + // ! this is fixed temporarily the value can be 1 or 2 + const NUM_BIG_INT_LIMBS: usize = 2; + + /// A multiplicative generator of the field. + /// `Self::GENERATOR` is an element having multiplicative order + /// `Self::MODULUS - 1`. + const GENERATOR: SmallFp; + + /// Additive identity of the field, i.e. the element `e` + /// such that, for all elements `f` of the field, `e + f = f`. + const ZERO: SmallFp; + + /// Multiplicative identity of the field, i.e. the element `e` + /// such that, for all elements `f` of the field, `e * f = f`. + const ONE: SmallFp; + + /// Negation of the multiplicative identity of the field. + const NEG_ONE: SmallFp; + + /// Let `N` be the size of the multiplicative group defined by the field. + /// Then `TWO_ADICITY` is the two-adicity of `N`, i.e. the integer `s` + /// such that `N = 2^s * t` for some odd integer `t`. + const TWO_ADICITY: u32; + + /// 2^s root of unity computed by GENERATOR^t + const TWO_ADIC_ROOT_OF_UNITY: SmallFp; + + /// An integer `b` such that there exists a multiplicative subgroup + /// of size `b^k` for some integer `k`. + const SMALL_SUBGROUP_BASE: Option = None; + + /// The integer `k` such that there exists a multiplicative subgroup + /// of size `Self::SMALL_SUBGROUP_BASE^k`. + const SMALL_SUBGROUP_BASE_ADICITY: Option = None; + + /// GENERATOR^((MODULUS-1) / (2^s * + /// SMALL_SUBGROUP_BASE^SMALL_SUBGROUP_BASE_ADICITY)) Used for mixed-radix + /// FFT. + const LARGE_SUBGROUP_ROOT_OF_UNITY: Option> = None; + + /// Precomputed material for use when computing square roots. + /// Currently uses the generic Tonelli-Shanks, + /// which works for every modulus. + const SQRT_PRECOMP: Option>>; + + /// Set a += b. + fn add_assign(a: &mut SmallFp, b: &SmallFp); + + /// Set a -= b. + fn sub_assign(a: &mut SmallFp, b: &SmallFp); + + /// Set a = a + a. + fn double_in_place(a: &mut SmallFp); + + /// Set a = -a; + fn neg_in_place(a: &mut SmallFp); + + /// Set a *= b. + fn mul_assign(a: &mut SmallFp, b: &SmallFp); + + /// Compute the inner product ``. + fn sum_of_products( + a: &[SmallFp; T], + b: &[SmallFp; T], + ) -> SmallFp; + + /// Set a *= a. + fn square_in_place(a: &mut SmallFp); + + /// Compute a^{-1} if `a` is not zero. + fn inverse(a: &SmallFp) -> Option>; + + /// Construct a field element from an integer in the range + /// `0..(Self::MODULUS - 1)`. Returns `None` if the integer is outside + /// this range. + fn from_bigint(other: BigInt<2>) -> Option>; + + /// Convert a field element to an integer in the range `0..(Self::MODULUS - + /// 1)`. + fn into_bigint(other: SmallFp) -> BigInt<2>; +} + +/// Represents an element of the prime field F_p, where `p == P::MODULUS`. +/// This type can represent elements in any field of size at most N * 64 bits. +#[derive(Educe)] +#[educe(Default, Hash, Clone, Copy, PartialEq, Eq)] +pub struct SmallFp { + pub value: P::T, + _phantom: PhantomData

, +} + +impl SmallFp

{ + #[doc(hidden)] + #[inline] + pub fn is_geq_modulus(&self) -> bool { + self.value >= P::MODULUS + } + + pub const fn new(value: P::T) -> Self { + Self { + value, + _phantom: PhantomData, + } + } + + pub fn num_bits_to_shave() -> usize { + 64 * P::NUM_BIG_INT_LIMBS - (Self::MODULUS_BIT_SIZE as usize) + } +} + +impl ark_std::fmt::Debug for SmallFp

{ + fn fmt(&self, f: &mut Formatter<'_>) -> ark_std::fmt::Result { + ark_std::fmt::Debug::fmt(&self.into_bigint(), f) + } +} + +impl Zero for SmallFp

{ + #[inline] + fn zero() -> Self { + P::ZERO + } + + #[inline] + fn is_zero(&self) -> bool { + *self == P::ZERO + } +} + +impl One for SmallFp

{ + #[inline] + fn one() -> Self { + P::ONE + } + + #[inline] + fn is_one(&self) -> bool { + *self == P::ONE + } +} + +impl AdditiveGroup for SmallFp

{ + type Scalar = Self; + const ZERO: Self = P::ZERO; + + #[inline] + fn double(&self) -> Self { + let mut temp = *self; + AdditiveGroup::double_in_place(&mut temp); + temp + } + + #[inline] + fn double_in_place(&mut self) -> &mut Self { + P::double_in_place(self); + self + } + + #[inline] + fn neg_in_place(&mut self) -> &mut Self { + P::neg_in_place(self); + self + } +} + +const fn const_to_bigint(value: u128) -> BigInt<2> { + let low = (value & 0xFFFFFFFFFFFFFFFF) as u64; + let high = (value >> 64) as u64; + BigInt::<2>::new([low, high]) +} + +impl PrimeField for SmallFp

{ + type BigInt = BigInt<2>; + + const MODULUS: Self::BigInt = const_to_bigint(P::MODULUS_128); + const MODULUS_MINUS_ONE_DIV_TWO: Self::BigInt = Self::MODULUS.divide_by_2_round_down(); + const MODULUS_BIT_SIZE: u32 = Self::MODULUS.const_num_bits(); + const TRACE: Self::BigInt = Self::MODULUS.two_adic_coefficient(); + const TRACE_MINUS_ONE_DIV_TWO: Self::BigInt = Self::TRACE.divide_by_2_round_down(); + + #[inline] + fn from_bigint(r: BigInt<2>) -> Option { + P::from_bigint(r) + } + + fn into_bigint(self) -> BigInt<2> { + P::into_bigint(self) + } +} + +impl FftField for SmallFp

{ + const GENERATOR: Self = P::GENERATOR; + const TWO_ADICITY: u32 = P::TWO_ADICITY; + const TWO_ADIC_ROOT_OF_UNITY: Self = P::TWO_ADIC_ROOT_OF_UNITY; + const SMALL_SUBGROUP_BASE: Option = P::SMALL_SUBGROUP_BASE; + const SMALL_SUBGROUP_BASE_ADICITY: Option = P::SMALL_SUBGROUP_BASE_ADICITY; + const LARGE_SUBGROUP_ROOT_OF_UNITY: Option = P::LARGE_SUBGROUP_ROOT_OF_UNITY; +} + +/// Note that this implementation of `Ord` compares field elements viewing +/// them as integers in the range 0, 1, ..., P::MODULUS - 1. However, other +/// implementations of `PrimeField` might choose a different ordering, and +/// as such, users should use this `Ord` for applications where +/// any ordering suffices (like in a BTreeMap), and not in applications +/// where a particular ordering is required. +impl Ord for SmallFp

{ + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.into_bigint().cmp(&other.into_bigint()) + } +} + +/// Note that this implementation of `PartialOrd` compares field elements +/// viewing them as integers in the range 0, 1, ..., `P::MODULUS` - 1. However, +/// other implementations of `PrimeField` might choose a different ordering, and +/// as such, users should use this `PartialOrd` for applications where +/// any ordering suffices (like in a BTreeMap), and not in applications +/// where a particular ordering is required. +impl PartialOrd for SmallFp

{ + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl ark_std::rand::distributions::Distribution> + for ark_std::rand::distributions::Standard +{ + #[inline] + fn sample(&self, rng: &mut R) -> SmallFp

{ + //* note: loop to prevent modulus bias of distribution + loop { + let random_val: BigInt<2> = rng.sample(ark_std::rand::distributions::Standard); + let random_elem: SmallFp

= SmallFp::from(random_val); + + if !random_elem.is_geq_modulus() { + return random_elem; + } + } + } +} + +pub enum ParseSmallFpError { + Empty, + InvalidFormat, + InvalidLeadingZero, +} + +impl FromStr for SmallFp

{ + type Err = ParseSmallFpError; + + /// Interpret a string of numbers as a (congruent) prime field element. + /// Does not accept unnecessary leading zeroes or a blank string. + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Err(ParseSmallFpError::Empty); + } + if s.starts_with('0') && s.len() > 1 { + return Err(ParseSmallFpError::InvalidLeadingZero); + } + + match s.parse::() { + Ok(val) => Ok(SmallFp::from(val)), + Err(_) => Err(ParseSmallFpError::InvalidFormat), + } + } +} + +/// Outputs a string containing the value of `self`, +/// represented as a decimal without leading zeroes. +impl Display for SmallFp

{ + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + // Always use BigInt conversion to display the true mathematical value + let bigint = P::into_bigint(*self); + write!(f, "{}", bigint) + } +} From 0fadbb9568f267bb7e5f8cfa658af42f7ade6fa0 Mon Sep 17 00:00:00 2001 From: benbencik Date: Thu, 25 Sep 2025 00:13:43 +0200 Subject: [PATCH 03/47] add tests --- examples/fpconfig_working.rs | 731 +++++++++++++++++++++++++++++++++++ ff/Cargo.toml | 20 +- 2 files changed, 747 insertions(+), 4 deletions(-) create mode 100644 examples/fpconfig_working.rs diff --git a/examples/fpconfig_working.rs b/examples/fpconfig_working.rs new file mode 100644 index 000000000..687bea822 --- /dev/null +++ b/examples/fpconfig_working.rs @@ -0,0 +1,731 @@ +// Simple example demonstrating field arithmetic with ark-ff using MontConfig +// This is equivalent to fpconfig.rs but uses the stable MontConfig derive + +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::{BigInt, Fp64, MontBackend, MontConfig, SmallFp, SmallFpConfig, SqrtPrecomputation}; + +// M31 prime field: 2^31 - 1 = 2147483647 +#[derive(MontConfig)] +#[modulus = "2147483647"] +#[generator = "3"] +pub struct M31Config; + +pub type M31Field = Fp64>; + +// BabyBear prime field: 2^31 - 2^27 + 1 = 2013265921 +#[derive(MontConfig)] +#[modulus = "2013265921"] +#[generator = "31"] +pub struct BabyBearConfig; + +pub type BabyBearField = Fp64>; + +#[derive(SmallFpConfig)] +#[modulus = "2147483647"] // m31 +#[generator = "7"] +#[backend = "standard"] +pub struct SmallField; + +#[derive(SmallFpConfig)] +#[modulus = "2013265921"] // BabyBear +#[generator = "31"] +#[backend = "montgomery"] +pub struct SmallFieldMont; + +fn main() { + let mut a = SmallFieldMont::new(20); + println!("{}", a); + SmallFieldMont::exit(&mut a); + println!("{}", a); +} + +#[cfg(test)] +mod tests { + use super::*; + + // ---------- Standard backend tests (existing) ---------- + #[test] + fn add_assign_test() { + let mut a = SmallField::new(20); + let b = SmallField::new(10); + let c = SmallField::new(30); + a += b; + assert_eq!(a.value, c.value); + + let mut a = SmallField::new(SmallField::MODULUS - 1); + let b = SmallField::new(2); + a += b; + assert_eq!(a.value, 1); + + // adding zero + let mut a = SmallField::new(42); + let b = SmallField::ZERO; + a += b; + assert_eq!(a.value, 42); + + // max values + let mut a = SmallField::new(SmallField::MODULUS - 1); + let b = SmallField::new(SmallField::MODULUS - 1); + a += b; + assert_eq!(a.value, SmallField::MODULUS - 2); + + // adding one to maximum + let mut a = SmallField::new(SmallField::MODULUS - 1); + let b = SmallField::ONE; + a += b; + assert_eq!(a.value, 0); + } + + #[test] + fn sub_assign_test() { + let mut a = SmallField::new(30); + let b = SmallField::new(10); + let c = SmallField::new(20); + a -= b; + assert_eq!(a.value, c.value); + + let mut a = SmallField::new(5); + let b = SmallField::new(10); + a -= b; + assert_eq!(a.value, SmallField::MODULUS - 5); + + // subtracting zero + let mut a = SmallField::new(42); + let b = SmallField::ZERO; + a -= b; + assert_eq!(a.value, 42); + + // subtracting from zero + let mut a = SmallField::ZERO; + let b = SmallField::new(1); + a -= b; + assert_eq!(a.value, SmallField::MODULUS - 1); + + // self subtraction + let mut a = SmallField::new(42); + let b = SmallField::new(42); + a -= b; + assert_eq!(a.value, 0); + + // maximum minus one + let mut a = SmallField::new(SmallField::MODULUS - 1); + let b = SmallField::ONE; + a -= b; + assert_eq!(a.value, SmallField::MODULUS - 2); + } + + #[test] + fn mul_assign_test() { + let mut a = SmallField::new(5); + let b = SmallField::new(10); + let c = SmallField::new(50); + a *= b; + assert_eq!(a.value, c.value); + + let mut a = SmallField::new(SmallField::MODULUS / 2); + let b = SmallField::new(3); + a *= b; + assert_eq!(a.value, (SmallField::MODULUS / 2) * 3 % SmallField::MODULUS); + + // multiply by zero + let mut a = SmallField::new(42); + let b = SmallField::ZERO; + a *= b; + assert_eq!(a.value, 0); + + // multiply by one + let mut a = SmallField::new(42); + let b = SmallField::ONE; + a *= b; + assert_eq!(a.value, 42); + + // maximum values + let mut a = SmallField::new(SmallField::MODULUS - 1); + let b = SmallField::new(SmallField::MODULUS - 1); + a *= b; + assert_eq!(a.value, 1); // (p-1)*(p-1) = p^2 - 2p + 1 ≡ 1 (mod p) + } + + #[test] + fn neg_in_place_test() { + let mut a = SmallField::new(10); + SmallField::neg_in_place(&mut a); + assert_eq!(a.value, SmallField::MODULUS - 10); + + let mut a = SmallField::ZERO; + SmallField::neg_in_place(&mut a); + assert_eq!(a.value, 0); + + // negate maximum + let mut a = SmallField::new(SmallField::MODULUS - 1); + SmallField::neg_in_place(&mut a); + assert_eq!(a.value, 1); + + // Edge double negation + let mut a = SmallField::new(42); + let original = a.value; + SmallField::neg_in_place(&mut a); + SmallField::neg_in_place(&mut a); + assert_eq!(a.value, original); + + // negate one + let mut a = SmallField::ONE; + SmallField::neg_in_place(&mut a); + assert_eq!(a.value, SmallField::MODULUS - 1); + } + + #[test] + fn double_in_place_test() { + let mut a = SmallField::new(10); + SmallField::double_in_place(&mut a); + assert_eq!(a.value, 20); + + let mut a = SmallField::new(SmallField::MODULUS - 1); + SmallField::double_in_place(&mut a); + assert_eq!(a.value, SmallField::MODULUS - 2); + + // double zero + let mut a = SmallField::ZERO; + SmallField::double_in_place(&mut a); + assert_eq!(a.value, 0); + + // double maximum/2 + 1 (should wrap) + if SmallField::MODULUS > 2 { + let mut a = SmallField::new(SmallField::MODULUS / 2 + 1); + SmallField::double_in_place(&mut a); + assert_eq!( + a.value, + (SmallField::MODULUS / 2 + 1) * 2 % SmallField::MODULUS + ); + } + + // double one + let mut a = SmallField::ONE; + SmallField::double_in_place(&mut a); + assert_eq!(a.value, 2); + } + + #[test] + fn square_in_place_test() { + let mut a = SmallField::new(5); + let b = SmallField::new(25); + SmallField::square_in_place(&mut a); + assert_eq!(a.value, b.value); + + let mut a = SmallField::new(SmallField::MODULUS - 1); + SmallField::square_in_place(&mut a); + assert_eq!(a.value, 1); + + // square zero + let mut a = SmallField::ZERO; + SmallField::square_in_place(&mut a); + assert_eq!(a.value, 0); + + // square one + let mut a = SmallField::ONE; + SmallField::square_in_place(&mut a); + assert_eq!(a.value, 1); + } + + #[test] + fn zero_inverse() { + let zero = SmallField::ZERO; + assert!(SmallField::inverse(&zero).is_none()) + } + + #[test] + fn test_specific_inverse() { + let mut val = SmallField::new(17); + let val_inv = SmallField::inverse(&val); + SmallField::mul_assign(&mut val, &val_inv.unwrap()); + assert_eq!(val, SmallField::ONE); + } + + #[test] + fn test_inverse() { + // inverse of 1 + let one = SmallField::ONE; + let one_inv = SmallField::inverse(&one).unwrap(); + assert_eq!(one_inv, SmallField::ONE); + + // inverse of p-1 (which should be p-1 since (p-1)^2 ≡ 1 mod p) + let neg_one = SmallField::new(SmallField::MODULUS - 1); + let neg_one_inv = SmallField::inverse(&neg_one).unwrap(); + assert_eq!(neg_one_inv.value, SmallField::MODULUS - 1); + + for i in 1..100 { + let val = SmallField::new(i); + if let Some(inv) = SmallField::inverse(&val) { + let mut product = val; + SmallField::mul_assign(&mut product, &inv); + assert_eq!(product, SmallField::ONE, "Failed for value {}", i); + } + } + + // inverse property: inv(inv(x)) = x + let test_val = SmallField::new(42 % SmallField::MODULUS); + if test_val.value != 0 { + let inv1 = SmallField::inverse(&test_val).unwrap(); + let inv2 = SmallField::inverse(&inv1).unwrap(); + assert_eq!(test_val, inv2); + } + + // inverse is multiplicative: inv(ab) = inv(a) * inv(b) + let a = SmallField::new(7 % SmallField::MODULUS); + let b = SmallField::new(11 % SmallField::MODULUS); + if a.value != 0 && b.value != 0 { + let mut ab = a; + SmallField::mul_assign(&mut ab, &b); + + let inv_ab = SmallField::inverse(&ab).unwrap(); + let inv_a = SmallField::inverse(&a).unwrap(); + let inv_b = SmallField::inverse(&b).unwrap(); + + let mut inv_a_times_inv_b = inv_a; + SmallField::mul_assign(&mut inv_a_times_inv_b, &inv_b); + + assert_eq!(inv_ab, inv_a_times_inv_b); + } + } + + #[test] + fn test_field_axioms() { + // Test additive identity + let a = SmallField::new(42 % SmallField::MODULUS); + let b = SmallField::new(73 % SmallField::MODULUS); + // commutativity of multiplication + let mut a_times_b = a; + let mut b_times_a = b; + SmallField::mul_assign(&mut a_times_b, &b); + SmallField::mul_assign(&mut b_times_a, &a); + assert_eq!(a_times_b, b_times_a); + + // associativity of addition: (a + b) + c = a + (b + c) + let c = SmallField::new(91 % SmallField::MODULUS); + let mut ab_plus_c = a; + SmallField::add_assign(&mut ab_plus_c, &b); + SmallField::add_assign(&mut ab_plus_c, &c); + + let mut a_plus_bc = a; + let mut bc = b; + SmallField::add_assign(&mut bc, &c); + SmallField::add_assign(&mut a_plus_bc, &bc); + + assert_eq!(ab_plus_c, a_plus_bc); + + // distributivity: a * (b + c) = a * b + a * c + let mut a_times_bc = a; + let mut bc = b; + SmallField::add_assign(&mut bc, &c); + SmallField::mul_assign(&mut a_times_bc, &bc); + + let mut ab_plus_ac = a; + SmallField::mul_assign(&mut ab_plus_ac, &b); + let mut ac = a; + SmallField::mul_assign(&mut ac, &c); + SmallField::add_assign(&mut ab_plus_ac, &ac); + + assert_eq!(a_times_bc, ab_plus_ac); + } + + #[test] + fn test_sum_of_products() { + let a = [SmallField::new(2), SmallField::new(3), SmallField::new(5)]; + let b = [SmallField::new(7), SmallField::new(11), SmallField::new(13)]; + let result = SmallField::sum_of_products(&a, &b); + assert_eq!(result.value, 112 % SmallField::MODULUS); + + let a = [SmallField::ZERO, SmallField::new(3), SmallField::ZERO]; + let b = [SmallField::new(7), SmallField::new(11), SmallField::new(13)]; + let result = SmallField::sum_of_products(&a, &b); + assert_eq!(result.value, 33 % SmallField::MODULUS); + } + + // ---------- Montgomery backend tests ---------- + #[test] + fn add_assign_test_montgomery() { + let mut a = SmallFieldMont::new(20); + let b = SmallFieldMont::new(10); + let mut c = SmallFieldMont::new(30); + a += b; + SmallFieldMont::exit(&mut a); + SmallFieldMont::exit(&mut c); + assert_eq!(a.value, c.value); + + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + let b = SmallFieldMont::new(2); + a += b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 1); + + // adding zero + let mut a = SmallFieldMont::new(42); + let b = SmallFieldMont::ZERO; + a += b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 42); + + // max values + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + let b = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + a += b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, SmallFieldMont::MODULUS - 2); + + // adding one to maximum + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + let b = SmallFieldMont::ONE; + a += b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 0); + } + + #[test] + fn sub_assign_test_montgomery() { + let mut a = SmallFieldMont::new(30); + let b = SmallFieldMont::new(10); + let mut c = SmallFieldMont::new(20); + a -= b; + SmallFieldMont::exit(&mut a); + SmallFieldMont::exit(&mut c); + assert_eq!(a.value, c.value); + + let mut a = SmallFieldMont::new(5); + let b = SmallFieldMont::new(10); + a -= b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, SmallFieldMont::MODULUS - 5); + + // subtracting zero + let mut a = SmallFieldMont::new(42); + let b = SmallFieldMont::ZERO; + a -= b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 42); + + // subtracting from zero + let mut a = SmallFieldMont::ZERO; + let b = SmallFieldMont::new(1); + a -= b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, SmallFieldMont::MODULUS - 1); + + // self subtraction + let mut a = SmallFieldMont::new(42); + let b = SmallFieldMont::new(42); + a -= b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 0); + + // maximum minus one + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + let b = SmallFieldMont::ONE; + a -= b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, SmallFieldMont::MODULUS - 2); + } + + #[test] + fn mul_assign_test_montgomery() { + let mut a = SmallFieldMont::new(5); + let b = SmallFieldMont::new(10); + let mut c = SmallFieldMont::new(50); + a *= b; + SmallFieldMont::exit(&mut a); + SmallFieldMont::exit(&mut c); + assert_eq!(a.value, c.value); + + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS / 2); + let b = SmallFieldMont::new(3); + a *= b; + SmallFieldMont::exit(&mut a); + assert_eq!( + a.value, + (SmallFieldMont::MODULUS / 2) * 3 % SmallFieldMont::MODULUS + ); + + // multiply by zero + let mut a = SmallFieldMont::new(42); + let b = SmallFieldMont::ZERO; + a *= b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 0); + + // multiply by one + let mut a = SmallFieldMont::new(42); + let b = SmallFieldMont::ONE; + a *= b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 42); + + // maximum values + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + let b = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + a *= b; + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 1); // (p-1)*(p-1) ≡ 1 (mod p) + } + + #[test] + fn neg_in_place_test_montgomery() { + let mut a = SmallFieldMont::new(10); + SmallFieldMont::neg_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, SmallFieldMont::MODULUS - 10); + + let mut a = SmallFieldMont::ZERO; + SmallFieldMont::neg_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 0); + + // negate maximum + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + SmallFieldMont::neg_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 1); + + // Edge double negation + let mut a = SmallFieldMont::new(42); + let original = { + let mut tmp = a; + SmallFieldMont::exit(&mut tmp); + tmp.value + }; + SmallFieldMont::neg_in_place(&mut a); + SmallFieldMont::neg_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, original); + + // negate one + let mut a = SmallFieldMont::ONE; + SmallFieldMont::neg_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, SmallFieldMont::MODULUS - 1); + } + + #[test] + fn double_in_place_test_montgomery() { + let mut a = SmallFieldMont::new(10); + SmallFieldMont::double_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 20); + + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + SmallFieldMont::double_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, SmallFieldMont::MODULUS - 2); + + // double zero + let mut a = SmallFieldMont::ZERO; + SmallFieldMont::double_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 0); + + // double maximum/2 + 1 (should wrap) + if SmallFieldMont::MODULUS > 2 { + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS / 2 + 1); + SmallFieldMont::double_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!( + a.value, + (SmallFieldMont::MODULUS / 2 + 1) * 2 % SmallFieldMont::MODULUS + ); + } + + // double one + let mut a = SmallFieldMont::ONE; + SmallFieldMont::double_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 2); + } + + #[test] + fn square_in_place_test_montgomery() { + let mut a = SmallFieldMont::new(5); + let mut b = SmallFieldMont::new(25); + SmallFieldMont::square_in_place(&mut a); + SmallFieldMont::exit(&mut a); + SmallFieldMont::exit(&mut b); + assert_eq!(a.value, b.value); + + let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + SmallFieldMont::square_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 1); + + // square zero + let mut a = SmallFieldMont::ZERO; + SmallFieldMont::square_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 0); + + // square one + let mut a = SmallFieldMont::ONE; + SmallFieldMont::square_in_place(&mut a); + SmallFieldMont::exit(&mut a); + assert_eq!(a.value, 1); + } + + #[test] + fn zero_inverse_montgomery() { + let zero = SmallFieldMont::ZERO; + assert!(SmallFieldMont::inverse(&zero).is_none()) + } + + #[test] + fn test_specific_inverse_montgomery() { + let mut val = SmallFieldMont::new(17); + let val_inv = SmallFieldMont::inverse(&val); + let mut val_copy = val; + SmallFieldMont::mul_assign(&mut val_copy, &val_inv.unwrap()); + SmallFieldMont::exit(&mut val_copy); + assert_eq!(val_copy.value, 1); + } + + #[test] + fn test_inverse_montgomery() { + // inverse of 1 + let one = SmallFieldMont::ONE; + let one_inv = SmallFieldMont::inverse(&one).unwrap(); + let mut one_inv_copy = one_inv; + SmallFieldMont::exit(&mut one_inv_copy); + assert_eq!(one_inv_copy.value, 1); + + // inverse of p-1 (which should be p-1 since (p-1)^2 ≡ 1 mod p) + let neg_one = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); + let neg_one_inv = SmallFieldMont::inverse(&neg_one).unwrap(); + let mut tmp = neg_one_inv; + SmallFieldMont::exit(&mut tmp); + assert_eq!(tmp.value, SmallFieldMont::MODULUS - 1); + + for i in 1..100 { + let val = SmallFieldMont::new(i); + if let Some(inv) = SmallFieldMont::inverse(&val) { + let mut product = val; + SmallFieldMont::mul_assign(&mut product, &inv); + SmallFieldMont::exit(&mut product); + assert_eq!(product.value, 1, "Failed for value {}", i); + } + } + + // inverse property: inv(inv(x)) = x + let mut test_val = SmallFieldMont::new(42 % SmallFieldMont::MODULUS); + { + let mut tv_copy = test_val; + SmallFieldMont::exit(&mut tv_copy); + if tv_copy.value != 0 { + let inv1 = SmallFieldMont::inverse(&test_val).unwrap(); + let inv2 = SmallFieldMont::inverse(&inv1).unwrap(); + let mut inv2_copy = inv2; + SmallFieldMont::exit(&mut inv2_copy); + let mut original_copy = test_val; + SmallFieldMont::exit(&mut original_copy); + assert_eq!(original_copy.value, inv2_copy.value); + } + } + + // inverse is multiplicative: inv(ab) = inv(a) * inv(b) + let a = SmallFieldMont::new(7 % SmallFieldMont::MODULUS); + let b = SmallFieldMont::new(11 % SmallFieldMont::MODULUS); + { + let mut a_copy = a; + SmallFieldMont::exit(&mut a_copy); + let mut b_copy = b; + SmallFieldMont::exit(&mut b_copy); + if a_copy.value != 0 && b_copy.value != 0 { + let mut ab = a; + SmallFieldMont::mul_assign(&mut ab, &b); + + let inv_ab = SmallFieldMont::inverse(&ab).unwrap(); + let inv_a = SmallFieldMont::inverse(&a).unwrap(); + let inv_b = SmallFieldMont::inverse(&b).unwrap(); + + let mut inv_a_times_inv_b = inv_a; + SmallFieldMont::mul_assign(&mut inv_a_times_inv_b, &inv_b); + + let mut tmp1 = inv_ab; + let mut tmp2 = inv_a_times_inv_b; + SmallFieldMont::exit(&mut tmp1); + SmallFieldMont::exit(&mut tmp2); + assert_eq!(tmp1.value, tmp2.value); + } + } + } + + #[test] + fn test_field_axioms_montgomery() { + // Test additive identity + let a = SmallFieldMont::new(42 % SmallFieldMont::MODULUS); + let b = SmallFieldMont::new(73 % SmallFieldMont::MODULUS); + // commutativity of multiplication + let mut a_times_b = a; + let mut b_times_a = b; + SmallFieldMont::mul_assign(&mut a_times_b, &b); + SmallFieldMont::mul_assign(&mut b_times_a, &a); + SmallFieldMont::exit(&mut a_times_b); + SmallFieldMont::exit(&mut b_times_a); + assert_eq!(a_times_b.value, b_times_a.value); + + // associativity of addition: (a + b) + c = a + (b + c) + let c = SmallFieldMont::new(91 % SmallFieldMont::MODULUS); + let mut ab_plus_c = a; + SmallFieldMont::add_assign(&mut ab_plus_c, &b); + SmallFieldMont::add_assign(&mut ab_plus_c, &c); + + let mut a_plus_bc = a; + let mut bc = b; + SmallFieldMont::add_assign(&mut bc, &c); + SmallFieldMont::add_assign(&mut a_plus_bc, &bc); + + SmallFieldMont::exit(&mut ab_plus_c); + SmallFieldMont::exit(&mut a_plus_bc); + assert_eq!(ab_plus_c.value, a_plus_bc.value); + + // distributivity: a * (b + c) = a * b + a * c + let mut a_times_bc = a; + let mut bc = b; + SmallFieldMont::add_assign(&mut bc, &c); + SmallFieldMont::mul_assign(&mut a_times_bc, &bc); + + let mut ab_plus_ac = a; + SmallFieldMont::mul_assign(&mut ab_plus_ac, &b); + let mut ac = a; + SmallFieldMont::mul_assign(&mut ac, &c); + SmallFieldMont::add_assign(&mut ab_plus_ac, &ac); + + SmallFieldMont::exit(&mut a_times_bc); + SmallFieldMont::exit(&mut ab_plus_ac); + assert_eq!(a_times_bc.value, ab_plus_ac.value); + } + + #[test] + fn test_sum_of_products_montgomery() { + let a = [ + SmallFieldMont::new(2), + SmallFieldMont::new(3), + SmallFieldMont::new(5), + ]; + let b = [ + SmallFieldMont::new(7), + SmallFieldMont::new(11), + SmallFieldMont::new(13), + ]; + let mut result = SmallFieldMont::sum_of_products(&a, &b); + SmallFieldMont::exit(&mut result); + assert_eq!(result.value, 112 % SmallFieldMont::MODULUS); + + let a = [ + SmallFieldMont::ZERO, + SmallFieldMont::new(3), + SmallFieldMont::ZERO, + ]; + let b = [ + SmallFieldMont::new(7), + SmallFieldMont::new(11), + SmallFieldMont::new(13), + ]; + let mut result = SmallFieldMont::sum_of_products(&a, &b); + SmallFieldMont::exit(&mut result); + assert_eq!(result.value, 33 % SmallFieldMont::MODULUS); + } +} diff --git a/ff/Cargo.toml b/ff/Cargo.toml index 72e4f178f..ddae4d072 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ark-ff" description = "A library for finite fields" -keywords = ["cryptography", "finite-fields" ] +keywords = ["cryptography", "finite-fields"] documentation = "https://docs.rs/ark-ff/" version.workspace = true authors.workspace = true @@ -30,7 +30,11 @@ digest = { workspace = true, features = ["alloc"] } itertools.workspace = true [dev-dependencies] -ark-test-curves = { workspace = true, features = ["bls12_381_curve", "mnt6_753", "secp256k1"] } +ark-test-curves = { workspace = true, features = [ + "bls12_381_curve", + "mnt6_753", + "secp256k1", +] } blake2.workspace = true sha3.workspace = true sha2.workspace = true @@ -40,8 +44,16 @@ serde_json.workspace = true serde_derive.workspace = true hex.workspace = true +[[example]] +name = "fpconfig" +path = "../examples/fpconfig.rs" + +[[example]] +name = "fpconfig_working" +path = "../examples/fpconfig_working.rs" + [features] default = [] -std = [ "ark-std/std", "ark-serialize/std", "itertools/use_std" ] -parallel = [ "std", "rayon", "ark-std/parallel", "ark-serialize/parallel" ] +std = ["ark-std/std", "ark-serialize/std", "itertools/use_std"] +parallel = ["std", "rayon", "ark-std/parallel", "ark-serialize/parallel"] asm = [] From 3d6636e3a935b28ca0ce057c082697ae771323ae Mon Sep 17 00:00:00 2001 From: benbencik Date: Thu, 25 Sep 2025 15:00:12 +0200 Subject: [PATCH 04/47] add benchmarks --- ff/Cargo.toml | 11 ++ ff/benches/cast_bench.rs | 49 ++++++++ ff/benches/field_comparison.rs | 202 +++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 ff/benches/cast_bench.rs create mode 100644 ff/benches/field_comparison.rs diff --git a/ff/Cargo.toml b/ff/Cargo.toml index ddae4d072..51ca755d3 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -35,6 +35,8 @@ ark-test-curves = { workspace = true, features = [ "mnt6_753", "secp256k1", ] } +p3_field = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-field" } +p3_goldilocks = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-goldilocks" } blake2.workspace = true sha3.workspace = true sha2.workspace = true @@ -43,6 +45,7 @@ serde.workspace = true serde_json.workspace = true serde_derive.workspace = true hex.workspace = true +criterion.workspace = true [[example]] name = "fpconfig" @@ -52,6 +55,14 @@ path = "../examples/fpconfig.rs" name = "fpconfig_working" path = "../examples/fpconfig_working.rs" +[[bench]] +name = "cast_bench" +harness = false + +[[bench]] +name = "field_comparison" +harness = false + [features] default = [] std = ["ark-std/std", "ark-serialize/std", "itertools/use_std"] diff --git a/ff/benches/cast_bench.rs b/ff/benches/cast_bench.rs new file mode 100644 index 000000000..335f71d69 --- /dev/null +++ b/ff/benches/cast_bench.rs @@ -0,0 +1,49 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use std::hint::black_box; + +fn mult_u128(c: &mut Criterion) { + c.bench_function("u128_mult", |b| { + b.iter(|| { + let a: u8 = 5; + let b: u8 = 254; + let c: u128 = (a as u128) * (b as u128); + black_box((c % 255) as u8); + }) + }); +} + +fn mult_u16(c: &mut Criterion) { + c.bench_function("u16_mult", |b| { + b.iter(|| { + let a: u8 = 5; + let b: u8 = 254; + let c: u16 = (a as u16) * (b as u16); + black_box((c % 255) as u8); + }) + }); +} + +fn add_u128(c: &mut Criterion) { + c.bench_function("u128_add", |b| { + b.iter(|| { + let a: u8 = 5; + let b: u8 = 254; + let c: u128 = (a as u128) + (b as u128); + black_box((c % 255) as u8); + }) + }); +} + +fn add_u16(c: &mut Criterion) { + c.bench_function("u8_add", |b| { + b.iter(|| { + let a: u8 = 5; + let b: u8 = 25; + let c: u8 = a + b; + black_box((c % 255) as u8); + }) + }); +} + +criterion_group!(benches, mult_u128, mult_u16, add_u128, add_u16); +criterion_main!(benches); diff --git a/ff/benches/field_comparison.rs b/ff/benches/field_comparison.rs new file mode 100644 index 000000000..c694684b5 --- /dev/null +++ b/ff/benches/field_comparison.rs @@ -0,0 +1,202 @@ +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::fields::{Fp128, Fp64, MontBackend, MontConfig}; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; +use ark_std::hint::black_box; +use criterion::{criterion_group, criterion_main, Criterion}; +use p3_field::AbstractField; +use p3_goldilocks::Goldilocks as P3Goldilocks; + +// Goldilock's prime from Plonk3 18446744069414584321; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] +#[generator = "2"] +#[backend = "standard"] +pub struct SmallF64Config; +pub type SmallF64 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] +#[generator = "2"] +#[backend = "montgomery"] +pub struct SmallF64ConfigMont; +pub type SmallF64Mont = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] +#[generator = "2"] +#[backend = "standard"] +pub struct SmallF128Config; +pub type SmallF128 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "2"] +#[backend = "montgomery"] +pub struct SmallF128ConfigMont; +pub type SmallF128Mont = SmallFp; + +#[derive(MontConfig)] +#[modulus = "18446744069414584321"] +#[generator = "2"] +pub struct F64Config; +pub type F64 = Fp64>; + +#[derive(MontConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "2"] +pub struct F128Config; +pub type F128 = Fp128>; + +// Benchmark functions + +fn naive_element_wise_mult_ark_bigint_64(c: &mut Criterion) { + // let's get four vectors and multiply them element-wise + let len = 2_i64.pow(22); + let v1: Vec = (0..len).map(F64::from).collect(); + let v2: Vec = (0..len).map(F64::from).collect(); + let v3: Vec = (0..len).map(F64::from).collect(); + let v4: Vec = (0..len).map(F64::from).collect(); + let mut element_wise_product: Vec = vec![F64::from(0); len as usize]; + + c.bench_function("naive_element_wise_mult_ark_bigint_64", |b| { + b.iter(|| { + for i in 0..len as usize { + element_wise_product[i] = v1.get(i).unwrap() + * v2.get(i).unwrap() + * v3.get(i).unwrap() + * v4.get(i).unwrap(); + } + black_box(element_wise_product.clone()); + }) + }); +} + +fn naive_element_wise_mult_ark_small_field_64_std(c: &mut Criterion) { + // let's get four vectors and multiply them element-wise + let len = 2_i64.pow(22); + let v1: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); + let v2: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); + let v3: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); + let v4: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); + let mut element_wise_product: Vec = vec![SmallF64::from(0u32); len as usize]; + + c.bench_function("naive_element_wise_mult_ark_small_field_64_std", |b| { + b.iter(|| { + for i in 0..len as usize { + element_wise_product[i] = v1.get(i).unwrap() + * v2.get(i).unwrap() + * v3.get(i).unwrap() + * v4.get(i).unwrap(); + } + black_box(&element_wise_product); + }) + }); +} + +fn naive_element_wise_mult_ark_small_field_64_mont(c: &mut Criterion) { + // let's get four vectors and multiply them element-wise + let len = 2_i64.pow(22); + let v1: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); + let v2: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); + let v3: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); + let v4: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); + let mut element_wise_product: Vec = vec![SmallF64Mont::from(0u32); len as usize]; + + c.bench_function("naive_element_wise_mult_ark_small_field_64_mont", |b| { + b.iter(|| { + for i in 0..len as usize { + element_wise_product[i] = v1.get(i).unwrap() + * v2.get(i).unwrap() + * v3.get(i).unwrap() + * v4.get(i).unwrap(); + } + black_box(&element_wise_product); + }) + }); +} + +fn naive_element_wise_mult_p3_64(c: &mut Criterion) { + // let's get four vectors and multiply them element-wise + let len = 2_i64.pow(22); + let v1: Vec = (0..len) + .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) + .collect(); + let v2: Vec = (0..len) + .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) + .collect(); + let v3: Vec = (0..len) + .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) + .collect(); + let v4: Vec = (0..len) + .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) + .collect(); + let mut element_wise_product: Vec = + vec![P3Goldilocks::from_canonical_u64(0_u64); len as usize]; + + c.bench_function("naive_element_wise_mult_p3_64", |b| { + b.iter(|| { + for i in 0..len as usize { + element_wise_product[i] = *v1.get(i).unwrap() + * *v2.get(i).unwrap() + * *v3.get(i).unwrap() + * *v4.get(i).unwrap(); + } + black_box(element_wise_product.clone()); + }) + }); +} + +fn naive_element_wise_mult_ark_bigint_128(c: &mut Criterion) { + // let's get four vectors and multiply them element-wise + let len = 2_i64.pow(22); + let v1: Vec = (0..len).map(F128::from).collect(); + let v2: Vec = (0..len).map(F128::from).collect(); + let v3: Vec = (0..len).map(F128::from).collect(); + let v4: Vec = (0..len).map(F128::from).collect(); + let mut element_wise_product: Vec = vec![F128::from(0); len as usize]; + + c.bench_function("naive_element_wise_mult_ark_bigint_128", |b| { + b.iter(|| { + for i in 0..len as usize { + element_wise_product[i] = v1.get(i).unwrap() + * v2.get(i).unwrap() + * v3.get(i).unwrap() + * v4.get(i).unwrap(); + } + black_box(element_wise_product.clone()); + }) + }); +} + +fn naive_element_wise_mult_ark_small_field_128(c: &mut Criterion) { + let len = 2_i64.pow(22); + let v1: Vec = (0..len).map(SmallF128::from).collect(); + let v2: Vec = (0..len).map(SmallF128::from).collect(); + let v3: Vec = (0..len).map(SmallF128::from).collect(); + let v4: Vec = (0..len).map(SmallF128::from).collect(); + let mut element_wise_product: Vec = vec![SmallF128::from(0); len as usize]; + + c.bench_function("naive_element_wise_mult_ark_small_field", |b| { + b.iter(|| { + for i in 0..len as usize { + element_wise_product[i] = v1.get(i).unwrap() + * v2.get(i).unwrap() + * v3.get(i).unwrap() + * v4.get(i).unwrap(); + } + black_box(element_wise_product.clone()); + }) + }); +} + +criterion_group!( + benches, + naive_element_wise_mult_ark_bigint_64, + naive_element_wise_mult_ark_small_field_64_std, + naive_element_wise_mult_ark_small_field_64_mont, + naive_element_wise_mult_p3_64, + naive_element_wise_mult_ark_bigint_128, + naive_element_wise_mult_ark_small_field_128, +); +criterion_main!(benches); From f7de46978a2aaa9fb0f0c9ff553e3bcf6cf3ad09 Mon Sep 17 00:00:00 2001 From: benbencik Date: Thu, 2 Oct 2025 11:45:35 +0200 Subject: [PATCH 05/47] add square root precomputation --- ff-macros/src/small_fp/montgomery_backend.rs | 5 ++- ff-macros/src/small_fp/standard_backend.rs | 4 +- ff-macros/src/small_fp/utils.rs | 42 ++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index e2738fa33..142603cd8 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -1,6 +1,7 @@ use super::*; use crate::small_fp::utils::{ compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, + generate_sqrt_precomputation, }; pub(crate) fn backend_impl( @@ -23,6 +24,7 @@ pub(crate) fn backend_impl( let (from_bigint_impl, into_bigint_impl) = generate_montgomery_bigint_casts(modulus, k_bits, r_mod_n); + let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity); quote! { type T = #ty; @@ -36,8 +38,7 @@ pub(crate) fn backend_impl( const TWO_ADICITY: u32 = #two_adicity; const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::new(#two_adic_root_mont as Self::T); - - const SQRT_PRECOMP: Option>> = None; + #sqrt_precomp_impl fn add_assign(a: &mut SmallFp, b: &SmallFp) { a.value = match a.value.overflowing_add(b.value) { diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs index 846f3894f..a7556a83b 100644 --- a/ff-macros/src/small_fp/standard_backend.rs +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -1,6 +1,7 @@ use super::*; use crate::small_fp::utils::{ compute_two_adic_root_of_unity, compute_two_adicity, generate_bigint_casts, + generate_sqrt_precomputation, }; pub(crate) fn backend_impl( @@ -12,6 +13,7 @@ pub(crate) fn backend_impl( let two_adic_root_of_unity = compute_two_adic_root_of_unity(modulus, generator, two_adicity); let (from_bigint_impl, into_bigint_impl) = generate_bigint_casts(modulus); + let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity); quote! { type T = #ty; @@ -24,7 +26,7 @@ pub(crate) fn backend_impl( const TWO_ADICITY: u32 = #two_adicity; const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::new(#two_adic_root_of_unity as Self::T); - const SQRT_PRECOMP: Option>> = None; + #sqrt_precomp_impl fn add_assign(a: &mut SmallFp, b: &SmallFp) { a.value = match a.value.overflowing_add(b.value) { diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index 99a13990a..aa91981b9 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -123,3 +123,45 @@ pub(crate) fn generate_montgomery_bigint_casts( }, ) } + +pub(crate) fn generate_sqrt_precomputation( + modulus: u128, + two_adicity: u32, +) -> proc_macro2::TokenStream { + if modulus % 4 == 3 { + // Case3Mod4 + let modulus_plus_one_div_four = (modulus + 1) / 4; + let lo = modulus_plus_one_div_four as u64; + let hi = (modulus_plus_one_div_four >> 64) as u64; + + quote! { + const SQRT_PRECOMP: Option>> = { + const MODULUS_PLUS_ONE_DIV_FOUR: [u64; 2] = [#lo, #hi]; + Some(SqrtPrecomputation::Case3Mod4 { + modulus_plus_one_div_four: &MODULUS_PLUS_ONE_DIV_FOUR, + }) + }; + } + } else { + // TonelliShanks + let trace = (modulus - 1) >> two_adicity; + // t is od integer division floors to (t-1)/2 + let trace_minus_one_div_two = trace / 2; + let lo = trace_minus_one_div_two as u64; + let hi = (trace_minus_one_div_two >> 64) as u64; + + quote! { + const SQRT_PRECOMP: Option>> = { + const TRACE_MINUS_ONE_DIV_TWO: [u64; 2] = [#lo, #hi]; + // TWO_ADIC_ROOT_OF_UNITY = g^{(p-1)/2^s} = g^t with odd t + // ord(g^t) = 2^s, while any square has order at most 2^{s-1} + // TWO_ADIC_ROOT_OF_UNITY not a square + Some(SqrtPrecomputation::TonelliShanks { + two_adicity: #two_adicity, + quadratic_nonresidue_to_trace: Self::TWO_ADIC_ROOT_OF_UNITY, + trace_of_modulus_minus_one_div_two: &TRACE_MINUS_ONE_DIV_TWO, + }) + }; + } + } +} From 4d835f49f491298f72e380981dbf75994220d261 Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 3 Oct 2025 11:00:56 +0200 Subject: [PATCH 06/47] split sampling method into cases --- .../models/small_fp/small_fp_backend.rs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 17003472d..0a6bd5804 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -264,14 +264,26 @@ impl ark_std::rand::distributions::Distribution> { #[inline] fn sample(&self, rng: &mut R) -> SmallFp

{ - //* note: loop to prevent modulus bias of distribution - loop { - let random_val: BigInt<2> = rng.sample(ark_std::rand::distributions::Standard); - let random_elem: SmallFp

= SmallFp::from(random_val); - - if !random_elem.is_geq_modulus() { - return random_elem; - } + // loop avoids sampling bias + match P::MODULUS_128 { + modulus if modulus <= u32::MAX as u128 => loop { + let random_val: u32 = rng.sample(ark_std::rand::distributions::Standard); + if (random_val as u128) < P::MODULUS_128 { + return SmallFp::from(random_val); + } + }, + modulus if modulus <= u64::MAX as u128 => loop { + let random_val: u64 = rng.sample(ark_std::rand::distributions::Standard); + if (random_val as u128) < P::MODULUS_128 { + return SmallFp::from(random_val); + } + }, + _ => loop { + let random_val: u128 = rng.sample(ark_std::rand::distributions::Standard); + if random_val < P::MODULUS_128 { + return SmallFp::from(random_val); + } + }, } } } From 4ba1448c26fb1aac2d2df24893f1bafa3fb10723 Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 3 Oct 2025 12:47:20 +0200 Subject: [PATCH 07/47] fix mul overflow issue --- ff-macros/src/small_fp/montgomery_backend.rs | 5 +++-- ff-macros/src/small_fp/utils.rs | 9 ++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 142603cd8..2aa5c21d8 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -1,7 +1,7 @@ use super::*; use crate::small_fp::utils::{ compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, - generate_sqrt_precomputation, + generate_sqrt_precomputation, safe_mul_const, }; pub(crate) fn backend_impl( @@ -140,7 +140,8 @@ fn mod_inverse_pow2(n: u128, bits: u32) -> u128 { pub(crate) fn new(modulus: u128, _ty: proc_macro2::TokenStream) -> proc_macro2::TokenStream { let k_bits = 128 - modulus.leading_zeros(); let r: u128 = 1u128 << k_bits; - let r2 = (r * r) % modulus; + let r_mod_n = r % modulus; + let r2 = safe_mul_const(r_mod_n, r_mod_n, modulus); quote! { pub fn new(value: ::T) -> SmallFp { diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index aa91981b9..735a315f1 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -23,7 +23,7 @@ const fn mod_add(x: u128, y: u128, modulus: u128) -> u128 { } } -const fn safe_mul_const(a: u128, b: u128, modulus: u128) -> u128 { +pub(crate) const fn safe_mul_const(a: u128, b: u128, modulus: u128) -> u128 { match a.overflowing_mul(b) { (val, false) => val % modulus, (_, true) => { @@ -95,7 +95,7 @@ pub(crate) fn generate_montgomery_bigint_casts( _k_bits: u32, r_mod_n: u128, ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let r2 = (r_mod_n * r_mod_n) % modulus; + let r2 = safe_mul_const(r_mod_n, r_mod_n, modulus); ( quote! { //* Convert from standard representation to Montgomery space @@ -118,7 +118,10 @@ pub(crate) fn generate_montgomery_bigint_casts( let mut tmp = a; let one = SmallFp::new(1 as Self::T); ::mul_assign(&mut tmp, &one); - ark_ff::BigInt([tmp.value as u64, 0]) + let val = tmp.value as u128; + let lo = val as u64; + let hi = (val >> 64) as u64; + ark_ff::BigInt([lo, hi]) } }, ) From d1937cce732b229a5973f3540188263621b0b2db Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 3 Oct 2025 13:58:23 +0200 Subject: [PATCH 08/47] extend testing suite (some tests failing) --- .../{fpconfig_working.rs => smallfp_tests.rs} | 156 ++++++-- ff/Cargo.toml | 5 +- .../models/small_fp/small_fp_backend.rs | 12 + test-templates/src/fields.rs | 339 ++++++++++++++++++ 4 files changed, 490 insertions(+), 22 deletions(-) rename examples/{fpconfig_working.rs => smallfp_tests.rs} (83%) diff --git a/examples/fpconfig_working.rs b/examples/smallfp_tests.rs similarity index 83% rename from examples/fpconfig_working.rs rename to examples/smallfp_tests.rs index 687bea822..a46b75d68 100644 --- a/examples/fpconfig_working.rs +++ b/examples/smallfp_tests.rs @@ -1,36 +1,87 @@ -// Simple example demonstrating field arithmetic with ark-ff using MontConfig -// This is equivalent to fpconfig.rs but uses the stable MontConfig derive - +use ark_algebra_test_templates::*; use ark_ff::ark_ff_macros::SmallFpConfig; -use ark_ff::{BigInt, Fp64, MontBackend, MontConfig, SmallFp, SmallFpConfig, SqrtPrecomputation}; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; -// M31 prime field: 2^31 - 1 = 2147483647 -#[derive(MontConfig)] -#[modulus = "2147483647"] -#[generator = "3"] -pub struct M31Config; +#[derive(SmallFpConfig)] +#[modulus = "251"] +#[generator = "6"] +#[backend = "standard"] +pub struct SmallF8Config; +pub type SmallF8 = SmallFp; -pub type M31Field = Fp64>; +#[derive(SmallFpConfig)] +#[modulus = "251"] +#[generator = "6"] +#[backend = "montgomery"] +pub struct SmallF8ConfigMont; +pub type SmallF8Mont = SmallFp; -// BabyBear prime field: 2^31 - 2^27 + 1 = 2013265921 -#[derive(MontConfig)] -#[modulus = "2013265921"] -#[generator = "31"] -pub struct BabyBearConfig; +#[derive(SmallFpConfig)] +#[modulus = "65521"] +#[generator = "2"] +#[backend = "standard"] +pub struct SmallF16Config; +pub type SmallF16 = SmallFp; -pub type BabyBearField = Fp64>; +#[derive(SmallFpConfig)] +#[modulus = "65521"] +#[generator = "2"] +#[backend = "montgomery"] +pub struct SmallF16ConfigMont; +pub type SmallF16Mont = SmallFp; #[derive(SmallFpConfig)] #[modulus = "2147483647"] // m31 #[generator = "7"] #[backend = "standard"] pub struct SmallField; +pub type SmallF32 = SmallFp; #[derive(SmallFpConfig)] -#[modulus = "2013265921"] // BabyBear -#[generator = "31"] +#[modulus = "2147483647"] // m31 +#[generator = "7"] #[backend = "montgomery"] pub struct SmallFieldMont; +pub type SmallF32Mont = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 +#[generator = "2"] +#[backend = "standard"] +pub struct SmallF64Config; +pub type SmallF64 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 +#[generator = "2"] +#[backend = "montgomery"] +pub struct SmallF64ConfigMont; +pub type SmallF64Mont = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "2"] +#[backend = "standard"] +pub struct SmallF128Config; +pub type SmallF128 = SmallFp; + +// #[derive(SmallFpConfig)] +// #[modulus = "143244528689204659050391023439224324689"] +// #[generator = "2"] +// #[backend = "montgomery"] +// pub struct SmallF128ConfigMont; +// pub type SmallF128Mont = SmallFp; + +test_small_field!(f8; SmallF8; small_prime_field); +test_small_field!(f8_mont; SmallF8Mont; small_prime_field); +test_small_field!(f16; SmallF16; small_prime_field); +test_small_field!(f16_mont; SmallF16Mont; small_prime_field); +test_small_field!(f32; SmallF32; small_prime_field); +test_small_field!(f32_mont; SmallF32Mont; small_prime_field); +test_small_field!(f64; SmallF64; small_prime_field); +test_small_field!(f64_mont; SmallF64Mont; small_prime_field); +test_small_field!(f128; SmallF128; small_prime_field); +// test_small_field!(f128_mont; SmallF128Mont; small_prime_field); fn main() { let mut a = SmallFieldMont::new(20); @@ -341,6 +392,35 @@ mod tests { assert_eq!(result.value, 33 % SmallField::MODULUS); } + #[test] + fn test_sqrt_standard_backend() { + use ark_ff::Field; + + for i in [4, 16, 144, 169, 256, 400] { + let a = SmallField::new(i); + let sqrt = a.sqrt(); + assert!(sqrt.is_some()); + let sqrt_val = sqrt.unwrap(); + let mut sqrt_squared = sqrt_val; + SmallField::square_in_place(&mut sqrt_squared); + assert_eq!(sqrt_squared.value, i); + } + + let three = SmallField::new(3); + let sqrt_three = three.sqrt(); + assert!(sqrt_three.is_none()); + + let one = SmallField::ONE; + let sqrt_one = one.sqrt(); + assert!(sqrt_one.is_some()); + assert_eq!(sqrt_one.unwrap(), SmallField::ONE); + + let zero = SmallField::ZERO; + let sqrt_zero = zero.sqrt(); + assert!(sqrt_zero.is_some()); + assert_eq!(sqrt_zero.unwrap(), SmallField::ZERO); + } + // ---------- Montgomery backend tests ---------- #[test] fn add_assign_test_montgomery() { @@ -574,7 +654,7 @@ mod tests { #[test] fn test_specific_inverse_montgomery() { - let mut val = SmallFieldMont::new(17); + let val = SmallFieldMont::new(17); let val_inv = SmallFieldMont::inverse(&val); let mut val_copy = val; SmallFieldMont::mul_assign(&mut val_copy, &val_inv.unwrap()); @@ -609,7 +689,7 @@ mod tests { } // inverse property: inv(inv(x)) = x - let mut test_val = SmallFieldMont::new(42 % SmallFieldMont::MODULUS); + let test_val = SmallFieldMont::new(42 % SmallFieldMont::MODULUS); { let mut tv_copy = test_val; SmallFieldMont::exit(&mut tv_copy); @@ -728,4 +808,40 @@ mod tests { SmallFieldMont::exit(&mut result); assert_eq!(result.value, 33 % SmallFieldMont::MODULUS); } + + #[test] + fn test_sqrt_montgomery_backend() { + use ark_ff::Field; + + for i in [4, 16, 144, 169, 256, 400] { + let a = SmallFieldMont::new(i); + let sqrt = a.sqrt(); + assert!(sqrt.is_some()); + let sqrt_val = sqrt.unwrap(); + let mut sqrt_squared = sqrt_val; + SmallFieldMont::square_in_place(&mut sqrt_squared); + let mut a_copy = a; + SmallFieldMont::exit(&mut sqrt_squared); + SmallFieldMont::exit(&mut a_copy); + assert_eq!(sqrt_squared.value, a_copy.value); + } + + let three = SmallFieldMont::new(31); + let sqrt_three = three.sqrt(); + assert!(sqrt_three.is_none()); + + let one = SmallFieldMont::ONE; + let sqrt_one = one.sqrt(); + assert!(sqrt_one.is_some()); + let mut sqrt_one_val = sqrt_one.unwrap(); + SmallFieldMont::exit(&mut sqrt_one_val); + assert_eq!(sqrt_one_val.value, 1); + + let zero = SmallFieldMont::ZERO; + let sqrt_zero = zero.sqrt(); + assert!(sqrt_zero.is_some()); + let mut sqrt_zero_val = sqrt_zero.unwrap(); + SmallFieldMont::exit(&mut sqrt_zero_val); + assert_eq!(sqrt_zero_val.value, 0); + } } diff --git a/ff/Cargo.toml b/ff/Cargo.toml index 51ca755d3..51df53b7e 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -35,6 +35,7 @@ ark-test-curves = { workspace = true, features = [ "mnt6_753", "secp256k1", ] } +ark-algebra-test-templates = { path = "../test-templates" } p3_field = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-field" } p3_goldilocks = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-goldilocks" } blake2.workspace = true @@ -52,8 +53,8 @@ name = "fpconfig" path = "../examples/fpconfig.rs" [[example]] -name = "fpconfig_working" -path = "../examples/fpconfig_working.rs" +name = "smallfp_tests" +path = "../examples/smallfp_tests.rs" [[bench]] name = "cast_bench" diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 0a6bd5804..0cbadca38 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -266,6 +266,18 @@ impl ark_std::rand::distributions::Distribution> fn sample(&self, rng: &mut R) -> SmallFp

{ // loop avoids sampling bias match P::MODULUS_128 { + modulus if modulus <= u8::MAX as u128 => loop { + let random_val: u8 = rng.sample(ark_std::rand::distributions::Standard); + if (random_val as u128) < P::MODULUS_128 { + return SmallFp::from(random_val); + } + }, + modulus if modulus <= u16::MAX as u128 => loop { + let random_val: u16 = rng.sample(ark_std::rand::distributions::Standard); + if (random_val as u128) < P::MODULUS_128 { + return SmallFp::from(random_val); + } + }, modulus if modulus <= u32::MAX as u128 => loop { let random_val: u32 = rng.sample(ark_std::rand::distributions::Standard); if (random_val as u128) < P::MODULUS_128 { diff --git a/test-templates/src/fields.rs b/test-templates/src/fields.rs index db0d9b61c..f40170aee 100644 --- a/test-templates/src/fields.rs +++ b/test-templates/src/fields.rs @@ -586,3 +586,342 @@ macro_rules! test_field { } }; } + +pub fn small_field_sum_of_products_test_helper(rng: &mut impl Rng) +where + F: ark_ff::Field + ark_std::UniformRand, +{ + let a = [(); N].map(|_| F::rand(rng)); + let b = [(); N].map(|_| F::rand(rng)); + let result_1 = F::sum_of_products(&a, &b); + let result_2 = a.into_iter().zip(b).map(|(a, b)| a * b).sum::(); + assert_eq!(result_1, result_2, "length: {N}"); +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __test_small_field { + ($field: ty) => { + #[test] + pub fn test_frobenius() { + use ark_ff::Field; + + // Test with specific values rather than random ones to avoid from_bigint issues + let test_values = [0, 1, 2, 42, 100]; + + for &val in &test_values { + let a = <$field>::from(val as u64); + let characteristic = <$field>::characteristic(); + let max_power = (<$field>::extension_degree() + 1) as usize; + + let mut a_0 = a; + a_0.frobenius_map_in_place(0); + assert_eq!(a, a_0); + assert_eq!(a, a.frobenius_map(0)); + + let mut a_q = a.pow(&characteristic); + for power in 1..max_power { + assert_eq!(a_q, a.frobenius_map(power)); + + let mut a_qi = a; + a_qi.frobenius_map_in_place(power); + assert_eq!(a_q, a_qi); + + a_q = a_q.pow(&characteristic); + } + } + } + + #[test] + fn test_add_properties() { + use ark_ff::AdditiveGroup; + use ark_std::UniformRand; + + let mut rng = test_rng(); + let zero = <$field>::zero(); + assert_eq!(-zero, zero); + assert!(zero.is_zero()); + assert!(<$field>::ZERO.is_zero()); + assert_eq!(<$field>::ZERO, zero); + + for _ in 0..(ITERATIONS * ITERATIONS) { + // Generate small values that are guaranteed to be within the modulus + // Using u16 range ensures compatibility with small fields like M31 + let a = <$field>::rand(&mut rng); + let b = <$field>::rand(&mut rng); + let c = <$field>::rand(&mut rng); + assert_eq!((a + b) + c, a + (b + c)); + + // Commutativity + assert_eq!(a + b, b + a); + + // Identity + assert_eq!(zero + a, a); + assert_eq!(zero + b, b); + assert_eq!(zero + c, c); + + // Negation + assert_eq!(-a + a, zero); + assert_eq!(-b + b, zero); + assert_eq!(-c + c, zero); + assert_eq!(-zero, zero); + + // Associativity and commutativity simultaneously + let t0 = (a + &b) + &c; // (a + b) + c + let t1 = (a + &c) + &b; // (a + c) + b + let t2 = (b + &c) + &a; // (b + c) + a + + assert_eq!(t0, t1); + assert_eq!(t1, t2); + + // Doubling + assert_eq!(a.double(), a + a); + assert_eq!(b.double(), b + b); + assert_eq!(c.double(), c + c); + } + } + + #[test] + fn test_sub_properties() { + use ark_std::UniformRand; + let mut rng = test_rng(); + let zero = <$field>::zero(); + + for _ in 0..(ITERATIONS * ITERATIONS) { + // Anti-commutativity + let a = <$field>::rand(&mut rng); + let b = <$field>::rand(&mut rng); + assert!(((a - b) + (b - a)).is_zero()); + + // Identity + assert_eq!(zero - a, -a); + assert_eq!(zero - b, -b); + + assert_eq!(a - zero, a); + assert_eq!(b - zero, b); + } + } + + #[test] + fn test_mul_properties() { + use ark_std::UniformRand; + let mut rng = test_rng(); + let zero = <$field>::zero(); + let one = <$field>::one(); + let minus_one = <$field>::NEG_ONE; + assert_eq!(one.inverse().unwrap(), one, "One inverse failed"); + assert!(one.is_one(), "One is not one"); + + assert!(<$field>::ONE.is_one(), "One constant is not one"); + assert_eq!(<$field>::ONE, one, "One constant is incorrect"); + assert_eq!(<$field>::NEG_ONE, -one, "NEG_ONE constant is incorrect"); + assert_eq!(<$field>::ONE + <$field>::NEG_ONE, zero, "1 + -1 neq 0"); + + for _ in 0..ITERATIONS { + // Associativity + let a = <$field>::rand(&mut rng); + let b = <$field>::rand(&mut rng); + let c = <$field>::rand(&mut rng); + assert_eq!((a * b) * c, a * (b * c), "Associativity failed"); + + // Commutativity + assert_eq!(a * b, b * a, "Commutativity failed"); + + // Identity + assert_eq!(one * a, a, "Identity mul failed"); + assert_eq!(one * b, b, "Identity mul failed"); + assert_eq!(one * c, c, "Identity mul failed"); + assert_eq!(minus_one * c, -c, "NEG_ONE mul failed"); + + assert_eq!(zero * a, zero, "Mul by zero failed"); + assert_eq!(zero * b, zero, "Mul by zero failed"); + assert_eq!(zero * c, zero, "Mul by zero failed"); + + // Inverses + assert_eq!(a * a.inverse().unwrap(), one, "Mul by inverse failed"); + assert_eq!(b * b.inverse().unwrap(), one, "Mul by inverse failed"); + assert_eq!(c * c.inverse().unwrap(), one, "Mul by inverse failed"); + + // Associativity and commutativity simultaneously + let t0 = (a * b) * c; + let t1 = (a * c) * b; + let t2 = (b * c) * a; + assert_eq!(t0, t1, "Associativity + commutativity failed"); + assert_eq!(t1, t2, "Associativity + commutativity failed"); + + // Squaring + assert_eq!(a * a, a.square(), "Squaring failed"); + assert_eq!(b * b, b.square(), "Squaring failed"); + assert_eq!(c * c, c.square(), "Squaring failed"); + + // Distributivity + assert_eq!(a * (b + c), a * b + a * c, "Distributivity failed"); + assert_eq!(b * (a + c), b * a + b * c, "Distributivity failed"); + assert_eq!(c * (a + b), c * a + c * b, "Distributivity failed"); + assert_eq!( + (a + b).square(), + a.square() + b.square() + a * ark_ff::AdditiveGroup::double(&b), + "Distributivity for square failed" + ); + assert_eq!( + (b + c).square(), + c.square() + b.square() + c * ark_ff::AdditiveGroup::double(&b), + "Distributivity for square failed" + ); + assert_eq!( + (c + a).square(), + a.square() + c.square() + a * ark_ff::AdditiveGroup::double(&c), + "Distributivity for square failed" + ); + } + } + + #[test] + fn test_pow() { + use ark_std::UniformRand; + let mut rng = test_rng(); + for _ in 0..(ITERATIONS / 10) { + for i in 0..20 { + // Exponentiate by various small numbers and ensure it is + // consistent with repeated multiplication. + let a = <$field>::rand(&mut rng); + let target = a.pow(&[i]); + let mut c = <$field>::one(); + for _ in 0..i { + c *= a; + } + assert_eq!(c, target); + } + let a = <$field>::rand(&mut rng); + + // Exponentiating by the modulus should have no effect; + let mut result = a; + for i in 0..<$field>::extension_degree() { + result = result.pow(<$field>::characteristic()) + } + assert_eq!(a, result); + + // Commutativity + let e1: [u64; 10] = rng.gen(); + let e2: [u64; 10] = rng.gen(); + assert_eq!(a.pow(&e1).pow(&e2), a.pow(&e2).pow(&e1)); + + // Distributivity + let e3: [u64; 10] = rng.gen(); + let a_to_e1 = a.pow(e1); + let a_to_e2 = a.pow(e2); + let a_to_e1_plus_e2 = a.pow(e1) * a.pow(e2); + assert_eq!( + a_to_e1_plus_e2.pow(&e3), + a_to_e1.pow(&e3) * a_to_e2.pow(&e3) + ); + } + } + + #[test] + fn test_sum_of_products_tests() { + use ark_std::{rand::Rng, UniformRand}; + let rng = &mut test_rng(); + + for _ in 0..ITERATIONS { + $crate::fields::sum_of_products_test_helper::<$field, 1>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 2>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 3>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 4>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 5>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 6>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 7>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 8>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 9>(rng); + $crate::fields::sum_of_products_test_helper::<$field, 10>(rng); + } + } + + // #[test] + // fn test_sqrt() { + // if <$field>::SQRT_PRECOMP.is_some() { + // use ark_std::UniformRand; + // let rng = &mut test_rng(); + + // assert!(<$field>::zero().sqrt().unwrap().is_zero()); + + // for _ in 0..ITERATIONS { + // // Ensure sqrt(a^2) = a or -a + // let a = <$field>::rand(rng); + // let b = a.square(); + // let sqrt = b.sqrt().unwrap(); + // assert!(a == sqrt || -a == sqrt); + + // if let Some(mut b) = a.sqrt() { + // b.square_in_place(); + // assert_eq!(a, b); + // } + + // let a = <$field>::rand(rng); + // let b = a.square(); + // assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + // } + // } + // } + + #[test] + fn test_mul_by_base_field_elem() { + use ark_std::UniformRand; + let rng = &mut test_rng(); + + for _ in 0..ITERATIONS { + let a = vec![ + <<$field as Field>::BasePrimeField>::rand(rng); + <$field>::extension_degree() as usize + ]; + let b = <<$field as Field>::BasePrimeField>::rand(rng); + + let mut a = <$field>::from_base_prime_field_elems(a).unwrap(); + let computed = a.mul_by_base_prime_field(&b); + + let embedded_b = <$field as Field>::from_base_prime_field(b); + + let naive = a * embedded_b; + + assert_eq!(computed, naive); + } + } + }; + + ($field: ty; small_prime_field) => { + $crate::__test_small_field!($field); + }; +} + +#[macro_export] +macro_rules! test_small_field { + ($mod_name:ident; $field:ty $(; $tail:tt)*) => { + mod $mod_name { + use super::*; + use ark_ff::{ + fields::{Field, LegendreSymbol}, + SmallFp, SmallFpConfig, + }; + use ark_serialize::{CanonicalSerialize, CanonicalDeserialize}; + use ark_std::{rand::Rng, rand::RngCore, test_rng, vec::Vec, Zero, One, UniformRand}; + const ITERATIONS: usize = 1000; + + $crate::__test_small_field!($field $(; $tail)*); + } + }; + + ($iters:expr; $mod_name:ident; $field:ty $(; $tail:tt)*) => { + mod $mod_name { + use super::*; + use ark_ff::{ + fields::{Field, LegendreSymbol}, + SmallFp, SmallFpConfig, + }; + use ark_serialize::{CanonicalSerialize, CanonicalDeserialize}; + use ark_std::{rand::Rng, rand::RngCore, test_rng, vec::Vec, Zero, One, UniformRand}; + const ITERATIONS: usize = $iters; + + $crate::__test_small_field!($field $(; $tail)*); + } + }; +} From a1a3343582d53f6866bbd3fc540c53311fd5d51b Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 3 Oct 2025 16:39:41 +0200 Subject: [PATCH 09/47] rewrite sampling function --- .../models/small_fp/small_fp_backend.rs | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 0cbadca38..c7b214c3f 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -263,39 +263,26 @@ impl ark_std::rand::distributions::Distribution> for ark_std::rand::distributions::Standard { #[inline] + // samples non-zero element, loop avoids modulo bias fn sample(&self, rng: &mut R) -> SmallFp

{ - // loop avoids sampling bias - match P::MODULUS_128 { - modulus if modulus <= u8::MAX as u128 => loop { - let random_val: u8 = rng.sample(ark_std::rand::distributions::Standard); - if (random_val as u128) < P::MODULUS_128 { - return SmallFp::from(random_val); - } - }, - modulus if modulus <= u16::MAX as u128 => loop { - let random_val: u16 = rng.sample(ark_std::rand::distributions::Standard); - if (random_val as u128) < P::MODULUS_128 { - return SmallFp::from(random_val); - } - }, - modulus if modulus <= u32::MAX as u128 => loop { - let random_val: u32 = rng.sample(ark_std::rand::distributions::Standard); - if (random_val as u128) < P::MODULUS_128 { - return SmallFp::from(random_val); - } - }, - modulus if modulus <= u64::MAX as u128 => loop { - let random_val: u64 = rng.sample(ark_std::rand::distributions::Standard); - if (random_val as u128) < P::MODULUS_128 { - return SmallFp::from(random_val); + macro_rules! sample_loop { + ($ty:ty) => { + loop { + let random_val: $ty = rng.sample(ark_std::rand::distributions::Standard); + let val_u128 = random_val as u128; + if val_u128 > 0 && val_u128 < P::MODULUS_128 { + return SmallFp::from(random_val); + } } - }, - _ => loop { - let random_val: u128 = rng.sample(ark_std::rand::distributions::Standard); - if random_val < P::MODULUS_128 { - return SmallFp::from(random_val); - } - }, + }; + } + + match P::MODULUS_128 { + modulus if modulus <= u8::MAX as u128 => sample_loop!(u8), + modulus if modulus <= u16::MAX as u128 => sample_loop!(u16), + modulus if modulus <= u32::MAX as u128 => sample_loop!(u32), + modulus if modulus <= u64::MAX as u128 => sample_loop!(u64), + _ => sample_loop!(u128), } } } From 9cc57f524ba6319919b78e9f93ac66d8fb0bc8ba Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 3 Oct 2025 16:40:11 +0200 Subject: [PATCH 10/47] fix overflowing bug in multiplication --- ff-macros/src/small_fp/montgomery_backend.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 2aa5c21d8..205228ad8 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -72,10 +72,13 @@ pub(crate) fn backend_impl( let t = a_u128.wrapping_mul(b_u128); let m = t.wrapping_mul(#n_prime) & #r_mask; - let mn = (m as u128).wrapping_mul(#modulus); + let mn = m.wrapping_mul(#modulus); - let t_plus_mn = t.wrapping_add(mn); + let (t_plus_mn, overflow) = t.overflowing_add(mn); let mut u = t_plus_mn >> #k_bits; + if overflow { + u += 1u128 << (128 - #k_bits); + } if u >= #modulus { u -= #modulus; From be2664d3ab81602ade1cf4193a39a60faed17d7f Mon Sep 17 00:00:00 2001 From: benbencik Date: Sat, 4 Oct 2025 00:36:43 +0200 Subject: [PATCH 11/47] fix the computation for bit size --- .../models/small_fp/small_fp_backend.rs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index c7b214c3f..1a3c1dc5c 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -141,7 +141,7 @@ impl SmallFp

{ } pub fn num_bits_to_shave() -> usize { - 64 * P::NUM_BIG_INT_LIMBS - (Self::MODULUS_BIT_SIZE as usize) + primitive_type_bit_size(P::MODULUS_128) - (Self::MODULUS_BIT_SIZE as usize) } } @@ -205,12 +205,30 @@ const fn const_to_bigint(value: u128) -> BigInt<2> { BigInt::<2>::new([low, high]) } +const fn const_num_bits_u128(value: u128) -> u32 { + if value == 0 { + 0 + } else { + 128 - value.leading_zeros() + } +} + +const fn primitive_type_bit_size(modulus_128: u128) -> usize { + match modulus_128 { + x if x <= u8::MAX as u128 => 8, + x if x <= u16::MAX as u128 => 16, + x if x <= u32::MAX as u128 => 32, + x if x <= u64::MAX as u128 => 64, + _ => 128, + } +} + impl PrimeField for SmallFp

{ type BigInt = BigInt<2>; const MODULUS: Self::BigInt = const_to_bigint(P::MODULUS_128); const MODULUS_MINUS_ONE_DIV_TWO: Self::BigInt = Self::MODULUS.divide_by_2_round_down(); - const MODULUS_BIT_SIZE: u32 = Self::MODULUS.const_num_bits(); + const MODULUS_BIT_SIZE: u32 = const_num_bits_u128(P::MODULUS_128); const TRACE: Self::BigInt = Self::MODULUS.two_adic_coefficient(); const TRACE_MINUS_ONE_DIV_TWO: Self::BigInt = Self::TRACE.divide_by_2_round_down(); From e39da817f681804623695d0ec915efc950948cf6 Mon Sep 17 00:00:00 2001 From: benbencik Date: Sat, 4 Oct 2025 11:29:54 +0200 Subject: [PATCH 12/47] consider (de)serialization of small elements --- ff/src/fields/models/small_fp/serialize.rs | 62 +++++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/ff/src/fields/models/small_fp/serialize.rs b/ff/src/fields/models/small_fp/serialize.rs index dd18b7460..606f666ff 100644 --- a/ff/src/fields/models/small_fp/serialize.rs +++ b/ff/src/fields/models/small_fp/serialize.rs @@ -1,9 +1,10 @@ use crate::fields::models::small_fp::small_fp_backend::{SmallFp, SmallFpConfig}; -use crate::{PrimeField, Zero}; +use crate::{BigInt, PrimeField, Zero}; use ark_serialize::{ buffer_byte_size, CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize, CanonicalSerializeWithFlags, Compress, EmptyFlags, Flags, SerializationError, Valid, Validate, }; +use ark_std::vec; impl CanonicalSerializeWithFlags for SmallFp

{ fn serialize_with_flags( @@ -21,16 +22,29 @@ impl CanonicalSerializeWithFlags for SmallFp

{ // serialized with `flags`. If `F::BIT_SIZE < 8`, // this is at most `N * 8 + 1` let output_byte_size = buffer_byte_size(Self::MODULUS_BIT_SIZE as usize + F::BIT_SIZE); + let mut w = writer; // Write out `self` to a temporary buffer. // The size of the buffer is $byte_size + 1 because `F::BIT_SIZE` // is at most 8 bits. - let mut bytes = crate::const_helpers::SerBuffer::zeroed(); - bytes.copy_from_u64_slice(&self.into_bigint().0); - // Mask out the bits of the last byte that correspond to the flag. - bytes[output_byte_size - 1] |= flags.u8_bitmask(); - bytes.write_up_to(writer, output_byte_size)?; + if output_byte_size <= 8 { + // Fields with type smaller than u64 + // Writes exactly the minimal required number of bytes + let bigint = self.into_bigint(); + let value = bigint.0[0]; + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&value.to_le_bytes()); + bytes[output_byte_size - 1] |= flags.u8_bitmask(); + w.write_all(&bytes[..output_byte_size])?; + } else { + // For larger fields, use the approach from `FpConfig` + let mut bytes = crate::const_helpers::SerBuffer::zeroed(); + bytes.copy_from_u64_slice(&self.into_bigint().0); + // Mask out the bits of the last byte that correspond to the flag. + bytes[output_byte_size - 1] |= flags.u8_bitmask(); + bytes.write_up_to(&mut w, output_byte_size)?; + } Ok(()) } @@ -71,16 +85,36 @@ impl CanonicalDeserializeWithFlags for SmallFp

{ // Calculate the number of bytes required to represent a field element // serialized with `flags`. let output_byte_size = Self::zero().serialized_size_with_flags::(); + let mut r = reader; + + if output_byte_size <= 8 { + // Fields with type smaller than u64 + let mut bytes = vec![0u8; output_byte_size]; + r.read_exact(&mut bytes)?; + let flags = F::from_u8_remove_flags(&mut bytes[output_byte_size - 1]) + .ok_or(SerializationError::UnexpectedFlags)?; - let mut masked_bytes = crate::const_helpers::SerBuffer::zeroed(); - masked_bytes.read_exact_up_to(reader, output_byte_size)?; - let flags = F::from_u8_remove_flags(&mut masked_bytes[output_byte_size - 1]) - .ok_or(SerializationError::UnexpectedFlags)?; + let mut limb_bytes = [0u8; 8]; + limb_bytes[..output_byte_size.min(8)] + .copy_from_slice(&bytes[..output_byte_size.min(8)]); + let limb = u64::from_le_bytes(limb_bytes); + let bigint = BigInt::<2>::new([limb, 0]); - let self_integer = masked_bytes.to_bigint(); - Self::from_bigint(self_integer) - .map(|v| (v, flags)) - .ok_or(SerializationError::InvalidData) + Self::from_bigint(bigint) + .map(|v| (v, flags)) + .ok_or(SerializationError::InvalidData) + } else { + // For larger fields, use the approach from `FpConfig` + let mut masked_bytes = crate::const_helpers::SerBuffer::zeroed(); + masked_bytes.read_exact_up_to(&mut r, output_byte_size)?; + let flags = F::from_u8_remove_flags(&mut masked_bytes[output_byte_size - 1]) + .ok_or(SerializationError::UnexpectedFlags)?; + + let self_integer = masked_bytes.to_bigint(); + Self::from_bigint(self_integer) + .map(|v| (v, flags)) + .ok_or(SerializationError::InvalidData) + } } } From 3c7daf90142200f30bf04efecc3f9f162db58f77 Mon Sep 17 00:00:00 2001 From: benbencik Date: Sat, 4 Oct 2025 12:03:19 +0200 Subject: [PATCH 13/47] rewrite computation for two adic root of unity --- ff-macros/src/small_fp/montgomery_backend.rs | 6 +-- ff-macros/src/small_fp/standard_backend.rs | 2 +- ff-macros/src/small_fp/utils.rs | 44 ++++++++++++++------ 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 205228ad8..625dfb3a7 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -1,7 +1,7 @@ use super::*; use crate::small_fp::utils::{ compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, - generate_sqrt_precomputation, safe_mul_const, + generate_sqrt_precomputation, mod_mul_const, }; pub(crate) fn backend_impl( @@ -19,7 +19,7 @@ pub(crate) fn backend_impl( let generator_mont = (generator % modulus) * (r_mod_n % modulus) % modulus; let two_adicity = compute_two_adicity(modulus); - let two_adic_root = compute_two_adic_root_of_unity(modulus, generator, two_adicity); + let two_adic_root = compute_two_adic_root_of_unity(modulus, two_adicity); let two_adic_root_mont = (two_adic_root * r_mod_n) % modulus; let (from_bigint_impl, into_bigint_impl) = @@ -144,7 +144,7 @@ pub(crate) fn new(modulus: u128, _ty: proc_macro2::TokenStream) -> proc_macro2:: let k_bits = 128 - modulus.leading_zeros(); let r: u128 = 1u128 << k_bits; let r_mod_n = r % modulus; - let r2 = safe_mul_const(r_mod_n, r_mod_n, modulus); + let r2 = mod_mul_const(r_mod_n, r_mod_n, modulus); quote! { pub fn new(value: ::T) -> SmallFp { diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs index a7556a83b..00100ef0d 100644 --- a/ff-macros/src/small_fp/standard_backend.rs +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -10,7 +10,7 @@ pub(crate) fn backend_impl( generator: u128, ) -> proc_macro2::TokenStream { let two_adicity = compute_two_adicity(modulus); - let two_adic_root_of_unity = compute_two_adic_root_of_unity(modulus, generator, two_adicity); + let two_adic_root_of_unity = compute_two_adic_root_of_unity(modulus, two_adicity); let (from_bigint_impl, into_bigint_impl) = generate_bigint_casts(modulus); let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity); diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index 735a315f1..463cd81a8 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -23,7 +23,7 @@ const fn mod_add(x: u128, y: u128, modulus: u128) -> u128 { } } -pub(crate) const fn safe_mul_const(a: u128, b: u128, modulus: u128) -> u128 { +pub(crate) const fn mod_mul_const(a: u128, b: u128, modulus: u128) -> u128 { match a.overflowing_mul(b) { (val, false) => val % modulus, (_, true) => { @@ -43,27 +43,47 @@ pub(crate) const fn safe_mul_const(a: u128, b: u128, modulus: u128) -> u128 { } } -// Two adicity root of unity `w` is defined as `w = g^((N-1)/2^s)` where `s` is two adidcity -// Therefore `w^(2^s) = 1 mod N` -pub(crate) const fn compute_two_adic_root_of_unity( - modulus: u128, - generator: u128, - two_adicity: u32, -) -> u128 { +pub(crate) const fn compute_two_adic_root_of_unity(modulus: u128, two_adicity: u32) -> u128 { + let qnr = find_quadratic_non_residue(modulus); let mut exp = (modulus - 1) >> two_adicity; - let mut base = generator % modulus; + let mut base = qnr % modulus; let mut result = 1u128; while exp > 0 { if exp & 1 == 1 { - result = safe_mul_const(result, base, modulus); + result = mod_mul_const(result, base, modulus); } - base = safe_mul_const(base, base, modulus); + base = mod_mul_const(base, base, modulus); exp /= 2; } result } +const fn pow_mod(mut base: u128, mut exp: u128, modulus: u128) -> u128 { + let mut result = 1; + base %= modulus; + while exp > 0 { + if exp % 2 == 1 { + result = mod_mul_const(result, base, modulus); + } + base = mod_mul_const(base, base, modulus); + exp /= 2; + } + result +} + +pub(crate) const fn find_quadratic_non_residue(modulus: u128) -> u128 { + let exponent = (modulus - 1) / 2; + let mut z = 2; + loop { + let legendre = pow_mod(z, exponent, modulus); + if legendre == modulus - 1 { + return z; + } + z += 1; + } +} + pub(crate) fn generate_bigint_casts( modulus: u128, ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { @@ -95,7 +115,7 @@ pub(crate) fn generate_montgomery_bigint_casts( _k_bits: u32, r_mod_n: u128, ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let r2 = safe_mul_const(r_mod_n, r_mod_n, modulus); + let r2 = mod_mul_const(r_mod_n, r_mod_n, modulus); ( quote! { //* Convert from standard representation to Montgomery space From 8a907d897fcc765249acd0e21f57548bef783772 Mon Sep 17 00:00:00 2001 From: benbencik Date: Sat, 4 Oct 2025 12:26:21 +0200 Subject: [PATCH 14/47] use safe mul to avoid overflows in compile-time --- ff-macros/src/small_fp/montgomery_backend.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 625dfb3a7..97c64384e 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -16,11 +16,13 @@ pub(crate) fn backend_impl( let n_prime = mod_inverse_pow2(modulus, k_bits); let one_mont = r_mod_n; - let generator_mont = (generator % modulus) * (r_mod_n % modulus) % modulus; + let generator_mont = mod_mul_const(generator % modulus, r_mod_n % modulus, modulus); let two_adicity = compute_two_adicity(modulus); let two_adic_root = compute_two_adic_root_of_unity(modulus, two_adicity); - let two_adic_root_mont = (two_adic_root * r_mod_n) % modulus; + let two_adic_root_mont = mod_mul_const(two_adic_root, r_mod_n, modulus); + + let neg_one_mont = mod_mul_const(modulus - 1, r_mod_n, modulus); let (from_bigint_impl, into_bigint_impl) = generate_montgomery_bigint_casts(modulus, k_bits, r_mod_n); @@ -33,7 +35,7 @@ pub(crate) fn backend_impl( const GENERATOR: SmallFp = SmallFp::new(#generator_mont as Self::T); const ZERO: SmallFp = SmallFp::new(0 as Self::T); const ONE: SmallFp = SmallFp::new(#one_mont as Self::T); - const NEG_ONE: SmallFp = SmallFp::new(((Self::MODULUS - 1) as u128 * #r_mod_n % #modulus) as Self::T); + const NEG_ONE: SmallFp = SmallFp::new(#neg_one_mont as Self::T); const TWO_ADICITY: u32 = #two_adicity; From ea4cce28c44d3927d80624247f74c67dced3861b Mon Sep 17 00:00:00 2001 From: benbencik Date: Sat, 4 Oct 2025 16:53:46 +0200 Subject: [PATCH 15/47] update the smallfp tests --- examples/smallfp_tests.rs | 847 ----------------------------------- ff/Cargo.toml | 8 - ff/tests/smallfp_tests.rs | 85 ++++ test-templates/src/fields.rs | 246 +++++++--- 4 files changed, 273 insertions(+), 913 deletions(-) delete mode 100644 examples/smallfp_tests.rs create mode 100644 ff/tests/smallfp_tests.rs diff --git a/examples/smallfp_tests.rs b/examples/smallfp_tests.rs deleted file mode 100644 index a46b75d68..000000000 --- a/examples/smallfp_tests.rs +++ /dev/null @@ -1,847 +0,0 @@ -use ark_algebra_test_templates::*; -use ark_ff::ark_ff_macros::SmallFpConfig; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; - -#[derive(SmallFpConfig)] -#[modulus = "251"] -#[generator = "6"] -#[backend = "standard"] -pub struct SmallF8Config; -pub type SmallF8 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "251"] -#[generator = "6"] -#[backend = "montgomery"] -pub struct SmallF8ConfigMont; -pub type SmallF8Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "65521"] -#[generator = "2"] -#[backend = "standard"] -pub struct SmallF16Config; -pub type SmallF16 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "65521"] -#[generator = "2"] -#[backend = "montgomery"] -pub struct SmallF16ConfigMont; -pub type SmallF16Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "2147483647"] // m31 -#[generator = "7"] -#[backend = "standard"] -pub struct SmallField; -pub type SmallF32 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "2147483647"] // m31 -#[generator = "7"] -#[backend = "montgomery"] -pub struct SmallFieldMont; -pub type SmallF32Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 -#[generator = "2"] -#[backend = "standard"] -pub struct SmallF64Config; -pub type SmallF64 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 -#[generator = "2"] -#[backend = "montgomery"] -pub struct SmallF64ConfigMont; -pub type SmallF64Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "2"] -#[backend = "standard"] -pub struct SmallF128Config; -pub type SmallF128 = SmallFp; - -// #[derive(SmallFpConfig)] -// #[modulus = "143244528689204659050391023439224324689"] -// #[generator = "2"] -// #[backend = "montgomery"] -// pub struct SmallF128ConfigMont; -// pub type SmallF128Mont = SmallFp; - -test_small_field!(f8; SmallF8; small_prime_field); -test_small_field!(f8_mont; SmallF8Mont; small_prime_field); -test_small_field!(f16; SmallF16; small_prime_field); -test_small_field!(f16_mont; SmallF16Mont; small_prime_field); -test_small_field!(f32; SmallF32; small_prime_field); -test_small_field!(f32_mont; SmallF32Mont; small_prime_field); -test_small_field!(f64; SmallF64; small_prime_field); -test_small_field!(f64_mont; SmallF64Mont; small_prime_field); -test_small_field!(f128; SmallF128; small_prime_field); -// test_small_field!(f128_mont; SmallF128Mont; small_prime_field); - -fn main() { - let mut a = SmallFieldMont::new(20); - println!("{}", a); - SmallFieldMont::exit(&mut a); - println!("{}", a); -} - -#[cfg(test)] -mod tests { - use super::*; - - // ---------- Standard backend tests (existing) ---------- - #[test] - fn add_assign_test() { - let mut a = SmallField::new(20); - let b = SmallField::new(10); - let c = SmallField::new(30); - a += b; - assert_eq!(a.value, c.value); - - let mut a = SmallField::new(SmallField::MODULUS - 1); - let b = SmallField::new(2); - a += b; - assert_eq!(a.value, 1); - - // adding zero - let mut a = SmallField::new(42); - let b = SmallField::ZERO; - a += b; - assert_eq!(a.value, 42); - - // max values - let mut a = SmallField::new(SmallField::MODULUS - 1); - let b = SmallField::new(SmallField::MODULUS - 1); - a += b; - assert_eq!(a.value, SmallField::MODULUS - 2); - - // adding one to maximum - let mut a = SmallField::new(SmallField::MODULUS - 1); - let b = SmallField::ONE; - a += b; - assert_eq!(a.value, 0); - } - - #[test] - fn sub_assign_test() { - let mut a = SmallField::new(30); - let b = SmallField::new(10); - let c = SmallField::new(20); - a -= b; - assert_eq!(a.value, c.value); - - let mut a = SmallField::new(5); - let b = SmallField::new(10); - a -= b; - assert_eq!(a.value, SmallField::MODULUS - 5); - - // subtracting zero - let mut a = SmallField::new(42); - let b = SmallField::ZERO; - a -= b; - assert_eq!(a.value, 42); - - // subtracting from zero - let mut a = SmallField::ZERO; - let b = SmallField::new(1); - a -= b; - assert_eq!(a.value, SmallField::MODULUS - 1); - - // self subtraction - let mut a = SmallField::new(42); - let b = SmallField::new(42); - a -= b; - assert_eq!(a.value, 0); - - // maximum minus one - let mut a = SmallField::new(SmallField::MODULUS - 1); - let b = SmallField::ONE; - a -= b; - assert_eq!(a.value, SmallField::MODULUS - 2); - } - - #[test] - fn mul_assign_test() { - let mut a = SmallField::new(5); - let b = SmallField::new(10); - let c = SmallField::new(50); - a *= b; - assert_eq!(a.value, c.value); - - let mut a = SmallField::new(SmallField::MODULUS / 2); - let b = SmallField::new(3); - a *= b; - assert_eq!(a.value, (SmallField::MODULUS / 2) * 3 % SmallField::MODULUS); - - // multiply by zero - let mut a = SmallField::new(42); - let b = SmallField::ZERO; - a *= b; - assert_eq!(a.value, 0); - - // multiply by one - let mut a = SmallField::new(42); - let b = SmallField::ONE; - a *= b; - assert_eq!(a.value, 42); - - // maximum values - let mut a = SmallField::new(SmallField::MODULUS - 1); - let b = SmallField::new(SmallField::MODULUS - 1); - a *= b; - assert_eq!(a.value, 1); // (p-1)*(p-1) = p^2 - 2p + 1 ≡ 1 (mod p) - } - - #[test] - fn neg_in_place_test() { - let mut a = SmallField::new(10); - SmallField::neg_in_place(&mut a); - assert_eq!(a.value, SmallField::MODULUS - 10); - - let mut a = SmallField::ZERO; - SmallField::neg_in_place(&mut a); - assert_eq!(a.value, 0); - - // negate maximum - let mut a = SmallField::new(SmallField::MODULUS - 1); - SmallField::neg_in_place(&mut a); - assert_eq!(a.value, 1); - - // Edge double negation - let mut a = SmallField::new(42); - let original = a.value; - SmallField::neg_in_place(&mut a); - SmallField::neg_in_place(&mut a); - assert_eq!(a.value, original); - - // negate one - let mut a = SmallField::ONE; - SmallField::neg_in_place(&mut a); - assert_eq!(a.value, SmallField::MODULUS - 1); - } - - #[test] - fn double_in_place_test() { - let mut a = SmallField::new(10); - SmallField::double_in_place(&mut a); - assert_eq!(a.value, 20); - - let mut a = SmallField::new(SmallField::MODULUS - 1); - SmallField::double_in_place(&mut a); - assert_eq!(a.value, SmallField::MODULUS - 2); - - // double zero - let mut a = SmallField::ZERO; - SmallField::double_in_place(&mut a); - assert_eq!(a.value, 0); - - // double maximum/2 + 1 (should wrap) - if SmallField::MODULUS > 2 { - let mut a = SmallField::new(SmallField::MODULUS / 2 + 1); - SmallField::double_in_place(&mut a); - assert_eq!( - a.value, - (SmallField::MODULUS / 2 + 1) * 2 % SmallField::MODULUS - ); - } - - // double one - let mut a = SmallField::ONE; - SmallField::double_in_place(&mut a); - assert_eq!(a.value, 2); - } - - #[test] - fn square_in_place_test() { - let mut a = SmallField::new(5); - let b = SmallField::new(25); - SmallField::square_in_place(&mut a); - assert_eq!(a.value, b.value); - - let mut a = SmallField::new(SmallField::MODULUS - 1); - SmallField::square_in_place(&mut a); - assert_eq!(a.value, 1); - - // square zero - let mut a = SmallField::ZERO; - SmallField::square_in_place(&mut a); - assert_eq!(a.value, 0); - - // square one - let mut a = SmallField::ONE; - SmallField::square_in_place(&mut a); - assert_eq!(a.value, 1); - } - - #[test] - fn zero_inverse() { - let zero = SmallField::ZERO; - assert!(SmallField::inverse(&zero).is_none()) - } - - #[test] - fn test_specific_inverse() { - let mut val = SmallField::new(17); - let val_inv = SmallField::inverse(&val); - SmallField::mul_assign(&mut val, &val_inv.unwrap()); - assert_eq!(val, SmallField::ONE); - } - - #[test] - fn test_inverse() { - // inverse of 1 - let one = SmallField::ONE; - let one_inv = SmallField::inverse(&one).unwrap(); - assert_eq!(one_inv, SmallField::ONE); - - // inverse of p-1 (which should be p-1 since (p-1)^2 ≡ 1 mod p) - let neg_one = SmallField::new(SmallField::MODULUS - 1); - let neg_one_inv = SmallField::inverse(&neg_one).unwrap(); - assert_eq!(neg_one_inv.value, SmallField::MODULUS - 1); - - for i in 1..100 { - let val = SmallField::new(i); - if let Some(inv) = SmallField::inverse(&val) { - let mut product = val; - SmallField::mul_assign(&mut product, &inv); - assert_eq!(product, SmallField::ONE, "Failed for value {}", i); - } - } - - // inverse property: inv(inv(x)) = x - let test_val = SmallField::new(42 % SmallField::MODULUS); - if test_val.value != 0 { - let inv1 = SmallField::inverse(&test_val).unwrap(); - let inv2 = SmallField::inverse(&inv1).unwrap(); - assert_eq!(test_val, inv2); - } - - // inverse is multiplicative: inv(ab) = inv(a) * inv(b) - let a = SmallField::new(7 % SmallField::MODULUS); - let b = SmallField::new(11 % SmallField::MODULUS); - if a.value != 0 && b.value != 0 { - let mut ab = a; - SmallField::mul_assign(&mut ab, &b); - - let inv_ab = SmallField::inverse(&ab).unwrap(); - let inv_a = SmallField::inverse(&a).unwrap(); - let inv_b = SmallField::inverse(&b).unwrap(); - - let mut inv_a_times_inv_b = inv_a; - SmallField::mul_assign(&mut inv_a_times_inv_b, &inv_b); - - assert_eq!(inv_ab, inv_a_times_inv_b); - } - } - - #[test] - fn test_field_axioms() { - // Test additive identity - let a = SmallField::new(42 % SmallField::MODULUS); - let b = SmallField::new(73 % SmallField::MODULUS); - // commutativity of multiplication - let mut a_times_b = a; - let mut b_times_a = b; - SmallField::mul_assign(&mut a_times_b, &b); - SmallField::mul_assign(&mut b_times_a, &a); - assert_eq!(a_times_b, b_times_a); - - // associativity of addition: (a + b) + c = a + (b + c) - let c = SmallField::new(91 % SmallField::MODULUS); - let mut ab_plus_c = a; - SmallField::add_assign(&mut ab_plus_c, &b); - SmallField::add_assign(&mut ab_plus_c, &c); - - let mut a_plus_bc = a; - let mut bc = b; - SmallField::add_assign(&mut bc, &c); - SmallField::add_assign(&mut a_plus_bc, &bc); - - assert_eq!(ab_plus_c, a_plus_bc); - - // distributivity: a * (b + c) = a * b + a * c - let mut a_times_bc = a; - let mut bc = b; - SmallField::add_assign(&mut bc, &c); - SmallField::mul_assign(&mut a_times_bc, &bc); - - let mut ab_plus_ac = a; - SmallField::mul_assign(&mut ab_plus_ac, &b); - let mut ac = a; - SmallField::mul_assign(&mut ac, &c); - SmallField::add_assign(&mut ab_plus_ac, &ac); - - assert_eq!(a_times_bc, ab_plus_ac); - } - - #[test] - fn test_sum_of_products() { - let a = [SmallField::new(2), SmallField::new(3), SmallField::new(5)]; - let b = [SmallField::new(7), SmallField::new(11), SmallField::new(13)]; - let result = SmallField::sum_of_products(&a, &b); - assert_eq!(result.value, 112 % SmallField::MODULUS); - - let a = [SmallField::ZERO, SmallField::new(3), SmallField::ZERO]; - let b = [SmallField::new(7), SmallField::new(11), SmallField::new(13)]; - let result = SmallField::sum_of_products(&a, &b); - assert_eq!(result.value, 33 % SmallField::MODULUS); - } - - #[test] - fn test_sqrt_standard_backend() { - use ark_ff::Field; - - for i in [4, 16, 144, 169, 256, 400] { - let a = SmallField::new(i); - let sqrt = a.sqrt(); - assert!(sqrt.is_some()); - let sqrt_val = sqrt.unwrap(); - let mut sqrt_squared = sqrt_val; - SmallField::square_in_place(&mut sqrt_squared); - assert_eq!(sqrt_squared.value, i); - } - - let three = SmallField::new(3); - let sqrt_three = three.sqrt(); - assert!(sqrt_three.is_none()); - - let one = SmallField::ONE; - let sqrt_one = one.sqrt(); - assert!(sqrt_one.is_some()); - assert_eq!(sqrt_one.unwrap(), SmallField::ONE); - - let zero = SmallField::ZERO; - let sqrt_zero = zero.sqrt(); - assert!(sqrt_zero.is_some()); - assert_eq!(sqrt_zero.unwrap(), SmallField::ZERO); - } - - // ---------- Montgomery backend tests ---------- - #[test] - fn add_assign_test_montgomery() { - let mut a = SmallFieldMont::new(20); - let b = SmallFieldMont::new(10); - let mut c = SmallFieldMont::new(30); - a += b; - SmallFieldMont::exit(&mut a); - SmallFieldMont::exit(&mut c); - assert_eq!(a.value, c.value); - - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - let b = SmallFieldMont::new(2); - a += b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 1); - - // adding zero - let mut a = SmallFieldMont::new(42); - let b = SmallFieldMont::ZERO; - a += b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 42); - - // max values - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - let b = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - a += b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, SmallFieldMont::MODULUS - 2); - - // adding one to maximum - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - let b = SmallFieldMont::ONE; - a += b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 0); - } - - #[test] - fn sub_assign_test_montgomery() { - let mut a = SmallFieldMont::new(30); - let b = SmallFieldMont::new(10); - let mut c = SmallFieldMont::new(20); - a -= b; - SmallFieldMont::exit(&mut a); - SmallFieldMont::exit(&mut c); - assert_eq!(a.value, c.value); - - let mut a = SmallFieldMont::new(5); - let b = SmallFieldMont::new(10); - a -= b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, SmallFieldMont::MODULUS - 5); - - // subtracting zero - let mut a = SmallFieldMont::new(42); - let b = SmallFieldMont::ZERO; - a -= b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 42); - - // subtracting from zero - let mut a = SmallFieldMont::ZERO; - let b = SmallFieldMont::new(1); - a -= b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, SmallFieldMont::MODULUS - 1); - - // self subtraction - let mut a = SmallFieldMont::new(42); - let b = SmallFieldMont::new(42); - a -= b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 0); - - // maximum minus one - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - let b = SmallFieldMont::ONE; - a -= b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, SmallFieldMont::MODULUS - 2); - } - - #[test] - fn mul_assign_test_montgomery() { - let mut a = SmallFieldMont::new(5); - let b = SmallFieldMont::new(10); - let mut c = SmallFieldMont::new(50); - a *= b; - SmallFieldMont::exit(&mut a); - SmallFieldMont::exit(&mut c); - assert_eq!(a.value, c.value); - - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS / 2); - let b = SmallFieldMont::new(3); - a *= b; - SmallFieldMont::exit(&mut a); - assert_eq!( - a.value, - (SmallFieldMont::MODULUS / 2) * 3 % SmallFieldMont::MODULUS - ); - - // multiply by zero - let mut a = SmallFieldMont::new(42); - let b = SmallFieldMont::ZERO; - a *= b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 0); - - // multiply by one - let mut a = SmallFieldMont::new(42); - let b = SmallFieldMont::ONE; - a *= b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 42); - - // maximum values - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - let b = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - a *= b; - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 1); // (p-1)*(p-1) ≡ 1 (mod p) - } - - #[test] - fn neg_in_place_test_montgomery() { - let mut a = SmallFieldMont::new(10); - SmallFieldMont::neg_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, SmallFieldMont::MODULUS - 10); - - let mut a = SmallFieldMont::ZERO; - SmallFieldMont::neg_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 0); - - // negate maximum - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - SmallFieldMont::neg_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 1); - - // Edge double negation - let mut a = SmallFieldMont::new(42); - let original = { - let mut tmp = a; - SmallFieldMont::exit(&mut tmp); - tmp.value - }; - SmallFieldMont::neg_in_place(&mut a); - SmallFieldMont::neg_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, original); - - // negate one - let mut a = SmallFieldMont::ONE; - SmallFieldMont::neg_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, SmallFieldMont::MODULUS - 1); - } - - #[test] - fn double_in_place_test_montgomery() { - let mut a = SmallFieldMont::new(10); - SmallFieldMont::double_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 20); - - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - SmallFieldMont::double_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, SmallFieldMont::MODULUS - 2); - - // double zero - let mut a = SmallFieldMont::ZERO; - SmallFieldMont::double_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 0); - - // double maximum/2 + 1 (should wrap) - if SmallFieldMont::MODULUS > 2 { - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS / 2 + 1); - SmallFieldMont::double_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!( - a.value, - (SmallFieldMont::MODULUS / 2 + 1) * 2 % SmallFieldMont::MODULUS - ); - } - - // double one - let mut a = SmallFieldMont::ONE; - SmallFieldMont::double_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 2); - } - - #[test] - fn square_in_place_test_montgomery() { - let mut a = SmallFieldMont::new(5); - let mut b = SmallFieldMont::new(25); - SmallFieldMont::square_in_place(&mut a); - SmallFieldMont::exit(&mut a); - SmallFieldMont::exit(&mut b); - assert_eq!(a.value, b.value); - - let mut a = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - SmallFieldMont::square_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 1); - - // square zero - let mut a = SmallFieldMont::ZERO; - SmallFieldMont::square_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 0); - - // square one - let mut a = SmallFieldMont::ONE; - SmallFieldMont::square_in_place(&mut a); - SmallFieldMont::exit(&mut a); - assert_eq!(a.value, 1); - } - - #[test] - fn zero_inverse_montgomery() { - let zero = SmallFieldMont::ZERO; - assert!(SmallFieldMont::inverse(&zero).is_none()) - } - - #[test] - fn test_specific_inverse_montgomery() { - let val = SmallFieldMont::new(17); - let val_inv = SmallFieldMont::inverse(&val); - let mut val_copy = val; - SmallFieldMont::mul_assign(&mut val_copy, &val_inv.unwrap()); - SmallFieldMont::exit(&mut val_copy); - assert_eq!(val_copy.value, 1); - } - - #[test] - fn test_inverse_montgomery() { - // inverse of 1 - let one = SmallFieldMont::ONE; - let one_inv = SmallFieldMont::inverse(&one).unwrap(); - let mut one_inv_copy = one_inv; - SmallFieldMont::exit(&mut one_inv_copy); - assert_eq!(one_inv_copy.value, 1); - - // inverse of p-1 (which should be p-1 since (p-1)^2 ≡ 1 mod p) - let neg_one = SmallFieldMont::new(SmallFieldMont::MODULUS - 1); - let neg_one_inv = SmallFieldMont::inverse(&neg_one).unwrap(); - let mut tmp = neg_one_inv; - SmallFieldMont::exit(&mut tmp); - assert_eq!(tmp.value, SmallFieldMont::MODULUS - 1); - - for i in 1..100 { - let val = SmallFieldMont::new(i); - if let Some(inv) = SmallFieldMont::inverse(&val) { - let mut product = val; - SmallFieldMont::mul_assign(&mut product, &inv); - SmallFieldMont::exit(&mut product); - assert_eq!(product.value, 1, "Failed for value {}", i); - } - } - - // inverse property: inv(inv(x)) = x - let test_val = SmallFieldMont::new(42 % SmallFieldMont::MODULUS); - { - let mut tv_copy = test_val; - SmallFieldMont::exit(&mut tv_copy); - if tv_copy.value != 0 { - let inv1 = SmallFieldMont::inverse(&test_val).unwrap(); - let inv2 = SmallFieldMont::inverse(&inv1).unwrap(); - let mut inv2_copy = inv2; - SmallFieldMont::exit(&mut inv2_copy); - let mut original_copy = test_val; - SmallFieldMont::exit(&mut original_copy); - assert_eq!(original_copy.value, inv2_copy.value); - } - } - - // inverse is multiplicative: inv(ab) = inv(a) * inv(b) - let a = SmallFieldMont::new(7 % SmallFieldMont::MODULUS); - let b = SmallFieldMont::new(11 % SmallFieldMont::MODULUS); - { - let mut a_copy = a; - SmallFieldMont::exit(&mut a_copy); - let mut b_copy = b; - SmallFieldMont::exit(&mut b_copy); - if a_copy.value != 0 && b_copy.value != 0 { - let mut ab = a; - SmallFieldMont::mul_assign(&mut ab, &b); - - let inv_ab = SmallFieldMont::inverse(&ab).unwrap(); - let inv_a = SmallFieldMont::inverse(&a).unwrap(); - let inv_b = SmallFieldMont::inverse(&b).unwrap(); - - let mut inv_a_times_inv_b = inv_a; - SmallFieldMont::mul_assign(&mut inv_a_times_inv_b, &inv_b); - - let mut tmp1 = inv_ab; - let mut tmp2 = inv_a_times_inv_b; - SmallFieldMont::exit(&mut tmp1); - SmallFieldMont::exit(&mut tmp2); - assert_eq!(tmp1.value, tmp2.value); - } - } - } - - #[test] - fn test_field_axioms_montgomery() { - // Test additive identity - let a = SmallFieldMont::new(42 % SmallFieldMont::MODULUS); - let b = SmallFieldMont::new(73 % SmallFieldMont::MODULUS); - // commutativity of multiplication - let mut a_times_b = a; - let mut b_times_a = b; - SmallFieldMont::mul_assign(&mut a_times_b, &b); - SmallFieldMont::mul_assign(&mut b_times_a, &a); - SmallFieldMont::exit(&mut a_times_b); - SmallFieldMont::exit(&mut b_times_a); - assert_eq!(a_times_b.value, b_times_a.value); - - // associativity of addition: (a + b) + c = a + (b + c) - let c = SmallFieldMont::new(91 % SmallFieldMont::MODULUS); - let mut ab_plus_c = a; - SmallFieldMont::add_assign(&mut ab_plus_c, &b); - SmallFieldMont::add_assign(&mut ab_plus_c, &c); - - let mut a_plus_bc = a; - let mut bc = b; - SmallFieldMont::add_assign(&mut bc, &c); - SmallFieldMont::add_assign(&mut a_plus_bc, &bc); - - SmallFieldMont::exit(&mut ab_plus_c); - SmallFieldMont::exit(&mut a_plus_bc); - assert_eq!(ab_plus_c.value, a_plus_bc.value); - - // distributivity: a * (b + c) = a * b + a * c - let mut a_times_bc = a; - let mut bc = b; - SmallFieldMont::add_assign(&mut bc, &c); - SmallFieldMont::mul_assign(&mut a_times_bc, &bc); - - let mut ab_plus_ac = a; - SmallFieldMont::mul_assign(&mut ab_plus_ac, &b); - let mut ac = a; - SmallFieldMont::mul_assign(&mut ac, &c); - SmallFieldMont::add_assign(&mut ab_plus_ac, &ac); - - SmallFieldMont::exit(&mut a_times_bc); - SmallFieldMont::exit(&mut ab_plus_ac); - assert_eq!(a_times_bc.value, ab_plus_ac.value); - } - - #[test] - fn test_sum_of_products_montgomery() { - let a = [ - SmallFieldMont::new(2), - SmallFieldMont::new(3), - SmallFieldMont::new(5), - ]; - let b = [ - SmallFieldMont::new(7), - SmallFieldMont::new(11), - SmallFieldMont::new(13), - ]; - let mut result = SmallFieldMont::sum_of_products(&a, &b); - SmallFieldMont::exit(&mut result); - assert_eq!(result.value, 112 % SmallFieldMont::MODULUS); - - let a = [ - SmallFieldMont::ZERO, - SmallFieldMont::new(3), - SmallFieldMont::ZERO, - ]; - let b = [ - SmallFieldMont::new(7), - SmallFieldMont::new(11), - SmallFieldMont::new(13), - ]; - let mut result = SmallFieldMont::sum_of_products(&a, &b); - SmallFieldMont::exit(&mut result); - assert_eq!(result.value, 33 % SmallFieldMont::MODULUS); - } - - #[test] - fn test_sqrt_montgomery_backend() { - use ark_ff::Field; - - for i in [4, 16, 144, 169, 256, 400] { - let a = SmallFieldMont::new(i); - let sqrt = a.sqrt(); - assert!(sqrt.is_some()); - let sqrt_val = sqrt.unwrap(); - let mut sqrt_squared = sqrt_val; - SmallFieldMont::square_in_place(&mut sqrt_squared); - let mut a_copy = a; - SmallFieldMont::exit(&mut sqrt_squared); - SmallFieldMont::exit(&mut a_copy); - assert_eq!(sqrt_squared.value, a_copy.value); - } - - let three = SmallFieldMont::new(31); - let sqrt_three = three.sqrt(); - assert!(sqrt_three.is_none()); - - let one = SmallFieldMont::ONE; - let sqrt_one = one.sqrt(); - assert!(sqrt_one.is_some()); - let mut sqrt_one_val = sqrt_one.unwrap(); - SmallFieldMont::exit(&mut sqrt_one_val); - assert_eq!(sqrt_one_val.value, 1); - - let zero = SmallFieldMont::ZERO; - let sqrt_zero = zero.sqrt(); - assert!(sqrt_zero.is_some()); - let mut sqrt_zero_val = sqrt_zero.unwrap(); - SmallFieldMont::exit(&mut sqrt_zero_val); - assert_eq!(sqrt_zero_val.value, 0); - } -} diff --git a/ff/Cargo.toml b/ff/Cargo.toml index 51df53b7e..47f5de2cf 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -48,14 +48,6 @@ serde_derive.workspace = true hex.workspace = true criterion.workspace = true -[[example]] -name = "fpconfig" -path = "../examples/fpconfig.rs" - -[[example]] -name = "smallfp_tests" -path = "../examples/smallfp_tests.rs" - [[bench]] name = "cast_bench" harness = false diff --git a/ff/tests/smallfp_tests.rs b/ff/tests/smallfp_tests.rs new file mode 100644 index 000000000..98f115c67 --- /dev/null +++ b/ff/tests/smallfp_tests.rs @@ -0,0 +1,85 @@ +use ark_algebra_test_templates::*; +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; + +#[derive(SmallFpConfig)] +#[modulus = "251"] +#[generator = "6"] +#[backend = "standard"] +pub struct SmallF8Config; +pub type SmallF8 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "251"] +#[generator = "6"] +#[backend = "montgomery"] +pub struct SmallF8ConfigMont; +pub type SmallF8Mont = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "65521"] +#[generator = "17"] +#[backend = "standard"] +pub struct SmallF16Config; +pub type SmallF16 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "65521"] +#[generator = "17"] +#[backend = "montgomery"] +pub struct SmallF16ConfigMont; +pub type SmallF16Mont = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "2147483647"] // m31 +#[generator = "7"] +#[backend = "standard"] +pub struct SmallField; +pub type SmallF32 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "2147483647"] // m31 +#[generator = "7"] +#[backend = "montgomery"] +pub struct SmallFieldMont; +pub type SmallF32Mont = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 +#[generator = "7"] +#[backend = "standard"] +pub struct SmallF64Config; +pub type SmallF64 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 +#[generator = "7"] +#[backend = "montgomery"] +pub struct SmallF64ConfigMont; +pub type SmallF64Mont = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "3"] +#[backend = "standard"] +pub struct SmallF128Config; +pub type SmallF128 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "3"] +#[backend = "montgomery"] +pub struct SmallF128ConfigMont; +pub type SmallF128Mont = SmallFp; + +test_small_field!(f8; SmallF8); +test_small_field!(f16; SmallF16); +test_small_field!(f32; SmallF32); +test_small_field!(f64; SmallF64); +test_small_field!(f128; SmallF128); + +test_small_field!(f8_mont; SmallF8Mont); +test_small_field!(f16_mont; SmallF16Mont); +test_small_field!(f32_mont; SmallF32Mont); +test_small_field!(f64_mont; SmallF64Mont); +test_small_field!(f128_mont; SmallF128Mont); diff --git a/test-templates/src/fields.rs b/test-templates/src/fields.rs index f40170aee..d5a8913b4 100644 --- a/test-templates/src/fields.rs +++ b/test-templates/src/fields.rs @@ -587,17 +587,6 @@ macro_rules! test_field { }; } -pub fn small_field_sum_of_products_test_helper(rng: &mut impl Rng) -where - F: ark_ff::Field + ark_std::UniformRand, -{ - let a = [(); N].map(|_| F::rand(rng)); - let b = [(); N].map(|_| F::rand(rng)); - let result_1 = F::sum_of_products(&a, &b); - let result_2 = a.into_iter().zip(b).map(|(a, b)| a * b).sum::(); - assert_eq!(result_1, result_2, "length: {N}"); -} - #[macro_export] #[doc(hidden)] macro_rules! __test_small_field { @@ -605,14 +594,13 @@ macro_rules! __test_small_field { #[test] pub fn test_frobenius() { use ark_ff::Field; + use ark_std::UniformRand; + let mut rng = ark_std::test_rng(); + let characteristic = <$field>::characteristic(); + let max_power = (<$field>::extension_degree() + 1) as usize; - // Test with specific values rather than random ones to avoid from_bigint issues - let test_values = [0, 1, 2, 42, 100]; - - for &val in &test_values { - let a = <$field>::from(val as u64); - let characteristic = <$field>::characteristic(); - let max_power = (<$field>::extension_degree() + 1) as usize; + for _ in 0..ITERATIONS { + let a = <$field>::rand(&mut rng); let mut a_0 = a; a_0.frobenius_map_in_place(0); @@ -625,13 +613,69 @@ macro_rules! __test_small_field { let mut a_qi = a; a_qi.frobenius_map_in_place(power); - assert_eq!(a_q, a_qi); + assert_eq!(a_qi, a_q, "failed on power {}", power); a_q = a_q.pow(&characteristic); } } } + #[test] + fn test_serialization() { + use ark_serialize::*; + use ark_std::UniformRand; + for compress in [Compress::Yes, Compress::No] { + for validate in [Validate::Yes, Validate::No] { + let buf_size = <$field>::zero().serialized_size(compress); + + let buffer_size = + buffer_bit_byte_size(<$field as Field>::BasePrimeField::MODULUS_BIT_SIZE as usize).1 * + (<$field>::extension_degree() as usize); + assert_eq!(buffer_size, buf_size); + + let mut rng = ark_std::test_rng(); + + for _ in 0..ITERATIONS { + let a = <$field>::rand(&mut rng); + { + let mut serialized = vec![0u8; buf_size]; + let mut cursor = Cursor::new(&mut serialized[..]); + a.serialize_with_mode(&mut cursor, compress).unwrap(); + + let mut cursor = Cursor::new(&serialized[..]); + let b = <$field>::deserialize_with_mode(&mut cursor, compress, validate).unwrap(); + assert_eq!(a, b); + } + + { + let mut serialized = vec![0; buf_size]; + let result = matches!( + a.serialize_with_flags(&mut &mut serialized[..], $crate::fields::DummyFlags).unwrap_err(), + SerializationError::NotEnoughSpace + ); + assert!(result); + + let result = matches!( + <$field>::deserialize_with_flags::<_, $crate::fields::DummyFlags>(&mut &serialized[..]).unwrap_err(), + SerializationError::NotEnoughSpace, + ); + assert!(result); + + { + let mut serialized = vec![0; buf_size - 1]; + let mut cursor = Cursor::new(&mut serialized[..]); + a.serialize_with_mode(&mut cursor, compress).unwrap_err(); + + let mut cursor = Cursor::new(&serialized[..]); + <$field>::deserialize_with_mode(&mut cursor, compress, validate).unwrap_err(); + } + } + } + } + } + + } + #[test] fn test_add_properties() { use ark_ff::AdditiveGroup; @@ -837,32 +881,32 @@ macro_rules! __test_small_field { } } - // #[test] - // fn test_sqrt() { - // if <$field>::SQRT_PRECOMP.is_some() { - // use ark_std::UniformRand; - // let rng = &mut test_rng(); - - // assert!(<$field>::zero().sqrt().unwrap().is_zero()); - - // for _ in 0..ITERATIONS { - // // Ensure sqrt(a^2) = a or -a - // let a = <$field>::rand(rng); - // let b = a.square(); - // let sqrt = b.sqrt().unwrap(); - // assert!(a == sqrt || -a == sqrt); - - // if let Some(mut b) = a.sqrt() { - // b.square_in_place(); - // assert_eq!(a, b); - // } - - // let a = <$field>::rand(rng); - // let b = a.square(); - // assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); - // } - // } - // } + #[test] + fn test_sqrt() { + if <$field>::SQRT_PRECOMP.is_some() { + use ark_std::UniformRand; + let rng = &mut test_rng(); + + assert!(<$field>::zero().sqrt().unwrap().is_zero()); + + for _ in 0..ITERATIONS { + // Ensure sqrt(a^2) = a or -a + let a = <$field>::rand(rng); + let b = a.square(); + let sqrt = b.sqrt().unwrap(); + assert!(a == sqrt || -a == sqrt); + + if let Some(mut b) = a.sqrt() { + b.square_in_place(); + assert_eq!(a, b); + } + + let a = <$field>::rand(rng); + let b = a.square(); + assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + } + } + } #[test] fn test_mul_by_base_field_elem() { @@ -886,42 +930,128 @@ macro_rules! __test_small_field { assert_eq!(computed, naive); } } - }; + #[test] + fn test_fft() { + use ark_ff::FftField; + use $crate::num_bigint::BigUint; + + let two_pow_2_adicity = BigUint::from(1_u8) << <$field>::TWO_ADICITY as u32; + assert_eq!( + <$field>::TWO_ADIC_ROOT_OF_UNITY.pow(two_pow_2_adicity.to_u64_digits()), + <$field>::one() + ); + + if let Some(small_subgroup_base) = <$field>::SMALL_SUBGROUP_BASE { + let small_subgroup_base_adicity = <$field>::SMALL_SUBGROUP_BASE_ADICITY.unwrap(); + let large_subgroup_root_of_unity = <$field>::LARGE_SUBGROUP_ROOT_OF_UNITY.unwrap(); + let pow = <$field>::from(two_pow_2_adicity) + * <$field>::from(small_subgroup_base as u64) + .pow([small_subgroup_base_adicity as u64]); + assert_eq!( + large_subgroup_root_of_unity.pow(pow.into_bigint()), + <$field>::one() + ); - ($field: ty; small_prime_field) => { - $crate::__test_small_field!($field); + for i in 0..=<$field>::TWO_ADICITY { + for j in 0..=small_subgroup_base_adicity { + let size = (1u64 << i) * (small_subgroup_base as u64).pow(j); + let root = <$field>::get_root_of_unity(size as u64).unwrap(); + assert_eq!(root.pow([size as u64]), <$field>::one()); + } + } + } else { + for i in 0..=<$field>::TWO_ADICITY { + let size = BigUint::from(1_u8) << i; + let root = <$field>::get_root_of_unity_big_int(size.clone()).unwrap(); + assert_eq!(root.pow(size.to_u64_digits()), <$field>::one()); + } + } + } + + + #[test] + fn test_constants() { + use ark_ff::{FpConfig, BigInteger, SqrtPrecomputation}; + use $crate::num_bigint::BigUint; + use $crate::num_integer::Integer; + + let modulus: BigUint = <$field>::MODULUS.into(); + let modulus_minus_one = &modulus - 1u8; + assert_eq!(BigUint::from(<$field>::MODULUS_MINUS_ONE_DIV_TWO), &modulus_minus_one / 2u32); + assert_eq!(<$field>::MODULUS_BIT_SIZE as u64, modulus.bits()); + if let Some(SqrtPrecomputation::Case3Mod4 { modulus_plus_one_div_four }) = <$field>::SQRT_PRECOMP { + // Handle the case where `(MODULUS + 1) / 4` + // has fewer limbs than `MODULUS`. + let check = ((&modulus + 1u8) / 4u8).to_u64_digits(); + let len = check.len(); + assert_eq!(&modulus_plus_one_div_four[..len], &check); + assert!(modulus_plus_one_div_four[len..].iter().all(Zero::is_zero)); + } + + let mut two_adicity = 0; + let mut trace = modulus_minus_one; + while trace.is_even() { + trace /= 2u8; + two_adicity += 1; + } + assert_eq!(two_adicity, <$field>::TWO_ADICITY); + assert_eq!(BigUint::from(<$field>::TRACE), trace); + let trace_minus_one_div_two = (&trace - 1u8) / 2u8; + assert_eq!(BigUint::from(<$field>::TRACE_MINUS_ONE_DIV_TWO), trace_minus_one_div_two); + + let two_adic_root_of_unity: BigUint = <$field>::TWO_ADIC_ROOT_OF_UNITY.into(); + let generator: BigUint = <$field>::GENERATOR.into_bigint().into(); + assert_eq!(two_adic_root_of_unity, generator.modpow(&trace, &modulus)); + match (<$field>::SMALL_SUBGROUP_BASE, <$field>::SMALL_SUBGROUP_BASE_ADICITY) { + (Some(base), Some(adicity)) => { + let mut e = generator; + for _i in 0..adicity { + e = e.modpow(&base.into(), &modulus) + } + }, + (None, None) => {}, + (_, _) => { + panic!("Should specify both `SMALL_SUBGROUP_BASE` and `SMALL_SUBGROUP_BASE_ADICITY`") + }, + } + } }; } +/// This macro includes most tests from `test_field!` but excludes: +// `test_montgomery_config`: small fields do not extend MontConfig +// `test_sum_of_products_edge_case`: cases assume use of BigInts #[macro_export] macro_rules! test_small_field { - ($mod_name:ident; $field:ty $(; $tail:tt)*) => { + ($mod_name:ident; $field:ty) => { mod $mod_name { use super::*; use ark_ff::{ fields::{Field, LegendreSymbol}, - SmallFp, SmallFpConfig, + FftField, PrimeField, SmallFp, SmallFpConfig, + }; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + use ark_std::{ + io::Cursor, rand::Rng, rand::RngCore, test_rng, vec::Vec, One, UniformRand, Zero, }; - use ark_serialize::{CanonicalSerialize, CanonicalDeserialize}; - use ark_std::{rand::Rng, rand::RngCore, test_rng, vec::Vec, Zero, One, UniformRand}; const ITERATIONS: usize = 1000; - $crate::__test_small_field!($field $(; $tail)*); + $crate::__test_small_field!($field); } }; - ($iters:expr; $mod_name:ident; $field:ty $(; $tail:tt)*) => { + ($iters:expr; $mod_name:ident; $field:ty) => { mod $mod_name { use super::*; use ark_ff::{ fields::{Field, LegendreSymbol}, - SmallFp, SmallFpConfig, + FftField, SmallFp, SmallFpConfig, }; - use ark_serialize::{CanonicalSerialize, CanonicalDeserialize}; - use ark_std::{rand::Rng, rand::RngCore, test_rng, vec::Vec, Zero, One, UniformRand}; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + use ark_std::{rand::Rng, rand::RngCore, test_rng, vec::Vec, One, UniformRand, Zero}; const ITERATIONS: usize = $iters; - $crate::__test_small_field!($field $(; $tail)*); + $crate::__test_small_field!($field); } }; } From 294521789dba02eaa0a7eaddda9cda9915fef0a3 Mon Sep 17 00:00:00 2001 From: benbencik Date: Sun, 5 Oct 2025 22:51:18 +0200 Subject: [PATCH 16/47] rewrite mul_assing to handle overflows correctly --- ff-macros/src/small_fp/montgomery_backend.rs | 55 ++++++++++++++++---- ff-macros/src/small_fp/standard_backend.rs | 3 -- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 97c64384e..e0c192274 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -21,7 +21,7 @@ pub(crate) fn backend_impl( let two_adicity = compute_two_adicity(modulus); let two_adic_root = compute_two_adic_root_of_unity(modulus, two_adicity); let two_adic_root_mont = mod_mul_const(two_adic_root, r_mod_n, modulus); - + let neg_one_mont = mod_mul_const(modulus - 1, r_mod_n, modulus); let (from_bigint_impl, into_bigint_impl) = @@ -72,15 +72,50 @@ pub(crate) fn backend_impl( let a_u128 = a.value as u128; let b_u128 = b.value as u128; - let t = a_u128.wrapping_mul(b_u128); - let m = t.wrapping_mul(#n_prime) & #r_mask; - let mn = m.wrapping_mul(#modulus); - - let (t_plus_mn, overflow) = t.overflowing_add(mn); - let mut u = t_plus_mn >> #k_bits; - if overflow { - u += 1u128 << (128 - #k_bits); - } + let a_lo = a_u128 as u64; + let a_hi = (a_u128 >> 64) as u64; + let b_lo = b_u128 as u64; + let b_hi = (b_u128 >> 64) as u64; + + // t = a * b (256-bit result stored as t_lo, t_hi) + let lolo = (a_lo as u128) * (b_lo as u128); + let lohi = (a_lo as u128) * (b_hi as u128); + let hilo = (a_hi as u128) * (b_lo as u128); + let hihi = (a_hi as u128) * (b_hi as u128); + + let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); + let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); + let t_lo = mid; + let t_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); + + // m = t_lo * n_prime & r_mask + let m = t_lo.wrapping_mul(#n_prime) & #r_mask; + + // mn = m * modulus + let m_lo = m as u64; + let m_hi = (m >> 64) as u64; + let n_lo = #modulus as u64; + let n_hi = (#modulus >> 64) as u64; + + let lolo = (m_lo as u128) * (n_lo as u128); + let lohi = (m_lo as u128) * (n_hi as u128); + let hilo = (m_hi as u128) * (n_lo as u128); + let hihi = (m_hi as u128) * (n_hi as u128); + + let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); + let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); + let mn_lo = mid; + let mn_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); + + // (t + mn) / R + let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); + let sum_hi = t_hi + mn_hi + (carry as u128); + + let mut u = if #k_bits < 128 { + (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)) + } else { + sum_hi >> (#k_bits - 128) + }; if u >= #modulus { u -= #modulus; diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs index 00100ef0d..08c1b4f70 100644 --- a/ff-macros/src/small_fp/standard_backend.rs +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -55,9 +55,6 @@ pub(crate) fn backend_impl( } } - // TODO: this could be done faster - // a = a1*C + a0, b = b1*C + b0 - // a*b = a1*b1*C^2 + (a1*b0 + a0*b1)*C + a0*b0 fn mul_assign(a: &mut SmallFp, b: &SmallFp) { let a_128 = (a.value as u128) % #modulus; let b_128 = (b.value as u128) % #modulus; From 582ac3dff31df05520b7951c361827cfcf3b95a0 Mon Sep 17 00:00:00 2001 From: benbencik Date: Wed, 8 Oct 2025 10:44:02 +0200 Subject: [PATCH 17/47] move tests and benches to test-curves --- ff/Cargo.toml | 12 +-- ff/benches/cast_bench.rs | 49 ----------- ff/tests/smallfp_tests.rs | 85 ------------------- test-curves/Cargo.toml | 8 ++ .../benches/smallfp_comparison.rs | 41 +-------- test-curves/src/lib.rs | 6 ++ test-curves/src/smallfp128.rs | 26 ++++++ test-curves/src/smallfp16.rs | 26 ++++++ test-curves/src/smallfp32.rs | 26 ++++++ test-curves/src/smallfp64.rs | 26 ++++++ test-curves/src/smallfp8.rs | 26 ++++++ 11 files changed, 147 insertions(+), 184 deletions(-) delete mode 100644 ff/benches/cast_bench.rs delete mode 100644 ff/tests/smallfp_tests.rs rename ff/benches/field_comparison.rs => test-curves/benches/smallfp_comparison.rs (83%) create mode 100644 test-curves/src/smallfp128.rs create mode 100644 test-curves/src/smallfp16.rs create mode 100644 test-curves/src/smallfp32.rs create mode 100644 test-curves/src/smallfp64.rs create mode 100644 test-curves/src/smallfp8.rs diff --git a/ff/Cargo.toml b/ff/Cargo.toml index 47f5de2cf..0432a9c78 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -36,9 +36,7 @@ ark-test-curves = { workspace = true, features = [ "secp256k1", ] } ark-algebra-test-templates = { path = "../test-templates" } -p3_field = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-field" } -p3_goldilocks = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-goldilocks" } -blake2.workspace = true + sha3.workspace = true sha2.workspace = true libtest-mimic.workspace = true @@ -48,14 +46,6 @@ serde_derive.workspace = true hex.workspace = true criterion.workspace = true -[[bench]] -name = "cast_bench" -harness = false - -[[bench]] -name = "field_comparison" -harness = false - [features] default = [] std = ["ark-std/std", "ark-serialize/std", "itertools/use_std"] diff --git a/ff/benches/cast_bench.rs b/ff/benches/cast_bench.rs deleted file mode 100644 index 335f71d69..000000000 --- a/ff/benches/cast_bench.rs +++ /dev/null @@ -1,49 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use std::hint::black_box; - -fn mult_u128(c: &mut Criterion) { - c.bench_function("u128_mult", |b| { - b.iter(|| { - let a: u8 = 5; - let b: u8 = 254; - let c: u128 = (a as u128) * (b as u128); - black_box((c % 255) as u8); - }) - }); -} - -fn mult_u16(c: &mut Criterion) { - c.bench_function("u16_mult", |b| { - b.iter(|| { - let a: u8 = 5; - let b: u8 = 254; - let c: u16 = (a as u16) * (b as u16); - black_box((c % 255) as u8); - }) - }); -} - -fn add_u128(c: &mut Criterion) { - c.bench_function("u128_add", |b| { - b.iter(|| { - let a: u8 = 5; - let b: u8 = 254; - let c: u128 = (a as u128) + (b as u128); - black_box((c % 255) as u8); - }) - }); -} - -fn add_u16(c: &mut Criterion) { - c.bench_function("u8_add", |b| { - b.iter(|| { - let a: u8 = 5; - let b: u8 = 25; - let c: u8 = a + b; - black_box((c % 255) as u8); - }) - }); -} - -criterion_group!(benches, mult_u128, mult_u16, add_u128, add_u16); -criterion_main!(benches); diff --git a/ff/tests/smallfp_tests.rs b/ff/tests/smallfp_tests.rs deleted file mode 100644 index 98f115c67..000000000 --- a/ff/tests/smallfp_tests.rs +++ /dev/null @@ -1,85 +0,0 @@ -use ark_algebra_test_templates::*; -use ark_ff::ark_ff_macros::SmallFpConfig; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; - -#[derive(SmallFpConfig)] -#[modulus = "251"] -#[generator = "6"] -#[backend = "standard"] -pub struct SmallF8Config; -pub type SmallF8 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "251"] -#[generator = "6"] -#[backend = "montgomery"] -pub struct SmallF8ConfigMont; -pub type SmallF8Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "65521"] -#[generator = "17"] -#[backend = "standard"] -pub struct SmallF16Config; -pub type SmallF16 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "65521"] -#[generator = "17"] -#[backend = "montgomery"] -pub struct SmallF16ConfigMont; -pub type SmallF16Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "2147483647"] // m31 -#[generator = "7"] -#[backend = "standard"] -pub struct SmallField; -pub type SmallF32 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "2147483647"] // m31 -#[generator = "7"] -#[backend = "montgomery"] -pub struct SmallFieldMont; -pub type SmallF32Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 -#[generator = "7"] -#[backend = "standard"] -pub struct SmallF64Config; -pub type SmallF64 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 -#[generator = "7"] -#[backend = "montgomery"] -pub struct SmallF64ConfigMont; -pub type SmallF64Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "3"] -#[backend = "standard"] -pub struct SmallF128Config; -pub type SmallF128 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "3"] -#[backend = "montgomery"] -pub struct SmallF128ConfigMont; -pub type SmallF128Mont = SmallFp; - -test_small_field!(f8; SmallF8); -test_small_field!(f16; SmallF16); -test_small_field!(f32; SmallF32); -test_small_field!(f64; SmallF64); -test_small_field!(f128; SmallF128); - -test_small_field!(f8_mont; SmallF8Mont); -test_small_field!(f16_mont; SmallF16Mont); -test_small_field!(f32_mont; SmallF32Mont); -test_small_field!(f64_mont; SmallF64Mont); -test_small_field!(f128_mont; SmallF128Mont); diff --git a/test-curves/Cargo.toml b/test-curves/Cargo.toml index fda91432b..52ac49d52 100644 --- a/test-curves/Cargo.toml +++ b/test-curves/Cargo.toml @@ -27,6 +27,9 @@ ark-ec = { workspace = true, default-features = false } ark-serialize = { workspace = true, default-features = false } ark-algebra-test-templates = { workspace = true, default-features = false } ark-algebra-bench-templates = { workspace = true, default-features = false } +criterion.workspace = true +p3_field = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-field" } +p3_goldilocks = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-goldilocks" } [features] default = [] @@ -78,3 +81,8 @@ harness = false name = "mnt6_753" path = "benches/mnt6_753.rs" harness = false + +[[bench]] +name = "smallfp_comparison" +path = "benches/smallfp_comparison.rs" +harness = false diff --git a/ff/benches/field_comparison.rs b/test-curves/benches/smallfp_comparison.rs similarity index 83% rename from ff/benches/field_comparison.rs rename to test-curves/benches/smallfp_comparison.rs index c694684b5..072039023 100644 --- a/ff/benches/field_comparison.rs +++ b/test-curves/benches/smallfp_comparison.rs @@ -1,41 +1,11 @@ -use ark_ff::ark_ff_macros::SmallFpConfig; use ark_ff::fields::{Fp128, Fp64, MontBackend, MontConfig}; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; use ark_std::hint::black_box; +use ark_test_curves::smallfp128::SmallF128; +use ark_test_curves::smallfp64::{SmallF64, SmallF64Mont}; use criterion::{criterion_group, criterion_main, Criterion}; use p3_field::AbstractField; use p3_goldilocks::Goldilocks as P3Goldilocks; -// Goldilock's prime from Plonk3 18446744069414584321; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] -#[generator = "2"] -#[backend = "standard"] -pub struct SmallF64Config; -pub type SmallF64 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] -#[generator = "2"] -#[backend = "montgomery"] -pub struct SmallF64ConfigMont; -pub type SmallF64Mont = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] -#[generator = "2"] -#[backend = "standard"] -pub struct SmallF128Config; -pub type SmallF128 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "2"] -#[backend = "montgomery"] -pub struct SmallF128ConfigMont; -pub type SmallF128Mont = SmallFp; - #[derive(MontConfig)] #[modulus = "18446744069414584321"] #[generator = "2"] @@ -48,10 +18,7 @@ pub type F64 = Fp64>; pub struct F128Config; pub type F128 = Fp128>; -// Benchmark functions - fn naive_element_wise_mult_ark_bigint_64(c: &mut Criterion) { - // let's get four vectors and multiply them element-wise let len = 2_i64.pow(22); let v1: Vec = (0..len).map(F64::from).collect(); let v2: Vec = (0..len).map(F64::from).collect(); @@ -73,7 +40,6 @@ fn naive_element_wise_mult_ark_bigint_64(c: &mut Criterion) { } fn naive_element_wise_mult_ark_small_field_64_std(c: &mut Criterion) { - // let's get four vectors and multiply them element-wise let len = 2_i64.pow(22); let v1: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); let v2: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); @@ -95,7 +61,6 @@ fn naive_element_wise_mult_ark_small_field_64_std(c: &mut Criterion) { } fn naive_element_wise_mult_ark_small_field_64_mont(c: &mut Criterion) { - // let's get four vectors and multiply them element-wise let len = 2_i64.pow(22); let v1: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); let v2: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); @@ -117,7 +82,6 @@ fn naive_element_wise_mult_ark_small_field_64_mont(c: &mut Criterion) { } fn naive_element_wise_mult_p3_64(c: &mut Criterion) { - // let's get four vectors and multiply them element-wise let len = 2_i64.pow(22); let v1: Vec = (0..len) .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) @@ -148,7 +112,6 @@ fn naive_element_wise_mult_p3_64(c: &mut Criterion) { } fn naive_element_wise_mult_ark_bigint_128(c: &mut Criterion) { - // let's get four vectors and multiply them element-wise let len = 2_i64.pow(22); let v1: Vec = (0..len).map(F128::from).collect(); let v2: Vec = (0..len).map(F128::from).collect(); diff --git a/test-curves/src/lib.rs b/test-curves/src/lib.rs index 0ebe128f5..15abae16d 100644 --- a/test-curves/src/lib.rs +++ b/test-curves/src/lib.rs @@ -31,3 +31,9 @@ pub mod bn384_small_two_adicity; pub mod secp256k1; pub mod fp128; + +pub mod smallfp128; +pub mod smallfp16; +pub mod smallfp32; +pub mod smallfp64; +pub mod smallfp8; diff --git a/test-curves/src/smallfp128.rs b/test-curves/src/smallfp128.rs new file mode 100644 index 000000000..0ffaf9d43 --- /dev/null +++ b/test-curves/src/smallfp128.rs @@ -0,0 +1,26 @@ +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; + +#[derive(SmallFpConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "3"] +#[backend = "standard"] +pub struct SmallF128Config; +pub type SmallF128 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "3"] +#[backend = "montgomery"] +pub struct SmallF128ConfigMont; +pub type SmallF128Mont = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f128; SmallF128); + test_small_field!(f128_mont; SmallF128Mont); +} diff --git a/test-curves/src/smallfp16.rs b/test-curves/src/smallfp16.rs new file mode 100644 index 000000000..e0813e397 --- /dev/null +++ b/test-curves/src/smallfp16.rs @@ -0,0 +1,26 @@ +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; + +#[derive(SmallFpConfig)] +#[modulus = "65521"] +#[generator = "17"] +#[backend = "standard"] +pub struct SmallF16Config; +pub type SmallF16 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "65521"] +#[generator = "17"] +#[backend = "montgomery"] +pub struct SmallF16ConfigMont; +pub type SmallF16Mont = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f16; SmallF16); + test_small_field!(f16_mont; SmallF16Mont); +} diff --git a/test-curves/src/smallfp32.rs b/test-curves/src/smallfp32.rs new file mode 100644 index 000000000..38ce45e63 --- /dev/null +++ b/test-curves/src/smallfp32.rs @@ -0,0 +1,26 @@ +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; + +#[derive(SmallFpConfig)] +#[modulus = "2147483647"] // m31 +#[generator = "7"] +#[backend = "standard"] +pub struct SmallField; +pub type SmallF32 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "2147483647"] // m31 +#[generator = "7"] +#[backend = "montgomery"] +pub struct SmallFieldMont; +pub type SmallF32Mont = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f32; SmallF32); + test_small_field!(f32_mont; SmallF32Mont); +} diff --git a/test-curves/src/smallfp64.rs b/test-curves/src/smallfp64.rs new file mode 100644 index 000000000..1e85e7fff --- /dev/null +++ b/test-curves/src/smallfp64.rs @@ -0,0 +1,26 @@ +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 +#[generator = "7"] +#[backend = "standard"] +pub struct SmallF64Config; +pub type SmallF64 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 +#[generator = "7"] +#[backend = "montgomery"] +pub struct SmallF64ConfigMont; +pub type SmallF64Mont = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f64; SmallF64); + test_small_field!(f64_mont; SmallF64Mont); +} diff --git a/test-curves/src/smallfp8.rs b/test-curves/src/smallfp8.rs new file mode 100644 index 000000000..f8a4ed64a --- /dev/null +++ b/test-curves/src/smallfp8.rs @@ -0,0 +1,26 @@ +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; + +#[derive(SmallFpConfig)] +#[modulus = "251"] +#[generator = "6"] +#[backend = "standard"] +pub struct SmallF8Config; +pub type SmallF8 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "251"] +#[generator = "6"] +#[backend = "montgomery"] +pub struct SmallF8ConfigMont; +pub type SmallF8Mont = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f8; SmallF8); + test_small_field!(f8_mont; SmallF8Mont); +} From b91c70467b627a8f722d97e730d51c65416a5879 Mon Sep 17 00:00:00 2001 From: benbencik Date: Wed, 8 Oct 2025 10:45:37 +0200 Subject: [PATCH 18/47] update doccomments --- ff-macros/src/small_fp/mod.rs | 2 ++ .../fields/models/small_fp/small_fp_backend.rs | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ff-macros/src/small_fp/mod.rs b/ff-macros/src/small_fp/mod.rs index 50205bde8..2d95ebf3f 100644 --- a/ff-macros/src/small_fp/mod.rs +++ b/ff-macros/src/small_fp/mod.rs @@ -4,6 +4,8 @@ mod utils; use quote::quote; +/// This function is called by the `#[derive(SmallFp)]` macro and generates +/// the implementation of the `SmallFpConfig` pub(crate) fn small_fp_config_helper( modulus: u128, generator: u128, diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 1a3c1dc5c..c27e56a68 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -9,8 +9,12 @@ use ark_std::{ use educe::Educe; use num_traits::Unsigned; -/// A trait that specifies the configuration of a prime field. -/// Also specifies how to perform arithmetic on field elements. +/// A trait that specifies the configuration of a prime field, including the +/// modulus, generator, and arithmetic implementation. +/// +/// This trait is intended to be implemented through the derive +/// macro, which allows specifying different backends for field arithmetic, +/// such as "standard" or "montgomery". pub trait SmallFpConfig: Send + Sync + 'static + Sized { type T: Copy + Default @@ -35,7 +39,8 @@ pub trait SmallFpConfig: Send + Sync + 'static + Sized { const MODULUS: Self::T; const MODULUS_128: u128; - // ! this is fixed temporarily the value can be 1 or 2 + // TODO: the value can be 1 or 2, it would be nice to have it generic. + /// Number of bigint libs used to represent the field elements. const NUM_BIG_INT_LIMBS: usize = 2; /// A multiplicative generator of the field. @@ -118,7 +123,10 @@ pub trait SmallFpConfig: Send + Sync + 'static + Sized { } /// Represents an element of the prime field F_p, where `p == P::MODULUS`. -/// This type can represent elements in any field of size at most N * 64 bits. +/// +/// This type can represent elements in any field of size up to 128 bits. +/// The arithmetic implementation is determined by the `P: SmallFpConfig` +/// parameter, which can be configured to use different backends #[derive(Educe)] #[educe(Default, Hash, Clone, Copy, PartialEq, Eq)] pub struct SmallFp { From f2f3fb12359e0e810d389263cbcb21ba99ae3662 Mon Sep 17 00:00:00 2001 From: benbencik Date: Thu, 9 Oct 2025 10:24:18 +0200 Subject: [PATCH 19/47] use the provided bench templates for fields --- ff/Cargo.toml | 2 +- test-curves/Cargo.toml | 7 +- test-curves/benches/smallfp.rs | 35 +++++ test-curves/benches/smallfp_comparison.rs | 165 ---------------------- 4 files changed, 38 insertions(+), 171 deletions(-) create mode 100644 test-curves/benches/smallfp.rs delete mode 100644 test-curves/benches/smallfp_comparison.rs diff --git a/ff/Cargo.toml b/ff/Cargo.toml index 0432a9c78..2f39d29ad 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -48,6 +48,6 @@ criterion.workspace = true [features] default = [] -std = ["ark-std/std", "ark-serialize/std", "itertools/use_std"] +std = ["ark-std/std", "ark-serialize/std"] parallel = ["std", "rayon", "ark-std/parallel", "ark-serialize/parallel"] asm = [] diff --git a/test-curves/Cargo.toml b/test-curves/Cargo.toml index 52ac49d52..adf24b8db 100644 --- a/test-curves/Cargo.toml +++ b/test-curves/Cargo.toml @@ -27,9 +27,6 @@ ark-ec = { workspace = true, default-features = false } ark-serialize = { workspace = true, default-features = false } ark-algebra-test-templates = { workspace = true, default-features = false } ark-algebra-bench-templates = { workspace = true, default-features = false } -criterion.workspace = true -p3_field = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-field" } -p3_goldilocks = { git = "https://github.com/succinctlabs/Plonky3", package = "p3-goldilocks" } [features] default = [] @@ -83,6 +80,6 @@ path = "benches/mnt6_753.rs" harness = false [[bench]] -name = "smallfp_comparison" -path = "benches/smallfp_comparison.rs" +name = "smallfp" +path = "benches/smallfp.rs" harness = false diff --git a/test-curves/benches/smallfp.rs b/test-curves/benches/smallfp.rs new file mode 100644 index 000000000..baaf530a3 --- /dev/null +++ b/test-curves/benches/smallfp.rs @@ -0,0 +1,35 @@ +use ark_algebra_bench_templates::*; +use ark_ff::fields::{Fp64, MontBackend, MontConfig}; +use ark_test_curves::{ + smallfp32::{SmallF32, SmallF32Mont}, + smallfp64::{SmallF64, SmallF64Mont}, +}; + +#[derive(MontConfig)] +#[modulus = "18446744069414584321"] +#[generator = "7"] +pub struct F64Config; +pub type F64 = Fp64>; + +#[derive(MontConfig)] +#[modulus = "2147483647"] +#[generator = "3"] +pub struct F32Config; +pub type F32 = Fp64>; + +f_bench!(prime, "F32", F32); +f_bench!(prime, "SmallF32", SmallF32); +f_bench!(prime, "SmallF32Mont", SmallF32Mont); + +f_bench!(prime, "F64", F64); +f_bench!(prime, "SmallF64", SmallF64); +f_bench!(prime, "SmallF64Mont", SmallF64Mont); + +criterion_main!( + f32::benches, + smallf32::benches, + smallf32mont::benches, + f64::benches, + smallf64::benches, + smallf64mont::benches, +); diff --git a/test-curves/benches/smallfp_comparison.rs b/test-curves/benches/smallfp_comparison.rs deleted file mode 100644 index 072039023..000000000 --- a/test-curves/benches/smallfp_comparison.rs +++ /dev/null @@ -1,165 +0,0 @@ -use ark_ff::fields::{Fp128, Fp64, MontBackend, MontConfig}; -use ark_std::hint::black_box; -use ark_test_curves::smallfp128::SmallF128; -use ark_test_curves::smallfp64::{SmallF64, SmallF64Mont}; -use criterion::{criterion_group, criterion_main, Criterion}; -use p3_field::AbstractField; -use p3_goldilocks::Goldilocks as P3Goldilocks; - -#[derive(MontConfig)] -#[modulus = "18446744069414584321"] -#[generator = "2"] -pub struct F64Config; -pub type F64 = Fp64>; - -#[derive(MontConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "2"] -pub struct F128Config; -pub type F128 = Fp128>; - -fn naive_element_wise_mult_ark_bigint_64(c: &mut Criterion) { - let len = 2_i64.pow(22); - let v1: Vec = (0..len).map(F64::from).collect(); - let v2: Vec = (0..len).map(F64::from).collect(); - let v3: Vec = (0..len).map(F64::from).collect(); - let v4: Vec = (0..len).map(F64::from).collect(); - let mut element_wise_product: Vec = vec![F64::from(0); len as usize]; - - c.bench_function("naive_element_wise_mult_ark_bigint_64", |b| { - b.iter(|| { - for i in 0..len as usize { - element_wise_product[i] = v1.get(i).unwrap() - * v2.get(i).unwrap() - * v3.get(i).unwrap() - * v4.get(i).unwrap(); - } - black_box(element_wise_product.clone()); - }) - }); -} - -fn naive_element_wise_mult_ark_small_field_64_std(c: &mut Criterion) { - let len = 2_i64.pow(22); - let v1: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); - let v2: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); - let v3: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); - let v4: Vec = (0..len).map(|i| SmallF64::from(i as u32)).collect(); - let mut element_wise_product: Vec = vec![SmallF64::from(0u32); len as usize]; - - c.bench_function("naive_element_wise_mult_ark_small_field_64_std", |b| { - b.iter(|| { - for i in 0..len as usize { - element_wise_product[i] = v1.get(i).unwrap() - * v2.get(i).unwrap() - * v3.get(i).unwrap() - * v4.get(i).unwrap(); - } - black_box(&element_wise_product); - }) - }); -} - -fn naive_element_wise_mult_ark_small_field_64_mont(c: &mut Criterion) { - let len = 2_i64.pow(22); - let v1: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); - let v2: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); - let v3: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); - let v4: Vec = (0..len).map(|i| SmallF64Mont::from(i as u32)).collect(); - let mut element_wise_product: Vec = vec![SmallF64Mont::from(0u32); len as usize]; - - c.bench_function("naive_element_wise_mult_ark_small_field_64_mont", |b| { - b.iter(|| { - for i in 0..len as usize { - element_wise_product[i] = v1.get(i).unwrap() - * v2.get(i).unwrap() - * v3.get(i).unwrap() - * v4.get(i).unwrap(); - } - black_box(&element_wise_product); - }) - }); -} - -fn naive_element_wise_mult_p3_64(c: &mut Criterion) { - let len = 2_i64.pow(22); - let v1: Vec = (0..len) - .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) - .collect(); - let v2: Vec = (0..len) - .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) - .collect(); - let v3: Vec = (0..len) - .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) - .collect(); - let v4: Vec = (0..len) - .map(|i| P3Goldilocks::from_canonical_u64(i as u64)) - .collect(); - let mut element_wise_product: Vec = - vec![P3Goldilocks::from_canonical_u64(0_u64); len as usize]; - - c.bench_function("naive_element_wise_mult_p3_64", |b| { - b.iter(|| { - for i in 0..len as usize { - element_wise_product[i] = *v1.get(i).unwrap() - * *v2.get(i).unwrap() - * *v3.get(i).unwrap() - * *v4.get(i).unwrap(); - } - black_box(element_wise_product.clone()); - }) - }); -} - -fn naive_element_wise_mult_ark_bigint_128(c: &mut Criterion) { - let len = 2_i64.pow(22); - let v1: Vec = (0..len).map(F128::from).collect(); - let v2: Vec = (0..len).map(F128::from).collect(); - let v3: Vec = (0..len).map(F128::from).collect(); - let v4: Vec = (0..len).map(F128::from).collect(); - let mut element_wise_product: Vec = vec![F128::from(0); len as usize]; - - c.bench_function("naive_element_wise_mult_ark_bigint_128", |b| { - b.iter(|| { - for i in 0..len as usize { - element_wise_product[i] = v1.get(i).unwrap() - * v2.get(i).unwrap() - * v3.get(i).unwrap() - * v4.get(i).unwrap(); - } - black_box(element_wise_product.clone()); - }) - }); -} - -fn naive_element_wise_mult_ark_small_field_128(c: &mut Criterion) { - let len = 2_i64.pow(22); - let v1: Vec = (0..len).map(SmallF128::from).collect(); - let v2: Vec = (0..len).map(SmallF128::from).collect(); - let v3: Vec = (0..len).map(SmallF128::from).collect(); - let v4: Vec = (0..len).map(SmallF128::from).collect(); - let mut element_wise_product: Vec = vec![SmallF128::from(0); len as usize]; - - c.bench_function("naive_element_wise_mult_ark_small_field", |b| { - b.iter(|| { - for i in 0..len as usize { - element_wise_product[i] = v1.get(i).unwrap() - * v2.get(i).unwrap() - * v3.get(i).unwrap() - * v4.get(i).unwrap(); - } - black_box(element_wise_product.clone()); - }) - }); -} - -criterion_group!( - benches, - naive_element_wise_mult_ark_bigint_64, - naive_element_wise_mult_ark_small_field_64_std, - naive_element_wise_mult_ark_small_field_64_mont, - naive_element_wise_mult_p3_64, - naive_element_wise_mult_ark_bigint_128, - naive_element_wise_mult_ark_small_field_128, -); -criterion_main!(benches); From 176fc97e38706589fae6baf04b969effb0e32bdc Mon Sep 17 00:00:00 2001 From: benbencik Date: Thu, 9 Oct 2025 10:54:18 +0200 Subject: [PATCH 20/47] add info about small fields to readme --- ff/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ff/README.md b/ff/README.md index ca81dc111..c80faf488 100644 --- a/ff/README.md +++ b/ff/README.md @@ -38,6 +38,34 @@ The above two models serve as abstractions for constructing the extension fields - [`Fp6_3over2`](https://github.com/arkworks-rs/algebra/blob/master/ff/src/fields/models/fp6_3over2.rs#L64) - Extension tower, similar to the above except that the towering order is reversed: it's a cubic extension on a quadratic extension field, i.e. `BaseField = Fp2`, but `BasePrimeField = Fp`. Only this latter one is exported by default as `Fp6`. - [`Fp12_2over3over2`](https://github.com/arkworks-rs/algebra/blob/master/ff/src/fields/models/fp12_2over3over2.rs#L66) - Extension tower: quadratic extension of `Fp6_3over2`, i.e. `BaseField = Fp6`. +## Instantiation + +You can instantiate fields in two ways: + +```rust +use ark_ff::ark_ff_macros::SmallFpConfig; +use ark_ff::fields::{Fp64, MontBackend, MontConfig}; +use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; + +// Standard (big integer) field +#[derive(MontConfig)] +#[modulus = "18446744069414584321"] +#[generator = "7"] +pub struct F64Config; +pub type F64 = Fp64>; + +// Small field (native integer backend): +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] +#[generator = "7"] +#[backend = "montgomery"] // or "standard" +pub struct SmallF64ConfigMont; +pub type SmallF64Mont = SmallFp; +``` + +The standard field implementation can represent arbitrarily large fields, while the small field implementation supports native integer types from `u8` to `u128` for faster arithmetic. The small field implementation requires that the modulus fits into u128. + + ## Usage There are two important traits when working with finite fields: [`Field`], From 0cad96a8e4f3979851a8a2c7e3df1358ac31c0a5 Mon Sep 17 00:00:00 2001 From: benbencik Date: Thu, 9 Oct 2025 11:22:17 +0200 Subject: [PATCH 21/47] add pending PR 1044 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e5eced54..10e382fb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - (`ark-poly`) Add fast polynomial division - (`ark-ec`) Improve GLV scalar multiplication performance by skipping leading zeroes. - (`ark-poly`) Make `SparsePolynomial.coeffs` field public +- [\#1044](https://github.com/arkworks-rs/algebra/pull/1044) Add implementation for small field with native integer types ### Breaking changes From 742bb394a1556745cad2234e2aac78a6817f3973 Mon Sep 17 00:00:00 2001 From: benbencik Date: Thu, 9 Oct 2025 11:44:59 +0200 Subject: [PATCH 22/47] fix markdown linter error --- ff/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/ff/README.md b/ff/README.md index c80faf488..c840f08e3 100644 --- a/ff/README.md +++ b/ff/README.md @@ -65,7 +65,6 @@ pub type SmallF64Mont = SmallFp; The standard field implementation can represent arbitrarily large fields, while the small field implementation supports native integer types from `u8` to `u128` for faster arithmetic. The small field implementation requires that the modulus fits into u128. - ## Usage There are two important traits when working with finite fields: [`Field`], From 4ccddcebbe214b16c0a8a2fe966e8aaa7ec1768e Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 10 Oct 2025 15:55:18 +0200 Subject: [PATCH 23/47] add mont multiplication fastpath --- ff-macros/src/small_fp/montgomery_backend.rs | 115 +++++++++++-------- 1 file changed, 68 insertions(+), 47 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index e0c192274..cb1441877 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -71,52 +71,72 @@ pub(crate) fn backend_impl( fn mul_assign(a: &mut SmallFp, b: &SmallFp) { let a_u128 = a.value as u128; let b_u128 = b.value as u128; + let mut u; - let a_lo = a_u128 as u64; - let a_hi = (a_u128 >> 64) as u64; - let b_lo = b_u128 as u64; - let b_hi = (b_u128 >> 64) as u64; - - // t = a * b (256-bit result stored as t_lo, t_hi) - let lolo = (a_lo as u128) * (b_lo as u128); - let lohi = (a_lo as u128) * (b_hi as u128); - let hilo = (a_hi as u128) * (b_lo as u128); - let hihi = (a_hi as u128) * (b_hi as u128); - - let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); - let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); - let t_lo = mid; - let t_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); - - // m = t_lo * n_prime & r_mask - let m = t_lo.wrapping_mul(#n_prime) & #r_mask; - - // mn = m * modulus - let m_lo = m as u64; - let m_hi = (m >> 64) as u64; - let n_lo = #modulus as u64; - let n_hi = (#modulus >> 64) as u64; - - let lolo = (m_lo as u128) * (n_lo as u128); - let lohi = (m_lo as u128) * (n_hi as u128); - let hilo = (m_hi as u128) * (n_lo as u128); - let hihi = (m_hi as u128) * (n_hi as u128); - - let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); - let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); - let mn_lo = mid; - let mn_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); - - // (t + mn) / R - let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); - let sum_hi = t_hi + mn_hi + (carry as u128); - - let mut u = if #k_bits < 128 { - (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)) - } else { - sum_hi >> (#k_bits - 128) - }; + if #modulus < (1u128 << 64) { + let t = a_u128 * b_u128; + let t_low = t & #r_mask; + + // m = t_lo * n_prime & r_mask + let m = t_low.wrapping_mul(#n_prime) & (#r_mask); + + // mn = m * modulus + let mn = m * #modulus; + // (t + mn) / R + let (sum, overflow) = t.overflowing_add(mn); + u = sum >> #k_bits; + if overflow { + u += 1u128 << (128 - #k_bits); + } + } + else { + let a_lo = a_u128 as u64; + let a_hi = (a_u128 >> 64) as u64; + let b_lo = b_u128 as u64; + let b_hi = (b_u128 >> 64) as u64; + + // t = a * b (256-bit result stored as t_lo, t_hi) + let lolo = (a_lo as u128) * (b_lo as u128); + let lohi = (a_lo as u128) * (b_hi as u128); + let hilo = (a_hi as u128) * (b_lo as u128); + let hihi = (a_hi as u128) * (b_hi as u128); + + let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); + let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); + let t_lo = mid; + let t_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); + + // m = t_lo * n_prime & r_mask + let m = t_lo.wrapping_mul(#n_prime) & #r_mask; + + // mn = m * modulus + let m_lo = m as u64; + let m_hi = (m >> 64) as u64; + let n_lo = #modulus as u64; + let n_hi = (#modulus >> 64) as u64; + + let lolo = (m_lo as u128) * (n_lo as u128); + let lohi = (m_lo as u128) * (n_hi as u128); + let hilo = (m_hi as u128) * (n_lo as u128); + let hihi = (m_hi as u128) * (n_hi as u128); + + let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); + let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); + let mn_lo = mid; + let mn_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); + + // (t + mn) / R + let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); + let sum_hi = t_hi + mn_hi + (carry as u128); + + u = if #k_bits < 128 { + (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)) + } else { + sum_hi >> (#k_bits - 128) + }; + + } if u >= #modulus { u -= #modulus; } @@ -169,12 +189,13 @@ pub(crate) fn backend_impl( } } -fn mod_inverse_pow2(n: u128, bits: u32) -> u128 { +fn mod_inverse_pow2(n: u128, k_bits: u32) -> u128 { let mut inv = 1u128; - for _ in 0..bits { + for _ in 0..k_bits { inv = inv.wrapping_mul(2u128.wrapping_sub(n.wrapping_mul(inv))); } - inv.wrapping_neg() + let mask = (1u128 << k_bits) - 1; + inv.wrapping_neg() & mask } pub(crate) fn new(modulus: u128, _ty: proc_macro2::TokenStream) -> proc_macro2::TokenStream { From 42d99e990ab9a103fc844a24c228896fb666873a Mon Sep 17 00:00:00 2001 From: benbencik Date: Sun, 12 Oct 2025 14:05:01 +0200 Subject: [PATCH 24/47] clean unused type --- ff-macros/src/small_fp/mod.rs | 27 ++++++-------------- ff-macros/src/small_fp/montgomery_backend.rs | 2 +- ff-macros/src/small_fp/standard_backend.rs | 2 +- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/ff-macros/src/small_fp/mod.rs b/ff-macros/src/small_fp/mod.rs index 2d95ebf3f..fff157add 100644 --- a/ff-macros/src/small_fp/mod.rs +++ b/ff-macros/src/small_fp/mod.rs @@ -12,28 +12,17 @@ pub(crate) fn small_fp_config_helper( backend: String, config_name: proc_macro2::Ident, ) -> proc_macro2::TokenStream { - let (ty, _suffix) = { - let u8_max = u128::from(u8::MAX); - let u16_max = u128::from(u16::MAX); - let u32_max = u128::from(u32::MAX); - let u64_max = u128::from(u64::MAX); - - if modulus <= u8_max { - (quote! { u8 }, "u8") - } else if modulus <= u16_max { - (quote! { u16 }, "u16") - } else if modulus <= u32_max { - (quote! { u32 }, "u32") - } else if modulus <= u64_max { - (quote! { u64 }, "u64") - } else { - (quote! { u128 }, "u128") - } + let ty = match modulus { + m if m < 1u128 << 8 => quote! { u8 }, + m if m < 1u128 << 16 => quote! { u16 }, + m if m < 1u128 << 32 => quote! { u32 }, + m if m < 1u128 << 64 => quote! { u64 }, + _ => quote! { u128 }, }; let backend_impl = match backend.as_str() { - "standard" => standard_backend::backend_impl(ty.clone(), modulus, generator), - "montgomery" => montgomery_backend::backend_impl(ty.clone(), modulus, generator), + "standard" => standard_backend::backend_impl(&ty, modulus, generator), + "montgomery" => montgomery_backend::backend_impl(&ty, modulus, generator), _ => panic!("Unknown backend type: {}", backend), }; diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index cb1441877..b44e1212f 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -5,7 +5,7 @@ use crate::small_fp::utils::{ }; pub(crate) fn backend_impl( - ty: proc_macro2::TokenStream, + ty: &proc_macro2::TokenStream, modulus: u128, generator: u128, ) -> proc_macro2::TokenStream { diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs index 08c1b4f70..23b571ae7 100644 --- a/ff-macros/src/small_fp/standard_backend.rs +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -5,7 +5,7 @@ use crate::small_fp::utils::{ }; pub(crate) fn backend_impl( - ty: proc_macro2::TokenStream, + ty: &proc_macro2::TokenStream, modulus: u128, generator: u128, ) -> proc_macro2::TokenStream { From 69768230ed3a86a16a208c15f3cb2aa4a6d2ab6e Mon Sep 17 00:00:00 2001 From: benbencik Date: Sun, 12 Oct 2025 16:52:41 +0200 Subject: [PATCH 25/47] specify mont mul impl at compile time --- ff-macros/src/small_fp/montgomery_backend.rs | 175 +++++++++++-------- 1 file changed, 101 insertions(+), 74 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index b44e1212f..a2f69df63 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -1,3 +1,5 @@ +use std::u32; + use super::*; use crate::small_fp::utils::{ compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, @@ -28,6 +30,9 @@ pub(crate) fn backend_impl( generate_montgomery_bigint_casts(modulus, k_bits, r_mod_n); let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity); + // Generate multiplication implementation based on type + let mul_impl = generate_mul_impl(ty, modulus, k_bits, r_mask, n_prime); + quote! { type T = #ty; const MODULUS: Self::T = #modulus as Self::T; @@ -68,80 +73,7 @@ pub(crate) fn backend_impl( } } - fn mul_assign(a: &mut SmallFp, b: &SmallFp) { - let a_u128 = a.value as u128; - let b_u128 = b.value as u128; - let mut u; - - if #modulus < (1u128 << 64) { - let t = a_u128 * b_u128; - let t_low = t & #r_mask; - - // m = t_lo * n_prime & r_mask - let m = t_low.wrapping_mul(#n_prime) & (#r_mask); - - // mn = m * modulus - let mn = m * #modulus; - - // (t + mn) / R - let (sum, overflow) = t.overflowing_add(mn); - u = sum >> #k_bits; - if overflow { - u += 1u128 << (128 - #k_bits); - } - } - else { - let a_lo = a_u128 as u64; - let a_hi = (a_u128 >> 64) as u64; - let b_lo = b_u128 as u64; - let b_hi = (b_u128 >> 64) as u64; - - // t = a * b (256-bit result stored as t_lo, t_hi) - let lolo = (a_lo as u128) * (b_lo as u128); - let lohi = (a_lo as u128) * (b_hi as u128); - let hilo = (a_hi as u128) * (b_lo as u128); - let hihi = (a_hi as u128) * (b_hi as u128); - - let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); - let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); - let t_lo = mid; - let t_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); - - // m = t_lo * n_prime & r_mask - let m = t_lo.wrapping_mul(#n_prime) & #r_mask; - - // mn = m * modulus - let m_lo = m as u64; - let m_hi = (m >> 64) as u64; - let n_lo = #modulus as u64; - let n_hi = (#modulus >> 64) as u64; - - let lolo = (m_lo as u128) * (n_lo as u128); - let lohi = (m_lo as u128) * (n_hi as u128); - let hilo = (m_hi as u128) * (n_lo as u128); - let hihi = (m_hi as u128) * (n_hi as u128); - - let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); - let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); - let mn_lo = mid; - let mn_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); - - // (t + mn) / R - let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); - let sum_hi = t_hi + mn_hi + (carry as u128); - - u = if #k_bits < 128 { - (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)) - } else { - sum_hi >> (#k_bits - 128) - }; - - } - if u >= #modulus { - u -= #modulus; - } - a.value = u as Self::T; - } + #mul_impl fn sum_of_products( a: &[SmallFp; T], @@ -189,6 +121,101 @@ pub(crate) fn backend_impl( } } +fn generate_mul_impl( + ty: &proc_macro2::TokenStream, + modulus: u128, + k_bits: u32, + r_mask: u128, + n_prime: u128, +) -> proc_macro2::TokenStream { + let ty_str = ty.to_string(); + + if ty_str == "u128" { + quote! { + #[inline(always)] + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + // 256-bit result stored as lo, hi + // t = a * b + let lolo = (a.value & 0xFFFFFFFFFFFFFFFF) * (b.value & 0xFFFFFFFFFFFFFFFF); + let lohi = (a.value & 0xFFFFFFFFFFFFFFFF) * (b.value >> 64); + let hilo = (a.value >> 64) * (b.value & 0xFFFFFFFFFFFFFFFF); + let hihi = (a.value >> 64) * (b.value >> 64); + + let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); + let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); + let t_lo = mid; + let t_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); + + // m = t_lo * n_prime & r_mask + let m = t_lo.wrapping_mul(#n_prime) & #r_mask; + + // mn = m * modulus + let lolo = (m & 0xFFFFFFFFFFFFFFFF) * (#modulus & 0xFFFFFFFFFFFFFFFF); + let lohi = (m & 0xFFFFFFFFFFFFFFFF) * (#modulus >> 64); + let hilo = (m >> 64) * (#modulus & 0xFFFFFFFFFFFFFFFF); + let hihi = (m >> 64) * (#modulus >> 64); + + let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); + let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); + let mn_lo = mid; + let mn_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); + + // (t + mn) / R + let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); + let sum_hi = t_hi + mn_hi + (carry as u128); + + let u = if #k_bits < 128 { + (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)) + } else { + sum_hi >> (#k_bits - 128) + }; + + let u = if u >= #modulus { u - #modulus } else { u }; + a.value = u as Self::T; + } + } + } else { + let (mul_ty, bits) = match ty_str.as_str() { + "u8" => (quote! {u16}, 16u32), + "u16" => (quote! {u32}, 32u32), + "u32" => (quote! {u64}, 64u32), + _ => (quote! {u128}, 128u32), + }; + + let r_mask_downcast = quote! { #r_mask as #mul_ty }; + let n_prime_downcast = quote! { #n_prime as #mul_ty }; + let modulus_downcast = quote! { #modulus as #mul_ty }; + let one = quote! { 1 as #mul_ty }; + + quote! { + #[inline(always)] + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + let a_val = a.value as #mul_ty; + let b_val = b.value as #mul_ty; + + let t = a_val * b_val; + let t_low = t & #r_mask_downcast; + + // m = t_lo * n_prime & r_mask + let m = t_low.wrapping_mul(#n_prime_downcast) & #r_mask_downcast; + + // mn = m * modulus + let mn = m * #modulus_downcast; + + // (t + mn) / R + let (sum, overflow) = t.overflowing_add(mn); + let mut u = sum >> #k_bits; + if overflow { + u += (#one) << (#bits - #k_bits); + } + + let u = if u >= #modulus_downcast { u - #modulus_downcast } else { u }; + a.value = u as Self::T; + } + } + } +} + fn mod_inverse_pow2(n: u128, k_bits: u32) -> u128 { let mut inv = 1u128; for _ in 0..k_bits { From 7a7f18b9152fbc632cb547878806e0454d72ddbc Mon Sep 17 00:00:00 2001 From: benbencik Date: Sun, 12 Oct 2025 17:55:04 +0200 Subject: [PATCH 26/47] add inlinging for arithmetic ops --- ff-macros/src/small_fp/montgomery_backend.rs | 8 +++++++- ff-macros/src/small_fp/standard_backend.rs | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index a2f69df63..971cf37af 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -47,6 +47,7 @@ pub(crate) fn backend_impl( const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::new(#two_adic_root_mont as Self::T); #sqrt_precomp_impl + #[inline(always)] fn add_assign(a: &mut SmallFp, b: &SmallFp) { a.value = match a.value.overflowing_add(b.value) { (val, false) => val % Self::MODULUS, @@ -54,6 +55,7 @@ pub(crate) fn backend_impl( }; } + #[inline(always)] fn sub_assign(a: &mut SmallFp, b: &SmallFp) { if a.value >= b.value { a.value -= b.value; @@ -62,11 +64,13 @@ pub(crate) fn backend_impl( } } + #[inline(always)] fn double_in_place(a: &mut SmallFp) { let tmp = *a; Self::add_assign(a, &tmp); } + #[inline(always)] fn neg_in_place(a: &mut SmallFp) { if a.value != (0 as Self::T) { a.value = Self::MODULUS - a.value; @@ -75,6 +79,7 @@ pub(crate) fn backend_impl( #mul_impl + #[inline(always)] fn sum_of_products( a: &[SmallFp; T], b: &[SmallFp; T],) -> SmallFp { @@ -87,6 +92,7 @@ pub(crate) fn backend_impl( acc } + #[inline(always)] fn square_in_place(a: &mut SmallFp) { let tmp = *a; Self::mul_assign(a, &tmp); @@ -107,7 +113,7 @@ pub(crate) fn backend_impl( } let mut sq = base; - Self::mul_assign(&mut sq, &base); + Self::square_in_place(&mut sq); base = sq; exp >>= 1; } diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs index 23b571ae7..6b406e84b 100644 --- a/ff-macros/src/small_fp/standard_backend.rs +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -28,6 +28,7 @@ pub(crate) fn backend_impl( const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::new(#two_adic_root_of_unity as Self::T); #sqrt_precomp_impl + #[inline(always)] fn add_assign(a: &mut SmallFp, b: &SmallFp) { a.value = match a.value.overflowing_add(b.value) { (val, false) => val % Self::MODULUS, @@ -35,6 +36,7 @@ pub(crate) fn backend_impl( }; } + #[inline(always)] fn sub_assign(a: &mut SmallFp, b: &SmallFp) { if a.value >= b.value { a.value -= b.value; @@ -43,18 +45,20 @@ pub(crate) fn backend_impl( } } + #[inline(always)] fn double_in_place(a: &mut SmallFp) { - //* Note: This might be faster using bitshifts let tmp = *a; Self::add_assign(a, &tmp); } + #[inline(always)] fn neg_in_place(a: &mut SmallFp) { if a.value != (0 as Self::T) { a.value = Self::MODULUS - a.value; } } + #[inline(always)] fn mul_assign(a: &mut SmallFp, b: &SmallFp) { let a_128 = (a.value as u128) % #modulus; let b_128 = (b.value as u128) % #modulus; From f59fa7dc7e0764927e2a59b95b88cac5806a1bfb Mon Sep 17 00:00:00 2001 From: benbencik Date: Sun, 12 Oct 2025 21:06:26 +0200 Subject: [PATCH 27/47] replace modulo operation in addition --- ff-macros/src/small_fp/montgomery_backend.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 971cf37af..789d57195 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -49,10 +49,16 @@ pub(crate) fn backend_impl( #[inline(always)] fn add_assign(a: &mut SmallFp, b: &SmallFp) { - a.value = match a.value.overflowing_add(b.value) { - (val, false) => val % Self::MODULUS, - (val, true) => (Self::T::MAX - Self::MODULUS + 1 + val) % Self::MODULUS, - }; + let (mut val, overflow) = a.value.overflowing_add(b.value); + + if overflow { + val = Self::T::MAX - Self::MODULUS + 1 + val + } + + if val >= Self::MODULUS { + val -= Self::MODULUS; + } + a.value = val; } #[inline(always)] From d6632081da71dfede770a1cc6d211f5f931d2bda Mon Sep 17 00:00:00 2001 From: benbencik Date: Mon, 13 Oct 2025 23:31:05 +0200 Subject: [PATCH 28/47] remove branching from mont multiplicaiton --- ff-macros/src/small_fp/montgomery_backend.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 789d57195..1d7df4996 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -133,6 +133,9 @@ pub(crate) fn backend_impl( } } +// Selects the appropriate multiplication algorithm at compile time: +// if modulus <= u64, multiply by casting to the next largest primitive +// otherwise, multiply in parts to form a 256-bit product before reduction fn generate_mul_impl( ty: &proc_macro2::TokenStream, modulus: u128, @@ -176,13 +179,8 @@ fn generate_mul_impl( let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); let sum_hi = t_hi + mn_hi + (carry as u128); - let u = if #k_bits < 128 { - (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)) - } else { - sum_hi >> (#k_bits - 128) - }; - - let u = if u >= #modulus { u - #modulus } else { u }; + let mut u = (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)); + u -= #modulus * (u >= #modulus) as u128; a.value = u as Self::T; } } @@ -217,11 +215,9 @@ fn generate_mul_impl( // (t + mn) / R let (sum, overflow) = t.overflowing_add(mn); let mut u = sum >> #k_bits; - if overflow { - u += (#one) << (#bits - #k_bits); - } - let u = if u >= #modulus_downcast { u - #modulus_downcast } else { u }; + u += ((#one) << (#bits - #k_bits)) * (overflow as #mul_ty); + u -= #modulus_downcast * ((u >= #modulus_downcast) as #mul_ty); a.value = u as Self::T; } } From 8acdef2e19f37fd8101b08a5abdae002308eaf79 Mon Sep 17 00:00:00 2001 From: benbencik Date: Tue, 14 Oct 2025 17:41:38 +0200 Subject: [PATCH 29/47] update utils helper functions --- ff-macros/src/small_fp/montgomery_backend.rs | 4 +- ff-macros/src/small_fp/standard_backend.rs | 4 +- ff-macros/src/small_fp/utils.rs | 56 +++++++++----------- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 1d7df4996..8b8815d78 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -21,14 +21,14 @@ pub(crate) fn backend_impl( let generator_mont = mod_mul_const(generator % modulus, r_mod_n % modulus, modulus); let two_adicity = compute_two_adicity(modulus); - let two_adic_root = compute_two_adic_root_of_unity(modulus, two_adicity); + let two_adic_root = compute_two_adic_root_of_unity(modulus, two_adicity, generator); let two_adic_root_mont = mod_mul_const(two_adic_root, r_mod_n, modulus); let neg_one_mont = mod_mul_const(modulus - 1, r_mod_n, modulus); let (from_bigint_impl, into_bigint_impl) = generate_montgomery_bigint_casts(modulus, k_bits, r_mod_n); - let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity); + let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity, Some(r_mod_n)); // Generate multiplication implementation based on type let mul_impl = generate_mul_impl(ty, modulus, k_bits, r_mask, n_prime); diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs index 6b406e84b..a51260806 100644 --- a/ff-macros/src/small_fp/standard_backend.rs +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -10,10 +10,10 @@ pub(crate) fn backend_impl( generator: u128, ) -> proc_macro2::TokenStream { let two_adicity = compute_two_adicity(modulus); - let two_adic_root_of_unity = compute_two_adic_root_of_unity(modulus, two_adicity); + let two_adic_root_of_unity = compute_two_adic_root_of_unity(modulus, two_adicity, generator); let (from_bigint_impl, into_bigint_impl) = generate_bigint_casts(modulus); - let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity); + let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity, None); quote! { type T = #ty; diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index 463cd81a8..39fe370f5 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -15,7 +15,7 @@ pub(crate) const fn compute_two_adicity(modulus: u128) -> u32 { two_adicity } -const fn mod_add(x: u128, y: u128, modulus: u128) -> u128 { +const fn mod_add_const(x: u128, y: u128, modulus: u128) -> u128 { if x >= modulus - y { x - (modulus - y) } else { @@ -33,9 +33,9 @@ pub(crate) const fn mod_mul_const(a: u128, b: u128, modulus: u128) -> u128 { while exp > 0 { if exp & 1 == 1 { - result = mod_add(result, base, modulus); + result = mod_add_const(result, base, modulus); } - base = mod_add(base, base, modulus); + base = mod_add_const(base, base, modulus); exp >>= 1; } result @@ -43,23 +43,7 @@ pub(crate) const fn mod_mul_const(a: u128, b: u128, modulus: u128) -> u128 { } } -pub(crate) const fn compute_two_adic_root_of_unity(modulus: u128, two_adicity: u32) -> u128 { - let qnr = find_quadratic_non_residue(modulus); - let mut exp = (modulus - 1) >> two_adicity; - let mut base = qnr % modulus; - let mut result = 1u128; - - while exp > 0 { - if exp & 1 == 1 { - result = mod_mul_const(result, base, modulus); - } - base = mod_mul_const(base, base, modulus); - exp /= 2; - } - result -} - -const fn pow_mod(mut base: u128, mut exp: u128, modulus: u128) -> u128 { +const fn pow_mod_const(mut base: u128, mut exp: u128, modulus: u128) -> u128 { let mut result = 1; base %= modulus; while exp > 0 { @@ -72,11 +56,22 @@ const fn pow_mod(mut base: u128, mut exp: u128, modulus: u128) -> u128 { result } +pub(crate) const fn compute_two_adic_root_of_unity( + modulus: u128, + two_adicity: u32, + generator: u128, +) -> u128 { + let exp = (modulus - 1) >> two_adicity; + let base = generator % modulus; + pow_mod_const(base, exp, modulus) +} + +// Finds smallest quadratic non-residue by using Euler's criterion pub(crate) const fn find_quadratic_non_residue(modulus: u128) -> u128 { let exponent = (modulus - 1) / 2; let mut z = 2; loop { - let legendre = pow_mod(z, exponent, modulus); + let legendre = pow_mod_const(z, exponent, modulus); if legendre == modulus - 1 { return z; } @@ -118,7 +113,6 @@ pub(crate) fn generate_montgomery_bigint_casts( let r2 = mod_mul_const(r_mod_n, r_mod_n, modulus); ( quote! { - //* Convert from standard representation to Montgomery space fn from_bigint(a: BigInt<2>) -> Option> { let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); if val > Self::MODULUS_128 { @@ -133,7 +127,6 @@ pub(crate) fn generate_montgomery_bigint_casts( } }, quote! { - //* Convert from Montgomery space to standard representation fn into_bigint(a: SmallFp) -> BigInt<2> { let mut tmp = a; let one = SmallFp::new(1 as Self::T); @@ -150,14 +143,15 @@ pub(crate) fn generate_montgomery_bigint_casts( pub(crate) fn generate_sqrt_precomputation( modulus: u128, two_adicity: u32, + r_mod_n: Option, ) -> proc_macro2::TokenStream { if modulus % 4 == 3 { - // Case3Mod4 let modulus_plus_one_div_four = (modulus + 1) / 4; let lo = modulus_plus_one_div_four as u64; let hi = (modulus_plus_one_div_four >> 64) as u64; quote! { + // Case3Mod4 square root precomputation const SQRT_PRECOMP: Option>> = { const MODULUS_PLUS_ONE_DIV_FOUR: [u64; 2] = [#lo, #hi]; Some(SqrtPrecomputation::Case3Mod4 { @@ -166,22 +160,24 @@ pub(crate) fn generate_sqrt_precomputation( }; } } else { - // TonelliShanks let trace = (modulus - 1) >> two_adicity; - // t is od integer division floors to (t-1)/2 let trace_minus_one_div_two = trace / 2; let lo = trace_minus_one_div_two as u64; let hi = (trace_minus_one_div_two >> 64) as u64; + let qnr = find_quadratic_non_residue(modulus); + let mut qnr_to_trace = pow_mod_const(qnr, trace, modulus); + + if r_mod_n.is_some() { + qnr_to_trace = mod_mul_const(qnr_to_trace, r_mod_n.unwrap(), modulus); + } quote! { + // TonelliShanks square root precomputation const SQRT_PRECOMP: Option>> = { const TRACE_MINUS_ONE_DIV_TWO: [u64; 2] = [#lo, #hi]; - // TWO_ADIC_ROOT_OF_UNITY = g^{(p-1)/2^s} = g^t with odd t - // ord(g^t) = 2^s, while any square has order at most 2^{s-1} - // TWO_ADIC_ROOT_OF_UNITY not a square Some(SqrtPrecomputation::TonelliShanks { two_adicity: #two_adicity, - quadratic_nonresidue_to_trace: Self::TWO_ADIC_ROOT_OF_UNITY, + quadratic_nonresidue_to_trace: SmallFp::new(#qnr_to_trace as Self::T), trace_of_modulus_minus_one_div_two: &TRACE_MINUS_ONE_DIV_TWO, }) }; From 391f0bafe9ec9f46ca727901ad0b8f13856db84f Mon Sep 17 00:00:00 2001 From: benbencik Date: Tue, 14 Oct 2025 17:56:20 +0200 Subject: [PATCH 30/47] specify supported moduli --- ff-macros/src/small_fp/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ff-macros/src/small_fp/mod.rs b/ff-macros/src/small_fp/mod.rs index fff157add..f23546867 100644 --- a/ff-macros/src/small_fp/mod.rs +++ b/ff-macros/src/small_fp/mod.rs @@ -22,7 +22,15 @@ pub(crate) fn small_fp_config_helper( let backend_impl = match backend.as_str() { "standard" => standard_backend::backend_impl(&ty, modulus, generator), - "montgomery" => montgomery_backend::backend_impl(&ty, modulus, generator), + "montgomery" => { + if modulus >= 1u128 << 127 { + panic!( + "SmallFpConfig montgomery backend supports only moduli < 2^127. Use MontConfig with BigInt instead of SmallFp." + ) + } + montgomery_backend::backend_impl(&ty, modulus, generator) + }, + _ => panic!("Unknown backend type: {}", backend), }; From 234bd8ff9671318421eebfbf7618a55d3281f0bb Mon Sep 17 00:00:00 2001 From: benbencik Date: Wed, 15 Oct 2025 10:30:41 +0200 Subject: [PATCH 31/47] reduce duplicity in tests --- test-curves/benches/smallfp.rs | 2 +- test-templates/src/fields.rs | 380 +-------------------------------- 2 files changed, 4 insertions(+), 378 deletions(-) diff --git a/test-curves/benches/smallfp.rs b/test-curves/benches/smallfp.rs index baaf530a3..75bb91563 100644 --- a/test-curves/benches/smallfp.rs +++ b/test-curves/benches/smallfp.rs @@ -13,7 +13,7 @@ pub type F64 = Fp64>; #[derive(MontConfig)] #[modulus = "2147483647"] -#[generator = "3"] +#[generator = "7"] pub struct F32Config; pub type F32 = Fp64>; diff --git a/test-templates/src/fields.rs b/test-templates/src/fields.rs index d5a8913b4..7003dcc06 100644 --- a/test-templates/src/fields.rs +++ b/test-templates/src/fields.rs @@ -591,384 +591,10 @@ macro_rules! test_field { #[doc(hidden)] macro_rules! __test_small_field { ($field: ty) => { - #[test] - pub fn test_frobenius() { - use ark_ff::Field; - use ark_std::UniformRand; - let mut rng = ark_std::test_rng(); - let characteristic = <$field>::characteristic(); - let max_power = (<$field>::extension_degree() + 1) as usize; - - for _ in 0..ITERATIONS { - let a = <$field>::rand(&mut rng); - - let mut a_0 = a; - a_0.frobenius_map_in_place(0); - assert_eq!(a, a_0); - assert_eq!(a, a.frobenius_map(0)); - - let mut a_q = a.pow(&characteristic); - for power in 1..max_power { - assert_eq!(a_q, a.frobenius_map(power)); - - let mut a_qi = a; - a_qi.frobenius_map_in_place(power); - assert_eq!(a_qi, a_q, "failed on power {}", power); - - a_q = a_q.pow(&characteristic); - } - } - } - - #[test] - fn test_serialization() { - use ark_serialize::*; - use ark_std::UniformRand; - for compress in [Compress::Yes, Compress::No] { - for validate in [Validate::Yes, Validate::No] { - let buf_size = <$field>::zero().serialized_size(compress); - - let buffer_size = - buffer_bit_byte_size(<$field as Field>::BasePrimeField::MODULUS_BIT_SIZE as usize).1 * - (<$field>::extension_degree() as usize); - assert_eq!(buffer_size, buf_size); - - let mut rng = ark_std::test_rng(); - - for _ in 0..ITERATIONS { - let a = <$field>::rand(&mut rng); - { - let mut serialized = vec![0u8; buf_size]; - let mut cursor = Cursor::new(&mut serialized[..]); - a.serialize_with_mode(&mut cursor, compress).unwrap(); - - let mut cursor = Cursor::new(&serialized[..]); - let b = <$field>::deserialize_with_mode(&mut cursor, compress, validate).unwrap(); - assert_eq!(a, b); - } - - { - let mut serialized = vec![0; buf_size]; - let result = matches!( - a.serialize_with_flags(&mut &mut serialized[..], $crate::fields::DummyFlags).unwrap_err(), - SerializationError::NotEnoughSpace - ); - assert!(result); - - let result = matches!( - <$field>::deserialize_with_flags::<_, $crate::fields::DummyFlags>(&mut &serialized[..]).unwrap_err(), - SerializationError::NotEnoughSpace, - ); - assert!(result); - - { - let mut serialized = vec![0; buf_size - 1]; - let mut cursor = Cursor::new(&mut serialized[..]); - a.serialize_with_mode(&mut cursor, compress).unwrap_err(); - - let mut cursor = Cursor::new(&serialized[..]); - <$field>::deserialize_with_mode(&mut cursor, compress, validate).unwrap_err(); - } - } - } - } - } - - } - - #[test] - fn test_add_properties() { - use ark_ff::AdditiveGroup; - use ark_std::UniformRand; - - let mut rng = test_rng(); - let zero = <$field>::zero(); - assert_eq!(-zero, zero); - assert!(zero.is_zero()); - assert!(<$field>::ZERO.is_zero()); - assert_eq!(<$field>::ZERO, zero); - - for _ in 0..(ITERATIONS * ITERATIONS) { - // Generate small values that are guaranteed to be within the modulus - // Using u16 range ensures compatibility with small fields like M31 - let a = <$field>::rand(&mut rng); - let b = <$field>::rand(&mut rng); - let c = <$field>::rand(&mut rng); - assert_eq!((a + b) + c, a + (b + c)); - - // Commutativity - assert_eq!(a + b, b + a); - - // Identity - assert_eq!(zero + a, a); - assert_eq!(zero + b, b); - assert_eq!(zero + c, c); - - // Negation - assert_eq!(-a + a, zero); - assert_eq!(-b + b, zero); - assert_eq!(-c + c, zero); - assert_eq!(-zero, zero); - - // Associativity and commutativity simultaneously - let t0 = (a + &b) + &c; // (a + b) + c - let t1 = (a + &c) + &b; // (a + c) + b - let t2 = (b + &c) + &a; // (b + c) + a - - assert_eq!(t0, t1); - assert_eq!(t1, t2); - - // Doubling - assert_eq!(a.double(), a + a); - assert_eq!(b.double(), b + b); - assert_eq!(c.double(), c + c); - } - } - - #[test] - fn test_sub_properties() { - use ark_std::UniformRand; - let mut rng = test_rng(); - let zero = <$field>::zero(); - - for _ in 0..(ITERATIONS * ITERATIONS) { - // Anti-commutativity - let a = <$field>::rand(&mut rng); - let b = <$field>::rand(&mut rng); - assert!(((a - b) + (b - a)).is_zero()); - - // Identity - assert_eq!(zero - a, -a); - assert_eq!(zero - b, -b); - - assert_eq!(a - zero, a); - assert_eq!(b - zero, b); - } - } - - #[test] - fn test_mul_properties() { - use ark_std::UniformRand; - let mut rng = test_rng(); - let zero = <$field>::zero(); - let one = <$field>::one(); - let minus_one = <$field>::NEG_ONE; - assert_eq!(one.inverse().unwrap(), one, "One inverse failed"); - assert!(one.is_one(), "One is not one"); - - assert!(<$field>::ONE.is_one(), "One constant is not one"); - assert_eq!(<$field>::ONE, one, "One constant is incorrect"); - assert_eq!(<$field>::NEG_ONE, -one, "NEG_ONE constant is incorrect"); - assert_eq!(<$field>::ONE + <$field>::NEG_ONE, zero, "1 + -1 neq 0"); - - for _ in 0..ITERATIONS { - // Associativity - let a = <$field>::rand(&mut rng); - let b = <$field>::rand(&mut rng); - let c = <$field>::rand(&mut rng); - assert_eq!((a * b) * c, a * (b * c), "Associativity failed"); - - // Commutativity - assert_eq!(a * b, b * a, "Commutativity failed"); - - // Identity - assert_eq!(one * a, a, "Identity mul failed"); - assert_eq!(one * b, b, "Identity mul failed"); - assert_eq!(one * c, c, "Identity mul failed"); - assert_eq!(minus_one * c, -c, "NEG_ONE mul failed"); - - assert_eq!(zero * a, zero, "Mul by zero failed"); - assert_eq!(zero * b, zero, "Mul by zero failed"); - assert_eq!(zero * c, zero, "Mul by zero failed"); - - // Inverses - assert_eq!(a * a.inverse().unwrap(), one, "Mul by inverse failed"); - assert_eq!(b * b.inverse().unwrap(), one, "Mul by inverse failed"); - assert_eq!(c * c.inverse().unwrap(), one, "Mul by inverse failed"); - - // Associativity and commutativity simultaneously - let t0 = (a * b) * c; - let t1 = (a * c) * b; - let t2 = (b * c) * a; - assert_eq!(t0, t1, "Associativity + commutativity failed"); - assert_eq!(t1, t2, "Associativity + commutativity failed"); - - // Squaring - assert_eq!(a * a, a.square(), "Squaring failed"); - assert_eq!(b * b, b.square(), "Squaring failed"); - assert_eq!(c * c, c.square(), "Squaring failed"); - - // Distributivity - assert_eq!(a * (b + c), a * b + a * c, "Distributivity failed"); - assert_eq!(b * (a + c), b * a + b * c, "Distributivity failed"); - assert_eq!(c * (a + b), c * a + c * b, "Distributivity failed"); - assert_eq!( - (a + b).square(), - a.square() + b.square() + a * ark_ff::AdditiveGroup::double(&b), - "Distributivity for square failed" - ); - assert_eq!( - (b + c).square(), - c.square() + b.square() + c * ark_ff::AdditiveGroup::double(&b), - "Distributivity for square failed" - ); - assert_eq!( - (c + a).square(), - a.square() + c.square() + a * ark_ff::AdditiveGroup::double(&c), - "Distributivity for square failed" - ); - } - } - - #[test] - fn test_pow() { - use ark_std::UniformRand; - let mut rng = test_rng(); - for _ in 0..(ITERATIONS / 10) { - for i in 0..20 { - // Exponentiate by various small numbers and ensure it is - // consistent with repeated multiplication. - let a = <$field>::rand(&mut rng); - let target = a.pow(&[i]); - let mut c = <$field>::one(); - for _ in 0..i { - c *= a; - } - assert_eq!(c, target); - } - let a = <$field>::rand(&mut rng); - - // Exponentiating by the modulus should have no effect; - let mut result = a; - for i in 0..<$field>::extension_degree() { - result = result.pow(<$field>::characteristic()) - } - assert_eq!(a, result); - - // Commutativity - let e1: [u64; 10] = rng.gen(); - let e2: [u64; 10] = rng.gen(); - assert_eq!(a.pow(&e1).pow(&e2), a.pow(&e2).pow(&e1)); - - // Distributivity - let e3: [u64; 10] = rng.gen(); - let a_to_e1 = a.pow(e1); - let a_to_e2 = a.pow(e2); - let a_to_e1_plus_e2 = a.pow(e1) * a.pow(e2); - assert_eq!( - a_to_e1_plus_e2.pow(&e3), - a_to_e1.pow(&e3) * a_to_e2.pow(&e3) - ); - } - } - - #[test] - fn test_sum_of_products_tests() { - use ark_std::{rand::Rng, UniformRand}; - let rng = &mut test_rng(); - - for _ in 0..ITERATIONS { - $crate::fields::sum_of_products_test_helper::<$field, 1>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 2>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 3>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 4>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 5>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 6>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 7>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 8>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 9>(rng); - $crate::fields::sum_of_products_test_helper::<$field, 10>(rng); - } - } - - #[test] - fn test_sqrt() { - if <$field>::SQRT_PRECOMP.is_some() { - use ark_std::UniformRand; - let rng = &mut test_rng(); - - assert!(<$field>::zero().sqrt().unwrap().is_zero()); - - for _ in 0..ITERATIONS { - // Ensure sqrt(a^2) = a or -a - let a = <$field>::rand(rng); - let b = a.square(); - let sqrt = b.sqrt().unwrap(); - assert!(a == sqrt || -a == sqrt); - - if let Some(mut b) = a.sqrt() { - b.square_in_place(); - assert_eq!(a, b); - } - - let a = <$field>::rand(rng); - let b = a.square(); - assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); - } - } - } - - #[test] - fn test_mul_by_base_field_elem() { - use ark_std::UniformRand; - let rng = &mut test_rng(); - - for _ in 0..ITERATIONS { - let a = vec![ - <<$field as Field>::BasePrimeField>::rand(rng); - <$field>::extension_degree() as usize - ]; - let b = <<$field as Field>::BasePrimeField>::rand(rng); - - let mut a = <$field>::from_base_prime_field_elems(a).unwrap(); - let computed = a.mul_by_base_prime_field(&b); - - let embedded_b = <$field as Field>::from_base_prime_field(b); - - let naive = a * embedded_b; - - assert_eq!(computed, naive); - } - } - #[test] - fn test_fft() { - use ark_ff::FftField; - use $crate::num_bigint::BigUint; - - let two_pow_2_adicity = BigUint::from(1_u8) << <$field>::TWO_ADICITY as u32; - assert_eq!( - <$field>::TWO_ADIC_ROOT_OF_UNITY.pow(two_pow_2_adicity.to_u64_digits()), - <$field>::one() - ); - - if let Some(small_subgroup_base) = <$field>::SMALL_SUBGROUP_BASE { - let small_subgroup_base_adicity = <$field>::SMALL_SUBGROUP_BASE_ADICITY.unwrap(); - let large_subgroup_root_of_unity = <$field>::LARGE_SUBGROUP_ROOT_OF_UNITY.unwrap(); - let pow = <$field>::from(two_pow_2_adicity) - * <$field>::from(small_subgroup_base as u64) - .pow([small_subgroup_base_adicity as u64]); - assert_eq!( - large_subgroup_root_of_unity.pow(pow.into_bigint()), - <$field>::one() - ); - - for i in 0..=<$field>::TWO_ADICITY { - for j in 0..=small_subgroup_base_adicity { - let size = (1u64 << i) * (small_subgroup_base as u64).pow(j); - let root = <$field>::get_root_of_unity(size as u64).unwrap(); - assert_eq!(root.pow([size as u64]), <$field>::one()); - } - } - } else { - for i in 0..=<$field>::TWO_ADICITY { - let size = BigUint::from(1_u8) << i; - let root = <$field>::get_root_of_unity_big_int(size.clone()).unwrap(); - assert_eq!(root.pow(size.to_u64_digits()), <$field>::one()); - } - } - } - + // Common field tests plus FFT tests + $crate::__test_field!($field; fft); + // Constants test for prime field as above #[test] fn test_constants() { use ark_ff::{FpConfig, BigInteger, SqrtPrecomputation}; From 917280260dfd6011a9f2091884db72b828a1b33a Mon Sep 17 00:00:00 2001 From: benbencik Date: Wed, 15 Oct 2025 23:30:33 +0200 Subject: [PATCH 32/47] revert ff Cargo.toml changes --- ff/Cargo.toml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ff/Cargo.toml b/ff/Cargo.toml index cafc2040f..31965cb92 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ark-ff" description = "A library for finite fields" -keywords = ["cryptography", "finite-fields"] +keywords = ["cryptography", "finite-fields" ] documentation = "https://docs.rs/ark-ff/" version.workspace = true authors.workspace = true @@ -28,13 +28,8 @@ num-bigint.workspace = true digest = { workspace = true, features = ["alloc"] } [dev-dependencies] -ark-test-curves = { workspace = true, features = [ - "bls12_381_curve", - "mnt6_753", - "secp256k1", -] } -ark-algebra-test-templates = { path = "../test-templates" } - +ark-test-curves = { workspace = true, features = ["bls12_381_curve", "mnt6_753", "secp256k1"] } +blake2.workspace = true sha3.workspace = true sha2.workspace = true libtest-mimic.workspace = true @@ -42,10 +37,9 @@ serde.workspace = true serde_json.workspace = true serde_derive.workspace = true hex.workspace = true -criterion.workspace = true [features] default = [] std = [ "ark-std/std", "ark-serialize/std" ] parallel = [ "std", "rayon", "ark-std/parallel", "ark-serialize/parallel" ] -asm = [] +asm = [] \ No newline at end of file From 94f79a0931a2fec9b8d8040c9bc7ec14a31c131e Mon Sep 17 00:00:00 2001 From: Andrew Zitek-Estrada <1497456+z-tech@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:56:34 +0100 Subject: [PATCH 33/47] Update ff/src/fields/models/small_fp/small_fp_backend.rs Co-authored-by: Weikeng Chen --- ff/src/fields/models/small_fp/small_fp_backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index c27e56a68..53386e933 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -40,7 +40,7 @@ pub trait SmallFpConfig: Send + Sync + 'static + Sized { const MODULUS_128: u128; // TODO: the value can be 1 or 2, it would be nice to have it generic. - /// Number of bigint libs used to represent the field elements. + /// Number of bigint limbs used to represent the field elements. const NUM_BIG_INT_LIMBS: usize = 2; /// A multiplicative generator of the field. From a91ada25fee7d82e7e25d253b787485da0f8f2f8 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:37:43 +0100 Subject: [PATCH 34/47] chkpt --- ff-macros/src/lib.rs | 2 +- ff-macros/src/small_fp/mod.rs | 8 +++----- ff-macros/src/small_fp/montgomery_backend.rs | 4 +--- ff-macros/src/small_fp/standard_backend.rs | 2 +- ff-macros/src/small_fp/utils.rs | 13 ++++++------- ff/src/fields/models/small_fp/field.rs | 2 +- ff/src/fields/models/small_fp/from.rs | 2 +- .../fields/models/small_fp/small_fp_backend.rs | 18 +++++++++--------- serialize/src/impls/int_like.rs | 2 +- serialize/src/impls/misc.rs | 2 +- serialize/src/lib.rs | 6 ++---- 11 files changed, 27 insertions(+), 34 deletions(-) diff --git a/ff-macros/src/lib.rs b/ff-macros/src/lib.rs index 1efd9c4e2..0facfd281 100644 --- a/ff-macros/src/lib.rs +++ b/ff-macros/src/lib.rs @@ -181,7 +181,7 @@ fn test_str_to_limbs() { if sign == Minus { string.insert(0, '-'); } - let (is_positive, limbs) = utils::str_to_limbs(&string.to_string()); + let (is_positive, limbs) = utils::str_to_limbs(&string.clone()); assert_eq!( limbs[0], format!("{}u64", signed_number.unsigned_abs() as u64), diff --git a/ff-macros/src/small_fp/mod.rs b/ff-macros/src/small_fp/mod.rs index f23546867..182d92258 100644 --- a/ff-macros/src/small_fp/mod.rs +++ b/ff-macros/src/small_fp/mod.rs @@ -23,11 +23,9 @@ pub(crate) fn small_fp_config_helper( let backend_impl = match backend.as_str() { "standard" => standard_backend::backend_impl(&ty, modulus, generator), "montgomery" => { - if modulus >= 1u128 << 127 { - panic!( - "SmallFpConfig montgomery backend supports only moduli < 2^127. Use MontConfig with BigInt instead of SmallFp." - ) - } + assert!(modulus < 1u128 << 127, + "SmallFpConfig montgomery backend supports only moduli < 2^127. Use MontConfig with BigInt instead of SmallFp." + ); montgomery_backend::backend_impl(&ty, modulus, generator) }, diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 8b8815d78..966d855b5 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -1,5 +1,3 @@ -use std::u32; - use super::*; use crate::small_fp::utils::{ compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, @@ -36,7 +34,7 @@ pub(crate) fn backend_impl( quote! { type T = #ty; const MODULUS: Self::T = #modulus as Self::T; - const MODULUS_128: u128 = #modulus; + const MODULUS_U128: u128 = #modulus; const GENERATOR: SmallFp = SmallFp::new(#generator_mont as Self::T); const ZERO: SmallFp = SmallFp::new(0 as Self::T); const ONE: SmallFp = SmallFp::new(#one_mont as Self::T); diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs index a51260806..c69f83ac5 100644 --- a/ff-macros/src/small_fp/standard_backend.rs +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -18,7 +18,7 @@ pub(crate) fn backend_impl( quote! { type T = #ty; const MODULUS: Self::T = #modulus as Self::T; - const MODULUS_128: u128 = #modulus; + const MODULUS_U128: u128 = #modulus; const GENERATOR: SmallFp = SmallFp::new(#generator as Self::T); const ZERO: SmallFp = SmallFp::new(0 as Self::T); const ONE: SmallFp = SmallFp::new(1 as Self::T); diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index 39fe370f5..e19dbb6a3 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -86,7 +86,7 @@ pub(crate) fn generate_bigint_casts( quote! { fn from_bigint(a: BigInt<2>) -> Option> { let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); - if val > Self::MODULUS_128 { + if val > Self::MODULUS_U128 { None } else { let reduced_val = val % #modulus; @@ -115,7 +115,7 @@ pub(crate) fn generate_montgomery_bigint_casts( quote! { fn from_bigint(a: BigInt<2>) -> Option> { let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); - if val > Self::MODULUS_128 { + if val > Self::MODULUS_U128 { None } else { let reduced_val = val % #modulus; @@ -165,11 +165,10 @@ pub(crate) fn generate_sqrt_precomputation( let lo = trace_minus_one_div_two as u64; let hi = (trace_minus_one_div_two >> 64) as u64; let qnr = find_quadratic_non_residue(modulus); - let mut qnr_to_trace = pow_mod_const(qnr, trace, modulus); - - if r_mod_n.is_some() { - qnr_to_trace = mod_mul_const(qnr_to_trace, r_mod_n.unwrap(), modulus); - } + let qnr_to_trace = match r_mod_n { + None => pow_mod_const(qnr, trace, modulus), + Some(reduced) => mod_mul_const(pow_mod_const(qnr, trace, modulus), reduced, modulus) + }; quote! { // TonelliShanks square root precomputation diff --git a/ff/src/fields/models/small_fp/field.rs b/ff/src/fields/models/small_fp/field.rs index 4581546bb..9aaa784d1 100644 --- a/ff/src/fields/models/small_fp/field.rs +++ b/ff/src/fields/models/small_fp/field.rs @@ -36,7 +36,7 @@ impl Field for SmallFp

{ #[inline] fn characteristic() -> &'static [u64] { - &Self::MODULUS.as_ref() + Self::MODULUS.as_ref() } #[inline] diff --git a/ff/src/fields/models/small_fp/from.rs b/ff/src/fields/models/small_fp/from.rs index c420fc780..12274df96 100644 --- a/ff/src/fields/models/small_fp/from.rs +++ b/ff/src/fields/models/small_fp/from.rs @@ -21,7 +21,7 @@ impl From for SmallFp

{ impl From for SmallFp

{ fn from(other: bool) -> Self { - if other == true { + if other { P::ONE } else { P::ZERO diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 53386e933..5c5390fa3 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -37,7 +37,7 @@ pub trait SmallFpConfig: Send + Sync + 'static + Sized { /// The modulus of the field. const MODULUS: Self::T; - const MODULUS_128: u128; + const MODULUS_U128: u128; // TODO: the value can be 1 or 2, it would be nice to have it generic. /// Number of bigint limbs used to represent the field elements. @@ -148,8 +148,8 @@ impl SmallFp

{ } } - pub fn num_bits_to_shave() -> usize { - primitive_type_bit_size(P::MODULUS_128) - (Self::MODULUS_BIT_SIZE as usize) + pub const fn num_bits_to_shave() -> usize { + primitive_type_bit_size(P::MODULUS_U128) - (Self::MODULUS_BIT_SIZE as usize) } } @@ -221,8 +221,8 @@ const fn const_num_bits_u128(value: u128) -> u32 { } } -const fn primitive_type_bit_size(modulus_128: u128) -> usize { - match modulus_128 { +const fn primitive_type_bit_size(modulus_u128: u128) -> usize { + match modulus_u128 { x if x <= u8::MAX as u128 => 8, x if x <= u16::MAX as u128 => 16, x if x <= u32::MAX as u128 => 32, @@ -234,9 +234,9 @@ const fn primitive_type_bit_size(modulus_128: u128) -> usize { impl PrimeField for SmallFp

{ type BigInt = BigInt<2>; - const MODULUS: Self::BigInt = const_to_bigint(P::MODULUS_128); + const MODULUS: Self::BigInt = const_to_bigint(P::MODULUS_U128); const MODULUS_MINUS_ONE_DIV_TWO: Self::BigInt = Self::MODULUS.divide_by_2_round_down(); - const MODULUS_BIT_SIZE: u32 = const_num_bits_u128(P::MODULUS_128); + const MODULUS_BIT_SIZE: u32 = const_num_bits_u128(P::MODULUS_U128); const TRACE: Self::BigInt = Self::MODULUS.two_adic_coefficient(); const TRACE_MINUS_ONE_DIV_TWO: Self::BigInt = Self::TRACE.divide_by_2_round_down(); @@ -296,14 +296,14 @@ impl ark_std::rand::distributions::Distribution> loop { let random_val: $ty = rng.sample(ark_std::rand::distributions::Standard); let val_u128 = random_val as u128; - if val_u128 > 0 && val_u128 < P::MODULUS_128 { + if val_u128 > 0 && val_u128 < P::MODULUS_U128 { return SmallFp::from(random_val); } } }; } - match P::MODULUS_128 { + match P::MODULUS_U128 { modulus if modulus <= u8::MAX as u128 => sample_loop!(u8), modulus if modulus <= u16::MAX as u128 => sample_loop!(u16), modulus if modulus <= u32::MAX as u128 => sample_loop!(u32), diff --git a/serialize/src/impls/int_like.rs b/serialize/src/impls/int_like.rs index ec324e991..eae4ff387 100644 --- a/serialize/src/impls/int_like.rs +++ b/serialize/src/impls/int_like.rs @@ -8,7 +8,7 @@ use ark_std::{ use num_bigint::BigUint; impl Valid for bool { - const TRIVIAL_CHECK: bool = true; + const TRIVIAL_CHECK: Self = true; fn check(&self) -> Result<(), SerializationError> { Ok(()) } diff --git a/serialize/src/impls/misc.rs b/serialize/src/impls/misc.rs index d59edb8d0..5af1e7993 100644 --- a/serialize/src/impls/misc.rs +++ b/serialize/src/impls/misc.rs @@ -255,7 +255,7 @@ where Self: 'a, { match Self::TRIVIAL_CHECK { - true => return Ok(()), + true => Ok(()), false => { let t: Vec<_> = batch.map(|v| v.as_ref().to_owned()).collect(); <::Owned>::batch_check(t.iter()) diff --git a/serialize/src/lib.rs b/serialize/src/lib.rs index c0a945c33..1869dab67 100644 --- a/serialize/src/lib.rs +++ b/serialize/src/lib.rs @@ -90,9 +90,7 @@ pub trait Valid: Sync { where Self: 'a, { - if Self::TRIVIAL_CHECK { - Ok(()) - } else { + if !Self::TRIVIAL_CHECK { #[cfg(feature = "parallel")] { use rayon::{iter::ParallelBridge, prelude::ParallelIterator}; @@ -104,8 +102,8 @@ pub trait Valid: Sync { item.check()?; } } - Ok(()) } + Ok(()) } } From e825b86aecac10d14184e8116e34f1da75a3bf87 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:39:53 +0100 Subject: [PATCH 35/47] fmt --- ff-macros/src/small_fp/utils.rs | 2 +- serialize/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index e19dbb6a3..a16ae406f 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -167,7 +167,7 @@ pub(crate) fn generate_sqrt_precomputation( let qnr = find_quadratic_non_residue(modulus); let qnr_to_trace = match r_mod_n { None => pow_mod_const(qnr, trace, modulus), - Some(reduced) => mod_mul_const(pow_mod_const(qnr, trace, modulus), reduced, modulus) + Some(reduced) => mod_mul_const(pow_mod_const(qnr, trace, modulus), reduced, modulus), }; quote! { diff --git a/serialize/src/lib.rs b/serialize/src/lib.rs index 1869dab67..803855799 100644 --- a/serialize/src/lib.rs +++ b/serialize/src/lib.rs @@ -103,7 +103,7 @@ pub trait Valid: Sync { } } } - Ok(()) + Ok(()) } } From 677650f4e6bf38846184c7eda95aadd9615901c3 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:07:06 +0100 Subject: [PATCH 36/47] use shave bits in sample func --- ff/src/fields/models/small_fp/small_fp_backend.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 5c5390fa3..9310365d1 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -289,14 +289,23 @@ impl ark_std::rand::distributions::Distribution> for ark_std::rand::distributions::Standard { #[inline] - // samples non-zero element, loop avoids modulo bias fn sample(&self, rng: &mut R) -> SmallFp

{ macro_rules! sample_loop { ($ty:ty) => { loop { - let random_val: $ty = rng.sample(ark_std::rand::distributions::Standard); - let val_u128 = random_val as u128; + let mut val_u128: u128 = rng.sample(ark_std::rand::distributions::Standard); + + let shave_bits = SmallFp::

::num_bits_to_shave(); + assert!(shave_bits <= 128); + + let mask = if shave_bits == 128 { + 0 + } else { + u128::MAX >> shave_bits + }; + val_u128 &= mask; if val_u128 > 0 && val_u128 < P::MODULUS_U128 { + let random_val = val_u128 as $ty; return SmallFp::from(random_val); } } From 32beb8204eb3ad4532a992475b1e993d4285f165 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:40:43 +0100 Subject: [PATCH 37/47] fix loop --- .../fields/models/small_fp/small_fp_backend.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 9310365d1..76ade9249 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -293,20 +293,12 @@ impl ark_std::rand::distributions::Distribution> macro_rules! sample_loop { ($ty:ty) => { loop { - let mut val_u128: u128 = rng.sample(ark_std::rand::distributions::Standard); - + let mut val: $ty = rng.sample(ark_std::rand::distributions::Standard); let shave_bits = SmallFp::

::num_bits_to_shave(); - assert!(shave_bits <= 128); - - let mask = if shave_bits == 128 { - 0 - } else { - u128::MAX >> shave_bits - }; - val_u128 &= mask; - if val_u128 > 0 && val_u128 < P::MODULUS_U128 { - let random_val = val_u128 as $ty; - return SmallFp::from(random_val); + let mask = <$ty>::MAX >> shave_bits; + val &= mask; + if val > 0 && u128::from(val) < P::MODULUS_U128 { + return SmallFp::from(val); } } }; From 0dd67b338e7d632d1ca3cb61a91f66278fbc6f43 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Tue, 11 Nov 2025 11:05:42 +0100 Subject: [PATCH 38/47] chkpt --- ff/src/fields/models/small_fp/from.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/ff/src/fields/models/small_fp/from.rs b/ff/src/fields/models/small_fp/from.rs index 12274df96..4ad9c9cc8 100644 --- a/ff/src/fields/models/small_fp/from.rs +++ b/ff/src/fields/models/small_fp/from.rs @@ -65,16 +65,7 @@ impl From for SmallFp

{ impl From for SmallFp

{ fn from(other: u16) -> Self { - let other_as_t = match P::T::try_from(other.into()) { - Ok(val) => val, - Err(_) => { - let modulus_as_u128: u128 = P::MODULUS.into(); - let reduced = (other as u128) % modulus_as_u128; - P::T::try_from(reduced).unwrap_or_else(|_| panic!("Reduced value should fit in T")) - }, - }; - let val = other_as_t % P::MODULUS; - SmallFp::new(val) + Self::from(other as u128) } } @@ -91,16 +82,7 @@ impl From for SmallFp

{ impl From for SmallFp

{ fn from(other: u8) -> Self { - let other_as_t = match P::T::try_from(other.into()) { - Ok(val) => val, - Err(_) => { - let modulus_as_u128: u128 = P::MODULUS.into(); - let reduced = (other as u128) % modulus_as_u128; - P::T::try_from(reduced).unwrap_or_else(|_| panic!("Reduced value should fit in T")) - }, - }; - let val = other_as_t % P::MODULUS; - SmallFp::new(val) + Self::from(other as u128) } } From 63589c5a6fc20f96dc1d4accc91d15de04a9d424 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Tue, 11 Nov 2025 11:42:18 +0100 Subject: [PATCH 39/47] depend on from(u128), from(u128) should reduce --- ff/src/fields/models/small_fp/from.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ff/src/fields/models/small_fp/from.rs b/ff/src/fields/models/small_fp/from.rs index 4ad9c9cc8..21b1075b8 100644 --- a/ff/src/fields/models/small_fp/from.rs +++ b/ff/src/fields/models/small_fp/from.rs @@ -3,7 +3,8 @@ use crate::{BigInt, PrimeField}; impl From for SmallFp

{ fn from(other: u128) -> Self { - let bigint = BigInt::<2>::new([other as u64, (other >> 64) as u64]); + let reduced_other = other % P::MODULUS_U128; + let bigint = BigInt::<2>::new([reduced_other as u64, (reduced_other >> 64) as u64]); Self::from_bigint(bigint).unwrap() } } From bc23b85d5d10e06c07609343f0111f73f5ec1728 Mon Sep 17 00:00:00 2001 From: benbencik Date: Wed, 10 Dec 2025 12:23:37 +0100 Subject: [PATCH 40/47] Merge small_fp_bench: optimize SmallFp Montgomery backend --- ff-macros/src/small_fp/mod.rs | 18 +- ff-macros/src/small_fp/montgomery_backend.rs | 295 ++++++++++++++---- ff-macros/src/small_fp/utils.rs | 16 +- ff/README.md | 3 +- ff/src/fields/models/small_fp/ops.rs | 1 + .../models/small_fp/small_fp_backend.rs | 2 + test-curves/Cargo.toml | 5 + test-curves/benches/smallfp.rs | 57 +++- test-curves/src/smallfp128.rs | 3 +- test-curves/src/smallfp16.rs | 11 +- test-curves/src/smallfp32.rs | 16 +- test-curves/src/smallfp64.rs | 7 +- test-curves/src/smallfp8.rs | 11 +- 13 files changed, 337 insertions(+), 108 deletions(-) diff --git a/ff-macros/src/small_fp/mod.rs b/ff-macros/src/small_fp/mod.rs index 182d92258..70eb92f37 100644 --- a/ff-macros/src/small_fp/mod.rs +++ b/ff-macros/src/small_fp/mod.rs @@ -34,17 +34,21 @@ pub(crate) fn small_fp_config_helper( let new_impl = match backend.as_str() { "standard" => standard_backend::new(), - "montgomery" => montgomery_backend::new(modulus, ty), + "montgomery" => montgomery_backend::new(modulus, ty.clone()), _ => panic!("Unknown backend type: {}", backend), }; quote! { - impl SmallFpConfig for #config_name { - #backend_impl - } + const _: () = { + use ark_ff::{SmallFp, SmallFpConfig}; - impl #config_name { - #new_impl - } + impl SmallFpConfig for #config_name { + #backend_impl + } + + impl #config_name { + #new_impl + } + }; } } diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 966d855b5..4e3acaff2 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -1,3 +1,4 @@ +use std::u32; use super::*; use crate::small_fp::utils::{ compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, @@ -87,13 +88,30 @@ pub(crate) fn backend_impl( fn sum_of_products( a: &[SmallFp; T], b: &[SmallFp; T],) -> SmallFp { - let mut acc = SmallFp::new(0 as Self::T); - for (x, y) in a.iter().zip(b.iter()) { - let mut prod = *x; - Self::mul_assign(&mut prod, y); - Self::add_assign(&mut acc, &prod); + match T { + 1 => { + let mut prod = a[0]; + Self::mul_assign(&mut prod, &b[0]); + prod + }, + 2 => { + let mut prod1 = a[0]; + Self::mul_assign(&mut prod1, &b[0]); + let mut prod2 = a[1]; + Self::mul_assign(&mut prod2, &b[1]); + Self::add_assign(&mut prod1, &prod2); + prod1 + }, + _ => { + let mut acc = SmallFp::new(0 as Self::T); + for (x, y) in a.iter().zip(b.iter()) { + let mut prod = *x; + Self::mul_assign(&mut prod, y); + Self::add_assign(&mut acc, &prod); + } + acc + } } - acc } #[inline(always)] @@ -133,7 +151,7 @@ pub(crate) fn backend_impl( // Selects the appropriate multiplication algorithm at compile time: // if modulus <= u64, multiply by casting to the next largest primitive -// otherwise, multiply in parts to form a 256-bit product before reduction +// otherwise, multiply in parts to form a 256-bit product fn generate_mul_impl( ty: &proc_macro2::TokenStream, modulus: u128, @@ -143,80 +161,225 @@ fn generate_mul_impl( ) -> proc_macro2::TokenStream { let ty_str = ty.to_string(); - if ty_str == "u128" { + match ty_str.as_str() { + "u128" => generate_u128_mul(modulus, k_bits, r_mask, n_prime), + "u64" => generate_u64_mul(modulus, k_bits, r_mask, n_prime), + "u32" => generate_u32_mul(modulus, k_bits, r_mask, n_prime), + "u8" | "u16" => generate_small_mul(ty, ty_str.as_str(), modulus, k_bits, r_mask, n_prime), + _ => panic!("Unsupported type: {}", ty_str), + } +} + +fn generate_u128_mul( + modulus: u128, + k_bits: u32, + r_mask: u128, + n_prime: u128, +) -> proc_macro2::TokenStream { + let modulus_lo = modulus & 0xFFFFFFFFFFFFFFFF; + let modulus_hi = modulus >> 64; + let shift_back = 128 - k_bits; + + quote! { + #[inline(always)] + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + const MODULUS_LO: u128 = #modulus_lo; + const MODULUS_HI: u128 = #modulus_hi; + const R_MASK: u128 = #r_mask; + const N_PRIME: u128 = #n_prime; + + // 256-bit result stored as lo, hi + // t = a * b + let a_lo = a.value & 0xFFFFFFFFFFFFFFFF; + let a_hi = a.value >> 64; + let b_lo = b.value & 0xFFFFFFFFFFFFFFFF; + let b_hi = b.value >> 64; + + let lolo = a_lo * b_lo; + let lohi = a_lo * b_hi; + let hilo = a_hi * b_lo; + let hihi = a_hi * b_hi; + + let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); + let (t_lo, mid_carry) = lolo.overflowing_add(cross_sum << 64); + let t_hi = hihi + ((cross_sum >> 64) | ((cross_carry as u128) << 64)) + (mid_carry as u128); + + // m = t_lo * n_prime & r_mask + let m = t_lo.wrapping_mul(N_PRIME) & R_MASK; + + // mn = m * modulus + let m_lo = m & 0xFFFFFFFFFFFFFFFF; + let m_hi = m >> 64; + + let lolo = m_lo * MODULUS_LO; + let lohi = m_lo * MODULUS_HI; + let hilo = m_hi * MODULUS_LO; + let hihi = m_hi * MODULUS_HI; + + let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); + let (mn_lo, mid_carry) = lolo.overflowing_add(cross_sum << 64); + let mn_hi = hihi + ((cross_sum >> 64) | ((cross_carry as u128) << 64)) + (mid_carry as u128); + + // (t + mn) / R + let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); + let sum_hi = t_hi + mn_hi + (carry as u128); + + let mut u = (sum_lo >> #k_bits) | (sum_hi << #shift_back); + u -= #modulus * (u >= #modulus) as u128; + a.value = u; + } + } +} + +fn generate_u64_mul( + modulus: u128, + k_bits: u32, + r_mask: u128, + n_prime: u128, +) -> proc_macro2::TokenStream { + // Use u128 for multiplication to avoid overflow when multiplying u64 values + let shift_bits = 128 - k_bits; + + quote! { + #[inline(always)] + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + const MODULUS_MUL_TY: u128 = #modulus as u128; + const N_PRIME: u128 = #n_prime as u128; + const R_MASK: u128 = #r_mask as u128; + + let mut t = (a.value as u128) * (b.value as u128); + let k = t.wrapping_mul(N_PRIME) & R_MASK; + let (t, overflow) = t.overflowing_add(k * MODULUS_MUL_TY); + + let mut r = (t >> #k_bits) + ((overflow as u128) << #shift_bits); + if r >= MODULUS_MUL_TY { + r -= MODULUS_MUL_TY; + } + a.value = r as u64; + } + } +} + +fn generate_u32_mul( + modulus: u128, + k_bits: u32, + r_mask: u128, + n_prime: u128, +) -> proc_macro2::TokenStream { + const M31_PRIME: u128 = 2147483647; // 2^31 - 1 + + if modulus == M31_PRIME { quote! { - #[inline(always)] + #[inline(always)] fn mul_assign(a: &mut SmallFp, b: &SmallFp) { - // 256-bit result stored as lo, hi - // t = a * b - let lolo = (a.value & 0xFFFFFFFFFFFFFFFF) * (b.value & 0xFFFFFFFFFFFFFFFF); - let lohi = (a.value & 0xFFFFFFFFFFFFFFFF) * (b.value >> 64); - let hilo = (a.value >> 64) * (b.value & 0xFFFFFFFFFFFFFFFF); - let hihi = (a.value >> 64) * (b.value >> 64); - - let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); - let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); - let t_lo = mid; - let t_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); - - // m = t_lo * n_prime & r_mask - let m = t_lo.wrapping_mul(#n_prime) & #r_mask; - - // mn = m * modulus - let lolo = (m & 0xFFFFFFFFFFFFFFFF) * (#modulus & 0xFFFFFFFFFFFFFFFF); - let lohi = (m & 0xFFFFFFFFFFFFFFFF) * (#modulus >> 64); - let hilo = (m >> 64) * (#modulus & 0xFFFFFFFFFFFFFFFF); - let hihi = (m >> 64) * (#modulus >> 64); - - let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); - let (mid, mid_carry) = lolo.overflowing_add(cross_sum << 64); - let mn_lo = mid; - let mn_hi = hihi + (cross_sum >> 64) + ((cross_carry as u128) << 64) + (mid_carry as u128); - - // (t + mn) / R - let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); - let sum_hi = t_hi + mn_hi + (carry as u128); - - let mut u = (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)); - u -= #modulus * (u >= #modulus) as u128; - a.value = u as Self::T; + const K: u64 = 31; + const MODULUS: u64 = (1u64 << K) - 1; + + let prod = (a.value as u64) * (b.value as u64); + let mut r = (prod & MODULUS) + (prod >> K); + + if r >= MODULUS { + r -= MODULUS; + } + a.value = r as u32; } } } else { - let (mul_ty, bits) = match ty_str.as_str() { - "u8" => (quote! {u16}, 16u32), - "u16" => (quote! {u32}, 32u32), - "u32" => (quote! {u64}, 64u32), - _ => (quote! {u128}, 128u32), - }; + quote! { + #[inline(always)] + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + const MODULUS_MUL_TY: u64 = #modulus as u64; + const N_PRIME: u64 = #n_prime as u64; + const R_MASK: u64 = #r_mask as u64; - let r_mask_downcast = quote! { #r_mask as #mul_ty }; - let n_prime_downcast = quote! { #n_prime as #mul_ty }; - let modulus_downcast = quote! { #modulus as #mul_ty }; - let one = quote! { 1 as #mul_ty }; + let t = (a.value as u64) * (b.value as u64); + let k = t.wrapping_mul(N_PRIME) & R_MASK; + let mut r = (t + (k * MODULUS_MUL_TY)) >> #k_bits; + if r >= MODULUS_MUL_TY { + r -= MODULUS_MUL_TY; + } + a.value = r as u32; + } + } + } +} + +fn generate_small_mul( + ty: &proc_macro2::TokenStream, + ty_str: &str, + modulus: u128, + k_bits: u32, + r_mask: u128, + n_prime: u128, +) -> proc_macro2::TokenStream { + const M7_PRIME: u128 = 127; // 2^7 - 1 + const M13_PRIME: u128 = 8191; // 2^13 - 1 + + if modulus == M7_PRIME { quote! { #[inline(always)] fn mul_assign(a: &mut SmallFp, b: &SmallFp) { - let a_val = a.value as #mul_ty; - let b_val = b.value as #mul_ty; + const K: u16 = 7; + const MODULUS: u16 = (1u16 << K) - 1; - let t = a_val * b_val; - let t_low = t & #r_mask_downcast; + let prod = (a.value as u16) * (b.value as u16); + let mut r = (prod & MODULUS) + (prod >> K); - // m = t_lo * n_prime & r_mask - let m = t_low.wrapping_mul(#n_prime_downcast) & #r_mask_downcast; + if r >= MODULUS { + r -= MODULUS; + } + a.value = r as u8; + } + } + } else if modulus == M13_PRIME { + quote! { + #[inline(always)] + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + const K: u32 = 13; + const MODULUS: u32 = (1u32 << K) - 1; - // mn = m * modulus - let mn = m * #modulus_downcast; + let prod = (a.value as u32) * (b.value as u32); + let mut r = (prod & MODULUS) + (prod >> K); - // (t + mn) / R - let (sum, overflow) = t.overflowing_add(mn); - let mut u = sum >> #k_bits; + if r >= MODULUS { + r -= MODULUS; + } + a.value = r as u16; + } + } + } else { + let mul_ty = match ty_str { + "u8" => quote! { u16 }, + "u16" => quote! { u32 }, + _ => unreachable!(), + }; - u += ((#one) << (#bits - #k_bits)) * (overflow as #mul_ty); - u -= #modulus_downcast * ((u >= #modulus_downcast) as #mul_ty); - a.value = u as Self::T; + quote! { + #[inline(always)] + fn mul_assign(a: &mut SmallFp, b: &SmallFp) { + const MODULUS_MUL_TY: #mul_ty = #modulus as #mul_ty; + const MODULUS_TY: #ty = #modulus as #ty; + const N_PRIME: #ty = #n_prime as #ty; + const MASK: #mul_ty = #r_mask as #mul_ty; + const K_BITS: u32 = #k_bits; + + let a_val = a.value as #mul_ty; + let b_val = b.value as #mul_ty; + + let tmp = a_val * b_val; + let carry1 = (tmp >> K_BITS) as #ty; + let r = (tmp & MASK) as #ty; + let m = r.wrapping_mul(N_PRIME); + + let tmp = (r as #mul_ty) + ((m as #mul_ty) * MODULUS_MUL_TY); + let carry2 = (tmp >> K_BITS) as #ty; + + let mut r = (carry1 as #mul_ty) + (carry2 as #mul_ty); + if r >= MODULUS_MUL_TY { + r -= MODULUS_MUL_TY; + } + a.value = r as #ty; } } } diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index a16ae406f..4820f8a97 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -84,7 +84,7 @@ pub(crate) fn generate_bigint_casts( ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { ( quote! { - fn from_bigint(a: BigInt<2>) -> Option> { + fn from_bigint(a: ark_ff::BigInt<2>) -> Option> { let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); if val > Self::MODULUS_U128 { None @@ -95,7 +95,7 @@ pub(crate) fn generate_bigint_casts( } }, quote! { - fn into_bigint(a: SmallFp) -> BigInt<2> { + fn into_bigint(a: SmallFp) -> ark_ff::BigInt<2> { let val = a.value as u128; let lo = val as u64; let hi = (val >> 64) as u64; @@ -113,7 +113,7 @@ pub(crate) fn generate_montgomery_bigint_casts( let r2 = mod_mul_const(r_mod_n, r_mod_n, modulus); ( quote! { - fn from_bigint(a: BigInt<2>) -> Option> { + fn from_bigint(a: ark_ff::BigInt<2>) -> Option> { let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); if val > Self::MODULUS_U128 { None @@ -127,7 +127,7 @@ pub(crate) fn generate_montgomery_bigint_casts( } }, quote! { - fn into_bigint(a: SmallFp) -> BigInt<2> { + fn into_bigint(a: SmallFp) -> ark_ff::BigInt<2> { let mut tmp = a; let one = SmallFp::new(1 as Self::T); ::mul_assign(&mut tmp, &one); @@ -152,9 +152,9 @@ pub(crate) fn generate_sqrt_precomputation( quote! { // Case3Mod4 square root precomputation - const SQRT_PRECOMP: Option>> = { + const SQRT_PRECOMP: Option>> = { const MODULUS_PLUS_ONE_DIV_FOUR: [u64; 2] = [#lo, #hi]; - Some(SqrtPrecomputation::Case3Mod4 { + Some(ark_ff::SqrtPrecomputation::Case3Mod4 { modulus_plus_one_div_four: &MODULUS_PLUS_ONE_DIV_FOUR, }) }; @@ -172,9 +172,9 @@ pub(crate) fn generate_sqrt_precomputation( quote! { // TonelliShanks square root precomputation - const SQRT_PRECOMP: Option>> = { + const SQRT_PRECOMP: Option>> = { const TRACE_MINUS_ONE_DIV_TWO: [u64; 2] = [#lo, #hi]; - Some(SqrtPrecomputation::TonelliShanks { + Some(ark_ff::SqrtPrecomputation::TonelliShanks { two_adicity: #two_adicity, quadratic_nonresidue_to_trace: SmallFp::new(#qnr_to_trace as Self::T), trace_of_modulus_minus_one_div_two: &TRACE_MINUS_ONE_DIV_TWO, diff --git a/ff/README.md b/ff/README.md index c840f08e3..2570d3dc3 100644 --- a/ff/README.md +++ b/ff/README.md @@ -43,9 +43,8 @@ The above two models serve as abstractions for constructing the extension fields You can instantiate fields in two ways: ```rust -use ark_ff::ark_ff_macros::SmallFpConfig; use ark_ff::fields::{Fp64, MontBackend, MontConfig}; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; +use ark_ff::{SmallFp, SmallFpConfig}; // Standard (big integer) field #[derive(MontConfig)] diff --git a/ff/src/fields/models/small_fp/ops.rs b/ff/src/fields/models/small_fp/ops.rs index b7e08b65b..46b5026bb 100644 --- a/ff/src/fields/models/small_fp/ops.rs +++ b/ff/src/fields/models/small_fp/ops.rs @@ -198,6 +198,7 @@ impl<'a, P: SmallFpConfig> core::ops::SubAssign<&'a mut Self> for SmallFp

{ } impl MulAssign<&Self> for SmallFp

{ + #[inline(always)] fn mul_assign(&mut self, other: &Self) { P::mul_assign(self, other) } diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 76ade9249..909e4eb2d 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -9,6 +9,8 @@ use ark_std::{ use educe::Educe; use num_traits::Unsigned; +pub use ark_ff_macros::SmallFpConfig; + /// A trait that specifies the configuration of a prime field, including the /// modulus, generator, and arithmetic implementation. /// diff --git a/test-curves/Cargo.toml b/test-curves/Cargo.toml index adf24b8db..98980be86 100644 --- a/test-curves/Cargo.toml +++ b/test-curves/Cargo.toml @@ -83,3 +83,8 @@ harness = false name = "smallfp" path = "benches/smallfp.rs" harness = false + +[[bench]] +name = "smallfp_compare" +path = "benches/smallfp_compare.rs" +harness = false \ No newline at end of file diff --git a/test-curves/benches/smallfp.rs b/test-curves/benches/smallfp.rs index 75bb91563..1a26dc348 100644 --- a/test-curves/benches/smallfp.rs +++ b/test-curves/benches/smallfp.rs @@ -1,10 +1,11 @@ use ark_algebra_bench_templates::*; -use ark_ff::fields::{Fp64, MontBackend, MontConfig}; +use ark_ff::fields::{Fp64, MontBackend, MontConfig, SmallFp, SmallFpConfig}; use ark_test_curves::{ - smallfp32::{SmallF32, SmallF32Mont}, - smallfp64::{SmallF64, SmallF64Mont}, + smallfp32::{SmallF32MontM31}, + smallfp64::{SmallF64MontGoldilock}, }; + #[derive(MontConfig)] #[modulus = "18446744069414584321"] #[generator = "7"] @@ -17,19 +18,53 @@ pub type F64 = Fp64>; pub struct F32Config; pub type F32 = Fp64>; +#[derive(MontConfig)] +#[modulus = "65521"] +#[generator = "17"] +pub struct F16Config; +pub type F16 = Fp64>; + +#[derive(SmallFpConfig)] +#[modulus = "65521"] +#[generator = "17"] +#[backend = "montgomery"] +pub struct F16ConfigMont; +pub type SmallF16Mont = SmallFp; + +#[derive(MontConfig)] +#[modulus = "251"] +#[generator = "6"] +pub struct F8Config; +pub type F8 = Fp64>; + +#[derive(SmallFpConfig)] +#[modulus = "251"] +#[generator = "6"] +#[backend = "montgomery"] +pub struct SmallF8ConfigMont; +pub type SmallF8Mont = SmallFp; + + + +f_bench!(prime, "F8", F8); +f_bench!(prime, "SmallF8Mont", SmallF8Mont); + +f_bench!(prime, "F16", F16); +f_bench!(prime, "SmallF16Mont", SmallF16Mont); + f_bench!(prime, "F32", F32); -f_bench!(prime, "SmallF32", SmallF32); -f_bench!(prime, "SmallF32Mont", SmallF32Mont); +f_bench!(prime, "SmallF32Mont", SmallF32MontM31); f_bench!(prime, "F64", F64); -f_bench!(prime, "SmallF64", SmallF64); -f_bench!(prime, "SmallF64Mont", SmallF64Mont); +f_bench!(prime, "SmallF64Mont", SmallF64MontGoldilock); criterion_main!( + f8::benches, + smallf8mont::benches, + f16::benches, + smallf16mont::benches, f32::benches, - smallf32::benches, - smallf32mont::benches, + smallf32montm31::benches, f64::benches, - smallf64::benches, - smallf64mont::benches, + smallf64montgoldilock::benches, ); diff --git a/test-curves/src/smallfp128.rs b/test-curves/src/smallfp128.rs index 0ffaf9d43..568c14d12 100644 --- a/test-curves/src/smallfp128.rs +++ b/test-curves/src/smallfp128.rs @@ -1,5 +1,4 @@ -use ark_ff::ark_ff_macros::SmallFpConfig; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; +use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "143244528689204659050391023439224324689"] diff --git a/test-curves/src/smallfp16.rs b/test-curves/src/smallfp16.rs index e0813e397..c77fe7e78 100644 --- a/test-curves/src/smallfp16.rs +++ b/test-curves/src/smallfp16.rs @@ -1,5 +1,4 @@ -use ark_ff::ark_ff_macros::SmallFpConfig; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; +use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "65521"] @@ -15,6 +14,13 @@ pub type SmallF16 = SmallFp; pub struct SmallF16ConfigMont; pub type SmallF16Mont = SmallFp; +#[derive(SmallFpConfig)] +#[modulus = "8191"] +#[generator = "17"] +#[backend = "montgomery"] +pub struct SmallF16ConfigMontM13; +pub type SmallF16MontM13 = SmallFp; + #[cfg(test)] mod tests { use super::*; @@ -23,4 +29,5 @@ mod tests { test_small_field!(f16; SmallF16); test_small_field!(f16_mont; SmallF16Mont); + test_small_field!(f16_mont_m13; SmallF16MontM13); } diff --git a/test-curves/src/smallfp32.rs b/test-curves/src/smallfp32.rs index 38ce45e63..78c4ad0f1 100644 --- a/test-curves/src/smallfp32.rs +++ b/test-curves/src/smallfp32.rs @@ -1,5 +1,4 @@ -use ark_ff::ark_ff_macros::SmallFpConfig; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; +use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "2147483647"] // m31 @@ -13,7 +12,15 @@ pub type SmallF32 = SmallFp; #[generator = "7"] #[backend = "montgomery"] pub struct SmallFieldMont; -pub type SmallF32Mont = SmallFp; +pub type SmallF32MontM31 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "2013265921"] +#[generator = "31"] +#[backend = "montgomery"] +pub struct SmallFieldMontBabybear; +pub type SmallF32MontBabybear = SmallFp; + #[cfg(test)] mod tests { @@ -22,5 +29,6 @@ mod tests { use ark_std::vec; test_small_field!(f32; SmallF32); - test_small_field!(f32_mont; SmallF32Mont); + test_small_field!(f32_mont_m31; SmallF32MontM31); + test_small_field!(f32_mont_babybear; SmallF32MontBabybear); } diff --git a/test-curves/src/smallfp64.rs b/test-curves/src/smallfp64.rs index 1e85e7fff..442bc7bb8 100644 --- a/test-curves/src/smallfp64.rs +++ b/test-curves/src/smallfp64.rs @@ -1,5 +1,4 @@ -use ark_ff::ark_ff_macros::SmallFpConfig; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; +use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 @@ -13,7 +12,7 @@ pub type SmallF64 = SmallFp; #[generator = "7"] #[backend = "montgomery"] pub struct SmallF64ConfigMont; -pub type SmallF64Mont = SmallFp; +pub type SmallF64MontGoldilock = SmallFp; #[cfg(test)] mod tests { @@ -22,5 +21,5 @@ mod tests { use ark_std::vec; test_small_field!(f64; SmallF64); - test_small_field!(f64_mont; SmallF64Mont); + test_small_field!(f64_mont; SmallF64MontGoldilock); } diff --git a/test-curves/src/smallfp8.rs b/test-curves/src/smallfp8.rs index f8a4ed64a..85c27bec1 100644 --- a/test-curves/src/smallfp8.rs +++ b/test-curves/src/smallfp8.rs @@ -1,5 +1,4 @@ -use ark_ff::ark_ff_macros::SmallFpConfig; -use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation}; +use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "251"] @@ -15,6 +14,13 @@ pub type SmallF8 = SmallFp; pub struct SmallF8ConfigMont; pub type SmallF8Mont = SmallFp; +#[derive(SmallFpConfig)] +#[modulus = "127"] +#[generator = "3"] +#[backend = "montgomery"] +pub struct SmallF8ConfigMontM7; +pub type SmallF8MontM7 = SmallFp; + #[cfg(test)] mod tests { use super::*; @@ -23,4 +29,5 @@ mod tests { test_small_field!(f8; SmallF8); test_small_field!(f8_mont; SmallF8Mont); + test_small_field!(f8_mont_m7; SmallF8MontM7); } From ba64b81e301a74e0f9edd2a1a06755df91098156 Mon Sep 17 00:00:00 2001 From: benbencik Date: Sat, 7 Feb 2026 17:52:10 +0100 Subject: [PATCH 41/47] get rid of the smallfp standard backend --- ff-macros/src/lib.rs | 12 +- ff-macros/src/small_fp/mod.rs | 23 +--- ff-macros/src/small_fp/standard_backend.rs | 138 --------------------- ff/README.md | 3 +- test-curves/benches/smallfp.rs | 30 +++-- test-curves/src/smallfp128.rs | 15 +-- test-curves/src/smallfp16.rs | 22 +--- test-curves/src/smallfp32.rs | 24 ++-- test-curves/src/smallfp64.rs | 15 +-- test-curves/src/smallfp8.rs | 22 +--- 10 files changed, 49 insertions(+), 255 deletions(-) delete mode 100644 ff-macros/src/small_fp/standard_backend.rs diff --git a/ff-macros/src/lib.rs b/ff-macros/src/lib.rs index 0facfd281..1284bdc6e 100644 --- a/ff-macros/src/lib.rs +++ b/ff-macros/src/lib.rs @@ -80,8 +80,9 @@ pub fn mont_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// The attributes available to this macro are: /// * `modulus`: Specify the prime modulus underlying this prime field. /// * `generator`: Specify the generator of the multiplicative subgroup. -/// * `backend`: Specify either "standard" or "montgomery" backend. -#[proc_macro_derive(SmallFpConfig, attributes(modulus, generator, backend))] +/// +/// Note: Only Montgomery backend is supported. +#[proc_macro_derive(SmallFpConfig, attributes(modulus, generator))] pub fn small_fp_config(input: TokenStream) -> TokenStream { let ast: syn::DeriveInput = syn::parse(input).unwrap(); @@ -95,12 +96,7 @@ pub fn small_fp_config(input: TokenStream) -> TokenStream { .parse() .expect("Generator should be a number"); - let backend: String = fetch_attr("backend", &ast.attrs) - .expect("Please supply a backend attribute") - .parse() - .expect("Backend should be a string"); - - small_fp::small_fp_config_helper(modulus, generator, backend, ast.ident).into() + small_fp::small_fp_config_helper(modulus, generator, ast.ident).into() } const ARG_MSG: &str = "Failed to parse unroll threshold; must be a positive integer"; diff --git a/ff-macros/src/small_fp/mod.rs b/ff-macros/src/small_fp/mod.rs index 70eb92f37..d6c721029 100644 --- a/ff-macros/src/small_fp/mod.rs +++ b/ff-macros/src/small_fp/mod.rs @@ -1,5 +1,4 @@ mod montgomery_backend; -mod standard_backend; mod utils; use quote::quote; @@ -9,7 +8,6 @@ use quote::quote; pub(crate) fn small_fp_config_helper( modulus: u128, generator: u128, - backend: String, config_name: proc_macro2::Ident, ) -> proc_macro2::TokenStream { let ty = match modulus { @@ -20,23 +18,12 @@ pub(crate) fn small_fp_config_helper( _ => quote! { u128 }, }; - let backend_impl = match backend.as_str() { - "standard" => standard_backend::backend_impl(&ty, modulus, generator), - "montgomery" => { - assert!(modulus < 1u128 << 127, - "SmallFpConfig montgomery backend supports only moduli < 2^127. Use MontConfig with BigInt instead of SmallFp." - ); - montgomery_backend::backend_impl(&ty, modulus, generator) - }, + assert!(modulus < 1u128 << 127, + "SmallFpConfig montgomery backend supports only moduli < 2^127. Use MontConfig with BigInt instead of SmallFp." + ); - _ => panic!("Unknown backend type: {}", backend), - }; - - let new_impl = match backend.as_str() { - "standard" => standard_backend::new(), - "montgomery" => montgomery_backend::new(modulus, ty.clone()), - _ => panic!("Unknown backend type: {}", backend), - }; + let backend_impl = montgomery_backend::backend_impl(&ty, modulus, generator); + let new_impl = montgomery_backend::new(modulus, ty.clone()); quote! { const _: () = { diff --git a/ff-macros/src/small_fp/standard_backend.rs b/ff-macros/src/small_fp/standard_backend.rs deleted file mode 100644 index c69f83ac5..000000000 --- a/ff-macros/src/small_fp/standard_backend.rs +++ /dev/null @@ -1,138 +0,0 @@ -use super::*; -use crate::small_fp::utils::{ - compute_two_adic_root_of_unity, compute_two_adicity, generate_bigint_casts, - generate_sqrt_precomputation, -}; - -pub(crate) fn backend_impl( - ty: &proc_macro2::TokenStream, - modulus: u128, - generator: u128, -) -> proc_macro2::TokenStream { - let two_adicity = compute_two_adicity(modulus); - let two_adic_root_of_unity = compute_two_adic_root_of_unity(modulus, two_adicity, generator); - - let (from_bigint_impl, into_bigint_impl) = generate_bigint_casts(modulus); - let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity, None); - - quote! { - type T = #ty; - const MODULUS: Self::T = #modulus as Self::T; - const MODULUS_U128: u128 = #modulus; - const GENERATOR: SmallFp = SmallFp::new(#generator as Self::T); - const ZERO: SmallFp = SmallFp::new(0 as Self::T); - const ONE: SmallFp = SmallFp::new(1 as Self::T); - const NEG_ONE: SmallFp = SmallFp::new((Self::MODULUS - 1) as Self::T); - - const TWO_ADICITY: u32 = #two_adicity; - const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::new(#two_adic_root_of_unity as Self::T); - #sqrt_precomp_impl - - #[inline(always)] - fn add_assign(a: &mut SmallFp, b: &SmallFp) { - a.value = match a.value.overflowing_add(b.value) { - (val, false) => val % Self::MODULUS, - (val, true) => (Self::T::MAX - Self::MODULUS + 1 + val) % Self::MODULUS, - }; - } - - #[inline(always)] - fn sub_assign(a: &mut SmallFp, b: &SmallFp) { - if a.value >= b.value { - a.value -= b.value; - } else { - a.value = Self::MODULUS - (b.value - a.value); - } - } - - #[inline(always)] - fn double_in_place(a: &mut SmallFp) { - let tmp = *a; - Self::add_assign(a, &tmp); - } - - #[inline(always)] - fn neg_in_place(a: &mut SmallFp) { - if a.value != (0 as Self::T) { - a.value = Self::MODULUS - a.value; - } - } - - #[inline(always)] - fn mul_assign(a: &mut SmallFp, b: &SmallFp) { - let a_128 = (a.value as u128) % #modulus; - let b_128 = (b.value as u128) % #modulus; - let mod_add = |x: u128, y: u128| -> u128 { - if x >= #modulus - y { - x - (#modulus - y) - } else { - x + y - } - }; - a.value = match a_128.overflowing_mul(b_128) { - (val, false) => (val % #modulus) as Self::T, - (_, true) => { - let mut result = 0u128; - let mut base = a_128 % #modulus; - let mut exp = b_128; - while exp > 0 { - if exp & 1 == 1 { - result = mod_add(result, base); - } - base = mod_add(base, base); - exp >>= 1; - } - result as Self::T - } - }; - } - - fn sum_of_products( - a: &[SmallFp; T], - b: &[SmallFp; T],) -> SmallFp { - let mut acc = SmallFp::new(0 as Self::T); - for (x, y) in a.iter().zip(b.iter()) { - let mut prod = *x; - Self::mul_assign(&mut prod, y); - Self::add_assign(&mut acc, &prod); - } - acc - } - - fn square_in_place(a: &mut SmallFp) { - let tmp = *a; - Self::mul_assign(a, &tmp); - } - - fn inverse(a: &SmallFp) -> Option> { - if a.value == 0 { - return None; - } - let mut base = *a; - let mut exp = Self::MODULUS - 2; - let mut acc = Self::ONE; - while exp > 0 { - if (exp & 1) == 1 { - Self::mul_assign(&mut acc, &base); - } - let mut sq = base; - Self::mul_assign(&mut sq, &base); - base = sq; - exp >>= 1; - } - Some(acc) - } - - #from_bigint_impl - - #into_bigint_impl - } -} - -pub(crate) fn new() -> proc_macro2::TokenStream { - quote! { - pub fn new(value: ::T) -> SmallFp { - SmallFp::new(value % ::MODULUS) - } - } -} diff --git a/ff/README.md b/ff/README.md index 2570d3dc3..31ecc1ab6 100644 --- a/ff/README.md +++ b/ff/README.md @@ -57,12 +57,11 @@ pub type F64 = Fp64>; #[derive(SmallFpConfig)] #[modulus = "18446744069414584321"] #[generator = "7"] -#[backend = "montgomery"] // or "standard" pub struct SmallF64ConfigMont; pub type SmallF64Mont = SmallFp; ``` -The standard field implementation can represent arbitrarily large fields, while the small field implementation supports native integer types from `u8` to `u128` for faster arithmetic. The small field implementation requires that the modulus fits into u128. +The standard field implementation can represent arbitrarily large fields, while the small field implementation supports native integer types from `u8` to `u128` for faster arithmetic. The both implementations use Montgomery arithmetic. The small field implementation requires that the modulus fits into u128. ## Usage diff --git a/test-curves/benches/smallfp.rs b/test-curves/benches/smallfp.rs index 1a26dc348..3aa57069b 100644 --- a/test-curves/benches/smallfp.rs +++ b/test-curves/benches/smallfp.rs @@ -1,8 +1,8 @@ use ark_algebra_bench_templates::*; use ark_ff::fields::{Fp64, MontBackend, MontConfig, SmallFp, SmallFpConfig}; use ark_test_curves::{ - smallfp32::{SmallF32MontM31}, - smallfp64::{SmallF64MontGoldilock}, + smallfp32::{SmallFp32M31}, + smallfp64::{SmallFp64Goldilock}, }; @@ -27,9 +27,8 @@ pub type F16 = Fp64>; #[derive(SmallFpConfig)] #[modulus = "65521"] #[generator = "17"] -#[backend = "montgomery"] -pub struct F16ConfigMont; -pub type SmallF16Mont = SmallFp; +pub struct SmallFp16Config; +pub type SmallFp16 = SmallFp; #[derive(MontConfig)] #[modulus = "251"] @@ -40,31 +39,30 @@ pub type F8 = Fp64>; #[derive(SmallFpConfig)] #[modulus = "251"] #[generator = "6"] -#[backend = "montgomery"] -pub struct SmallF8ConfigMont; -pub type SmallF8Mont = SmallFp; +pub struct SmallFp8Config; +pub type SmallFp8 = SmallFp; f_bench!(prime, "F8", F8); -f_bench!(prime, "SmallF8Mont", SmallF8Mont); +f_bench!(prime, "SmallF8Mont", SmallFp8); f_bench!(prime, "F16", F16); -f_bench!(prime, "SmallF16Mont", SmallF16Mont); +f_bench!(prime, "SmallF16Mont", SmallFp16); f_bench!(prime, "F32", F32); -f_bench!(prime, "SmallF32Mont", SmallF32MontM31); +f_bench!(prime, "SmallF32Mont", SmallFp32M31); f_bench!(prime, "F64", F64); -f_bench!(prime, "SmallF64Mont", SmallF64MontGoldilock); +f_bench!(prime, "SmallF64Mont", SmallFp64Goldilock); criterion_main!( f8::benches, - smallf8mont::benches, + smallfp8::benches, f16::benches, - smallf16mont::benches, + smallfp16::benches, f32::benches, - smallf32montm31::benches, + smallfp32m31::benches, f64::benches, - smallf64montgoldilock::benches, + smallfp64goldilock::benches, ); diff --git a/test-curves/src/smallfp128.rs b/test-curves/src/smallfp128.rs index 568c14d12..bff794bf3 100644 --- a/test-curves/src/smallfp128.rs +++ b/test-curves/src/smallfp128.rs @@ -3,16 +3,8 @@ use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "143244528689204659050391023439224324689"] #[generator = "3"] -#[backend = "standard"] -pub struct SmallF128Config; -pub type SmallF128 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "3"] -#[backend = "montgomery"] -pub struct SmallF128ConfigMont; -pub type SmallF128Mont = SmallFp; +pub struct SmallFp128Config; +pub type SmallFp128 = SmallFp; #[cfg(test)] mod tests { @@ -20,6 +12,5 @@ mod tests { use ark_algebra_test_templates::*; use ark_std::vec; - test_small_field!(f128; SmallF128); - test_small_field!(f128_mont; SmallF128Mont); + test_small_field!(f128; SmallFp128); } diff --git a/test-curves/src/smallfp16.rs b/test-curves/src/smallfp16.rs index c77fe7e78..1fc5c883c 100644 --- a/test-curves/src/smallfp16.rs +++ b/test-curves/src/smallfp16.rs @@ -3,23 +3,14 @@ use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "65521"] #[generator = "17"] -#[backend = "standard"] -pub struct SmallF16Config; -pub type SmallF16 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "65521"] -#[generator = "17"] -#[backend = "montgomery"] -pub struct SmallF16ConfigMont; -pub type SmallF16Mont = SmallFp; +pub struct SmallFp16Config; +pub type SmallFp16 = SmallFp; #[derive(SmallFpConfig)] #[modulus = "8191"] #[generator = "17"] -#[backend = "montgomery"] -pub struct SmallF16ConfigMontM13; -pub type SmallF16MontM13 = SmallFp; +pub struct SmallFp16ConfigM13; +pub type SmallFp16M13 = SmallFp; #[cfg(test)] mod tests { @@ -27,7 +18,6 @@ mod tests { use ark_algebra_test_templates::*; use ark_std::vec; - test_small_field!(f16; SmallF16); - test_small_field!(f16_mont; SmallF16Mont); - test_small_field!(f16_mont_m13; SmallF16MontM13); + test_small_field!(f16; SmallFp16); + test_small_field!(f16_mont_m13; SmallFp16M13); } diff --git a/test-curves/src/smallfp32.rs b/test-curves/src/smallfp32.rs index 78c4ad0f1..6c42f938d 100644 --- a/test-curves/src/smallfp32.rs +++ b/test-curves/src/smallfp32.rs @@ -1,25 +1,16 @@ use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] -#[modulus = "2147483647"] // m31 +#[modulus = "2147483647"] #[generator = "7"] -#[backend = "standard"] -pub struct SmallField; -pub type SmallF32 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "2147483647"] // m31 -#[generator = "7"] -#[backend = "montgomery"] -pub struct SmallFieldMont; -pub type SmallF32MontM31 = SmallFp; +pub struct SmallFp32ConfigM31; +pub type SmallFp32M31 = SmallFp; #[derive(SmallFpConfig)] #[modulus = "2013265921"] #[generator = "31"] -#[backend = "montgomery"] -pub struct SmallFieldMontBabybear; -pub type SmallF32MontBabybear = SmallFp; +pub struct SmallFpBabybearConfig; +pub type SmallFp32Babybear = SmallFp; #[cfg(test)] @@ -28,7 +19,6 @@ mod tests { use ark_algebra_test_templates::*; use ark_std::vec; - test_small_field!(f32; SmallF32); - test_small_field!(f32_mont_m31; SmallF32MontM31); - test_small_field!(f32_mont_babybear; SmallF32MontBabybear); + test_small_field!(f32; SmallFp32M31); + test_small_field!(f32_mont_babybear; SmallFp32Babybear); } diff --git a/test-curves/src/smallfp64.rs b/test-curves/src/smallfp64.rs index 442bc7bb8..a65235c2a 100644 --- a/test-curves/src/smallfp64.rs +++ b/test-curves/src/smallfp64.rs @@ -3,16 +3,8 @@ use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 #[generator = "7"] -#[backend = "standard"] -pub struct SmallF64Config; -pub type SmallF64 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 -#[generator = "7"] -#[backend = "montgomery"] -pub struct SmallF64ConfigMont; -pub type SmallF64MontGoldilock = SmallFp; +pub struct SmallFp64GoldilockConfig; +pub type SmallFp64Goldilock = SmallFp; #[cfg(test)] mod tests { @@ -20,6 +12,5 @@ mod tests { use ark_algebra_test_templates::*; use ark_std::vec; - test_small_field!(f64; SmallF64); - test_small_field!(f64_mont; SmallF64MontGoldilock); + test_small_field!(f64; SmallFp64Goldilock); } diff --git a/test-curves/src/smallfp8.rs b/test-curves/src/smallfp8.rs index 85c27bec1..3bc12fc21 100644 --- a/test-curves/src/smallfp8.rs +++ b/test-curves/src/smallfp8.rs @@ -3,23 +3,14 @@ use ark_ff::{SmallFp, SmallFpConfig}; #[derive(SmallFpConfig)] #[modulus = "251"] #[generator = "6"] -#[backend = "standard"] -pub struct SmallF8Config; -pub type SmallF8 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "251"] -#[generator = "6"] -#[backend = "montgomery"] -pub struct SmallF8ConfigMont; -pub type SmallF8Mont = SmallFp; +pub struct SmallFp8Config; +pub type SmallFp8 = SmallFp; #[derive(SmallFpConfig)] #[modulus = "127"] #[generator = "3"] -#[backend = "montgomery"] -pub struct SmallF8ConfigMontM7; -pub type SmallF8MontM7 = SmallFp; +pub struct SmallFp8ConfigM7; +pub type SmallFp8M7 = SmallFp; #[cfg(test)] mod tests { @@ -27,7 +18,6 @@ mod tests { use ark_algebra_test_templates::*; use ark_std::vec; - test_small_field!(f8; SmallF8); - test_small_field!(f8_mont; SmallF8Mont); - test_small_field!(f8_mont_m7; SmallF8MontM7); + test_small_field!(f8; SmallFp8); + test_small_field!(f8_mont_m7; SmallFp8M7); } From c18bddd62bb02cebd3c0100697fb02ce22b4ca1d Mon Sep 17 00:00:00 2001 From: benbencik Date: Sun, 8 Feb 2026 15:57:22 +0100 Subject: [PATCH 42/47] rewrite the new constructor --- ff-macros/src/small_fp/mod.rs | 4 +- ff-macros/src/small_fp/montgomery_backend.rs | 48 ++++++++----------- ff-macros/src/small_fp/utils.rs | 44 +++-------------- ff/src/fields/models/small_fp/from.rs | 6 +-- .../models/small_fp/small_fp_backend.rs | 12 ++++- test-curves/Cargo.toml | 5 -- 6 files changed, 43 insertions(+), 76 deletions(-) diff --git a/ff-macros/src/small_fp/mod.rs b/ff-macros/src/small_fp/mod.rs index d6c721029..e321452c4 100644 --- a/ff-macros/src/small_fp/mod.rs +++ b/ff-macros/src/small_fp/mod.rs @@ -23,7 +23,7 @@ pub(crate) fn small_fp_config_helper( ); let backend_impl = montgomery_backend::backend_impl(&ty, modulus, generator); - let new_impl = montgomery_backend::new(modulus, ty.clone()); + let exit_impl = montgomery_backend::exit_impl(); quote! { const _: () = { @@ -34,7 +34,7 @@ pub(crate) fn small_fp_config_helper( } impl #config_name { - #new_impl + #exit_impl } }; } diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 4e3acaff2..dd03bf790 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -1,4 +1,3 @@ -use std::u32; use super::*; use crate::small_fp::utils::{ compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, @@ -26,8 +25,9 @@ pub(crate) fn backend_impl( let neg_one_mont = mod_mul_const(modulus - 1, r_mod_n, modulus); let (from_bigint_impl, into_bigint_impl) = - generate_montgomery_bigint_casts(modulus, k_bits, r_mod_n); + generate_montgomery_bigint_casts(); let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity, Some(r_mod_n)); + let r2 = mod_mul_const(r_mod_n, r_mod_n, modulus); // Generate multiplication implementation based on type let mul_impl = generate_mul_impl(ty, modulus, k_bits, r_mask, n_prime); @@ -36,14 +36,14 @@ pub(crate) fn backend_impl( type T = #ty; const MODULUS: Self::T = #modulus as Self::T; const MODULUS_U128: u128 = #modulus; - const GENERATOR: SmallFp = SmallFp::new(#generator_mont as Self::T); - const ZERO: SmallFp = SmallFp::new(0 as Self::T); - const ONE: SmallFp = SmallFp::new(#one_mont as Self::T); - const NEG_ONE: SmallFp = SmallFp::new(#neg_one_mont as Self::T); + const GENERATOR: SmallFp = SmallFp::from_raw(#generator_mont as Self::T); + const ZERO: SmallFp = SmallFp::from_raw(0 as Self::T); + const ONE: SmallFp = SmallFp::from_raw(#one_mont as Self::T); + const NEG_ONE: SmallFp = SmallFp::from_raw(#neg_one_mont as Self::T); const TWO_ADICITY: u32 = #two_adicity; - const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::new(#two_adic_root_mont as Self::T); + const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::from_raw(#two_adic_root_mont as Self::T); #sqrt_precomp_impl #[inline(always)] @@ -77,7 +77,7 @@ pub(crate) fn backend_impl( #[inline(always)] fn neg_in_place(a: &mut SmallFp) { - if a.value != (0 as Self::T) { + if a.value != Self::ZERO.value { a.value = Self::MODULUS - a.value; } } @@ -103,7 +103,7 @@ pub(crate) fn backend_impl( prod1 }, _ => { - let mut acc = SmallFp::new(0 as Self::T); + let mut acc = Self::ZERO; for (x, y) in a.iter().zip(b.iter()) { let mut prod = *x; Self::mul_assign(&mut prod, y); @@ -143,6 +143,15 @@ pub(crate) fn backend_impl( Some(result) } + #[inline] + fn new(value: Self::T) -> SmallFp { + let reduced_value = value % Self::MODULUS; + let mut tmp = SmallFp::from_raw(reduced_value); + let r2_elem = SmallFp::from_raw(#r2 as Self::T); + Self::mul_assign(&mut tmp, &r2_elem); + tmp + } + #from_bigint_impl #into_bigint_impl @@ -394,26 +403,11 @@ fn mod_inverse_pow2(n: u128, k_bits: u32) -> u128 { inv.wrapping_neg() & mask } -pub(crate) fn new(modulus: u128, _ty: proc_macro2::TokenStream) -> proc_macro2::TokenStream { - let k_bits = 128 - modulus.leading_zeros(); - let r: u128 = 1u128 << k_bits; - let r_mod_n = r % modulus; - let r2 = mod_mul_const(r_mod_n, r_mod_n, modulus); - +pub(crate) fn exit_impl() -> proc_macro2::TokenStream { quote! { - pub fn new(value: ::T) -> SmallFp { - let reduced_value = value % ::MODULUS; - let mut tmp = SmallFp::new(reduced_value); - let r2_elem = SmallFp::new(#r2 as ::T); - ::mul_assign(&mut tmp, &r2_elem); - tmp - } - pub fn exit(a: &mut SmallFp) { - let mut tmp = *a; - let one = SmallFp::new(1 as ::T); - ::mul_assign(&mut tmp, &one); - a.value = tmp.value; + let one = SmallFp::from_raw(1 as ::T); + ::mul_assign(a, &one); } } } diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index 4820f8a97..ba47ffdff 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -79,38 +79,8 @@ pub(crate) const fn find_quadratic_non_residue(modulus: u128) -> u128 { } } -pub(crate) fn generate_bigint_casts( - modulus: u128, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - ( - quote! { - fn from_bigint(a: ark_ff::BigInt<2>) -> Option> { - let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); - if val > Self::MODULUS_U128 { - None - } else { - let reduced_val = val % #modulus; - Some(SmallFp::new(reduced_val as Self::T)) - } - } - }, - quote! { - fn into_bigint(a: SmallFp) -> ark_ff::BigInt<2> { - let val = a.value as u128; - let lo = val as u64; - let hi = (val >> 64) as u64; - ark_ff::BigInt([lo, hi]) - } - }, - ) -} - pub(crate) fn generate_montgomery_bigint_casts( - modulus: u128, - _k_bits: u32, - r_mod_n: u128, ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let r2 = mod_mul_const(r_mod_n, r_mod_n, modulus); ( quote! { fn from_bigint(a: ark_ff::BigInt<2>) -> Option> { @@ -118,19 +88,17 @@ pub(crate) fn generate_montgomery_bigint_casts( if val > Self::MODULUS_U128 { None } else { - let reduced_val = val % #modulus; - let mut tmp = SmallFp::new(reduced_val as Self::T); - let r2_elem = SmallFp::new(#r2 as Self::T); - ::mul_assign(&mut tmp, &r2_elem); - Some(tmp) + let reduced_val = val % Self::MODULUS_U128; + let val_t = Self::T::try_from(reduced_val).ok().unwrap(); + Some(Self::new(val_t)) } } }, quote! { fn into_bigint(a: SmallFp) -> ark_ff::BigInt<2> { let mut tmp = a; - let one = SmallFp::new(1 as Self::T); - ::mul_assign(&mut tmp, &one); + let one = SmallFp::from_raw(1 as Self::T); + Self::mul_assign(&mut tmp, &one); let val = tmp.value as u128; let lo = val as u64; let hi = (val >> 64) as u64; @@ -176,7 +144,7 @@ pub(crate) fn generate_sqrt_precomputation( const TRACE_MINUS_ONE_DIV_TWO: [u64; 2] = [#lo, #hi]; Some(ark_ff::SqrtPrecomputation::TonelliShanks { two_adicity: #two_adicity, - quadratic_nonresidue_to_trace: SmallFp::new(#qnr_to_trace as Self::T), + quadratic_nonresidue_to_trace: SmallFp::from_raw(#qnr_to_trace as Self::T), trace_of_modulus_minus_one_div_two: &TRACE_MINUS_ONE_DIV_TWO, }) }; diff --git a/ff/src/fields/models/small_fp/from.rs b/ff/src/fields/models/small_fp/from.rs index 21b1075b8..82962e66e 100644 --- a/ff/src/fields/models/small_fp/from.rs +++ b/ff/src/fields/models/small_fp/from.rs @@ -3,9 +3,9 @@ use crate::{BigInt, PrimeField}; impl From for SmallFp

{ fn from(other: u128) -> Self { - let reduced_other = other % P::MODULUS_U128; - let bigint = BigInt::<2>::new([reduced_other as u64, (reduced_other >> 64) as u64]); - Self::from_bigint(bigint).unwrap() + let reduced = other % P::MODULUS_U128; + let val = P::T::try_from(reduced).ok().unwrap(); + P::new(val) } } diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index 909e4eb2d..e8685c703 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -114,6 +114,9 @@ pub trait SmallFpConfig: Send + Sync + 'static + Sized { /// Compute a^{-1} if `a` is not zero. fn inverse(a: &SmallFp) -> Option>; + /// Construct a field element from a standard integer value + fn new(value: Self::T) -> SmallFp; + /// Construct a field element from an integer in the range /// `0..(Self::MODULUS - 1)`. Returns `None` if the integer is outside /// this range. @@ -143,13 +146,20 @@ impl SmallFp

{ self.value >= P::MODULUS } - pub const fn new(value: P::T) -> Self { + // Does NOT perform Montgomery conversion or modular reduction + pub const fn from_raw(value: P::T) -> Self { Self { value, _phantom: PhantomData, } } + // Creates a new field element in Montgomery form + #[inline] + pub fn new(value: P::T) -> Self { + P::new(value) + } + pub const fn num_bits_to_shave() -> usize { primitive_type_bit_size(P::MODULUS_U128) - (Self::MODULUS_BIT_SIZE as usize) } diff --git a/test-curves/Cargo.toml b/test-curves/Cargo.toml index 98980be86..adf24b8db 100644 --- a/test-curves/Cargo.toml +++ b/test-curves/Cargo.toml @@ -83,8 +83,3 @@ harness = false name = "smallfp" path = "benches/smallfp.rs" harness = false - -[[bench]] -name = "smallfp_compare" -path = "benches/smallfp_compare.rs" -harness = false \ No newline at end of file From 4ae1cdfde2cb548c81a64ec1dd8e110c3c84b5eb Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 13 Feb 2026 09:39:46 +0100 Subject: [PATCH 43/47] update u128 multiplication --- ff-macros/src/small_fp/montgomery_backend.rs | 199 ++++++++++++------- 1 file changed, 130 insertions(+), 69 deletions(-) diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index dd03bf790..8a48bbe7a 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -9,10 +9,18 @@ pub(crate) fn backend_impl( modulus: u128, generator: u128, ) -> proc_macro2::TokenStream { - let k_bits = 128 - modulus.leading_zeros(); - let r: u128 = 1u128 << k_bits; - let r_mod_n = r % modulus; - let r_mask = r - 1; + let ty_str = ty.to_string(); + let is_u128 = ty_str == "u128"; + + // For u128, we use R = 2^128 for smaller types, R = 2^k_bits + let k_bits = if is_u128 { 128u32 } else { 128 - modulus.leading_zeros() }; + let r: u128 = if k_bits == 128 { 0u128 } else { 1u128 << k_bits }; + let r_mod_n = if k_bits == 128 { + (((1u128 << 127) % modulus) + ((1u128 << 127) % modulus)) % modulus + } else { + r % modulus + }; + let r_mask = if k_bits == 128 { u128::MAX } else { r - 1 }; let n_prime = mod_inverse_pow2(modulus, k_bits); let one_mont = r_mod_n; @@ -32,6 +40,41 @@ pub(crate) fn backend_impl( // Generate multiplication implementation based on type let mul_impl = generate_mul_impl(ty, modulus, k_bits, r_mask, n_prime); + let type_bits = match ty_str.as_str() { + "u8" => 8u32, + "u16" => 16u32, + "u32" => 32u32, + "u64" => 64u32, + "u128" => 128u32, + _ => panic!("unsupported type"), + }; + + // If there is a spare bit skip the overflow branch + let has_spare_bit = modulus.leading_zeros() >= (128 - type_bits + 1); + let add_assign_impl = if has_spare_bit { + quote! { + #[inline(always)] + fn add_assign(a: &mut SmallFp, b: &SmallFp) { + let val = a.value.wrapping_add(b.value); + a.value = if val >= Self::MODULUS { val - Self::MODULUS } else { val }; + } + } + } else { + quote! { + #[inline(always)] + fn add_assign(a: &mut SmallFp, b: &SmallFp) { + let (mut val, overflow) = a.value.overflowing_add(b.value); + if overflow { + val = Self::T::MAX - Self::MODULUS + 1 + val + } + if val >= Self::MODULUS { + val -= Self::MODULUS; + } + a.value = val; + } + } + }; + quote! { type T = #ty; const MODULUS: Self::T = #modulus as Self::T; @@ -41,24 +84,12 @@ pub(crate) fn backend_impl( const ONE: SmallFp = SmallFp::from_raw(#one_mont as Self::T); const NEG_ONE: SmallFp = SmallFp::from_raw(#neg_one_mont as Self::T); - const TWO_ADICITY: u32 = #two_adicity; const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::from_raw(#two_adic_root_mont as Self::T); + #sqrt_precomp_impl - #[inline(always)] - fn add_assign(a: &mut SmallFp, b: &SmallFp) { - let (mut val, overflow) = a.value.overflowing_add(b.value); - - if overflow { - val = Self::T::MAX - Self::MODULUS + 1 + val - } - - if val >= Self::MODULUS { - val -= Self::MODULUS; - } - a.value = val; - } + #add_assign_impl #[inline(always)] fn sub_assign(a: &mut SmallFp, b: &SmallFp) { @@ -179,63 +210,93 @@ fn generate_mul_impl( } } +// Montgomery multiplication for 2 limbs (similar to ff-asm/src/lib.rs) fn generate_u128_mul( modulus: u128, - k_bits: u32, - r_mask: u128, - n_prime: u128, + _k_bits: u32, + _r_mask: u128, + _n_prime: u128, ) -> proc_macro2::TokenStream { - let modulus_lo = modulus & 0xFFFFFFFFFFFFFFFF; - let modulus_hi = modulus >> 64; - let shift_back = 128 - k_bits; + let modulus_lo = (modulus & 0xFFFFFFFFFFFFFFFF) as u64; + let modulus_hi = (modulus >> 64) as u64; + let inv = mod_inverse_pow2(modulus_lo as u128, 64) as u64; quote! { #[inline(always)] fn mul_assign(a: &mut SmallFp, b: &SmallFp) { - const MODULUS_LO: u128 = #modulus_lo; - const MODULUS_HI: u128 = #modulus_hi; - const R_MASK: u128 = #r_mask; - const N_PRIME: u128 = #n_prime; - - // 256-bit result stored as lo, hi - // t = a * b - let a_lo = a.value & 0xFFFFFFFFFFFFFFFF; - let a_hi = a.value >> 64; - let b_lo = b.value & 0xFFFFFFFFFFFFFFFF; - let b_hi = b.value >> 64; - - let lolo = a_lo * b_lo; - let lohi = a_lo * b_hi; - let hilo = a_hi * b_lo; - let hihi = a_hi * b_hi; - - let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); - let (t_lo, mid_carry) = lolo.overflowing_add(cross_sum << 64); - let t_hi = hihi + ((cross_sum >> 64) | ((cross_carry as u128) << 64)) + (mid_carry as u128); - - // m = t_lo * n_prime & r_mask - let m = t_lo.wrapping_mul(N_PRIME) & R_MASK; - - // mn = m * modulus - let m_lo = m & 0xFFFFFFFFFFFFFFFF; - let m_hi = m >> 64; - - let lolo = m_lo * MODULUS_LO; - let lohi = m_lo * MODULUS_HI; - let hilo = m_hi * MODULUS_LO; - let hihi = m_hi * MODULUS_HI; - - let (cross_sum, cross_carry) = lohi.overflowing_add(hilo); - let (mn_lo, mid_carry) = lolo.overflowing_add(cross_sum << 64); - let mn_hi = hihi + ((cross_sum >> 64) | ((cross_carry as u128) << 64)) + (mid_carry as u128); - - // (t + mn) / R - let (sum_lo, carry) = t_lo.overflowing_add(mn_lo); - let sum_hi = t_hi + mn_hi + (carry as u128); - - let mut u = (sum_lo >> #k_bits) | (sum_hi << #shift_back); - u -= #modulus * (u >= #modulus) as u128; - a.value = u; + const MODULUS: [u64; 2] = [#modulus_lo, #modulus_hi]; + const INV: u64 = #inv; + + let a_limbs = [a.value as u64, (a.value >> 64) as u64]; + let b_limbs = [b.value as u64, (b.value >> 64) as u64]; + + #[inline(always)] + fn umul(a: u64, b: u64) -> (u64, u64) { + let full = (a as u128) * (b as u128); + (full as u64, (full >> 64) as u64) + } + + // r accumulator: 3 words (r[0], r[1], r[2]) to hold intermediate + let mut r0: u64 = 0; + let mut r1: u64 = 0; + let mut r2: u64 = 0; + + + let (lo, hi) = umul(a_limbs[0], b_limbs[0]); + let (r0_new, c) = r0.overflowing_add(lo); + r0 = r0_new; + let carry1 = c as u64; + + let (lo, hi2) = umul(a_limbs[1], b_limbs[0]); + let (r1_new, c1) = r1.overflowing_add(lo); + let (r1_new, c2) = r1_new.overflowing_add(hi + carry1); + r1 = r1_new; + r2 = r2.wrapping_add(hi2).wrapping_add(c1 as u64 + c2 as u64); + + let m = r0.wrapping_mul(INV); + + let (lo, hi) = umul(m, MODULUS[0]); + let (_, c) = r0.overflowing_add(lo); // r0 + lo should be 0 mod 2^64 + let carry = hi.wrapping_add(c as u64); + + let (lo, hi) = umul(m, MODULUS[1]); + let (new_r0, c1) = r1.overflowing_add(lo); + let (new_r0, c2) = new_r0.overflowing_add(carry); + r0 = new_r0; + r1 = r2.wrapping_add(hi).wrapping_add(c1 as u64 + c2 as u64); + r2 = 0; + + + let (lo, hi) = umul(a_limbs[0], b_limbs[1]); + let (r0_new, c) = r0.overflowing_add(lo); + r0 = r0_new; + let carry1 = c as u64; + + let (lo, hi2) = umul(a_limbs[1], b_limbs[1]); + let (r1_new, c1) = r1.overflowing_add(lo); + let (r1_new, c2) = r1_new.overflowing_add(hi + carry1); + r1 = r1_new; + r2 = r2.wrapping_add(hi2).wrapping_add(c1 as u64 + c2 as u64); + + let m = r0.wrapping_mul(INV); + + let (lo, hi) = umul(m, MODULUS[0]); + let (_, c) = r0.overflowing_add(lo); + let carry = hi.wrapping_add(c as u64); + + let (lo, hi) = umul(m, MODULUS[1]); + let (new_r0, c1) = r1.overflowing_add(lo); + let (new_r0, c2) = new_r0.overflowing_add(carry); + r0 = new_r0; + r1 = r2.wrapping_add(hi).wrapping_add(c1 as u64 + c2 as u64); + + + let mut result = (r0 as u128) | ((r1 as u128) << 64); + let modulus_val = (#modulus_lo as u128) | ((#modulus_hi as u128) << 64); + if result >= modulus_val { + result -= modulus_val; + } + a.value = result; } } } @@ -399,7 +460,7 @@ fn mod_inverse_pow2(n: u128, k_bits: u32) -> u128 { for _ in 0..k_bits { inv = inv.wrapping_mul(2u128.wrapping_sub(n.wrapping_mul(inv))); } - let mask = (1u128 << k_bits) - 1; + let mask = if k_bits == 128 { u128::MAX } else { (1u128 << k_bits) - 1 }; inv.wrapping_neg() & mask } From c9cd6750f82df164e9a4bd32556b830d6e9ea9b7 Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 20 Feb 2026 11:21:09 +0100 Subject: [PATCH 44/47] unify montconfig and smallconfig macros (intermediate step) --- ff-macros/src/lib.rs | 49 +++++------ ff-macros/src/montgomery/sum_of_products.rs | 2 +- ff-macros/src/small_fp/montgomery_backend.rs | 86 +++++++++++-------- ff-macros/src/small_fp/utils.rs | 14 +-- .../models/small_fp/small_fp_backend.rs | 2 - test-curves/benches/smallfp.rs | 39 +++++---- test-curves/src/lib.rs | 6 +- test-curves/src/smallfp.rs | 65 ++++++++++++++ test-curves/src/smallfp128.rs | 16 ---- test-curves/src/smallfp16.rs | 23 ----- test-curves/src/smallfp32.rs | 24 ------ test-curves/src/smallfp64.rs | 16 ---- test-curves/src/smallfp8.rs | 23 ----- 13 files changed, 164 insertions(+), 201 deletions(-) create mode 100644 test-curves/src/smallfp.rs delete mode 100644 test-curves/src/smallfp128.rs delete mode 100644 test-curves/src/smallfp16.rs delete mode 100644 test-curves/src/smallfp32.rs delete mode 100644 test-curves/src/smallfp64.rs delete mode 100644 test-curves/src/smallfp8.rs diff --git a/ff-macros/src/lib.rs b/ff-macros/src/lib.rs index 1284bdc6e..b6dd3c0b5 100644 --- a/ff-macros/src/lib.rs +++ b/ff-macros/src/lib.rs @@ -65,38 +65,29 @@ pub fn mont_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let small_subgroup_power: Option = fetch_attr("small_subgroup_power", &ast.attrs) .map(|s| s.parse().expect("small_subgroup_power should be a number")); - montgomery::mont_config_helper( - modulus, - generator, + // TODO: Both macros are being generated at the same time. Create unified type + let mont_impl = montgomery::mont_config_helper( + modulus.clone(), + generator.clone(), small_subgroup_base, small_subgroup_power, - ast.ident, - ) - .into() -} - -/// Derive the `SmallFpConfig` trait for small prime fields. -/// -/// The attributes available to this macro are: -/// * `modulus`: Specify the prime modulus underlying this prime field. -/// * `generator`: Specify the generator of the multiplicative subgroup. -/// -/// Note: Only Montgomery backend is supported. -#[proc_macro_derive(SmallFpConfig, attributes(modulus, generator))] -pub fn small_fp_config(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); - - let modulus: u128 = fetch_attr("modulus", &ast.attrs) - .expect("Please supply a modulus attribute") - .parse() - .expect("Modulus should be a number"); - - let generator: u128 = fetch_attr("generator", &ast.attrs) - .expect("Please supply a generator attribute") - .parse() - .expect("Generator should be a number"); + ast.ident.clone(), + ); + + let small_fp_impl = if modulus < (BigUint::from(1u128) << 127) { + use num_traits::ToPrimitive; + let modulus_u128 = modulus.to_u128().unwrap(); + let generator_u128 = generator.to_u128().unwrap(); + small_fp::small_fp_config_helper(modulus_u128, generator_u128, ast.ident) + } else { + quote::quote! {} + }; - small_fp::small_fp_config_helper(modulus, generator, ast.ident).into() + quote::quote! { + #mont_impl + #small_fp_impl + } + .into() } const ARG_MSG: &str = "Failed to parse unroll threshold; must be a positive integer"; diff --git a/ff-macros/src/montgomery/sum_of_products.rs b/ff-macros/src/montgomery/sum_of_products.rs index 57b992e7a..42d4461a6 100644 --- a/ff-macros/src/montgomery/sum_of_products.rs +++ b/ff-macros/src/montgomery/sum_of_products.rs @@ -73,7 +73,7 @@ pub(super) fn sum_of_products_impl(num_limbs: usize, modulus: &[u64]) -> proc_ma } else { a.chunks(#chunk_size).zip(b.chunks(#chunk_size)).map(|(a, b)| { if a.len() == #chunk_size { - Self::sum_of_products::<#chunk_size>(a.try_into().unwrap(), b.try_into().unwrap()) + >::sum_of_products::<#chunk_size>(a.try_into().unwrap(), b.try_into().unwrap()) } else { a.iter().zip(b).map(|(a, b)| *a * b).sum() } diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 8a48bbe7a..50ff00b44 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -13,8 +13,16 @@ pub(crate) fn backend_impl( let is_u128 = ty_str == "u128"; // For u128, we use R = 2^128 for smaller types, R = 2^k_bits - let k_bits = if is_u128 { 128u32 } else { 128 - modulus.leading_zeros() }; - let r: u128 = if k_bits == 128 { 0u128 } else { 1u128 << k_bits }; + let k_bits = if is_u128 { + 128u32 + } else { + 128 - modulus.leading_zeros() + }; + let r: u128 = if k_bits == 128 { + 0u128 + } else { + 1u128 << k_bits + }; let r_mod_n = if k_bits == 128 { (((1u128 << 127) % modulus) + ((1u128 << 127) % modulus)) % modulus } else { @@ -32,8 +40,7 @@ pub(crate) fn backend_impl( let neg_one_mont = mod_mul_const(modulus - 1, r_mod_n, modulus); - let (from_bigint_impl, into_bigint_impl) = - generate_montgomery_bigint_casts(); + let (from_bigint_impl, into_bigint_impl) = generate_montgomery_bigint_casts(); let sqrt_precomp_impl = generate_sqrt_precomputation(modulus, two_adicity, Some(r_mod_n)); let r2 = mod_mul_const(r_mod_n, r_mod_n, modulus); @@ -48,7 +55,7 @@ pub(crate) fn backend_impl( "u128" => 128u32, _ => panic!("unsupported type"), }; - + // If there is a spare bit skip the overflow branch let has_spare_bit = modulus.leading_zeros() >= (128 - type_bits + 1); let add_assign_impl = if has_spare_bit { @@ -56,7 +63,7 @@ pub(crate) fn backend_impl( #[inline(always)] fn add_assign(a: &mut SmallFp, b: &SmallFp) { let val = a.value.wrapping_add(b.value); - a.value = if val >= Self::MODULUS { val - Self::MODULUS } else { val }; + a.value = if val >= ::MODULUS { val - ::MODULUS } else { val }; } } } else { @@ -65,10 +72,10 @@ pub(crate) fn backend_impl( fn add_assign(a: &mut SmallFp, b: &SmallFp) { let (mut val, overflow) = a.value.overflowing_add(b.value); if overflow { - val = Self::T::MAX - Self::MODULUS + 1 + val + val = ::T::MAX - ::MODULUS + 1 + val } - if val >= Self::MODULUS { - val -= Self::MODULUS; + if val >= ::MODULUS { + val -= ::MODULUS; } a.value = val; } @@ -86,7 +93,7 @@ pub(crate) fn backend_impl( const TWO_ADICITY: u32 = #two_adicity; const TWO_ADIC_ROOT_OF_UNITY: SmallFp = SmallFp::from_raw(#two_adic_root_mont as Self::T); - + #sqrt_precomp_impl #add_assign_impl @@ -96,20 +103,20 @@ pub(crate) fn backend_impl( if a.value >= b.value { a.value -= b.value; } else { - a.value = Self::MODULUS - (b.value - a.value); + a.value = ::MODULUS - (b.value - a.value); } } #[inline(always)] fn double_in_place(a: &mut SmallFp) { let tmp = *a; - Self::add_assign(a, &tmp); + ::add_assign(a, &tmp); } #[inline(always)] fn neg_in_place(a: &mut SmallFp) { - if a.value != Self::ZERO.value { - a.value = Self::MODULUS - a.value; + if a.value != ::ZERO.value { + a.value = ::MODULUS - a.value; } } @@ -122,23 +129,23 @@ pub(crate) fn backend_impl( match T { 1 => { let mut prod = a[0]; - Self::mul_assign(&mut prod, &b[0]); + ::mul_assign(&mut prod, &b[0]); prod }, 2 => { let mut prod1 = a[0]; - Self::mul_assign(&mut prod1, &b[0]); + ::mul_assign(&mut prod1, &b[0]); let mut prod2 = a[1]; - Self::mul_assign(&mut prod2, &b[1]); - Self::add_assign(&mut prod1, &prod2); + ::mul_assign(&mut prod2, &b[1]); + ::add_assign(&mut prod1, &prod2); prod1 }, _ => { - let mut acc = Self::ZERO; + let mut acc = ::ZERO; for (x, y) in a.iter().zip(b.iter()) { let mut prod = *x; - Self::mul_assign(&mut prod, y); - Self::add_assign(&mut acc, &prod); + ::mul_assign(&mut prod, y); + ::add_assign(&mut acc, &prod); } acc } @@ -148,7 +155,7 @@ pub(crate) fn backend_impl( #[inline(always)] fn square_in_place(a: &mut SmallFp) { let tmp = *a; - Self::mul_assign(a, &tmp); + ::mul_assign(a, &tmp); } fn inverse(a: &SmallFp) -> Option> { @@ -156,17 +163,17 @@ pub(crate) fn backend_impl( return None; } - let mut result = Self::ONE; + let mut result = ::ONE; let mut base = *a; - let mut exp = Self::MODULUS - 2; + let mut exp = ::MODULUS - 2; while exp > 0 { if exp & 1 == 1 { - Self::mul_assign(&mut result, &base); + ::mul_assign(&mut result, &base); } let mut sq = base; - Self::square_in_place(&mut sq); + ::square_in_place(&mut sq); base = sq; exp >>= 1; } @@ -176,10 +183,10 @@ pub(crate) fn backend_impl( #[inline] fn new(value: Self::T) -> SmallFp { - let reduced_value = value % Self::MODULUS; + let reduced_value = value % ::MODULUS; let mut tmp = SmallFp::from_raw(reduced_value); let r2_elem = SmallFp::from_raw(#r2 as Self::T); - Self::mul_assign(&mut tmp, &r2_elem); + ::mul_assign(&mut tmp, &r2_elem); tmp } @@ -226,22 +233,21 @@ fn generate_u128_mul( fn mul_assign(a: &mut SmallFp, b: &SmallFp) { const MODULUS: [u64; 2] = [#modulus_lo, #modulus_hi]; const INV: u64 = #inv; - + let a_limbs = [a.value as u64, (a.value >> 64) as u64]; let b_limbs = [b.value as u64, (b.value >> 64) as u64]; - + #[inline(always)] fn umul(a: u64, b: u64) -> (u64, u64) { let full = (a as u128) * (b as u128); (full as u64, (full >> 64) as u64) } - // r accumulator: 3 words (r[0], r[1], r[2]) to hold intermediate let mut r0: u64 = 0; let mut r1: u64 = 0; let mut r2: u64 = 0; - + let (lo, hi) = umul(a_limbs[0], b_limbs[0]); let (r0_new, c) = r0.overflowing_add(lo); r0 = r0_new; @@ -256,7 +262,7 @@ fn generate_u128_mul( let m = r0.wrapping_mul(INV); let (lo, hi) = umul(m, MODULUS[0]); - let (_, c) = r0.overflowing_add(lo); // r0 + lo should be 0 mod 2^64 + let (_, c) = r0.overflowing_add(lo); let carry = hi.wrapping_add(c as u64); let (lo, hi) = umul(m, MODULUS[1]); @@ -433,18 +439,18 @@ fn generate_small_mul( const N_PRIME: #ty = #n_prime as #ty; const MASK: #mul_ty = #r_mask as #mul_ty; const K_BITS: u32 = #k_bits; - + let a_val = a.value as #mul_ty; let b_val = b.value as #mul_ty; - + let tmp = a_val * b_val; let carry1 = (tmp >> K_BITS) as #ty; let r = (tmp & MASK) as #ty; let m = r.wrapping_mul(N_PRIME); - + let tmp = (r as #mul_ty) + ((m as #mul_ty) * MODULUS_MUL_TY); let carry2 = (tmp >> K_BITS) as #ty; - + let mut r = (carry1 as #mul_ty) + (carry2 as #mul_ty); if r >= MODULUS_MUL_TY { r -= MODULUS_MUL_TY; @@ -460,7 +466,11 @@ fn mod_inverse_pow2(n: u128, k_bits: u32) -> u128 { for _ in 0..k_bits { inv = inv.wrapping_mul(2u128.wrapping_sub(n.wrapping_mul(inv))); } - let mask = if k_bits == 128 { u128::MAX } else { (1u128 << k_bits) - 1 }; + let mask = if k_bits == 128 { + u128::MAX + } else { + (1u128 << k_bits) - 1 + }; inv.wrapping_neg() & mask } diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index ba47ffdff..9bd005001 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -85,20 +85,20 @@ pub(crate) fn generate_montgomery_bigint_casts( quote! { fn from_bigint(a: ark_ff::BigInt<2>) -> Option> { let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); - if val > Self::MODULUS_U128 { + if val > ::MODULUS_U128 { None } else { - let reduced_val = val % Self::MODULUS_U128; - let val_t = Self::T::try_from(reduced_val).ok().unwrap(); - Some(Self::new(val_t)) + let reduced_val = val % ::MODULUS_U128; + let val_t = ::T::try_from(reduced_val).ok().unwrap(); + Some(::new(val_t)) } } }, quote! { fn into_bigint(a: SmallFp) -> ark_ff::BigInt<2> { let mut tmp = a; - let one = SmallFp::from_raw(1 as Self::T); - Self::mul_assign(&mut tmp, &one); + let one = SmallFp::from_raw(1 as ::T); + ::mul_assign(&mut tmp, &one); let val = tmp.value as u128; let lo = val as u64; let hi = (val >> 64) as u64; @@ -144,7 +144,7 @@ pub(crate) fn generate_sqrt_precomputation( const TRACE_MINUS_ONE_DIV_TWO: [u64; 2] = [#lo, #hi]; Some(ark_ff::SqrtPrecomputation::TonelliShanks { two_adicity: #two_adicity, - quadratic_nonresidue_to_trace: SmallFp::from_raw(#qnr_to_trace as Self::T), + quadratic_nonresidue_to_trace: SmallFp::from_raw(#qnr_to_trace as ::T), trace_of_modulus_minus_one_div_two: &TRACE_MINUS_ONE_DIV_TWO, }) }; diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index e8685c703..aabad91de 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -9,8 +9,6 @@ use ark_std::{ use educe::Educe; use num_traits::Unsigned; -pub use ark_ff_macros::SmallFpConfig; - /// A trait that specifies the configuration of a prime field, including the /// modulus, generator, and arithmetic implementation. /// diff --git a/test-curves/benches/smallfp.rs b/test-curves/benches/smallfp.rs index 3aa57069b..8a5c1bc77 100644 --- a/test-curves/benches/smallfp.rs +++ b/test-curves/benches/smallfp.rs @@ -1,10 +1,12 @@ use ark_algebra_bench_templates::*; -use ark_ff::fields::{Fp64, MontBackend, MontConfig, SmallFp, SmallFpConfig}; -use ark_test_curves::{ - smallfp32::{SmallFp32M31}, - smallfp64::{SmallFp64Goldilock}, -}; +use ark_ff::fields::{Fp128, Fp64, MontBackend, MontConfig, SmallFp}; +use ark_test_curves::smallfp::{SmallFp128, SmallFp32M31, SmallFp64Goldilock}; +#[derive(MontConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "3"] +pub struct F128Config; +pub type F128 = Fp128>; #[derive(MontConfig)] #[modulus = "18446744069414584321"] @@ -24,7 +26,7 @@ pub type F32 = Fp64>; pub struct F16Config; pub type F16 = Fp64>; -#[derive(SmallFpConfig)] +#[derive(MontConfig)] #[modulus = "65521"] #[generator = "17"] pub struct SmallFp16Config; @@ -36,14 +38,12 @@ pub type SmallFp16 = SmallFp; pub struct F8Config; pub type F8 = Fp64>; -#[derive(SmallFpConfig)] +#[derive(MontConfig)] #[modulus = "251"] #[generator = "6"] pub struct SmallFp8Config; pub type SmallFp8 = SmallFp; - - f_bench!(prime, "F8", F8); f_bench!(prime, "SmallF8Mont", SmallFp8); @@ -56,13 +56,18 @@ f_bench!(prime, "SmallF32Mont", SmallFp32M31); f_bench!(prime, "F64", F64); f_bench!(prime, "SmallF64Mont", SmallFp64Goldilock); +f_bench!(prime, "F128", F128); +f_bench!(prime, "SmallF128Mont", SmallFp128); + criterion_main!( - f8::benches, - smallfp8::benches, - f16::benches, - smallfp16::benches, - f32::benches, - smallfp32m31::benches, - f64::benches, - smallfp64goldilock::benches, + // f8::benches, + // smallfp8::benches, + // f16::benches, + // smallfp16::benches, + // f32::benches, + // smallfp32m31::benches, + // f64::benches, + // smallfp64goldilock::benches, + f128::benches, + smallfp128::benches ); diff --git a/test-curves/src/lib.rs b/test-curves/src/lib.rs index 15abae16d..2557b0190 100644 --- a/test-curves/src/lib.rs +++ b/test-curves/src/lib.rs @@ -32,8 +32,4 @@ pub mod secp256k1; pub mod fp128; -pub mod smallfp128; -pub mod smallfp16; -pub mod smallfp32; -pub mod smallfp64; -pub mod smallfp8; +pub mod smallfp; diff --git a/test-curves/src/smallfp.rs b/test-curves/src/smallfp.rs new file mode 100644 index 000000000..af8bfdab8 --- /dev/null +++ b/test-curves/src/smallfp.rs @@ -0,0 +1,65 @@ +use ark_ff::{MontConfig, SmallFp}; + +#[derive(MontConfig)] +#[modulus = "251"] +#[generator = "6"] +pub struct SmallFp8Config; +pub type SmallFp8 = SmallFp; + +#[derive(MontConfig)] +#[modulus = "127"] +#[generator = "3"] +pub struct SmallFp8ConfigM7; +pub type SmallFp8M7 = SmallFp; + +#[derive(MontConfig)] +#[modulus = "65521"] +#[generator = "17"] +pub struct SmallFp16Config; +pub type SmallFp16 = SmallFp; + +#[derive(MontConfig)] +#[modulus = "8191"] +#[generator = "17"] +pub struct SmallFp16ConfigM13; +pub type SmallFp16M13 = SmallFp; + +#[derive(MontConfig)] +#[modulus = "2147483647"] +#[generator = "7"] +pub struct SmallFp32ConfigM31; +pub type SmallFp32M31 = SmallFp; + +#[derive(MontConfig)] +#[modulus = "2013265921"] +#[generator = "31"] +pub struct SmallFpBabybearConfig; +pub type SmallFp32Babybear = SmallFp; + +#[derive(MontConfig)] +#[modulus = "18446744069414584321"] // Goldilocks prime 2^64 - 2^32 + 1 +#[generator = "7"] +pub struct SmallFp64GoldilockConfig; +pub type SmallFp64Goldilock = SmallFp; + +#[derive(MontConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "3"] +pub struct SmallFp128Config; +pub type SmallFp128 = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f8; SmallFp8); + test_small_field!(f8_m7; SmallFp8M7); + test_small_field!(f16; SmallFp16); + test_small_field!(f16_m13; SmallFp16M13); + test_small_field!(f32_m31; SmallFp32M31); + test_small_field!(f32_babybear; SmallFp32Babybear); + test_small_field!(f64_goldilock; SmallFp64Goldilock); + test_small_field!(f128; SmallFp128); +} diff --git a/test-curves/src/smallfp128.rs b/test-curves/src/smallfp128.rs deleted file mode 100644 index bff794bf3..000000000 --- a/test-curves/src/smallfp128.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "3"] -pub struct SmallFp128Config; -pub type SmallFp128 = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f128; SmallFp128); -} diff --git a/test-curves/src/smallfp16.rs b/test-curves/src/smallfp16.rs deleted file mode 100644 index 1fc5c883c..000000000 --- a/test-curves/src/smallfp16.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "65521"] -#[generator = "17"] -pub struct SmallFp16Config; -pub type SmallFp16 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "8191"] -#[generator = "17"] -pub struct SmallFp16ConfigM13; -pub type SmallFp16M13 = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f16; SmallFp16); - test_small_field!(f16_mont_m13; SmallFp16M13); -} diff --git a/test-curves/src/smallfp32.rs b/test-curves/src/smallfp32.rs deleted file mode 100644 index 6c42f938d..000000000 --- a/test-curves/src/smallfp32.rs +++ /dev/null @@ -1,24 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "2147483647"] -#[generator = "7"] -pub struct SmallFp32ConfigM31; -pub type SmallFp32M31 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "2013265921"] -#[generator = "31"] -pub struct SmallFpBabybearConfig; -pub type SmallFp32Babybear = SmallFp; - - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f32; SmallFp32M31); - test_small_field!(f32_mont_babybear; SmallFp32Babybear); -} diff --git a/test-curves/src/smallfp64.rs b/test-curves/src/smallfp64.rs deleted file mode 100644 index a65235c2a..000000000 --- a/test-curves/src/smallfp64.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 -#[generator = "7"] -pub struct SmallFp64GoldilockConfig; -pub type SmallFp64Goldilock = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f64; SmallFp64Goldilock); -} diff --git a/test-curves/src/smallfp8.rs b/test-curves/src/smallfp8.rs deleted file mode 100644 index 3bc12fc21..000000000 --- a/test-curves/src/smallfp8.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "251"] -#[generator = "6"] -pub struct SmallFp8Config; -pub type SmallFp8 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "127"] -#[generator = "3"] -pub struct SmallFp8ConfigM7; -pub type SmallFp8M7 = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f8; SmallFp8); - test_small_field!(f8_mont_m7; SmallFp8M7); -} From a339f6e0efe8321e46a515af551eff0181beb061 Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 20 Feb 2026 15:18:33 +0100 Subject: [PATCH 45/47] Revert "unify montconfig and smallconfig macros (intermediate step)" This reverts commit c9cd6750f82df164e9a4bd32556b830d6e9ea9b7. --- ff-macros/src/lib.rs | 49 ++++++++------ ff-macros/src/montgomery/sum_of_products.rs | 2 +- ff-macros/src/small_fp/montgomery_backend.rs | 47 +++++++------- ff-macros/src/small_fp/utils.rs | 14 ++-- .../models/small_fp/small_fp_backend.rs | 2 + test-curves/benches/smallfp.rs | 35 ++++------ test-curves/src/lib.rs | 6 +- test-curves/src/smallfp.rs | 65 ------------------- test-curves/src/smallfp128.rs | 16 +++++ test-curves/src/smallfp16.rs | 23 +++++++ test-curves/src/smallfp32.rs | 23 +++++++ test-curves/src/smallfp64.rs | 16 +++++ test-curves/src/smallfp8.rs | 23 +++++++ 13 files changed, 181 insertions(+), 140 deletions(-) delete mode 100644 test-curves/src/smallfp.rs create mode 100644 test-curves/src/smallfp128.rs create mode 100644 test-curves/src/smallfp16.rs create mode 100644 test-curves/src/smallfp32.rs create mode 100644 test-curves/src/smallfp64.rs create mode 100644 test-curves/src/smallfp8.rs diff --git a/ff-macros/src/lib.rs b/ff-macros/src/lib.rs index b6dd3c0b5..e98e2ab09 100644 --- a/ff-macros/src/lib.rs +++ b/ff-macros/src/lib.rs @@ -65,31 +65,40 @@ pub fn mont_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let small_subgroup_power: Option = fetch_attr("small_subgroup_power", &ast.attrs) .map(|s| s.parse().expect("small_subgroup_power should be a number")); - // TODO: Both macros are being generated at the same time. Create unified type - let mont_impl = montgomery::mont_config_helper( - modulus.clone(), - generator.clone(), + montgomery::mont_config_helper( + modulus, + generator, small_subgroup_base, small_subgroup_power, - ast.ident.clone(), - ); - - let small_fp_impl = if modulus < (BigUint::from(1u128) << 127) { - use num_traits::ToPrimitive; - let modulus_u128 = modulus.to_u128().unwrap(); - let generator_u128 = generator.to_u128().unwrap(); - small_fp::small_fp_config_helper(modulus_u128, generator_u128, ast.ident) - } else { - quote::quote! {} - }; - - quote::quote! { - #mont_impl - #small_fp_impl - } + ast.ident, + ) .into() } +/// Derive the `SmallFpConfig` trait for small prime fields. +/// +/// The attributes available to this macro are: +/// * `modulus`: Specify the prime modulus underlying this prime field. +/// * `generator`: Specify the generator of the multiplicative subgroup. +/// +/// Note: Only Montgomery backend is supported. +#[proc_macro_derive(SmallFpConfig, attributes(modulus, generator))] +pub fn small_fp_config(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + + let modulus: u128 = fetch_attr("modulus", &ast.attrs) + .expect("Please supply a modulus attribute") + .parse() + .expect("Modulus should be a number"); + + let generator: u128 = fetch_attr("generator", &ast.attrs) + .expect("Please supply a generator attribute") + .parse() + .expect("Generator should be a number"); + + small_fp::small_fp_config_helper(modulus, generator, ast.ident).into() +} + const ARG_MSG: &str = "Failed to parse unroll threshold; must be a positive integer"; /// Attribute used to unroll for loops found inside a function block. diff --git a/ff-macros/src/montgomery/sum_of_products.rs b/ff-macros/src/montgomery/sum_of_products.rs index 42d4461a6..57b992e7a 100644 --- a/ff-macros/src/montgomery/sum_of_products.rs +++ b/ff-macros/src/montgomery/sum_of_products.rs @@ -73,7 +73,7 @@ pub(super) fn sum_of_products_impl(num_limbs: usize, modulus: &[u64]) -> proc_ma } else { a.chunks(#chunk_size).zip(b.chunks(#chunk_size)).map(|(a, b)| { if a.len() == #chunk_size { - >::sum_of_products::<#chunk_size>(a.try_into().unwrap(), b.try_into().unwrap()) + Self::sum_of_products::<#chunk_size>(a.try_into().unwrap(), b.try_into().unwrap()) } else { a.iter().zip(b).map(|(a, b)| *a * b).sum() } diff --git a/ff-macros/src/small_fp/montgomery_backend.rs b/ff-macros/src/small_fp/montgomery_backend.rs index 50ff00b44..add81ed2b 100644 --- a/ff-macros/src/small_fp/montgomery_backend.rs +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -63,7 +63,7 @@ pub(crate) fn backend_impl( #[inline(always)] fn add_assign(a: &mut SmallFp, b: &SmallFp) { let val = a.value.wrapping_add(b.value); - a.value = if val >= ::MODULUS { val - ::MODULUS } else { val }; + a.value = if val >= Self::MODULUS { val - Self::MODULUS } else { val }; } } } else { @@ -72,10 +72,10 @@ pub(crate) fn backend_impl( fn add_assign(a: &mut SmallFp, b: &SmallFp) { let (mut val, overflow) = a.value.overflowing_add(b.value); if overflow { - val = ::T::MAX - ::MODULUS + 1 + val + val = Self::T::MAX - Self::MODULUS + 1 + val } - if val >= ::MODULUS { - val -= ::MODULUS; + if val >= Self::MODULUS { + val -= Self::MODULUS; } a.value = val; } @@ -103,20 +103,20 @@ pub(crate) fn backend_impl( if a.value >= b.value { a.value -= b.value; } else { - a.value = ::MODULUS - (b.value - a.value); + a.value = Self::MODULUS - (b.value - a.value); } } #[inline(always)] fn double_in_place(a: &mut SmallFp) { let tmp = *a; - ::add_assign(a, &tmp); + Self::add_assign(a, &tmp); } #[inline(always)] fn neg_in_place(a: &mut SmallFp) { - if a.value != ::ZERO.value { - a.value = ::MODULUS - a.value; + if a.value != Self::ZERO.value { + a.value = Self::MODULUS - a.value; } } @@ -129,23 +129,23 @@ pub(crate) fn backend_impl( match T { 1 => { let mut prod = a[0]; - ::mul_assign(&mut prod, &b[0]); + Self::mul_assign(&mut prod, &b[0]); prod }, 2 => { let mut prod1 = a[0]; - ::mul_assign(&mut prod1, &b[0]); + Self::mul_assign(&mut prod1, &b[0]); let mut prod2 = a[1]; - ::mul_assign(&mut prod2, &b[1]); - ::add_assign(&mut prod1, &prod2); + Self::mul_assign(&mut prod2, &b[1]); + Self::add_assign(&mut prod1, &prod2); prod1 }, _ => { - let mut acc = ::ZERO; + let mut acc = Self::ZERO; for (x, y) in a.iter().zip(b.iter()) { let mut prod = *x; - ::mul_assign(&mut prod, y); - ::add_assign(&mut acc, &prod); + Self::mul_assign(&mut prod, y); + Self::add_assign(&mut acc, &prod); } acc } @@ -155,7 +155,7 @@ pub(crate) fn backend_impl( #[inline(always)] fn square_in_place(a: &mut SmallFp) { let tmp = *a; - ::mul_assign(a, &tmp); + Self::mul_assign(a, &tmp); } fn inverse(a: &SmallFp) -> Option> { @@ -163,17 +163,17 @@ pub(crate) fn backend_impl( return None; } - let mut result = ::ONE; + let mut result = Self::ONE; let mut base = *a; - let mut exp = ::MODULUS - 2; + let mut exp = Self::MODULUS - 2; while exp > 0 { if exp & 1 == 1 { - ::mul_assign(&mut result, &base); + Self::mul_assign(&mut result, &base); } let mut sq = base; - ::square_in_place(&mut sq); + Self::square_in_place(&mut sq); base = sq; exp >>= 1; } @@ -183,10 +183,10 @@ pub(crate) fn backend_impl( #[inline] fn new(value: Self::T) -> SmallFp { - let reduced_value = value % ::MODULUS; + let reduced_value = value % Self::MODULUS; let mut tmp = SmallFp::from_raw(reduced_value); let r2_elem = SmallFp::from_raw(#r2 as Self::T); - ::mul_assign(&mut tmp, &r2_elem); + Self::mul_assign(&mut tmp, &r2_elem); tmp } @@ -243,6 +243,7 @@ fn generate_u128_mul( (full as u64, (full >> 64) as u64) } + // r accumulator: 3 words (r[0], r[1], r[2]) to hold intermediate let mut r0: u64 = 0; let mut r1: u64 = 0; let mut r2: u64 = 0; @@ -262,7 +263,7 @@ fn generate_u128_mul( let m = r0.wrapping_mul(INV); let (lo, hi) = umul(m, MODULUS[0]); - let (_, c) = r0.overflowing_add(lo); + let (_, c) = r0.overflowing_add(lo); // r0 + lo should be 0 mod 2^64 let carry = hi.wrapping_add(c as u64); let (lo, hi) = umul(m, MODULUS[1]); diff --git a/ff-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs index 9bd005001..ba47ffdff 100644 --- a/ff-macros/src/small_fp/utils.rs +++ b/ff-macros/src/small_fp/utils.rs @@ -85,20 +85,20 @@ pub(crate) fn generate_montgomery_bigint_casts( quote! { fn from_bigint(a: ark_ff::BigInt<2>) -> Option> { let val = (a.0[0] as u128) + ((a.0[1] as u128) << 64); - if val > ::MODULUS_U128 { + if val > Self::MODULUS_U128 { None } else { - let reduced_val = val % ::MODULUS_U128; - let val_t = ::T::try_from(reduced_val).ok().unwrap(); - Some(::new(val_t)) + let reduced_val = val % Self::MODULUS_U128; + let val_t = Self::T::try_from(reduced_val).ok().unwrap(); + Some(Self::new(val_t)) } } }, quote! { fn into_bigint(a: SmallFp) -> ark_ff::BigInt<2> { let mut tmp = a; - let one = SmallFp::from_raw(1 as ::T); - ::mul_assign(&mut tmp, &one); + let one = SmallFp::from_raw(1 as Self::T); + Self::mul_assign(&mut tmp, &one); let val = tmp.value as u128; let lo = val as u64; let hi = (val >> 64) as u64; @@ -144,7 +144,7 @@ pub(crate) fn generate_sqrt_precomputation( const TRACE_MINUS_ONE_DIV_TWO: [u64; 2] = [#lo, #hi]; Some(ark_ff::SqrtPrecomputation::TonelliShanks { two_adicity: #two_adicity, - quadratic_nonresidue_to_trace: SmallFp::from_raw(#qnr_to_trace as ::T), + quadratic_nonresidue_to_trace: SmallFp::from_raw(#qnr_to_trace as Self::T), trace_of_modulus_minus_one_div_two: &TRACE_MINUS_ONE_DIV_TWO, }) }; diff --git a/ff/src/fields/models/small_fp/small_fp_backend.rs b/ff/src/fields/models/small_fp/small_fp_backend.rs index aabad91de..e8685c703 100644 --- a/ff/src/fields/models/small_fp/small_fp_backend.rs +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -9,6 +9,8 @@ use ark_std::{ use educe::Educe; use num_traits::Unsigned; +pub use ark_ff_macros::SmallFpConfig; + /// A trait that specifies the configuration of a prime field, including the /// modulus, generator, and arithmetic implementation. /// diff --git a/test-curves/benches/smallfp.rs b/test-curves/benches/smallfp.rs index 8a5c1bc77..1a0c32d61 100644 --- a/test-curves/benches/smallfp.rs +++ b/test-curves/benches/smallfp.rs @@ -1,12 +1,6 @@ use ark_algebra_bench_templates::*; -use ark_ff::fields::{Fp128, Fp64, MontBackend, MontConfig, SmallFp}; -use ark_test_curves::smallfp::{SmallFp128, SmallFp32M31, SmallFp64Goldilock}; - -#[derive(MontConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "3"] -pub struct F128Config; -pub type F128 = Fp128>; +use ark_ff::fields::{Fp64, MontBackend, MontConfig, SmallFp, SmallFpConfig}; +use ark_test_curves::{smallfp32::SmallFp32M31, smallfp64::SmallFp64Goldilock}; #[derive(MontConfig)] #[modulus = "18446744069414584321"] @@ -26,7 +20,7 @@ pub type F32 = Fp64>; pub struct F16Config; pub type F16 = Fp64>; -#[derive(MontConfig)] +#[derive(SmallFpConfig)] #[modulus = "65521"] #[generator = "17"] pub struct SmallFp16Config; @@ -38,7 +32,7 @@ pub type SmallFp16 = SmallFp; pub struct F8Config; pub type F8 = Fp64>; -#[derive(MontConfig)] +#[derive(SmallFpConfig)] #[modulus = "251"] #[generator = "6"] pub struct SmallFp8Config; @@ -56,18 +50,13 @@ f_bench!(prime, "SmallF32Mont", SmallFp32M31); f_bench!(prime, "F64", F64); f_bench!(prime, "SmallF64Mont", SmallFp64Goldilock); -f_bench!(prime, "F128", F128); -f_bench!(prime, "SmallF128Mont", SmallFp128); - criterion_main!( - // f8::benches, - // smallfp8::benches, - // f16::benches, - // smallfp16::benches, - // f32::benches, - // smallfp32m31::benches, - // f64::benches, - // smallfp64goldilock::benches, - f128::benches, - smallfp128::benches + f8::benches, + smallfp8::benches, + f16::benches, + smallfp16::benches, + f32::benches, + smallfp32m31::benches, + f64::benches, + smallfp64goldilock::benches, ); diff --git a/test-curves/src/lib.rs b/test-curves/src/lib.rs index 2557b0190..15abae16d 100644 --- a/test-curves/src/lib.rs +++ b/test-curves/src/lib.rs @@ -32,4 +32,8 @@ pub mod secp256k1; pub mod fp128; -pub mod smallfp; +pub mod smallfp128; +pub mod smallfp16; +pub mod smallfp32; +pub mod smallfp64; +pub mod smallfp8; diff --git a/test-curves/src/smallfp.rs b/test-curves/src/smallfp.rs deleted file mode 100644 index af8bfdab8..000000000 --- a/test-curves/src/smallfp.rs +++ /dev/null @@ -1,65 +0,0 @@ -use ark_ff::{MontConfig, SmallFp}; - -#[derive(MontConfig)] -#[modulus = "251"] -#[generator = "6"] -pub struct SmallFp8Config; -pub type SmallFp8 = SmallFp; - -#[derive(MontConfig)] -#[modulus = "127"] -#[generator = "3"] -pub struct SmallFp8ConfigM7; -pub type SmallFp8M7 = SmallFp; - -#[derive(MontConfig)] -#[modulus = "65521"] -#[generator = "17"] -pub struct SmallFp16Config; -pub type SmallFp16 = SmallFp; - -#[derive(MontConfig)] -#[modulus = "8191"] -#[generator = "17"] -pub struct SmallFp16ConfigM13; -pub type SmallFp16M13 = SmallFp; - -#[derive(MontConfig)] -#[modulus = "2147483647"] -#[generator = "7"] -pub struct SmallFp32ConfigM31; -pub type SmallFp32M31 = SmallFp; - -#[derive(MontConfig)] -#[modulus = "2013265921"] -#[generator = "31"] -pub struct SmallFpBabybearConfig; -pub type SmallFp32Babybear = SmallFp; - -#[derive(MontConfig)] -#[modulus = "18446744069414584321"] // Goldilocks prime 2^64 - 2^32 + 1 -#[generator = "7"] -pub struct SmallFp64GoldilockConfig; -pub type SmallFp64Goldilock = SmallFp; - -#[derive(MontConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "3"] -pub struct SmallFp128Config; -pub type SmallFp128 = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f8; SmallFp8); - test_small_field!(f8_m7; SmallFp8M7); - test_small_field!(f16; SmallFp16); - test_small_field!(f16_m13; SmallFp16M13); - test_small_field!(f32_m31; SmallFp32M31); - test_small_field!(f32_babybear; SmallFp32Babybear); - test_small_field!(f64_goldilock; SmallFp64Goldilock); - test_small_field!(f128; SmallFp128); -} diff --git a/test-curves/src/smallfp128.rs b/test-curves/src/smallfp128.rs new file mode 100644 index 000000000..bff794bf3 --- /dev/null +++ b/test-curves/src/smallfp128.rs @@ -0,0 +1,16 @@ +use ark_ff::{SmallFp, SmallFpConfig}; + +#[derive(SmallFpConfig)] +#[modulus = "143244528689204659050391023439224324689"] +#[generator = "3"] +pub struct SmallFp128Config; +pub type SmallFp128 = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f128; SmallFp128); +} diff --git a/test-curves/src/smallfp16.rs b/test-curves/src/smallfp16.rs new file mode 100644 index 000000000..1fc5c883c --- /dev/null +++ b/test-curves/src/smallfp16.rs @@ -0,0 +1,23 @@ +use ark_ff::{SmallFp, SmallFpConfig}; + +#[derive(SmallFpConfig)] +#[modulus = "65521"] +#[generator = "17"] +pub struct SmallFp16Config; +pub type SmallFp16 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "8191"] +#[generator = "17"] +pub struct SmallFp16ConfigM13; +pub type SmallFp16M13 = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f16; SmallFp16); + test_small_field!(f16_mont_m13; SmallFp16M13); +} diff --git a/test-curves/src/smallfp32.rs b/test-curves/src/smallfp32.rs new file mode 100644 index 000000000..333684b6f --- /dev/null +++ b/test-curves/src/smallfp32.rs @@ -0,0 +1,23 @@ +use ark_ff::{SmallFp, SmallFpConfig}; + +#[derive(SmallFpConfig)] +#[modulus = "2147483647"] +#[generator = "7"] +pub struct SmallFp32ConfigM31; +pub type SmallFp32M31 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "2013265921"] +#[generator = "31"] +pub struct SmallFpBabybearConfig; +pub type SmallFp32Babybear = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f32; SmallFp32M31); + test_small_field!(f32_mont_babybear; SmallFp32Babybear); +} diff --git a/test-curves/src/smallfp64.rs b/test-curves/src/smallfp64.rs new file mode 100644 index 000000000..a65235c2a --- /dev/null +++ b/test-curves/src/smallfp64.rs @@ -0,0 +1,16 @@ +use ark_ff::{SmallFp, SmallFpConfig}; + +#[derive(SmallFpConfig)] +#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 +#[generator = "7"] +pub struct SmallFp64GoldilockConfig; +pub type SmallFp64Goldilock = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f64; SmallFp64Goldilock); +} diff --git a/test-curves/src/smallfp8.rs b/test-curves/src/smallfp8.rs new file mode 100644 index 000000000..3bc12fc21 --- /dev/null +++ b/test-curves/src/smallfp8.rs @@ -0,0 +1,23 @@ +use ark_ff::{SmallFp, SmallFpConfig}; + +#[derive(SmallFpConfig)] +#[modulus = "251"] +#[generator = "6"] +pub struct SmallFp8Config; +pub type SmallFp8 = SmallFp; + +#[derive(SmallFpConfig)] +#[modulus = "127"] +#[generator = "3"] +pub struct SmallFp8ConfigM7; +pub type SmallFp8M7 = SmallFp; + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f8; SmallFp8); + test_small_field!(f8_mont_m7; SmallFp8M7); +} From edfe2eedea4357a5d7fec40fd6c31860402ffe5b Mon Sep 17 00:00:00 2001 From: benbencik Date: Fri, 20 Feb 2026 21:05:03 +0100 Subject: [PATCH 46/47] add define_field macro --- ff-macros/src/lib.rs | 45 ++++++++++++++++++ ff-macros/src/utils.rs | 83 +++++++++++++++++++++++++++++++++- ff/src/fields/mod.rs | 1 + test-curves/benches/smallfp.rs | 16 +------ test-curves/src/lib.rs | 6 +-- test-curves/src/smallfp.rs | 46 +++++++++++++++++++ test-curves/src/smallfp128.rs | 16 ------- test-curves/src/smallfp16.rs | 23 ---------- test-curves/src/smallfp32.rs | 23 ---------- test-curves/src/smallfp64.rs | 16 ------- test-curves/src/smallfp8.rs | 23 ---------- 11 files changed, 177 insertions(+), 121 deletions(-) create mode 100644 test-curves/src/smallfp.rs delete mode 100644 test-curves/src/smallfp128.rs delete mode 100644 test-curves/src/smallfp16.rs delete mode 100644 test-curves/src/smallfp32.rs delete mode 100644 test-curves/src/smallfp64.rs delete mode 100644 test-curves/src/smallfp8.rs diff --git a/ff-macros/src/lib.rs b/ff-macros/src/lib.rs index e98e2ab09..30ffbda34 100644 --- a/ff-macros/src/lib.rs +++ b/ff-macros/src/lib.rs @@ -9,6 +9,7 @@ use num_bigint::BigUint; use proc_macro::TokenStream; +use quote::format_ident; use syn::{Expr, ExprLit, Item, ItemFn, Lit, Meta}; mod montgomery; @@ -28,6 +29,50 @@ pub fn to_sign_and_limbs(input: TokenStream) -> TokenStream { quote::quote!(#tuple).into() } +/// Define optimal field type and its corresponding config +/// +/// If modulus fits into a native datatype, the resulting type is SmallFp<Config> +/// Otherwise, it is the appropriately sized Fp*Config, N>> +#[proc_macro] +pub fn define_field(input: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(input as utils::FieldArgs); + + let modulus_big = args + .modulus + .parse::() + .expect("modulus should be a decimal integer string"); + + let limbs = utils::str_to_limbs_u64(&args.modulus).1.len(); + + let name = args.name; + let config_name = format_ident!("{}Config", name); + let modulus = syn::LitStr::new(&args.modulus, proc_macro2::Span::call_site()); + let generator = syn::LitStr::new(&args.generator, proc_macro2::Span::call_site()); + + let is_small_modulus = modulus_big < (BigUint::from(1u128) << 127); + let derives = if is_small_modulus { + quote::quote! { #[derive(ark_ff::SmallFpConfig)] } + } else { + quote::quote! { #[derive(ark_ff::MontConfig)] } + }; + + let field_ty = if is_small_modulus { + quote::quote! { ark_ff::SmallFp<#config_name> } + } else { + let fp_alias = utils::fp_alias_for_limbs(limbs); + quote::quote! { #fp_alias> } + }; + + quote::quote! { + #derives + #[modulus = #modulus] + #[generator = #generator] + pub struct #config_name; + pub type #name = #field_ty; + } + .into() +} + /// Derive the `MontConfig` trait. /// /// The attributes available to this macro are diff --git a/ff-macros/src/utils.rs b/ff-macros/src/utils.rs index 4ebdfcd4f..12450bdd9 100644 --- a/ff-macros/src/utils.rs +++ b/ff-macros/src/utils.rs @@ -3,7 +3,88 @@ use std::str::FromStr; use num_bigint::{BigInt, Sign}; use num_traits::Num; use proc_macro::TokenStream; -use syn::{Expr, Lit}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use syn::parse::{Parse, ParseStream}; +use syn::{Expr, Lit, Token}; + +pub(crate) struct FieldArgs { + pub modulus: String, + pub generator: String, + pub name: Ident, +} + +impl Parse for FieldArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut modulus: Option = None; + let mut generator: Option = None; + let mut name: Option = None; + + while !input.is_empty() { + let key: Ident = input.parse()?; + input.parse::()?; + + match key.to_string().as_str() { + "modulus" => { + let lit: syn::LitStr = input.parse()?; + modulus = Some(lit.value()); + }, + "generator" => { + let lit: syn::LitStr = input.parse()?; + generator = Some(lit.value()); + }, + "name" => { + if input.peek(syn::LitStr) { + let lit: syn::LitStr = input.parse()?; + name = Some(Ident::new(&lit.value(), Span::call_site())); + } else { + name = Some(input.parse()?); + } + }, + _ => { + return Err(syn::Error::new( + key.span(), + "unknown argument; expected one of: modulus, generator, name", + )) + }, + } + + if input.peek(Token![,]) { + input.parse::()?; + } + } + + Ok(Self { + modulus: modulus.ok_or_else(|| { + syn::Error::new(Span::call_site(), "missing required argument: modulus") + })?, + generator: generator.ok_or_else(|| { + syn::Error::new(Span::call_site(), "missing required argument: generator") + })?, + name: name.ok_or_else(|| { + syn::Error::new(Span::call_site(), "missing required argument: name") + })?, + }) + } +} + +pub(crate) fn fp_alias_for_limbs(limbs: usize) -> TokenStream2 { + match limbs { + 1 => quote::quote!(ark_ff::Fp64), + 2 => quote::quote!(ark_ff::Fp128), + 3 => quote::quote!(ark_ff::Fp192), + 4 => quote::quote!(ark_ff::Fp256), + 5 => quote::quote!(ark_ff::Fp320), + 6 => quote::quote!(ark_ff::Fp384), + 7 => quote::quote!(ark_ff::Fp448), + 8 => quote::quote!(ark_ff::Fp512), + 9 => quote::quote!(ark_ff::Fp576), + 10 => quote::quote!(ark_ff::Fp640), + 11 => quote::quote!(ark_ff::Fp704), + 12 => quote::quote!(ark_ff::Fp768), + 13 => quote::quote!(ark_ff::Fp832), + _ => panic!("unsupported field size: {limbs} limbs (supported range is 1..=13)"), + } +} pub(crate) fn parse_string(input: TokenStream) -> Option { let input: Expr = syn::parse(input).unwrap(); diff --git a/ff/src/fields/mod.rs b/ff/src/fields/mod.rs index fdaa2d7c1..ec88635ed 100644 --- a/ff/src/fields/mod.rs +++ b/ff/src/fields/mod.rs @@ -12,6 +12,7 @@ use ark_std::{ }; pub use ark_ff_macros; +pub use ark_ff_macros::define_field; pub use num_traits::{One, Zero}; use zeroize::Zeroize; diff --git a/test-curves/benches/smallfp.rs b/test-curves/benches/smallfp.rs index 1a0c32d61..150f5f869 100644 --- a/test-curves/benches/smallfp.rs +++ b/test-curves/benches/smallfp.rs @@ -1,6 +1,6 @@ use ark_algebra_bench_templates::*; -use ark_ff::fields::{Fp64, MontBackend, MontConfig, SmallFp, SmallFpConfig}; -use ark_test_curves::{smallfp32::SmallFp32M31, smallfp64::SmallFp64Goldilock}; +use ark_ff::fields::{Fp64, MontBackend, MontConfig}; +use ark_test_curves::smallfp::{SmallFp16, SmallFp32M31, SmallFp64Goldilock, SmallFp8}; #[derive(MontConfig)] #[modulus = "18446744069414584321"] @@ -20,24 +20,12 @@ pub type F32 = Fp64>; pub struct F16Config; pub type F16 = Fp64>; -#[derive(SmallFpConfig)] -#[modulus = "65521"] -#[generator = "17"] -pub struct SmallFp16Config; -pub type SmallFp16 = SmallFp; - #[derive(MontConfig)] #[modulus = "251"] #[generator = "6"] pub struct F8Config; pub type F8 = Fp64>; -#[derive(SmallFpConfig)] -#[modulus = "251"] -#[generator = "6"] -pub struct SmallFp8Config; -pub type SmallFp8 = SmallFp; - f_bench!(prime, "F8", F8); f_bench!(prime, "SmallF8Mont", SmallFp8); diff --git a/test-curves/src/lib.rs b/test-curves/src/lib.rs index 15abae16d..2557b0190 100644 --- a/test-curves/src/lib.rs +++ b/test-curves/src/lib.rs @@ -32,8 +32,4 @@ pub mod secp256k1; pub mod fp128; -pub mod smallfp128; -pub mod smallfp16; -pub mod smallfp32; -pub mod smallfp64; -pub mod smallfp8; +pub mod smallfp; diff --git a/test-curves/src/smallfp.rs b/test-curves/src/smallfp.rs new file mode 100644 index 000000000..731fba0f9 --- /dev/null +++ b/test-curves/src/smallfp.rs @@ -0,0 +1,46 @@ +use ark_ff::define_field; + +define_field!(modulus = "251", generator = "6", name = SmallFp8,); + +define_field!(modulus = "65521", generator = "17", name = SmallFp16,); + +// Mersenne13 prime 2^13 - 1 +define_field!(modulus = "8191", generator = "17", name = SmallFp16M13,); + +// Mersenne31 prime 2^31 - 1 +define_field!(modulus = "2147483647", generator = "7", name = SmallFp32M31,); + +// Babybear prime 2^31 - 2^27 + 1 +define_field!( + modulus = "2013265921", + generator = "31", + name = SmallFp32Babybear, +); + +// Goldilocks prime 2^64 - 2^32 + 1 +define_field!( + modulus = "18446744069414584321", + generator = "7", + name = SmallFp64Goldilock, +); + +define_field!( + modulus = "143244528689204659050391023439224324689", + generator = "3", + name = SmallFp128, +); + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_std::vec; + + test_small_field!(f8; SmallFp8); + test_small_field!(f16; SmallFp16); + test_small_field!(f16_mont_m13; SmallFp16M13); + test_small_field!(f32; SmallFp32M31); + test_small_field!(f32_mont_babybear; SmallFp32Babybear); + test_small_field!(f64; SmallFp64Goldilock); + test_small_field!(f128; SmallFp128); +} diff --git a/test-curves/src/smallfp128.rs b/test-curves/src/smallfp128.rs deleted file mode 100644 index bff794bf3..000000000 --- a/test-curves/src/smallfp128.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "143244528689204659050391023439224324689"] -#[generator = "3"] -pub struct SmallFp128Config; -pub type SmallFp128 = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f128; SmallFp128); -} diff --git a/test-curves/src/smallfp16.rs b/test-curves/src/smallfp16.rs deleted file mode 100644 index 1fc5c883c..000000000 --- a/test-curves/src/smallfp16.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "65521"] -#[generator = "17"] -pub struct SmallFp16Config; -pub type SmallFp16 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "8191"] -#[generator = "17"] -pub struct SmallFp16ConfigM13; -pub type SmallFp16M13 = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f16; SmallFp16); - test_small_field!(f16_mont_m13; SmallFp16M13); -} diff --git a/test-curves/src/smallfp32.rs b/test-curves/src/smallfp32.rs deleted file mode 100644 index 333684b6f..000000000 --- a/test-curves/src/smallfp32.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "2147483647"] -#[generator = "7"] -pub struct SmallFp32ConfigM31; -pub type SmallFp32M31 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "2013265921"] -#[generator = "31"] -pub struct SmallFpBabybearConfig; -pub type SmallFp32Babybear = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f32; SmallFp32M31); - test_small_field!(f32_mont_babybear; SmallFp32Babybear); -} diff --git a/test-curves/src/smallfp64.rs b/test-curves/src/smallfp64.rs deleted file mode 100644 index a65235c2a..000000000 --- a/test-curves/src/smallfp64.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "18446744069414584321"] // Goldilock's prime 2^64 - 2^32 + 1 -#[generator = "7"] -pub struct SmallFp64GoldilockConfig; -pub type SmallFp64Goldilock = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f64; SmallFp64Goldilock); -} diff --git a/test-curves/src/smallfp8.rs b/test-curves/src/smallfp8.rs deleted file mode 100644 index 3bc12fc21..000000000 --- a/test-curves/src/smallfp8.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ark_ff::{SmallFp, SmallFpConfig}; - -#[derive(SmallFpConfig)] -#[modulus = "251"] -#[generator = "6"] -pub struct SmallFp8Config; -pub type SmallFp8 = SmallFp; - -#[derive(SmallFpConfig)] -#[modulus = "127"] -#[generator = "3"] -pub struct SmallFp8ConfigM7; -pub type SmallFp8M7 = SmallFp; - -#[cfg(test)] -mod tests { - use super::*; - use ark_algebra_test_templates::*; - use ark_std::vec; - - test_small_field!(f8; SmallFp8); - test_small_field!(f8_mont_m7; SmallFp8M7); -} From 80763012a8158e1ec56e58e325dfab7c0495e1ed Mon Sep 17 00:00:00 2001 From: benbencik Date: Mon, 2 Mar 2026 14:42:52 +0100 Subject: [PATCH 47/47] (de)serialize directly in mont representation --- ff/src/fields/models/small_fp/serialize.rs | 70 +++++++--------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/ff/src/fields/models/small_fp/serialize.rs b/ff/src/fields/models/small_fp/serialize.rs index 606f666ff..a0f1d1458 100644 --- a/ff/src/fields/models/small_fp/serialize.rs +++ b/ff/src/fields/models/small_fp/serialize.rs @@ -1,10 +1,9 @@ use crate::fields::models::small_fp::small_fp_backend::{SmallFp, SmallFpConfig}; -use crate::{BigInt, PrimeField, Zero}; +use crate::{PrimeField, Zero}; use ark_serialize::{ buffer_byte_size, CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize, CanonicalSerializeWithFlags, Compress, EmptyFlags, Flags, SerializationError, Valid, Validate, }; -use ark_std::vec; impl CanonicalSerializeWithFlags for SmallFp

{ fn serialize_with_flags( @@ -24,27 +23,12 @@ impl CanonicalSerializeWithFlags for SmallFp

{ let output_byte_size = buffer_byte_size(Self::MODULUS_BIT_SIZE as usize + F::BIT_SIZE); let mut w = writer; - // Write out `self` to a temporary buffer. - // The size of the buffer is $byte_size + 1 because `F::BIT_SIZE` - // is at most 8 bits. - - if output_byte_size <= 8 { - // Fields with type smaller than u64 - // Writes exactly the minimal required number of bytes - let bigint = self.into_bigint(); - let value = bigint.0[0]; - let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&value.to_le_bytes()); - bytes[output_byte_size - 1] |= flags.u8_bitmask(); - w.write_all(&bytes[..output_byte_size])?; - } else { - // For larger fields, use the approach from `FpConfig` - let mut bytes = crate::const_helpers::SerBuffer::zeroed(); - bytes.copy_from_u64_slice(&self.into_bigint().0); - // Mask out the bits of the last byte that correspond to the flag. - bytes[output_byte_size - 1] |= flags.u8_bitmask(); - bytes.write_up_to(&mut w, output_byte_size)?; - } + // Serialize the Montgomery representation directly + let raw: u128 = self.value.into(); + let mut bytes = [0u8; 17]; + bytes[..16].copy_from_slice(&raw.to_le_bytes()); + bytes[output_byte_size - 1] |= flags.u8_bitmask(); + w.write_all(&bytes[..output_byte_size])?; Ok(()) } @@ -87,34 +71,24 @@ impl CanonicalDeserializeWithFlags for SmallFp

{ let output_byte_size = Self::zero().serialized_size_with_flags::(); let mut r = reader; - if output_byte_size <= 8 { - // Fields with type smaller than u64 - let mut bytes = vec![0u8; output_byte_size]; - r.read_exact(&mut bytes)?; - let flags = F::from_u8_remove_flags(&mut bytes[output_byte_size - 1]) - .ok_or(SerializationError::UnexpectedFlags)?; + // Deserialize directly into the Montgomery representation, + let mut bytes = [0u8; 17]; + r.read_exact(&mut bytes[..output_byte_size])?; + let flags = F::from_u8_remove_flags(&mut bytes[output_byte_size - 1]) + .ok_or(SerializationError::UnexpectedFlags)?; - let mut limb_bytes = [0u8; 8]; - limb_bytes[..output_byte_size.min(8)] - .copy_from_slice(&bytes[..output_byte_size.min(8)]); - let limb = u64::from_le_bytes(limb_bytes); - let bigint = BigInt::<2>::new([limb, 0]); + let mut le_bytes = [0u8; 16]; + le_bytes[..output_byte_size.min(16)].copy_from_slice(&bytes[..output_byte_size.min(16)]); + let raw = u128::from_le_bytes(le_bytes); - Self::from_bigint(bigint) - .map(|v| (v, flags)) - .ok_or(SerializationError::InvalidData) - } else { - // For larger fields, use the approach from `FpConfig` - let mut masked_bytes = crate::const_helpers::SerBuffer::zeroed(); - masked_bytes.read_exact_up_to(&mut r, output_byte_size)?; - let flags = F::from_u8_remove_flags(&mut masked_bytes[output_byte_size - 1]) - .ok_or(SerializationError::UnexpectedFlags)?; - - let self_integer = masked_bytes.to_bigint(); - Self::from_bigint(self_integer) - .map(|v| (v, flags)) - .ok_or(SerializationError::InvalidData) + if raw >= P::MODULUS_U128 { + return Err(SerializationError::InvalidData); } + + let value = P::T::try_from(raw) + .ok() + .ok_or(SerializationError::InvalidData)?; + Ok((SmallFp::from_raw(value), flags)) } }