commonware_math/algebra.rs
1//! Provides traits for algebraic operations.
2//!
3//! These traits are designed to lean on the existing Rust operations in [`std::ops`],
4//! so that the familiar `+`, `+=`, etc. operators can be used. The traits are also
5//! designed with performant implementations in mind, so implementations try to
6//! use methods which don't require copying unnecessarily.
7use commonware_parallel::Strategy as ParStrategy;
8use core::{
9 fmt::Debug,
10 iter,
11 ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
12};
13use rand_core::CryptoRngCore;
14
15/// Yield all the bits in a u64, from lowest to highest.
16fn yield_bits_le(x: u64) -> impl Iterator<Item = bool> {
17 (0..64).map(move |i| (x >> i) & 1 != 0)
18}
19
20/// Yield the bits in a u64, until they all become 0.
21fn yield_bits_le_until_zeroes(x: u64) -> impl Iterator<Item = bool> {
22 (0..64 - x.leading_zeros()).map(move |i| (x >> i) & 1 != 0)
23}
24
25/// Yield all of the bits in an array of u64s, in little endian order.
26fn yield_bits_le_arr(xs: &[u64]) -> impl Iterator<Item = bool> + use<'_> {
27 let (&last, start) = xs.split_last().unwrap_or((&0, &[]));
28 start
29 .iter()
30 .copied()
31 .flat_map(yield_bits_le)
32 .chain(yield_bits_le_until_zeroes(last))
33}
34
35/// Inner utility for [`Additive::scale`] and [`Ring::exp`].
36///
37/// The "double-and-add" / "square-and-multiply" algorithms work over an arbitrary
38/// monoid, i.e. something supporting:
39///
40/// 1. 1 : T
41/// 2. (<>) : T -> T -> T
42///
43/// We take these two operations, along with a helper for applying the operation
44/// to oneself, in order to make the algorithm generic.
45fn monoid_exp<T: Clone>(
46 zero: T,
47 op: impl Fn(&mut T, &T),
48 self_op: impl Fn(&mut T),
49 x: &T,
50 bits_le: &[u64],
51) -> T {
52 let mut acc = zero;
53 let mut w = x.clone();
54 for b in yield_bits_le_arr(bits_le) {
55 if b {
56 op(&mut acc, &w);
57 }
58 self_op(&mut w)
59 }
60 acc
61}
62
63/// Return `shift, shift * base, shift * base^2, ...`.
64///
65/// Use `powers(R::one(), base)` if you just want the standard powers.
66pub fn powers<R: Ring>(shift: R, base: &R) -> impl Iterator<Item = R> + '_ {
67 iter::successors(Some(shift), move |state: &R| Some(state.clone() * base))
68}
69
70/// A basic trait we expect algebraic data structures to implement.
71///
72/// Types implementing this trait need to support:
73///
74/// 1. `T.clone()`,
75/// 2. `format!("{:?}", &T)`
76/// 2. `&T == &T`,
77/// 3. `&T != &T`.
78///
79/// In other words, being clonable, and comparable for equality.
80pub trait Object: Clone + Debug + PartialEq + Eq + Send + Sync {}
81
82/// A type that supports addition, subtraction, and negation.
83///
84/// For some type `T` implementing this trait, the following operations must be
85/// supported:
86///
87/// 1. `&mut T += &T`,
88/// 2. `T + &T`,
89/// 3. `&mut T -= &T`,
90/// 4. `T - &T`,
91/// 5. `-T`.
92///
93/// There are other combinations of borrowing that could be chosen, but these
94/// should be efficiently implementable, even for a "heavier" struct, e.g.
95/// a vector of values.
96///
97/// # Properties
98///
99/// Implementations are expected to form a commutative group under addition:
100///
101/// - `+=` and `-=` agree with `+` and `-`,
102/// - addition satisfies `a + b = b + a` and `(a + b) + c = a + (b + c)`,
103/// - [`Additive::zero`] satisfies `0 + a = a`,
104/// - negation satisfies `a + (-a) = 0` and `a - b = a + (-b)`,
105/// - [`Additive::scale`] satisfies `a.scale(&[]) = 0`,
106/// `a.scale(&[1]) = a`, and `a.scale(m + n) = a.scale(m) + a.scale(n)`.
107///
108/// # Usage
109///
110///
111/// ```
112/// # use commonware_math::algebra::Additive;
113///
114/// // We use .clone() whenever ownership is needed.
115/// fn example<T: Additive>(mut x: T, y: T) {
116/// x += &y;
117/// x.clone() + &y;
118/// x -= &y;
119/// x.clone() - &y;
120/// -x.clone();
121/// T::zero();
122/// }
123/// ```
124pub trait Additive:
125 Object
126 + for<'a> AddAssign<&'a Self>
127 + for<'a> Add<&'a Self, Output = Self>
128 + for<'a> SubAssign<&'a Self>
129 + for<'a> Sub<&'a Self, Output = Self>
130 + Neg<Output = Self>
131{
132 /// The neutral element for addition.
133 fn zero() -> Self;
134
135 /// Add an element to itself.
136 ///
137 /// This has a default implementation involving a clone.
138 ///
139 /// This can be overriden if a more efficient implementation is available.
140 fn double(&mut self) {
141 *self += &self.clone();
142 }
143
144 /// Scale this number by a positive integer.
145 ///
146 /// To support arbitrary positive integers, we expect to see 64 bit limbs
147 /// in little endian order.
148 ///
149 /// For example, for a 256 bit integer, we expect a slice of 4 elements,
150 /// starting with the lowest 64 bits.
151 fn scale(&self, bits_le: &[u64]) -> Self {
152 monoid_exp(Self::zero(), |a, b| *a += b, |a| a.double(), self, bits_le)
153 }
154}
155
156/// A type that supports multiplication.
157///
158/// For some type `T` implementing this trait, the following operations must be
159/// supported:
160///
161/// 1. `&mut T *= &T`,
162/// 2. `T * &T`.
163///
164/// As with [`Additive`], the borrowing scheme is chosen to keep implementations
165/// efficient even for heavier structures.
166///
167/// # Properties
168///
169/// Implementations are expected to have a commutative, associative
170/// multiplication:
171///
172/// - `*=` agrees with `*`,
173/// - multiplication satisfies `a * b = b * a` and `(a * b) * c = a * (b * c)`.
174///
175/// # Usage
176///
177/// ```
178/// # use commonware_math::algebra::Multiplicative;
179///
180/// // We use .clone() whenever ownership is needed.
181/// fn example<T: Multiplicative>(mut x: T, y: T) {
182/// x *= &y;
183/// x.clone() * &y;
184/// }
185/// ```
186pub trait Multiplicative:
187 Object + for<'a> MulAssign<&'a Self> + for<'a> Mul<&'a Self, Output = Self>
188{
189 /// Multiply an element with itself.
190 ///
191 /// This has a default implementation involving a clone.
192 ///
193 /// This can be overriden for a specific type that's better.
194 fn square(&mut self) {
195 *self *= &self.clone();
196 }
197}
198
199/// A type which implements [`Additive`], and supports scaling by some other type.
200///
201/// Mathematically, this is a (right) `R`-module.
202///
203/// The following operations must be supported (in addition to [`Additive`]):
204/// 1. `T *= &R`,
205/// 2. `T * &R`
206///
207/// # Properties
208///
209/// Implementations are expected to behave like a right `R`-module action:
210///
211/// - `*=` agrees with `*`,
212/// - scalar multiplication satisfies `(a + b) * x = a * x + b * x`,
213/// - [`Space::msm`] satisfies `msm(points, scalars) = sum_i points[i] * scalars[i]`,
214/// - when `R: Multiplicative`, `((a * x) * y) = a * (x * y)`,
215/// - when `R: Ring`, `a * 1 = a` and `a * 0 = 0`.
216///
217///
218/// # Usage
219///
220/// ```
221/// # use commonware_math::algebra::Space;
222///
223/// // We use .clone() whenever ownership is needed.
224/// fn example<R, T: Space<R>>(mut x: T, y: R) {
225/// x *= &y;
226/// x.clone() * &y;
227/// }
228/// ```
229pub trait Space<R>:
230 Additive + for<'a> MulAssign<&'a R> + for<'a> Mul<&'a R, Output = Self>
231{
232 /// Calculate `sum_i points[i] * scalars[i]`.
233 ///
234 /// There's a default implementation, but for many types, a more efficient
235 /// algorithm is possible.
236 ///
237 /// Both slices should be considered as padded with [`Additive::zero`] so
238 /// that they have the same length.
239 ///
240 /// For empty slices, the result should be [`Additive::zero`];
241 fn msm(points: &[Self], scalars: &[R], _strategy: &impl ParStrategy) -> Self {
242 msm_naive(points, scalars)
243 }
244}
245
246/// A naive implementation of [`Space::msm`].
247///
248/// This is what the trait does by default.
249///
250/// This is useful when implementing the trait, because for small inputs it
251/// might be worth just using the naive implementation, because faster
252/// algorithms have some overhead in terms of allocating space.
253pub fn msm_naive<R, K: Space<R>>(points: &[K], scalars: &[R]) -> K {
254 let mut out = K::zero();
255 for (s, p) in scalars.iter().zip(points.iter()) {
256 out += &(p.clone() * s);
257 }
258 out
259}
260
261impl<R: Additive + Multiplicative> Space<R> for R {}
262
263/// An instance of a mathematical Ring.
264///
265/// This combines [`Additive`] and [`Multiplicative`], and introduces a
266/// neutral element for multiplication, [`Ring::one`].
267///
268/// # Properties
269///
270/// Implementations are expected to be commutative rings:
271///
272/// - [`Additive`] and [`Multiplicative`] laws both hold,
273/// - [`Ring::one`] satisfies `1 * a = a`,
274/// - multiplication satisfies `(a + b) * c = a * c + b * c`,
275/// - [`Ring::exp`] satisfies `a.exp(&[]) = 1`,
276/// `a.exp(&[1]) = a`, and `a.exp(m + n) = a.exp(m) * a.exp(n)`.
277pub trait Ring: Additive + Multiplicative {
278 /// The neutral element for multiplication.
279 ///
280 /// Multiplying by this element does nothing.
281 fn one() -> Self;
282
283 /// Exponentiate this number by a positive integer.
284 ///
285 /// To support arbitrary positive integers, we expect to see 64 bit limbs
286 /// in little endian order.
287 ///
288 /// For example, for a 256 bit integer, we expect a slice of 4 elements,
289 /// starting with the lowest 64 bits.
290 fn exp(&self, bits_le: &[u64]) -> Self {
291 monoid_exp(Self::one(), |a, b| *a *= b, |a| a.square(), self, bits_le)
292 }
293}
294
295/// An instance of a mathematical Field.
296///
297/// This inherits from [`Ring`], and requires the existence of multiplicative
298/// inverses as well.
299///
300/// # Properties
301///
302/// Implementations are expected to satisfy the usual field inverse laws:
303///
304/// - all [`Ring`] laws hold,
305/// - `0.inv() = 0`,
306/// - for any nonzero `a`, `a * a.inv() = 1`.
307pub trait Field: Ring {
308 /// The multiplicative inverse of an element.
309 ///
310 /// For [`Additive::zero`], this should return [`Additive::zero`].
311 ///
312 /// For any other element `x`, this should return an element `y` such that
313 /// `x * y` is equal to [`Ring::one`].
314 fn inv(&self) -> Self;
315}
316
317/// A [`Field`] which supports operations allowing for efficient NTTs.
318///
319/// Fields implementing this trait must have characteristic not equal to 2
320/// (so that 2 is invertible), and must have a multiplicative group with
321/// sufficiently large 2-adic order.
322///
323/// # Properties
324///
325/// Implementations are expected to satisfy the NTT-specific laws checked by
326/// the property tests:
327///
328/// - `root_of_unity(lg)` returns `None` only when `lg > MAX_LG_ROOT_ORDER`,
329/// - otherwise, `root_of_unity(lg)^(2^lg) = 1` and, for `lg > 0`, `root_of_unity(lg)^(2^lg - 1) != 1`,
330/// - `coset_shift()` and `coset_shift_inv()` satisfy `coset_shift() * coset_shift_inv() = 1`,
331/// - `div_2(a)` satisfies `div_2(a) * (1 + 1) = a`.
332pub trait FieldNTT: Field {
333 /// The maximum (lg) of the power of two root of unity this fields supports.
334 const MAX_LG_ROOT_ORDER: u8;
335
336 /// A root of unity of order `2^lg`.
337 ///
338 /// In other words, for `r = root_of_unity(lg)`, `k = 2^lg` should be the
339 /// smallest power such that `r^k = 1`.
340 ///
341 /// This function should return `None` only for `lg > MAX_LG_ROOT_ORDER`.
342 fn root_of_unity(lg: u8) -> Option<Self>;
343
344 /// An element which is not a power of a root of unity.
345 ///
346 /// In other words, for any `lg`, `k`, this element should not equal
347 /// `Self::root_of_unity(lg)^k`.
348 fn coset_shift() -> Self;
349
350 fn coset_shift_inv() -> Self {
351 Self::coset_shift().inv()
352 }
353
354 /// Return the result of dividing this element by `2`.
355 ///
356 /// This is equivalent to `self * (1 + 1)^-1`, but is usually implementable
357 /// in a more efficient way.
358 fn div_2(&self) -> Self {
359 (Self::one() + &Self::one()).inv() * self
360 }
361}
362
363/// A group suitable for use in cryptography.
364///
365/// This is a cyclic group, with a specified generator.
366///
367/// The group is of prime order, and thus has an associated field of scalars.
368///
369/// This trait requires that this type implements [`Space`] over that field.
370pub trait CryptoGroup: Space<Self::Scalar> {
371 type Scalar: Field;
372
373 /// Return the generator point of this group.
374 fn generator() -> Self;
375}
376
377/// A [`CryptoGroup`] which supports obliviously sampling elements.
378///
379/// This capability is also often referred to as "hash to curve", in the
380/// context of Elliptic Curve Cryptography, but we use the term "group"
381/// to match the naming conventions for other traits.
382///
383/// Advanced protocols use this capability to create new generator elements
384/// whose discrete logarithm relative to other points is unknown.
385///
386/// # Properties
387///
388/// - equal `(domain_separator, message)` pairs satisfy
389/// `hash_to_group(dst0, m0) = hash_to_group(dst1, m1)` whenever
390/// `(dst0, m0) = (dst1, m1)`.
391pub trait HashToGroup: CryptoGroup {
392 /// Hash a domain separator, and a message, returning a group element.
393 ///
394 /// This should return an element without knowing its discrete logarithm.
395 ///
396 /// In particular, hashing into a [`CryptoGroup::Scalar`], and then multiplying
397 /// that by [`CryptoGroup::generator`] DOES NOT work.
398 fn hash_to_group(domain_separator: &[u8], message: &[u8]) -> Self;
399
400 /// Convert randomness to a group element, without learning its discrete logarithm.
401 ///
402 /// This has a default implementation assuming 128 bits of collision security.
403 /// This works by generating 256 bits of randomness, and then passing that
404 /// to [`HashToGroup::hash_to_group`].
405 ///
406 /// If you have a more efficient implementation, or want more collision security,
407 /// override this method.
408 fn rand_to_group(mut rng: impl CryptoRngCore) -> Self {
409 let mut bytes = [0u8; 32];
410 rng.fill_bytes(&mut bytes);
411 Self::hash_to_group(&[], &bytes)
412 }
413}
414
415/// A trait for objects that can be randomly sampled.
416///
417/// The only stipulation about this sampling process is that the result
418/// should be indistinguishable from one sampled uniformly at random.
419///
420/// Beyond that, we don't assume that we don't learn other things about the
421/// object as the sampler.
422pub trait Random {
423 /// Sample an object uniformly at random.
424 fn random(rng: impl CryptoRngCore) -> Self;
425}
426
427#[cfg(any(test, feature = "arbitrary"))]
428pub mod test_suites {
429 //! A collection of property tests for algebraic types.
430 //!
431 //! Provides pre-canned test suites that verify algebraic laws hold for a given type.
432 //! For example, [`fuzz_additive`] checks:
433 //!
434 //! - `+=` is consistent with `+`
435 //! - Addition is commutative
436 //! - Addition is associative
437 //! - Zero is the neutral element
438 //! - Negation is the additive inverse
439 //!
440 //! These functions take `&mut Unstructured` so users can run the harness themselves.
441 //!
442 //! # Example
443 //!
444 //! ```
445 //! # use commonware_math::algebra::test_suites::*;
446 //! # use commonware_math::fields::goldilocks::F;
447 //! commonware_invariants::minifuzz::test(|u| fuzz_field::<F>(u));
448 //! ```
449 use super::*;
450 use arbitrary::{Arbitrary, Unstructured};
451
452 fn check_add_assign<T: Additive>(a: T, b: T) {
453 let mut acc = a.clone();
454 acc += &b;
455 assert_eq!(acc, a + &b, "+= does not match +");
456 }
457
458 fn check_add_commutes<T: Additive>(a: T, b: T) {
459 assert_eq!(a.clone() + &b, b + &a, "+ not commutative");
460 }
461
462 fn check_add_associates<T: Additive>(a: T, b: T, c: T) {
463 assert_eq!((a.clone() + &b) + &c, a + &(b + &c), "+ not associative");
464 }
465
466 fn check_add_zero<T: Additive>(a: T) {
467 assert_eq!(T::zero() + &a, a, "a + 0 != a");
468 }
469
470 fn check_add_neg_self<T: Additive>(a: T) {
471 let neg_a = -a.clone();
472 assert_eq!(T::zero(), a + &neg_a, "a - a != 0");
473 }
474
475 fn check_sub_vs_add_neg<T: Additive>(a: T, b: T) {
476 assert_eq!(a.clone() - &b, a + &-b, "a - b != a + (-b)");
477 }
478
479 fn check_sub_assign<T: Additive>(a: T, b: T) {
480 let mut acc = a.clone();
481 acc -= &b;
482 assert_eq!(acc, a - &b, "-= different from -");
483 }
484
485 /// Fuzz the [`Additive`] trait properties.
486 ///
487 /// Takes arbitrary data and checks that algebraic laws hold.
488 pub fn fuzz_additive<T: Additive + for<'a> Arbitrary<'a>>(
489 u: &mut Unstructured<'_>,
490 ) -> arbitrary::Result<()> {
491 let a: T = u.arbitrary()?;
492 let b: T = u.arbitrary()?;
493 let c: T = u.arbitrary()?;
494 check_add_assign(a.clone(), b.clone());
495 check_add_commutes(a.clone(), b.clone());
496 check_add_associates(a.clone(), b.clone(), c);
497 check_add_zero(a.clone());
498 check_add_neg_self(a.clone());
499 check_sub_vs_add_neg(a.clone(), b.clone());
500 check_sub_assign(a, b);
501 Ok(())
502 }
503
504 fn check_mul_assign<T: Multiplicative>(a: T, b: T) {
505 let mut acc = a.clone();
506 acc *= &b;
507 assert_eq!(acc, a * &b, "*= different from *");
508 }
509
510 fn check_mul_commutes<T: Multiplicative>(a: T, b: T) {
511 assert_eq!(a.clone() * &b, b * &a, "* not commutative");
512 }
513
514 fn check_mul_associative<T: Multiplicative>(a: T, b: T, c: T) {
515 assert_eq!((a.clone() * &b) * &c, a * &(b * &c), "* not associative");
516 }
517
518 /// Fuzz the [`Multiplicative`] trait properties.
519 ///
520 /// Takes arbitrary data and checks that algebraic laws hold.
521 pub fn fuzz_multiplicative<T: Multiplicative + for<'a> Arbitrary<'a>>(
522 u: &mut Unstructured<'_>,
523 ) -> arbitrary::Result<()> {
524 let a: T = u.arbitrary()?;
525 let b: T = u.arbitrary()?;
526 let c: T = u.arbitrary()?;
527 check_mul_assign(a.clone(), b.clone());
528 check_mul_commutes(a.clone(), b.clone());
529 check_mul_associative(a, b, c);
530 Ok(())
531 }
532
533 fn check_mul_one<T: Ring>(a: T) {
534 assert_eq!(T::one() * &a, a, "a * 1 != a");
535 }
536
537 fn check_mul_distributes<T: Ring>(a: T, b: T, c: T) {
538 assert_eq!(
539 (a.clone() + &b) * &c,
540 a * &c + &(b * &c),
541 "(a + b) * c != a * c + b * c"
542 );
543 }
544
545 /// Fuzz the [`Ring`] trait properties.
546 ///
547 /// Takes arbitrary data and checks that algebraic laws hold.
548 /// This also checks [`Additive`] and [`Multiplicative`] properties.
549 pub fn fuzz_ring<T: Ring + for<'a> Arbitrary<'a>>(
550 u: &mut Unstructured<'_>,
551 ) -> arbitrary::Result<()> {
552 fuzz_additive::<T>(u)?;
553 fuzz_multiplicative::<T>(u)?;
554 let a: T = u.arbitrary()?;
555 let b: T = u.arbitrary()?;
556 let c: T = u.arbitrary()?;
557 check_mul_one(a.clone());
558 check_mul_distributes(a, b, c);
559 Ok(())
560 }
561
562 fn check_inv<T: Field>(a: T) {
563 if a == T::zero() {
564 assert_eq!(T::zero(), a.inv(), "0.inv() != 0");
565 } else {
566 assert_eq!(a.inv() * &a, T::one(), "a * a.inv() != 1");
567 }
568 }
569
570 /// Fuzz the [`Field`] trait properties.
571 ///
572 /// Takes arbitrary data and checks that algebraic laws hold.
573 /// This also checks [`Ring`] properties.
574 pub fn fuzz_field<T: Field + for<'a> Arbitrary<'a>>(
575 u: &mut Unstructured<'_>,
576 ) -> arbitrary::Result<()> {
577 fuzz_ring::<T>(u)?;
578 let a: T = u.arbitrary()?;
579 check_inv(a);
580 Ok(())
581 }
582
583 fn check_scale_distributes<R, K: Space<R>>(a: K, b: K, x: R) {
584 assert_eq!((a.clone() + &b) * &x, a * &x + &(b * &x));
585 }
586
587 fn check_scale_assign<R, K: Space<R>>(a: K, b: R) {
588 let mut acc = a.clone();
589 acc *= &b;
590 assert_eq!(acc, a * &b);
591 }
592
593 fn check_msm_eq_naive<R, K: Space<R>>(points: &[K], scalars: &[R]) {
594 use commonware_parallel::Sequential;
595 assert_eq!(
596 msm_naive(points, scalars),
597 K::msm(points, scalars, &Sequential)
598 );
599 }
600
601 /// Fuzz the [`Space`] trait properties, assuming nothing about the scalar `R`.
602 ///
603 /// Takes arbitrary data and checks that algebraic laws hold.
604 pub fn fuzz_space<R: Debug + for<'a> Arbitrary<'a>, K: Space<R> + for<'a> Arbitrary<'a>>(
605 u: &mut Unstructured<'_>,
606 ) -> arbitrary::Result<()> {
607 let a: K = u.arbitrary()?;
608 let b: K = u.arbitrary()?;
609 let x: R = u.arbitrary()?;
610 check_scale_distributes(a.clone(), b, x);
611 let c: R = u.arbitrary()?;
612 check_scale_assign(a, c);
613 let len: usize = u.int_in_range(0..=16)?;
614 let points: Vec<K> = (0..len)
615 .map(|_| u.arbitrary())
616 .collect::<arbitrary::Result<_>>()?;
617 let scalars: Vec<R> = (0..len)
618 .map(|_| u.arbitrary())
619 .collect::<arbitrary::Result<_>>()?;
620 check_msm_eq_naive(&points, &scalars);
621 Ok(())
622 }
623
624 fn check_scale_compat<R: Multiplicative, K: Space<R>>(a: K, b: R, c: R) {
625 assert_eq!((a.clone() * &b) * &c, a * &(b * &c));
626 }
627
628 /// Fuzz the [`Space`] trait properties, assuming `R` is [`Multiplicative`].
629 ///
630 /// Takes arbitrary data and checks that algebraic laws hold.
631 /// This also checks base [`Space`] properties plus compatibility with multiplication.
632 pub fn fuzz_space_multiplicative<
633 R: Multiplicative + for<'a> Arbitrary<'a>,
634 K: Space<R> + for<'a> Arbitrary<'a>,
635 >(
636 u: &mut Unstructured<'_>,
637 ) -> arbitrary::Result<()> {
638 fuzz_space::<R, K>(u)?;
639 let a: K = u.arbitrary()?;
640 let b: R = u.arbitrary()?;
641 let c: R = u.arbitrary()?;
642 check_scale_compat(a, b, c);
643 Ok(())
644 }
645
646 fn check_scale_one<R: Ring, K: Space<R>>(a: K) {
647 assert_eq!(a.clone(), a * &R::one());
648 }
649
650 fn check_scale_zero<R: Ring, K: Space<R>>(a: K) {
651 assert_eq!(K::zero(), a * &R::zero());
652 }
653
654 /// Fuzz the [`Space`] trait properties, assuming `R` is a [`Ring`].
655 ///
656 /// Takes arbitrary data and checks that algebraic laws hold.
657 /// This also checks [`fuzz_space_multiplicative`] properties plus compatibility
658 /// with [`Ring::one()`] and [`Additive::zero()`].
659 pub fn fuzz_space_ring<R: Ring + for<'a> Arbitrary<'a>, K: Space<R> + for<'a> Arbitrary<'a>>(
660 u: &mut Unstructured<'_>,
661 ) -> arbitrary::Result<()> {
662 fuzz_space_multiplicative::<R, K>(u)?;
663 let a: K = u.arbitrary()?;
664 check_scale_one::<R, K>(a.clone());
665 check_scale_zero::<R, K>(a);
666 Ok(())
667 }
668
669 fn check_hash_to_group<G: HashToGroup>(data: [[u8; 4]; 4]) {
670 let (dst0, m0, dst1, m1) = (&data[0], &data[1], &data[2], &data[3]);
671 assert_eq!(
672 (dst0, m0) == (dst1, m1),
673 G::hash_to_group(dst0, m0) == G::hash_to_group(dst1, m1)
674 );
675 }
676
677 /// Fuzz the [`HashToGroup`] trait properties.
678 ///
679 /// Takes arbitrary data and checks that the hash function is deterministic.
680 /// This doesn't check any properties related to [`CryptoGroup`].
681 pub fn fuzz_hash_to_group<G: HashToGroup>(u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
682 let data: [[u8; 4]; 4] = u.arbitrary()?;
683 check_hash_to_group::<G>(data);
684 Ok(())
685 }
686
687 fn check_root_of_unity_order<T: FieldNTT>(lg: u8) {
688 if lg > T::MAX_LG_ROOT_ORDER {
689 assert!(
690 T::root_of_unity(lg).is_none(),
691 "root_of_unity should be None for lg > MAX"
692 );
693 return;
694 }
695 let root = T::root_of_unity(lg).expect("root_of_unity should be Some for lg <= MAX");
696
697 let mut order = Vec::new();
698 let mut remaining = lg;
699 while remaining >= 64 {
700 order.push(0u64);
701 remaining -= 64;
702 }
703 order.push(1u64 << remaining);
704
705 assert_eq!(root.exp(&order), T::one(), "root^(2^lg) should equal 1");
706 if lg > 0 {
707 let last = order.len() - 1;
708 order[0] = order[0].wrapping_sub(1);
709 for i in 0..last {
710 if order[i] == u64::MAX {
711 order[i + 1] = order[i + 1].wrapping_sub(1);
712 }
713 }
714 assert_ne!(
715 root.exp(&order),
716 T::one(),
717 "root^(2^lg - 1) should not equal 1"
718 );
719 }
720 }
721
722 fn check_div_2<T: FieldNTT>(a: T) {
723 let two = T::one() + &T::one();
724 assert_eq!(a.div_2() * &two, a, "div_2(a) * 2 should equal a");
725 }
726
727 fn check_coset_shift_inv<T: FieldNTT>() {
728 assert_eq!(
729 T::coset_shift() * &T::coset_shift_inv(),
730 T::one(),
731 "coset_shift * coset_shift_inv should equal 1"
732 );
733 }
734
735 /// Run the test suite for the [`FieldNTT`] trait.
736 ///
737 /// This will also run [`fuzz_field`].
738 pub fn fuzz_field_ntt<T: FieldNTT + for<'a> Arbitrary<'a>>(
739 u: &mut Unstructured<'_>,
740 ) -> arbitrary::Result<()> {
741 fuzz_field::<T>(u)?;
742
743 // Biased choice towards div_2 checks
744 match u.int_in_range(0u8..=9)? {
745 0 => {
746 check_coset_shift_inv::<T>();
747 }
748 1 => {
749 let lg = u.int_in_range(0..=T::MAX_LG_ROOT_ORDER + 1)?;
750 check_root_of_unity_order::<T>(lg);
751 }
752 _ => {
753 check_div_2(T::arbitrary(u)?);
754 }
755 }
756 Ok(())
757 }
758}
759
760commonware_macros::stability_scope!(ALPHA {
761 #[cfg(any(test, feature = "fuzz"))]
762 pub mod fuzz {
763 use super::*;
764 use crate::fields::goldilocks::F;
765 use arbitrary::{Arbitrary, Unstructured};
766 use commonware_parallel::Sequential;
767
768 #[derive(Debug, Arbitrary)]
769 pub enum Plan {
770 ExpOne(F),
771 ExpZero(F),
772 Exp(F, u32, u32),
773 PowersMatchesExp(F, F, u16),
774 ScaleOne(F),
775 ScaleZero(F),
776 Scale(F, u32, u32),
777 Msm2([F; 2], [F; 2]),
778 }
779
780 impl Plan {
781 pub fn run(self, _u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
782 match self {
783 Self::ExpOne(x) => {
784 assert_eq!(x.exp(&[1]), x);
785 }
786 Self::ExpZero(x) => {
787 assert_eq!(x.exp(&[]), F::one());
788 }
789 Self::Exp(x, a, b) => {
790 let a = u64::from(a);
791 let b = u64::from(b);
792 assert_eq!(x.exp(&[a + b]), x.exp(&[a]) * x.exp(&[b]));
793 }
794 Self::PowersMatchesExp(shift, base, index) => {
795 let pow_i = powers(shift, &base)
796 .take(usize::from(index) + 1)
797 .last()
798 .expect("len=index+1 guarantees at least one item");
799 assert_eq!(pow_i, shift * base.exp(&[u64::from(index)]));
800 }
801 Self::ScaleOne(x) => {
802 assert_eq!(x.scale(&[1]), x);
803 }
804 Self::ScaleZero(x) => {
805 assert_eq!(x.scale(&[]), F::zero());
806 }
807 Self::Scale(x, a, b) => {
808 let a = u64::from(a);
809 let b = u64::from(b);
810 assert_eq!(x.scale(&[a + b]), x.scale(&[a]) + x.scale(&[b]));
811 }
812 Self::Msm2(a, b) => {
813 assert_eq!(F::msm(&a, &b, &Sequential), a[0] * b[0] + a[1] * b[1]);
814 }
815 }
816 Ok(())
817 }
818 }
819
820 #[test]
821 fn test_fuzz() {
822 commonware_invariants::minifuzz::test(|u| u.arbitrary::<Plan>()?.run(u));
823 }
824 }
825});