From 84d82f63846b0825cc360828c8ef81bac9cee4d0 Mon Sep 17 00:00:00 2001 From: Alexander Abdugafarov Date: Tue, 3 Mar 2026 00:40:09 +0500 Subject: [PATCH 1/3] Added a separate `Modulus` associated type --- src/field.rs | 15 ++++++++++----- src/field/ark_ff_field.rs | 1 + src/field/ark_ff_fp.rs | 3 ++- src/field/crypto_bigint_boxed_monty.rs | 5 +++-- src/field/crypto_bigint_const_monty.rs | 3 ++- src/field/crypto_bigint_monty.rs | 5 +++-- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/field.rs b/src/field.rs index 2904f9c..06769cb 100644 --- a/src/field.rs +++ b/src/field.rs @@ -40,6 +40,11 @@ pub trait Field: /// Underlying representation of an element type Inner: Debug + Eq + Clone + Sync + Send; + /// Type used to represent the modulus. Usually the same as `Self::Inner`, + /// but may differ when the modulus doesn't fit in the element representation + /// (e.g. F2 where elements are `bool` but the modulus is `2: u8`). + type Modulus: Debug + Eq + Clone + Sync + Send; + fn inner(&self) -> &Self::Inner; fn inner_mut(&mut self) -> &mut Self::Inner; } @@ -62,11 +67,11 @@ pub trait PrimeField: Field { // Note: Not using `&self` to avoid conflicts with `Zero` trait. fn is_zero(value: &Self) -> bool; - fn modulus(&self) -> Self::Inner; + fn modulus(&self) -> Self::Modulus; fn modulus_minus_one_div_two(&self) -> Self::Inner; - fn make_cfg(modulus: &Self::Inner) -> Result; + fn make_cfg(modulus: &Self::Modulus) -> Result; /// Creates a new instance of a prime field element from /// an arbitrary element of `Self::Inner`. The method @@ -91,7 +96,7 @@ pub trait PrimeField: Field { pub trait ConstPrimeField: Field + ConstSemiring + Inv> + From + From + From { - const MODULUS: Self::Inner; + const MODULUS: Self::Modulus; const MODULUS_MINUS_ONE_DIV_TWO: Self::Inner; /// Creates a new instance of a prime field element from @@ -123,7 +128,7 @@ impl PrimeField for T { } #[inline(always)] - fn modulus(&self) -> Self::Inner { + fn modulus(&self) -> Self::Modulus { Self::MODULUS } @@ -132,7 +137,7 @@ impl PrimeField for T { Self::MODULUS_MINUS_ONE_DIV_TWO } - fn make_cfg(modulus: &Self::Inner) -> Result { + fn make_cfg(modulus: &Self::Modulus) -> Result { if *modulus == Self::MODULUS { Ok(()) } else { diff --git a/src/field/ark_ff_field.rs b/src/field/ark_ff_field.rs index ea6ef0f..1a59e2e 100644 --- a/src/field/ark_ff_field.rs +++ b/src/field/ark_ff_field.rs @@ -400,6 +400,7 @@ impl Ring for ArkField {} impl Field for ArkField { type Inner = F; + type Modulus = ::BigInt; #[inline(always)] fn inner(&self) -> &Self::Inner { diff --git a/src/field/ark_ff_fp.rs b/src/field/ark_ff_fp.rs index cd852c2..85dcaba 100644 --- a/src/field/ark_ff_fp.rs +++ b/src/field/ark_ff_fp.rs @@ -471,6 +471,7 @@ impl, const N: usize> IntRing for Fp { impl, const N: usize> Field for Fp { type Inner = BigInt; + type Modulus = Self::Inner; #[inline(always)] fn inner(&self) -> &Self::Inner { @@ -485,7 +486,7 @@ impl, const N: usize> Field for Fp { /// ConstPrimeField is only implemented for MontConfig and MontBackend impl, const N: usize> ConstPrimeField for Fp, N> { - const MODULUS: Self::Inner = M::MODULUS; + const MODULUS: Self::Modulus = M::MODULUS; const MODULUS_MINUS_ONE_DIV_TWO: Self::Inner = , N> as ArkPrimeField>::MODULUS_MINUS_ONE_DIV_TWO; diff --git a/src/field/crypto_bigint_boxed_monty.rs b/src/field/crypto_bigint_boxed_monty.rs index 0bdc663..2601b0b 100644 --- a/src/field/crypto_bigint_boxed_monty.rs +++ b/src/field/crypto_bigint_boxed_monty.rs @@ -406,6 +406,7 @@ impl Ring for BoxedMontyField {} impl Field for BoxedMontyField { type Inner = BoxedUint; + type Modulus = Self::Inner; #[inline(always)] fn inner(&self) -> &Self::Inner { @@ -435,7 +436,7 @@ impl PrimeField for BoxedMontyField { value.0.is_zero().into() } - fn modulus(&self) -> Self::Inner { + fn modulus(&self) -> Self::Modulus { self.0.params().modulus().clone().get() } @@ -445,7 +446,7 @@ impl PrimeField for BoxedMontyField { (value - BoxedUint::one()) / NonZero::new(BoxedUint::from(2_u8)).unwrap() } - fn make_cfg(modulus: &Self::Inner) -> Result { + fn make_cfg(modulus: &Self::Modulus) -> Result { let Some(modulus) = Odd::new(modulus.clone()).into_option() else { return Err(FieldError::InvalidModulus); }; diff --git a/src/field/crypto_bigint_const_monty.rs b/src/field/crypto_bigint_const_monty.rs index 4f9526d..cfc6583 100644 --- a/src/field/crypto_bigint_const_monty.rs +++ b/src/field/crypto_bigint_const_monty.rs @@ -543,6 +543,7 @@ impl, const LIMBS: usize> Ring for ConstMontyField, const LIMBS: usize> Field for ConstMontyField { type Inner = Uint; + type Modulus = Self::Inner; #[inline(always)] fn inner(&self) -> &Self::Inner { @@ -556,7 +557,7 @@ impl, const LIMBS: usize> Field for ConstMontyField, const LIMBS: usize> ConstPrimeField for ConstMontyField { - const MODULUS: Self::Inner = *Uint::new_ref(Mod::PARAMS.modulus().as_ref()); + const MODULUS: Self::Modulus = *Uint::new_ref(Mod::PARAMS.modulus().as_ref()); const MODULUS_MINUS_ONE_DIV_TWO: Self::Inner = { let m_minus_one = CBUint::wrapping_sub(Self::MODULUS.inner(), &CBUint::ONE); let two = CBUint::::wrapping_add(&CBUint::ONE, &CBUint::ONE); diff --git a/src/field/crypto_bigint_monty.rs b/src/field/crypto_bigint_monty.rs index 7f05e10..b034a36 100644 --- a/src/field/crypto_bigint_monty.rs +++ b/src/field/crypto_bigint_monty.rs @@ -487,6 +487,7 @@ impl Ring for MontyField {} impl Field for MontyField { type Inner = Uint; + type Modulus = Self::Inner; #[inline(always)] fn inner(&self) -> &Self::Inner { @@ -510,7 +511,7 @@ impl PrimeField for MontyField { value.0.as_montgomery().is_zero() } - fn modulus(&self) -> Self::Inner { + fn modulus(&self) -> Self::Modulus { Uint::new(self.0.params().modulus().get()) } @@ -523,7 +524,7 @@ impl PrimeField for MontyField { ) } - fn make_cfg(modulus: &Self::Inner) -> Result { + fn make_cfg(modulus: &Self::Modulus) -> Result { let Some(modulus) = Odd::new(*modulus.inner()).into_option() else { return Err(FieldError::InvalidModulus); }; From abf20d406394071c3f6d4f2d8d103fa1587fb5ba Mon Sep 17 00:00:00 2001 From: Alexander Abdugafarov Date: Tue, 3 Mar 2026 00:40:27 +0500 Subject: [PATCH 2/3] F2 field --- src/field.rs | 1 + src/field/f2.rs | 765 ++++++++++++++++++++++++++++++++++++++++ src/semiring/boolean.rs | 24 +- 3 files changed, 781 insertions(+), 9 deletions(-) create mode 100644 src/field/f2.rs diff --git a/src/field.rs b/src/field.rs index 06769cb..1ce0b5e 100644 --- a/src/field.rs +++ b/src/field.rs @@ -10,6 +10,7 @@ pub mod crypto_bigint_const_monty; pub(crate) mod crypto_bigint_helpers; #[cfg(feature = "crypto_bigint")] pub mod crypto_bigint_monty; +pub mod f2; use crate::{ConstSemiring, ring::Ring}; use core::{ diff --git a/src/field/f2.rs b/src/field/f2.rs new file mode 100644 index 0000000..c839365 --- /dev/null +++ b/src/field/f2.rs @@ -0,0 +1,765 @@ +use crate::{ConstPrimeField, Field, Ring, Semiring, boolean::Boolean}; +use core::{ + fmt::{Debug, Display, Formatter, Result as FmtResult}, + hash::Hash, + iter::{Product, Sum}, + ops::{Add, AddAssign, Deref, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, + str::FromStr, +}; +use crypto_primitives_proc_macros::InfallibleCheckedOp; +use num_traits::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedSub, ConstOne, ConstZero, Inv, One, Pow, + Zero, +}; + +#[cfg(feature = "rand")] +use rand::{distr::StandardUniform, prelude::*}; + +/// The field with two elements, GF(2). Elements are {0, 1} represented as +/// {false, true}. Arithmetic is modulo 2: addition and subtraction are XOR, +/// multiplication is AND. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, InfallibleCheckedOp)] +#[infallible_checked_unary_op((CheckedNeg, neg))] +#[infallible_checked_binary_op((CheckedAdd, add), (CheckedSub, sub), (CheckedMul, mul))] +#[repr(transparent)] +pub struct F2(bool); + +impl F2 { + /// Creates a new F2 element from a bool value. + #[inline(always)] + pub const fn new(value: bool) -> Self { + Self(value) + } + + /// Get the inner bool value. + #[inline(always)] + pub const fn inner(&self) -> &bool { + &self.0 + } + + /// Convert to the inner bool value. + #[inline(always)] + pub const fn into_inner(self) -> bool { + self.0 + } +} + +// +// Core traits +// + +impl Display for F2 { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "{} (mod 2)", u8::from(self.0)) + } +} + +impl Default for F2 { + #[inline(always)] + fn default() -> Self { + Self::ZERO + } +} + +impl Deref for F2 { + type Target = bool; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromStr for F2 { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "0" | "false" => Ok(Self::ZERO), + "1" | "true" => Ok(Self::ONE), + _ => Err(()), + } + } +} + +// +// Zero and One traits +// + +impl Zero for F2 { + #[inline(always)] + fn zero() -> Self { + Self::ZERO + } + + #[inline(always)] + fn is_zero(&self) -> bool { + !self.0 + } +} + +impl One for F2 { + #[inline(always)] + fn one() -> Self { + Self::ONE + } +} + +impl ConstZero for F2 { + const ZERO: Self = Self(false); +} + +impl ConstOne for F2 { + const ONE: Self = Self(true); +} + +// +// Basic arithmetic operations +// + +impl Neg for F2 { + type Output = Self; + + #[inline(always)] + fn neg(self) -> Self::Output { + // In GF(2), -x = x + self + } +} + +macro_rules! impl_basic_op_forward_to_assign { + ($trait:ident, $method:ident, $assign_method:ident) => { + impl $trait for F2 { + type Output = F2; + + #[inline(always)] + fn $method(self, rhs: F2) -> Self::Output { + self.$method(&rhs) + } + } + + impl $trait<&Self> for F2 { + type Output = F2; + + #[inline(always)] + fn $method(mut self, rhs: &F2) -> Self::Output { + self.$assign_method(rhs); + self + } + } + + impl $trait for &F2 { + type Output = F2; + + #[inline(always)] + fn $method(self, rhs: F2) -> Self::Output { + (*self).$method(&rhs) + } + } + + impl $trait for &F2 { + type Output = F2; + + #[inline(always)] + fn $method(self, rhs: &F2) -> Self::Output { + (*self).$method(rhs) + } + } + }; +} + +impl_basic_op_forward_to_assign!(Add, add, add_assign); +impl_basic_op_forward_to_assign!(Sub, sub, sub_assign); +impl_basic_op_forward_to_assign!(Mul, mul, mul_assign); +impl_basic_op_forward_to_assign!(Div, div, div_assign); + +impl Pow for F2 { + type Output = Self; + + fn pow(self, rhs: u32) -> Self::Output { + // 0^0 = 1 (by convention) + // 0^n = 0 for n > 0 + // 1^n = 1 for all n + if rhs == 0 || self.0 { + Self::ONE + } else { + Self::ZERO + } + } +} + +impl Inv for F2 { + type Output = Option; + + #[inline(always)] + fn inv(self) -> Self::Output { + // 0 has no inverse, 1 is its own inverse + if self.0 { Some(self) } else { None } + } +} + +// +// Checked arithmetic operations +// + +impl CheckedDiv for F2 { + #[allow(clippy::arithmetic_side_effects)] // False alert + fn checked_div(&self, rhs: &Self) -> Option { + Some(*self * rhs.inv()?) + } +} + +// +// Arithmetic assign operations +// + +macro_rules! impl_op_assign_boilerplate { + ($trait:ident, $method:ident) => { + impl $trait for F2 { + #[inline(always)] + fn $method(&mut self, rhs: F2) { + self.$method(&rhs); + } + } + }; +} + +impl_op_assign_boilerplate!(AddAssign, add_assign); +impl_op_assign_boilerplate!(SubAssign, sub_assign); +impl_op_assign_boilerplate!(MulAssign, mul_assign); +impl_op_assign_boilerplate!(DivAssign, div_assign); + +impl AddAssign<&Self> for F2 { + #[allow(clippy::suspicious_op_assign_impl)] // False alert + #[inline(always)] + fn add_assign(&mut self, rhs: &Self) { + self.0 ^= rhs.0; + } +} + +impl SubAssign<&Self> for F2 { + #[allow(clippy::suspicious_op_assign_impl)] // False alert + #[inline(always)] + fn sub_assign(&mut self, rhs: &Self) { + // In GF(2), subtraction = addition = XOR + self.0 ^= rhs.0; + } +} + +impl MulAssign<&Self> for F2 { + #[allow(clippy::suspicious_op_assign_impl)] // False alert + #[inline(always)] + fn mul_assign(&mut self, rhs: &Self) { + self.0 &= rhs.0; + } +} + +impl DivAssign<&Self> for F2 { + #[allow(clippy::arithmetic_side_effects, clippy::suspicious_op_assign_impl)] // False alert + #[inline(always)] + fn div_assign(&mut self, rhs: &Self) { + *self *= rhs.inv().expect("Division by zero"); + } +} + +// +// Aggregate operations +// + +impl Sum for F2 { + #[allow(clippy::arithmetic_side_effects)] // False alert + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl<'a> Sum<&'a Self> for F2 { + #[allow(clippy::arithmetic_side_effects)] // False alert + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl Product for F2 { + #[allow(clippy::arithmetic_side_effects)] // False alert + fn product>(iter: I) -> Self { + iter.fold(Self::ONE, |acc, x| acc * x) + } +} + +impl<'a> Product<&'a Self> for F2 { + #[allow(clippy::arithmetic_side_effects)] // False alert + fn product>(iter: I) -> Self { + iter.fold(Self::ONE, |acc, x| acc * x) + } +} + +// +// Conversions +// + +impl From<&Self> for F2 { + fn from(value: &Self) -> Self { + *value + } +} + +impl From for F2 { + #[inline(always)] + fn from(value: bool) -> Self { + Self(value) + } +} + +impl From for F2 { + #[inline(always)] + fn from(value: Boolean) -> Self { + Self(*value) + } +} + +impl From<&Boolean> for F2 { + #[inline(always)] + fn from(value: &Boolean) -> Self { + Self(**value) + } +} + +macro_rules! impl_from_unsigned { + ($($t:ty),* $(,)?) => { + $( + impl From<$t> for F2 { + #[inline(always)] + fn from(value: $t) -> Self { + Self(value % 2 != 0) + } + } + + impl From<&$t> for F2 { + #[inline(always)] + fn from(value: &$t) -> Self { + Self::from(*value) + } + } + )* + }; +} + +macro_rules! impl_from_signed { + ($($t:ty),* $(,)?) => { + $( + impl From<$t> for F2 { + #[inline(always)] + fn from(value: $t) -> Self { + // In F2, -1 = 1, so only parity matters + Self(value.unsigned_abs() % 2 != 0) + } + } + + impl From<&$t> for F2 { + #[inline(always)] + fn from(value: &$t) -> Self { + Self::from(*value) + } + } + )* + }; +} + +impl_from_unsigned!(u8, u16, u32, u64, u128); +impl_from_signed!(i8, i16, i32, i64, i128); + +// +// Semiring, Ring and Field +// + +impl Semiring for F2 {} + +impl Ring for F2 {} + +impl Field for F2 { + type Inner = bool; + type Modulus = u8; + + #[inline(always)] + fn inner(&self) -> &Self::Inner { + &self.0 + } + + #[inline(always)] + fn inner_mut(&mut self) -> &mut Self::Inner { + &mut self.0 + } +} + +impl ConstPrimeField for F2 { + const MODULUS: Self::Modulus = 2; + const MODULUS_MINUS_ONE_DIV_TWO: Self::Inner = false; + + #[inline(always)] + fn new(inner: Self::Inner) -> Self { + Self(inner) + } + + #[inline(always)] + fn new_unchecked(inner: Self::Inner) -> Self { + Self(inner) + } +} + +// +// RNG +// + +#[cfg(feature = "rand")] +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> F2 { + F2::new(rng.random()) + } +} + +// +// Zeroize +// + +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for F2 {} + +// +// Tests +// + +#[allow( + clippy::arithmetic_side_effects, + clippy::cast_lossless, + clippy::op_ref, + clippy::bool_assert_comparison +)] +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + ConstPrimeField, FromPrimitiveWithConfig, PrimeField, ensure_type_implements_trait, + }; + use alloc::format; + use num_traits::{One, Zero}; + + const V0: F2 = F2::ZERO; + const V1: F2 = F2::ONE; + + #[test] + fn ensure_blanket_traits() { + // NB: this ensures `PrimeField` implementation too! + ensure_type_implements_trait!(F2, FromPrimitiveWithConfig); + } + + #[test] + fn constants() { + assert_eq!(V0, F2(false)); + assert_eq!(V1, F2(true)); + assert_eq!(V0, F2::zero()); + assert_eq!(V1, F2::one()); + } + + #[test] + fn zero_one_basics() { + assert!(V0.is_zero()); + assert!(PrimeField::is_zero(&V0)); + assert!(!V1.is_zero()); + assert!(!PrimeField::is_zero(&V1)); + assert_ne!(V0, V1); + } + + #[test] + fn basic_ops() { + // Addition (XOR) + assert_eq!(V0 + V0, V0); + assert_eq!(V0 + V1, V1); + assert_eq!(V1 + V0, V1); + assert_eq!(V1 + V1, V0); + + // Subtraction (same as addition) + assert_eq!(V0 - V0, V0); + assert_eq!(V1 - V0, V1); + assert_eq!(V0 - V1, V1); + assert_eq!(V1 - V1, V0); + + // Multiplication (AND) + assert_eq!(V0 * V0, V0); + assert_eq!(V0 * V1, V0); + assert_eq!(V1 * V0, V0); + assert_eq!(V1 * V1, V1); + + // Division + assert_eq!(V0 / V1, V0); + assert_eq!(V1 / V1, V1); + } + + #[test] + fn negation_properties() { + // In char 2, -x = x + assert_eq!(-V0, V0); + assert_eq!(-V1, V1); + assert_eq!(V1 + (-V1), V0); + assert_eq!(-(-V1), V1); + } + + #[test] + fn inversion_properties() { + let inv = V1.inv().expect("1 should be invertible"); + assert_eq!(V1 * inv, F2::one()); + assert!(V0.inv().is_none()); + } + + #[test] + #[should_panic] + fn div_by_zero_panics() { + let _ = V1 / V0; + } + + #[test] + fn assign_ops_with_refs_and_values() { + let mut x = V1; + x += V1; + assert_eq!(x, V0); + + let mut x = V1; + x += &V1; + assert_eq!(x, V0); + + let mut x = V1; + x -= V1; + assert_eq!(x, V0); + + let mut x = V1; + x -= &V1; + assert_eq!(x, V0); + + let mut x = V1; + x *= V1; + assert_eq!(x, V1); + + let mut x = V1; + x *= &V1; + assert_eq!(x, V1); + + let mut x = V1; + x /= V1; + assert_eq!(x, V1); + + let mut x = V1; + x /= &V1; + assert_eq!(x, V1); + } + + #[test] + fn ref_and_value_combinations_add_sub_mul() { + let a = V1; + let b = V0; + + // Add: all ref/value combinations + assert_eq!(a + b, a); + assert_eq!(a + &b, a); + assert_eq!(&a + b, a); + assert_eq!(&a + &b, a); + + // Sub + assert_eq!(a - b, a); + assert_eq!(a - &b, a); + assert_eq!(&a - b, a); + assert_eq!(&a - &b, a); + + // Mul + assert_eq!(a * b, V0); + assert_eq!(a * &b, V0); + assert_eq!(&a * b, V0); + assert_eq!(&a * &b, V0); + } + + #[test] + fn checked_operations() { + // Checked neg + assert_eq!(V0.checked_neg(), Some(V0)); + assert_eq!(V1.checked_neg(), Some(V1)); + + // Checked add + assert_eq!(V0.checked_add(&V0), Some(V0)); + assert_eq!(V0.checked_add(&V1), Some(V1)); + assert_eq!(V1.checked_add(&V0), Some(V1)); + assert_eq!(V1.checked_add(&V1), Some(V0)); + + // Checked sub + assert_eq!(V0.checked_sub(&V0), Some(V0)); + assert_eq!(V1.checked_sub(&V0), Some(V1)); + assert_eq!(V0.checked_sub(&V1), Some(V1)); + assert_eq!(V1.checked_sub(&V1), Some(V0)); + + // Checked mul + assert_eq!(V0.checked_mul(&V0), Some(V0)); + assert_eq!(V0.checked_mul(&V1), Some(V0)); + assert_eq!(V1.checked_mul(&V0), Some(V0)); + assert_eq!(V1.checked_mul(&V1), Some(V1)); + + // Checked div (fails for division by zero) + assert_eq!(V0.checked_div(&V1), Some(V0)); + assert_eq!(V1.checked_div(&V1), Some(V1)); + assert_eq!(V0.checked_div(&V0), None); + assert_eq!(V1.checked_div(&V0), None); + } + + #[test] + fn pow_operation() { + // 0^0 = 1 by convention + assert_eq!(V0.pow(0), V1); + + // 0^n = 0 for n > 0 + assert_eq!(V0.pow(1), V0); + assert_eq!(V0.pow(2), V0); + assert_eq!(V0.pow(100), V0); + + // 1^n = 1 for all n + assert_eq!(V1.pow(0), V1); + assert_eq!(V1.pow(1), V1); + assert_eq!(V1.pow(2), V1); + assert_eq!(V1.pow(100), V1); + } + + #[test] + fn sum_and_product_trait_basic() { + let v = [V1, V0, V1, V1]; + + // Sum: 1 + 0 + 1 + 1 = 1 (XOR fold) + let sum1: F2 = v.iter().cloned().sum(); + let sum2: F2 = v.iter().sum(); + let sum3: F2 = v.into_iter().sum(); + assert_eq!(sum1, V1); + assert_eq!(sum2, V1); + assert_eq!(sum3, V1); + + // Product: 1 * 0 * 1 * 1 = 0 (AND fold) + let prod1: F2 = v.iter().product(); + let prod2: F2 = v.into_iter().product(); + assert_eq!(prod1, V0); + assert_eq!(prod2, V0); + + // Empty iterators: neutral elements + let empty: [F2; 0] = []; + let sum_empty: F2 = empty.iter().cloned().sum(); + assert_eq!(sum_empty, V0); + let prod_empty: F2 = empty.iter().product(); + assert_eq!(prod_empty, V1); + } + + #[test] + fn conversions() { + // Self-reference conversion + assert_eq!(F2::from(&V1), V1); + + // bool conversion + assert_eq!(F2::from(true), V1); + assert_eq!(F2::from(false), V0); + + let t: F2 = true.into(); + let f: F2 = false.into(); + assert_eq!(t, V1); + assert_eq!(f, V0); + + // Boolean conversion + assert_eq!(F2::from(Boolean::from(true)), V1); + assert_eq!(F2::from(Boolean::from(false)), V0); + assert_eq!(F2::from(&Boolean::from(true)), V1); + + // Integer conversions + assert_eq!(F2::from(0_u64), V0); + assert_eq!(F2::from(1_u32), V1); + assert_eq!(F2::from(2_u64), V0); + assert_eq!(F2::from(3_u64), V1); + assert_eq!(F2::from(255_u8), V1); + + assert_eq!(F2::from(0_i64), V0); + assert_eq!(F2::from(1_i32), V1); + assert_eq!(F2::from(-1_i32), V1); + assert_eq!(F2::from(-2_i64), V0); + assert_eq!(F2::from(-3_i64), V1); + + // Integer reference conversions + assert_eq!(F2::from(&42_u32), V0); + assert_eq!(F2::from(&-100_i64), V0); + } + + #[test] + fn const_prime_field() { + assert_eq!(::MODULUS, 2_u8); + assert_eq!(::MODULUS_MINUS_ONE_DIV_TWO, false); + + assert_eq!(F2::new(true), V1); + assert_eq!(F2::new(false), V0); + assert_eq!(F2::new_unchecked(true), V1); + assert_eq!(F2::new_unchecked(false), V0); + } + + #[test] + fn prime_field_methods() { + assert_eq!(V1.modulus(), 2_u8); + assert_eq!(V1.modulus_minus_one_div_two(), false); + assert_eq!(F2::make_cfg(&2_u8), Ok(())); + assert!(F2::make_cfg(&0_u8).is_err()); + assert!(F2::make_cfg(&3_u8).is_err()); + } + + #[test] + fn formatting_traits() { + assert_eq!(format!("{:?}", V1), "F2(true)"); + assert_eq!(format!("{:?}", V0), "F2(false)"); + assert_eq!(format!("{}", V1), "1 (mod 2)"); + assert_eq!(format!("{}", V0), "0 (mod 2)"); + } + + #[test] + fn default_trait() { + assert_eq!(F2::default(), V0); + } + + #[test] + fn ord_trait() { + assert!(V0 < V1); + assert!(V0 <= V1); + assert!(V0 <= V0); + assert!(V1 > V0); + assert!(V1 >= V0); + assert!(V1 >= V1); + assert_eq!(V0, V0); + assert_eq!(V1, V1); + assert_ne!(V0, V1); + } + + #[test] + fn from_str() { + assert_eq!("0".parse::(), Ok(V0)); + assert_eq!("1".parse::(), Ok(V1)); + assert_eq!("false".parse::(), Ok(V0)); + assert_eq!("true".parse::(), Ok(V1)); + + assert!("2".parse::().is_err()); + assert!("-1".parse::().is_err()); + assert!("abc".parse::().is_err()); + assert!("".parse::().is_err()); + } + + #[test] + fn field_inner() { + assert_eq!(*V1.inner(), true); + assert_eq!(*V0.inner(), false); + assert_eq!(V1.into_inner(), true); + assert_eq!(V0.into_inner(), false); + + // Field::inner + assert_eq!(*Field::inner(&V1), true); + assert_eq!(*Field::inner(&V0), false); + + // Field::inner_mut + let mut x = V0; + *Field::inner_mut(&mut x) = true; + assert_eq!(x, V1); + } +} diff --git a/src/semiring/boolean.rs b/src/semiring/boolean.rs index efe8c14..ca5523f 100644 --- a/src/semiring/boolean.rs +++ b/src/semiring/boolean.rs @@ -1,7 +1,7 @@ -use crate::{IntSemiring, Semiring}; +use crate::{IntSemiring, Semiring, f2::F2}; use core::{ fmt::{Debug, Display, Formatter, Result as FmtResult}, - hash::{Hash, Hasher}, + hash::Hash, iter::{Product, Sum}, ops::{Add, AddAssign, Deref, Mul, MulAssign, Sub, SubAssign}, str::{FromStr, ParseBoolError}, @@ -15,7 +15,7 @@ use rand::{distr::StandardUniform, prelude::*}; /// Arithmetic operations behave like modulo-2 arithmetic: /// - In debug mode: overflow panics (e.g., true + true panics) /// - In release mode: overflow wraps (e.g., true + true = false) -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct Boolean(bool); @@ -98,12 +98,6 @@ impl Deref for Boolean { } } -impl Hash for Boolean { - fn hash(&self, state: &mut H) { - self.0.hash(state) - } -} - impl FromStr for Boolean { type Err = ParseBoolError; @@ -174,6 +168,18 @@ impl_from_boolean_for!(bool); impl_from_boolean_for!(i8, i16, i32, i64, i128, isize); impl_from_boolean_for!(u8, u16, u32, u64, u128, usize); +impl From for Boolean { + fn from(value: F2) -> Self { + Self(*value) + } +} + +impl From<&F2> for Boolean { + fn from(value: &F2) -> Self { + Self(**value) + } +} + // // Basic arithmetic operations // From 6e527e8fb9afc76e5d2b0cc021ac160e5650836f Mon Sep 17 00:00:00 2001 From: Alexander Abdugafarov Date: Tue, 3 Mar 2026 15:19:33 +0500 Subject: [PATCH 3/3] Improved parsing Boolean and F2 from numeric strings --- src/field/f2.rs | 24 ++++++++++++++--------- src/semiring/boolean.rs | 42 +++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/field/f2.rs b/src/field/f2.rs index 7aec95b..56658d3 100644 --- a/src/field/f2.rs +++ b/src/field/f2.rs @@ -71,14 +71,12 @@ impl Deref for F2 { } impl FromStr for F2 { - type Err = (); + type Err = ::Err; + /// See [`Boolean::from_str`] for accepted formats. + #[inline(always)] fn from_str(s: &str) -> Result { - match s { - "0" | "false" => Ok(Self::ZERO), - "1" | "true" => Ok(Self::ONE), - _ => Err(()), - } + Boolean::from_str(s).map(|b| Self(*b)) } } @@ -745,14 +743,22 @@ mod tests { #[test] fn from_str() { - assert_eq!("0".parse::(), Ok(V0)); - assert_eq!("1".parse::(), Ok(V1)); assert_eq!("false".parse::(), Ok(V0)); assert_eq!("true".parse::(), Ok(V1)); + assert_eq!("0".parse::(), Ok(V0)); + assert_eq!("0000".parse::(), Ok(V0)); + assert_eq!("0x0".parse::(), Ok(V0)); + assert_eq!("0x0000".parse::(), Ok(V0)); + + assert_eq!("1".parse::(), Ok(V1)); + assert_eq!("0001".parse::(), Ok(V1)); + assert_eq!("0x1".parse::(), Ok(V1)); + assert_eq!("0x0001".parse::(), Ok(V1)); + + assert!("invalid".parse::().is_err()); assert!("2".parse::().is_err()); assert!("-1".parse::().is_err()); - assert!("abc".parse::().is_err()); assert!("".parse::().is_err()); } diff --git a/src/semiring/boolean.rs b/src/semiring/boolean.rs index ccbd6f7..49d330c 100644 --- a/src/semiring/boolean.rs +++ b/src/semiring/boolean.rs @@ -4,7 +4,7 @@ use core::{ hash::Hash, iter::{Product, Sum}, ops::{Add, AddAssign, Deref, Mul, MulAssign, Sub, SubAssign}, - str::{FromStr, ParseBoolError}, + str::FromStr, }; use num_traits::{CheckedAdd, CheckedMul, CheckedSub, ConstOne, ConstZero, One, Pow, Zero}; @@ -99,14 +99,29 @@ impl Deref for Boolean { } impl FromStr for Boolean { - type Err = ParseBoolError; + type Err = (); - /// In addition to "true" and "false", also supports "1" and "0". + /// Supports "false", "true" and numeric strings "0", "1". + /// Hexadecimal notation is also supported with "0x" prefix. fn from_str(s: &str) -> Result { match s { - "1" => Ok(Self::TRUE), - "0" => Ok(Self::FALSE), - _ => bool::from_str(s).map(Self), + "false" => Ok(Self::ZERO), + "true" => Ok(Self::ONE), + _ => { + let (radix, s) = if let Some(s) = s.strip_prefix("0x") { + (16, s) + } else { + (10, s) + }; + let parsed = u8::from_str_radix(s, radix).map_err(|_| ())?; + if parsed == 0 { + Ok(Self::ZERO) + } else if parsed == 1 { + Ok(Self::ONE) + } else { + Err(()) + } + } } } } @@ -593,12 +608,23 @@ mod tests { #[test] fn from_str() { - assert_eq!("true".parse::(), Ok(Boolean::TRUE)); assert_eq!("false".parse::(), Ok(Boolean::FALSE)); - assert_eq!("1".parse::(), Ok(Boolean::TRUE)); + assert_eq!("true".parse::(), Ok(Boolean::TRUE)); + assert_eq!("0".parse::(), Ok(Boolean::FALSE)); + assert_eq!("0000".parse::(), Ok(Boolean::FALSE)); + assert_eq!("0x0".parse::(), Ok(Boolean::FALSE)); + assert_eq!("0x0000".parse::(), Ok(Boolean::FALSE)); + + assert_eq!("1".parse::(), Ok(Boolean::TRUE)); + assert_eq!("0001".parse::(), Ok(Boolean::TRUE)); + assert_eq!("0x1".parse::(), Ok(Boolean::TRUE)); + assert_eq!("0x0001".parse::(), Ok(Boolean::TRUE)); + assert!("invalid".parse::().is_err()); assert!("2".parse::().is_err()); + assert!("-1".parse::().is_err()); + assert!("".parse::().is_err()); } #[test]