diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index a0b8ac57..5ef85242 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -19,7 +19,7 @@ transcript = { package = "flexible-transcript", path = "../transcript", version ff = "0.12" group = "0.12" -multiexp = { path = "../multiexp", optional = true } +multiexp = { path = "../multiexp", features = ["batch"], optional = true } [dev-dependencies] hex-literal = "0.3" @@ -33,8 +33,8 @@ transcript = { package = "flexible-transcript", path = "../transcript", features [features] serialize = [] -cross_group = [] +cross_group = ["multiexp"] secure_capacity_difference = [] -# These only apply to cross_group, yet are default to ensure its integrity and performance -default = ["secure_capacity_difference", "multiexp"] +# Only applies to cross_group, yet is default to ensure security +default = ["secure_capacity_difference"] diff --git a/crypto/dleq/src/cross_group/bits.rs b/crypto/dleq/src/cross_group/bits.rs new file mode 100644 index 00000000..474daa8b --- /dev/null +++ b/crypto/dleq/src/cross_group/bits.rs @@ -0,0 +1,141 @@ +use rand_core::{RngCore, CryptoRng}; + +use transcript::Transcript; + +use group::{ff::PrimeFieldBits, prime::PrimeGroup}; + +use crate::{Generators, cross_group::DLEqError}; + +#[cfg(feature = "serialize")] +use std::io::{Read, Write}; +#[cfg(feature = "serialize")] +use crate::cross_group::read_point; + +pub trait RingSignature: Sized { + type Context; + + const LEN: usize; + + fn prove( + rng: &mut R, + transcript: T, + generators: (Generators, Generators), + ring: &[(G0, G1)], + actual: usize, + blinding_key: (G0::Scalar, G1::Scalar) + ) -> Self; + + fn verify( + &self, + rng: &mut R, + transcript: T, + generators: (Generators, Generators), + context: &mut Self::Context, + ring: &[(G0, G1)] + ) -> Result<(), DLEqError>; + + #[cfg(feature = "serialize")] + fn serialize(&self, w: &mut W) -> std::io::Result<()>; + #[cfg(feature = "serialize")] + fn deserialize(r: &mut R) -> std::io::Result; +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub(crate) struct Bits> { + pub(crate) commitments: (G0, G1), + signature: RING +} + +impl> Bits + where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + fn transcript(transcript: &mut T, i: usize, commitments: (G0, G1)) { + if i == 0 { + transcript.domain_separate(b"cross_group_dleq"); + } + transcript.append_message(b"bit_group", &u16::try_from(i).unwrap().to_le_bytes()); + transcript.append_message(b"commitment_0", commitments.0.to_bytes().as_ref()); + transcript.append_message(b"commitment_1", commitments.1.to_bytes().as_ref()); + } + + fn ring(pow_2: (G0, G1), commitments: (G0, G1)) -> Vec<(G0, G1)> { + let mut res = vec![(G0::identity(), G1::identity()); RING::LEN]; + res[RING::LEN - 1] = commitments; + for i in (0 .. (RING::LEN - 1)).rev() { + res[i] = (res[i + 1].0 - pow_2.0, res[i + 1].1 - pow_2.1); + } + res + } + + fn shift(pow_2: &mut (G0, G1)) { + pow_2.0 = pow_2.0.double(); + pow_2.1 = pow_2.1.double(); + if RING::LEN == 4 { + pow_2.0 = pow_2.0.double(); + pow_2.1 = pow_2.1.double(); + } + } + + pub(crate) fn prove( + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + i: usize, + pow_2: &mut (G0, G1), + bits: u8, + blinding_key: (G0::Scalar, G1::Scalar) + ) -> Self { + debug_assert!((RING::LEN == 2) || (RING::LEN == 4)); + + let mut commitments = ( + (generators.0.alt * blinding_key.0), + (generators.1.alt * blinding_key.1) + ); + commitments.0 += pow_2.0 * G0::Scalar::from(bits.into()); + commitments.1 += pow_2.1 * G1::Scalar::from(bits.into()); + Self::transcript(transcript, i, commitments); + + let ring = Self::ring(*pow_2, commitments); + // Invert the index to get the raw blinding key's position in the ring + let actual = RING::LEN - 1 - usize::from(bits); + let signature = RING::prove(rng, transcript.clone(), generators, &ring, actual, blinding_key); + + Self::shift(pow_2); + Bits { commitments, signature } + } + + pub(crate) fn verify( + &self, + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + context: &mut RING::Context, + i: usize, + pow_2: &mut (G0, G1) + ) -> Result<(), DLEqError> { + debug_assert!((RING::LEN == 2) || (RING::LEN == 4)); + + Self::transcript(transcript, i, self.commitments); + self.signature.verify( + rng, + transcript.clone(), + generators, + context, + &Self::ring(*pow_2, self.commitments) + )?; + + Self::shift(pow_2); + Ok(()) + } + + #[cfg(feature = "serialize")] + pub(crate) fn serialize(&self, w: &mut W) -> std::io::Result<()> { + w.write_all(self.commitments.0.to_bytes().as_ref())?; + w.write_all(self.commitments.1.to_bytes().as_ref())?; + self.signature.serialize(w) + } + + #[cfg(feature = "serialize")] + pub(crate) fn deserialize(r: &mut Re) -> std::io::Result { + Ok(Bits { commitments: (read_point(r)?, read_point(r)?), signature: RING::deserialize(r)? }) + } +} diff --git a/crypto/dleq/src/cross_group/linear/aos.rs b/crypto/dleq/src/cross_group/linear/aos.rs new file mode 100644 index 00000000..ada2309a --- /dev/null +++ b/crypto/dleq/src/cross_group/linear/aos.rs @@ -0,0 +1,278 @@ +use rand_core::{RngCore, CryptoRng}; + +use subtle::{ConstantTimeEq, ConditionallySelectable}; + +use transcript::Transcript; + +use group::{ff::{Field, PrimeFieldBits}, prime::PrimeGroup}; + +use multiexp::BatchVerifier; + +use crate::{ + Generators, + cross_group::{DLEqError, scalar::{scalar_convert, mutual_scalar_from_bytes}, bits::RingSignature} +}; + +#[cfg(feature = "serialize")] +use std::io::{Read, Write}; +#[cfg(feature = "serialize")] +use ff::PrimeField; +#[cfg(feature = "serialize")] +use crate::{read_scalar, cross_group::read_point}; + +#[allow(non_snake_case)] +fn nonces< + T: Transcript, + G0: PrimeGroup, + G1: PrimeGroup +>(mut transcript: T, nonces: (G0, G1)) -> (G0::Scalar, G1::Scalar) + where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + transcript.append_message(b"nonce_0", nonces.0.to_bytes().as_ref()); + transcript.append_message(b"nonce_1", nonces.1.to_bytes().as_ref()); + mutual_scalar_from_bytes(transcript.challenge(b"challenge").as_ref()) +} + +#[allow(non_snake_case)] +fn calculate_R( + generators: (Generators, Generators), + s: (G0::Scalar, G1::Scalar), + A: (G0, G1), + e: (G0::Scalar, G1::Scalar) +) -> (G0, G1) { + (((generators.0.alt * s.0) - (A.0 * e.0)), ((generators.1.alt * s.1) - (A.1 * e.1))) +} + +#[allow(non_snake_case)] +fn R_nonces( + transcript: T, + generators: (Generators, Generators), + s: (G0::Scalar, G1::Scalar), + A: (G0, G1), + e: (G0::Scalar, G1::Scalar) +) -> (G0::Scalar, G1::Scalar) where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + nonces(transcript, calculate_R(generators, s, A, e)) +} + +#[allow(non_snake_case)] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ClassicAos { + // Merged challenges have a slight security reduction, yet one already applied to the scalar + // being proven for, and this saves ~8kb. Alternatively, challenges could be redefined as a seed, + // present here, which is then hashed for each of the two challenges, remaining unbiased/unique + // while maintaining the bandwidth savings, yet also while adding 252 hashes for + // Secp256k1/Ed25519 + e_0: G0::Scalar, + s: [(G0::Scalar, G1::Scalar); RING_LEN] +} + +impl< + G0: PrimeGroup, + G1: PrimeGroup, + const RING_LEN: usize +> RingSignature for ClassicAos + where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + type Context = (); + + const LEN: usize = RING_LEN; + + fn prove( + rng: &mut R, + transcript: T, + generators: (Generators, Generators), + ring: &[(G0, G1)], + actual: usize, + blinding_key: (G0::Scalar, G1::Scalar) + ) -> Self { + // While it is possible to use larger values, it's not efficient to do so + // 2 + 2 == 2^2, yet 2 + 2 + 2 < 2^3 + debug_assert!((RING_LEN == 2) || (RING_LEN == 4)); + + let mut e_0 = G0::Scalar::zero(); + let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); RING_LEN]; + + let r = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); + #[allow(non_snake_case)] + let original_R = (generators.0.alt * r.0, generators.1.alt * r.1); + #[allow(non_snake_case)] + let mut R = original_R; + + for i in ((actual + 1) .. (actual + RING_LEN + 1)).map(|i| i % RING_LEN) { + let e = nonces(transcript.clone(), R); + e_0 = G0::Scalar::conditional_select(&e_0, &e.0, usize::ct_eq(&i, &0)); + + // Solve for the real index + if i == actual { + s[i] = (r.0 + (e.0 * blinding_key.0), r.1 + (e.1 * blinding_key.1)); + debug_assert_eq!(calculate_R(generators, s[i], ring[actual], e), original_R); + break; + // Generate a decoy response + } else { + s[i] = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); + } + + R = calculate_R(generators, s[i], ring[i], e); + } + + ClassicAos { e_0, s } + } + + fn verify( + &self, + _rng: &mut R, + transcript: T, + generators: (Generators, Generators), + _: &mut Self::Context, + ring: &[(G0, G1)] + ) -> Result<(), DLEqError> { + debug_assert!((RING_LEN == 2) || (RING_LEN == 4)); + + let e_0 = (self.e_0, scalar_convert(self.e_0).ok_or(DLEqError::InvalidChallenge)?); + let mut e = None; + for i in 0 .. RING_LEN { + e = Some(R_nonces(transcript.clone(), generators, self.s[i], ring[i], e.unwrap_or(e_0))); + } + + // Will panic if the above loop is never run somehow + // If e wasn't an Option, and instead initially set to e_0, it'd always pass + if e_0 != e.unwrap() { + Err(DLEqError::InvalidProof)?; + } + Ok(()) + } + + #[cfg(feature = "serialize")] + fn serialize(&self, w: &mut W) -> std::io::Result<()> { + w.write_all(self.e_0.to_repr().as_ref())?; + for i in 0 .. Self::LEN { + w.write_all(self.s[i].0.to_repr().as_ref())?; + w.write_all(self.s[i].1.to_repr().as_ref())?; + } + Ok(()) + } + + #[cfg(feature = "serialize")] + fn deserialize(r: &mut R) -> std::io::Result { + let e_0 = read_scalar(r)?; + let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); RING_LEN]; + for i in 0 .. Self::LEN { + s[i] = (read_scalar(r)?, read_scalar(r)?); + } + Ok(ClassicAos { e_0, s }) + } +} + +#[allow(non_snake_case)] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct MultiexpAos { + R_0: (G0, G1), + s: [(G0::Scalar, G1::Scalar); 2] +} + +impl MultiexpAos { + #[allow(non_snake_case)] + fn R_batch( + generators: (Generators, Generators), + s: (G0::Scalar, G1::Scalar), + A: (G0, G1), + e: (G0::Scalar, G1::Scalar) + ) -> (Vec<(G0::Scalar, G0)>, Vec<(G1::Scalar, G1)>) { + (vec![(s.0, generators.0.alt), (-e.0, A.0)], vec![(s.1, generators.1.alt), (-e.1, A.1)]) + } +} + +impl RingSignature for MultiexpAos + where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + type Context = (BatchVerifier<(), G0>, BatchVerifier<(), G1>); + + const LEN: usize = 2; + + fn prove( + rng: &mut R, + transcript: T, + generators: (Generators, Generators), + ring: &[(G0, G1)], + actual: usize, + blinding_key: (G0::Scalar, G1::Scalar) + ) -> Self { + #[allow(non_snake_case)] + let mut R_0 = (G0::identity(), G1::identity()); + let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); 2]; // Can't use Self::LEN due to 76200 + + let r = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); + #[allow(non_snake_case)] + let original_R = (generators.0.alt * r.0, generators.1.alt * r.1); + #[allow(non_snake_case)] + let mut R = original_R; + + for i in ((actual + 1) .. (actual + Self::LEN + 1)).map(|i| i % Self::LEN) { + if i == 0 { + R_0.0 = R.0; + R_0.1 = R.1; + } + + // Solve for the real index + let e = nonces(transcript.clone(), R); + if i == actual { + s[i] = (r.0 + (e.0 * blinding_key.0), r.1 + (e.1 * blinding_key.1)); + debug_assert_eq!(calculate_R(generators, s[i], ring[actual], e), original_R); + break; + // Generate a decoy response + } else { + s[i] = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); + } + + R = calculate_R(generators, s[i], ring[i], e); + } + + MultiexpAos { R_0, s } + } + + fn verify( + &self, + rng: &mut R, + transcript: T, + generators: (Generators, Generators), + batch: &mut Self::Context, + ring: &[(G0, G1)] + ) -> Result<(), DLEqError> { + let mut e = nonces(transcript.clone(), self.R_0); + for i in 0 .. (Self::LEN - 1) { + e = R_nonces(transcript.clone(), generators, self.s[i], ring[i], e); + } + + let mut statements = Self::R_batch( + generators, + *self.s.last().unwrap(), + *ring.last().unwrap(), + e + ); + statements.0.push((-G0::Scalar::one(), self.R_0.0)); + statements.1.push((-G1::Scalar::one(), self.R_0.1)); + batch.0.queue(&mut *rng, (), statements.0); + batch.1.queue(&mut *rng, (), statements.1); + + Ok(()) + } + + #[cfg(feature = "serialize")] + fn serialize(&self, w: &mut W) -> std::io::Result<()> { + w.write_all(self.R_0.0.to_bytes().as_ref())?; + w.write_all(self.R_0.1.to_bytes().as_ref())?; + for i in 0 .. Self::LEN { + w.write_all(self.s[i].0.to_repr().as_ref())?; + w.write_all(self.s[i].1.to_repr().as_ref())?; + } + Ok(()) + } + + #[cfg(feature = "serialize")] + fn deserialize(r: &mut R) -> std::io::Result { + #[allow(non_snake_case)] + let R_0 = (read_point(r)?, read_point(r)?); + let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); 2]; + for i in 0 .. Self::LEN { + s[i] = (read_scalar(r)?, read_scalar(r)?); + } + Ok(MultiexpAos { R_0, s }) + } +} diff --git a/crypto/dleq/src/cross_group/linear/concise.rs b/crypto/dleq/src/cross_group/linear/concise.rs new file mode 100644 index 00000000..21cf5652 --- /dev/null +++ b/crypto/dleq/src/cross_group/linear/concise.rs @@ -0,0 +1,217 @@ +use rand_core::{RngCore, CryptoRng}; + +use digest::Digest; + +use transcript::Transcript; + +use group::{ff::{Field, PrimeField, PrimeFieldBits}, prime::PrimeGroup}; + +use crate::{ + Generators, + cross_group::{ + DLEqError, DLEqProof, + scalar::{scalar_convert, mutual_scalar_from_bytes}, + schnorr::SchnorrPoK, + linear::aos::ClassicAos, + bits::Bits + } +}; + +#[cfg(feature = "serialize")] +use std::io::{Read, Write}; + +pub type ConciseDLEq = DLEqProof< + G0, + G1, + ClassicAos, + ClassicAos + >; + +impl ConciseDLEq + where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + fn prove_internal( + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + f: (G0::Scalar, G1::Scalar) + ) -> (Self, (G0::Scalar, G1::Scalar)) { + Self::initialize_transcript( + transcript, + generators, + ((generators.0.primary * f.0), (generators.1.primary * f.1)) + ); + + let poks = ( + SchnorrPoK::::prove(rng, transcript, generators.0.primary, f.0), + SchnorrPoK::::prove(rng, transcript, generators.1.primary, f.1) + ); + + let mut blinding_key_total = (G0::Scalar::zero(), G1::Scalar::zero()); + let mut blinding_key = |rng: &mut R, last| { + let blinding_key = ( + Self::blinding_key(&mut *rng, &mut blinding_key_total.0, last), + Self::blinding_key(&mut *rng, &mut blinding_key_total.1, last) + ); + if last { + debug_assert_eq!(blinding_key_total.0, G0::Scalar::zero()); + debug_assert_eq!(blinding_key_total.1, G1::Scalar::zero()); + } + blinding_key + }; + + let mut pow_2 = (generators.0.primary, generators.1.primary); + + let raw_bits = f.0.to_le_bits(); + let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); + let mut bits = Vec::with_capacity(capacity); + let mut these_bits: u8 = 0; + for (i, bit) in raw_bits.iter().enumerate() { + if i > ((capacity / 2) * 2) { + break; + } + + let bit = *bit as u8; + debug_assert_eq!(bit | 1, 1); + + if (i % 2) == 0 { + these_bits = bit; + continue; + } else { + these_bits += bit << 1; + } + + let last = i == (capacity - 1); + let blinding_key = blinding_key(&mut *rng, last); + bits.push( + Bits::prove(&mut *rng, transcript, generators, i / 2, &mut pow_2, these_bits, blinding_key) + ); + } + + let mut remainder = None; + if (capacity % 2) == 1 { + let blinding_key = blinding_key(&mut *rng, true); + remainder = Some( + Bits::prove( + &mut *rng, + transcript, + generators, + capacity / 2, + &mut pow_2, + these_bits, + blinding_key + ) + ); + } + + let proof = DLEqProof { bits, remainder, poks }; + debug_assert_eq!( + proof.reconstruct_keys(), + (generators.0.primary * f.0, generators.1.primary * f.1) + ); + (proof, f) + } + + /// Prove the cross-Group Discrete Log Equality for the points derived from the scalar created as + /// the output of the passed in Digest. Given the non-standard requirements to achieve + /// uniformity, needing to be < 2^x instead of less than a prime moduli, this is the simplest way + /// to safely and securely generate a Scalar, without risk of failure, nor bias + /// It also ensures a lack of determinable relation between keys, guaranteeing security in the + /// currently expected use case for this, atomic swaps, where each swap leaks the key. Knowing + /// the relationship between keys would allow breaking all swaps after just one + pub fn prove( + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + digest: D + ) -> (Self, (G0::Scalar, G1::Scalar)) { + Self::prove_internal( + rng, + transcript, + generators, + mutual_scalar_from_bytes(digest.finalize().as_ref()) + ) + } + + /// Prove the cross-Group Discrete Log Equality for the points derived from the scalar passed in, + /// failing if it's not mutually valid. This allows for rejection sampling externally derived + /// scalars until they're safely usable, as needed + pub fn prove_without_bias( + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + f0: G0::Scalar + ) -> Option<(Self, (G0::Scalar, G1::Scalar))> { + scalar_convert(f0).map(|f1| Self::prove_internal(rng, transcript, generators, (f0, f1))) + } + + /// Verify a cross-Group Discrete Log Equality statement, returning the points proven for + pub fn verify( + &self, + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators) + ) -> Result<(G0, G1), DLEqError> { + let capacity = G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY); + if (self.bits.len() != (capacity / 2).try_into().unwrap()) || ( + // These shouldn't be possible, as deserialize ensures this is present for fields with this + // characteristic, and proofs locally generated will have it. Regardless, best to ensure + (self.remainder.is_none() && ((capacity % 2) == 1)) || + (self.remainder.is_some() && ((capacity % 2) == 0)) + ) { + return Err(DLEqError::InvalidProofLength); + } + + let keys = self.reconstruct_keys(); + Self::initialize_transcript(transcript, generators, keys); + if !( + self.poks.0.verify(transcript, generators.0.primary, keys.0) && + self.poks.1.verify(transcript, generators.1.primary, keys.1) + ) { + Err(DLEqError::InvalidProofOfKnowledge)?; + } + + let mut pow_2 = (generators.0.primary, generators.1.primary); + for (i, bits) in self.bits.iter().enumerate() { + bits.verify(&mut *rng, transcript, generators, &mut (), i, &mut pow_2)?; + } + if let Some(bit) = &self.remainder { + bit.verify(&mut *rng, transcript, generators, &mut (), self.bits.len(), &mut pow_2)?; + } + + Ok(keys) + } + + #[cfg(feature = "serialize")] + pub fn serialize(&self, w: &mut W) -> std::io::Result<()> { + for bit in &self.bits { + bit.serialize(w)?; + } + if let Some(bit) = &self.remainder { + bit.serialize(w)?; + } + self.poks.0.serialize(w)?; + self.poks.1.serialize(w) + } + + #[cfg(feature = "serialize")] + pub fn deserialize(r: &mut R) -> std::io::Result { + let capacity = G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY); + let mut bits = Vec::with_capacity(capacity.try_into().unwrap()); + for _ in 0 .. (capacity / 2) { + bits.push(Bits::deserialize(r)?); + } + + let mut remainder = None; + if (capacity % 2) == 1 { + remainder = Some(Bits::deserialize(r)?); + } + + Ok( + DLEqProof { + bits, + remainder, + poks: (SchnorrPoK::deserialize(r)?, SchnorrPoK::deserialize(r)?) + } + ) + } +} diff --git a/crypto/dleq/src/cross_group/linear/efficient.rs b/crypto/dleq/src/cross_group/linear/efficient.rs new file mode 100644 index 00000000..696744d6 --- /dev/null +++ b/crypto/dleq/src/cross_group/linear/efficient.rs @@ -0,0 +1,182 @@ +use rand_core::{RngCore, CryptoRng}; + +use digest::Digest; + +use transcript::Transcript; + +use group::{ff::{Field, PrimeField, PrimeFieldBits}, prime::PrimeGroup}; +use multiexp::BatchVerifier; + +use crate::{ + Generators, + cross_group::{ + DLEqError, DLEqProof, + scalar::{scalar_convert, mutual_scalar_from_bytes}, + schnorr::SchnorrPoK, + linear::aos::MultiexpAos, + bits::Bits + } +}; + +#[cfg(feature = "serialize")] +use std::io::{Read, Write}; + +pub type EfficientDLEq = DLEqProof, MultiexpAos>; + +impl EfficientDLEq + where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + fn prove_internal( + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + f: (G0::Scalar, G1::Scalar) + ) -> (Self, (G0::Scalar, G1::Scalar)) { + Self::initialize_transcript( + transcript, + generators, + ((generators.0.primary * f.0), (generators.1.primary * f.1)) + ); + + let poks = ( + SchnorrPoK::::prove(rng, transcript, generators.0.primary, f.0), + SchnorrPoK::::prove(rng, transcript, generators.1.primary, f.1) + ); + + let mut blinding_key_total = (G0::Scalar::zero(), G1::Scalar::zero()); + let mut blinding_key = |rng: &mut R, last| { + let blinding_key = ( + Self::blinding_key(&mut *rng, &mut blinding_key_total.0, last), + Self::blinding_key(&mut *rng, &mut blinding_key_total.1, last) + ); + if last { + debug_assert_eq!(blinding_key_total.0, G0::Scalar::zero()); + debug_assert_eq!(blinding_key_total.1, G1::Scalar::zero()); + } + blinding_key + }; + + let mut pow_2 = (generators.0.primary, generators.1.primary); + + let raw_bits = f.0.to_le_bits(); + let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); + let mut bits = Vec::with_capacity(capacity); + for (i, bit) in raw_bits.iter().enumerate() { + let bit = *bit as u8; + debug_assert_eq!(bit | 1, 1); + + let last = i == (capacity - 1); + let blinding_key = blinding_key(&mut *rng, last); + bits.push( + Bits::prove(&mut *rng, transcript, generators, i, &mut pow_2, bit, blinding_key) + ); + + if last { + break; + } + } + + let proof = DLEqProof { bits, remainder: None, poks }; + debug_assert_eq!( + proof.reconstruct_keys(), + (generators.0.primary * f.0, generators.1.primary * f.1) + ); + (proof, f) + } + + /// Prove the cross-Group Discrete Log Equality for the points derived from the scalar created as + /// the output of the passed in Digest. Given the non-standard requirements to achieve + /// uniformity, needing to be < 2^x instead of less than a prime moduli, this is the simplest way + /// to safely and securely generate a Scalar, without risk of failure, nor bias + /// It also ensures a lack of determinable relation between keys, guaranteeing security in the + /// currently expected use case for this, atomic swaps, where each swap leaks the key. Knowing + /// the relationship between keys would allow breaking all swaps after just one + pub fn prove( + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + digest: D + ) -> (Self, (G0::Scalar, G1::Scalar)) { + Self::prove_internal( + rng, + transcript, + generators, + mutual_scalar_from_bytes(digest.finalize().as_ref()) + ) + } + + /// Prove the cross-Group Discrete Log Equality for the points derived from the scalar passed in, + /// failing if it's not mutually valid. This allows for rejection sampling externally derived + /// scalars until they're safely usable, as needed + pub fn prove_without_bias( + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + f0: G0::Scalar + ) -> Option<(Self, (G0::Scalar, G1::Scalar))> { + scalar_convert(f0).map(|f1| Self::prove_internal(rng, transcript, generators, (f0, f1))) + } + + /// Verify a cross-Group Discrete Log Equality statement, returning the points proven for + pub fn verify( + &self, + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators) + ) -> Result<(G0, G1), DLEqError> { + let capacity = G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY); + // The latter case shouldn't be possible yet would explicitly be invalid + if (self.bits.len() != capacity.try_into().unwrap()) || self.remainder.is_some() { + return Err(DLEqError::InvalidProofLength); + } + + let keys = self.reconstruct_keys(); + Self::initialize_transcript(transcript, generators, keys); + // TODO: Batch + if !( + self.poks.0.verify(transcript, generators.0.primary, keys.0) && + self.poks.1.verify(transcript, generators.1.primary, keys.1) + ) { + Err(DLEqError::InvalidProofOfKnowledge)?; + } + + let mut batch = ( + BatchVerifier::new(self.bits.len() * 3), + BatchVerifier::new(self.bits.len() * 3) + ); + let mut pow_2 = (generators.0.primary, generators.1.primary); + for (i, bits) in self.bits.iter().enumerate() { + bits.verify(&mut *rng, transcript, generators, &mut batch, i, &mut pow_2)?; + } + if (!batch.0.verify_vartime()) || (!batch.1.verify_vartime()) { + Err(DLEqError::InvalidProof)?; + } + + Ok(keys) + } + + #[cfg(feature = "serialize")] + pub fn serialize(&self, w: &mut W) -> std::io::Result<()> { + for bit in &self.bits { + bit.serialize(w)?; + } + self.poks.0.serialize(w)?; + self.poks.1.serialize(w) + } + + #[cfg(feature = "serialize")] + pub fn deserialize(r: &mut R) -> std::io::Result { + let capacity = G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY); + let mut bits = Vec::with_capacity(capacity.try_into().unwrap()); + for _ in 0 .. capacity { + bits.push(Bits::deserialize(r)?); + } + + Ok( + DLEqProof { + bits, + remainder: None, + poks: (SchnorrPoK::deserialize(r)?, SchnorrPoK::deserialize(r)?) + } + ) + } +} diff --git a/crypto/dleq/src/cross_group/linear/mod.rs b/crypto/dleq/src/cross_group/linear/mod.rs new file mode 100644 index 00000000..20322079 --- /dev/null +++ b/crypto/dleq/src/cross_group/linear/mod.rs @@ -0,0 +1,7 @@ +pub(crate) mod aos; + +mod concise; +pub use concise::ConciseDLEq; + +mod efficient; +pub use efficient::EfficientDLEq; diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index d2a81763..57231b93 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -1,26 +1,24 @@ use thiserror::Error; use rand_core::{RngCore, CryptoRng}; -use digest::Digest; - -use subtle::{ConstantTimeEq, ConditionallySelectable}; - use transcript::Transcript; -use group::{ff::{Field, PrimeField, PrimeFieldBits}, prime::PrimeGroup}; +use group::{ff::{PrimeField, PrimeFieldBits}, prime::PrimeGroup}; use crate::Generators; pub mod scalar; -use scalar::{scalar_convert, mutual_scalar_from_bytes}; pub(crate) mod schnorr; use schnorr::SchnorrPoK; +mod bits; +use bits::{RingSignature, Bits}; + +pub mod linear; + #[cfg(feature = "serialize")] -use std::io::{Read, Write}; -#[cfg(feature = "serialize")] -use crate::read_scalar; +use std::io::Read; #[cfg(feature = "serialize")] pub(crate) fn read_point(r: &mut R) -> std::io::Result { @@ -33,187 +31,6 @@ pub(crate) fn read_point(r: &mut R) -> std::io::Result { - commitments: (G0, G1), - // Merged challenges have a slight security reduction, yet one already applied to the scalar - // being proven for, and this saves ~8kb. Alternatively, challenges could be redefined as a seed, - // present here, which is then hashed for each of the two challenges, remaining unbiased/unique - // while maintaining the bandwidth savings, yet also while adding 252 hashes for - // Secp256k1/Ed25519 - e_0: G0::Scalar, - s: [(G0::Scalar, G1::Scalar); POSSIBLE_VALUES] -} - -impl Bits - where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { - pub fn transcript(transcript: &mut T, i: usize, commitments: (G0, G1)) { - if i == 0 { - transcript.domain_separate(b"cross_group_dleq"); - } - transcript.append_message(b"bit_group", &u16::try_from(i).unwrap().to_le_bytes()); - transcript.append_message(b"commitment_0", commitments.0.to_bytes().as_ref()); - transcript.append_message(b"commitment_1", commitments.1.to_bytes().as_ref()); - } - - #[allow(non_snake_case)] - fn nonces(mut transcript: T, nonces: (G0, G1)) -> (G0::Scalar, G1::Scalar) { - transcript.append_message(b"nonce_0", nonces.0.to_bytes().as_ref()); - transcript.append_message(b"nonce_1", nonces.1.to_bytes().as_ref()); - mutual_scalar_from_bytes(transcript.challenge(b"challenge").as_ref()) - } - - #[allow(non_snake_case)] - fn R( - generators: (Generators, Generators), - s: (G0::Scalar, G1::Scalar), - A: (G0, G1), - e: (G0::Scalar, G1::Scalar) - ) -> (G0, G1) { - (((generators.0.alt * s.0) - (A.0 * e.0)), ((generators.1.alt * s.1) - (A.1 * e.1))) - } - - #[allow(non_snake_case)] - fn R_nonces( - transcript: T, - generators: (Generators, Generators), - s: (G0::Scalar, G1::Scalar), - A: (G0, G1), - e: (G0::Scalar, G1::Scalar) - ) -> (G0::Scalar, G1::Scalar) { - Self::nonces(transcript, Self::R(generators, s, A, e)) - } - - fn ring(pow_2: (G0, G1), commitments: (G0, G1)) -> [(G0, G1); POSSIBLE_VALUES] { - let mut res = [(G0::identity(), G1::identity()); POSSIBLE_VALUES]; - res[POSSIBLE_VALUES - 1] = commitments; - for i in (0 .. (POSSIBLE_VALUES - 1)).rev() { - res[i] = (res[i + 1].0 - pow_2.0, res[i + 1].1 - pow_2.1); - } - res - } - - pub fn prove( - rng: &mut R, - transcript: &mut T, - generators: (Generators, Generators), - i: usize, - pow_2: &mut (G0, G1), - bits: u8, - blinding_key: (G0::Scalar, G1::Scalar) - ) -> Bits { - // While it is possible to use larger values, it's not efficient to do so - // 2 + 2 == 2^2, yet 2 + 2 + 2 < 2^3 - debug_assert!((POSSIBLE_VALUES == 2) || (POSSIBLE_VALUES == 4)); - - let mut commitments = ( - (generators.0.alt * blinding_key.0), - (generators.1.alt * blinding_key.1) - ); - commitments.0 += pow_2.0 * G0::Scalar::from(bits.into()); - commitments.1 += pow_2.1 * G1::Scalar::from(bits.into()); - Self::transcript(transcript, i, commitments); - - let ring = Self::ring(*pow_2, commitments); - // Invert the index to get the raw blinding key's position in the ring - let actual = POSSIBLE_VALUES - 1 - usize::from(bits); - - let mut e_0 = G0::Scalar::zero(); - let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); POSSIBLE_VALUES]; - - let r = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); - #[allow(non_snake_case)] - let original_R = (generators.0.alt * r.0, generators.1.alt * r.1); - #[allow(non_snake_case)] - let mut R = original_R; - - for i in ((actual + 1) .. (actual + POSSIBLE_VALUES + 1)).map(|i| i % POSSIBLE_VALUES) { - let e = Self::nonces(transcript.clone(), R); - e_0 = G0::Scalar::conditional_select(&e_0, &e.0, usize::ct_eq(&i, &0)); - - // Solve for the real index - if i == actual { - s[i] = (r.0 + (e.0 * blinding_key.0), r.1 + (e.1 * blinding_key.1)); - debug_assert_eq!(Self::R(generators, s[i], ring[actual], e), original_R); - break; - // Generate a decoy response - } else { - s[i] = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); - } - - R = Self::R(generators, s[i], ring[i], e); - } - - pow_2.0 = pow_2.0.double(); - pow_2.1 = pow_2.1.double(); - if POSSIBLE_VALUES == 4 { - pow_2.0 = pow_2.0.double(); - pow_2.1 = pow_2.1.double(); - } - - Bits { commitments, e_0, s } - } - - pub fn verify( - &self, - transcript: &mut T, - generators: (Generators, Generators), - i: usize, - pow_2: &mut (G0, G1) - ) -> Result<(), DLEqError> { - debug_assert!((POSSIBLE_VALUES == 2) || (POSSIBLE_VALUES == 4)); - - Self::transcript(transcript, i, self.commitments); - - let ring = Self::ring(*pow_2, self.commitments); - let e_0 = (self.e_0, scalar_convert(self.e_0).ok_or(DLEqError::InvalidChallenge)?); - let mut e = None; - for i in 0 .. POSSIBLE_VALUES { - e = Some( - Self::R_nonces(transcript.clone(), generators, self.s[i], ring[i], e.unwrap_or(e_0)) - ); - } - - // Will panic if the above loop is never run somehow - // If e wasn't an Option, and instead initially set to e_0, it'd always pass - if e_0 != e.unwrap() { - return Err(DLEqError::InvalidProof); - } - - pow_2.0 = pow_2.0.double(); - pow_2.1 = pow_2.1.double(); - if POSSIBLE_VALUES == 4 { - pow_2.0 = pow_2.0.double(); - pow_2.1 = pow_2.1.double(); - } - - Ok(()) - } - - #[cfg(feature = "serialize")] - pub fn serialize(&self, w: &mut W) -> std::io::Result<()> { - w.write_all(self.commitments.0.to_bytes().as_ref())?; - w.write_all(self.commitments.1.to_bytes().as_ref())?; - w.write_all(self.e_0.to_repr().as_ref())?; - for i in 0 .. POSSIBLE_VALUES { - w.write_all(self.s[i].0.to_repr().as_ref())?; - w.write_all(self.s[i].1.to_repr().as_ref())?; - } - Ok(()) - } - - #[cfg(feature = "serialize")] - pub fn deserialize(r: &mut R) -> std::io::Result> { - let commitments = (read_point(r)?, read_point(r)?); - let e_0 = read_scalar(r)?; - let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); POSSIBLE_VALUES]; - for i in 0 .. POSSIBLE_VALUES { - s[i] = (read_scalar(r)?, read_scalar(r)?); - } - Ok(Bits { commitments, e_0, s }) - } -} - #[derive(Error, PartialEq, Eq, Debug)] pub enum DLEqError { #[error("invalid proof of knowledge")] @@ -229,15 +46,24 @@ pub enum DLEqError { // Debug would be such a dump of data this likely isn't helpful, but at least it's available to // anyone who wants it #[derive(Clone, PartialEq, Eq, Debug)] -pub struct DLEqProof { - bits: Vec>, - remainder: Option>, +pub struct DLEqProof< + G0: PrimeGroup, + G1: PrimeGroup, + RING: RingSignature, + REM: RingSignature +> where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + bits: Vec>, + remainder: Option>, poks: (SchnorrPoK, SchnorrPoK) } -impl DLEqProof - where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { - fn initialize_transcript( +impl< + G0: PrimeGroup, + G1: PrimeGroup, + RING: RingSignature, + REM: RingSignature +> DLEqProof where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + pub(crate) fn initialize_transcript( transcript: &mut T, generators: (Generators, Generators), keys: (G0, G1) @@ -249,7 +75,7 @@ impl DLEqProof transcript.append_message(b"point_1", keys.1.to_bytes().as_ref()); } - fn blinding_key( + pub(crate) fn blinding_key( rng: &mut R, total: &mut F, last: bool @@ -264,195 +90,16 @@ impl DLEqProof } fn reconstruct_keys(&self) -> (G0, G1) { - let remainder = self.remainder - .as_ref() - .map(|bit| bit.commitments) - .unwrap_or((G0::identity(), G1::identity())); - ( - self.bits.iter().map(|bit| bit.commitments.0).sum::() + remainder.0, - self.bits.iter().map(|bit| bit.commitments.1).sum::() + remainder.1 - ) - } - - fn prove_internal( - rng: &mut R, - transcript: &mut T, - generators: (Generators, Generators), - f: (G0::Scalar, G1::Scalar) - ) -> (Self, (G0::Scalar, G1::Scalar)) { - Self::initialize_transcript( - transcript, - generators, - ((generators.0.primary * f.0), (generators.1.primary * f.1)) + let mut res = ( + self.bits.iter().map(|bit| bit.commitments.0).sum::(), + self.bits.iter().map(|bit| bit.commitments.1).sum::() ); - let poks = ( - SchnorrPoK::::prove(rng, transcript, generators.0.primary, f.0), - SchnorrPoK::::prove(rng, transcript, generators.1.primary, f.1) - ); - - let mut blinding_key_total = (G0::Scalar::zero(), G1::Scalar::zero()); - let mut blinding_key = |rng: &mut R, last| { - let blinding_key = ( - Self::blinding_key(&mut *rng, &mut blinding_key_total.0, last), - Self::blinding_key(&mut *rng, &mut blinding_key_total.1, last) - ); - if last { - debug_assert_eq!(blinding_key_total.0, G0::Scalar::zero()); - debug_assert_eq!(blinding_key_total.1, G1::Scalar::zero()); - } - blinding_key - }; - - let mut pow_2 = (generators.0.primary, generators.1.primary); - - let raw_bits = f.0.to_le_bits(); - let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); - let mut bits = Vec::with_capacity(capacity); - let mut these_bits: u8 = 0; - for (i, bit) in raw_bits.iter().enumerate() { - if i > ((capacity / 2) * 2) { - break; - } - - let bit = *bit as u8; - debug_assert_eq!(bit | 1, 1); - - if (i % 2) == 0 { - these_bits = bit; - continue; - } else { - these_bits += bit << 1; - } - - let last = i == (capacity - 1); - let blinding_key = blinding_key(&mut *rng, last); - bits.push( - Bits::prove(&mut *rng, transcript, generators, i / 2, &mut pow_2, these_bits, blinding_key) - ); - } - - let mut remainder = None; - if (capacity % 2) == 1 { - let blinding_key = blinding_key(&mut *rng, true); - remainder = Some( - Bits::prove( - &mut *rng, - transcript, - generators, - capacity / 2, - &mut pow_2, - these_bits, - blinding_key - ) - ); - } - - let proof = DLEqProof { bits, remainder, poks }; - debug_assert_eq!( - proof.reconstruct_keys(), - (generators.0.primary * f.0, generators.1.primary * f.1) - ); - (proof, f) - } - - /// Prove the cross-Group Discrete Log Equality for the points derived from the scalar created as - /// the output of the passed in Digest. Given the non-standard requirements to achieve - /// uniformity, needing to be < 2^x instead of less than a prime moduli, this is the simplest way - /// to safely and securely generate a Scalar, without risk of failure, nor bias - /// It also ensures a lack of determinable relation between keys, guaranteeing security in the - /// currently expected use case for this, atomic swaps, where each swap leaks the key. Knowing - /// the relationship between keys would allow breaking all swaps after just one - pub fn prove( - rng: &mut R, - transcript: &mut T, - generators: (Generators, Generators), - digest: D - ) -> (Self, (G0::Scalar, G1::Scalar)) { - Self::prove_internal( - rng, - transcript, - generators, - mutual_scalar_from_bytes(digest.finalize().as_ref()) - ) - } - - /// Prove the cross-Group Discrete Log Equality for the points derived from the scalar passed in, - /// failing if it's not mutually valid. This allows for rejection sampling externally derived - /// scalars until they're safely usable, as needed - pub fn prove_without_bias( - rng: &mut R, - transcript: &mut T, - generators: (Generators, Generators), - f0: G0::Scalar - ) -> Option<(Self, (G0::Scalar, G1::Scalar))> { - scalar_convert(f0).map(|f1| Self::prove_internal(rng, transcript, generators, (f0, f1))) - } - - /// Verify a cross-Group Discrete Log Equality statement, returning the points proven for - pub fn verify( - &self, - transcript: &mut T, - generators: (Generators, Generators) - ) -> Result<(G0, G1), DLEqError> { - let capacity = G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY); - if (self.bits.len() != (capacity / 2).try_into().unwrap()) || ( - // This shouldn't be possible, as deserialize ensures this is present for fields with this - // characteristic, and proofs locally generated will have it. Regardless, best to ensure - self.remainder.is_none() && ((capacity % 2) == 1) - ) { - return Err(DLEqError::InvalidProofLength); - } - - let keys = self.reconstruct_keys(); - Self::initialize_transcript(transcript, generators, keys); - if !( - self.poks.0.verify(transcript, generators.0.primary, keys.0) && - self.poks.1.verify(transcript, generators.1.primary, keys.1) - ) { - Err(DLEqError::InvalidProofOfKnowledge)?; - } - - let mut pow_2 = (generators.0.primary, generators.1.primary); - for (i, bits) in self.bits.iter().enumerate() { - bits.verify(transcript, generators, i, &mut pow_2)?; - } if let Some(bit) = &self.remainder { - bit.verify(transcript, generators, self.bits.len(), &mut pow_2)?; + res.0 += bit.commitments.0; + res.1 += bit.commitments.1; } - Ok(keys) - } - - #[cfg(feature = "serialize")] - pub fn serialize(&self, w: &mut W) -> std::io::Result<()> { - for bit in &self.bits { - bit.serialize(w)?; - } - if let Some(bit) = &self.remainder { - bit.serialize(w)?; - } - self.poks.0.serialize(w)?; - self.poks.1.serialize(w) - } - - #[cfg(feature = "serialize")] - pub fn deserialize(r: &mut R) -> std::io::Result> { - let capacity = G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY); - let mut bits = Vec::with_capacity(capacity.try_into().unwrap()); - for _ in 0 .. (capacity / 2) { - bits.push(Bits::deserialize(r)?); - } - let mut remainder = None; - if (capacity % 2) == 1 { - remainder = Some(Bits::deserialize(r)?); - } - Ok( - DLEqProof { - bits, - remainder, - poks: (SchnorrPoK::deserialize(r)?, SchnorrPoK::deserialize(r)?) - } - ) + res } } diff --git a/crypto/dleq/src/tests/cross_group/linear/concise.rs b/crypto/dleq/src/tests/cross_group/linear/concise.rs new file mode 100644 index 00000000..1fbe49a7 --- /dev/null +++ b/crypto/dleq/src/tests/cross_group/linear/concise.rs @@ -0,0 +1,98 @@ +use rand_core::{RngCore, OsRng}; + +use ff::{Field, PrimeField}; + +use k256::Scalar; +#[cfg(feature = "serialize")] +use k256::ProjectivePoint; +#[cfg(feature = "serialize")] +use dalek_ff_group::EdwardsPoint; + +use blake2::{Digest, Blake2b512}; + +use crate::{ + cross_group::{scalar::mutual_scalar_from_bytes, linear::ConciseDLEq}, + tests::cross_group::{transcript, generators} +}; + +#[test] +fn test_linear_concise_cross_group_dleq() { + let generators = generators(); + + for i in 0 .. 1 { + let (proof, keys) = if i == 0 { + let mut seed = [0; 32]; + OsRng.fill_bytes(&mut seed); + + ConciseDLEq::prove( + &mut OsRng, + &mut transcript(), + generators, + Blake2b512::new().chain_update(seed) + ) + } else { + let mut key; + let mut res; + while { + key = Scalar::random(&mut OsRng); + res = ConciseDLEq::prove_without_bias( + &mut OsRng, + &mut transcript(), + generators, + key + ); + res.is_none() + } {} + let res = res.unwrap(); + assert_eq!(key, res.1.0); + res + }; + + let public_keys = proof.verify(&mut OsRng, &mut transcript(), generators).unwrap(); + assert_eq!(generators.0.primary * keys.0, public_keys.0); + assert_eq!(generators.1.primary * keys.1, public_keys.1); + + #[cfg(feature = "serialize")] + { + let mut buf = vec![]; + proof.serialize(&mut buf).unwrap(); + let deserialized = ConciseDLEq::::deserialize( + &mut std::io::Cursor::new(&buf) + ).unwrap(); + assert_eq!(proof, deserialized); + deserialized.verify(&mut OsRng, &mut transcript(), generators).unwrap(); + } + } +} + +#[test] +fn test_remainder() { + // Uses Secp256k1 for both to achieve an odd capacity of 255 + assert_eq!(Scalar::CAPACITY, 255); + let generators = (generators().0, generators().0); + let keys = mutual_scalar_from_bytes(&[0xFF; 32]); + assert_eq!(keys.0, keys.1); + + let (proof, res) = ConciseDLEq::prove_without_bias( + &mut OsRng, + &mut transcript(), + generators, + keys.0 + ).unwrap(); + assert_eq!(keys, res); + + let public_keys = proof.verify(&mut OsRng, &mut transcript(), generators).unwrap(); + assert_eq!(generators.0.primary * keys.0, public_keys.0); + assert_eq!(generators.1.primary * keys.1, public_keys.1); + + #[cfg(feature = "serialize")] + { + let mut buf = vec![]; + proof.serialize(&mut buf).unwrap(); + let deserialized = ConciseDLEq::::deserialize( + &mut std::io::Cursor::new(&buf) + ).unwrap(); + assert_eq!(proof, deserialized); + deserialized.verify(&mut OsRng, &mut transcript(), generators).unwrap(); + } +} diff --git a/crypto/dleq/src/tests/cross_group/linear/efficient.rs b/crypto/dleq/src/tests/cross_group/linear/efficient.rs new file mode 100644 index 00000000..bafc4902 --- /dev/null +++ b/crypto/dleq/src/tests/cross_group/linear/efficient.rs @@ -0,0 +1,66 @@ +use rand_core::{RngCore, OsRng}; + +use ff::Field; + +use k256::Scalar; +#[cfg(feature = "serialize")] +use k256::ProjectivePoint; +#[cfg(feature = "serialize")] +use dalek_ff_group::EdwardsPoint; + +use blake2::{Digest, Blake2b512}; + +use crate::{ + cross_group::linear::EfficientDLEq, + tests::cross_group::{transcript, generators} +}; + +#[test] +fn test_linear_efficient_cross_group_dleq() { + let generators = generators(); + + for i in 0 .. 1 { + let (proof, keys) = if i == 0 { + let mut seed = [0; 32]; + OsRng.fill_bytes(&mut seed); + + EfficientDLEq::prove( + &mut OsRng, + &mut transcript(), + generators, + Blake2b512::new().chain_update(seed) + ) + } else { + let mut key; + let mut res; + while { + key = Scalar::random(&mut OsRng); + res = EfficientDLEq::prove_without_bias( + &mut OsRng, + &mut transcript(), + generators, + key + ); + res.is_none() + } {} + let res = res.unwrap(); + assert_eq!(key, res.1.0); + res + }; + + let public_keys = proof.verify(&mut OsRng, &mut transcript(), generators).unwrap(); + assert_eq!(generators.0.primary * keys.0, public_keys.0); + assert_eq!(generators.1.primary * keys.1, public_keys.1); + + #[cfg(feature = "serialize")] + { + let mut buf = vec![]; + proof.serialize(&mut buf).unwrap(); + let deserialized = EfficientDLEq::::deserialize( + &mut std::io::Cursor::new(&buf) + ).unwrap(); + assert_eq!(proof, deserialized); + deserialized.verify(&mut OsRng, &mut transcript(), generators).unwrap(); + } + } +} diff --git a/crypto/dleq/src/tests/cross_group/linear/mod.rs b/crypto/dleq/src/tests/cross_group/linear/mod.rs new file mode 100644 index 00000000..5603b63d --- /dev/null +++ b/crypto/dleq/src/tests/cross_group/linear/mod.rs @@ -0,0 +1,2 @@ +mod concise; +mod efficient; diff --git a/crypto/dleq/src/tests/cross_group/mod.rs b/crypto/dleq/src/tests/cross_group/mod.rs index 9f3a1916..9557127d 100644 --- a/crypto/dleq/src/tests/cross_group/mod.rs +++ b/crypto/dleq/src/tests/cross_group/mod.rs @@ -2,7 +2,7 @@ mod scalar; mod schnorr; use hex_literal::hex; -use rand_core::{RngCore, OsRng}; +use rand_core::OsRng; use ff::{Field, PrimeField}; use group::{Group, GroupEncoding}; @@ -10,17 +10,17 @@ use group::{Group, GroupEncoding}; use k256::{Scalar, ProjectivePoint}; use dalek_ff_group::{self as dfg, EdwardsPoint, CompressedEdwardsY}; -use blake2::{Digest, Blake2b512}; - use transcript::RecommendedTranscript; -use crate::{Generators, cross_group::{DLEqProof, scalar::mutual_scalar_from_bytes}}; +use crate::{Generators, cross_group::linear::EfficientDLEq}; -fn transcript() -> RecommendedTranscript { +mod linear; + +pub(crate) fn transcript() -> RecommendedTranscript { RecommendedTranscript::new(b"Cross-Group DLEq Proof Test") } -fn generators() -> (Generators, Generators) { +pub(crate) fn generators() -> (Generators, Generators) { ( Generators::new( ProjectivePoint::GENERATOR, @@ -46,7 +46,7 @@ fn test_rejection_sampling() { } assert!( - DLEqProof::prove_without_bias( + EfficientDLEq::prove_without_bias( &mut OsRng, &mut RecommendedTranscript::new(b""), generators(), @@ -54,85 +54,3 @@ fn test_rejection_sampling() { ).is_none() ); } - -#[test] -fn test_cross_group_dleq() { - let generators = generators(); - - for i in 0 .. 2 { - let (proof, keys) = if i == 0 { - let mut seed = [0; 32]; - OsRng.fill_bytes(&mut seed); - - DLEqProof::prove( - &mut OsRng, - &mut transcript(), - generators, - Blake2b512::new().chain_update(seed) - ) - } else { - let mut key; - let mut res; - while { - key = Scalar::random(&mut OsRng); - res = DLEqProof::prove_without_bias( - &mut OsRng, - &mut transcript(), - generators, - key - ); - res.is_none() - } {} - let res = res.unwrap(); - assert_eq!(key, res.1.0); - res - }; - - let public_keys = proof.verify(&mut transcript(), generators).unwrap(); - assert_eq!(generators.0.primary * keys.0, public_keys.0); - assert_eq!(generators.1.primary * keys.1, public_keys.1); - - #[cfg(feature = "serialize")] - { - let mut buf = vec![]; - proof.serialize(&mut buf).unwrap(); - let deserialized = DLEqProof::::deserialize( - &mut std::io::Cursor::new(&buf) - ).unwrap(); - assert_eq!(proof, deserialized); - deserialized.verify(&mut transcript(), generators).unwrap(); - } - } -} - -#[test] -fn test_remainder() { - // Uses Secp256k1 for both to achieve an odd capacity of 255 - assert_eq!(Scalar::CAPACITY, 255); - let generators = (generators().0, generators().0); - let keys = mutual_scalar_from_bytes(&[0xFF; 32]); - assert_eq!(keys.0, keys.1); - - let (proof, res) = DLEqProof::prove_without_bias( - &mut OsRng, - &mut transcript(), - generators, - keys.0 - ).unwrap(); - assert_eq!(keys, res); - - let public_keys = proof.verify(&mut transcript(), generators).unwrap(); - assert_eq!(generators.0.primary * keys.0, public_keys.0); - assert_eq!(generators.1.primary * keys.1, public_keys.1); - - #[cfg(feature = "serialize")] - { - let mut buf = vec![]; - proof.serialize(&mut buf).unwrap(); - let deserialized = DLEqProof::::deserialize( - &mut std::io::Cursor::new(&buf) - ).unwrap(); - assert_eq!(proof, deserialized); - deserialized.verify(&mut transcript(), generators).unwrap(); - } -}