From a6ff0d079fa9a1e9276f5286a6d428b8f8201eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= Date: Fri, 26 Jan 2024 11:23:52 +0100 Subject: [PATCH] Compatible transcripts between dalek and arkworks!! --- examples/schnorr.rs | 12 ++++--- src/hash/legacy.rs | 4 +-- src/lib.rs | 1 + src/plugins/group/common.rs | 15 +++------ src/plugins/group/writer.rs | 44 ++++++++++++++++++++++--- src/plugins/tests.rs | 66 ++++++++++++++++++++++++++++++------- src/plugins/traits.rs | 10 +++--- src/safe.rs | 5 +-- 8 files changed, 116 insertions(+), 41 deletions(-) diff --git a/examples/schnorr.rs b/examples/schnorr.rs index bcb4a2d..3966fca 100644 --- a/examples/schnorr.rs +++ b/examples/schnorr.rs @@ -21,8 +21,8 @@ where IOPattern: GroupIOPattern, { fn add_schnorr_io(self) -> Self { - self.add_points(1, "P") - .add_points(1, "X") + self.add_points(1, "generator (P)") + .add_points(1, "public key (X)") .ratchet() .add_points(1, "commitment (K)") .challenge_scalars(1, "challenge (c)") @@ -41,11 +41,13 @@ fn keygen() -> (G::ScalarField, G) { /// The prove algorithm takes as input /// - the prover state `Arthur`, that has access to a random oracle `H` and can absorb/squeeze elements from the group `G`. +/// - The generator `P` in the group. /// - the secret key $x \in \mathbb{Z}_p$ /// It returns a zero-knowledge proof of knowledge of `x` as a sequence of bytes. #[allow(non_snake_case)] fn prove( - // the hash function `H` works over bytes, unless otherwise denoted with an additional type argument implementing `nimue::Unit`. + // the hash function `H` works over bytes. + // Algebraic hashes over a particular domain can be denoted with an additional type argument implementing `nimue::Unit`. arthur: &mut Arthur, // the generator P: G, @@ -55,7 +57,7 @@ fn prove( where H: DuplexHash, G: CurveGroup, - Arthur: FieldChallenges, + Arthur: GroupWriter, { // `Arthur` types implement a cryptographically-secure random number generator that is tied to the protocol transcript // and that can be accessed via the `rng()` funciton. @@ -109,7 +111,7 @@ where if P * r == K + X * c { Ok(()) } else { - Err(nimue::ProofError::InvalidProof) + Err(ProofError::InvalidProof) } // from here, another proof can be verified using the same merlin instance diff --git a/src/hash/legacy.rs b/src/hash/legacy.rs index 3caa37b..c1f7372 100644 --- a/src/hash/legacy.rs +++ b/src/hash/legacy.rs @@ -80,7 +80,7 @@ impl DigestBridge { let mut squeeze_hasher = D::new(); Digest::update(&mut squeeze_hasher, &Self::mask_squeeze_end()); Digest::update(&mut squeeze_hasher, &self.cv); - Digest::update(&mut squeeze_hasher, &byte_count.to_be_bytes()); + Digest::update(&mut squeeze_hasher, byte_count.to_be_bytes()); self.cv = Digest::finalize(squeeze_hasher); // set the sponge state in absorb mode @@ -171,7 +171,7 @@ impl DuplexHash for Di } else if let Mode::Squeeze(i) = self.mode { // Add the squeeze mask, current digest, and index let mut output_hasher_prefix = self.hasher.clone(); - Digest::update(&mut output_hasher_prefix, &i.to_be_bytes()); + Digest::update(&mut output_hasher_prefix, i.to_be_bytes()); let digest = output_hasher_prefix.finalize(); // Copy the digest into the output, and store the rest for later let chunk_len = usize::min(output.len(), Self::DIGEST_SIZE); diff --git a/src/lib.rs b/src/lib.rs index 2210737..4685518 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,6 +133,7 @@ mod iopattern; /// Verifier state and transcript deserialization. mod merlin; /// APIs for common zkp libraries. +#[cfg(any(feature = "ark", feature = "group"))] pub mod plugins; /// SAFE API. mod safe; diff --git a/src/plugins/group/common.rs b/src/plugins/group/common.rs index ea49612..c98bf58 100644 --- a/src/plugins/group/common.rs +++ b/src/plugins/group/common.rs @@ -5,18 +5,13 @@ use super::{FieldChallenges, FieldPublic}; use crate::plugins::bytes_uniform_modp; fn from_bytes_mod_order(bytes: &[u8]) -> F { - let two = F::ONE + F::ONE; - let basis = two.pow(&[64]); - let mut iterator = bytes.chunks_exact(8); - let mut acc = F::ZERO; + let basis = F::from(256); + let bytes = bytes.to_vec(); - while let Some(chunk) = iterator.next() { - let chunk = u64::from_be_bytes(chunk.try_into().unwrap()); - acc = acc * basis + F::from(chunk); + let mut acc = F::ZERO; + for byte in bytes { + acc = acc * basis + F::from(byte as u64); } - let reminder = iterator.remainder(); - let reminder = u64::from_be_bytes(reminder.try_into().unwrap()); - acc = acc * basis + F::from(reminder); acc } diff --git a/src/plugins/group/writer.rs b/src/plugins/group/writer.rs index 2de645e..657be3c 100644 --- a/src/plugins/group/writer.rs +++ b/src/plugins/group/writer.rs @@ -1,14 +1,48 @@ -use crate::DuplexHash; -use group::ff::PrimeField; +use group::{ff::PrimeField, Group, GroupEncoding}; use rand::{CryptoRng, RngCore}; -use super::{FieldPublic, FieldWriter}; -use crate::{Arthur, ProofResult}; +use super::{FieldPublic, FieldWriter, GroupPublic, GroupWriter}; +use crate::{Arthur, ByteTranscriptWriter, DuplexHash, ProofResult}; -impl FieldWriter for Arthur { +impl FieldWriter for Arthur +where + F: PrimeField, + H: DuplexHash, + R: RngCore + CryptoRng, +{ fn add_scalars(&mut self, input: &[F]) -> ProofResult<()> { let serialized = self.public_scalars(input); self.transcript.extend(serialized?); Ok(()) } } + +impl GroupPublic for Arthur +where + G: Group + GroupEncoding, + H: DuplexHash, + R: RngCore + CryptoRng, +{ + type Repr = Vec; + fn public_points(&mut self, input: &[G]) -> crate::ProofResult { + let mut buf = Vec::new(); + for p in input.iter() { + buf.extend_from_slice(&::to_bytes(p)); + } + self.add_bytes(&buf)?; + Ok(buf) + } +} + +impl GroupWriter for Arthur +where + G: Group + GroupEncoding, + H: DuplexHash, + R: RngCore + CryptoRng, +{ + fn add_points(&mut self, input: &[G]) -> crate::ProofResult<()> { + let serialized = self.public_points(input); + self.transcript.extend(serialized?); + Ok(()) + } +} diff --git a/src/plugins/tests.rs b/src/plugins/tests.rs index 63ab28a..045ead0 100644 --- a/src/plugins/tests.rs +++ b/src/plugins/tests.rs @@ -1,5 +1,9 @@ +use ark_ec::PrimeGroup; +use ark_serialize::CanonicalSerialize; +use group::{Group, GroupEncoding}; + use crate::hash::Keccak; -use crate::plugins; +use crate::{plugins, ByteIOPattern}; use crate::{ByteTranscript, DuplexHash, IOPattern}; fn group_iopattern() -> IOPattern @@ -11,7 +15,10 @@ where use plugins::group::{FieldIOPattern, GroupIOPattern}; IOPattern::new("github.com/mmaker/nimue") + .add_scalars(1, "com") + .challenge_bytes(16, "chal") .add_points(1, "com") + .challenge_bytes(16, "chal") .challenge_scalars(1, "chal") } @@ -24,12 +31,14 @@ where use plugins::ark::{FieldIOPattern, GroupIOPattern}; IOPattern::new("github.com/mmaker/nimue") + .add_scalars(1, "com") + .challenge_bytes(16, "chal") .add_points(1, "com") + .challenge_bytes(16, "chal") .challenge_scalars(1, "chal") } -/// Compatibility betweek arkworks and dalek can only be tested when handling scalars. -/// In fact, arkworks does not yet implement ristretto points as per `curve25519_dalek::ristretto::Ristretto` +// Check that the transcripts generated using the Group trait can be compatible with transcripts generated using dalek. #[test] fn test_compatible_ark_dalek() { type ArkG = ark_curve25519::EdwardsProjective; @@ -39,23 +48,56 @@ fn test_compatible_ark_dalek() { type GroupF = curve25519_dalek::scalar::Scalar; let ark_scalar = ArkF::from(0x42); let dalek_scalar = GroupF::from(0x42u64); + // ***IMPORTANT*** + // Looks like dalek and arkworks use different generator points. + let ark_generator = ArkG::generator(); + let dalek_generator = -GroupG::generator(); + + // **basic checks** + // Check point encoding is the same in both libraries. + let mut ark_generator_bytes = Vec::new(); + ark_generator.serialize_compressed(&mut ark_generator_bytes).unwrap(); + let dalek_generator_bytes = ::to_bytes(&dalek_generator); + assert_eq!(&ark_generator_bytes, &dalek_generator_bytes); + // Check scalar encoding is the same in both libraries. + let mut ark_scalar_bytes = Vec::new(); + ark_scalar.serialize_compressed(&mut ark_scalar_bytes).unwrap(); + let dalek_scalar_bytes = dalek_scalar.to_bytes(); + assert_eq!(&ark_scalar_bytes, &dalek_scalar_bytes); + + let ark_point = ark_generator * ark_scalar; + let dalek_point = dalek_generator * dalek_scalar; let ark_io = ark_iopattern::(); let dalek_io = group_iopattern::(); + let mut ark_chal = [0u8; 16]; + let mut dalek_chal = [0u8; 16]; + // Check that the IO Patterns are the same. + let mut ark_prover = ark_io.to_arthur(); + let mut dalek_prover = dalek_io.to_arthur(); assert_eq!(ark_io.as_bytes(), dalek_io.as_bytes()); - let mut ark_challenges = [0u8; 16]; - let mut ark_prover = ark_io.to_arthur(); + // Check that scalars absorption leads to the same transcript. plugins::ark::FieldWriter::add_scalars(&mut ark_prover, &[ark_scalar]).unwrap(); - ark_prover - .fill_challenge_bytes(&mut ark_challenges) - .unwrap(); - - let mut dalek_chal = [0u8; 16]; - let mut dalek_prover = dalek_io.to_arthur(); + ark_prover.fill_challenge_bytes(&mut ark_chal).unwrap(); plugins::group::FieldWriter::add_scalars(&mut dalek_prover, &[dalek_scalar]).unwrap(); dalek_prover.fill_challenge_bytes(&mut dalek_chal).unwrap(); + assert_eq!(ark_chal, dalek_chal); + + // Check that points absorption leads to the same transcript. + plugins::ark::GroupWriter::add_points(&mut ark_prover, &[ark_point]).unwrap(); + ark_prover.fill_challenge_bytes(&mut ark_chal).unwrap(); + plugins::group::GroupWriter::add_points(&mut dalek_prover, &[dalek_point]).unwrap(); + dalek_prover.fill_challenge_bytes(&mut dalek_chal).unwrap(); + assert_eq!(ark_chal, dalek_chal); + + // Check that scalars challenges are interpreted in the same way from bytes. + let [ark_chal_scalar]: [ArkF; 1] = plugins::ark::FieldChallenges::challenge_scalars(&mut ark_prover).unwrap(); + let [dalek_chal_scalar]: [GroupF; 1] = plugins::group::FieldChallenges::challenge_scalars(&mut dalek_prover).unwrap(); + let mut ark_scalar_bytes = Vec::new(); + ark_chal_scalar.serialize_compressed(&mut ark_scalar_bytes).unwrap(); + let dalek_scalar_bytes = dalek_chal_scalar.to_bytes(); + assert_eq!(&ark_scalar_bytes, &dalek_scalar_bytes); - assert_eq!(ark_challenges, dalek_chal); } diff --git a/src/plugins/traits.rs b/src/plugins/traits.rs index 6d7c215..78ca5a9 100644 --- a/src/plugins/traits.rs +++ b/src/plugins/traits.rs @@ -8,7 +8,7 @@ macro_rules! field_traits { } pub trait FieldChallenges { - fn fill_challenge_scalars(&mut self, output: &mut [F]) -> crate::ProofResult<()>; + fn fill_challenge_scalars(&mut self, output: &mut [F]) -> $crate::ProofResult<()>; fn challenge_scalars(&mut self) -> crate::ProofResult<[F; N]> { let mut output = [F::default(); N]; @@ -44,13 +44,13 @@ macro_rules! group_traits { } pub trait GroupWriter: FieldWriter<$ScalarField> { - fn add_points(&mut self, input: &[G]) -> crate::ProofResult<()>; + fn add_points(&mut self, input: &[G]) -> $crate::ProofResult<()>; } pub trait GroupReader: FieldReader<$ScalarField> { - fn fill_next_points(&mut self, output: &mut [G]) -> crate::ProofResult<()>; + fn fill_next_points(&mut self, output: &mut [G]) -> $crate::ProofResult<()>; - fn next_points(&mut self) -> crate::ProofResult<[G; N]> { + fn next_points(&mut self) -> $crate::ProofResult<[G; N]> { let mut output = [G::default(); N]; self.fill_next_points(&mut output).map(|()| output) } @@ -58,7 +58,7 @@ macro_rules! group_traits { pub trait GroupPublic { type Repr; - fn public_points(&mut self, input: &[G]) -> crate::ProofResult; + fn public_points(&mut self, input: &[G]) -> $crate::ProofResult; } }; } diff --git a/src/safe.rs b/src/safe.rs index eb7b50d..6f9a530 100644 --- a/src/safe.rs +++ b/src/safe.rs @@ -103,9 +103,10 @@ impl> Safe { Some(op) => { self.stack.clear(); Err(format!( - "Invalid tag. Got {:?}, expected {:?}", + "Invalid tag. Got {:?}, expected {:?}. The stack remaining is: {:?}", Op::Squeeze(output.len()), - op + op, + self.stack ) .into()) }