jf_plonk/circuit/
transcript.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the Jellyfish library.
3
4// You should have received a copy of the MIT License
5// along with the Jellyfish library. If not, see <https://mit-license.org/>.
6
7//! Implementing *native* circuit for rescue transcript
8
9use super::plonk_verifier::*;
10use ark_ec::pairing::Pairing;
11use ark_ff::PrimeField;
12use ark_std::{string::ToString, vec::Vec};
13use core::marker::PhantomData;
14use jf_relation::{
15    gadgets::{
16        ecc::{PointVariable, SWToTEConParam},
17        ultraplonk::mod_arith::FpElemVar,
18    },
19    Circuit,
20    CircuitError::{self, ParameterError},
21    PlonkCircuit, Variable,
22};
23use jf_rescue::{gadgets::RescueNativeGadget, RescueParameter, STATE_SIZE};
24
25/// Struct of variables representing a Rescue transcript type, including
26/// `STATE_SIZE` variables for the state, and a vector of variables for
27/// the transcript.
28pub struct RescueTranscriptVar<F: RescueParameter> {
29    transcript_var: Vec<Variable>,
30    state_var: [Variable; STATE_SIZE],
31    _phantom: PhantomData<F>,
32}
33
34impl<F> RescueTranscriptVar<F>
35where
36    F: RescueParameter + SWToTEConParam,
37{
38    /// create a new RescueTranscriptVar for a given circuit.
39    pub(crate) fn new(circuit: &mut PlonkCircuit<F>) -> Self {
40        Self {
41            transcript_var: Vec::new(),
42            state_var: [circuit.zero(); STATE_SIZE],
43            _phantom: PhantomData,
44        }
45    }
46
47    // append the verification key and the public input
48    pub(crate) fn append_vk_and_pub_input_vars<E: Pairing<BaseField = F>>(
49        &mut self,
50        circuit: &mut PlonkCircuit<F>,
51        vk_var: &VerifyingKeyVar<E>,
52        pub_input: &[FpElemVar<F>],
53    ) -> Result<(), CircuitError> {
54        // to enable a more efficient verifier circuit, we remove
55        // the following messages (c.f. merlin transcript)
56        //  - field_size_in_bits
57        //  - domain size
58        //  - number of inputs
59        //  - wire subsets separators
60
61        // selector commitments
62        for com in vk_var.selector_comms.iter() {
63            // the commitment vars are already in TE form
64            self.transcript_var.push(com.get_x());
65            self.transcript_var.push(com.get_y());
66        }
67        // sigma commitments
68        for com in vk_var.sigma_comms.iter() {
69            // the commitment vars are already in TE form
70            self.transcript_var.push(com.get_x());
71            self.transcript_var.push(com.get_y());
72        }
73        // public input
74        for e in pub_input {
75            let pub_var = e.convert_to_var(circuit)?;
76            self.transcript_var.push(pub_var)
77        }
78        Ok(())
79    }
80
81    // Append the variable to the transcript.
82    // For efficiency purpose, label is not used for rescue FS.
83    pub(crate) fn append_variable(
84        &mut self,
85        _label: &'static [u8],
86        var: &Variable,
87    ) -> Result<(), CircuitError> {
88        self.transcript_var.push(*var);
89
90        Ok(())
91    }
92
93    // Append the message variables to the transcript.
94    // For efficiency purpose, label is not used for rescue FS.
95    pub(crate) fn append_message_vars(
96        &mut self,
97        _label: &'static [u8],
98        msg_vars: &[Variable],
99    ) -> Result<(), CircuitError> {
100        for e in msg_vars.iter() {
101            self.append_variable(_label, e)?;
102        }
103
104        Ok(())
105    }
106
107    // Append a commitment variable (in the form of PointVariable) to the
108    // transcript. The caller needs to make sure that the commitment is
109    // already converted to TE form before generating the variables.
110    // For efficiency purpose, label is not used for rescue FS.
111    pub(crate) fn append_commitment_var(
112        &mut self,
113        _label: &'static [u8],
114        poly_comm_var: &PointVariable,
115    ) -> Result<(), CircuitError> {
116        // push the x and y coordinate of comm to the transcript
117        self.transcript_var.push(poly_comm_var.get_x());
118        self.transcript_var.push(poly_comm_var.get_y());
119
120        Ok(())
121    }
122
123    // Append  a slice of commitment variables (in the form of PointVariable) to the
124    // The caller needs to make sure that the commitment is
125    // already converted to TE form before generating the variables.
126    // transcript For efficiency purpose, label is not used for rescue FS.
127    pub(crate) fn append_commitments_vars(
128        &mut self,
129        _label: &'static [u8],
130        poly_comm_vars: &[PointVariable],
131    ) -> Result<(), CircuitError> {
132        for poly_comm_var in poly_comm_vars.iter() {
133            // push the x and y coordinate of comm to the transcript
134            self.transcript_var.push(poly_comm_var.get_x());
135            self.transcript_var.push(poly_comm_var.get_y());
136        }
137        Ok(())
138    }
139
140    // Append the proof evaluation to the transcript
141    pub(crate) fn append_proof_evaluations_vars(
142        &mut self,
143        circuit: &mut PlonkCircuit<F>,
144        evals: &ProofEvaluationsVar<F>,
145    ) -> Result<(), CircuitError> {
146        for e in &evals.wires_evals {
147            let tmp = e.convert_to_var(circuit)?;
148            self.transcript_var.push(tmp);
149        }
150        for e in &evals.wire_sigma_evals {
151            let tmp = e.convert_to_var(circuit)?;
152            self.transcript_var.push(tmp);
153        }
154        let tmp = evals.perm_next_eval.convert_to_var(circuit)?;
155        self.transcript_var.push(tmp);
156        Ok(())
157    }
158
159    // generate the challenge for the current transcript
160    // and append it to the transcript
161    // For efficiency purpose, label is not used for rescue FS.
162    // Note that this function currently only supports bls12-377
163    // curve due to its decomposition method.
164    //
165    // `_label` is omitted for efficiency.
166    pub(crate) fn get_challenge_var<E>(
167        &mut self,
168        _label: &'static [u8],
169        circuit: &mut PlonkCircuit<F>,
170    ) -> Result<Variable, CircuitError>
171    where
172        E: Pairing,
173    {
174        if !circuit.support_lookup() {
175            return Err(ParameterError("does not support range table".to_string()));
176        }
177
178        if E::ScalarField::MODULUS_BIT_SIZE != 253 || E::BaseField::MODULUS_BIT_SIZE != 377 {
179            return Err(ParameterError(
180                "Curve Parameter does not support for rescue transcript circuit".to_string(),
181            ));
182        }
183
184        // ==================================
185        // This algorithm takes in 3 steps
186        // 1. state: [F: STATE_SIZE] = hash(state|transcript)
187        // 2. challenge = state[0] in Fr
188        // 3. transcript = vec![]
189        // ==================================
190
191        // step 1. state: [F: STATE_SIZE] = hash(state|transcript)
192        let input_var = [self.state_var.as_ref(), self.transcript_var.as_ref()].concat();
193        let res_var =
194            RescueNativeGadget::<F>::rescue_sponge_with_padding(circuit, &input_var, STATE_SIZE)
195                .unwrap();
196        let out_var = res_var[0];
197
198        // step 2. challenge = state[0] in Fr
199        let challenge_var = circuit.truncate(out_var, 248)?;
200
201        // 3. transcript = vec![challenge]
202        // finish and update the states
203        self.state_var.copy_from_slice(&res_var[0..STATE_SIZE]);
204        self.transcript_var = Vec::new();
205
206        Ok(challenge_var)
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use crate::{
214        proof_system::structs::VerifyingKey,
215        transcript::{PlonkTranscript, RescueTranscript},
216    };
217    use ark_bls12_377::Bls12_377;
218    use ark_ec::{
219        short_weierstrass::{Affine, SWCurveConfig},
220        AffineRepr, CurveGroup,
221    };
222    use ark_std::{format, vec, UniformRand};
223    use jf_pcs::prelude::{Commitment, UnivariateVerifierParam};
224    use jf_relation::gadgets::ecc::TEPoint;
225    use jf_utils::{bytes_to_field_elements, field_switching, test_rng};
226
227    const RANGE_BIT_LEN_FOR_TEST: usize = 16;
228    #[test]
229    fn test_rescue_transcript_challenge_circuit() {
230        test_rescue_transcript_challenge_circuit_helper::<Bls12_377, _, _>()
231    }
232    fn test_rescue_transcript_challenge_circuit_helper<E, F, P>()
233    where
234        E: Pairing<BaseField = F, G1Affine = Affine<P>>,
235        F: RescueParameter + SWToTEConParam,
236        P: SWCurveConfig<BaseField = F>,
237    {
238        let mut circuit = PlonkCircuit::<F>::new_ultra_plonk(RANGE_BIT_LEN_FOR_TEST);
239
240        let label = "testing".as_ref();
241
242        let mut transcript_var = RescueTranscriptVar::new(&mut circuit);
243        let mut transcript = RescueTranscript::<F>::new(label);
244
245        for _ in 0..10 {
246            for i in 0..10 {
247                let msg = format!("message {}", i);
248                let vals = bytes_to_field_elements(msg.as_bytes());
249                let message_vars: Vec<Variable> = vals
250                    .iter()
251                    .map(|x| circuit.create_variable(*x).unwrap())
252                    .collect();
253
254                transcript.append_message(label, msg.as_bytes()).unwrap();
255
256                transcript_var
257                    .append_message_vars(label, &message_vars)
258                    .unwrap();
259            }
260
261            let challenge = transcript.get_challenge::<E>(label).unwrap();
262
263            let challenge_var = transcript_var
264                .get_challenge_var::<E>(label, &mut circuit)
265                .unwrap();
266
267            assert_eq!(
268                circuit.witness(challenge_var).unwrap().into_bigint(),
269                field_switching::<_, F>(&challenge).into_bigint()
270            );
271        }
272    }
273
274    #[test]
275    fn test_rescue_transcript_append_vk_and_input_circuit() {
276        test_rescue_transcript_append_vk_and_input_circuit_helper::<Bls12_377, _, _>()
277    }
278    fn test_rescue_transcript_append_vk_and_input_circuit_helper<E, F, P>()
279    where
280        E: Pairing<BaseField = F, G1Affine = Affine<P>>,
281        F: RescueParameter + SWToTEConParam,
282        P: SWCurveConfig<BaseField = F>,
283    {
284        let mut circuit = PlonkCircuit::<F>::new_ultra_plonk(RANGE_BIT_LEN_FOR_TEST);
285
286        let mut rng = test_rng();
287
288        let label = "testing".as_ref();
289
290        let mut transcript_var = RescueTranscriptVar::new(&mut circuit);
291        let mut transcript = RescueTranscript::<F>::new(label);
292
293        let g = E::G1Affine::generator();
294        let h = E::G2Affine::generator();
295        let beta_h = E::G2::rand(&mut rng).into_affine();
296        let open_key: UnivariateVerifierParam<E> = UnivariateVerifierParam {
297            g,
298            h,
299            beta_h,
300            powers_of_h: vec![h, beta_h],
301            powers_of_g: vec![g],
302        };
303
304        let dummy_vk = VerifyingKey {
305            domain_size: 512,
306            num_inputs: 0,
307            sigma_comms: Vec::new(),
308            selector_comms: Vec::new(),
309            k: Vec::new(),
310            open_key: open_key.clone(),
311            is_merged: false,
312            plookup_vk: None,
313        };
314
315        let dummy_vk_var = VerifyingKeyVar::new(&mut circuit, &dummy_vk).unwrap();
316
317        // build challenge from transcript and check for correctness
318        transcript.append_vk_and_pub_input(&dummy_vk, &[]).unwrap();
319        transcript_var
320            .append_vk_and_pub_input_vars::<E>(&mut circuit, &dummy_vk_var, &[])
321            .unwrap();
322
323        let challenge = transcript.get_challenge::<E>(label).unwrap();
324
325        let challenge_var = transcript_var
326            .get_challenge_var::<E>(label, &mut circuit)
327            .unwrap();
328
329        assert_eq!(
330            circuit.witness(challenge_var).unwrap(),
331            field_switching(&challenge)
332        );
333
334        for _ in 0..10 {
335            // inputs
336            let input: Vec<E::ScalarField> =
337                (0..16).map(|_| E::ScalarField::rand(&mut rng)).collect();
338
339            // sigma commitments
340            let sigma_comms: Vec<Commitment<E>> = (0..42)
341                .map(|_| Commitment(E::G1::rand(&mut rng).into_affine()))
342                .collect();
343            let mut sigma_comms_vars: Vec<PointVariable> = Vec::new();
344            for e in sigma_comms.iter() {
345                // convert point into TE form
346                let p: TEPoint<F> = e.0.into();
347                sigma_comms_vars.push(circuit.create_point_variable(p).unwrap());
348            }
349
350            // selector commitments
351            let selector_comms: Vec<Commitment<E>> = (0..33)
352                .map(|_| Commitment(E::G1::rand(&mut rng).into_affine()))
353                .collect();
354            let mut selector_comms_vars: Vec<PointVariable> = Vec::new();
355            for e in selector_comms.iter() {
356                // convert point into TE form
357                let p: TEPoint<F> = e.0.into();
358                selector_comms_vars.push(circuit.create_point_variable(p).unwrap());
359            }
360
361            // k
362            let k: Vec<E::ScalarField> = (0..5).map(|_| E::ScalarField::rand(&mut rng)).collect();
363
364            let vk = VerifyingKey {
365                domain_size: 512,
366                num_inputs: input.len(),
367                sigma_comms,
368                selector_comms,
369                k,
370                open_key: open_key.clone(),
371                is_merged: false,
372                plookup_vk: None,
373            };
374            let vk_var = VerifyingKeyVar::new(&mut circuit, &vk).unwrap();
375
376            // build challenge from transcript and check for correctness
377            transcript.append_vk_and_pub_input(&vk, &input).unwrap();
378            let m = 128;
379            let input_vars: Vec<Variable> = input
380                .iter()
381                .map(|&x| circuit.create_public_variable(field_switching(&x)).unwrap())
382                .collect();
383
384            let input_fp_elem_vars: Vec<FpElemVar<F>> = input_vars
385                .iter()
386                .map(|&x| FpElemVar::new_unchecked(&mut circuit, x, m, None).unwrap())
387                .collect();
388            transcript_var
389                .append_vk_and_pub_input_vars::<E>(&mut circuit, &vk_var, &input_fp_elem_vars)
390                .unwrap();
391
392            let challenge = transcript.get_challenge::<E>(label).unwrap();
393
394            let challenge_var = transcript_var
395                .get_challenge_var::<E>(label, &mut circuit)
396                .unwrap();
397
398            assert_eq!(
399                circuit.witness(challenge_var).unwrap(),
400                field_switching(&challenge)
401            );
402        }
403    }
404}