mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Tweak multiexp to compile on core
On `core`, it'll use a serial implementation of no benefit other than the fact that when `alloc` _is_ enabled, it'll use the multi-scalar multiplication algorithms. `schnorr-signatures` was prior tweaked to include a shim for `SchnorrSignature::verify` which didn't use `multiexp_vartime` yet this same premise. Now, instead of callers writing these shims, it's within `multiexp`.
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -6334,8 +6334,6 @@ dependencies = [
|
|||||||
"group",
|
"group",
|
||||||
"k256",
|
"k256",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"rustversion",
|
|
||||||
"std-shims",
|
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustversion = "1"
|
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||||
|
|
||||||
std-shims = { path = "../../common/std-shims", version = "0.1.1", default-features = false, features = ["alloc"] }
|
|
||||||
|
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive", "alloc"] }
|
|
||||||
|
|
||||||
ff = { version = "0.13", default-features = false, features = ["bits"] }
|
ff = { version = "0.13", default-features = false, features = ["bits"] }
|
||||||
group = { version = "0.13", default-features = false }
|
group = { version = "0.13", default-features = false }
|
||||||
@@ -35,8 +31,9 @@ k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic"
|
|||||||
dalek-ff-group = { path = "../dalek-ff-group" }
|
dalek-ff-group = { path = "../dalek-ff-group" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["std-shims/std", "zeroize/std", "ff/std", "rand_core?/std"]
|
alloc = ["zeroize/alloc"]
|
||||||
|
std = ["alloc", "zeroize/std", "ff/std", "rand_core?/std"]
|
||||||
|
|
||||||
batch = ["rand_core"]
|
batch = ["alloc", "rand_core"]
|
||||||
|
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ culminating in commit
|
|||||||
[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06).
|
[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06).
|
||||||
Any subsequent changes have not undergone auditing.
|
Any subsequent changes have not undergone auditing.
|
||||||
|
|
||||||
This library is usable under no_std, via alloc, when the default features are
|
This library is usable under no-`std` and no-`alloc`. With the `alloc` feature,
|
||||||
disabled.
|
the library is fully functional. Without the `alloc` feature, the `multiexp`
|
||||||
|
function is shimmed with a serial implementation.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std_shims::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
|||||||
@@ -2,41 +2,40 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(feature = "alloc")]
|
||||||
#[macro_use]
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
#[allow(unused_imports)]
|
|
||||||
use std_shims::prelude::*;
|
|
||||||
use std_shims::vec::Vec;
|
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use ff::PrimeFieldBits;
|
use ff::PrimeFieldBits;
|
||||||
use group::Group;
|
use group::Group;
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
mod straus;
|
mod straus;
|
||||||
use straus::*;
|
#[cfg(feature = "alloc")]
|
||||||
|
|
||||||
mod pippenger;
|
mod pippenger;
|
||||||
use pippenger::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "batch")]
|
#[cfg(feature = "batch")]
|
||||||
mod batch;
|
mod batch;
|
||||||
#[cfg(feature = "batch")]
|
|
||||||
pub use batch::BatchVerifier;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(all(test, feature = "alloc"))]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
// Use black_box when possible
|
#[cfg(feature = "alloc")]
|
||||||
#[rustversion::since(1.66)]
|
mod underlying {
|
||||||
use core::hint::black_box;
|
use super::*;
|
||||||
#[rustversion::before(1.66)]
|
|
||||||
fn black_box<T>(val: T) -> T {
|
|
||||||
val
|
|
||||||
}
|
|
||||||
|
|
||||||
fn u8_from_bool(bit_ref: &mut bool) -> u8 {
|
use core::hint::black_box;
|
||||||
|
use alloc::{vec, vec::Vec};
|
||||||
|
|
||||||
|
pub(crate) use straus::*;
|
||||||
|
|
||||||
|
pub(crate) use pippenger::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "batch")]
|
||||||
|
pub use batch::BatchVerifier;
|
||||||
|
|
||||||
|
fn u8_from_bool(bit_ref: &mut bool) -> u8 {
|
||||||
let bit_ref = black_box(bit_ref);
|
let bit_ref = black_box(bit_ref);
|
||||||
|
|
||||||
let mut bit = black_box(*bit_ref);
|
let mut bit = black_box(*bit_ref);
|
||||||
@@ -47,14 +46,14 @@ fn u8_from_bool(bit_ref: &mut bool) -> u8 {
|
|||||||
|
|
||||||
bit_ref.zeroize();
|
bit_ref.zeroize();
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert scalars to `window`-sized bit groups, as needed to index a table
|
// Convert scalars to `window`-sized bit groups, as needed to index a table
|
||||||
// This algorithm works for `window <= 8`
|
// This algorithm works for `window <= 8`
|
||||||
pub(crate) fn prep_bits<G: Group<Scalar: PrimeFieldBits>>(
|
pub(crate) fn prep_bits<G: Group<Scalar: PrimeFieldBits>>(
|
||||||
pairs: &[(G::Scalar, G)],
|
pairs: &[(G::Scalar, G)],
|
||||||
window: u8,
|
window: u8,
|
||||||
) -> Vec<Vec<u8>> {
|
) -> Vec<Vec<u8>> {
|
||||||
let w_usize = usize::from(window);
|
let w_usize = usize::from(window);
|
||||||
|
|
||||||
let mut groupings = vec![];
|
let mut groupings = vec![];
|
||||||
@@ -71,62 +70,18 @@ pub(crate) fn prep_bits<G: Group<Scalar: PrimeFieldBits>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
groupings
|
groupings
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
enum Algorithm {
|
enum Algorithm {
|
||||||
Null,
|
Null,
|
||||||
Single,
|
Single,
|
||||||
Straus(u8),
|
Straus(u8),
|
||||||
Pippenger(u8),
|
Pippenger(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// These are 'rule of thumb's obtained via benchmarking `k256` and `curve25519-dalek`
|
||||||
Release (with runs 20, so all of these are off by 20x):
|
fn algorithm(len: usize) -> Algorithm {
|
||||||
|
|
||||||
k256
|
|
||||||
Straus 3 is more efficient at 5 with 678µs per
|
|
||||||
Straus 4 is more efficient at 10 with 530µs per
|
|
||||||
Straus 5 is more efficient at 35 with 467µs per
|
|
||||||
|
|
||||||
Pippenger 5 is more efficient at 125 with 431µs per
|
|
||||||
Pippenger 6 is more efficient at 275 with 349µs per
|
|
||||||
Pippenger 7 is more efficient at 375 with 360µs per
|
|
||||||
|
|
||||||
dalek
|
|
||||||
Straus 3 is more efficient at 5 with 519µs per
|
|
||||||
Straus 4 is more efficient at 10 with 376µs per
|
|
||||||
Straus 5 is more efficient at 170 with 330µs per
|
|
||||||
|
|
||||||
Pippenger 5 is more efficient at 125 with 305µs per
|
|
||||||
Pippenger 6 is more efficient at 275 with 250µs per
|
|
||||||
Pippenger 7 is more efficient at 450 with 205µs per
|
|
||||||
Pippenger 8 is more efficient at 800 with 213µs per
|
|
||||||
|
|
||||||
Debug (with runs 5, so...):
|
|
||||||
|
|
||||||
k256
|
|
||||||
Straus 3 is more efficient at 5 with 2532µs per
|
|
||||||
Straus 4 is more efficient at 10 with 1930µs per
|
|
||||||
Straus 5 is more efficient at 80 with 1632µs per
|
|
||||||
|
|
||||||
Pippenger 5 is more efficient at 150 with 1441µs per
|
|
||||||
Pippenger 6 is more efficient at 300 with 1235µs per
|
|
||||||
Pippenger 7 is more efficient at 475 with 1182µs per
|
|
||||||
Pippenger 8 is more efficient at 625 with 1170µs per
|
|
||||||
|
|
||||||
dalek:
|
|
||||||
Straus 3 is more efficient at 5 with 971µs per
|
|
||||||
Straus 4 is more efficient at 10 with 782µs per
|
|
||||||
Straus 5 is more efficient at 75 with 778µs per
|
|
||||||
Straus 6 is more efficient at 165 with 867µs per
|
|
||||||
|
|
||||||
Pippenger 5 is more efficient at 125 with 677µs per
|
|
||||||
Pippenger 6 is more efficient at 250 with 655µs per
|
|
||||||
Pippenger 7 is more efficient at 475 with 500µs per
|
|
||||||
Pippenger 8 is more efficient at 875 with 499µs per
|
|
||||||
*/
|
|
||||||
fn algorithm(len: usize) -> Algorithm {
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
Algorithm::Null
|
Algorithm::Null
|
||||||
@@ -173,13 +128,13 @@ fn algorithm(len: usize) -> Algorithm {
|
|||||||
} else {
|
} else {
|
||||||
Algorithm::Pippenger(8)
|
Algorithm::Pippenger(8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
|
pub fn multiexp<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
|
||||||
pairs: &[(G::Scalar, G)],
|
pairs: &[(G::Scalar, G)],
|
||||||
) -> 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,
|
||||||
@@ -187,15 +142,37 @@ pub fn multiexp<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
|
|||||||
Algorithm::Straus(window) => straus(pairs, window),
|
Algorithm::Straus(window) => straus(pairs, window),
|
||||||
Algorithm::Pippenger(window) => pippenger(pairs, window),
|
Algorithm::Pippenger(window) => pippenger(pairs, window),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a multiexponentiation in variable time, automatically selecting the optimal algorithm
|
/// Performs a multiexponentiation in variable time, automatically selecting the optimal algorithm
|
||||||
/// based on the amount of pairs.
|
/// based on the amount of pairs.
|
||||||
pub fn multiexp_vartime<G: Group<Scalar: PrimeFieldBits>>(pairs: &[(G::Scalar, G)]) -> G {
|
pub fn multiexp_vartime<G: Group<Scalar: 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,
|
||||||
Algorithm::Straus(window) => straus_vartime(pairs, window),
|
Algorithm::Straus(window) => straus_vartime(pairs, window),
|
||||||
Algorithm::Pippenger(window) => pippenger_vartime(pairs, window),
|
Algorithm::Pippenger(window) => pippenger_vartime(pairs, window),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "alloc"))]
|
||||||
|
mod underlying {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Performs a multiexponentiation, automatically selecting the optimal algorithm based on the
|
||||||
|
/// amount of pairs.
|
||||||
|
pub fn multiexp<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
|
||||||
|
pairs: &[(G::Scalar, G)],
|
||||||
|
) -> G {
|
||||||
|
pairs.iter().map(|(scalar, point)| *point * scalar).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a multiexponentiation in variable time, automatically selecting the optimal algorithm
|
||||||
|
/// based on the amount of pairs.
|
||||||
|
pub fn multiexp_vartime<G: Group<Scalar: PrimeFieldBits>>(pairs: &[(G::Scalar, G)]) -> G {
|
||||||
|
pairs.iter().map(|(scalar, point)| *point * scalar).sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use underlying::*;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use alloc::vec;
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use ff::PrimeFieldBits;
|
use ff::PrimeFieldBits;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std_shims::vec::Vec;
|
use alloc::{vec, vec::Vec};
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ digest = { version = "0.11.0-rc.1", default-features = false, features = ["block
|
|||||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, optional = true }
|
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, optional = true }
|
||||||
|
|
||||||
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false }
|
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false }
|
||||||
multiexp = { path = "../multiexp", version = "0.4", default-features = false, features = ["batch"], optional = true }
|
multiexp = { path = "../multiexp", version = "0.4", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
@@ -40,7 +40,7 @@ dalek-ff-group = { path = "../dalek-ff-group" }
|
|||||||
ciphersuite = { path = "../ciphersuite" }
|
ciphersuite = { path = "../ciphersuite" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
alloc = ["zeroize/alloc", "digest/alloc", "ciphersuite/alloc", "multiexp"]
|
alloc = ["zeroize/alloc", "digest/alloc", "ciphersuite/alloc", "multiexp/alloc", "multiexp/batch"]
|
||||||
aggregate = ["alloc", "transcript"]
|
aggregate = ["alloc", "transcript"]
|
||||||
std = ["alloc", "std-shims/std", "rand_core/std", "zeroize/std", "transcript?/std", "ciphersuite/std", "multiexp/std"]
|
std = ["alloc", "std-shims/std", "rand_core/std", "zeroize/std", "transcript?/std", "ciphersuite/std", "multiexp/std"]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ use ciphersuite::{
|
|||||||
},
|
},
|
||||||
GroupIo,
|
GroupIo,
|
||||||
};
|
};
|
||||||
|
use multiexp::multiexp_vartime;
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
use multiexp::{multiexp_vartime, BatchVerifier};
|
use multiexp::BatchVerifier;
|
||||||
|
|
||||||
/// Half-aggregation from <https://eprint.iacr.org/2021/350>.
|
/// Half-aggregation from <https://eprint.iacr.org/2021/350>.
|
||||||
#[cfg(feature = "aggregate")]
|
#[cfg(feature = "aggregate")]
|
||||||
@@ -109,12 +110,7 @@ impl<C: GroupIo> SchnorrSignature<C> {
|
|||||||
/// different keys/messages.
|
/// different keys/messages.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn verify(&self, public_key: C::G, challenge: C::F) -> bool {
|
pub fn verify(&self, public_key: C::G, challenge: C::F) -> bool {
|
||||||
let statements = self.batch_statements(public_key, challenge);
|
multiexp_vartime(&self.batch_statements(public_key, challenge)).is_identity().into()
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
let res = multiexp_vartime(&statements);
|
|
||||||
#[cfg(not(feature = "alloc"))]
|
|
||||||
let res = statements.into_iter().map(|(scalar, point)| point * scalar).sum::<C::G>();
|
|
||||||
res.is_identity().into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue a signature for batch verification.
|
/// Queue a signature for batch verification.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ std-shims = { path = "../../common/std-shims", default-features = false }
|
|||||||
|
|
||||||
flexible-transcript = { path = "../../crypto/transcript", default-features = false, features = ["recommended", "merlin"] }
|
flexible-transcript = { path = "../../crypto/transcript", default-features = false, features = ["recommended", "merlin"] }
|
||||||
|
|
||||||
multiexp = { path = "../../crypto/multiexp", default-features = false, features = ["batch"], optional = true }
|
multiexp = { path = "../../crypto/multiexp", default-features = false }
|
||||||
|
|
||||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false }
|
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false }
|
||||||
minimal-ed448 = { path = "../../crypto/ed448", default-features = false }
|
minimal-ed448 = { path = "../../crypto/ed448", default-features = false }
|
||||||
@@ -46,7 +46,8 @@ bitcoin-serai = { path = "../../networks/bitcoin", default-features = false, fea
|
|||||||
alloc = [
|
alloc = [
|
||||||
"std-shims/alloc",
|
"std-shims/alloc",
|
||||||
|
|
||||||
"multiexp",
|
"multiexp/alloc",
|
||||||
|
"multiexp/batch",
|
||||||
|
|
||||||
"dalek-ff-group/alloc",
|
"dalek-ff-group/alloc",
|
||||||
"minimal-ed448/alloc",
|
"minimal-ed448/alloc",
|
||||||
|
|||||||
Reference in New Issue
Block a user