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}