snark_verifier/system/halo2/transcript/
halo2.rs

1//! Transcript for verifier in [`halo2_proofs`] circuit.
2
3use crate::halo2_proofs;
4use crate::util::arithmetic::FieldExt;
5use crate::{
6    loader::{
7        halo2::{EcPoint, EccInstructions, Halo2Loader, Scalar},
8        native::{self, NativeLoader},
9        Loader, ScalarLoader,
10    },
11    util::{
12        arithmetic::{fe_to_fe, CurveAffine, PrimeField},
13        hash::{OptimizedPoseidonSpec, Poseidon},
14        transcript::{Transcript, TranscriptRead, TranscriptWrite},
15        Itertools,
16    },
17    Error,
18};
19use halo2_proofs::transcript::EncodedChallenge;
20use std::{
21    io::{self, Read, Write},
22    rc::Rc,
23};
24
25/// Encoding that encodes elliptic curve point into native field elements.
26pub trait NativeEncoding<C>: EccInstructions<C>
27where
28    C: CurveAffine,
29{
30    /// Encode.
31    fn encode(
32        &self,
33        ctx: &mut Self::Context,
34        ec_point: &Self::AssignedEcPoint,
35    ) -> Result<Vec<Self::AssignedScalar>, Error>;
36}
37
38/// A way to keep track of what gets read in the transcript.
39#[derive(Clone, Debug)]
40pub enum TranscriptObject<C, L>
41where
42    C: CurveAffine,
43    L: Loader<C>,
44{
45    /// Scalar
46    Scalar(L::LoadedScalar),
47    /// Elliptic curve point
48    EcPoint(L::LoadedEcPoint),
49}
50
51#[derive(Debug)]
52/// Transcript for verifier in [`halo2_proofs`] circuit using poseidon hasher.
53/// Currently It assumes the elliptic curve scalar field is same as native
54/// field.
55pub struct PoseidonTranscript<
56    C,
57    L,
58    S,
59    const T: usize,
60    const RATE: usize,
61    const R_F: usize,
62    const R_P: usize,
63> where
64    C: CurveAffine,
65    L: Loader<C>,
66{
67    loader: L,
68    stream: S,
69    /// Only relevant for Halo2 loader: as elements from `stream` are read, they are assigned as witnesses.
70    /// The loaded witnesses are pushed to `loaded_stream`. This way at the end we have the entire proof transcript
71    /// as loaded witnesses.
72    pub loaded_stream: Vec<TranscriptObject<C, L>>,
73    buf: Poseidon<C::Scalar, <L as ScalarLoader<C::Scalar>>::LoadedScalar, T, RATE>,
74}
75
76impl<C, R, EccChip, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
77    PoseidonTranscript<C, Rc<Halo2Loader<C, EccChip>>, R, T, RATE, R_F, R_P>
78where
79    C: CurveAffine,
80    R: Read,
81    EccChip: NativeEncoding<C>,
82{
83    /// Initialize [`PoseidonTranscript`] given readable or writeable stream for
84    /// verifying or proving with [`NativeLoader`].
85    pub fn new<const SECURE_MDS: usize>(loader: &Rc<Halo2Loader<C, EccChip>>, stream: R) -> Self
86    where
87        C::Scalar: FieldExt,
88    {
89        let buf = Poseidon::new::<R_F, R_P, SECURE_MDS>(loader);
90        Self { loader: loader.clone(), stream, buf, loaded_stream: vec![] }
91    }
92
93    /// Initialize [`PoseidonTranscript`] from a precomputed spec of round constants and MDS matrix because computing the constants is expensive.
94    pub fn from_spec(
95        loader: &Rc<Halo2Loader<C, EccChip>>,
96        stream: R,
97        spec: OptimizedPoseidonSpec<C::Scalar, T, RATE>,
98    ) -> Self {
99        let buf = Poseidon::from_spec(loader, spec);
100        Self { loader: loader.clone(), stream, buf, loaded_stream: vec![] }
101    }
102
103    /// Clear the buffer and set the stream to a new one. Effectively the same as starting from a new transcript.
104    pub fn new_stream(&mut self, stream: R) {
105        self.buf.clear();
106        self.loaded_stream.clear();
107        self.stream = stream;
108    }
109}
110
111impl<C, R, EccChip, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
112    Transcript<C, Rc<Halo2Loader<C, EccChip>>>
113    for PoseidonTranscript<C, Rc<Halo2Loader<C, EccChip>>, R, T, RATE, R_F, R_P>
114where
115    C: CurveAffine,
116    R: Read,
117    EccChip: NativeEncoding<C>,
118{
119    fn loader(&self) -> &Rc<Halo2Loader<C, EccChip>> {
120        &self.loader
121    }
122
123    fn squeeze_challenge(&mut self) -> Scalar<C, EccChip> {
124        self.buf.squeeze()
125    }
126
127    fn common_scalar(&mut self, scalar: &Scalar<C, EccChip>) -> Result<(), Error> {
128        self.buf.update(&[scalar.clone()]);
129        Ok(())
130    }
131
132    fn common_ec_point(&mut self, ec_point: &EcPoint<C, EccChip>) -> Result<(), Error> {
133        let encoded = self
134            .loader
135            .ecc_chip()
136            .encode(&mut self.loader.ctx_mut(), &ec_point.assigned())
137            .map(|encoded| {
138                encoded
139                    .into_iter()
140                    .map(|encoded| self.loader.scalar_from_assigned(encoded))
141                    .collect_vec()
142            })
143            .map_err(|_| {
144                Error::Transcript(
145                    io::ErrorKind::Other,
146                    "Failed to encode elliptic curve point into native field elements".to_string(),
147                )
148            })?;
149        self.buf.update(&encoded);
150        Ok(())
151    }
152}
153
154impl<C, R, EccChip, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
155    TranscriptRead<C, Rc<Halo2Loader<C, EccChip>>>
156    for PoseidonTranscript<C, Rc<Halo2Loader<C, EccChip>>, R, T, RATE, R_F, R_P>
157where
158    C: CurveAffine,
159    R: Read,
160    EccChip: NativeEncoding<C>,
161{
162    fn read_scalar(&mut self) -> Result<Scalar<C, EccChip>, Error> {
163        let scalar = {
164            let mut data = <C::Scalar as PrimeField>::Repr::default();
165            self.stream.read_exact(data.as_mut()).unwrap();
166            C::Scalar::from_repr(data).unwrap()
167        };
168        let scalar = self.loader.assign_scalar(scalar);
169        self.loaded_stream.push(TranscriptObject::Scalar(scalar.clone()));
170        self.common_scalar(&scalar)?;
171        Ok(scalar)
172    }
173
174    fn read_ec_point(&mut self) -> Result<EcPoint<C, EccChip>, Error> {
175        let ec_point = {
176            let mut compressed = C::Repr::default();
177            self.stream.read_exact(compressed.as_mut()).unwrap();
178            C::from_bytes(&compressed).unwrap()
179        };
180        let ec_point = self.loader.assign_ec_point(ec_point);
181        self.loaded_stream.push(TranscriptObject::EcPoint(ec_point.clone()));
182        self.common_ec_point(&ec_point)?;
183        Ok(ec_point)
184    }
185}
186
187impl<C: CurveAffine, S, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
188    PoseidonTranscript<C, NativeLoader, S, T, RATE, R_F, R_P>
189{
190    /// Initialize [`PoseidonTranscript`] given readable or writeable stream for
191    /// verifying or proving with [`NativeLoader`].
192    pub fn new<const SECURE_MDS: usize>(stream: S) -> Self
193    where
194        C::Scalar: FieldExt,
195    {
196        Self {
197            loader: NativeLoader,
198            stream,
199            buf: Poseidon::new::<R_F, R_P, SECURE_MDS>(&NativeLoader),
200            loaded_stream: vec![],
201        }
202    }
203
204    /// Initialize [`PoseidonTranscript`] from a precomputed spec of round constants and MDS matrix because computing the constants is expensive.
205    pub fn from_spec(stream: S, spec: OptimizedPoseidonSpec<C::Scalar, T, RATE>) -> Self {
206        Self {
207            loader: NativeLoader,
208            stream,
209            buf: Poseidon::from_spec(&NativeLoader, spec),
210            loaded_stream: vec![],
211        }
212    }
213
214    /// Clear the buffer and set the stream to a new one. Effectively the same as starting from a new transcript.
215    pub fn new_stream(&mut self, stream: S) {
216        self.buf.clear();
217        self.loaded_stream.clear();
218        self.stream = stream;
219    }
220}
221
222impl<C: CurveAffine, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
223    PoseidonTranscript<C, NativeLoader, Vec<u8>, T, RATE, R_F, R_P>
224{
225    /// Clear the buffer and stream.
226    pub fn clear(&mut self) {
227        self.buf.clear();
228        self.loaded_stream.clear();
229        self.stream.clear();
230    }
231}
232
233impl<C: CurveAffine, S, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
234    Transcript<C, NativeLoader> for PoseidonTranscript<C, NativeLoader, S, T, RATE, R_F, R_P>
235{
236    fn loader(&self) -> &NativeLoader {
237        &native::LOADER
238    }
239
240    fn squeeze_challenge(&mut self) -> C::Scalar {
241        self.buf.squeeze()
242    }
243
244    fn common_scalar(&mut self, scalar: &C::Scalar) -> Result<(), Error> {
245        self.buf.update(&[*scalar]);
246        Ok(())
247    }
248
249    fn common_ec_point(&mut self, ec_point: &C) -> Result<(), Error> {
250        let encoded: Vec<_> = Option::from(ec_point.coordinates().map(|coordinates| {
251            [coordinates.x(), coordinates.y()].into_iter().cloned().map(fe_to_fe).collect_vec()
252        }))
253        .ok_or_else(|| {
254            Error::Transcript(
255                io::ErrorKind::Other,
256                "Invalid elliptic curve point encoding in proof".to_string(),
257            )
258        })?;
259        self.buf.update(&encoded);
260        Ok(())
261    }
262}
263
264impl<C, R, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
265    TranscriptRead<C, NativeLoader> for PoseidonTranscript<C, NativeLoader, R, T, RATE, R_F, R_P>
266where
267    C: CurveAffine,
268    R: Read,
269{
270    fn read_scalar(&mut self) -> Result<C::Scalar, Error> {
271        let mut data = <C::Scalar as PrimeField>::Repr::default();
272        self.stream
273            .read_exact(data.as_mut())
274            .map_err(|err| Error::Transcript(err.kind(), err.to_string()))?;
275        let scalar = C::Scalar::from_repr_vartime(data).ok_or_else(|| {
276            Error::Transcript(io::ErrorKind::Other, "Invalid scalar encoding in proof".to_string())
277        })?;
278        self.loaded_stream.push(TranscriptObject::Scalar(scalar));
279        self.common_scalar(&scalar)?;
280        Ok(scalar)
281    }
282
283    fn read_ec_point(&mut self) -> Result<C, Error> {
284        let mut data = C::Repr::default();
285        self.stream
286            .read_exact(data.as_mut())
287            .map_err(|err| Error::Transcript(err.kind(), err.to_string()))?;
288        let ec_point = Option::<C>::from(C::from_bytes(&data)).ok_or_else(|| {
289            Error::Transcript(
290                io::ErrorKind::Other,
291                "Invalid elliptic curve point encoding in proof".to_string(),
292            )
293        })?;
294        self.loaded_stream.push(TranscriptObject::EcPoint(ec_point));
295        self.common_ec_point(&ec_point)?;
296        Ok(ec_point)
297    }
298}
299
300impl<C, W, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
301    PoseidonTranscript<C, NativeLoader, W, T, RATE, R_F, R_P>
302where
303    C: CurveAffine,
304    W: Write,
305{
306    /// Returns mutable `stream`.
307    pub fn stream_mut(&mut self) -> &mut W {
308        &mut self.stream
309    }
310
311    /// Finalize transcript and returns `stream`.
312    pub fn finalize(self) -> W {
313        self.stream
314    }
315}
316
317impl<C, W, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize> TranscriptWrite<C>
318    for PoseidonTranscript<C, NativeLoader, W, T, RATE, R_F, R_P>
319where
320    C: CurveAffine,
321    W: Write,
322{
323    fn write_scalar(&mut self, scalar: C::Scalar) -> Result<(), Error> {
324        self.common_scalar(&scalar)?;
325        let data = scalar.to_repr();
326        self.stream_mut().write_all(data.as_ref()).map_err(|err| {
327            Error::Transcript(err.kind(), "Failed to write scalar to transcript".to_string())
328        })
329    }
330
331    fn write_ec_point(&mut self, ec_point: C) -> Result<(), Error> {
332        self.common_ec_point(&ec_point)?;
333        let data = ec_point.to_bytes();
334        self.stream_mut().write_all(data.as_ref()).map_err(|err| {
335            Error::Transcript(
336                err.kind(),
337                "Failed to write elliptic curve to transcript".to_string(),
338            )
339        })
340    }
341}
342
343/// [`EncodedChallenge`] implemented for verifier in [`halo2_proofs`] circuit.
344/// Currently It assumes the elliptic curve scalar field is same as native
345/// field.
346#[derive(Debug)]
347pub struct ChallengeScalar<C: CurveAffine>(C::Scalar);
348
349impl<C: CurveAffine> EncodedChallenge<C> for ChallengeScalar<C> {
350    type Input = C::Scalar;
351
352    fn new(challenge_input: &C::Scalar) -> Self {
353        ChallengeScalar(*challenge_input)
354    }
355
356    fn get_scalar(&self) -> C::Scalar {
357        self.0
358    }
359}
360
361impl<C: CurveAffine, S, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
362    halo2_proofs::transcript::Transcript<C, ChallengeScalar<C>>
363    for PoseidonTranscript<C, NativeLoader, S, T, RATE, R_F, R_P>
364{
365    fn squeeze_challenge(&mut self) -> ChallengeScalar<C> {
366        ChallengeScalar::new(&Transcript::squeeze_challenge(self))
367    }
368
369    fn common_point(&mut self, ec_point: C) -> io::Result<()> {
370        match Transcript::common_ec_point(self, &ec_point) {
371            Err(Error::Transcript(kind, msg)) => Err(io::Error::new(kind, msg)),
372            Err(_) => unreachable!(),
373            _ => Ok(()),
374        }
375    }
376
377    fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> {
378        match Transcript::common_scalar(self, &scalar) {
379            Err(Error::Transcript(kind, msg)) => Err(io::Error::new(kind, msg)),
380            Err(_) => unreachable!(),
381            _ => Ok(()),
382        }
383    }
384}
385
386impl<C, R, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
387    halo2_proofs::transcript::TranscriptRead<C, ChallengeScalar<C>>
388    for PoseidonTranscript<C, NativeLoader, R, T, RATE, R_F, R_P>
389where
390    C: CurveAffine,
391    R: Read,
392{
393    fn read_point(&mut self) -> io::Result<C> {
394        match TranscriptRead::read_ec_point(self) {
395            Err(Error::Transcript(kind, msg)) => Err(io::Error::new(kind, msg)),
396            Err(_) => unreachable!(),
397            Ok(value) => Ok(value),
398        }
399    }
400
401    fn read_scalar(&mut self) -> io::Result<C::Scalar> {
402        match TranscriptRead::read_scalar(self) {
403            Err(Error::Transcript(kind, msg)) => Err(io::Error::new(kind, msg)),
404            Err(_) => unreachable!(),
405            Ok(value) => Ok(value),
406        }
407    }
408}
409
410impl<C, R, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
411    halo2_proofs::transcript::TranscriptReadBuffer<R, C, ChallengeScalar<C>>
412    for PoseidonTranscript<C, NativeLoader, R, T, RATE, R_F, R_P>
413where
414    C: CurveAffine,
415    C::Scalar: FieldExt,
416    R: Read,
417{
418    fn init(reader: R) -> Self {
419        Self::new::<0>(reader)
420    }
421}
422
423impl<C, W, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
424    halo2_proofs::transcript::TranscriptWrite<C, ChallengeScalar<C>>
425    for PoseidonTranscript<C, NativeLoader, W, T, RATE, R_F, R_P>
426where
427    C: CurveAffine,
428    W: Write,
429{
430    fn write_point(&mut self, ec_point: C) -> io::Result<()> {
431        halo2_proofs::transcript::Transcript::<C, ChallengeScalar<C>>::common_point(
432            self, ec_point,
433        )?;
434        let data = ec_point.to_bytes();
435        self.stream_mut().write_all(data.as_ref())
436    }
437
438    fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> {
439        halo2_proofs::transcript::Transcript::<C, ChallengeScalar<C>>::common_scalar(self, scalar)?;
440        let data = scalar.to_repr();
441        self.stream_mut().write_all(data.as_ref())
442    }
443}
444
445impl<C, W, const T: usize, const RATE: usize, const R_F: usize, const R_P: usize>
446    halo2_proofs::transcript::TranscriptWriterBuffer<W, C, ChallengeScalar<C>>
447    for PoseidonTranscript<C, NativeLoader, W, T, RATE, R_F, R_P>
448where
449    C: CurveAffine,
450    C::Scalar: FieldExt,
451    W: Write,
452{
453    fn init(writer: W) -> Self {
454        Self::new::<0>(writer)
455    }
456
457    fn finalize(self) -> W {
458        self.finalize()
459    }
460}
461
462mod halo2_lib {
463    use crate::system::halo2::transcript::halo2::NativeEncoding;
464    use halo2_base::utils::{BigPrimeField, CurveAffineExt};
465    use halo2_ecc::ecc::BaseFieldEccChip;
466
467    impl<C: CurveAffineExt> NativeEncoding<C> for BaseFieldEccChip<'_, C>
468    where
469        C::Scalar: BigPrimeField,
470        C::Base: BigPrimeField,
471    {
472        fn encode(
473            &self,
474            _: &mut Self::Context,
475            ec_point: &Self::AssignedEcPoint,
476        ) -> Result<Vec<Self::AssignedScalar>, crate::Error> {
477            Ok(vec![*ec_point.x().native(), *ec_point.y().native()])
478        }
479    }
480}