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