mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +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:
@@ -13,30 +13,38 @@ use std::io::{Read, Write};
|
|||||||
use crate::cross_group::read_point;
|
use crate::cross_group::read_point;
|
||||||
|
|
||||||
pub(crate) enum BitSignature {
|
pub(crate) enum BitSignature {
|
||||||
|
ClassicLinear,
|
||||||
ConciseLinear,
|
ConciseLinear,
|
||||||
EfficientLinear
|
EfficientLinear,
|
||||||
|
CompromiseLinear
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BitSignature {
|
impl BitSignature {
|
||||||
pub(crate) const fn to_u8(&self) -> u8 {
|
pub(crate) const fn to_u8(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
BitSignature::ConciseLinear => 0,
|
BitSignature::ClassicLinear => 0,
|
||||||
BitSignature::EfficientLinear => 1
|
BitSignature::ConciseLinear => 1,
|
||||||
|
BitSignature::EfficientLinear => 2,
|
||||||
|
BitSignature::CompromiseLinear => 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn from(algorithm: u8) -> BitSignature {
|
pub(crate) const fn from(algorithm: u8) -> BitSignature {
|
||||||
match algorithm {
|
match algorithm {
|
||||||
0 => BitSignature::ConciseLinear,
|
0 => BitSignature::ClassicLinear,
|
||||||
1 => BitSignature::EfficientLinear,
|
1 => BitSignature::ConciseLinear,
|
||||||
|
2 => BitSignature::EfficientLinear,
|
||||||
|
3 => BitSignature::CompromiseLinear,
|
||||||
_ => panic!("Unknown algorithm")
|
_ => panic!("Unknown algorithm")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn bits(&self) -> usize {
|
pub(crate) const fn bits(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
|
BitSignature::ClassicLinear => 1,
|
||||||
BitSignature::ConciseLinear => 2,
|
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> {
|
fn aos_form<G0: PrimeGroup, G1: PrimeGroup>(&self) -> Re<G0, G1> {
|
||||||
match self {
|
match self {
|
||||||
|
BitSignature::ClassicLinear => Re::e_default(),
|
||||||
BitSignature::ConciseLinear => 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>)
|
poks: (SchnorrPoK<G0>, SchnorrPoK<G1>)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ConciseLinearDLEq<G0, G1> = DLEqProof<
|
macro_rules! dleq {
|
||||||
|
($name: ident, $signature: expr, $remainder: expr) => {
|
||||||
|
pub type $name<G0, G1> = DLEqProof<
|
||||||
G0,
|
G0,
|
||||||
G1,
|
G1,
|
||||||
{ BitSignature::ConciseLinear.to_u8() },
|
{ $signature.to_u8() },
|
||||||
{ BitSignature::ConciseLinear.ring_len() },
|
{ $signature.ring_len() },
|
||||||
// There may not be a remainder, yet if there is, it'll be just one bit
|
// 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
|
// A ring for one bit has a RING_LEN of 2
|
||||||
2
|
{ if $remainder { 2 } else { 0 } }
|
||||||
>;
|
>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type EfficientLinearDLEq<G0, G1> = DLEqProof<
|
// Proves for 1-bit at a time with the signature form (e, s), as originally described in MRL-0010.
|
||||||
G0,
|
// Uses a merged challenge, unlike MRL-0010, for the ring signature, saving an element from each
|
||||||
G1,
|
// bit and removing a hash while slightly reducing challenge security. This security reduction is
|
||||||
{ BitSignature::EfficientLinear.to_u8() },
|
// already applied to the scalar being proven for, a result of the requirement it's mutually valid
|
||||||
{ BitSignature::EfficientLinear.ring_len() },
|
// over both scalar fields, hence its application here as well. This is mainly here as a point of
|
||||||
0
|
// 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<
|
impl<
|
||||||
G0: PrimeGroup,
|
G0: PrimeGroup,
|
||||||
@@ -279,8 +295,10 @@ impl<
|
|||||||
Self::transcript(transcript, generators, keys);
|
Self::transcript(transcript, generators, keys);
|
||||||
|
|
||||||
let batch_capacity = match BitSignature::from(SIGNATURE) {
|
let batch_capacity = match BitSignature::from(SIGNATURE) {
|
||||||
|
BitSignature::ClassicLinear => 3,
|
||||||
BitSignature::ConciseLinear => 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));
|
let mut batch = (BatchVerifier::new(batch_capacity), BatchVerifier::new(batch_capacity));
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ use transcript::RecommendedTranscript;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Generators,
|
Generators,
|
||||||
cross_group::{scalar::mutual_scalar_from_bytes, EfficientLinearDLEq, ConciseLinearDLEq}
|
cross_group::{
|
||||||
|
scalar::mutual_scalar_from_bytes,
|
||||||
|
ClassicLinearDLEq, EfficientLinearDLEq, ConciseLinearDLEq, CompromiseLinearDLEq
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mod scalar;
|
mod scalar;
|
||||||
@@ -62,7 +65,39 @@ macro_rules! verify_and_deserialize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! test_dleq {
|
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]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let generators = generators();
|
let generators = generators();
|
||||||
@@ -83,12 +118,7 @@ macro_rules! test_dleq {
|
|||||||
let mut res;
|
let mut res;
|
||||||
while {
|
while {
|
||||||
key = Scalar::random(&mut OsRng);
|
key = Scalar::random(&mut OsRng);
|
||||||
res = $type::prove_without_bias(
|
res = $type::prove_without_bias(&mut OsRng, &mut transcript(), generators, key);
|
||||||
&mut OsRng,
|
|
||||||
&mut transcript(),
|
|
||||||
generators,
|
|
||||||
key
|
|
||||||
);
|
|
||||||
res.is_none()
|
res.is_none()
|
||||||
} {}
|
} {}
|
||||||
let res = res.unwrap();
|
let res = res.unwrap();
|
||||||
@@ -102,8 +132,20 @@ macro_rules! test_dleq {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test_dleq!(test_efficient_linear_dleq, EfficientLinearDLEq);
|
test_dleq!("ClassicLinear", benchmark_classic_linear, test_classic_linear, ClassicLinearDLEq);
|
||||||
test_dleq!(test_concise_linear_dleq, ConciseLinearDLEq);
|
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]
|
#[test]
|
||||||
fn test_rejection_sampling() {
|
fn test_rejection_sampling() {
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ fn test_ed25519() {
|
|||||||
test_multiexp::<EdwardsPoint>();
|
test_multiexp::<EdwardsPoint>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
#[ignore]
|
||||||
|
#[test]
|
||||||
fn benchmark() {
|
fn benchmark() {
|
||||||
// Activate the processor's boost clock
|
// Activate the processor's boost clock
|
||||||
for _ in 0 .. 30 {
|
for _ in 0 .. 30 {
|
||||||
|
|||||||
Reference in New Issue
Block a user