From 1a2e6dc5cf9ecac0279a8ed27bfea035e00cc72f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 7 Jul 2022 07:30:10 -0400 Subject: [PATCH] Consolidate concise/efficient and clean --- .../dleq/src/cross_group/{linear => }/aos.rs | 4 + crypto/dleq/src/cross_group/bits.rs | 121 ++++---- crypto/dleq/src/cross_group/linear/concise.rs | 217 --------------- .../dleq/src/cross_group/linear/efficient.rs | 182 ------------ crypto/dleq/src/cross_group/linear/mod.rs | 7 - crypto/dleq/src/cross_group/mod.rs | 263 +++++++++++++++++- crypto/dleq/src/cross_group/schnorr.rs | 26 +- .../src/tests/cross_group/{linear => }/aos.rs | 5 +- .../src/tests/cross_group/linear/concise.rs | 98 ------- .../src/tests/cross_group/linear/efficient.rs | 66 ----- .../dleq/src/tests/cross_group/linear/mod.rs | 2 - crypto/dleq/src/tests/cross_group/mod.rs | 105 ++++++- crypto/dleq/src/tests/cross_group/schnorr.rs | 25 +- 13 files changed, 458 insertions(+), 663 deletions(-) rename crypto/dleq/src/cross_group/{linear => }/aos.rs (96%) delete mode 100644 crypto/dleq/src/cross_group/linear/concise.rs delete mode 100644 crypto/dleq/src/cross_group/linear/efficient.rs delete mode 100644 crypto/dleq/src/cross_group/linear/mod.rs rename crypto/dleq/src/tests/cross_group/{linear => }/aos.rs (92%) delete mode 100644 crypto/dleq/src/tests/cross_group/linear/concise.rs delete mode 100644 crypto/dleq/src/tests/cross_group/linear/efficient.rs delete mode 100644 crypto/dleq/src/tests/cross_group/linear/mod.rs diff --git a/crypto/dleq/src/cross_group/linear/aos.rs b/crypto/dleq/src/cross_group/aos.rs similarity index 96% rename from crypto/dleq/src/cross_group/linear/aos.rs rename to crypto/dleq/src/cross_group/aos.rs index 8d2a8c67..fb468969 100644 --- a/crypto/dleq/src/cross_group/linear/aos.rs +++ b/crypto/dleq/src/cross_group/aos.rs @@ -55,6 +55,8 @@ impl< > Aos where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { #[allow(non_snake_case)] fn nonces(mut transcript: T, nonces: (G0, G1)) -> (G0::Scalar, G1::Scalar) { + transcript.domain_separate(b"aos_membership_proof"); + transcript.append_message(b"ring_len", &u8::try_from(RING_LEN).unwrap().to_le_bytes()); 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()) @@ -151,6 +153,7 @@ impl< debug_assert!((RING_LEN == 2) || (RING_LEN == 4)); debug_assert_eq!(RING_LEN, ring.len()); + #[allow(non_snake_case)] match self.Re_0 { Re::R(R0_0, R1_0) => { let mut e = Self::nonces(transcript.clone(), (R0_0, R1_0)); @@ -164,6 +167,7 @@ impl< *ring.last().unwrap(), e ); + // TODO: Make something else negative to speed up vartime statements.0.push((-G0::Scalar::one(), R0_0)); statements.1.push((-G1::Scalar::one(), R1_0)); batch.0.queue(&mut *rng, (), statements.0); diff --git a/crypto/dleq/src/cross_group/bits.rs b/crypto/dleq/src/cross_group/bits.rs index 474daa8b..06e66f58 100644 --- a/crypto/dleq/src/cross_group/bits.rs +++ b/crypto/dleq/src/cross_group/bits.rs @@ -3,73 +3,89 @@ use rand_core::{RngCore, CryptoRng}; use transcript::Transcript; use group::{ff::PrimeFieldBits, prime::PrimeGroup}; +use multiexp::BatchVerifier; -use crate::{Generators, cross_group::DLEqError}; +use crate::{Generators, cross_group::{DLEqError, aos::{Re, Aos}}}; #[cfg(feature = "serialize")] use std::io::{Read, Write}; #[cfg(feature = "serialize")] use crate::cross_group::read_point; -pub trait RingSignature: Sized { - type Context; +pub(crate) enum BitSignature { + ConciseLinear, + EfficientLinear +} - const LEN: usize; +impl BitSignature { + pub(crate) const fn to_u8(&self) -> u8 { + match self { + BitSignature::ConciseLinear => 0, + BitSignature::EfficientLinear => 1 + } + } - fn prove( - rng: &mut R, - transcript: T, - generators: (Generators, Generators), - ring: &[(G0, G1)], - actual: usize, - blinding_key: (G0::Scalar, G1::Scalar) - ) -> Self; + pub(crate) const fn from(algorithm: u8) -> BitSignature { + match algorithm { + 0 => BitSignature::ConciseLinear, + 1 => BitSignature::EfficientLinear, + _ => panic!("Unknown algorithm") + } + } - fn verify( - &self, - rng: &mut R, - transcript: T, - generators: (Generators, Generators), - context: &mut Self::Context, - ring: &[(G0, G1)] - ) -> Result<(), DLEqError>; + pub(crate) const fn bits(&self) -> usize { + match self { + BitSignature::ConciseLinear => 2, + BitSignature::EfficientLinear => 1 + } + } - #[cfg(feature = "serialize")] - fn serialize(&self, w: &mut W) -> std::io::Result<()>; - #[cfg(feature = "serialize")] - fn deserialize(r: &mut R) -> std::io::Result; + pub(crate) const fn ring_len(&self) -> usize { + 2_usize.pow(self.bits() as u32) + } + + fn aos_form(&self) -> Re { + match self { + BitSignature::ConciseLinear => Re::e_default(), + BitSignature::EfficientLinear => Re::R_default() + } + } } #[derive(Clone, PartialEq, Eq, Debug)] -pub(crate) struct Bits> { +pub(crate) struct Bits< + G0: PrimeGroup, + G1: PrimeGroup, + const SIGNATURE: u8, + const RING_LEN: usize +> { pub(crate) commitments: (G0, G1), - signature: RING + signature: Aos } -impl> Bits - where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { +impl< + G0: PrimeGroup, + G1: PrimeGroup, + const SIGNATURE: u8, + const RING_LEN: usize +> 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.domain_separate(b"bits"); + transcript.append_message(b"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); + let mut res = vec![commitments; RING_LEN]; + for i in 1 .. RING_LEN { + 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 { + for _ in 0 .. BitSignature::from(SIGNATURE).bits() { pow_2.0 = pow_2.0.double(); pow_2.1 = pow_2.1.double(); } @@ -84,20 +100,24 @@ impl> Bits 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); + let signature = Aos::prove( + rng, + transcript.clone(), + generators, + &Self::ring(*pow_2, commitments), + usize::from(bits), + blinding_key, + BitSignature::from(SIGNATURE).aos_form() + ); Self::shift(pow_2); Bits { commitments, signature } @@ -108,18 +128,17 @@ impl> Bits, Generators), - context: &mut RING::Context, + batch: &mut (BatchVerifier<(), G0>, BatchVerifier<(), G1>), 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, + batch, &Self::ring(*pow_2, self.commitments) )?; @@ -135,7 +154,7 @@ impl> Bits(r: &mut Re) -> std::io::Result { - Ok(Bits { commitments: (read_point(r)?, read_point(r)?), signature: RING::deserialize(r)? }) + pub(crate) fn deserialize(r: &mut R) -> std::io::Result { + Ok(Bits { commitments: (read_point(r)?, read_point(r)?), signature: Aos::deserialize(r)? }) } } diff --git a/crypto/dleq/src/cross_group/linear/concise.rs b/crypto/dleq/src/cross_group/linear/concise.rs deleted file mode 100644 index 21cf5652..00000000 --- a/crypto/dleq/src/cross_group/linear/concise.rs +++ /dev/null @@ -1,217 +0,0 @@ -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 deleted file mode 100644 index 696744d6..00000000 --- a/crypto/dleq/src/cross_group/linear/efficient.rs +++ /dev/null @@ -1,182 +0,0 @@ -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 deleted file mode 100644 index 20322079..00000000 --- a/crypto/dleq/src/cross_group/linear/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -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 57231b93..a0a74c5a 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -1,24 +1,28 @@ use thiserror::Error; + use rand_core::{RngCore, CryptoRng}; +use digest::Digest; use transcript::Transcript; -use group::{ff::{PrimeField, PrimeFieldBits}, prime::PrimeGroup}; +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}; pub(crate) mod schnorr; use schnorr::SchnorrPoK; -mod bits; -use bits::{RingSignature, Bits}; +pub(crate) mod aos; -pub mod linear; +mod bits; +use bits::{BitSignature, Bits}; #[cfg(feature = "serialize")] -use std::io::Read; +use std::io::{Read, Write}; #[cfg(feature = "serialize")] pub(crate) fn read_point(r: &mut R) -> std::io::Result { @@ -49,25 +53,48 @@ pub enum DLEqError { pub struct DLEqProof< G0: PrimeGroup, G1: PrimeGroup, - RING: RingSignature, - REM: RingSignature + const SIGNATURE: u8, + const RING_LEN: usize, + const REMAINDER_RING_LEN: usize > where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { - bits: Vec>, - remainder: Option>, + bits: Vec>, + remainder: Option>, 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 +>; + + pub type EfficientLinearDLEq = DLEqProof< + G0, + G1, + { BitSignature::EfficientLinear.to_u8() }, + { BitSignature::EfficientLinear.ring_len() }, + 0 +>; + impl< G0: PrimeGroup, G1: PrimeGroup, - RING: RingSignature, - REM: RingSignature -> DLEqProof where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { - pub(crate) fn initialize_transcript( + const SIGNATURE: u8, + const RING_LEN: usize, + const REMAINDER_RING_LEN: usize +> DLEqProof where + G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { + + pub(crate) fn transcript( transcript: &mut T, generators: (Generators, Generators), keys: (G0, G1) ) { + transcript.domain_separate(b"cross_group_dleq"); generators.0.transcript(transcript); generators.1.transcript(transcript); transcript.domain_separate(b"points"); @@ -102,4 +129,214 @@ impl< res } + + fn prove_internal( + rng: &mut R, + transcript: &mut T, + generators: (Generators, Generators), + f: (G0::Scalar, G1::Scalar) + ) -> (Self, (G0::Scalar, G1::Scalar)) { + Self::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 capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); + let bits_per_group = BitSignature::from(SIGNATURE).bits(); + + let mut pow_2 = (generators.0.primary, generators.1.primary); + + let raw_bits = f.0.to_le_bits(); + let mut bits = Vec::with_capacity(capacity); + let mut these_bits: u8 = 0; + for (i, bit) in raw_bits.iter().enumerate() { + if i == capacity { + break; + } + + let bit = *bit as u8; + debug_assert_eq!(bit | 1, 1); + + // Accumulate this bit + these_bits |= bit << (i % bits_per_group); + if (i % bits_per_group) == (bits_per_group - 1) { + let last = i == (capacity - 1); + let blinding_key = blinding_key(&mut *rng, last); + bits.push( + Bits::prove( + &mut *rng, + transcript, + generators, + i / bits_per_group, + &mut pow_2, + these_bits, + blinding_key + ) + ); + these_bits = 0; + } + } + debug_assert_eq!(bits.len(), capacity / bits_per_group); + + let mut remainder = None; + if capacity != ((capacity / bits_per_group) * bits_per_group) { + let blinding_key = blinding_key(&mut *rng, true); + remainder = Some( + Bits::prove( + &mut *rng, + transcript, + generators, + capacity / bits_per_group, + &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 = usize::try_from( + G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY) + ).unwrap(); + let bits_per_group = BitSignature::from(SIGNATURE).bits(); + let has_remainder = (capacity % bits_per_group) != 0; + + // These shouldn't be possible, as locally created and deserialized proofs should be properly + // formed in these regards, yet it doesn't hurt to check and would be problematic if true + if (self.bits.len() != (capacity / bits_per_group)) || ( + (self.remainder.is_none() && has_remainder) || (self.remainder.is_some() && !has_remainder) + ) { + return Err(DLEqError::InvalidProofLength); + } + + let keys = self.reconstruct_keys(); + Self::transcript(transcript, generators, keys); + + let batch_capacity = match BitSignature::from(SIGNATURE) { + BitSignature::ConciseLinear => 3, + BitSignature::EfficientLinear => (self.bits.len() + 1) * 3 + }; + let mut batch = (BatchVerifier::new(batch_capacity), BatchVerifier::new(batch_capacity)); + + self.poks.0.verify(&mut *rng, transcript, generators.0.primary, keys.0, &mut batch.0); + self.poks.1.verify(&mut *rng, transcript, generators.1.primary, keys.1, &mut batch.1); + + 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 let Some(bit) = &self.remainder { + bit.verify(&mut *rng, transcript, generators, &mut batch, self.bits.len(), &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)?; + } + 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 = usize::try_from( + G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY) + ).unwrap(); + let bits_per_group = BitSignature::from(SIGNATURE).bits(); + + let mut bits = Vec::with_capacity(capacity / bits_per_group); + for _ in 0 .. (capacity / bits_per_group) { + bits.push(Bits::deserialize(r)?); + } + + let mut remainder = None; + if (capacity % bits_per_group) != 0 { + remainder = Some(Bits::deserialize(r)?); + } + + Ok( + DLEqProof { + bits, + remainder, + poks: (SchnorrPoK::deserialize(r)?, SchnorrPoK::deserialize(r)?) + } + ) + } } diff --git a/crypto/dleq/src/cross_group/schnorr.rs b/crypto/dleq/src/cross_group/schnorr.rs index cbd60aa6..c996f971 100644 --- a/crypto/dleq/src/cross_group/schnorr.rs +++ b/crypto/dleq/src/cross_group/schnorr.rs @@ -2,7 +2,8 @@ use rand_core::{RngCore, CryptoRng}; use transcript::Transcript; -use group::{ff::Field, prime::PrimeGroup}; +use group::{ff::{Field, PrimeFieldBits}, prime::PrimeGroup}; +use multiexp::BatchVerifier; use crate::challenge; @@ -20,7 +21,7 @@ pub(crate) struct SchnorrPoK { s: G::Scalar } -impl SchnorrPoK { +impl SchnorrPoK where G::Scalar: PrimeFieldBits { // Not hram due to the lack of m #[allow(non_snake_case)] fn hra(transcript: &mut T, generator: G, R: G, A: G) -> G::Scalar { @@ -46,16 +47,23 @@ impl SchnorrPoK { } } - #[must_use] - pub(crate) fn verify( + pub(crate) fn verify( &self, + rng: &mut R, transcript: &mut T, generator: G, - public_key: G - ) -> bool { - (generator * self.s) == ( - self.R + (public_key * Self::hra(transcript, generator, self.R, public_key)) - ) + public_key: G, + batch: &mut BatchVerifier<(), G> + ) { + batch.queue( + rng, + (), + [ + (-self.s, generator), + (G::Scalar::one(), self.R), + (Self::hra(transcript, generator, self.R, public_key), public_key) + ] + ); } #[cfg(feature = "serialize")] diff --git a/crypto/dleq/src/tests/cross_group/linear/aos.rs b/crypto/dleq/src/tests/cross_group/aos.rs similarity index 92% rename from crypto/dleq/src/tests/cross_group/linear/aos.rs rename to crypto/dleq/src/tests/cross_group/aos.rs index 9662ab52..efd37026 100644 --- a/crypto/dleq/src/tests/cross_group/linear/aos.rs +++ b/crypto/dleq/src/tests/cross_group/aos.rs @@ -5,7 +5,7 @@ use group::{ff::Field, Group}; use multiexp::BatchVerifier; use crate::{ - cross_group::linear::aos::{Re, Aos}, + cross_group::aos::{Re, Aos}, tests::cross_group::{G0, G1, transcript, generators} }; @@ -21,6 +21,8 @@ fn test_aos(default: Re) { let generators = generators(); let mut ring_keys = [(::Scalar::zero(), ::Scalar::zero()); RING_LEN]; + // Side-effect of G0 being a type-alias with identity() deprecated + #[allow(deprecated)] let mut ring = [(G0::identity(), G1::identity()); RING_LEN]; for i in 0 .. RING_LEN { ring_keys[i] = ( @@ -58,6 +60,7 @@ fn test_aos_e() { test_aos::<4>(Re::e_default()); } +#[allow(non_snake_case)] #[test] fn test_aos_R() { // Batch verification appreciates the longer vectors, which means not batching bits diff --git a/crypto/dleq/src/tests/cross_group/linear/concise.rs b/crypto/dleq/src/tests/cross_group/linear/concise.rs deleted file mode 100644 index 1fbe49a7..00000000 --- a/crypto/dleq/src/tests/cross_group/linear/concise.rs +++ /dev/null @@ -1,98 +0,0 @@ -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 deleted file mode 100644 index bafc4902..00000000 --- a/crypto/dleq/src/tests/cross_group/linear/efficient.rs +++ /dev/null @@ -1,66 +0,0 @@ -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 deleted file mode 100644 index 5603b63d..00000000 --- a/crypto/dleq/src/tests/cross_group/linear/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -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 9557127d..38ef0341 100644 --- a/crypto/dleq/src/tests/cross_group/mod.rs +++ b/crypto/dleq/src/tests/cross_group/mod.rs @@ -1,26 +1,33 @@ -mod scalar; -mod schnorr; - use hex_literal::hex; -use rand_core::OsRng; +use rand_core::{RngCore, OsRng}; use ff::{Field, PrimeField}; use group::{Group, GroupEncoding}; +use blake2::{Digest, Blake2b512}; + use k256::{Scalar, ProjectivePoint}; use dalek_ff_group::{self as dfg, EdwardsPoint, CompressedEdwardsY}; use transcript::RecommendedTranscript; -use crate::{Generators, cross_group::linear::EfficientDLEq}; +use crate::{ + Generators, + cross_group::{scalar::mutual_scalar_from_bytes, EfficientLinearDLEq, ConciseLinearDLEq} +}; -mod linear; +mod scalar; +mod schnorr; +mod aos; + +type G0 = ProjectivePoint; +type G1 = EdwardsPoint; pub(crate) fn transcript() -> RecommendedTranscript { RecommendedTranscript::new(b"Cross-Group DLEq Proof Test") } -pub(crate) fn generators() -> (Generators, Generators) { +pub(crate) fn generators() -> (Generators, Generators) { ( Generators::new( ProjectivePoint::GENERATOR, @@ -38,6 +45,66 @@ pub(crate) fn generators() -> (Generators, Generators { + 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 = $type::::deserialize(&mut std::io::Cursor::new(&buf)).unwrap(); + assert_eq!(proof, deserialized); + } + } +} + +macro_rules! test_dleq { + ($name: ident, $type: ident) => { + #[test] + fn $name() { + let generators = generators(); + + for i in 0 .. 1 { + let (proof, keys) = if i == 0 { + let mut seed = [0; 32]; + OsRng.fill_bytes(&mut seed); + + $type::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 = $type::prove_without_bias( + &mut OsRng, + &mut transcript(), + generators, + key + ); + res.is_none() + } {} + let res = res.unwrap(); + assert_eq!(key, res.1.0); + res + }; + + verify_and_deserialize!($type, proof, generators, keys); + } + } + } +} + +test_dleq!(test_efficient_linear_dleq, EfficientLinearDLEq); +test_dleq!(test_concise_linear_dleq, ConciseLinearDLEq); + #[test] fn test_rejection_sampling() { let mut pow_2 = Scalar::one(); @@ -46,7 +113,8 @@ fn test_rejection_sampling() { } assert!( - EfficientDLEq::prove_without_bias( + // Either would work + EfficientLinearDLEq::prove_without_bias( &mut OsRng, &mut RecommendedTranscript::new(b""), generators(), @@ -54,3 +122,24 @@ fn test_rejection_sampling() { ).is_none() ); } + +#[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); + // This will ignore any unused bits, ensuring every remaining one is set + let keys = mutual_scalar_from_bytes(&[0xFF; 32]); + assert_eq!(keys.0 + Scalar::one(), Scalar::from(2u64).pow_vartime(&[255])); + assert_eq!(keys.0, keys.1); + + let (proof, res) = ConciseLinearDLEq::prove_without_bias( + &mut OsRng, + &mut transcript(), + generators, + keys.0 + ).unwrap(); + assert_eq!(keys, res); + + verify_and_deserialize!(ConciseLinearDLEq, proof, generators, keys); +} diff --git a/crypto/dleq/src/tests/cross_group/schnorr.rs b/crypto/dleq/src/tests/cross_group/schnorr.rs index 8298afda..857044db 100644 --- a/crypto/dleq/src/tests/cross_group/schnorr.rs +++ b/crypto/dleq/src/tests/cross_group/schnorr.rs @@ -1,23 +1,30 @@ use rand_core::OsRng; -use group::{ff::Field, prime::PrimeGroup}; +use group::{ff::{Field, PrimeFieldBits}, prime::PrimeGroup}; +use multiexp::BatchVerifier; use transcript::RecommendedTranscript; use crate::cross_group::schnorr::SchnorrPoK; -fn test_schnorr() { +fn test_schnorr() where G::Scalar: PrimeFieldBits { let private = G::Scalar::random(&mut OsRng); let transcript = RecommendedTranscript::new(b"Schnorr Test"); - assert!( - SchnorrPoK::prove( - &mut OsRng, - &mut transcript.clone(), - G::generator(), - private - ).verify(&mut transcript.clone(), G::generator(), G::generator() * private) + let mut batch = BatchVerifier::new(3); + SchnorrPoK::prove( + &mut OsRng, + &mut transcript.clone(), + G::generator(), + private + ).verify( + &mut OsRng, + &mut transcript.clone(), + G::generator(), + G::generator() * private, + &mut batch ); + assert!(batch.verify_vartime()); } #[test]