Skip to content

Commit 2c5c803

Browse files
mmagicianmmaker
andauthored
Refactor MSM: Use VariableBaseMSM trait (#425)
Co-authored-by: Michele Orrù <278405+mmaker@users.noreply.github.com> Co-authored-by: Michele Orrù <maker@python.it> Co-authored-by: Michele Orrù <michele.orru@berkeley.edu>
1 parent 6657240 commit 2c5c803

5 files changed

Lines changed: 138 additions & 70 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
- [\#393](https://github.com/arkworks-rs/algebra/pull/393) (`ark-ec`, `ark-ff`) Rename `FpXParams` to `FpXConfig` and `FpXParamsWrapper` to `FpXConfigWrapper`.
3333
- [\#396](https://github.com/arkworks-rs/algebra/pull/396) (`ark-ec`) Remove `mul_bits` feature, and remove default implementations of `mul` and `mul_by_cofactor_to_projective`.
3434
- [\#408](https://github.com/arkworks-rs/algebra/pull/408) (`ark-ff`) Change the output of `Display` formatting for BigInt & Fp from hex to decimal.
35-
- [\#412](https://github.com/arkworks-rs/algebra/pull/412) (`ark-poly`) rename UV/MVPolynomial to DenseUV/MVPolynomial
35+
- [\#412](https://github.com/arkworks-rs/algebra/pull/412) (`ark-poly`) Rename UV/MVPolynomial to DenseUV/MVPolynomial.
36+
- [\#425](https://github.com/arkworks-rs/algebra/pull/425) (`ark-ec`) Refactor `VariableBase` struct to `VariableBaseMSM` trait and implement it for `GroupProjective`.
3637

3738
### Features
3839

ec/src/models/short_weierstrass_jacobian.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ use ark_ff::{
1616
ToConstraintField, UniformRand,
1717
};
1818

19-
use crate::{models::SWModelParameters as Parameters, AffineCurve, ProjectiveCurve};
19+
use crate::{
20+
models::SWModelParameters as Parameters, msm::VariableBaseMSM, AffineCurve, ProjectiveCurve,
21+
};
2022

2123
use num_traits::{One, Zero};
2224
use zeroize::Zeroize;
@@ -919,3 +921,19 @@ where
919921
GroupAffine::from(*self).to_field_elements()
920922
}
921923
}
924+
925+
impl<P: Parameters> VariableBaseMSM for GroupProjective<P> {
926+
type MSMBase = GroupAffine<P>;
927+
928+
type Scalar = <Self as ProjectiveCurve>::ScalarField;
929+
930+
#[inline]
931+
fn _double_in_place(&mut self) -> &mut Self {
932+
self.double_in_place()
933+
}
934+
935+
#[inline]
936+
fn _add_assign_mixed(&mut self, other: &Self::MSMBase) {
937+
self.add_assign_mixed(other)
938+
}
939+
}

ec/src/msm/variable_base/mod.rs

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,76 @@
11
use ark_ff::{prelude::*, PrimeField};
2-
use ark_std::{borrow::Borrow, iterable::Iterable, ops::AddAssign, vec::Vec};
3-
4-
use crate::{AffineCurve, ProjectiveCurve};
2+
use ark_std::{
3+
borrow::Borrow,
4+
iterable::Iterable,
5+
ops::{Add, AddAssign},
6+
vec::Vec,
7+
};
58

69
#[cfg(feature = "parallel")]
710
use rayon::prelude::*;
811

912
pub mod stream_pippenger;
1013
pub use stream_pippenger::*;
1114

12-
pub struct VariableBase;
15+
pub trait VariableBaseMSM:
16+
Eq
17+
+ Sized
18+
+ Sync
19+
+ Zero
20+
+ Clone
21+
+ Copy
22+
+ Send
23+
+ AddAssign<Self>
24+
+ for<'a> AddAssign<&'a Self>
25+
+ for<'a> Add<&'a Self, Output = Self>
26+
{
27+
type MSMBase: Sync + Copy;
28+
29+
type Scalar: PrimeField;
30+
31+
#[doc(hidden)]
32+
fn _double_in_place(&mut self) -> &mut Self;
33+
34+
#[doc(hidden)]
35+
fn _add_assign_mixed(&mut self, other: &Self::MSMBase);
1336

14-
impl VariableBase {
1537
/// Optimized implementation of multi-scalar multiplication.
1638
///
17-
/// Will return `None` if `bases` and `scalar` have different lengths.
39+
/// Multiply the [`PrimeField`] elements in `scalars` with the
40+
/// respective group elements in `bases` and sum the resulting set.
41+
///
42+
/// <section class="warning">
1843
///
19-
/// Reference: [`VariableBase::msm`]
20-
pub fn msm_checked_len<G: AffineCurve>(
21-
bases: &[G],
22-
scalars: &[<G::ScalarField as PrimeField>::BigInt],
23-
) -> Option<G::Projective> {
24-
(bases.len() == scalars.len()).then(|| Self::msm(bases, scalars))
44+
/// If the elements have different length, it will chop the slices to the
45+
/// shortest length between `scalars.len()` and `bases.len()`.
46+
///
47+
/// </section>
48+
fn msm(bases: &[Self::MSMBase], scalars: &[Self::Scalar]) -> Self {
49+
let bigints = cfg_into_iter!(scalars)
50+
.map(|s| s.into_bigint())
51+
.collect::<Vec<_>>();
52+
Self::msm_bigint(bases, &bigints)
2553
}
2654

27-
/// Optimized implementation of multi-scalar multiplication.
55+
/// Optimized implementation of multi-scalar multiplication, that checks bounds.
2856
///
29-
/// Will multiply the tuples of the diagonal product of `bases × scalars`
30-
/// and sum the resulting set. Will iterate only for the elements of the
31-
/// smallest of the two sets, ignoring the remaining elements of the biggest
32-
/// set.
57+
/// Performs `Self::msm`, checking that `bases` and `scalars` have the same length.
58+
/// If the length are not equal, returns an error containing the shortest legth over which msm can be performed.
3359
///
34-
/// ∑i (Bi · Si)
35-
pub fn msm<G: AffineCurve>(
36-
bases: &[G],
37-
scalars: &[<G::ScalarField as PrimeField>::BigInt],
38-
) -> G::Projective {
39-
let size = ark_std::cmp::min(bases.len(), scalars.len());
40-
let scalars = &scalars[..size];
60+
/// Reference: [`VariableBaseMSM::msm`]
61+
fn msm_checked(bases: &[Self::MSMBase], scalars: &[Self::Scalar]) -> Result<Self, usize> {
62+
(bases.len() == scalars.len())
63+
.then(|| Self::msm(bases, scalars))
64+
.ok_or(usize::min(bases.len(), scalars.len()))
65+
}
66+
67+
/// Optimized implementation of multi-scalar multiplication.
68+
fn msm_bigint(
69+
bases: &[Self::MSMBase],
70+
bigints: &[<Self::Scalar as PrimeField>::BigInt],
71+
) -> Self {
72+
let size = ark_std::cmp::min(bases.len(), bigints.len());
73+
let scalars = &bigints[..size];
4174
let bases = &bases[..size];
4275
let scalars_and_bases_iter = scalars.iter().zip(bases).filter(|(s, _)| !s.is_zero());
4376

@@ -47,10 +80,10 @@ impl VariableBase {
4780
super::ln_without_floats(size) + 2
4881
};
4982

50-
let num_bits = G::ScalarField::MODULUS_BIT_SIZE as usize;
51-
let fr_one = G::ScalarField::one().into_bigint();
83+
let num_bits = Self::Scalar::MODULUS_BIT_SIZE as usize;
84+
let fr_one = Self::Scalar::one().into_bigint();
5285

53-
let zero = G::Projective::zero();
86+
let zero = Self::zero();
5487
let window_starts: Vec<_> = (0..num_bits).step_by(c).collect();
5588

5689
// Each window is of size `c`.
@@ -67,7 +100,7 @@ impl VariableBase {
67100
if scalar == fr_one {
68101
// We only process unit scalars once in the first window.
69102
if w_start == 0 {
70-
res.add_assign_mixed(base);
103+
res._add_assign_mixed(base);
71104
}
72105
} else {
73106
let mut scalar = scalar;
@@ -83,7 +116,7 @@ impl VariableBase {
83116
// bucket.
84117
// (Recall that `buckets` doesn't have a zero bucket.)
85118
if scalar != 0 {
86-
buckets[(scalar - 1) as usize].add_assign_mixed(base);
119+
buckets[(scalar - 1) as usize]._add_assign_mixed(base);
87120
}
88121
}
89122
});
@@ -102,7 +135,7 @@ impl VariableBase {
102135

103136
// `running_sum` = sum_{j in i..num_buckets} bucket[j],
104137
// where we iterate backward from i = num_buckets to 0.
105-
let mut running_sum = G::Projective::zero();
138+
let mut running_sum = Self::zero();
106139
buckets.into_iter().rev().for_each(|b| {
107140
running_sum += &b;
108141
res += &running_sum;
@@ -122,21 +155,19 @@ impl VariableBase {
122155
.fold(zero, |mut total, sum_i| {
123156
total += sum_i;
124157
for _ in 0..c {
125-
total.double_in_place();
158+
total._double_in_place();
126159
}
127160
total
128161
})
129162
}
130163
/// Streaming multi-scalar multiplication algorithm with hard-coded chunk
131164
/// size.
132-
pub fn msm_chunks<G, F, I: ?Sized, J>(bases_stream: &J, scalars_stream: &I) -> G::Projective
165+
fn msm_chunks<I: ?Sized, J>(bases_stream: &J, scalars_stream: &I) -> Self
133166
where
134-
G: AffineCurve<ScalarField = F>,
135167
I: Iterable,
136-
F: PrimeField,
137-
I::Item: Borrow<F>,
168+
I::Item: Borrow<Self::Scalar>,
138169
J: Iterable,
139-
J::Item: Borrow<G>,
170+
J::Item: Borrow<Self::MSMBase>,
140171
{
141172
assert!(scalars_stream.len() <= bases_stream.len());
142173

@@ -149,7 +180,7 @@ impl VariableBase {
149180
// See <https://github.com/rust-lang/rust/issues/77404>
150181
let mut bases = bases_init.skip(bases_stream.len() - scalars_stream.len());
151182
let step: usize = 1 << 20;
152-
let mut result = G::Projective::zero();
183+
let mut result = Self::zero();
153184
for _ in 0..(scalars_stream.len() + step - 1) / step {
154185
let bases_step = (&mut bases)
155186
.take(step)
@@ -159,7 +190,7 @@ impl VariableBase {
159190
.take(step)
160191
.map(|s| s.borrow().into_bigint())
161192
.collect::<Vec<_>>();
162-
result.add_assign(crate::msm::VariableBase::msm(
193+
result.add_assign(Self::msm_bigint(
163194
bases_step.as_slice(),
164195
scalars_step.as_slice(),
165196
));

ec/src/msm/variable_base/stream_pippenger.rs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use ark_ff::{PrimeField, Zero};
55
use ark_std::{borrow::Borrow, ops::AddAssign, vec::Vec};
66
use hashbrown::HashMap;
77

8+
use super::VariableBaseMSM;
9+
810
/// Struct for the chunked Pippenger algorithm.
911
pub struct ChunkedPippenger<G: AffineCurve> {
1012
scalars_buffer: Vec<<G::ScalarField as PrimeField>::BigInt>,
@@ -13,7 +15,11 @@ pub struct ChunkedPippenger<G: AffineCurve> {
1315
buf_size: usize,
1416
}
1517

16-
impl<G: AffineCurve> ChunkedPippenger<G> {
18+
impl<G> ChunkedPippenger<G>
19+
where
20+
G: AffineCurve,
21+
G::Projective: VariableBaseMSM<MSMBase = G, Scalar = G::ScalarField>,
22+
{
1723
/// Initialize a chunked Pippenger instance with default parameters.
1824
pub fn new(max_msm_buffer: usize) -> Self {
1925
Self {
@@ -44,10 +50,11 @@ impl<G: AffineCurve> ChunkedPippenger<G> {
4450
self.scalars_buffer.push(*scalar.borrow());
4551
self.bases_buffer.push(*base.borrow());
4652
if self.scalars_buffer.len() == self.buf_size {
47-
self.result.add_assign(crate::msm::VariableBase::msm(
48-
self.bases_buffer.as_slice(),
49-
self.scalars_buffer.as_slice(),
50-
));
53+
self.result
54+
.add_assign(<G::Projective as VariableBaseMSM>::msm_bigint(
55+
self.bases_buffer.as_slice(),
56+
self.scalars_buffer.as_slice(),
57+
));
5158
self.scalars_buffer.clear();
5259
self.bases_buffer.clear();
5360
}
@@ -57,10 +64,11 @@ impl<G: AffineCurve> ChunkedPippenger<G> {
5764
#[inline(always)]
5865
pub fn finalize(mut self) -> G::Projective {
5966
if !self.scalars_buffer.is_empty() {
60-
self.result.add_assign(crate::msm::VariableBase::msm(
61-
self.bases_buffer.as_slice(),
62-
self.scalars_buffer.as_slice(),
63-
));
67+
self.result
68+
.add_assign(<G::Projective as VariableBaseMSM>::msm_bigint(
69+
self.bases_buffer.as_slice(),
70+
self.scalars_buffer.as_slice(),
71+
));
6472
}
6573
self.result
6674
}
@@ -73,7 +81,11 @@ pub struct HashMapPippenger<G: AffineCurve> {
7381
buf_size: usize,
7482
}
7583

76-
impl<G: AffineCurve> HashMapPippenger<G> {
84+
impl<G> HashMapPippenger<G>
85+
where
86+
G: AffineCurve,
87+
G::Projective: VariableBaseMSM<MSMBase = G, Scalar = G::ScalarField>,
88+
{
7789
/// Produce a new hash map with the maximum msm buffer size.
7890
pub fn new(max_msm_buffer: usize) -> Self {
7991
Self {
@@ -104,9 +116,7 @@ impl<G: AffineCurve> HashMapPippenger<G> {
104116
.map(|s| s.into_bigint())
105117
.collect::<Vec<_>>();
106118
self.result
107-
.add_assign(crate::msm::variable_base::VariableBase::msm(
108-
&bases, &scalars,
109-
));
119+
.add_assign(G::Projective::msm_bigint(&bases, &scalars));
110120
self.buffer.clear();
111121
}
112122
}
@@ -123,9 +133,7 @@ impl<G: AffineCurve> HashMapPippenger<G> {
123133
.collect::<Vec<_>>();
124134

125135
self.result
126-
.add_assign(crate::msm::variable_base::VariableBase::msm(
127-
&bases, &scalars,
128-
));
136+
.add_assign(G::Projective::msm_bigint(&bases, &scalars));
129137
}
130138
self.result
131139
}

0 commit comments

Comments
 (0)