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}