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