diff --git a/crates/proof-of-sql/src/base/commitment/commitment_evaluation_proof.rs b/crates/proof-of-sql/src/base/commitment/commitment_evaluation_proof.rs index 7faa63963..65fa6cc2d 100644 --- a/crates/proof-of-sql/src/base/commitment/commitment_evaluation_proof.rs +++ b/crates/proof-of-sql/src/base/commitment/commitment_evaluation_proof.rs @@ -1,5 +1,5 @@ use super::Commitment; -use crate::base::scalar::Scalar; +use crate::base::{proof::Transcript as _, scalar::Scalar}; #[cfg(feature = "blitzar")] use crate::base::{scalar::MontScalar, slice_ops}; #[cfg(feature = "blitzar")] @@ -101,12 +101,16 @@ impl CommitmentEvaluationProof for InnerProductProof { } else { crate::base::polynomial::compute_evaluation_vector(b, b_point); } - Self::create( - transcript, - &slice_ops::slice_cast(a), - &slice_ops::slice_cast(b), - generators_offset, - ) + // The InnerProductProof from blitzar only works with the merlin Transcript. + // So, we wrap the call to it. + transcript.wrap_transcript(|transcript| { + Self::create( + transcript, + &slice_ops::slice_cast(a), + &slice_ops::slice_cast(b), + generators_offset, + ) + }) } fn verify_batched_proof( @@ -128,19 +132,23 @@ impl CommitmentEvaluationProof for InnerProductProof { } else { crate::base::polynomial::compute_evaluation_vector(b, b_point); } - self.verify( - transcript, - &commit_batch - .iter() - .zip(batching_factors.iter()) - .map(|(c, m)| *m * c) - .fold(Default::default(), |mut a, c| { - a += c; - a - }), - &product.into(), - &slice_ops::slice_cast(b), - generators_offset, - ) + // The InnerProductProof from blitzar only works with the merlin Transcript. + // So, we wrap the call to it. + transcript.wrap_transcript(|transcript| { + self.verify( + transcript, + &commit_batch + .iter() + .zip(batching_factors.iter()) + .map(|(c, m)| *m * c) + .fold(Default::default(), |mut a, c| { + a += c; + a + }), + &product.into(), + &slice_ops::slice_cast(b), + generators_offset, + ) + }) } } diff --git a/crates/proof-of-sql/src/base/proof/transcript.rs b/crates/proof-of-sql/src/base/proof/transcript.rs index 6dd48d88d..4456f9113 100644 --- a/crates/proof-of-sql/src/base/proof/transcript.rs +++ b/crates/proof-of-sql/src/base/proof/transcript.rs @@ -13,7 +13,7 @@ pub trait Transcript { /// Appends the provided messages by appending the reversed raw bytes (i.e. assuming the message is bigendian) fn extend_as_be(&mut self, messages: impl IntoIterator); /// Appends the provided messages by appending the raw bytes (i.e. assuming the message is littleendian) - fn extend_as_le_from_refs<'a, M: AsBytes + 'a>( + fn extend_as_le_from_refs<'a, M: AsBytes + 'a + ?Sized>( &mut self, messages: impl IntoIterator, ); @@ -24,7 +24,101 @@ pub trait Transcript { ); /// Request a scalar challenge. Assumes that the reversed raw bytes are the canonical value of the scalar (i.e. bigendian form) fn scalar_challenge_as_be(&mut self) -> S; - #[cfg(test)] /// Request a challenge. Returns the raw, unreversed, bytes. (i.e. littleendian form) fn challenge_as_le(&mut self) -> [u8; 32]; + + /// Appends a type that implements [serde::Serialize] by appending the raw bytes (i.e. assuming the message is littleendian) + fn extend_serialize_as_le(&mut self, message: &(impl serde::Serialize + ?Sized)) { + self.extend_as_le_from_refs([postcard::to_allocvec(message).unwrap().as_slice()]); + } + /// Appends a type that implements [ark_serialize::CanonicalSerialize] by appending the raw bytes (i.e. assuming the message is littleendian) + fn extend_canonical_serialize_as_le( + &mut self, + message: &(impl ark_serialize::CanonicalSerialize + ?Sized), + ) { + let mut buf = Vec::with_capacity(message.compressed_size()); + message.serialize_compressed(&mut buf).unwrap(); + self.extend_as_le_from_refs([buf.as_slice()]); + } + /// "Lift" a function so that it can be applied to an `impl Transcript` of (possibly) different type than self. + /// This allows for interopability between transcript types. + fn wrap_transcript(&mut self, op: impl FnOnce(&mut T) -> R) -> R { + let mut transcript = T::new(); + transcript.extend_as_le_from_refs([&self.challenge_as_le()]); + let result = op(&mut transcript); + self.extend_as_le_from_refs([&transcript.challenge_as_le()]); + result + } +} + +#[cfg(test)] +mod tests { + use super::Transcript; + use crate::base::proof::Keccak256Transcript; + + #[test] + fn we_can_extend_transcript_with_serialize() { + let mut transcript1: Keccak256Transcript = Transcript::new(); + let mut transcript2: Keccak256Transcript = Transcript::new(); + + transcript1.extend_serialize_as_le(&(123, vec!["hi", "there"])); + transcript2.extend_serialize_as_le(&(123, vec!["hi", "there"])); + + assert_eq!(transcript1.challenge_as_le(), transcript2.challenge_as_le()); + + transcript2.extend_serialize_as_le(&234.567); + + assert_ne!(transcript1.challenge_as_le(), transcript2.challenge_as_le()); + } + + #[test] + fn we_can_extend_transcript_with_canonical_serialize() { + let mut transcript1: Keccak256Transcript = Transcript::new(); + let mut transcript2: Keccak256Transcript = Transcript::new(); + + transcript1.extend_canonical_serialize_as_le(&( + 123_u16, + vec!["hi".to_string(), "there".to_string()], + )); + transcript2.extend_canonical_serialize_as_le(&( + 123_u16, + vec!["hi".to_string(), "there".to_string()], + )); + + assert_eq!(transcript1.challenge_as_le(), transcript2.challenge_as_le()); + + transcript2.extend_canonical_serialize_as_le(&ark_bls12_381::FQ_ONE); + + assert_ne!(transcript1.challenge_as_le(), transcript2.challenge_as_le()); + } + + #[test] + fn we_can_extend_transcript_with_wrapped_transcript() { + let mut transcript1: Keccak256Transcript = Transcript::new(); + let mut transcript2: Keccak256Transcript = Transcript::new(); + + let result1 = transcript1.wrap_transcript(|transcript: &mut merlin::Transcript| { + transcript.append_u64(b"test", 320); + let mut result = vec![0; 3]; + transcript.challenge_bytes(b"test2", &mut result); + result + }); + let result2 = transcript2.wrap_transcript(|transcript: &mut merlin::Transcript| { + transcript.append_u64(b"test", 320); + let mut result = vec![0; 3]; + transcript.challenge_bytes(b"test2", &mut result); + result + }); + + assert_eq!(result1, result2); + assert_eq!(transcript1.challenge_as_le(), transcript2.challenge_as_le()); + + transcript1.wrap_transcript(|transcript: &mut merlin::Transcript| { + let mut result = vec![0; 32]; + transcript.challenge_bytes(b"test3", &mut result); + result + }); + + assert_ne!(transcript1.challenge_as_le(), transcript2.challenge_as_le()); + } } diff --git a/crates/proof-of-sql/src/base/proof/transcript_core.rs b/crates/proof-of-sql/src/base/proof/transcript_core.rs index 063a836ad..57c9549b4 100644 --- a/crates/proof-of-sql/src/base/proof/transcript_core.rs +++ b/crates/proof-of-sql/src/base/proof/transcript_core.rs @@ -33,7 +33,7 @@ impl Transcript for T { self.raw_append(bytes) }) } - fn extend_as_le_from_refs<'a, M: AsBytes + 'a>( + fn extend_as_le_from_refs<'a, M: AsBytes + 'a + ?Sized>( &mut self, messages: impl IntoIterator, ) { @@ -50,7 +50,6 @@ impl Transcript for T { fn scalar_challenge_as_be(&mut self) -> S { receive_challenge_as_be::<[u64; 4]>(self).into() } - #[cfg(test)] fn challenge_as_le(&mut self) -> [u8; 32] { self.raw_challenge() } diff --git a/crates/proof-of-sql/src/base/proof/transcript_protocol.rs b/crates/proof-of-sql/src/base/proof/transcript_protocol.rs index 1be168756..e59d289cf 100644 --- a/crates/proof-of-sql/src/base/proof/transcript_protocol.rs +++ b/crates/proof-of-sql/src/base/proof/transcript_protocol.rs @@ -53,6 +53,7 @@ pub trait TranscriptProtocol { ); /// Compute a challenge variable (which requires a label). + #[cfg(test)] fn challenge_scalar_single( &mut self, label: MessageLabel, @@ -120,12 +121,9 @@ pub enum MessageLabel { /// Represents a challenge in the computation of an inner product. #[cfg(test)] InnerProductChallenge, - /// Denotes a sumcheck protocol message. - Sumcheck, /// Represents a challenge in the sumcheck protocol. + #[cfg(test)] SumcheckChallenge, - /// Represents a round evaluation in the sumcheck protocol. - SumcheckRoundEvaluation, /// Represents a proof resulting from a query. QueryProof, /// Represents a commitment to a query. @@ -165,9 +163,8 @@ impl MessageLabel { MessageLabel::InnerProduct => b"ipp v1", #[cfg(test)] MessageLabel::InnerProductChallenge => b"ippchallenge v1", - MessageLabel::Sumcheck => b"sumcheckproof v1", + #[cfg(test)] MessageLabel::SumcheckChallenge => b"sumcheckchallenge v1", - MessageLabel::SumcheckRoundEvaluation => b"sumcheckroundevaluationscalars v1", MessageLabel::QueryProof => b"queryproof v1", MessageLabel::QueryCommit => b"querycommit v1", MessageLabel::QueryResultData => b"queryresultdata v1", diff --git a/crates/proof-of-sql/src/proof_primitive/sumcheck/proof.rs b/crates/proof-of-sql/src/proof_primitive/sumcheck/proof.rs index 12d588029..48315558b 100644 --- a/crates/proof-of-sql/src/proof_primitive/sumcheck/proof.rs +++ b/crates/proof-of-sql/src/proof_primitive/sumcheck/proof.rs @@ -1,12 +1,11 @@ use crate::{ base::{ polynomial::{CompositePolynomial, CompositePolynomialInfo}, - proof::{MessageLabel, ProofError, TranscriptProtocol}, + proof::{ProofError, Transcript}, scalar::Scalar, }, proof_primitive::sumcheck::{prove_round, ProverState, Subclaim}, }; -use merlin::Transcript; use serde::{Deserialize, Serialize}; /** * Adopted from arkworks @@ -23,26 +22,25 @@ pub struct SumcheckProof { impl SumcheckProof { #[tracing::instrument(name = "SumcheckProof::create", level = "debug", skip_all)] pub fn create( - transcript: &mut Transcript, + transcript: &mut impl Transcript, evaluation_point: &mut [S], polynomial: &CompositePolynomial, ) -> Self { assert_eq!(evaluation_point.len(), polynomial.num_variables); - transcript.append_auto( - MessageLabel::Sumcheck, - &(polynomial.max_multiplicands, polynomial.num_variables), - ); + transcript.extend_as_be([ + polynomial.max_multiplicands as u64, + polynomial.num_variables as u64, + ]); + // This challenge is in order to keep transcript messages grouped. (This simplifies the Solidity implementation.) + transcript.scalar_challenge_as_be::(); let mut r = None; let mut state = ProverState::create(polynomial); let mut evaluations = Vec::with_capacity(polynomial.num_variables); for scalar in evaluation_point.iter_mut().take(polynomial.num_variables) { let round_evaluations = prove_round(&mut state, &r); - transcript.append_canonical_serialize( - MessageLabel::SumcheckRoundEvaluation, - &round_evaluations, - ); + transcript.extend_scalars_as_be(&round_evaluations); + *scalar = transcript.scalar_challenge_as_be(); evaluations.push(round_evaluations); - *scalar = transcript.challenge_scalar_single(MessageLabel::SumcheckChallenge); r = Some(*scalar); } @@ -56,17 +54,16 @@ impl SumcheckProof { )] pub fn verify_without_evaluation( &self, - transcript: &mut Transcript, + transcript: &mut impl Transcript, polynomial_info: CompositePolynomialInfo, claimed_sum: &S, ) -> Result, ProofError> { - transcript.append_auto( - MessageLabel::Sumcheck, - &( - polynomial_info.max_multiplicands, - polynomial_info.num_variables, - ), - ); + transcript.extend_as_be([ + polynomial_info.max_multiplicands as u64, + polynomial_info.num_variables as u64, + ]); + // This challenge is in order to keep transcript messages grouped. (This simplifies the Solidity implementation.) + transcript.scalar_challenge_as_be::(); if self.evaluations.len() != polynomial_info.num_variables { return Err(ProofError::VerificationError( "invalid number of evaluations", @@ -74,12 +71,8 @@ impl SumcheckProof { } let mut evaluation_point = Vec::with_capacity(polynomial_info.num_variables); for round_index in 0..polynomial_info.num_variables { - transcript.append_canonical_serialize( - MessageLabel::SumcheckRoundEvaluation, - &self.evaluations[round_index], - ); - evaluation_point - .push(transcript.challenge_scalar_single(MessageLabel::SumcheckChallenge)); + transcript.extend_scalars_as_be(&self.evaluations[round_index]); + evaluation_point.push(transcript.scalar_challenge_as_be()); } Subclaim::create( evaluation_point,