Nova/src/provider/mod.rs
src/provider/mod.rsRaw
1//! This module implements Nova's traits using the following configuration:
2//! `CommitmentEngine` with Pedersen's commitments
3//! `Group` with pasta curves and BN256/Grumpkin
4//! `RO` traits with Poseidon
5//! `EvaluationEngine` with an IPA-based polynomial evaluation argument
6
7pub mod bn256_grumpkin;
8pub mod ipa_pc;
9pub mod keccak;
10pub mod pasta;
11pub mod pedersen;
12pub mod poseidon;
13
14use ff::PrimeField;
15use pasta_curves::{self, arithmetic::CurveAffine, group::Group as AnotherGroup};
16
17/// Native implementation of fast multiexp
18/// Adapted from zcash/halo2
19fn cpu_multiexp_serial<C: CurveAffine>(coeffs: &[C::Scalar], bases: &[C], acc: &mut C::Curve) {
20 let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect();
21
22 let c = if bases.len() < 4 {
23 1
24 } else if bases.len() < 32 {
25 3
26 } else {
27 (f64::from(bases.len() as u32)).ln().ceil() as usize
28 };
29
30 fn get_at<F: PrimeField>(segment: usize, c: usize, bytes: &F::Repr) -> usize {
31 let skip_bits = segment * c;
32 let skip_bytes = skip_bits / 8;
33
34 if skip_bytes >= 32 {
35 return 0;
36 }
37
38 let mut v = [0; 8];
39 for (v, o) in v.iter_mut().zip(bytes.as_ref()[skip_bytes..].iter()) {
40 *v = *o;
41 }
42
43 let mut tmp = u64::from_le_bytes(v);
44 tmp >>= skip_bits - (skip_bytes * 8);
45 tmp %= 1 << c;
46
47 tmp as usize
48 }
49
50 let segments = (256 / c) + 1;
51
52 for current_segment in (0..segments).rev() {
53 for _ in 0..c {
54 *acc = acc.double();
55 }
56
57 #[derive(Clone, Copy)]
58 enum Bucket<C: CurveAffine> {
59 None,
60 Affine(C),
61 Projective(C::Curve),
62 }
63
64 impl<C: CurveAffine> Bucket<C> {
65 fn add_assign(&mut self, other: &C) {
66 *self = match *self {
67 Bucket::None => Bucket::Affine(*other),
68 Bucket::Affine(a) => Bucket::Projective(a + *other),
69 Bucket::Projective(mut a) => {
70 a += *other;
71 Bucket::Projective(a)
72 }
73 }
74 }
75
76 fn add(self, mut other: C::Curve) -> C::Curve {
77 match self {
78 Bucket::None => other,
79 Bucket::Affine(a) => {
80 other += a;
81 other
82 }
83 Bucket::Projective(a) => other + a,
84 }
85 }
86 }
87
88 let mut buckets: Vec<Bucket<C>> = vec![Bucket::None; (1 << c) - 1];
89
90 for (coeff, base) in coeffs.iter().zip(bases.iter()) {
91 let coeff = get_at::<C::Scalar>(current_segment, c, coeff);
92 if coeff != 0 {
93 buckets[coeff - 1].add_assign(base);
94 }
95 }
96
97 // Summation by parts
98 // e.g. 3a + 2b + 1c = a +
99 // (a) + b +
100 // ((a) + b) + c
101 let mut running_sum = C::Curve::identity();
102 for exp in buckets.into_iter().rev() {
103 running_sum = exp.add(running_sum);
104 *acc += &running_sum;
105 }
106 }
107}
108
109/// Performs a multi-exponentiation operation without GPU acceleration.
110///
111/// This function will panic if coeffs and bases have a different length.
112///
113/// This will use multithreading if beneficial.
114/// Adapted from zcash/halo2
115pub(crate) fn cpu_best_multiexp<C: CurveAffine>(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve {
116 assert_eq!(coeffs.len(), bases.len());
117
118 let num_threads = rayon::current_num_threads();
119 if coeffs.len() > num_threads {
120 let chunk = coeffs.len() / num_threads;
121 let num_chunks = coeffs.chunks(chunk).len();
122 let mut results = vec![C::Curve::identity(); num_chunks];
123 rayon::scope(|scope| {
124 for ((coeffs, bases), acc) in coeffs
125 .chunks(chunk)
126 .zip(bases.chunks(chunk))
127 .zip(results.iter_mut())
128 {
129 scope.spawn(move |_| {
130 cpu_multiexp_serial(coeffs, bases, acc);
131 });
132 }
133 });
134 results.iter().fold(C::Curve::identity(), |a, b| a + b)
135 } else {
136 let mut acc = C::Curve::identity();
137 cpu_multiexp_serial(coeffs, bases, &mut acc);
138 acc
139 }
140}