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