Skip to content
Prev Previous commit
Next Next commit
auto detect subgroup
  • Loading branch information
z-tech committed Mar 30, 2026
commit 954ffab7c9dd0513c2402cad34aab922cb53c76b
22 changes: 20 additions & 2 deletions ff-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#![forbid(unsafe_code)]

use num_bigint::BigUint;
use num_traits::Zero;
use proc_macro::TokenStream;
use quote::format_ident;
use syn::{Expr, ExprLit, Item, ItemFn, Lit, Meta};
Expand Down Expand Up @@ -75,11 +76,28 @@ pub fn define_field(input: TokenStream) -> TokenStream {
.parse::<BigUint>()
.expect("generator should be a decimal integer string");

// Auto-detect small subgroup params (base 3)
let mut trace = &modulus_big - BigUint::from(1u32);
while trace.bit(0) == false {
trace >>= 1u32;
}
let base = BigUint::from(3u32);
let mut power = 0u32;
while (&trace % &base).is_zero() {
trace /= &base;
power += 1;
}
let (small_subgroup_base, small_subgroup_power) = if power > 0 {
(Some(3u32), Some(power))
} else {
(None, None)
};

let config_impl = montgomery::mont_config_helper(
modulus_big,
generator_big,
None,
None,
small_subgroup_base,
small_subgroup_power,
config_name.clone(),
);

Expand Down
20 changes: 18 additions & 2 deletions ff-macros/src/small_fp/montgomery_backend.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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,
compute_large_subgroup_root, compute_two_adic_root_of_unity, compute_two_adicity,
detect_small_subgroup, generate_montgomery_bigint_casts, generate_sqrt_precomputation,
mod_mul_const,
};

pub(crate) fn backend_impl(
Expand Down Expand Up @@ -56,6 +57,19 @@ pub(crate) fn backend_impl(

let neg_one_mont = mod_mul_const(modulus - 1, r_mod_n, modulus);

let mixed_radix_impl = if let Some((base, power)) = detect_small_subgroup(modulus, two_adicity)
{
let large_root = compute_large_subgroup_root(modulus, generator, two_adicity, base, power);
let large_root_mont = mod_mul_const(large_root, r_mod_n, modulus);
quote! {
const SMALL_SUBGROUP_BASE: Option<u32> = Some(#base);
const SMALL_SUBGROUP_BASE_ADICITY: Option<u32> = Some(#power);
const LARGE_SUBGROUP_ROOT_OF_UNITY: Option<SmallFp<Self>> = Some(SmallFp::from_raw(#large_root_mont as Self::T));
}
} else {
quote! {}
};

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);
Expand Down Expand Up @@ -110,6 +124,8 @@ pub(crate) fn backend_impl(
const TWO_ADICITY: u32 = #two_adicity;
const TWO_ADIC_ROOT_OF_UNITY: SmallFp<Self> = SmallFp::from_raw(#two_adic_root_mont as Self::T);

#mixed_radix_impl

#sqrt_precomp_impl

#add_assign_impl
Expand Down
37 changes: 37 additions & 0 deletions ff-macros/src/small_fp/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,43 @@ pub(crate) const fn find_quadratic_non_residue(modulus: u128) -> u128 {
}
}

/// Auto-detect small subgroup parameters from the modulus.
/// Checks if a small prime base (currently 3) divides the odd part of p-1.
/// Returns `(base, adicity)` if found.
pub(crate) fn detect_small_subgroup(modulus: u128, two_adicity: u32) -> Option<(u32, u32)> {
let mut trace = (modulus - 1) >> two_adicity; // odd part of p-1

// Check base 3
let base: u128 = 3;
let mut adicity = 0u32;
while trace % base == 0 {
trace /= base;
adicity += 1;
}

if adicity > 0 {
Some((base as u32, adicity))
} else {
None
}
}

/// Compute the large subgroup root of unity:
/// generator^((p-1) / (2^two_adicity * base^power))
pub(crate) fn compute_large_subgroup_root(
modulus: u128,
generator: u128,
two_adicity: u32,
base: u32,
power: u32,
) -> u128 {
let mut remaining = (modulus - 1) >> two_adicity;
for _ in 0..power {
remaining /= base as u128;
}
pow_mod_const(generator, remaining, modulus)
}

pub(crate) fn generate_montgomery_bigint_casts(
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
(
Expand Down
Loading