diff --git a/ff/src/fields/binary/bf1.rs b/ff/src/fields/binary/bf1.rs new file mode 100644 index 000000000..333e950da --- /dev/null +++ b/ff/src/fields/binary/bf1.rs @@ -0,0 +1,270 @@ +use crate::binary::small_unit::U1; +use ark_std::ops::Deref; +use ark_std::ops::{Add, AddAssign, Mul, MulAssign}; +use ark_std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; + +/// A binary field element represented using a single bit. +/// +/// The [`Bf1`] type wraps a [`U1`] (1-bit unsigned integer) to represent binary field elements. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Bf1(U1); + +impl Bf1 { + /// Creates a new instance of [`Bf1`] from a [`U1`] value. + pub const fn new(val: U1) -> Self { + Self(val) + } + + /// Returns the inner [`U1`] value of the [`Bf1`] element. + pub fn inner(self) -> U1 { + self.0 + } +} + +impl Add for Bf1 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self::new(self.0 ^ rhs.0) + } +} + +impl AddAssign for Bf1 { + fn add_assign(&mut self, rhs: Self) { + self.0 = self.0 ^ rhs.0; + } +} + +impl Mul for Bf1 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self::new(self.0 & rhs.0) + } +} + +impl MulAssign for Bf1 { + fn mul_assign(&mut self, rhs: Self) { + self.0 = self.0 & rhs.0; + } +} + +impl BitAnd for Bf1 { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + Self::new(self.0 & rhs.0) + } +} + +impl BitAndAssign for Bf1 { + fn bitand_assign(&mut self, rhs: Self) { + self.0 = self.0 & rhs.0; + } +} + +impl BitOr for Bf1 { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self::new(self.0 | rhs.0) + } +} + +impl BitOrAssign for Bf1 { + fn bitor_assign(&mut self, rhs: Self) { + self.0 = self.0 | rhs.0; + } +} + +impl BitXor for Bf1 { + type Output = Self; + + fn bitxor(self, rhs: Self) -> Self::Output { + Self::new(self.0 ^ rhs.0) + } +} + +impl BitXorAssign for Bf1 { + fn bitxor_assign(&mut self, rhs: Self) { + self.0 = self.0 ^ rhs.0; + } +} + +impl Deref for Bf1 { + type Target = U1; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_addition() { + let a = Bf1::new(U1::new(0)); + let b = Bf1::new(U1::new(1)); + assert_eq!(a + b, Bf1::new(U1::new(1))); + + let c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(1)); + assert_eq!(c + d, Bf1::new(U1::new(0))); + + let e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + assert_eq!(e + f, Bf1::new(U1::new(0))); + } + + #[test] + fn test_addition_assign() { + let mut a = Bf1::new(U1::new(0)); + let b = Bf1::new(U1::new(1)); + a += b; + assert_eq!(a, Bf1::new(U1::new(1))); + + let mut c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(1)); + c += d; + assert_eq!(c, Bf1::new(U1::new(0))); + + let mut e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + e += f; + assert_eq!(e, Bf1::new(U1::new(0))); + } + + #[test] + fn test_multiplication() { + let a = Bf1::new(U1::new(1)); + let b = Bf1::new(U1::new(1)); + assert_eq!(a * b, Bf1::new(U1::new(1))); + + let c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(0)); + assert_eq!(c * d, Bf1::new(U1::new(0))); + + let e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + assert_eq!(e * f, Bf1::new(U1::new(0))); + } + + #[test] + fn test_multiplication_assign() { + let mut a = Bf1::new(U1::new(1)); + let b = Bf1::new(U1::new(1)); + a *= b; + assert_eq!(a, Bf1::new(U1::new(1))); + + let mut c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(0)); + c *= d; + assert_eq!(c, Bf1::new(U1::new(0))); + + let mut e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + e *= f; + assert_eq!(e, Bf1::new(U1::new(0))); + } + + #[test] + fn test_bitwise_and() { + let a = Bf1::new(U1::new(1)); + let b = Bf1::new(U1::new(1)); + assert_eq!(a & b, Bf1::new(U1::new(1))); + + let c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(0)); + assert_eq!(c & d, Bf1::new(U1::new(0))); + + let e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + assert_eq!(e & f, Bf1::new(U1::new(0))); + } + + #[test] + fn test_bitwise_and_assign() { + let mut a = Bf1::new(U1::new(1)); + let b = Bf1::new(U1::new(1)); + a &= b; + assert_eq!(a, Bf1::new(U1::new(1))); + + let mut c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(0)); + c &= d; + assert_eq!(c, Bf1::new(U1::new(0))); + + let mut e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + e &= f; + assert_eq!(e, Bf1::new(U1::new(0))); + } + + #[test] + fn test_bitwise_or() { + let a = Bf1::new(U1::new(1)); + let b = Bf1::new(U1::new(1)); + assert_eq!(a | b, Bf1::new(U1::new(1))); + + let c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(0)); + assert_eq!(c | d, Bf1::new(U1::new(1))); + + let e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + assert_eq!(e | f, Bf1::new(U1::new(0))); + } + + #[test] + fn test_bitwise_or_assign() { + let mut a = Bf1::new(U1::new(1)); + let b = Bf1::new(U1::new(1)); + a |= b; + assert_eq!(a, Bf1::new(U1::new(1))); + + let mut c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(0)); + c |= d; + assert_eq!(c, Bf1::new(U1::new(1))); + + let mut e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + e |= f; + assert_eq!(e, Bf1::new(U1::new(0))); + } + + #[test] + fn test_bitwise_xor() { + let a = Bf1::new(U1::new(1)); + let b = Bf1::new(U1::new(1)); + assert_eq!(a ^ b, Bf1::new(U1::new(0))); + + let c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(0)); + assert_eq!(c ^ d, Bf1::new(U1::new(1))); + + let e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + assert_eq!(e ^ f, Bf1::new(U1::new(0))); + } + + #[test] + fn test_bitwise_xor_assign() { + let mut a = Bf1::new(U1::new(1)); + let b = Bf1::new(U1::new(1)); + a ^= b; + assert_eq!(a, Bf1::new(U1::new(0))); + + let mut c = Bf1::new(U1::new(1)); + let d = Bf1::new(U1::new(0)); + c ^= d; + assert_eq!(c, Bf1::new(U1::new(1))); + + let mut e = Bf1::new(U1::new(0)); + let f = Bf1::new(U1::new(0)); + e ^= f; + assert_eq!(e, Bf1::new(U1::new(0))); + } +} diff --git a/ff/src/fields/binary/mod.rs b/ff/src/fields/binary/mod.rs new file mode 100644 index 000000000..a606e8951 --- /dev/null +++ b/ff/src/fields/binary/mod.rs @@ -0,0 +1,2 @@ +pub mod bf1; +pub mod small_unit; diff --git a/ff/src/fields/binary/small_unit.rs b/ff/src/fields/binary/small_unit.rs new file mode 100644 index 000000000..fb6ecd698 --- /dev/null +++ b/ff/src/fields/binary/small_unit.rs @@ -0,0 +1,267 @@ +// Derived from work licensed under the Apache License 2.0 +// Copyright 2024 Irreducible Inc. + +use ark_std::ops::Deref; +use ark_std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; + +/// Represents an unsigned integer type with a bit-width of `N`. +/// +/// This type wraps a `u8` value and ensures that only the least significant `N` bits are valid. +/// Any operations performed will respect the constraints imposed by the bit-width `N`. +/// +/// # Type Parameters +/// - `N`: The number of bits in the type. Must be strictly less than 8. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct SmallU(u8); + +impl SmallU { + /// Represents the zero value for this type. + /// + /// All bits are cleared (set to `0`). + pub const ZERO: Self = Self(0); + + /// Represents the one value for this type. + /// + /// Only the least significant bit is set to `1`. + pub const ONE: Self = Self(1); + + /// A constant representing the maximum possible value for the [`SmallU`] type, + /// where all `N` least significant bits are set to `1`. + /// + /// For example: + /// - If `N = 3`, the value is `0b00000111` (decimal `7`). + /// - If `N = 1`, the value is `0b00000001` (decimal `1`). + pub const MAX: Self = Self((1u8 << N) - 1); + + /// Creates a new instance of [`SmallU`] with the given value. + /// + /// The value is masked so that only the least significant `N` bits are retained. + /// + /// # Parameters + /// - `val`: The input value to initialize the instance. + /// + /// # Returns + /// A new [`SmallU`] instance where only the least significant `N` bits are valid. + pub const fn new(val: u8) -> Self { + // Mask the input value with the maximum possible value (`MAX`) + // to retain only the least significant `N` bits. + Self(val & Self::MAX.0) + } + + /// Creates a new instance of [`SmallU`] without applying bit-width constraints. + /// + /// This method does not mask the input value and directly assigns it to the internal field. + /// Use this method only when the input value is guaranteed to be valid for the specified + /// bit-width `N`, as this bypasses the safety check provided by the `new` method. + /// + /// # Parameters + /// - `val`: The input value to initialize the instance. + /// + /// # Returns + /// A new [`SmallU`] instance with the raw input value. + /// + /// # Safety + /// It is the caller's responsibility to ensure the input value respects the `N` bit-width. + pub const fn new_unchecked(val: u8) -> Self { + Self(val) + } +} + +impl BitAnd for SmallU { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + Self::new_unchecked(self.0 & rhs.0) + } +} + +impl BitAndAssign for SmallU { + fn bitand_assign(&mut self, rhs: Self) { + self.0 &= rhs.0; + } +} + +impl BitOr for SmallU { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self::new_unchecked(self.0 | rhs.0) + } +} + +impl BitOrAssign for SmallU { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +impl BitXor for SmallU { + type Output = Self; + + fn bitxor(self, rhs: Self) -> Self::Output { + Self::new_unchecked(self.0 ^ rhs.0) + } +} + +impl BitXorAssign for SmallU { + fn bitxor_assign(&mut self, rhs: Self) { + self.0 ^= rhs.0; + } +} + +impl Deref for SmallU { + type Target = u8; + + /// Returns a reference to the inner `u8` value. + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A concrete instantiation of [`SmallU`] with a 1-bit width. +/// +/// This type allows only the values `0` and `1`. +pub type U1 = SmallU<1>; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_u1_initialization() { + // Test U1 values + assert_eq!(U1::new(0), U1::ZERO); + assert_eq!(U1::new(1), U1::ONE); + + // Should wrap around to 0 due to bit masking + assert_eq!(U1::new(2), U1::ZERO); + } + + #[test] + fn test_u1_bitwise_operations() { + // Initialize U1 values. + let a = U1::new(0); + let b = U1::new(1); + + // Test bitwise AND operation. + // 0 AND 1 = 0. + assert_eq!(a & b, U1::new(0)); + // Test bitwise OR operation. + // 0 OR 1 = 1. + assert_eq!(a | b, U1::new(1)); + // Test bitwise XOR operation. + // 0 XOR 1 = 1. + assert_eq!(a ^ b, U1::new(1)); + + // Repeat tests where both operands are `b` (value 1). + // 1 AND 1 = 1. + assert_eq!(b & b, U1::new(1)); + // 1 OR 1 = 1. + assert_eq!(b | b, U1::new(1)); + // 1 XOR 1 = 0. + assert_eq!(b ^ b, U1::new(0)); + + // Repeat tests where both operands are `a` (value 0). + // 0 AND 0 = 0. + assert_eq!(a & a, U1::new(0)); + // 0 OR 0 = 0. + assert_eq!(a | a, U1::new(0)); + // 0 XOR 0 = 0. + assert_eq!(a ^ a, U1::new(0)); + } + + #[test] + fn test_u1_constants() { + // `U1::ZERO` is defined as the U1 representation of 0. + assert_eq!(U1::ZERO, U1::new(0)); + + // `U1::ONE` is defined as the U1 representation of 1. + assert_eq!(U1::ONE, U1::new(1)); + + // In a U1 type, `MAX` represents all bits set, which is equivalent to 1 since only one bit + // is allowed. + assert_eq!(U1::MAX, U1::new(1)); + } + + #[test] + fn test_u1_overflow() { + // Should wrap around to 0 + assert_eq!(U1::new(2), U1::new(0)); + // Should wrap around to 1 + assert_eq!(U1::new(3), U1::new(1)); + } + + #[test] + fn test_u1_equality_and_ordering() { + // Create a U1 instance `a` with the value 0. + let a = U1::new(0); + + // Create another U1 instance `b` with the value 1. + let b = U1::new(1); + + // Check that `a` is less than `b`. + // This verifies the `<` operator implementation for U1. + assert!(a < b); + + // Check that `b` is greater than `a`. + // This verifies the `>` operator implementation for U1. + assert!(b > a); + } + + #[test] + fn test_bitwise_operations() { + let a = U1::new(0); + let b = U1::new(1); + + // Bitwise AND + assert_eq!(a & b, U1::new(0)); + assert_eq!(b & b, U1::new(1)); + + // Bitwise OR + assert_eq!(a | b, U1::new(1)); + assert_eq!(b | b, U1::new(1)); + + // Bitwise XOR + assert_eq!(a ^ b, U1::new(1)); + assert_eq!(b ^ b, U1::new(0)); + + let mut c = U1::new(1); + c &= U1::new(0); + assert_eq!(c, U1::new(0)); + + let mut d = U1::new(0); + d |= U1::new(1); + assert_eq!(d, U1::new(1)); + + let mut e = U1::new(1); + e ^= U1::new(1); + assert_eq!(e, U1::new(0)); + } + + #[test] + fn test_non_zero_one_bitwise_operations() { + let a = U1::new(3); // 3 wraps to 1 + let b = U1::new(2); // 2 wraps to 0 + + // Bitwise AND + assert_eq!(a & b, U1::new(0)); + + // Bitwise OR + assert_eq!(a | b, U1::new(1)); + + // Bitwise XOR + assert_eq!(a ^ b, U1::new(1)); + + let mut c = U1::new(3); + c &= U1::new(2); + assert_eq!(c, U1::new(0)); + + let mut d = U1::new(2); + d |= U1::new(3); + assert_eq!(d, U1::new(1)); + + let mut e = U1::new(3); + e ^= U1::new(2); + assert_eq!(e, U1::new(1)); + } +} diff --git a/ff/src/fields/mod.rs b/ff/src/fields/mod.rs index dcacfe356..c5f228bf5 100644 --- a/ff/src/fields/mod.rs +++ b/ff/src/fields/mod.rs @@ -26,6 +26,8 @@ pub use self::models::*; pub mod field_hashers; +pub mod binary; + mod prime; pub use prime::*;