Skip to main content

miden_stark_transcript/
verifier.rs

1//! Verifier-side transcript channel.
2
3use alloc::vec::Vec;
4
5use p3_challenger::{CanSample, CanSampleBits, CanSampleUniformBits};
6use p3_field::{BasedVectorSpace, Field};
7use thiserror::Error;
8
9use crate::{
10    TranscriptData,
11    channel::{Channel, TranscriptChallenger},
12};
13
14/// Verifier channel that reads transcript data and observes into the challenger.
15#[derive(Clone, Debug)]
16pub struct VerifierTranscript<'a, F, C, Ch> {
17    challenger: Ch,
18    fields: &'a [F],
19    commitments: &'a [C],
20}
21
22impl<'a, F, C, Ch> VerifierTranscript<'a, F, C, Ch> {
23    /// Creates a new verifier transcript backed by the provided challenger.
24    pub fn new(challenger: Ch, fields: &'a [F], commitments: &'a [C]) -> Self {
25        Self { challenger, fields, commitments }
26    }
27
28    /// Creates a verifier transcript backed by a `TranscriptData` container.
29    pub fn from_data(challenger: Ch, data: &'a TranscriptData<F, C>) -> Self {
30        let (fields, commitments) = data.as_slices();
31        Self::new(challenger, fields, commitments)
32    }
33
34    /// Finalize the transcript, checking emptiness and producing a binding digest.
35    ///
36    /// Returns [`TranscriptError::TrailingData`] if any unread fields or commitments
37    /// remain. On success, delegates to
38    /// [`CanFinalizeDigest::finalize`](p3_challenger::CanFinalizeDigest::finalize) on the inner
39    /// challenger — the digest must match the prover's digest for the proof to be valid.
40    pub fn finalize(self) -> Result<Ch::Digest, TranscriptError>
41    where
42        F: Field,
43        C: Clone,
44        Ch: TranscriptChallenger<F, C>,
45    {
46        if !self.fields.is_empty() || !self.commitments.is_empty() {
47            return Err(TranscriptError::TrailingData);
48        }
49        Ok(self.challenger.finalize())
50    }
51
52    /// Returns the total byte size of the remaining unconsumed transcript data.
53    pub fn size_in_bytes(&self) -> usize {
54        size_of_val(self.fields) + size_of_val(self.commitments)
55    }
56}
57
58/// Verifier-side channel interface for transcript operations.
59pub trait VerifierChannel: Channel {
60    fn receive_field_slice(&mut self, count: usize) -> Result<&[Self::F], TranscriptError>;
61
62    fn receive_commitment_slice(
63        &mut self,
64        count: usize,
65    ) -> Result<&[Self::Commitment], TranscriptError>;
66
67    fn receive_field(&mut self) -> Result<&Self::F, TranscriptError> {
68        self.receive_field_slice(1).map(|values| values.first().unwrap())
69    }
70
71    fn receive_algebra_element<A>(&mut self) -> Result<A, TranscriptError>
72    where
73        Self::F: Field,
74        A: BasedVectorSpace<Self::F>,
75    {
76        let coeffs = self.receive_field_slice(A::DIMENSION)?;
77        Ok(A::from_basis_coefficients_slice(coeffs).unwrap())
78    }
79
80    fn receive_algebra_slice<A>(&mut self, count: usize) -> Result<Vec<A>, TranscriptError>
81    where
82        Self::F: Field,
83        A: BasedVectorSpace<Self::F>,
84    {
85        let mut values = Vec::with_capacity(count);
86        for _ in 0..count {
87            let coeffs = self.receive_field_slice(A::DIMENSION)?;
88            values.push(A::from_basis_coefficients_slice(coeffs).unwrap());
89        }
90        Ok(values)
91    }
92
93    fn receive_commitment(&mut self) -> Result<&Self::Commitment, TranscriptError> {
94        self.receive_commitment_slice(1).map(|values| values.first().unwrap())
95    }
96
97    fn receive_hint_field_slice(&mut self, count: usize) -> Result<&[Self::F], TranscriptError>;
98
99    fn receive_hint_commitment_slice(
100        &mut self,
101        count: usize,
102    ) -> Result<&[Self::Commitment], TranscriptError>;
103
104    fn receive_hint_field(&mut self) -> Result<&Self::F, TranscriptError> {
105        self.receive_hint_field_slice(1).map(|values| values.first().unwrap())
106    }
107
108    /// Read exactly `N` hint field elements as a fixed-size array.
109    fn receive_hint_field_array<const N: usize>(&mut self) -> Result<[Self::F; N], TranscriptError>
110    where
111        Self::F: Copy,
112    {
113        self.receive_hint_field_slice(N)?
114            .try_into()
115            .map_err(|_| TranscriptError::NoMoreFields)
116    }
117
118    fn receive_hint_commitment(&mut self) -> Result<&Self::Commitment, TranscriptError> {
119        self.receive_hint_commitment_slice(1).map(|values| values.first().unwrap())
120    }
121
122    fn grind(&mut self, bits: usize) -> Result<Self::F, TranscriptError>;
123
124    fn is_empty(&self) -> bool;
125}
126
127impl<'a, F, C, Ch> Channel for VerifierTranscript<'a, F, C, Ch>
128where
129    F: Field,
130    C: Clone,
131    Ch: TranscriptChallenger<F, C>,
132{
133    type F = F;
134    type Commitment = C;
135    type Challenger = Ch;
136
137    fn sample(&mut self) -> F {
138        CanSample::<F>::sample(&mut self.challenger)
139    }
140
141    fn sample_bits(&mut self, bits: usize) -> usize {
142        self.challenger.sample_bits(bits)
143    }
144}
145
146impl<'a, F, C, Ch> VerifierChannel for VerifierTranscript<'a, F, C, Ch>
147where
148    F: Field,
149    C: Clone,
150    Ch: TranscriptChallenger<F, C>,
151{
152    // === Observed data ===
153    fn receive_field_slice(&mut self, count: usize) -> Result<&'a [Self::F], TranscriptError> {
154        let values = pop_slice(&mut self.fields, count).ok_or(TranscriptError::NoMoreFields)?;
155        self.challenger.observe_slice(values);
156        Ok(values)
157    }
158
159    fn receive_commitment_slice(
160        &mut self,
161        count: usize,
162    ) -> Result<&'a [Self::Commitment], TranscriptError> {
163        let values =
164            pop_slice(&mut self.commitments, count).ok_or(TranscriptError::NoMoreCommitments)?;
165        self.challenger.observe_slice(values);
166        Ok(values)
167    }
168
169    fn receive_hint_field_slice(&mut self, count: usize) -> Result<&'a [Self::F], TranscriptError> {
170        pop_slice(&mut self.fields, count).ok_or(TranscriptError::NoMoreFields)
171    }
172
173    fn receive_hint_commitment_slice(
174        &mut self,
175        count: usize,
176    ) -> Result<&'a [Self::Commitment], TranscriptError> {
177        pop_slice(&mut self.commitments, count).ok_or(TranscriptError::NoMoreCommitments)
178    }
179
180    fn grind(&mut self, bits: usize) -> Result<Self::F, TranscriptError> {
181        let (witness, rest) = self.fields.split_first().ok_or(TranscriptError::NoMoreFields)?;
182        self.fields = rest;
183        if self.challenger.check_witness(bits, *witness) {
184            Ok(*witness)
185        } else {
186            Err(TranscriptError::InvalidGrinding)
187        }
188    }
189
190    fn is_empty(&self) -> bool {
191        self.fields.is_empty() && self.commitments.is_empty()
192    }
193}
194
195impl<'a, F, C, Ch, T> CanSample<T> for VerifierTranscript<'a, F, C, Ch>
196where
197    Ch: CanSample<T>,
198{
199    #[inline]
200    fn sample(&mut self) -> T {
201        self.challenger.sample()
202    }
203}
204
205impl<'a, F, C, Ch> CanSampleBits<usize> for VerifierTranscript<'a, F, C, Ch>
206where
207    Ch: CanSampleBits<usize>,
208{
209    #[inline]
210    fn sample_bits(&mut self, bits: usize) -> usize {
211        self.challenger.sample_bits(bits)
212    }
213}
214
215impl<'a, F, C, Ch> CanSampleUniformBits<F> for VerifierTranscript<'a, F, C, Ch>
216where
217    Ch: CanSampleUniformBits<F>,
218{
219    #[inline]
220    fn sample_uniform_bits<const RESAMPLE: bool>(
221        &mut self,
222        bits: usize,
223    ) -> Result<usize, p3_challenger::ResamplingError> {
224        self.challenger.sample_uniform_bits::<RESAMPLE>(bits)
225    }
226}
227
228fn pop_slice<'a, T>(values: &mut &'a [T], count: usize) -> Option<&'a [T]> {
229    if values.len() < count {
230        return None;
231    }
232    let (slice, rest) = values.split_at(count);
233    *values = rest;
234    Some(slice)
235}
236
237/// Errors that can occur during transcript consumption.
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
239pub enum TranscriptError {
240    #[error("no more field elements in transcript")]
241    NoMoreFields,
242    #[error("no more commitments in transcript")]
243    NoMoreCommitments,
244    #[error("invalid grinding witness")]
245    InvalidGrinding,
246    #[error("trailing data in transcript")]
247    TrailingData,
248}