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 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..f23546867 --- /dev/null +++ b/ff-macros/src/small_fp/mod.rs @@ -0,0 +1,52 @@ +mod montgomery_backend; +mod standard_backend; +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, + backend: String, + config_name: proc_macro2::Ident, +) -> proc_macro2::TokenStream { + 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, 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), + }; + + 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..8b8815d78 --- /dev/null +++ b/ff-macros/src/small_fp/montgomery_backend.rs @@ -0,0 +1,258 @@ +use std::u32; + +use super::*; +use crate::small_fp::utils::{ + compute_two_adic_root_of_unity, compute_two_adicity, generate_montgomery_bigint_casts, + generate_sqrt_precomputation, mod_mul_const, +}; + +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 = 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, 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, Some(r_mod_n)); + + // 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; + 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(#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); + #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; + } + + #[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; + } + } + + #mul_impl + + #[inline(always)] + 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 + } + + #[inline(always)] + 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::square_in_place(&mut sq); + base = sq; + exp >>= 1; + } + + Some(result) + } + + #from_bigint_impl + + #into_bigint_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, + 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 mut u = (sum_lo >> #k_bits) | (sum_hi << (128 - #k_bits)); + u -= #modulus * (u >= #modulus) as u128; + 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; + + u += ((#one) << (#bits - #k_bits)) * (overflow as #mul_ty); + u -= #modulus_downcast * ((u >= #modulus_downcast) as #mul_ty); + 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 { + inv = inv.wrapping_mul(2u128.wrapping_sub(n.wrapping_mul(inv))); + } + let mask = (1u128 << k_bits) - 1; + 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); + + 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..a51260806 --- /dev/null +++ b/ff-macros/src/small_fp/standard_backend.rs @@ -0,0 +1,138 @@ +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_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); + #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-macros/src/small_fp/utils.rs b/ff-macros/src/small_fp/utils.rs new file mode 100644 index 000000000..39fe370f5 --- /dev/null +++ b/ff-macros/src/small_fp/utils.rs @@ -0,0 +1,186 @@ +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_const(x: u128, y: u128, modulus: u128) -> u128 { + if x >= modulus - y { + x - (modulus - y) + } else { + x + y + } +} + +pub(crate) const fn mod_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_const(result, base, modulus); + } + base = mod_add_const(base, base, modulus); + exp >>= 1; + } + result + }, + } +} + +const fn pow_mod_const(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 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_const(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) { + ( + 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 = mod_mul_const(r_mod_n, r_mod_n, modulus); + ( + 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; + 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! { + fn into_bigint(a: SmallFp) -> BigInt<2> { + let mut tmp = a; + let one = SmallFp::new(1 as Self::T); + ::mul_assign(&mut tmp, &one); + let val = tmp.value as u128; + let lo = val as u64; + let hi = (val >> 64) as u64; + ark_ff::BigInt([lo, hi]) + } + }, + ) +} + +pub(crate) fn generate_sqrt_precomputation( + modulus: u128, + two_adicity: u32, + r_mod_n: Option, +) -> proc_macro2::TokenStream { + if modulus % 4 == 3 { + 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 { + modulus_plus_one_div_four: &MODULUS_PLUS_ONE_DIV_FOUR, + }) + }; + } + } else { + let trace = (modulus - 1) >> two_adicity; + 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]; + Some(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/Cargo.toml b/ff/Cargo.toml index 30323fdaa..31965cb92 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -42,4 +42,4 @@ hex.workspace = true 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 diff --git a/ff/README.md b/ff/README.md index ca81dc111..c840f08e3 100644 --- a/ff/README.md +++ b/ff/README.md @@ -38,6 +38,33 @@ 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`], 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 new file mode 100644 index 000000000..8f5a9bfff --- /dev/null +++ b/ff/src/fields/models/small_fp/mod.rs @@ -0,0 +1,7 @@ +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..606f666ff --- /dev/null +++ b/ff/src/fields/models/small_fp/serialize.rs @@ -0,0 +1,135 @@ +use crate::fields::models::small_fp::small_fp_backend::{SmallFp, SmallFpConfig}; +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( + &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); + 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)?; + } + 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 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 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]); + + 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) + } + } +} + +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..c27e56a68 --- /dev/null +++ b/ff/src/fields/models/small_fp/small_fp_backend.rs @@ -0,0 +1,351 @@ +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, 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 + + 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; + + // 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. + /// `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 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 { + 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 { + primitive_type_bit_size(P::MODULUS_128) - (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]) +} + +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 = 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(); + + #[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] + // 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; + if val_u128 > 0 && val_u128 < 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), + } + } +} + +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) + } +} diff --git a/test-curves/Cargo.toml b/test-curves/Cargo.toml index fda91432b..adf24b8db 100644 --- a/test-curves/Cargo.toml +++ b/test-curves/Cargo.toml @@ -78,3 +78,8 @@ harness = false name = "mnt6_753" path = "benches/mnt6_753.rs" harness = false + +[[bench]] +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..75bb91563 --- /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 = "7"] +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/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); +} diff --git a/test-templates/src/fields.rs b/test-templates/src/fields.rs index db0d9b61c..7003dcc06 100644 --- a/test-templates/src/fields.rs +++ b/test-templates/src/fields.rs @@ -586,3 +586,98 @@ macro_rules! test_field { } }; } + +#[macro_export] +#[doc(hidden)] +macro_rules! __test_small_field { + ($field: ty) => { + // 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}; + 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) => { + mod $mod_name { + use super::*; + use ark_ff::{ + fields::{Field, LegendreSymbol}, + 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, + }; + const ITERATIONS: usize = 1000; + + $crate::__test_small_field!($field); + } + }; + + ($iters:expr; $mod_name:ident; $field:ty) => { + mod $mod_name { + use super::*; + use ark_ff::{ + fields::{Field, LegendreSymbol}, + FftField, SmallFp, SmallFpConfig, + }; + 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); + } + }; +}