lambdaworks_math/msm/
pippenger.rs1use crate::{cyclic_group::IsGroup, unsigned_integer::element::UnsignedInteger};
2
3use super::naive::MSMError;
4
5use alloc::vec;
6
7pub fn msm<const NUM_LIMBS: usize, G>(
19 cs: &[UnsignedInteger<NUM_LIMBS>],
20 points: &[G],
21) -> Result<G, MSMError>
22where
23 G: IsGroup,
24{
25 if cs.len() != points.len() {
26 return Err(MSMError::LengthMismatch(cs.len(), points.len()));
27 }
28
29 let window_size = optimum_window_size(cs.len());
30
31 Ok(msm_with(cs, points, window_size))
32}
33
34fn optimum_window_size(data_length: usize) -> usize {
35 const SCALE_FACTORS: (usize, usize) = (4, 5);
36
37 let len_isqrt = data_length.checked_ilog2().unwrap_or(0);
39 (len_isqrt as usize * SCALE_FACTORS.0) / SCALE_FACTORS.1
40}
41
42pub fn msm_with<const NUM_LIMBS: usize, G>(
43 cs: &[UnsignedInteger<NUM_LIMBS>],
44 points: &[G],
45 window_size: usize,
46) -> G
47where
48 G: IsGroup,
49{
50 const MIN_WINDOW_SIZE: usize = 2;
52 const MAX_WINDOW_SIZE: usize = 32;
53
54 let window_size = window_size.clamp(MIN_WINDOW_SIZE, MAX_WINDOW_SIZE);
55
56 let num_windows = (64 * NUM_LIMBS - 1) / window_size + 1;
58
59 let n_buckets = (1 << window_size) - 1;
67 let mut buckets = vec![G::neutral_element(); n_buckets];
68
69 (0..num_windows)
70 .rev()
71 .map(|window_idx| {
72 cs.iter().zip(points).for_each(|(k, p)| {
74 let window_unmasked = (k >> (window_idx * window_size)).limbs[NUM_LIMBS - 1];
77 let m_ij = window_unmasked & n_buckets as u64;
78 if m_ij != 0 {
79 let idx = (m_ij - 1) as usize;
80 buckets[idx] = buckets[idx].operate_with(p);
81 }
82 });
83
84 buckets
86 .iter_mut()
87 .rev()
90 .scan(G::neutral_element(), |m, b| {
91 *m = m.operate_with(b); *b = G::neutral_element(); Some(m.clone())
94 })
95 .reduce(|g, m| g.operate_with(&m))
98 .unwrap_or_else(G::neutral_element)
99 })
100 .reduce(|t, g| t.operate_with_self(1_u64 << window_size).operate_with(&g))
102 .unwrap_or_else(G::neutral_element)
103}
104
105#[cfg(feature = "parallel")]
106pub fn parallel_msm_with<const NUM_LIMBS: usize, G>(
110 cs: &[UnsignedInteger<NUM_LIMBS>],
111 points: &[G],
112 window_size: usize,
113) -> G
114where
115 G: IsGroup + Send + Sync,
116{
117 use rayon::prelude::*;
118
119 assert!(window_size < usize::BITS as usize); let num_windows = (64 * NUM_LIMBS - 1) / window_size + 1;
123 let n_buckets = (1 << window_size) - 1;
124
125 (0..num_windows)
127 .into_par_iter()
128 .map(|window_idx| {
129 let mut buckets = vec![G::neutral_element(); n_buckets];
130 let shift = window_idx * window_size;
132 cs.iter().zip(points).for_each(|(k, p)| {
133 let window_unmasked = (k >> shift).limbs[NUM_LIMBS - 1];
136 let m_ij = window_unmasked & n_buckets as u64;
137 if m_ij != 0 {
138 let idx = (m_ij - 1) as usize;
139 buckets[idx] = buckets[idx].operate_with(p);
140 }
141 });
142
143 let mut m = G::neutral_element();
144
145 let window_item = buckets
147 .into_iter()
150 .rev()
151 .map(|b| {
152 m = m.operate_with(&b); m.clone()
154 })
155 .reduce(|g, m| g.operate_with(&m))
156 .unwrap_or_else(G::neutral_element);
157
158 window_item.operate_with_self(UnsignedInteger::<NUM_LIMBS>::from_u64(1) << shift)
159 })
160 .reduce(G::neutral_element, |a, b| a.operate_with(&b))
161}
162
163#[cfg(test)]
164mod tests {
165 use crate::cyclic_group::IsGroup;
166 use crate::msm::{naive, pippenger};
167 use crate::{
168 elliptic_curve::{
169 short_weierstrass::curves::bls12_381::curve::BLS12381Curve, traits::IsEllipticCurve,
170 },
171 unsigned_integer::element::UnsignedInteger,
172 };
173 use alloc::vec::Vec;
174 use proptest::{collection, prelude::*, prop_assert_eq, prop_compose, proptest};
175
176 const _CASES: u32 = 20;
177 const _MAX_WSIZE: usize = 8;
178 const _MAX_LEN: usize = 30;
179
180 prop_compose! {
181 fn unsigned_integer()(limbs: [u64; 6]) -> UnsignedInteger<6> {
182 UnsignedInteger::from_limbs(limbs)
183 }
184 }
185
186 prop_compose! {
187 fn unsigned_integer_vec()(vec in collection::vec(unsigned_integer(), 0.._MAX_LEN)) -> Vec<UnsignedInteger<6>> {
188 vec
189 }
190 }
191
192 prop_compose! {
193 fn point()(power: u128) -> <BLS12381Curve as IsEllipticCurve>::PointRepresentation {
194 BLS12381Curve::generator().operate_with_self(power)
195 }
196 }
197
198 prop_compose! {
199 fn points_vec()(vec in collection::vec(point(), 0.._MAX_LEN)) -> Vec<<BLS12381Curve as IsEllipticCurve>::PointRepresentation> {
200 vec
201 }
202 }
203
204 proptest! {
205 #![proptest_config(ProptestConfig {
206 cases: _CASES, .. ProptestConfig::default()
207 })]
208 #[test]
210 fn test_pippenger_matches_naive_msm(window_size in 1.._MAX_WSIZE, cs in unsigned_integer_vec(), points in points_vec()) {
211 let min_len = cs.len().min(points.len());
212 let cs = cs[..min_len].to_vec();
213 let points = points[..min_len].to_vec();
214
215 let pippenger = pippenger::msm_with(&cs, &points, window_size);
216 let naive = naive::msm(&cs, &points).unwrap();
217
218 prop_assert_eq!(naive, pippenger);
219 }
220
221 #[test]
223 #[cfg(feature = "parallel")]
224 fn test_parallel_pippenger_matches_sequential(window_size in 1.._MAX_WSIZE, cs in unsigned_integer_vec(), points in points_vec()) {
225 let min_len = cs.len().min(points.len());
226 let cs = cs[..min_len].to_vec();
227 let points = points[..min_len].to_vec();
228
229 let sequential = pippenger::msm_with(&cs, &points, window_size);
230 let parallel = pippenger::parallel_msm_with(&cs, &points, window_size);
231
232 prop_assert_eq!(parallel, sequential);
233 }
234 }
235}