From 9f8d1aa220ec389a8516541afa6187f7db04b289 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 7 Jul 2022 00:26:34 -0400 Subject: [PATCH] Clean AOS signatures --- crypto/dleq/Cargo.toml | 2 - crypto/dleq/src/cross_group/linear/aos.rs | 305 ++++++++---------- .../dleq/src/tests/cross_group/linear/aos.rs | 65 ++++ 3 files changed, 191 insertions(+), 181 deletions(-) create mode 100644 crypto/dleq/src/tests/cross_group/linear/aos.rs diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index 5ef85242..8943544c 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -12,8 +12,6 @@ rand_core = "0.6" digest = "0.10" -subtle = "2.4" - transcript = { package = "flexible-transcript", path = "../transcript", version = "0.1" } ff = "0.12" diff --git a/crypto/dleq/src/cross_group/linear/aos.rs b/crypto/dleq/src/cross_group/linear/aos.rs index ada2309a..8d2a8c67 100644 --- a/crypto/dleq/src/cross_group/linear/aos.rs +++ b/crypto/dleq/src/cross_group/linear/aos.rs @@ -1,7 +1,5 @@ use rand_core::{RngCore, CryptoRng}; -use subtle::{ConstantTimeEq, ConditionallySelectable}; - use transcript::Transcript; use group::{ff::{Field, PrimeFieldBits}, prime::PrimeGroup}; @@ -10,7 +8,7 @@ use multiexp::BatchVerifier; use crate::{ Generators, - cross_group::{DLEqError, scalar::{scalar_convert, mutual_scalar_from_bytes}, bits::RingSignature} + cross_group::{DLEqError, scalar::{scalar_convert, mutual_scalar_from_bytes}} }; #[cfg(feature = "serialize")] @@ -20,48 +18,33 @@ 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)] +#[allow(non_camel_case_types)] #[derive(Clone, PartialEq, Eq, Debug)] -pub struct ClassicAos { +pub(crate) enum Re { + R(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, + e(G0::Scalar) +} + +impl Re { + #[allow(non_snake_case)] + pub(crate) fn R_default() -> Re { + Re::R(G0::identity(), G1::identity()) + } + + pub(crate) fn e_default() -> Re { + Re::e(G0::Scalar::zero()) + } +} + +#[allow(non_snake_case)] +#[derive(Clone, PartialEq, Eq, Debug)] +pub(crate) struct Aos { + Re_0: Re, s: [(G0::Scalar, G1::Scalar); RING_LEN] } @@ -69,106 +52,24 @@ impl< G0: PrimeGroup, G1: PrimeGroup, const RING_LEN: usize -> RingSignature for ClassicAos - where G0::Scalar: PrimeFieldBits, G1::Scalar: PrimeFieldBits { - type Context = (); +> 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.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()) + } - const LEN: usize = RING_LEN; - - fn prove( - rng: &mut R, - transcript: T, + #[allow(non_snake_case)] + fn R( 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 } + 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))) } - 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), @@ -178,25 +79,34 @@ impl MultiexpAos { ) -> (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>); + #[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)) + } - const LEN: usize = 2; - - fn prove( + #[allow(non_snake_case)] + pub(crate) fn prove( rng: &mut R, transcript: T, generators: (Generators, Generators), ring: &[(G0, G1)], actual: usize, - blinding_key: (G0::Scalar, G1::Scalar) + blinding_key: (G0::Scalar, G1::Scalar), + mut Re_0: Re ) -> 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 + // 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)); + debug_assert_eq!(RING_LEN, ring.len()); + + 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)] @@ -204,75 +114,112 @@ impl RingSignature for MultiexpAos { *R0_0 = R.0; *R1_0 = R.1 }, + Re::e(ref mut e_0) => *e_0 = e.0 + } } // 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); + 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 = calculate_R(generators, s[i], ring[i], e); + R = Self::R(generators, s[i], ring[i], e); } - MultiexpAos { R_0, s } + Aos { Re_0, s } } - fn verify( + // Assumes the ring has already been transcripted in some form. Critically insecure if it hasn't + pub(crate) fn verify( &self, rng: &mut R, transcript: T, generators: (Generators, Generators), - batch: &mut Self::Context, + batch: &mut (BatchVerifier<(), G0>, BatchVerifier<(), G1>), 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); - } + debug_assert!((RING_LEN == 2) || (RING_LEN == 4)); + debug_assert_eq!(RING_LEN, ring.len()); - 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); + match self.Re_0 { + Re::R(R0_0, R1_0) => { + let mut e = Self::nonces(transcript.clone(), (R0_0, R1_0)); + for i in 0 .. (RING_LEN - 1) { + e = Self::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(), R0_0)); + statements.1.push((-G1::Scalar::one(), R1_0)); + batch.0.queue(&mut *rng, (), statements.0); + batch.1.queue(&mut *rng, (), statements.1); + }, + + Re::e(e_0) => { + let e_0 = (e_0, scalar_convert(e_0).ok_or(DLEqError::InvalidChallenge)?); + let mut e = None; + for i in 0 .. RING_LEN { + 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() { + Err(DLEqError::InvalidProof)?; + } + } + } 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 { + pub(crate) fn serialize(&self, w: &mut W) -> std::io::Result<()> { + match self.Re_0 { + Re::R(R0, R1) => { + w.write_all(R0.to_bytes().as_ref())?; + w.write_all(R1.to_bytes().as_ref())?; + }, + Re::e(e) => w.write_all(e.to_repr().as_ref())? + } + + for i in 0 .. RING_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 { + pub(crate) fn deserialize(r: &mut R, mut Re_0: Re) -> std::io::Result { + match Re_0 { + Re::R(ref mut R0, ref mut R1) => { *R0 = read_point(r)?; *R1 = read_point(r)? }, + Re::e(ref mut e) => *e = read_scalar(r)? + } + + let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); RING_LEN]; + for i in 0 .. RING_LEN { s[i] = (read_scalar(r)?, read_scalar(r)?); } - Ok(MultiexpAos { R_0, s }) + + Ok(Aos { Re_0, s }) } } diff --git a/crypto/dleq/src/tests/cross_group/linear/aos.rs b/crypto/dleq/src/tests/cross_group/linear/aos.rs new file mode 100644 index 00000000..9662ab52 --- /dev/null +++ b/crypto/dleq/src/tests/cross_group/linear/aos.rs @@ -0,0 +1,65 @@ +use rand_core::OsRng; + +use group::{ff::Field, Group}; + +use multiexp::BatchVerifier; + +use crate::{ + cross_group::linear::aos::{Re, Aos}, + tests::cross_group::{G0, G1, transcript, generators} +}; + +#[cfg(feature = "serialize")] +fn test_aos_serialization(proof: Aos, Re_0: Re) { + let mut buf = vec![]; + proof.serialize(&mut buf).unwrap(); + let deserialized = Aos::deserialize(&mut std::io::Cursor::new(buf), Re_0).unwrap(); + assert_eq!(proof, deserialized); +} + +fn test_aos(default: Re) { + let generators = generators(); + + let mut ring_keys = [(::Scalar::zero(), ::Scalar::zero()); RING_LEN]; + let mut ring = [(G0::identity(), G1::identity()); RING_LEN]; + for i in 0 .. RING_LEN { + ring_keys[i] = ( + ::Scalar::random(&mut OsRng), + ::Scalar::random(&mut OsRng) + ); + ring[i] = (generators.0.alt * ring_keys[i].0, generators.1.alt * ring_keys[i].1); + } + + for actual in 0 .. RING_LEN { + let proof = Aos::<_, _, RING_LEN>::prove( + &mut OsRng, + transcript(), + generators, + &ring, + actual, + ring_keys[actual], + default.clone() + ); + + let mut batch = (BatchVerifier::new(0), BatchVerifier::new(0)); + proof.verify(&mut OsRng, transcript(), generators, &mut batch, &ring).unwrap(); + // For e, these should have nothing. For R, these should have 6 elements each which sum to 0 + assert!(batch.0.verify_vartime()); + assert!(batch.1.verify_vartime()); + + #[cfg(feature = "serialize")] + test_aos_serialization(proof, default.clone()); + } +} + +#[test] +fn test_aos_e() { + test_aos::<2>(Re::e_default()); + test_aos::<4>(Re::e_default()); +} + +#[test] +fn test_aos_R() { + // Batch verification appreciates the longer vectors, which means not batching bits + test_aos::<2>(Re::R_default()); +}