From 2e3585421555d4f9feb1354f2e518bdb3a5ce82a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 2 Jul 2022 02:46:40 -0400 Subject: [PATCH] Rewrite the cross-group DLEq API to not allow proving for biased scalars --- crypto/dleq/Cargo.toml | 5 ++ crypto/dleq/src/cross_group/mod.rs | 48 +++++++++--- crypto/dleq/src/tests/cross_group/mod.rs | 94 ++++++++++++++++++------ 3 files changed, 115 insertions(+), 32 deletions(-) diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index 27a806e8..6d9e80a7 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -10,6 +10,8 @@ edition = "2021" thiserror = "1" rand_core = "0.6" +digest = "0.10" + subtle = "2.4" transcript = { package = "flexible-transcript", path = "../transcript", version = "0.1" } @@ -21,6 +23,9 @@ multiexp = { path = "../multiexp" } [dev-dependencies] hex-literal = "0.3" + +blake2 = "0.10" + k256 = { version = "0.11", features = ["arithmetic", "bits"] } dalek-ff-group = { path = "../dalek-ff-group" } diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index 7509ae6c..687f5b41 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -1,6 +1,8 @@ use thiserror::Error; use rand_core::{RngCore, CryptoRng}; +use digest::Digest; + use subtle::{Choice, ConditionallySelectable}; use transcript::Transcript; @@ -182,21 +184,12 @@ impl DLEqProof transcript.append_message(b"commitment_1", commitments.1.to_bytes().as_ref()); } - /// Prove the cross-Group Discrete Log Equality for the points derived from the provided Scalar. - /// Since DLEq is proven for the same Scalar in both fields, and the provided Scalar may not be - /// valid in the other Scalar field, the Scalar is normalized as needed and the normalized forms - /// are returned. These are the actually equal discrete logarithms. The passed in Scalar is - /// solely to enable various forms of Scalar generation, such as deterministic schemes - pub fn prove( + fn prove_internal( rng: &mut R, transcript: &mut T, generators: (Generators, Generators), - f: G0::Scalar + f: (G0::Scalar, G1::Scalar) ) -> (Self, (G0::Scalar, G1::Scalar)) { - // At least one bit will be dropped from either field element, making it irrelevant which one - // we get a random element in - let f = scalar_normalize::<_, G1::Scalar>(f); - Self::initialize_transcript( transcript, generators, @@ -270,6 +263,39 @@ impl DLEqProof (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, + Self::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, diff --git a/crypto/dleq/src/tests/cross_group/mod.rs b/crypto/dleq/src/tests/cross_group/mod.rs index fd4b3e9b..f39aa4ce 100644 --- a/crypto/dleq/src/tests/cross_group/mod.rs +++ b/crypto/dleq/src/tests/cross_group/mod.rs @@ -2,23 +2,26 @@ mod scalar; mod schnorr; use hex_literal::hex; -use rand_core::OsRng; +use rand_core::{RngCore, OsRng}; -use ff::Field; +use ff::{Field, PrimeField}; use group::{Group, GroupEncoding}; use k256::{Scalar, ProjectivePoint}; -use dalek_ff_group::{EdwardsPoint, CompressedEdwardsY}; +use dalek_ff_group::{self as dfg, EdwardsPoint, CompressedEdwardsY}; + +use blake2::{Digest, Blake2b512}; use transcript::RecommendedTranscript; use crate::{Generators, cross_group::DLEqProof}; -#[test] -fn test_dleq() { - let transcript = || RecommendedTranscript::new(b"Cross-Group DLEq Proof Test"); +fn transcript() -> RecommendedTranscript { + RecommendedTranscript::new(b"Cross-Group DLEq Proof Test") +} - let generators = ( +fn generators() -> (Generators, Generators) { + ( Generators::new( ProjectivePoint::GENERATOR, ProjectivePoint::from_bytes( @@ -32,23 +35,72 @@ fn test_dleq() { hex!("8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94") ).decompress().unwrap() ) + ) +} + +#[test] +fn test_rejection_sampling() { + let mut pow_2 = Scalar::one(); + for _ in 0 .. dfg::Scalar::CAPACITY { + pow_2 = pow_2.double(); + } + + assert!( + DLEqProof::prove_without_bias( + &mut OsRng, + &mut RecommendedTranscript::new(b""), + generators(), + pow_2 + ).is_none() ); +} - let key = Scalar::random(&mut OsRng); - let (proof, keys) = DLEqProof::prove(&mut OsRng, &mut transcript(), generators, key); +#[test] +fn test_dleq() { + let generators = generators(); - 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); + for i in 0 .. 2 { + let (proof, keys) = if i == 0 { + let mut seed = [0; 32]; + OsRng.fill_bytes(&mut seed); - #[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(); + 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(); + } } }