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    ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
11};
12use rand_core::CryptoRngCore;
13
14/// Yield all the bits in a u64, from lowest to highest.
15fn yield_bits_le(x: u64) -> impl Iterator<Item = bool> {
16    (0..64).map(move |i| (x >> i) & 1 != 0)
17}
18
19/// Yield the bits in a u64, until they all become 0.
20fn yield_bits_le_until_zeroes(x: u64) -> impl Iterator<Item = bool> {
21    (0..64 - x.leading_zeros()).map(move |i| (x >> i) & 1 != 0)
22}
23
24/// Yield all of the bits in an array of u64s, in little endian order.
25fn yield_bits_le_arr(xs: &[u64]) -> impl Iterator<Item = bool> + use<'_> {
26    let (&last, start) = xs.split_last().unwrap_or((&0, &[]));
27    start
28        .iter()
29        .copied()
30        .flat_map(yield_bits_le)
31        .chain(yield_bits_le_until_zeroes(last))
32}
33
34/// Inner utility for [`Additive::scale`] and [`Ring::exp`].
35///
36/// The "double-and-add" / "square-and-multiply" algorithms work over an arbitrary
37/// monoid, i.e. something supporting:
38///
39/// 1. 1 : T
40/// 2. (<>) : T -> T -> T
41///
42/// We take these two operations, along with a helper for applying the operation
43/// to oneself, in order to make the algorithm generic.
44fn monoid_exp<T: Clone>(
45    zero: T,
46    op: impl Fn(&mut T, &T),
47    self_op: impl Fn(&mut T),
48    x: &T,
49    bits_le: &[u64],
50) -> T {
51    let mut acc = zero;
52    let mut w = x.clone();
53    for b in yield_bits_le_arr(bits_le) {
54        if b {
55            op(&mut acc, &w);
56        }
57        self_op(&mut w)
58    }
59    acc
60}
61
62/// A basic trait we expect algebraic data structures to implement.
63///
64/// Types implementing this trait need to support:
65///
66/// 1. `T.clone()`,
67/// 2. `format!("{:?}", &T)`
68/// 2. `&T == &T`,
69/// 3. `&T != &T`.
70///
71/// In other words, being clonable, and comparable for equality.
72pub trait Object: Clone + Debug + PartialEq + Eq + Send + Sync {}
73
74/// A type that supports addition, subtraction, and negation.
75///
76/// For some type `T` implementing this trait, the following operations must be
77/// supported:
78///
79/// 1. `&mut T += &T`,
80/// 2. `T + &T`,
81/// 3. `&mut T -= &T`,
82/// 4. `T - &T`,
83/// 5. `-T`.
84///
85/// There are other combinations of borrowing that could be chosen, but these
86/// should be efficiently implementable, even for a "heavier" struct, e.g.
87/// a vector of values.
88///
89/// # Usage
90///
91///
92/// ```
93/// # use commonware_math::algebra::Additive;
94///
95/// // We use .clone() whenever ownership is needed.
96/// fn example<T: Additive>(mut x: T, y: T) {
97///     x += &y;
98///     x.clone() + &y;
99///     x -= &y;
100///     x.clone() - &y;
101///     -x.clone();
102///     T::zero();
103/// }
104/// ```
105pub trait Additive:
106    Object
107    + for<'a> AddAssign<&'a Self>
108    + for<'a> Add<&'a Self, Output = Self>
109    + for<'a> SubAssign<&'a Self>
110    + for<'a> Sub<&'a Self, Output = Self>
111    + Neg<Output = Self>
112{
113    /// The neutral element for addition.
114    fn zero() -> Self;
115
116    /// Add an element to itself.
117    ///
118    /// This has a default implementation involving a clone.
119    ///
120    /// This can be overriden if a more efficient implementation is available.
121    fn double(&mut self) {
122        *self += &self.clone();
123    }
124
125    /// Scale this number by a positive integer.
126    ///
127    /// To support arbitrary positive integers, we expect to see 64 bit limbs
128    /// in little endian order.
129    ///
130    /// For example, for a 256 bit integer, we expect a slice of 4 elements,
131    /// starting with the lowest 64 bits.
132    fn scale(&self, bits_le: &[u64]) -> Self {
133        monoid_exp(Self::zero(), |a, b| *a += b, |a| a.double(), self, bits_le)
134    }
135}
136
137/// A type that supports multiplication.
138///
139/// For some type `T` implementing this trait, the following operations must be
140/// supported:
141///
142/// 1. `&mut T *= &T`,
143/// 2. `T * &T`.
144///
145/// As with [`Additive`], the borrowing scheme is chosen to keep implementations
146/// efficient even for heavier structures.
147///
148/// # Usage
149///
150/// ```
151/// # use commonware_math::algebra::Multiplicative;
152///
153/// // We use .clone() whenever ownership is needed.
154/// fn example<T: Multiplicative>(mut x: T, y: T) {
155///     x *= &y;
156///     x.clone() * &y;
157/// }
158/// ```
159pub trait Multiplicative:
160    Object + for<'a> MulAssign<&'a Self> + for<'a> Mul<&'a Self, Output = Self>
161{
162    /// Multiply an element with itself.
163    ///
164    /// This has a default implementation involving a clone.
165    ///
166    /// This can be overriden for a specific type that's better.
167    fn square(&mut self) {
168        *self *= &self.clone();
169    }
170}
171
172/// A type which implements [`Additive`], and supports scaling by some other type.
173///
174/// Mathematically, this is a (right) `R`-module.
175///
176/// The following operations must be supported (in addition to [`Additive`]):
177/// 1. `T *= &R`,
178/// 2. `T * &R`
179///
180///
181/// # Usage
182///
183/// ```
184/// # use commonware_math::algebra::Space;
185///
186/// // We use .clone() whenever ownership is needed.
187/// fn example<R, T: Space<R>>(mut x: T, y: R) {
188///     x *= &y;
189///     x.clone() * &y;
190/// }
191/// ```
192pub trait Space<R>:
193    Additive + for<'a> MulAssign<&'a R> + for<'a> Mul<&'a R, Output = Self>
194{
195    /// Calculate `sum_i points[i] * scalars[i]`.
196    ///
197    /// There's a default implementation, but for many types, a more efficient
198    /// algorithm is possible.
199    ///
200    /// Both slices should be considered as padded with [`Additive::zero`] so
201    /// that they have the same length.
202    ///
203    /// For empty slices, the result should be [`Additive::zero`];
204    fn msm(points: &[Self], scalars: &[R], _strategy: &impl ParStrategy) -> Self {
205        msm_naive(points, scalars)
206    }
207}
208
209/// A naive implementation of [`Space::msm`].
210///
211/// This is what the trait does by default.
212///
213/// This is useful when implementing the trait, because for small inputs it
214/// might be worth just using the naive implementation, because faster
215/// algorithms have some overhead in terms of allocating space.
216pub fn msm_naive<R, K: Space<R>>(points: &[K], scalars: &[R]) -> K {
217    let mut out = K::zero();
218    for (s, p) in scalars.iter().zip(points.iter()) {
219        out += &(p.clone() * s);
220    }
221    out
222}
223
224impl<R: Additive + Multiplicative> Space<R> for R {}
225
226/// An instance of a mathematical Ring.
227///
228/// This combines [`Additive`] and [`Multiplicative`], and introduces a
229/// neutral element for multiplication, [`Ring::one`].
230pub trait Ring: Additive + Multiplicative {
231    /// The neutral element for multiplication.
232    ///
233    /// Multiplying by this element does nothing.
234    fn one() -> Self;
235
236    /// Exponentiate this number by a positive integer.
237    ///
238    /// To support arbitrary positive integers, we expect to see 64 bit limbs
239    /// in little endian order.
240    ///
241    /// For example, for a 256 bit integer, we expect a slice of 4 elements,
242    /// starting with the lowest 64 bits.
243    fn exp(&self, bits_le: &[u64]) -> Self {
244        monoid_exp(Self::one(), |a, b| *a *= b, |a| a.square(), self, bits_le)
245    }
246}
247
248/// An instance of a mathematical Field.
249///
250/// This inherits from [`Ring`], and requires the existence of multiplicative
251/// inverses as well.
252pub trait Field: Ring {
253    /// The multiplicative inverse of an element.
254    ///
255    /// For [`Additive::zero`], this should return [`Additive::zero`].
256    ///
257    /// For any other element `x`, this should return an element `y` such that
258    /// `x * y` is equal to [`Ring::one`].
259    fn inv(&self) -> Self;
260}
261
262/// A group suitable for use in cryptography.
263///
264/// This is a cyclic group, with a specified generator.
265///
266/// The group is of prime order, and thus has an associated field of scalars.
267///
268/// This trait requires that this type implements [`Space`] over that field.
269pub trait CryptoGroup: Space<Self::Scalar> {
270    type Scalar: Field;
271
272    /// Return the generator point of this group.
273    fn generator() -> Self;
274}
275
276/// A [`CryptoGroup`] which supports obliviously sampling elements.
277///
278/// This capability is also often referred to as "hash to curve", in the
279/// context of Elliptic Curve Cryptography, but we use the term "group"
280/// to match the naming conventions for other traits.
281///
282/// Advanced protocols use this capability to create new generator elements
283/// whose discrete logarithm relative to other points is unknown.
284pub trait HashToGroup: CryptoGroup {
285    /// Hash a domain separator, and a message, returning a group element.
286    ///
287    /// This should return an element without knowing its discrete logarithm.
288    ///
289    /// In particular, hashing into a [`CryptoGroup::Scalar`], and then multiplying
290    /// that by [`CryptoGroup::generator`] DOES NOT work.
291    fn hash_to_group(domain_separator: &[u8], message: &[u8]) -> Self;
292
293    /// Convert randomness to a group element, without learning its discrete logarithm.
294    ///
295    /// This has a default implementation assuming 128 bits of collision security.
296    /// This works by generating 256 bits of randomness, and then passing that
297    /// to [`HashToGroup::hash_to_group`].
298    ///
299    /// If you have a more efficient implementation, or want more collision security,
300    /// override this method.
301    fn rand_to_group(mut rng: impl CryptoRngCore) -> Self {
302        let mut bytes = [0u8; 32];
303        rng.fill_bytes(&mut bytes);
304        Self::hash_to_group(&[], &bytes)
305    }
306}
307
308/// A trait for objects that can be randomly sampled.
309///
310/// The only stipulation about this sampling process is that the result
311/// should be indistinguishable from one sampled uniformly at random.
312///
313/// Beyond that, we don't assume that we don't learn other things about the
314/// object as the sampler.
315pub trait Random {
316    /// Sample an object uniformly at random.
317    fn random(rng: impl CryptoRngCore) -> Self;
318}
319
320#[cfg(any(feature = "test_strategies", test))]
321pub mod test_suites {
322    use super::*;
323    use proptest::{
324        prelude::*,
325        test_runner::{Config, TestRunner},
326    };
327
328    // This alias exists because I got tired of typing this out so many times.
329    type TestResult = Result<(), TestCaseError>;
330
331    fn run_proptest<T: Debug>(
332        file: &'static str,
333        strat: &impl Strategy<Value = T>,
334        test: impl Fn(T) -> TestResult,
335    ) {
336        let config = Config::default().clone_with_source_file(file);
337        TestRunner::new(config).run(strat, test).unwrap()
338    }
339
340    fn check_add_assign<T: Additive>((a, b): (T, T)) -> TestResult {
341        let mut acc = a.clone();
342        acc += &b;
343        prop_assert_eq!(acc, a + &b, "+= does not match +");
344        Ok(())
345    }
346
347    fn check_add_commutes<T: Additive>((a, b): (T, T)) -> TestResult {
348        prop_assert_eq!(a.clone() + &b, b + &a, "+ not commutative");
349        Ok(())
350    }
351
352    fn check_add_associates<T: Additive>((a, b, c): (T, T, T)) -> TestResult {
353        prop_assert_eq!(
354            (a.clone() + &b) + &c,
355            a.clone() + &(b + &c),
356            "+ not associative"
357        );
358        Ok(())
359    }
360
361    fn check_add_zero<T: Additive>(a: T) -> TestResult {
362        prop_assert_eq!(T::zero() + &a, a, "a + 0 != a");
363        Ok(())
364    }
365
366    fn check_add_neg_self<T: Additive>(a: T) -> TestResult {
367        let neg_a = -a.clone();
368        prop_assert_eq!(T::zero(), a + &neg_a, "a - a != 0");
369        Ok(())
370    }
371
372    fn check_sub_vs_add_neg<T: Additive>((a, b): (T, T)) -> TestResult {
373        prop_assert_eq!(a.clone() - &b, a.clone() + &-b, "a - b != a + (-b)");
374        Ok(())
375    }
376
377    fn check_sub_assign<T: Additive>((a, b): (T, T)) -> TestResult {
378        let mut acc = a.clone();
379        acc -= &b;
380        prop_assert_eq!(acc, a - &b, "-= different from -");
381        Ok(())
382    }
383
384    /// Run the test suite for the [`Additive`] trait.
385    ///
386    /// Use `file!()` for the first argument.
387    pub fn test_additive<T: Additive>(file: &'static str, strat: &impl Strategy<Value = T>) {
388        let strat2 = &(strat, strat);
389        let strat3 = &(strat, strat, strat);
390
391        run_proptest(file, strat2, check_add_assign);
392        run_proptest(file, strat2, check_add_commutes);
393        run_proptest(file, strat3, check_add_associates);
394        run_proptest(file, strat, check_add_zero);
395        run_proptest(file, strat, check_add_neg_self);
396        run_proptest(file, strat2, check_sub_vs_add_neg);
397        run_proptest(file, strat2, check_sub_assign);
398    }
399
400    fn check_mul_assign<T: Multiplicative>((a, b): (T, T)) -> TestResult {
401        let mut acc = a.clone();
402        acc *= &b;
403        prop_assert_eq!(acc, a * &b, "*= different from *");
404        Ok(())
405    }
406
407    fn check_mul_commutes<T: Multiplicative>((a, b): (T, T)) -> TestResult {
408        prop_assert_eq!(a.clone() * &b, b * &a, "* not commutative");
409        Ok(())
410    }
411
412    fn check_mul_associative<T: Multiplicative>((a, b, c): (T, T, T)) -> TestResult {
413        prop_assert_eq!((a.clone() * &b) * &c, a * &(b * &c), "* not associative");
414        Ok(())
415    }
416
417    /// Run the test suite for the [`Multiplicative`] trait.
418    ///
419    /// Use `file!()` for the first argument.
420    pub fn test_multiplicative<T: Multiplicative>(
421        file: &'static str,
422        strat: &impl Strategy<Value = T>,
423    ) {
424        let strat2 = &(strat, strat);
425        let strat3 = &(strat, strat, strat);
426
427        run_proptest(file, strat2, check_mul_assign);
428        run_proptest(file, strat2, check_mul_commutes);
429        run_proptest(file, strat3, check_mul_associative);
430    }
431
432    fn check_mul_one<T: Ring>(a: T) -> TestResult {
433        prop_assert_eq!(T::one() * &a, a, "a * 1 != a");
434        Ok(())
435    }
436
437    fn check_mul_distributes<T: Ring>((a, b, c): (T, T, T)) -> TestResult {
438        prop_assert_eq!(
439            (a.clone() + &b) * &c,
440            a * &c + &(b * &c),
441            "(a + b) * c != a * c + b * c"
442        );
443        Ok(())
444    }
445
446    /// Run the test suite for the [`Ring`] trait.
447    ///
448    /// This will also run [`test_additive`] and [`test_multiplicative`].
449    ///
450    /// Use `file!()` for the first argument.
451    pub fn test_ring<T: Ring>(file: &'static str, strat: &impl Strategy<Value = T>) {
452        test_additive(file, strat);
453        test_multiplicative(file, strat);
454
455        let strat3 = &(strat, strat, strat);
456
457        run_proptest(file, strat, check_mul_one);
458        run_proptest(file, strat3, check_mul_distributes);
459    }
460
461    fn check_inv<T: Field>(a: T) -> TestResult {
462        if a == T::zero() {
463            prop_assert_eq!(T::zero(), a.inv(), "0.inv() != 0");
464        } else {
465            prop_assert_eq!(a.inv() * &a, T::one(), "a * a.inv() != 1");
466        }
467        Ok(())
468    }
469
470    /// Run the test suite for the [`Field`] trait.
471    ///
472    /// This will also run [`test_ring`].
473    ///
474    /// Ue `file!()` for the first argument.
475    pub fn test_field<T: Field>(file: &'static str, strat: &impl Strategy<Value = T>) {
476        test_ring(file, strat);
477
478        run_proptest(file, strat, check_inv);
479    }
480
481    fn check_scale_distributes<R, K: Space<R>>((a, b, x): (K, K, R)) -> TestResult {
482        prop_assert_eq!((a.clone() + &b) * &x, a * &x + &(b * &x));
483        Ok(())
484    }
485
486    fn check_scale_assign<R, K: Space<R>>((a, b): (K, R)) -> TestResult {
487        let mut acc = a.clone();
488        acc *= &b;
489        prop_assert_eq!(acc, a * &b);
490        Ok(())
491    }
492
493    fn check_msm_eq_naive<R, K: Space<R>>(points: &[K], scalars: &[R]) -> TestResult {
494        use commonware_parallel::Sequential;
495        prop_assert_eq!(
496            msm_naive(points, scalars),
497            K::msm(points, scalars, &Sequential)
498        );
499        Ok(())
500    }
501
502    /// Run tests for [`Space`], assuming nothing about the scalar `R`.
503    ///
504    /// Use `file!()` for the first argument.
505    pub fn test_space<R: Debug, K: Space<R>>(
506        file: &'static str,
507        r_strat: &impl Strategy<Value = R>,
508        k_strat: &impl Strategy<Value = K>,
509    ) {
510        run_proptest(file, &(k_strat, k_strat, r_strat), check_scale_distributes);
511        run_proptest(file, &(k_strat, r_strat), check_scale_assign);
512        run_proptest(
513            file,
514            &(0..Config::default().max_default_size_range).prop_flat_map(|len| {
515                (
516                    prop::collection::vec(k_strat, len),
517                    prop::collection::vec(r_strat, len),
518                )
519            }),
520            |(points, scalars)| check_msm_eq_naive(&points, &scalars),
521        );
522    }
523
524    fn check_scale_compat<R: Multiplicative, K: Space<R>>((a, b, c): (K, R, R)) -> TestResult {
525        prop_assert_eq!((a.clone() * &b) * &c, a * &(b * &c));
526        Ok(())
527    }
528
529    /// Run tests for [`Space`], assuming `R` is [`Multiplicative`].
530    ///
531    /// This will also run [`test_space`], but check additional compatibility
532    /// properties with `R` having multiplication.
533    ///
534    /// Use `file!()` for the first argument.
535    pub fn test_space_multiplicative<R: Multiplicative, K: Space<R>>(
536        file: &'static str,
537        r_strat: &impl Strategy<Value = R>,
538        k_strat: &impl Strategy<Value = K>,
539    ) {
540        test_space(file, r_strat, k_strat);
541        run_proptest(file, &(k_strat, r_strat, r_strat), check_scale_compat);
542    }
543
544    fn check_scale_one<R: Ring, K: Space<R>>(a: K) -> TestResult {
545        prop_assert_eq!(a.clone(), a * &R::one());
546        Ok(())
547    }
548
549    fn check_scale_zero<R: Ring, K: Space<R>>(a: K) -> TestResult {
550        prop_assert_eq!(K::zero(), a * &R::zero());
551        Ok(())
552    }
553
554    /// Run tests for [`Space`] assuming that `R` is a [`Ring`].
555    ///
556    /// This also runs the tests in [`test_space_multiplicative`].
557    ///
558    /// This additionally checks compatibility with [`Ring::one()`] and
559    /// [`Additive::zero()`].
560    ///
561    /// Use `file!()` for the first argument.
562    pub fn test_space_ring<R: Ring, K: Space<R>>(
563        file: &'static str,
564        r_strat: &impl Strategy<Value = R>,
565        k_strat: &impl Strategy<Value = K>,
566    ) {
567        test_space_multiplicative(file, r_strat, k_strat);
568
569        run_proptest(file, k_strat, check_scale_one);
570        run_proptest(file, k_strat, check_scale_zero);
571    }
572
573    fn check_hash_to_group<G: HashToGroup>(data: [[u8; 4]; 4]) -> TestResult {
574        let (dst0, m0, dst1, m1) = (&data[0], &data[1], &data[2], &data[3]);
575        prop_assert_eq!(
576            (dst0, m0) == (dst1, m1),
577            G::hash_to_group(dst0, m0) == G::hash_to_group(dst1, m1)
578        );
579        Ok(())
580    }
581
582    /// Run tests for [`HashToGroup`].
583    ///
584    /// This doesn't run any tests related to [`CryptoGroup`], just the hash
585    /// to group functionality itself.
586    pub fn test_hash_to_group<G: HashToGroup>(file: &'static str) {
587        run_proptest(file, &any::<[[u8; 4]; 4]>(), check_hash_to_group::<G>);
588    }
589}
590
591#[cfg(test)]
592mod test {
593    use super::*;
594    use crate::fields::goldilocks::F;
595    use commonware_parallel::Sequential;
596    use proptest::prelude::*;
597
598    proptest! {
599        #[test]
600        fn test_exp_one(x: F) {
601            assert_eq!(x.exp(&[1]), x);
602        }
603
604        #[test]
605        fn test_exp_zero(x: F) {
606            assert_eq!(x.exp(&[]), F::one());
607        }
608
609        #[test]
610        fn test_exp(x: F, a: u32, b: u32) {
611            let a = u64::from(a);
612            let b = u64::from(b);
613            assert_eq!(x.exp(&[a + b]), x.exp(&[a]) * x.exp(&[b]));
614        }
615
616        #[test]
617        fn test_scale_one(x: F) {
618            assert_eq!(x.scale(&[1]), x);
619        }
620
621        #[test]
622        fn test_scale_zero(x: F) {
623            assert_eq!(x.scale(&[]), F::zero());
624        }
625
626        #[test]
627        fn test_scale(x: F, a: u32, b: u32) {
628            let a = u64::from(a);
629            let b = u64::from(b);
630            assert_eq!(x.scale(&[a + b]), x.scale(&[a]) + x.scale(&[b]));
631        }
632
633        #[test]
634        fn test_msm_2(a: [F; 2], b: [F; 2]) {
635            assert_eq!(F::msm(&a, &b, &Sequential), a[0] * b[0] + a[1] * b[1]);
636        }
637    }
638}