From 44e0a41ca1f01d32b5cae76d080ae1691a41d038 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 7 Jul 2022 08:26:59 -0400 Subject: [PATCH] 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. --- crypto/dleq/src/cross_group/bits.rs | 24 ++++++--- crypto/dleq/src/cross_group/mod.rs | 52 +++++++++++++------- crypto/dleq/src/tests/cross_group/mod.rs | 62 ++++++++++++++++++++---- crypto/multiexp/src/tests/mod.rs | 2 +- 4 files changed, 105 insertions(+), 35 deletions(-) diff --git a/crypto/dleq/src/cross_group/bits.rs b/crypto/dleq/src/cross_group/bits.rs index 06e66f58..ac77de97 100644 --- a/crypto/dleq/src/cross_group/bits.rs +++ b/crypto/dleq/src/cross_group/bits.rs @@ -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(&self) -> Re { 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() } } } diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index a0a74c5a..65692585 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -62,23 +62,39 @@ pub struct DLEqProof< poks: (SchnorrPoK, SchnorrPoK) } -pub type ConciseLinearDLEq = 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 = 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 = 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)); diff --git a/crypto/dleq/src/tests/cross_group/mod.rs b/crypto/dleq/src/tests/cross_group/mod.rs index 38ef0341..a6cd4f73 100644 --- a/crypto/dleq/src/tests/cross_group/mod.rs +++ b/crypto/dleq/src/tests/cross_group/mod.rs @@ -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() { diff --git a/crypto/multiexp/src/tests/mod.rs b/crypto/multiexp/src/tests/mod.rs index 628c52c8..45e968a7 100644 --- a/crypto/multiexp/src/tests/mod.rs +++ b/crypto/multiexp/src/tests/mod.rs @@ -96,8 +96,8 @@ fn test_ed25519() { test_multiexp::(); } -#[test] #[ignore] +#[test] fn benchmark() { // Activate the processor's boost clock for _ in 0 .. 30 {