use crate::algebra::{Additive, CryptoGroup, Field, Multiplicative, Object, Random, Ring, Space}; use commonware_codec::{FixedSize, Read, ReadExt, Write}; use core::{ fmt::Debug, ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; use rand_core::CryptoRngCore; const P: u64 = 4_611_686_018_427_389_243; const Q: u64 = 9_223_372_036_854_778_487; fn mul_mod(a: u64, b: u64, p: u64) -> u64 { ((u128::from(a) * u128::from(b)) % u128::from(p)) as u64 } /// The prime field F_p for the test modulus `p`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct F(u64); impl F { pub const MAX: u64 = P - 1; } impl FixedSize for F { const SIZE: usize = 8; } impl Write for F { fn write(&self, buf: &mut impl bytes::BufMut) { self.0.write(buf); } } impl Read for F { type Cfg = (); fn read_cfg( buf: &mut impl bytes::Buf, _cfg: &Self::Cfg, ) -> Result { let value = u64::read(buf)?; if value >= P { return Err(commonware_codec::Error::Invalid("F", "out of range")); } Ok(Self(value)) } } impl From for F { fn from(value: u8) -> Self { Self::from(u64::from(value)) } } impl From for F { fn from(value: u64) -> Self { Self(value % P) } } impl Object for F {} impl Random for F { fn random(mut rng: impl CryptoRngCore) -> Self { let mut bytes = [0u8; 8]; rng.fill_bytes(&mut bytes); Self(u64::from_le_bytes(bytes) % P) } } impl<'a> Add<&'a Self> for F { type Output = Self; fn add(self, rhs: &'a Self) -> Self::Output { let sum = self.0 + rhs.0; Self(if sum >= P { sum - P } else { sum }) } } impl<'a> AddAssign<&'a Self> for F { fn add_assign(&mut self, rhs: &'a Self) { *self = *self + rhs; } } impl Neg for F { type Output = Self; fn neg(self) -> Self::Output { Self(if self.0 == 0 { 0 } else { P - self.0 }) } } impl<'a> Sub<&'a Self> for F { type Output = Self; fn sub(self, rhs: &'a Self) -> Self::Output { self + &-*rhs } } impl<'a> SubAssign<&'a Self> for F { fn sub_assign(&mut self, rhs: &'a Self) { *self = *self - rhs; } } impl Additive for F { fn zero() -> Self { Self(0) } } impl<'a> Mul<&'a Self> for F { type Output = Self; fn mul(self, rhs: &'a Self) -> Self::Output { Self(mul_mod(self.0, rhs.0, P)) } } impl<'a> MulAssign<&'a Self> for F { fn mul_assign(&mut self, rhs: &'a Self) { *self = *self * rhs; } } impl Multiplicative for F {} impl Ring for F { fn one() -> Self { Self(1) } } impl Field for F { fn inv(&self) -> Self { self.exp(&[P - 2]) } } #[cfg(any(test, feature = "arbitrary"))] impl arbitrary::Arbitrary<'_> for F { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { Ok(Self(u.arbitrary::()? % P)) } } /// A prime group of order `p`. /// /// This is constructed as a subgroup of the units in `F_q`. /// /// `q = 2p + 1`, so the group of units has a subgroup of order `p`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct G(u64); impl FixedSize for G { const SIZE: usize = 8; } impl Write for G { fn write(&self, buf: &mut impl bytes::BufMut) { self.0.write(buf); } } impl Read for G { type Cfg = (); fn read_cfg( buf: &mut impl bytes::Buf, _cfg: &Self::Cfg, ) -> Result { let value = u64::read(buf)?; if value >= Q { return Err(commonware_codec::Error::Invalid("G", "out of range")); } let out = Self(value); if out.0 == 0 || out.scale(&[P]).0 != 1 { return Err(commonware_codec::Error::Invalid("G", "not in subgroup")); } Ok(out) } } impl Object for G {} impl<'a> Add<&'a Self> for G { type Output = Self; fn add(self, rhs: &'a Self) -> Self::Output { Self(mul_mod(self.0, rhs.0, Q)) } } impl<'a> AddAssign<&'a Self> for G { fn add_assign(&mut self, rhs: &'a Self) { *self = *self + rhs; } } impl Neg for G { type Output = Self; fn neg(self) -> Self::Output { self.scale(&[Q - 2]) } } impl<'a> Sub<&'a Self> for G { type Output = Self; fn sub(self, rhs: &'a Self) -> Self::Output { self + &-*rhs } } impl<'a> SubAssign<&'a Self> for G { fn sub_assign(&mut self, rhs: &'a Self) { *self = *self - rhs; } } impl Additive for G { fn zero() -> Self { Self(1) } } impl<'a> Mul<&'a F> for G { type Output = Self; fn mul(self, rhs: &'a F) -> Self::Output { self.scale(&[rhs.0]) } } impl<'a> MulAssign<&'a F> for G { fn mul_assign(&mut self, rhs: &'a F) { *self = *self * rhs; } } impl Space for G {} impl CryptoGroup for G { type Scalar = F; fn generator() -> Self { // 4 = 2^2 is a non-trivial quadratic residue, so it generates the // unique subgroup of order p because p is prime. Self(4) } } #[cfg(any(test, feature = "arbitrary"))] impl arbitrary::Arbitrary<'_> for G { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { Ok(Self::generator() * &u.arbitrary::()?) } } commonware_macros::stability_scope!(ALPHA { #[cfg(any(test, feature = "fuzz"))] pub mod fuzz { use super::*; use crate::algebra::test_suites; use arbitrary::{Arbitrary, Unstructured}; use commonware_codec::Encode as _; #[derive(Debug, Arbitrary)] pub enum Plan { FCodec(F), GCodec(G), FuzzField, FuzzSpace, } impl Plan { pub fn run(self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> { match self { Self::FCodec(x) => { assert_eq!(&x, &F::read(&mut x.encode()).unwrap()); } Self::GCodec(x) => { assert_eq!(&x, &G::read(&mut x.encode()).unwrap()); } Self::FuzzField => { test_suites::fuzz_field::(u)?; } Self::FuzzSpace => { test_suites::fuzz_space::(u)?; } } Ok(()) } } #[test] fn test_fuzz() { use commonware_invariants::minifuzz; minifuzz::test(|u| u.arbitrary::()?.run(u)); } } }); #[allow(clippy::module_inception)] #[cfg(test)] mod test { use super::*; use commonware_codec::Encode; use commonware_utils::test_rng; #[test] fn test_field_and_group_guards() { let mut rng = test_rng(); let random = F::random(&mut rng); assert!(random == F::zero() || random.inv() * &random == F::one()); assert!(F::read(&mut P.encode().as_ref()).is_err()); assert!(G::read(&mut Q.encode().as_ref()).is_err()); assert!(G::read(&mut 0u64.encode().as_ref()).is_err()); } #[cfg(feature = "arbitrary")] mod conformance { use super::super::*; use commonware_codec::conformance::CodecConformance; commonware_conformance::conformance_tests! { CodecConformance, CodecConformance } } }