Tweak multiexp to Zeroize points when invoked in constant time, not just scalars

This commit is contained in:
Luke Parker
2025-08-19 22:28:59 -04:00
parent 8a1b56a928
commit 17c1d5cd6b
8 changed files with 18 additions and 12 deletions

2
Cargo.lock generated
View File

@@ -5119,7 +5119,7 @@ dependencies = [
[[package]] [[package]]
name = "multiexp" name = "multiexp"
version = "0.4.0" version = "0.4.1"
dependencies = [ dependencies = [
"dalek-ff-group", "dalek-ff-group",
"ff", "ff",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "multiexp" name = "multiexp"
version = "0.4.0" version = "0.4.1"
description = "Multiexponentiation algorithms for ff/group" description = "Multiexponentiation algorithms for ff/group"
license = "MIT" license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/multiexp" repository = "https://github.com/serai-dex/serai/tree/develop/crypto/multiexp"

View File

@@ -12,7 +12,7 @@ use crate::{multiexp, multiexp_vartime};
// Flatten the contained statements to a single Vec. // Flatten the contained statements to a single Vec.
// Wrapped in Zeroizing in case any of the included statements contain private values. // Wrapped in Zeroizing in case any of the included statements contain private values.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn flat<Id: Copy + Zeroize, G: Group<Scalar: PrimeFieldBits + Zeroize> + Zeroize>( fn flat<Id: Copy + Zeroize, G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
slice: &[(Id, Vec<(G::Scalar, G)>)], slice: &[(Id, Vec<(G::Scalar, G)>)],
) -> Zeroizing<Vec<(G::Scalar, G)>> { ) -> Zeroizing<Vec<(G::Scalar, G)>> {
Zeroizing::new(slice.iter().flat_map(|pairs| pairs.1.iter()).copied().collect::<Vec<_>>()) Zeroizing::new(slice.iter().flat_map(|pairs| pairs.1.iter()).copied().collect::<Vec<_>>())
@@ -21,11 +21,11 @@ fn flat<Id: Copy + Zeroize, G: Group<Scalar: PrimeFieldBits + Zeroize> + Zeroize
/// A batch verifier intended to verify a series of statements are each equivalent to zero. /// A batch verifier intended to verify a series of statements are each equivalent to zero.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
#[derive(Clone, Zeroize)] #[derive(Clone, Zeroize)]
pub struct BatchVerifier<Id: Copy + Zeroize, G: Group<Scalar: PrimeFieldBits + Zeroize> + Zeroize>( pub struct BatchVerifier<Id: Copy + Zeroize, G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
Zeroizing<Vec<(Id, Vec<(G::Scalar, G)>)>>, Zeroizing<Vec<(Id, Vec<(G::Scalar, G)>)>>,
); );
impl<Id: Copy + Zeroize, G: Group<Scalar: PrimeFieldBits + Zeroize> + Zeroize> impl<Id: Copy + Zeroize, G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>
BatchVerifier<Id, G> BatchVerifier<Id, G>
{ {
/// Create a new batch verifier, expected to verify the following amount of statements. /// Create a new batch verifier, expected to verify the following amount of statements.

View File

@@ -5,6 +5,8 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
#[macro_use] #[macro_use]
extern crate alloc; extern crate alloc;
#[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::vec::Vec; use std_shims::vec::Vec;
use zeroize::Zeroize; use zeroize::Zeroize;
@@ -175,7 +177,9 @@ fn algorithm(len: usize) -> Algorithm {
/// Performs a multiexponentiation, automatically selecting the optimal algorithm based on the /// Performs a multiexponentiation, automatically selecting the optimal algorithm based on the
/// amount of pairs. /// amount of pairs.
pub fn multiexp<G: Group<Scalar: PrimeFieldBits + Zeroize>>(pairs: &[(G::Scalar, G)]) -> G { pub fn multiexp<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
pairs: &[(G::Scalar, G)],
) -> G {
match algorithm(pairs.len()) { match algorithm(pairs.len()) {
Algorithm::Null => Group::identity(), Algorithm::Null => Group::identity(),
Algorithm::Single => pairs[0].1 * pairs[0].0, Algorithm::Single => pairs[0].1 * pairs[0].0,

View File

@@ -7,7 +7,7 @@ use crate::prep_bits;
// Pippenger's algorithm for multiexponentiation, as published in the SIAM Journal on Computing // Pippenger's algorithm for multiexponentiation, as published in the SIAM Journal on Computing
// DOI: 10.1137/0209022 // DOI: 10.1137/0209022
pub(crate) fn pippenger<G: Group<Scalar: PrimeFieldBits>>( pub(crate) fn pippenger<G: Zeroize + Group<Scalar: PrimeFieldBits>>(
pairs: &[(G::Scalar, G)], pairs: &[(G::Scalar, G)],
window: u8, window: u8,
) -> G { ) -> G {
@@ -25,6 +25,7 @@ pub(crate) fn pippenger<G: Group<Scalar: PrimeFieldBits>>(
for p in 0 .. bits.len() { for p in 0 .. bits.len() {
buckets[usize::from(bits[p][n])] += pairs[p].1; buckets[usize::from(bits[p][n])] += pairs[p].1;
} }
buckets.zeroize();
let mut intermediate_sum = G::identity(); let mut intermediate_sum = G::identity();
for b in (1 .. buckets.len()).rev() { for b in (1 .. buckets.len()).rev() {

View File

@@ -24,12 +24,12 @@ fn prep_tables<G: Group>(pairs: &[(G::Scalar, G)], window: u8) -> Vec<Vec<G>> {
// Straus's algorithm for multiexponentiation, as published in The American Mathematical Monthly // Straus's algorithm for multiexponentiation, as published in The American Mathematical Monthly
// DOI: 10.2307/2310929 // DOI: 10.2307/2310929
pub(crate) fn straus<G: Group<Scalar: PrimeFieldBits + Zeroize>>( pub(crate) fn straus<G: Zeroize + Group<Scalar: PrimeFieldBits>>(
pairs: &[(G::Scalar, G)], pairs: &[(G::Scalar, G)],
window: u8, window: u8,
) -> G { ) -> G {
let mut groupings = prep_bits(pairs, window); let mut groupings = prep_bits(pairs, window);
let tables = prep_tables(pairs, window); let mut tables = prep_tables(pairs, window);
let mut res = G::identity(); let mut res = G::identity();
for b in (0 .. groupings[0].len()).rev() { for b in (0 .. groupings[0].len()).rev() {
@@ -45,6 +45,7 @@ pub(crate) fn straus<G: Group<Scalar: PrimeFieldBits + Zeroize>>(
} }
groupings.zeroize(); groupings.zeroize();
tables.zeroize();
res res
} }

View File

@@ -9,7 +9,7 @@ use group::Group;
use crate::BatchVerifier; use crate::BatchVerifier;
pub(crate) fn test_batch<G: Group<Scalar: PrimeFieldBits + Zeroize> + Zeroize>() { pub(crate) fn test_batch<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>() {
let valid = |batch: BatchVerifier<_, G>| { let valid = |batch: BatchVerifier<_, G>| {
assert!(batch.verify()); assert!(batch.verify());
assert!(batch.verify_vartime()); assert!(batch.verify_vartime());

View File

@@ -18,7 +18,7 @@ mod batch;
use batch::test_batch; use batch::test_batch;
#[allow(dead_code)] #[allow(dead_code)]
fn benchmark_internal<G: Group<Scalar: PrimeFieldBits + Zeroize>>(straus_bool: bool) { fn benchmark_internal<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(straus_bool: bool) {
let runs: usize = 20; let runs: usize = 20;
let mut start = 0; let mut start = 0;
@@ -83,7 +83,7 @@ fn benchmark_internal<G: Group<Scalar: PrimeFieldBits + Zeroize>>(straus_bool: b
} }
} }
fn test_multiexp<G: Group<Scalar: PrimeFieldBits + Zeroize>>() { fn test_multiexp<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>() {
let test = |pairs: &[_], sum| { let test = |pairs: &[_], sum| {
// These should automatically determine the best algorithm // These should automatically determine the best algorithm
assert_eq!(multiexp(pairs), sum); assert_eq!(multiexp(pairs), sum);