1use crate::{merkle::MerkleProof, pcs::structs::ZipPlusCommitment};
2use crypto_primitives::BaseFieldConfig;
3use itertools::Itertools;
4use std::{
5 borrow::Borrow,
6 io::{Cursor, ErrorKind, Read, Write},
7};
8use zinc_transcript::{
9 Blake3Transcript, TranscriptError,
10 traits::{ConstTranscribable, Transcribable, Transcript},
11};
12use zinc_utils::{add, mul, rem};
13
14macro_rules! safe_cast {
15 ($value:expr, $from:ident, $to:ident) => {
16 $to::try_from($value).map_err(|_err| {
17 TranscriptError(
18 ErrorKind::Unsupported,
19 format!(
20 "Failed to convert {} to {}",
21 stringify!($from),
22 stringify!($to)
23 ),
24 )
25 })
26 };
27}
28
29macro_rules! common_methods {
30 () => {
31 #[allow(clippy::unwrap_used)]
35 pub fn squeeze_challenge_idx(&mut self, cap: usize) -> usize {
36 let num = safe_cast!(self.fs_transcript.get_challenge::<u32>(), u32, usize)
37 .expect("Conversion from u32 to usize should never fail");
38 rem!(num, cap, "Challenge cap is zero")
39 }
40 };
41}
42
43#[derive(Debug, Clone)]
50pub struct PcsProverTranscript {
51 pub fs_transcript: Blake3Transcript,
55
56 pub stream: Cursor<Vec<u8>>,
59}
60
61impl PcsProverTranscript {
64 pub fn new_from_commitment(comm: &ZipPlusCommitment) -> Self {
65 Self::new_from_commitments(std::slice::from_ref(comm).iter())
66 }
67
68 pub fn new_from_commitments<'a>(comms: impl Iterator<Item = &'a ZipPlusCommitment>) -> Self {
69 let mut result = Self {
70 fs_transcript: Blake3Transcript::default(),
71 stream: Cursor::default(),
72 };
73
74 for comm in comms {
75 result.fs_transcript.absorb_bytes(&comm.root);
76 }
77
78 result
79 }
80
81 pub fn reserve_capacity(&mut self, additional_capacity: usize) {
82 self.stream.get_mut().reserve(additional_capacity)
83 }
84
85 pub fn into_verification_transcript(self) -> PcsVerifierTranscript {
90 let mut result = PcsVerifierTranscript {
91 fs_transcript: Blake3Transcript::default(),
92 stream: self.stream,
93 };
94 result.stream.set_position(0);
95
96 result
97 }
98
99 common_methods!();
100
101 pub fn write_field_elements<C>(
109 &mut self,
110 cfg: &C,
111 elems: &[C::Element],
112 ) -> Result<(), TranscriptError>
113 where
114 C: BaseFieldConfig,
115 C::Integer: ConstTranscribable,
116 {
117 self.write_const_many_iter(elems.iter().map(|e| cfg.lift(e)), elems.len())
118 }
119
120 pub fn write<T: Transcribable>(&mut self, v: &T) -> Result<(), TranscriptError> {
121 let data_len = v.get_num_bytes();
122
123 if T::LENGTH_NUM_BYTES > 0 {
125 let len_bytes = data_len
126 .to_le_bytes()
127 .into_iter()
128 .take(T::LENGTH_NUM_BYTES)
129 .collect_vec();
130 self.stream
131 .write_all(&len_bytes)
132 .map_err(to_transcript_error)?;
133 self.fs_transcript.absorb_bytes(&len_bytes);
136 }
137
138 let prev_pos = safe_cast!(self.stream.position(), u64, usize)?;
139 let next_pos = add!(prev_pos, data_len);
140
141 let inner = self.stream.get_mut();
142 if inner.len() < next_pos {
143 inner.resize(next_pos, 0_u8);
144 }
145
146 let inner_slice = &mut inner[prev_pos..next_pos];
147 v.write_transcription_bytes_exact(inner_slice);
148 self.fs_transcript.absorb_bytes(inner_slice);
149
150 self.stream.set_position(safe_cast!(next_pos, usize, u64)?);
151 Ok(())
152 }
153
154 pub fn write_const_many<T: ConstTranscribable>(
158 &mut self,
159 vs: &[T],
160 ) -> Result<(), TranscriptError> {
161 self.write_const_many_iter::<T, _>(vs, vs.len())
162 }
163
164 pub fn write_const_many_iter<'a, T, I>(
168 &mut self,
169 vs: I,
170 vs_len: usize,
171 ) -> Result<(), TranscriptError>
172 where
173 T: ConstTranscribable + 'a,
174 I: IntoIterator,
175 I::Item: Borrow<T>,
176 {
177 let prev_pos = safe_cast!(self.stream.position(), u64, usize)?;
178 let data_len = mul!(vs_len, T::NUM_BYTES);
179 let next_pos = add!(prev_pos, data_len);
180
181 let inner = self.stream.get_mut();
182 if inner.len() < next_pos {
184 inner.resize(next_pos, 0_u8);
185 }
186
187 for (chunk, v) in inner[prev_pos..next_pos].chunks_mut(T::NUM_BYTES).zip(vs) {
188 v.borrow().write_transcription_bytes_exact(chunk);
189 self.fs_transcript.absorb_bytes(chunk);
190 }
191
192 self.stream.set_position(next_pos as u64);
193 Ok(())
194 }
195
196 fn write_usize(&mut self, value: usize) -> Result<(), TranscriptError> {
197 let value_u64 = safe_cast!(value, usize, u64)?;
198 self.write(&value_u64)
199 }
200
201 pub fn write_merkle_proof(&mut self, proof: &MerkleProof) -> Result<(), TranscriptError> {
202 self.write_usize(proof.leaf_index)?;
204 self.write_usize(proof.leaf_count)?;
205
206 self.write_usize(proof.siblings.len())?;
208
209 self.write_const_many(&proof.siblings)?;
211 Ok(())
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct PcsVerifierTranscript {
218 pub fs_transcript: Blake3Transcript,
222
223 pub stream: Cursor<Vec<u8>>,
226}
227
228impl PcsVerifierTranscript {
229 common_methods!();
230
231 pub fn check_eof(&self) -> Result<(), TranscriptError> {
236 let position = safe_cast!(self.stream.position(), u64, usize)?;
237 let len = self.stream.get_ref().len();
238 if position == len {
239 Ok(())
240 } else {
241 Err(TranscriptError(
242 ErrorKind::InvalidData,
243 format!(
244 "proof stream not fully consumed: {} unread byte(s)",
245 len.saturating_sub(position)
246 ),
247 ))
248 }
249 }
250
251 pub fn read_field_elements<C>(
259 &mut self,
260 cfg: &C,
261 n: usize,
262 ) -> Result<Vec<C::Element>, TranscriptError>
263 where
264 C: BaseFieldConfig,
265 C::Integer: ConstTranscribable,
266 {
267 let ints: Vec<C::Integer> = self.read_const_many(n)?;
268 let modulus = cfg.modulus();
269 if ints.iter().any(|int| *int >= modulus) {
270 return Err(TranscriptError(
271 ErrorKind::InvalidData,
272 "Non-canonical field element".to_owned(),
273 ));
274 }
275 let elems = ints.iter().map(|int| cfg.project(int)).collect_vec();
276 Ok(elems)
277 }
278
279 pub fn read<T: Transcribable>(&mut self) -> Result<T, TranscriptError> {
280 let data_len = if T::LENGTH_NUM_BYTES > 0 {
281 let mut len_buf = vec![0u8; T::LENGTH_NUM_BYTES];
282 self.stream
283 .read_exact(&mut len_buf)
284 .map_err(to_transcript_error)?;
285 self.fs_transcript.absorb_bytes(&len_buf);
286 T::read_num_bytes(&len_buf)
287 } else {
288 T::read_num_bytes(&[])
292 };
293
294 read_stream_slice(&mut self.stream, data_len, |slice| {
295 self.fs_transcript.absorb_bytes(slice);
296 Ok(T::read_transcription_bytes_exact(slice))
297 })
298 }
299
300 pub fn read_const_many<T: ConstTranscribable>(
301 &mut self,
302 n: usize,
303 ) -> Result<Vec<T>, TranscriptError> {
304 read_stream_slice(&mut self.stream, mul!(n, T::NUM_BYTES), |slice| {
305 Ok(slice
306 .chunks(T::NUM_BYTES)
307 .map(|bs| {
308 self.fs_transcript.absorb_bytes(bs);
309 T::read_transcription_bytes_exact(bs)
310 })
311 .collect_vec())
312 })
313 }
314
315 fn read_usize(&mut self) -> Result<usize, TranscriptError> {
316 let value = self.read::<u64>()?;
317 safe_cast!(value, u64, usize)
318 }
319
320 pub fn read_merkle_proof(&mut self) -> Result<MerkleProof, TranscriptError> {
321 let leaf_index = self.read_usize()?;
323 let leaf_count = self.read_usize()?;
324
325 let path_length = self.read_usize()?;
327
328 let merkle_path = self.read_const_many(path_length)?;
330
331 Ok(MerkleProof::new(leaf_index, leaf_count, merkle_path))
332 }
333}
334
335#[inline]
339fn read_stream_slice<T>(
340 stream: &mut Cursor<Vec<u8>>,
341 length: usize,
342 mut action: impl FnMut(&[u8]) -> Result<T, TranscriptError>,
343) -> Result<T, TranscriptError> {
344 let prev_pos = safe_cast!(stream.position(), u64, usize)?;
345 let next_pos = add!(prev_pos, length);
346
347 let stream_vec = stream.get_ref();
348 if next_pos > stream_vec.len() {
349 return Err(TranscriptError(
350 ErrorKind::UnexpectedEof,
351 format!(
352 "Attempted to read beyond the end of the stream: {} + {} exceeds stream length {}",
353 prev_pos,
354 length,
355 stream_vec.len()
356 ),
357 ));
358 }
359 let res = action(&stream_vec[prev_pos..next_pos])?;
360 stream.set_position(safe_cast!(next_pos, usize, u64)?);
361 Ok(res)
362}
363
364fn to_transcript_error(err: std::io::Error) -> TranscriptError {
366 TranscriptError(err.kind(), err.to_string())
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use crate::merkle::MtHash;
373
374 #[allow(unused_macros)]
375 macro_rules! test_read_write {
376 ($write_fn:ident, $read_fn:ident, $original_value:expr, $assert_msg:expr) => {{
378 let comm = ZipPlusCommitment::default();
379 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
380 transcript
381 .$write_fn(&$original_value)
382 .expect(&format!("Failed to write {}", $assert_msg));
383 let mut transcript: PcsVerifierTranscript = transcript.into_verification_transcript();
384 transcript.fs_transcript.absorb_bytes(&comm.root);
385 let read_value = transcript
386 .$read_fn()
387 .expect(&format!("Failed to read {}", $assert_msg));
388 assert_eq!(
389 $original_value, read_value,
390 "{} read does not match original",
391 $assert_msg
392 );
393 }};
394 }
395
396 #[allow(unused_macros)]
397 macro_rules! test_read_write_vec {
398 ($write_fn:ident, $read_fn:ident, $original_values:expr, $assert_msg:expr) => {{
400 let comm = ZipPlusCommitment::default();
401 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
402 transcript
403 .$write_fn(&$original_values)
404 .expect(&format!("Failed to write {}", $assert_msg));
405 let mut transcript: PcsVerifierTranscript = transcript.into_verification_transcript();
406 transcript.fs_transcript.absorb_bytes(&comm.root);
407 let read_values = transcript
408 .$read_fn($original_values.len())
409 .expect(&format!("Failed to read {}", $assert_msg));
410 assert_eq!(
411 $original_values, read_values,
412 "{} read does not match original",
413 $assert_msg
414 );
415 }};
416 }
417
418 #[test]
419 fn test_pcs_transcript_read_write() {
420 let original_hash = MtHash::default();
422 test_read_write!(write, read, original_hash, "hash");
423
424 let original_hashes = vec![MtHash::default(); 1024];
426 test_read_write_vec!(
427 write_const_many,
428 read_const_many,
429 original_hashes,
430 "hashes vector"
431 );
432 }
433
434 const CAP: usize = 1 << 24;
435
436 #[test]
443 fn wire_writes_bind_subsequent_challenges() {
444 let comm = ZipPlusCommitment::default();
445
446 let challenge_after = |payload: &[u64]| {
447 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
448 transcript
449 .write_const_many(payload)
450 .expect("write should succeed");
451 transcript.squeeze_challenge_idx(CAP)
452 };
453
454 assert_ne!(
455 challenge_after(&[1, 2, 3, 4]),
456 challenge_after(&[1, 2, 3, 5]),
457 "changing a written value left the next challenge unchanged: \
458 wire bytes are not entering the transcript"
459 );
460 }
461
462 #[test]
465 fn length_prefixed_writes_bind_subsequent_challenges() {
466 let comm = ZipPlusCommitment::default();
467
468 let challenge_after = |value: u64| {
469 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
470 transcript.write(&value).expect("write should succeed");
471 transcript.squeeze_challenge_idx(CAP)
472 };
473
474 assert_ne!(
475 challenge_after(7),
476 challenge_after(8),
477 "`write` is not absorbing its payload"
478 );
479 }
480
481 #[test]
491 fn transcript_state_is_independent_of_call_chunking() {
492 let comm = ZipPlusCommitment::default();
493 let payload: Vec<u64> = (0..8).collect();
494
495 let mut prover = PcsProverTranscript::new_from_commitment(&comm);
497 prover
498 .write_const_many(&payload[..3])
499 .expect("write should succeed");
500 prover
501 .write_const_many(&payload[3..])
502 .expect("write should succeed");
503 let prover_challenge = prover.squeeze_challenge_idx(CAP);
504
505 let mut verifier = prover.into_verification_transcript();
507 verifier.fs_transcript.absorb_bytes(&comm.root);
508 let read: Vec<u64> = verifier
509 .read_const_many(payload.len())
510 .expect("read should succeed");
511 let verifier_challenge = verifier.squeeze_challenge_idx(CAP);
512
513 assert_eq!(read, payload, "round-trip lost data");
514 assert_eq!(
515 prover_challenge, verifier_challenge,
516 "prover and verifier transcripts diverged on identical data"
517 );
518 verifier
519 .check_eof()
520 .expect("stream should be fully consumed");
521 }
522
523 #[test]
526 fn check_eof_rejects_unread_trailing_bytes() {
527 let comm = ZipPlusCommitment::default();
528 let mut prover = PcsProverTranscript::new_from_commitment(&comm);
529 prover
530 .write_const_many(&[1u64, 2, 3])
531 .expect("write should succeed");
532
533 let mut verifier = prover.into_verification_transcript();
534 verifier.fs_transcript.absorb_bytes(&comm.root);
535 let _: Vec<u64> = verifier.read_const_many(2).expect("read should succeed");
536
537 assert!(
538 verifier.check_eof().is_err(),
539 "check_eof accepted a stream with unread trailing bytes"
540 );
541 }
542}