Update the DLEq proof for any amount of generators

The two-generator limit wasn't required nor beneficial. This does 
theoretically optimize FROST, yet not for any current constructions. A 
follow up proof which would optimize current constructions has been 
noted in #38.

Adds explicit no_std support to the core DLEq proof.

Closes #34.
This commit is contained in:
Luke Parker
2022-07-13 23:29:48 -04:00
parent 46975812c3
commit 5ede5b9e8f
9 changed files with 110 additions and 105 deletions

View File

@@ -6,9 +6,8 @@ use group::{ff::{Field, PrimeFieldBits}, prime::PrimeGroup};
use multiexp::BatchVerifier;
use crate::{
Generators,
cross_group::{DLEqError, scalar::{scalar_convert, mutual_scalar_from_bytes}}
use crate::cross_group::{
Generators, DLEqError, scalar::{scalar_convert, mutual_scalar_from_bytes}
};
#[cfg(feature = "serialize")]

View File

@@ -5,7 +5,7 @@ use transcript::Transcript;
use group::{ff::PrimeFieldBits, prime::PrimeGroup};
use multiexp::BatchVerifier;
use crate::{Generators, cross_group::{DLEqError, aos::{Re, Aos}}};
use crate::cross_group::{Generators, DLEqError, aos::{Re, Aos}};
#[cfg(feature = "serialize")]
use std::io::{Read, Write};

View File

@@ -8,8 +8,6 @@ use transcript::Transcript;
use group::{ff::{Field, PrimeField, PrimeFieldBits}, prime::PrimeGroup};
use multiexp::BatchVerifier;
use crate::Generators;
pub mod scalar;
use scalar::{scalar_convert, mutual_scalar_from_bytes};
@@ -35,6 +33,24 @@ pub(crate) fn read_point<R: Read, G: PrimeGroup>(r: &mut R) -> std::io::Result<G
Ok(point.unwrap())
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Generators<G: PrimeGroup> {
pub primary: G,
pub alt: G
}
impl<G: PrimeGroup> Generators<G> {
pub fn new(primary: G, alt: G) -> Generators<G> {
Generators { primary, alt }
}
fn transcript<T: Transcript>(&self, transcript: &mut T) {
transcript.domain_separate(b"generators");
transcript.append_message(b"primary", self.primary.to_bytes().as_ref());
transcript.append_message(b"alternate", self.alt.to_bytes().as_ref());
}
}
#[derive(Error, PartialEq, Eq, Debug)]
pub enum DLEqError {
#[error("invalid proof of knowledge")]

View File

@@ -1,4 +1,5 @@
use thiserror::Error;
#![cfg_attr(not(feature = "std"), no_std)]
use rand_core::{RngCore, CryptoRng};
use transcript::Transcript;
@@ -15,24 +16,6 @@ pub mod cross_group;
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Generators<G: PrimeGroup> {
primary: G,
alt: G
}
impl<G: PrimeGroup> Generators<G> {
pub fn new(primary: G, alt: G) -> Generators<G> {
Generators { primary, alt }
}
fn transcript<T: Transcript>(&self, transcript: &mut T) {
transcript.domain_separate(b"generators");
transcript.append_message(b"primary", self.primary.to_bytes().as_ref());
transcript.append_message(b"alternate", self.alt.to_bytes().as_ref());
}
}
pub(crate) fn challenge<T: Transcript, F: PrimeField>(transcript: &mut T) -> F {
// From here, there are three ways to get a scalar under the ff/group API
// 1: Scalar::random(ChaCha12Rng::from_seed(self.transcript.rng_seed(b"challenge")))
@@ -70,9 +53,8 @@ fn read_scalar<R: Read, F: PrimeField>(r: &mut R) -> io::Result<F> {
Ok(scalar.unwrap())
}
#[derive(Error, Debug)]
#[derive(Debug)]
pub enum DLEqError {
#[error("invalid proof")]
InvalidProof
}
@@ -84,34 +66,26 @@ pub struct DLEqProof<G: PrimeGroup> {
#[allow(non_snake_case)]
impl<G: PrimeGroup> DLEqProof<G> {
fn challenge<T: Transcript>(
transcript: &mut T,
generators: Generators<G>,
nonces: (G, G),
points: (G, G)
) -> G::Scalar {
generators.transcript(transcript);
transcript.domain_separate(b"dleq");
transcript.append_message(b"nonce_primary", nonces.0.to_bytes().as_ref());
transcript.append_message(b"nonce_alternate", nonces.1.to_bytes().as_ref());
transcript.append_message(b"point_primary", points.0.to_bytes().as_ref());
transcript.append_message(b"point_alternate", points.1.to_bytes().as_ref());
challenge(transcript)
fn transcript<T: Transcript>(transcript: &mut T, generator: G, nonce: G, point: G) {
transcript.append_message(b"generator", generator.to_bytes().as_ref());
transcript.append_message(b"nonce", nonce.to_bytes().as_ref());
transcript.append_message(b"point", point.to_bytes().as_ref());
}
pub fn prove<R: RngCore + CryptoRng, T: Transcript>(
rng: &mut R,
transcript: &mut T,
generators: Generators<G>,
generators: &[G],
scalar: G::Scalar
) -> DLEqProof<G> {
let r = G::Scalar::random(rng);
let c = Self::challenge(
transcript,
generators,
(generators.primary * r, generators.alt * r),
(generators.primary * scalar, generators.alt * scalar)
);
transcript.domain_separate(b"dleq");
for generator in generators {
Self::transcript(transcript, *generator, *generator * r, *generator * scalar);
}
let c = challenge(transcript);
let s = r + (c * scalar);
DLEqProof { c, s }
@@ -120,18 +94,19 @@ impl<G: PrimeGroup> DLEqProof<G> {
pub fn verify<T: Transcript>(
&self,
transcript: &mut T,
generators: Generators<G>,
points: (G, G)
generators: &[G],
points: &[G]
) -> Result<(), DLEqError> {
if self.c != Self::challenge(
transcript,
generators,
(
(generators.primary * self.s) - (points.0 * self.c),
(generators.alt * self.s) - (points.1 * self.c)
),
points
) {
if generators.len() != points.len() {
Err(DLEqError::InvalidProof)?;
}
transcript.domain_separate(b"dleq");
for (generator, point) in generators.iter().zip(points) {
Self::transcript(transcript, *generator, (*generator * self.s) - (*point * self.c), *point);
}
if self.c != challenge(transcript) {
Err(DLEqError::InvalidProof)?;
}

View File

@@ -12,10 +12,9 @@ use dalek_ff_group::{self as dfg, EdwardsPoint};
use transcript::{Transcript, RecommendedTranscript};
use crate::{
Generators,
cross_group::{
scalar::mutual_scalar_from_bytes,
ClassicLinearDLEq, EfficientLinearDLEq, ConciseLinearDLEq, CompromiseLinearDLEq
Generators, ClassicLinearDLEq, EfficientLinearDLEq, ConciseLinearDLEq, CompromiseLinearDLEq
}
};

View File

@@ -11,33 +11,47 @@ use k256::{Scalar, ProjectivePoint};
use transcript::{Transcript, RecommendedTranscript};
use crate::{Generators, DLEqProof};
use crate::DLEqProof;
#[test]
fn test_dleq() {
let transcript = || RecommendedTranscript::new(b"DLEq Proof Test");
let generators = Generators::new(
let generators = [
ProjectivePoint::GENERATOR,
ProjectivePoint::from_bytes(
&(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0").into())
).unwrap(),
// Just an increment of the last byte from the previous, where the previous two are valid
ProjectivePoint::from_bytes(
&(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4").into())
).unwrap(),
ProjectivePoint::from_bytes(
&(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803aca").into())
).unwrap(),
ProjectivePoint::from_bytes(
&(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803acb").into())
).unwrap()
);
];
let key = Scalar::random(&mut OsRng);
let proof = DLEqProof::prove(&mut OsRng, &mut transcript(), generators, key);
for i in 0 .. 5 {
let key = Scalar::random(&mut OsRng);
let proof = DLEqProof::prove(&mut OsRng, &mut transcript(), &generators[.. i], key);
let keys = (generators.primary * key, generators.alt * key);
proof.verify(&mut transcript(), generators, keys).unwrap();
let mut keys = [ProjectivePoint::GENERATOR; 5];
for k in 0 .. 5 {
keys[k] = generators[k] * key;
}
proof.verify(&mut transcript(), &generators[.. i], &keys[.. i]).unwrap();
#[cfg(feature = "serialize")]
{
let mut buf = vec![];
proof.serialize(&mut buf).unwrap();
let deserialized = DLEqProof::<ProjectivePoint>::deserialize(
&mut std::io::Cursor::new(&buf)
).unwrap();
assert_eq!(proof, deserialized);
deserialized.verify(&mut transcript(), generators, keys).unwrap();
#[cfg(feature = "serialize")]
{
let mut buf = vec![];
proof.serialize(&mut buf).unwrap();
let deserialized = DLEqProof::<ProjectivePoint>::deserialize(
&mut std::io::Cursor::new(&buf)
).unwrap();
assert_eq!(proof, deserialized);
}
}
}