mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-12 09:26:51 +00:00
Add Classic/Compromise DLEqs and a benchmark
Formatted results from my laptop: EfficientLinear had a average prove time of 188ms EfficientLinear had a average verify time of 126ms CompromiseLinear had a average prove time of 176ms CompromiseLinear had a average verify time of 141ms ConciseLinear had a average prove time of 191ms ConciseLinear had a average verify time of 160ms ClassicLinear had a average prove time of 214ms ClassicLinear had a average verify time of 159ms There is a decent error margin here. Concise is a drop-in replacement for Classic, in practice *not* theory. Efficient is optimal for performance, yet largest. Compromise is a middleground.
This commit is contained in:
parent
1a2e6dc5cf
commit
44e0a41ca1
4 changed files with 105 additions and 35 deletions
|
@ -13,30 +13,38 @@ use std::io::{Read, Write};
|
|||
use crate::cross_group::read_point;
|
||||
|
||||
pub(crate) enum BitSignature {
|
||||
ClassicLinear,
|
||||
ConciseLinear,
|
||||
EfficientLinear
|
||||
EfficientLinear,
|
||||
CompromiseLinear
|
||||
}
|
||||
|
||||
impl BitSignature {
|
||||
pub(crate) const fn to_u8(&self) -> u8 {
|
||||
match self {
|
||||
BitSignature::ConciseLinear => 0,
|
||||
BitSignature::EfficientLinear => 1
|
||||
BitSignature::ClassicLinear => 0,
|
||||
BitSignature::ConciseLinear => 1,
|
||||
BitSignature::EfficientLinear => 2,
|
||||
BitSignature::CompromiseLinear => 3
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn from(algorithm: u8) -> BitSignature {
|
||||
match algorithm {
|
||||
0 => BitSignature::ConciseLinear,
|
||||
1 => BitSignature::EfficientLinear,
|
||||
0 => BitSignature::ClassicLinear,
|
||||
1 => BitSignature::ConciseLinear,
|
||||
2 => BitSignature::EfficientLinear,
|
||||
3 => BitSignature::CompromiseLinear,
|
||||
_ => panic!("Unknown algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn bits(&self) -> usize {
|
||||
match self {
|
||||
BitSignature::ClassicLinear => 1,
|
||||
BitSignature::ConciseLinear => 2,
|
||||
BitSignature::EfficientLinear => 1
|
||||
BitSignature::EfficientLinear => 1,
|
||||
BitSignature::CompromiseLinear => 2
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,8 +54,10 @@ impl BitSignature {
|
|||
|
||||
fn aos_form<G0: PrimeGroup, G1: PrimeGroup>(&self) -> Re<G0, G1> {
|
||||
match self {
|
||||
BitSignature::ClassicLinear => Re::e_default(),
|
||||
BitSignature::ConciseLinear => Re::e_default(),
|
||||
BitSignature::EfficientLinear => Re::R_default()
|
||||
BitSignature::EfficientLinear => Re::R_default(),
|
||||
BitSignature::CompromiseLinear => Re::R_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,23 +62,39 @@ pub struct DLEqProof<
|
|||
poks: (SchnorrPoK<G0>, SchnorrPoK<G1>)
|
||||
}
|
||||
|
||||
pub type ConciseLinearDLEq<G0, G1> = DLEqProof<
|
||||
G0,
|
||||
G1,
|
||||
{ BitSignature::ConciseLinear.to_u8() },
|
||||
{ BitSignature::ConciseLinear.ring_len() },
|
||||
// There may not be a remainder, yet if there is, it'll be just one bit
|
||||
// A ring for one bit has a RING_LEN of 2
|
||||
2
|
||||
>;
|
||||
macro_rules! dleq {
|
||||
($name: ident, $signature: expr, $remainder: expr) => {
|
||||
pub type $name<G0, G1> = DLEqProof<
|
||||
G0,
|
||||
G1,
|
||||
{ $signature.to_u8() },
|
||||
{ $signature.ring_len() },
|
||||
// There may not be a remainder, yet if there is one, it'll be just one bit
|
||||
// A ring for one bit has a RING_LEN of 2
|
||||
{ if $remainder { 2 } else { 0 } }
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
||||
pub type EfficientLinearDLEq<G0, G1> = DLEqProof<
|
||||
G0,
|
||||
G1,
|
||||
{ BitSignature::EfficientLinear.to_u8() },
|
||||
{ BitSignature::EfficientLinear.ring_len() },
|
||||
0
|
||||
>;
|
||||
// Proves for 1-bit at a time with the signature form (e, s), as originally described in MRL-0010.
|
||||
// Uses a merged challenge, unlike MRL-0010, for the ring signature, saving an element from each
|
||||
// bit and removing a hash while slightly reducing challenge security. This security reduction is
|
||||
// already applied to the scalar being proven for, a result of the requirement it's mutually valid
|
||||
// over both scalar fields, hence its application here as well. This is mainly here as a point of
|
||||
// reference for the following DLEq proofs, all which use merged challenges
|
||||
dleq!(ClassicLinearDLEq, BitSignature::ClassicLinear, false);
|
||||
|
||||
// Proves for 2-bits at a time to save 3/7 elements of every other bit
|
||||
dleq!(ConciseLinearDLEq, BitSignature::ConciseLinear, true);
|
||||
|
||||
// Uses AOS signatures of the form R, s, to enable the final step of the ring signature to be
|
||||
// batch verified, at the cost of adding an additional element per bit
|
||||
dleq!(EfficientLinearDLEq, BitSignature::EfficientLinear, false);
|
||||
|
||||
// Proves for 2-bits at a time while using the R, s form. This saves 3/7 elements of every other
|
||||
// bit, while adding 1 element to every bit, and is more efficient than ConciseLinear yet less
|
||||
// efficient than EfficientLinear due to having more ring signature steps which aren't batched
|
||||
dleq!(CompromiseLinearDLEq, BitSignature::CompromiseLinear, true);
|
||||
|
||||
impl<
|
||||
G0: PrimeGroup,
|
||||
|
@ -279,8 +295,10 @@ impl<
|
|||
Self::transcript(transcript, generators, keys);
|
||||
|
||||
let batch_capacity = match BitSignature::from(SIGNATURE) {
|
||||
BitSignature::ClassicLinear => 3,
|
||||
BitSignature::ConciseLinear => 3,
|
||||
BitSignature::EfficientLinear => (self.bits.len() + 1) * 3
|
||||
BitSignature::EfficientLinear => (self.bits.len() + 1) * 3,
|
||||
BitSignature::CompromiseLinear => (self.bits.len() + 1) * 3
|
||||
};
|
||||
let mut batch = (BatchVerifier::new(batch_capacity), BatchVerifier::new(batch_capacity));
|
||||
|
||||
|
|
|
@ -13,7 +13,10 @@ use transcript::RecommendedTranscript;
|
|||
|
||||
use crate::{
|
||||
Generators,
|
||||
cross_group::{scalar::mutual_scalar_from_bytes, EfficientLinearDLEq, ConciseLinearDLEq}
|
||||
cross_group::{
|
||||
scalar::mutual_scalar_from_bytes,
|
||||
ClassicLinearDLEq, EfficientLinearDLEq, ConciseLinearDLEq, CompromiseLinearDLEq
|
||||
}
|
||||
};
|
||||
|
||||
mod scalar;
|
||||
|
@ -62,7 +65,39 @@ macro_rules! verify_and_deserialize {
|
|||
}
|
||||
|
||||
macro_rules! test_dleq {
|
||||
($name: ident, $type: ident) => {
|
||||
($str: expr, $benchmark: ident, $name: ident, $type: ident) => {
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn $benchmark() {
|
||||
println!("Benchmarking with Secp256k1/Ed25519");
|
||||
let generators = generators();
|
||||
|
||||
let mut seed = [0; 32];
|
||||
OsRng.fill_bytes(&mut seed);
|
||||
let key = Blake2b512::new().chain_update(seed);
|
||||
|
||||
let runs = 200;
|
||||
let mut proofs = Vec::with_capacity(usize::try_from(runs).unwrap());
|
||||
let time = std::time::Instant::now();
|
||||
for _ in 0 .. runs {
|
||||
proofs.push($type::prove(&mut OsRng, &mut transcript(), generators, key.clone()).0);
|
||||
}
|
||||
println!("{} had a average prove time of {}ms", $str, time.elapsed().as_millis() / runs);
|
||||
|
||||
let time = std::time::Instant::now();
|
||||
for proof in &proofs {
|
||||
proof.verify(&mut OsRng, &mut transcript(), generators).unwrap();
|
||||
}
|
||||
println!("{} had a average verify time of {}ms", $str, time.elapsed().as_millis() / runs);
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
{
|
||||
let mut buf = vec![];
|
||||
proofs[0].serialize(&mut buf);
|
||||
println!("{} had a proof size of {} bytes", $str, buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn $name() {
|
||||
let generators = generators();
|
||||
|
@ -83,12 +118,7 @@ macro_rules! test_dleq {
|
|||
let mut res;
|
||||
while {
|
||||
key = Scalar::random(&mut OsRng);
|
||||
res = $type::prove_without_bias(
|
||||
&mut OsRng,
|
||||
&mut transcript(),
|
||||
generators,
|
||||
key
|
||||
);
|
||||
res = $type::prove_without_bias(&mut OsRng, &mut transcript(), generators, key);
|
||||
res.is_none()
|
||||
} {}
|
||||
let res = res.unwrap();
|
||||
|
@ -102,8 +132,20 @@ macro_rules! test_dleq {
|
|||
}
|
||||
}
|
||||
|
||||
test_dleq!(test_efficient_linear_dleq, EfficientLinearDLEq);
|
||||
test_dleq!(test_concise_linear_dleq, ConciseLinearDLEq);
|
||||
test_dleq!("ClassicLinear", benchmark_classic_linear, test_classic_linear, ClassicLinearDLEq);
|
||||
test_dleq!("ConciseLinear", benchmark_concise_linear, test_concise_linear, ConciseLinearDLEq);
|
||||
test_dleq!(
|
||||
"EfficientLinear",
|
||||
benchmark_efficient_linear,
|
||||
test_efficient_linear,
|
||||
EfficientLinearDLEq
|
||||
);
|
||||
test_dleq!(
|
||||
"CompromiseLinear",
|
||||
benchmark_compromise_linear,
|
||||
test_compromise_linear,
|
||||
CompromiseLinearDLEq
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn test_rejection_sampling() {
|
||||
|
|
|
@ -96,8 +96,8 @@ fn test_ed25519() {
|
|||
test_multiexp::<EdwardsPoint>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn benchmark() {
|
||||
// Activate the processor's boost clock
|
||||
for _ in 0 .. 30 {
|
||||
|
|
Loading…
Reference in a new issue