diff --git a/Cargo.toml b/Cargo.toml index d38c9ce..22eba30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ zerocopy = { version = "0.8", default-features = false } zeroize = { version = "1.8.1", default-features = false, features = ["alloc"] } hashbrown = { version = "0.15", default-features = false } ahash = { version = "0.8", default-features = false } +spongefish = { path="/Users/maker/Code/spongefish/spongefish" } [dev-dependencies] bls12_381 = "0.8.0" @@ -53,6 +54,11 @@ p256 = { version = "0.13", features = ["arithmetic"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" sha2 = "0.10" +spongefish = { path="/Users/maker/Code/spongefish/spongefish", features = ["curve25519-dalek"] } + +[[example]] +name = "schnorr" +required-features = ["spongefish/curve25519-dalek"] [[bench]] name = "msm" @@ -61,4 +67,3 @@ harness = false [profile.dev] # Makes tests run much faster at the cost of slightly longer builds and worse debug info. opt-level = 1 - diff --git a/examples/simple_composition.rs b/examples/simple_composition.rs index 4256ee8..d59a1ec 100644 --- a/examples/simple_composition.rs +++ b/examples/simple_composition.rs @@ -5,10 +5,9 @@ use curve25519_dalek::scalar::Scalar; use group::Group; use rand::rngs::OsRng; use sigma_proofs::{ - codec::Shake128DuplexSponge, composition::{ComposedRelation, ComposedWitness}, errors::Error, - LinearRelation, Nizk, + LinearRelation, }; type G = RistrettoPoint; @@ -56,7 +55,7 @@ fn prove(P1: G, x2: Scalar, H: G) -> ProofResult> { ComposedWitness::Simple(vec![Scalar::from(0u64)]), ComposedWitness::Simple(vec![x2]), ]); - let nizk = Nizk::<_, Shake128DuplexSponge>::new(b"or_proof_example", instance); + let nizk = instance.into_nizk(b"or_proof_example"); nizk.prove_batchable(&witness, &mut OsRng) } @@ -65,7 +64,7 @@ fn prove(P1: G, x2: Scalar, H: G) -> ProofResult> { #[allow(non_snake_case)] fn verify(P1: G, P2: G, Q: G, H: G, proof: &[u8]) -> ProofResult<()> { let protocol = create_relation(P1, P2, Q, H); - let nizk = Nizk::<_, Shake128DuplexSponge>::new(b"or_proof_example", protocol); + let nizk = protocol.into_nizk(b"or_proof_example"); nizk.verify_batchable(proof) } diff --git a/src/codec.rs b/src/codec.rs index 40a14e7..b8bec84 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -25,9 +25,6 @@ pub trait Codec { /// Generates an empty codec that can be identified by a domain separator. fn new(protocol_identifier: &[u8], session_identifier: &[u8], instance_label: &[u8]) -> Self; - /// Allows for precomputed initialization of the codec with a specific IV. - fn from_iv(iv: [u8; 64]) -> Self; - /// Absorbs data into the codec. fn prover_message(&mut self, data: &[u8]); @@ -86,16 +83,8 @@ where { type Challenge = G::Scalar; - fn new(protocol_id: &[u8], session_id: &[u8], instance_label: &[u8]) -> Self { - let iv = compute_iv::(protocol_id, session_id, instance_label); - Self::from_iv(iv) - } - - fn from_iv(iv: [u8; 64]) -> Self { - Self { - hasher: H::new(iv), - _marker: core::marker::PhantomData, - } + fn new(_protocol_id: &[u8], _session_id: &[u8], _instance_label: &[u8]) -> Self { + todo!() } fn prover_message(&mut self, data: &[u8]) { diff --git a/src/composition.rs b/src/composition.rs index c3d689e..b1da31f 100644 --- a/src/composition.rs +++ b/src/composition.rs @@ -18,6 +18,8 @@ //! ) //! ``` +use std::io::Read; + use alloc::vec::Vec; use ff::{Field, PrimeField}; use group::prime::PrimeGroup; @@ -25,6 +27,7 @@ use group::prime::PrimeGroup; use rand::{CryptoRng, Rng}; #[cfg(not(feature = "std"))] use rand_core::{CryptoRng, RngCore as Rng}; +use sha3::digest::ExtendableOutput; use sha3::{Digest, Sha3_256}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; @@ -477,7 +480,7 @@ impl SigmaProtocol fn verifier( &self, - commitment: &Self::Commitment, + commitment: &[Self::Commitment], challenge: &Self::Challenge, response: &Self::Response, ) -> Result<(), Error> { @@ -556,8 +559,8 @@ impl SigmaProtocol } } - fn protocol_identifier(&self) -> impl AsRef<[u8]> { - let mut hasher = Sha3_256::new(); + fn protocol_identifier(&self) -> [u8; 64] { + let mut hasher = sha3::TurboShake128::default(); match self { ComposedRelation::Simple(p) => { @@ -566,14 +569,12 @@ impl SigmaProtocol hasher.update(p.protocol_identifier()); } ComposedRelation::And(protocols) => { - let mut hasher = Sha3_256::new(); hasher.update([1u8; 32]); for p in protocols { hasher.update(p.protocol_identifier()); } } ComposedRelation::Or(protocols) => { - let mut hasher = Sha3_256::new(); hasher.update([2u8; 32]); for p in protocols { hasher.update(p.protocol_identifier()); @@ -581,7 +582,7 @@ impl SigmaProtocol } } - hasher.finalize() + hasher.finalize_xof().take(64).try_into().unwrap() } fn serialize_response(&self, response: &Self::Response) -> Vec { diff --git a/src/errors.rs b/src/errors.rs index 3350d40..c61f5b2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -60,6 +60,12 @@ pub enum Error { }, } +impl From for Error { + fn from(_value: spongefish::VerificationError) -> Self { + Error::VerificationFailure + } +} + // Manual Display implementation for no_std compatibility #[cfg(not(feature = "std"))] impl fmt::Display for InvalidInstance { diff --git a/src/fiat_shamir.rs b/src/fiat_shamir.rs index 28c69d7..870b0a6 100644 --- a/src/fiat_shamir.rs +++ b/src/fiat_shamir.rs @@ -14,19 +14,14 @@ use crate::errors::Error; use crate::traits::SigmaProtocol; -use crate::{codec::Codec, traits::SigmaProtocolSimulator}; +use crate::traits::SigmaProtocolSimulator; use alloc::vec::Vec; #[cfg(feature = "std")] use rand::{CryptoRng, RngCore}; #[cfg(not(feature = "std"))] use rand_core::{CryptoRng, RngCore}; - -type Transcript

= ( -

::Commitment, -

::Challenge, -

::Response, -); +use spongefish::{ProverState, VerifierState}; /// A Fiat-Shamir transformation of a [`SigmaProtocol`] into a non-interactive proof. /// @@ -40,23 +35,20 @@ type Transcript

= ( /// - `P`: the Sigma protocol implementation. /// - `C`: the codec used for Fiat-Shamir. #[derive(Debug)] -pub struct Nizk +pub struct Nizk

where P: SigmaProtocol, P::Challenge: PartialEq, - C: Codec, { - /// Current codec state. - pub hash_state: C, + pub session_id: Vec, /// Underlying interactive proof. pub interactive_proof: P, } -impl Nizk +impl

Nizk

where P: SigmaProtocol, P::Challenge: PartialEq, - C: Codec + Clone, { /// Constructs a new [`Nizk`] instance. /// @@ -67,97 +59,12 @@ where /// # Returns /// A new [`Nizk`] that can generate and verify non-interactive proofs. pub fn new(session_identifier: &[u8], interactive_proof: P) -> Self { - let hash_state = C::new( - interactive_proof.protocol_identifier().as_ref(), - session_identifier, - interactive_proof.instance_label().as_ref(), - ); Self { - hash_state, + session_id: session_identifier.to_vec(), interactive_proof, } } - pub fn from_iv(iv: [u8; 64], interactive_proof: P) -> Self { - let hash_state = C::from_iv(iv); - Self { - hash_state, - interactive_proof, - } - } - - /// Generates a non-interactive proof for a witness. - /// - /// Executes the interactive protocol steps (commit, derive challenge via hash, respond), - /// and checks the result locally for consistency. - /// - /// # Parameters - /// - `witness`: The secret witness for the Sigma protocol. - /// - `rng`: A cryptographically secure random number generator. - /// - /// # Returns - /// A [`Result`] containing a `Transcript

` on success. The `Transcript` includes: - /// - `P::Commitment`: The prover's commitment(s). - /// - `P::Challenge`: The challenge derived via Fiat-Shamir. - /// - `P::Response`: The prover's response. - /// - /// # Panics - /// Panics if local verification fails. - fn prove( - &self, - witness: &P::Witness, - rng: &mut (impl RngCore + CryptoRng), - ) -> Result, Error> { - let mut hash_state = self.hash_state.clone(); - - let (commitment, prover_state) = self.interactive_proof.prover_commit(witness, rng)?; - // Fiat Shamir challenge - let serialized_commitment = self.interactive_proof.serialize_commitment(&commitment); - hash_state.prover_message(&serialized_commitment); - let challenge = hash_state.verifier_challenge(); - // Prover's response - let response = self - .interactive_proof - .prover_response(prover_state, &challenge)?; - - Ok((commitment, challenge, response)) - } - - /// Verifies a non-interactive proof using the Fiat-Shamir transformation. - /// - /// # Parameters - /// - `commitment`: The commitment(s) sent by the prover. - /// - `challenge`: The challenge allegedly derived via Fiat-Shamir. - /// - `response`: The prover's response to the challenge. - /// - /// # Returns - /// - `Ok(())` if the proof is valid. - /// - `Err(Error::VerificationFailure)` if the challenge is invalid or the response fails to verify. - /// - /// # Errors - /// - Returns [`Error::VerificationFailure`] if: - /// - The challenge doesn't match the recomputed one from the commitment. - /// - The response fails verification under the Sigma protocol. - fn verify( - &self, - commitment: &P::Commitment, - challenge: &P::Challenge, - response: &P::Response, - ) -> Result<(), Error> { - let mut hash_state = self.hash_state.clone(); - - // Recompute the challenge - let serialized_commitment = self.interactive_proof.serialize_commitment(commitment); - hash_state.prover_message(&serialized_commitment); - let expected_challenge = hash_state.verifier_challenge(); - // Verification of the proof - match *challenge == expected_challenge { - true => self - .interactive_proof - .verifier(commitment, challenge, response), - false => Err(Error::VerificationFailure), - } - } /// Generates a batchable, serialized non-interactive proof. /// /// # Parameters @@ -174,11 +81,19 @@ where witness: &P::Witness, rng: &mut (impl RngCore + CryptoRng), ) -> Result, Error> { - let (commitment, _challenge, response) = self.prove(witness, rng)?; - let mut bytes = Vec::new(); - bytes.extend_from_slice(&self.interactive_proof.serialize_commitment(&commitment)); - bytes.extend_from_slice(&self.interactive_proof.serialize_response(&response)); - Ok(bytes) + let mut prover_state = ProverState::new_std( + self.interactive_proof.protocol_identifier(), + &self.session_id, + ); + + let (commitment, ip_state) = self.interactive_proof.prover_commit(witness, rng)?; + prover_state.prover_messages(&commitment); + let challenge = prover_state.verifier_message::(); + let response = self + .interactive_proof + .prover_response(ip_state, &challenge)?; + prover_state.prover_messages(&response); + Ok(prover_state.narg_string().to_vec()) } /// Verifies a batchable non-interactive proof. @@ -194,113 +109,94 @@ where /// - Returns [`Error::VerificationFailure`] if: /// - The challenge doesn't match the recomputed one from the commitment. /// - The response fails verification under the Sigma protocol. - pub fn verify_batchable(&self, proof: &[u8]) -> Result<(), Error> { - let commitment = self.interactive_proof.deserialize_commitment(proof)?; - let commitment_size = self - .interactive_proof - .serialize_commitment(&commitment) - .len(); - let response = self - .interactive_proof - .deserialize_response(&proof[commitment_size..])?; - let response_size = self.interactive_proof.serialize_response(&response).len(); - - // Proof size check - if proof.len() != commitment_size + response_size { - return Err(Error::VerificationFailure); - } - - // Assert correct proof size - let total_expected_len = - commitment_size + self.interactive_proof.serialize_response(&response).len(); - if proof.len() != total_expected_len { - return Err(Error::VerificationFailure); - } - - let mut hash_state = self.hash_state.clone(); - - // Recompute the challenge - let serialized_commitment = self.interactive_proof.serialize_commitment(&commitment); - hash_state.prover_message(&serialized_commitment); - let challenge = hash_state.verifier_challenge(); - // Verification of the proof + pub fn verify_batchable(&self, narg_string: &[u8]) -> Result<(), Error> { + let mut verifier_state = VerifierState::new_std( + self.interactive_proof.protocol_identifier(), + &self.session_id, + narg_string, + ); + // xxx + let commitment_len = self.interactive_proof.commitment_len(); + let response_len = self.interactive_proof.response_len(); + let commitment = verifier_state.prover_messages_vec(commitment_len)?; + let challenge = verifier_state.verifier_message::(); + let response = verifier_state.prover_messages_vec(response_len)?; self.interactive_proof .verifier(&commitment, &challenge, &response) } } -impl Nizk +impl

Nizk

where P: SigmaProtocol + SigmaProtocolSimulator, P::Challenge: PartialEq, - C: Codec + Clone, { - /// Generates a compact serialized proof. - /// - /// Uses a more space-efficient representation compared to batchable proofs. - /// - /// # Parameters - /// - `witness`: The secret witness. - /// - `rng`: A cryptographically secure random number generator. - /// - /// # Returns - /// A compact, serialized proof. - /// - /// # Panics - /// Panics if serialization fails. - pub fn prove_compact( - &self, - witness: &P::Witness, - rng: &mut (impl RngCore + CryptoRng), - ) -> Result, Error> { - let (_commitment, challenge, response) = self.prove(witness, rng)?; - let mut bytes = Vec::new(); - bytes.extend_from_slice(&self.interactive_proof.serialize_challenge(&challenge)); - bytes.extend_from_slice(&self.interactive_proof.serialize_response(&response)); - Ok(bytes) - } - - /// Verifies a compact proof. - /// - /// Recomputes the commitment from the challenge and response, then verifies it. - /// - /// # Parameters - /// - `proof`: A compact serialized proof. - /// - /// # Returns - /// - `Ok(())` if the proof is valid. - /// - `Err(Error)` if deserialization or verification fails. - /// - /// # Errors - /// - Returns [`Error::VerificationFailure`] if: - /// - Deserialization fails. - /// - The recomputed commitment or response is invalid under the Sigma protocol. - pub fn verify_compact(&self, proof: &[u8]) -> Result<(), Error> { - // Deserialize challenge and response from compact proof - let challenge = self.interactive_proof.deserialize_challenge(proof)?; - let challenge_size = self.interactive_proof.serialize_challenge(&challenge).len(); - let response = self - .interactive_proof - .deserialize_response(&proof[challenge_size..])?; - let response_size = self.interactive_proof.serialize_response(&response).len(); - - // Proof size check - if proof.len() != challenge_size + response_size { - return Err(Error::VerificationFailure); - } - - // Assert correct proof size - let total_expected_len = - challenge_size + self.interactive_proof.serialize_response(&response).len(); - if proof.len() != total_expected_len { - return Err(Error::VerificationFailure); - } - - // Compute the commitments - let commitment = self - .interactive_proof - .simulate_commitment(&challenge, &response)?; - // Verify the proof - self.verify(&commitment, &challenge, &response) - } + // /// Generates a compact serialized proof. + // /// + // /// Uses a more space-efficient representation compared to batchable proofs. + // /// + // /// # Parameters + // /// - `witness`: The secret witness. + // /// - `rng`: A cryptographically secure random number generator. + // /// + // /// # Returns + // /// A compact, serialized proof. + // /// + // /// # Panics + // /// Panics if serialization fails. + // pub fn prove_compact( + // &self, + // witness: &P::Witness, + // rng: &mut (impl RngCore + CryptoRng), + // ) -> Result, Error> { + // let (_commitment, challenge, response) = self.prove(witness, rng)?; + // let mut bytes = Vec::new(); + // bytes.extend_from_slice(&self.interactive_proof.serialize_challenge(&challenge)); + // bytes.extend_from_slice(&self.interactive_proof.serialize_response(&response)); + // Ok(bytes) + // } + + // /// Verifies a compact proof. + // /// + // /// Recomputes the commitment from the challenge and response, then verifies it. + // /// + // /// # Parameters + // /// - `proof`: A compact serialized proof. + // /// + // /// # Returns + // /// - `Ok(())` if the proof is valid. + // /// - `Err(Error)` if deserialization or verification fails. + // /// + // /// # Errors + // /// - Returns [`Error::VerificationFailure`] if: + // /// - Deserialization fails. + // /// - The recomputed commitment or response is invalid under the Sigma protocol. + // pub fn verify_compact(&self, proof: &[u8]) -> Result<(), Error> { + // // Deserialize challenge and response from compact proof + // let challenge = self.interactive_proof.deserialize_challenge(proof)?; + // let challenge_size = self.interactive_proof.serialize_challenge(&challenge).len(); + // let response = self + // .interactive_proof + // .deserialize_response(&proof[challenge_size..])?; + // let response_size = self.interactive_proof.serialize_response(&response).len(); + + // // Proof size check + // if proof.len() != challenge_size + response_size { + // return Err(Error::VerificationFailure); + // } + + // // Assert correct proof size + // let total_expected_len = + // challenge_size + self.interactive_proof.serialize_response(&response).len(); + // if proof.len() != total_expected_len { + // return Err(Error::VerificationFailure); + // } + + // // Compute the commitments + // let commitment = self + // .interactive_proof + // .simulate_commitment(&challenge, &response)?; + // // Verify the proof + // self.verify(&commitment, &challenge, &response) + // } } diff --git a/src/lib.rs b/src/lib.rs index 993d549..23c0419 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,7 @@ extern crate alloc; pub mod codec; -pub mod composition; +// pub mod composition; pub mod errors; pub mod linear_relation; pub mod traits; @@ -80,8 +80,8 @@ pub(crate) mod fiat_shamir; pub(crate) mod group; pub(crate) mod schnorr_protocol; -#[cfg(test)] -pub mod tests; +// #[cfg(test)] +// pub mod tests; pub use fiat_shamir::Nizk; pub use group::msm::VariableMultiScalarMul; diff --git a/src/linear_relation/mod.rs b/src/linear_relation/mod.rs index 8fe6383..bd23286 100644 --- a/src/linear_relation/mod.rs +++ b/src/linear_relation/mod.rs @@ -13,13 +13,10 @@ use alloc::vec::Vec; use core::iter; use core::marker::PhantomData; -use ff::Field; -use group::prime::PrimeGroup; - -use crate::codec::Shake128DuplexSponge; use crate::errors::{Error, InvalidInstance}; use crate::group::msm::VariableMultiScalarMul; -use crate::Nizk; +use ff::Field; +use group::prime::PrimeGroup; /// Implementations of conversion operations such as From and FromIterator for var and term types. mod convert; @@ -555,44 +552,6 @@ impl LinearRelation { .collect() } - /// Convert this LinearRelation into a non-interactive zero-knowledge protocol - /// using the ShakeCodec and a specified context/domain separator. - /// - /// # Parameters - /// - `context`: Domain separator bytes for the Fiat-Shamir transform - /// - /// # Returns - /// A `Nizk` instance ready for proving and verification - /// - /// # Example - /// ``` - /// # use sigma_proofs::{LinearRelation, Nizk}; - /// # use curve25519_dalek::RistrettoPoint as G; - /// # use curve25519_dalek::scalar::Scalar; - /// # use rand::rngs::OsRng; - /// # use group::Group; - /// - /// let mut relation = LinearRelation::::new(); - /// let x_var = relation.allocate_scalar(); - /// let g_var = relation.allocate_element(); - /// let p_var = relation.allocate_eq(x_var * g_var); - /// - /// relation.set_element(g_var, G::generator()); - /// let x = Scalar::random(&mut OsRng); - /// relation.compute_image(&[x]).unwrap(); - /// - /// // Convert to NIZK with custom context - /// let nizk = relation.into_nizk(b"my-protocol-v1").unwrap(); - /// let proof = nizk.prove_batchable(&vec![x], &mut OsRng).unwrap(); - /// assert!(nizk.verify_batchable(&proof).is_ok()); - /// ``` - pub fn into_nizk( - self, - session_identifier: &[u8], - ) -> Result, Shake128DuplexSponge>, InvalidInstance> { - Ok(Nizk::new(session_identifier, self.try_into()?)) - } - /// Construct a [CanonicalLinearRelation] from this generalized linear relation. /// /// The construction may fail if the linear relation is malformed, unsatisfiable, or trivial. diff --git a/src/schnorr_protocol.rs b/src/schnorr_protocol.rs index ea54e74..4ebf8ff 100644 --- a/src/schnorr_protocol.rs +++ b/src/schnorr_protocol.rs @@ -4,12 +4,10 @@ //! a Sigma protocol proving different types of discrete logarithm relations (eg. Schnorr, Pedersen's commitments) //! through a group morphism abstraction (see [Maurer09](https://crypto-test.ethz.ch/publications/files/Maurer09.pdf)). -use crate::errors::Error; -use crate::group::serialization::{ - deserialize_elements, deserialize_scalars, serialize_elements, serialize_scalars, -}; +use crate::errors::{Error, Result}; use crate::linear_relation::CanonicalLinearRelation; -use crate::traits::{SigmaProtocol, SigmaProtocolSimulator}; +use crate::traits::{SigmaProtocol, SigmaProtocolSimulator, Transcript}; +use crate::{LinearRelation, Nizk}; use alloc::vec::Vec; use ff::Field; @@ -18,11 +16,16 @@ use group::prime::PrimeGroup; use rand::{CryptoRng, Rng, RngCore}; #[cfg(not(feature = "std"))] use rand_core::{CryptoRng, RngCore, RngCore as Rng}; +use spongefish::Codec; -impl SigmaProtocol for CanonicalLinearRelation { - type Commitment = Vec; +impl SigmaProtocol for CanonicalLinearRelation +where + G: PrimeGroup + Codec, + G::Scalar: Codec, +{ + type Commitment = G; type ProverState = (Vec, Vec); - type Response = Vec; + type Response = G::Scalar; type Witness = Vec; type Challenge = G::Scalar; @@ -45,7 +48,7 @@ impl SigmaProtocol for CanonicalLinearRelation { &self, witness: &Self::Witness, rng: &mut (impl RngCore + CryptoRng), - ) -> Result<(Self::Commitment, Self::ProverState), Error> { + ) -> Result<(Vec, Self::ProverState)> { if witness.len() < self.num_scalars { return Err(Error::InvalidInstanceWitnessPair); } @@ -85,7 +88,7 @@ impl SigmaProtocol for CanonicalLinearRelation { &self, prover_state: Self::ProverState, challenge: &Self::Challenge, - ) -> Result { + ) -> Result> { let (nonces, witness) = prover_state; let responses = nonces @@ -113,10 +116,10 @@ impl SigmaProtocol for CanonicalLinearRelation { /// -[`Error::InvalidInstanceWitnessPair`] if the commitment or response length is incorrect. fn verifier( &self, - commitment: &Self::Commitment, + commitment: &[Self::Commitment], challenge: &Self::Challenge, - response: &Self::Response, - ) -> Result<(), Error> { + response: &[Self::Response], + ) -> Result<()> { if commitment.len() != self.image.len() || response.len() != self.num_scalars { return Err(Error::InvalidInstanceWitnessPair); } @@ -132,115 +135,121 @@ impl SigmaProtocol for CanonicalLinearRelation { Err(Error::VerificationFailure) } } - - /// Serializes the prover's commitment into a byte vector. - /// - /// This function encodes the vector of group elements (the commitment) - /// into a binary format suitable for transmission or storage. This is - /// typically the first message sent in a Sigma protocol round. - /// - /// # Parameters - /// - `commitment`: A vector of group elements representing the prover's commitment. - /// - /// # Returns - /// A `Vec` containing the serialized group elements. - fn serialize_commitment(&self, commitment: &Self::Commitment) -> Vec { - serialize_elements(commitment) + fn commitment_len(&self) -> usize { + self.image.len() } - /// Serializes the verifier's challenge scalar into bytes. - /// - /// Converts the challenge scalar into a fixed-length byte encoding. This can be used - /// for Fiat–Shamir hashing, transcript recording, or proof transmission. - /// - /// # Parameters - /// - `challenge`: The scalar challenge value. - /// - /// # Returns - /// A `Vec` containing the serialized scalar. - fn serialize_challenge(&self, &challenge: &Self::Challenge) -> Vec { - serialize_scalars::(&[challenge]) + fn response_len(&self) -> usize { + self.num_scalars } - /// Serializes the prover's response vector into a byte format. - /// - /// The response is a vector of scalars computed by the prover after receiving - /// the verifier's challenge. This function encodes the vector into a format - /// suitable for transmission or inclusion in a batchable proof. - /// - /// # Parameters - /// - `response`: A vector of scalar responses computed by the prover. - /// - /// # Returns - /// A `Vec` containing the serialized scalars. - fn serialize_response(&self, response: &Self::Response) -> Vec { - serialize_scalars::(response) + fn instance_label(&self) -> impl AsRef<[u8]> { + self.label() } - /// Deserializes a byte slice into a vector of group elements (commitment). - /// - /// This function reconstructs the prover’s commitment from its binary representation. - /// The number of elements expected is determined by the number of linear constraints - /// in the underlying linear relation. - /// - /// # Parameters - /// - `data`: A byte slice containing the serialized commitment. - /// - /// # Returns - /// A `Vec` containing the deserialized group elements. - /// - /// # Errors - /// - Returns [`Error::VerificationFailure`] if the data is malformed or contains an invalid encoding. - fn deserialize_commitment(&self, data: &[u8]) -> Result { - deserialize_elements::(data, self.image.len()).ok_or(Error::VerificationFailure) + fn protocol_identifier(&self) -> [u8; 64] { + let mut id = [0u8; 64]; + id[..32].clone_from_slice(b"ietf sigma proof linear relation"); + id } +} - /// Deserializes a byte slice into a challenge scalar. - /// - /// This function expects a single scalar to be encoded and returns it as the verifier's challenge. +impl CanonicalLinearRelation +where + G: PrimeGroup + Codec, + G::Scalar: Codec, +{ + /// Convert this LinearRelation into a non-interactive zero-knowledge protocol + /// using the ShakeCodec and a specified context/domain separator. /// /// # Parameters - /// - `data`: A byte slice containing the serialized scalar challenge. + /// - `context`: Domain separator bytes for the Fiat-Shamir transform /// /// # Returns - /// The deserialized scalar challenge value. - /// - /// # Errors - /// - Returns [`Error::VerificationFailure`] if deserialization fails or data is invalid. - fn deserialize_challenge(&self, data: &[u8]) -> Result { - let scalars = deserialize_scalars::(data, 1).ok_or(Error::VerificationFailure)?; - Ok(scalars[0]) + /// A `Nizk` instance ready for proving and verification + /// + /// # Example + /// ``` + /// # use sigma_proofs::{LinearRelation, Nizk}; + /// # use curve25519_dalek::RistrettoPoint as G; + /// # use curve25519_dalek::scalar::Scalar; + /// # use rand::rngs::OsRng; + /// # use group::Group; + /// + /// let mut relation = LinearRelation::::new(); + /// let x_var = relation.allocate_scalar(); + /// let g_var = relation.allocate_element(); + /// let p_var = relation.allocate_eq(x_var * g_var); + /// + /// relation.set_element(g_var, G::generator()); + /// let x = Scalar::random(&mut OsRng); + /// relation.compute_image(&[x]).unwrap(); + /// + /// // Convert to NIZK with custom context + /// let nizk = relation.into_nizk(b"my-protocol-v1").unwrap(); + /// let proof = nizk.prove_batchable(&vec![x], &mut OsRng).unwrap(); + /// assert!(nizk.verify_batchable(&proof).is_ok()); + /// ``` + pub fn into_nizk(self, session_identifier: &[u8]) -> Result>> { + Ok(Nizk::new(session_identifier, self)) } +} - /// Deserializes a byte slice into the prover's response vector. +impl LinearRelation +where + G: PrimeGroup + Codec, + G::Scalar: Codec, +{ + /// Convert this LinearRelation into a non-interactive zero-knowledge protocol + /// using the Fiat-Shamir transform. /// - /// The response vector contains scalars used in the second round of the Sigma protocol. - /// The expected number of scalars matches the number of witness variables. + /// This is a convenience method that combines `.canonical()` and `.into_nizk()`. /// /// # Parameters - /// - `data`: A byte slice containing the serialized response. + /// - `session_identifier`: Domain separator bytes for the Fiat-Shamir transform /// /// # Returns - /// A vector of deserialized scalars. - /// - /// # Errors - /// - Returns [`Error::VerificationFailure`] if the byte data is malformed or the length is incorrect. - fn deserialize_response(&self, data: &[u8]) -> Result { - deserialize_scalars::(data, self.num_scalars).ok_or(Error::VerificationFailure) - } - - fn instance_label(&self) -> impl AsRef<[u8]> { - self.label() - } - - fn protocol_identifier(&self) -> impl AsRef<[u8]> { - b"draft-zkproof-fiat-shamir" + /// A `Nizk` instance ready for proving and verification + /// + /// # Example + /// ``` + /// # use sigma_proofs::{LinearRelation, Nizk}; + /// # use curve25519_dalek::RistrettoPoint as G; + /// # use curve25519_dalek::scalar::Scalar; + /// # use rand::rngs::OsRng; + /// # use group::Group; + /// + /// let mut relation = LinearRelation::::new(); + /// let x_var = relation.allocate_scalar(); + /// let g_var = relation.allocate_element(); + /// let p_var = relation.allocate_eq(x_var * g_var); + /// + /// relation.set_element(g_var, G::generator()); + /// let x = Scalar::random(&mut OsRng); + /// relation.compute_image(&[x]).unwrap(); + /// + /// // Convert to NIZK directly + /// let nizk = relation.into_nizk(b"my-protocol-v1").unwrap(); + /// let proof = nizk.prove_batchable(&vec![x], &mut OsRng).unwrap(); + /// assert!(nizk.verify_batchable(&proof).is_ok()); + /// ``` + pub fn into_nizk( + self, + session_identifier: &[u8], + ) -> crate::errors::Result>> + where + G: spongefish::Codec, + G::Scalar: spongefish::Codec, + { + self.canonical() + .map_err(|_| crate::errors::Error::InvalidInstanceWitnessPair)? + .into_nizk(session_identifier) } } - impl SigmaProtocolSimulator for CanonicalLinearRelation where - G: PrimeGroup, + G: PrimeGroup + Codec, + G::Scalar: Codec, { /// Simulates a valid transcript for a given challenge without a witness. /// @@ -250,11 +259,10 @@ where /// /// # Returns /// - A commitment and response forming a valid proof for the given challenge. - fn simulate_response(&self, rng: &mut R) -> Self::Response { - let response: Vec = (0..self.num_scalars) + fn simulate_response(&self, rng: &mut R) -> Vec { + (0..self.num_scalars) .map(|_| G::Scalar::random(&mut *rng)) - .collect(); - response + .collect() } /// Simulates a full proof transcript using a randomly generated challenge. @@ -264,10 +272,7 @@ where /// /// # Returns /// - A tuple `(commitment, challenge, response)` forming a valid proof. - fn simulate_transcript( - &self, - rng: &mut R, - ) -> Result<(Self::Commitment, Self::Challenge, Self::Response), Error> { + fn simulate_transcript(&self, rng: &mut R) -> Result> { let challenge = G::Scalar::random(&mut *rng); let response = self.simulate_response(&mut *rng); let commitment = self.simulate_commitment(&challenge, &response)?; @@ -288,8 +293,8 @@ where fn simulate_commitment( &self, challenge: &Self::Challenge, - response: &Self::Response, - ) -> Result { + response: &[Self::Response], + ) -> Result> { if response.len() != self.num_scalars { return Err(Error::InvalidInstanceWitnessPair); } diff --git a/src/tests/spec/test_vectors.rs b/src/tests/spec/test_vectors.rs index c21c9ca..69f04ab 100644 --- a/src/tests/spec/test_vectors.rs +++ b/src/tests/spec/test_vectors.rs @@ -3,12 +3,11 @@ use hex::FromHex; use json::JsonValue; use std::collections::HashMap; -use crate::codec::KeccakByteSchnorrCodec; use crate::fiat_shamir::Nizk; use crate::linear_relation::CanonicalLinearRelation; use crate::tests::spec::{custom_schnorr_protocol::DeterministicSchnorrProof, rng::TestDRNG}; -type SchnorrNizk = Nizk, KeccakByteSchnorrCodec>; +type SchnorrNizk = Nizk>; #[derive(Debug)] struct TestVector { diff --git a/src/traits.rs b/src/traits.rs index f669656..ad0752b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -4,12 +4,20 @@ //! used to describe interactive zero-knowledge proofs of knowledge, //! such as Schnorr proofs, that follow the 3-message Sigma protocol structure. -use crate::errors::Error; use alloc::vec::Vec; #[cfg(feature = "std")] use rand::{CryptoRng, Rng}; #[cfg(not(feature = "std"))] use rand_core::{CryptoRng, RngCore as Rng}; +use spongefish::Codec; + +use crate::errors::Result; + +pub type Transcript

= ( + Vec<

::Commitment>, +

::Challenge, + Vec<

::Response>, +); /// A trait defining the behavior of a generic Sigma protocol. /// @@ -46,11 +54,11 @@ use rand_core::{CryptoRng, RngCore as Rng}; /// - `protocol_identifier` — A fixed byte identifier of the protocol. /// - `instance_label` — A label specific to the instance being proven. pub trait SigmaProtocol { - type Commitment; + type Commitment: Codec; + type Challenge: Codec; + type Response: Codec; type ProverState; - type Response; type Witness; - type Challenge; /// First step of the protocol. Given the witness and RNG, this generates: /// - A public commitment to send to the verifier. @@ -59,14 +67,14 @@ pub trait SigmaProtocol { &self, witness: &Self::Witness, rng: &mut (impl Rng + CryptoRng), - ) -> Result<(Self::Commitment, Self::ProverState), Error>; + ) -> Result<(Vec, Self::ProverState)>; /// Computes the prover's response to a challenge based on the prover state. fn prover_response( &self, state: Self::ProverState, challenge: &Self::Challenge, - ) -> Result; + ) -> Result>; /// Final step of the protocol: checks that the commitment, challenge, and response form a valid transcript. /// @@ -75,40 +83,20 @@ pub trait SigmaProtocol { /// - `Err(())` otherwise. fn verifier( &self, - commitment: &Self::Commitment, + commitment: &[Self::Commitment], challenge: &Self::Challenge, - response: &Self::Response, - ) -> Result<(), Error>; - - /// Serializes a commitment to bytes. - fn serialize_commitment(&self, commitment: &Self::Commitment) -> Vec; - - /// Serializes a challenge to bytes. - fn serialize_challenge(&self, challenge: &Self::Challenge) -> Vec; + response: &[Self::Response], + ) -> Result<()>; - /// Serializes a response to bytes. - fn serialize_response(&self, response: &Self::Response) -> Vec; + fn commitment_len(&self) -> usize; - /// Deserializes a commitment from bytes. - fn deserialize_commitment(&self, data: &[u8]) -> Result; + fn response_len(&self) -> usize; - /// Deserializes a challenge from bytes. - fn deserialize_challenge(&self, data: &[u8]) -> Result; - - /// Deserializes a response from bytes. - fn deserialize_response(&self, data: &[u8]) -> Result; - - fn protocol_identifier(&self) -> impl AsRef<[u8]>; + fn protocol_identifier(&self) -> [u8; 64]; fn instance_label(&self) -> impl AsRef<[u8]>; } -type Transcript

= ( -

::Commitment, -

::Challenge, -

::Response, -); - /// A trait defining the behavior of a Sigma protocol for which simulation of transcripts is necessary. /// /// Every Sigma protocol can be simulated, but in practice, this is primarily used @@ -124,7 +112,7 @@ pub trait SigmaProtocolSimulator: SigmaProtocol { /// Generates a random response (e.g. for simulation or OR composition). /// /// Typically used to simulate a proof without a witness. - fn simulate_response(&self, rng: &mut R) -> Self::Response; + fn simulate_response(&self, rng: &mut R) -> Vec; /// Simulates a commitment for which ('commitment', 'challenge', 'response') is a valid transcript. /// @@ -132,13 +120,10 @@ pub trait SigmaProtocolSimulator: SigmaProtocol { fn simulate_commitment( &self, challenge: &Self::Challenge, - response: &Self::Response, - ) -> Result; + response: &[Self::Response], + ) -> Result>; /// Generates a full simulated proof transcript (commitment, challenge, response) /// without requiring knowledge of a witness. - fn simulate_transcript( - &self, - rng: &mut R, - ) -> Result, Error>; + fn simulate_transcript(&self, rng: &mut R) -> Result>; }