multiexp/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(not(feature = "std"), no_std)]
4
5#[cfg(not(feature = "std"))]
6#[macro_use]
7extern crate alloc;
8use std_shims::vec::Vec;
9
10use zeroize::Zeroize;
11
12use ff::PrimeFieldBits;
13use group::Group;
14
15mod straus;
16use straus::*;
17
18mod pippenger;
19use pippenger::*;
20
21#[cfg(feature = "batch")]
22mod batch;
23#[cfg(feature = "batch")]
24pub use batch::BatchVerifier;
25
26#[cfg(test)]
27mod tests;
28
29// Use black_box when possible
30#[rustversion::since(1.66)]
31use core::hint::black_box;
32#[rustversion::before(1.66)]
33fn black_box<T>(val: T) -> T {
34  val
35}
36
37fn u8_from_bool(bit_ref: &mut bool) -> u8 {
38  let bit_ref = black_box(bit_ref);
39
40  let mut bit = black_box(*bit_ref);
41  let res = black_box(bit as u8);
42  bit.zeroize();
43  debug_assert!((res | 1) == 1);
44
45  bit_ref.zeroize();
46  res
47}
48
49// Convert scalars to `window`-sized bit groups, as needed to index a table
50// This algorithm works for `window <= 8`
51pub(crate) fn prep_bits<G: Group>(pairs: &[(G::Scalar, G)], window: u8) -> Vec<Vec<u8>>
52where
53  G::Scalar: PrimeFieldBits,
54{
55  let w_usize = usize::from(window);
56
57  let mut groupings = vec![];
58  for pair in pairs {
59    let p = groupings.len();
60    let mut bits = pair.0.to_le_bits();
61    groupings.push(vec![0; (bits.len() + (w_usize - 1)) / w_usize]);
62
63    for (i, mut bit) in bits.iter_mut().enumerate() {
64      let mut bit = u8_from_bool(&mut bit);
65      groupings[p][i / w_usize] |= bit << (i % w_usize);
66      bit.zeroize();
67    }
68  }
69
70  groupings
71}
72
73#[derive(Clone, Copy, PartialEq, Eq, Debug)]
74enum Algorithm {
75  Null,
76  Single,
77  Straus(u8),
78  Pippenger(u8),
79}
80
81/*
82Release (with runs 20, so all of these are off by 20x):
83
84k256
85Straus 3 is more efficient at 5 with 678µs per
86Straus 4 is more efficient at 10 with 530µs per
87Straus 5 is more efficient at 35 with 467µs per
88
89Pippenger 5 is more efficient at 125 with 431µs per
90Pippenger 6 is more efficient at 275 with 349µs per
91Pippenger 7 is more efficient at 375 with 360µs per
92
93dalek
94Straus 3 is more efficient at 5 with 519µs per
95Straus 4 is more efficient at 10 with 376µs per
96Straus 5 is more efficient at 170 with 330µs per
97
98Pippenger 5 is more efficient at 125 with 305µs per
99Pippenger 6 is more efficient at 275 with 250µs per
100Pippenger 7 is more efficient at 450 with 205µs per
101Pippenger 8 is more efficient at 800 with 213µs per
102
103Debug (with runs 5, so...):
104
105k256
106Straus 3 is more efficient at 5 with 2532µs per
107Straus 4 is more efficient at 10 with 1930µs per
108Straus 5 is more efficient at 80 with 1632µs per
109
110Pippenger 5 is more efficient at 150 with 1441µs per
111Pippenger 6 is more efficient at 300 with 1235µs per
112Pippenger 7 is more efficient at 475 with 1182µs per
113Pippenger 8 is more efficient at 625 with 1170µs per
114
115dalek:
116Straus 3 is more efficient at 5 with 971µs per
117Straus 4 is more efficient at 10 with 782µs per
118Straus 5 is more efficient at 75 with 778µs per
119Straus 6 is more efficient at 165 with 867µs per
120
121Pippenger 5 is more efficient at 125 with 677µs per
122Pippenger 6 is more efficient at 250 with 655µs per
123Pippenger 7 is more efficient at 475 with 500µs per
124Pippenger 8 is more efficient at 875 with 499µs per
125*/
126fn algorithm(len: usize) -> Algorithm {
127  #[cfg(not(debug_assertions))]
128  if len == 0 {
129    Algorithm::Null
130  } else if len == 1 {
131    Algorithm::Single
132  } else if len < 10 {
133    // Straus 2 never showed a performance benefit, even with just 2 elements
134    Algorithm::Straus(3)
135  } else if len < 20 {
136    Algorithm::Straus(4)
137  } else if len < 50 {
138    Algorithm::Straus(5)
139  } else if len < 100 {
140    Algorithm::Pippenger(4)
141  } else if len < 125 {
142    Algorithm::Pippenger(5)
143  } else if len < 275 {
144    Algorithm::Pippenger(6)
145  } else if len < 400 {
146    Algorithm::Pippenger(7)
147  } else {
148    Algorithm::Pippenger(8)
149  }
150
151  #[cfg(debug_assertions)]
152  if len == 0 {
153    Algorithm::Null
154  } else if len == 1 {
155    Algorithm::Single
156  } else if len < 10 {
157    Algorithm::Straus(3)
158  } else if len < 80 {
159    Algorithm::Straus(4)
160  } else if len < 100 {
161    Algorithm::Straus(5)
162  } else if len < 125 {
163    Algorithm::Pippenger(4)
164  } else if len < 275 {
165    Algorithm::Pippenger(5)
166  } else if len < 475 {
167    Algorithm::Pippenger(6)
168  } else if len < 750 {
169    Algorithm::Pippenger(7)
170  } else {
171    Algorithm::Pippenger(8)
172  }
173}
174
175/// Performs a multiexponentation, automatically selecting the optimal algorithm based on the
176/// amount of pairs.
177pub fn multiexp<G: Group>(pairs: &[(G::Scalar, G)]) -> G
178where
179  G::Scalar: PrimeFieldBits + Zeroize,
180{
181  match algorithm(pairs.len()) {
182    Algorithm::Null => Group::identity(),
183    Algorithm::Single => pairs[0].1 * pairs[0].0,
184    // These functions panic if called without any pairs
185    Algorithm::Straus(window) => straus(pairs, window),
186    Algorithm::Pippenger(window) => pippenger(pairs, window),
187  }
188}
189
190/// Performs a multiexponentation in variable time, automatically selecting the optimal algorithm
191/// based on the amount of pairs.
192pub fn multiexp_vartime<G: Group>(pairs: &[(G::Scalar, G)]) -> G
193where
194  G::Scalar: PrimeFieldBits,
195{
196  match algorithm(pairs.len()) {
197    Algorithm::Null => Group::identity(),
198    Algorithm::Single => pairs[0].1 * pairs[0].0,
199    Algorithm::Straus(window) => straus_vartime(pairs, window),
200    Algorithm::Pippenger(window) => pippenger_vartime(pairs, window),
201  }
202}