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:
Luke Parker 2022-07-07 08:26:59 -04:00
parent 1a2e6dc5cf
commit 44e0a41ca1
4 changed files with 105 additions and 35 deletions

View file

@ -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()
}
}
}

View file

@ -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));

View file

@ -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() {

View file

@ -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 {