snark_verifier_sdk/
halo2.rs

1use super::{read_instances, write_instances, CircuitExt, PlonkSuccinctVerifier, Snark};
2#[cfg(feature = "display")]
3use ark_std::{end_timer, start_timer};
4use halo2_base::halo2_proofs;
5pub use halo2_base::poseidon::hasher::spec::OptimizedPoseidonSpec;
6use halo2_proofs::{
7    circuit::Layouter,
8    halo2curves::{
9        bn256::{Bn256, Fr, G1Affine},
10        group::ff::Field,
11    },
12    plonk::{
13        create_proof, keygen_vk, verify_proof, Circuit, ConstraintSystem, Error, ProvingKey,
14        VerifyingKey,
15    },
16    poly::{
17        commitment::{ParamsProver, Prover, Verifier},
18        kzg::{
19            commitment::{KZGCommitmentScheme, ParamsKZG},
20            msm::DualMSM,
21            multiopen::{ProverGWC, ProverSHPLONK, VerifierGWC, VerifierSHPLONK},
22            strategy::{AccumulatorStrategy, GuardKZG},
23        },
24        VerificationStrategy,
25    },
26};
27use itertools::Itertools;
28use lazy_static::lazy_static;
29use rand::{rngs::StdRng, SeedableRng};
30use snark_verifier::{
31    cost::CostEstimation,
32    loader::native::NativeLoader,
33    pcs::{
34        kzg::{KzgAccumulator, KzgAsVerifyingKey, KzgSuccinctVerifyingKey},
35        AccumulationScheme, PolynomialCommitmentScheme, Query,
36    },
37    system::halo2::{compile, Config},
38    util::arithmetic::Rotation,
39    util::transcript::TranscriptWrite,
40    verifier::plonk::{PlonkProof, PlonkProtocol},
41};
42use std::{
43    fs::{self, File},
44    marker::PhantomData,
45    path::Path,
46};
47
48pub mod aggregation;
49pub mod utils;
50
51// Poseidon parameters
52// We use the same ones Scroll uses for security: https://github.com/scroll-tech/poseidon-circuit/blob/714f50c7572a4ff6f2b1fa51a9604a99cd7b6c71/src/poseidon/primitives/bn256/fp.rs
53// Verify generated constants: https://github.com/scroll-tech/poseidon-circuit/blob/714f50c7572a4ff6f2b1fa51a9604a99cd7b6c71/src/poseidon/primitives/bn256/mod.rs#L65
54const T: usize = 3;
55const RATE: usize = 2;
56const R_F: usize = 8; // https://github.com/scroll-tech/poseidon-circuit/blob/714f50c7572a4ff6f2b1fa51a9604a99cd7b6c71/src/poseidon/primitives/p128pow5t3.rs#L26
57const R_P: usize = 57; // https://github.com/scroll-tech/poseidon-circuit/blob/714f50c7572a4ff6f2b1fa51a9604a99cd7b6c71/src/poseidon/primitives/bn256/mod.rs#L8
58const SECURE_MDS: usize = 0;
59
60pub type PoseidonTranscript<L, S> =
61    snark_verifier::system::halo2::transcript::halo2::PoseidonTranscript<
62        G1Affine,
63        L,
64        S,
65        T,
66        RATE,
67        R_F,
68        R_P,
69    >;
70
71lazy_static! {
72    pub static ref POSEIDON_SPEC: OptimizedPoseidonSpec<Fr, T, RATE> =
73        OptimizedPoseidonSpec::new::<R_F, R_P, SECURE_MDS>();
74}
75
76/// Generates a native proof using either SHPLONK or GWC proving method. Uses Poseidon for Fiat-Shamir.
77///
78/// Caches the instances and proof if `path = Some(instance_path, proof_path)` is specified.
79pub fn gen_proof<'params, C, P, V>(
80    // TODO: pass Option<&'params ParamsKZG<Bn256>> but hard to get lifetimes to work with `Cow`
81    params: &'params ParamsKZG<Bn256>,
82    pk: &ProvingKey<G1Affine>,
83    circuit: C,
84    instances: Vec<Vec<Fr>>,
85    path: Option<(&Path, &Path)>,
86) -> Vec<u8>
87where
88    C: Circuit<Fr>,
89    P: Prover<'params, KZGCommitmentScheme<Bn256>>,
90    V: Verifier<
91        'params,
92        KZGCommitmentScheme<Bn256>,
93        Guard = GuardKZG<'params, Bn256>,
94        MSMAccumulator = DualMSM<'params, Bn256>,
95    >,
96{
97    if let Some((instance_path, proof_path)) = path {
98        let cached_instances = read_instances(instance_path);
99        if matches!(cached_instances, Ok(tmp) if tmp == instances) && proof_path.exists() {
100            #[cfg(feature = "display")]
101            let read_time = start_timer!(|| format!("Reading proof from {proof_path:?}"));
102
103            let proof = fs::read(proof_path).unwrap();
104
105            #[cfg(feature = "display")]
106            end_timer!(read_time);
107            return proof;
108        }
109    }
110
111    let instances = instances.iter().map(Vec::as_slice).collect_vec();
112
113    #[cfg(feature = "display")]
114    let proof_time = start_timer!(|| "Create proof");
115
116    let mut transcript =
117        PoseidonTranscript::<NativeLoader, Vec<u8>>::from_spec(vec![], POSEIDON_SPEC.clone());
118    let rng = StdRng::from_entropy();
119    create_proof::<_, P, _, _, _, _>(params, pk, &[circuit], &[&instances], rng, &mut transcript)
120        .unwrap();
121    let proof = transcript.finalize();
122
123    #[cfg(feature = "display")]
124    end_timer!(proof_time);
125
126    // validate proof before caching
127    assert!(
128        {
129            let mut transcript_read = PoseidonTranscript::<NativeLoader, &[u8]>::from_spec(
130                &proof[..],
131                POSEIDON_SPEC.clone(),
132            );
133            VerificationStrategy::<_, V>::finalize(
134                verify_proof::<_, V, _, _, _>(
135                    params.verifier_params(),
136                    pk.get_vk(),
137                    AccumulatorStrategy::new(params.verifier_params()),
138                    &[instances.as_slice()],
139                    &mut transcript_read,
140                )
141                .unwrap(),
142            )
143        },
144        "SNARK proof failed to verify"
145    );
146
147    if let Some((instance_path, proof_path)) = path {
148        write_instances(&instances, instance_path);
149        fs::write(proof_path, &proof).unwrap();
150    }
151
152    proof
153}
154
155/// Generates a native proof using original Plonk (GWC '19) multi-open scheme. Uses Poseidon for Fiat-Shamir.
156///
157/// Caches the instances and proof if `path = Some(instance_path, proof_path)` is specified.
158pub fn gen_proof_gwc<C: Circuit<Fr>>(
159    params: &ParamsKZG<Bn256>,
160    pk: &ProvingKey<G1Affine>,
161    circuit: C,
162    instances: Vec<Vec<Fr>>,
163    path: Option<(&Path, &Path)>,
164) -> Vec<u8> {
165    gen_proof::<C, ProverGWC<_>, VerifierGWC<_>>(params, pk, circuit, instances, path)
166}
167
168/// Generates a native proof using SHPLONK multi-open scheme. Uses Poseidon for Fiat-Shamir.
169///
170/// Caches the instances and proof if `path` is specified.
171pub fn gen_proof_shplonk<C: Circuit<Fr>>(
172    params: &ParamsKZG<Bn256>,
173    pk: &ProvingKey<G1Affine>,
174    circuit: C,
175    instances: Vec<Vec<Fr>>,
176    path: Option<(&Path, &Path)>,
177) -> Vec<u8> {
178    gen_proof::<C, ProverSHPLONK<_>, VerifierSHPLONK<_>>(params, pk, circuit, instances, path)
179}
180
181/// Generates a SNARK using either SHPLONK or GWC multi-open scheme. Uses Poseidon for Fiat-Shamir.
182///
183/// Tries to first deserialize from / later serialize the entire SNARK into `path` if specified.
184/// Serialization is done using `bincode`.
185pub fn gen_snark<'params, ConcreteCircuit, P, V>(
186    params: &'params ParamsKZG<Bn256>,
187    pk: &ProvingKey<G1Affine>,
188    circuit: ConcreteCircuit,
189    path: Option<impl AsRef<Path>>,
190) -> Snark
191where
192    ConcreteCircuit: CircuitExt<Fr>,
193    P: Prover<'params, KZGCommitmentScheme<Bn256>>,
194    V: Verifier<
195        'params,
196        KZGCommitmentScheme<Bn256>,
197        Guard = GuardKZG<'params, Bn256>,
198        MSMAccumulator = DualMSM<'params, Bn256>,
199    >,
200{
201    if let Some(path) = &path {
202        if let Ok(snark) = read_snark(path) {
203            return snark;
204        }
205    }
206    let protocol = compile(
207        params,
208        pk.get_vk(),
209        Config::kzg()
210            .with_num_instance(circuit.num_instance())
211            .with_accumulator_indices(ConcreteCircuit::accumulator_indices()),
212    );
213
214    let instances = circuit.instances();
215    let proof = gen_proof::<ConcreteCircuit, P, V>(params, pk, circuit, instances.clone(), None);
216
217    let snark = Snark::new(protocol, instances, proof);
218    if let Some(path) = &path {
219        let f = File::create(path).unwrap();
220        #[cfg(feature = "display")]
221        let write_time = start_timer!(|| "Write SNARK");
222        bincode::serialize_into(f, &snark).unwrap();
223        #[cfg(feature = "display")]
224        end_timer!(write_time);
225    }
226    #[allow(clippy::let_and_return)]
227    snark
228}
229
230/// Generates a SNARK using GWC multi-open scheme. Uses Poseidon for Fiat-Shamir.
231///
232/// Tries to first deserialize from / later serialize the entire SNARK into `path` if specified.
233/// Serialization is done using `bincode`.
234pub fn gen_snark_gwc<ConcreteCircuit: CircuitExt<Fr>>(
235    params: &ParamsKZG<Bn256>,
236    pk: &ProvingKey<G1Affine>,
237    circuit: ConcreteCircuit,
238    path: Option<impl AsRef<Path>>,
239) -> Snark {
240    gen_snark::<ConcreteCircuit, ProverGWC<_>, VerifierGWC<_>>(params, pk, circuit, path)
241}
242
243/// Generates a SNARK using SHPLONK multi-open scheme. Uses Poseidon for Fiat-Shamir.
244///
245/// Tries to first deserialize from / later serialize the entire SNARK into `path` if specified.
246/// Serialization is done using `bincode`.
247pub fn gen_snark_shplonk<ConcreteCircuit: CircuitExt<Fr>>(
248    params: &ParamsKZG<Bn256>,
249    pk: &ProvingKey<G1Affine>,
250    circuit: ConcreteCircuit,
251    path: Option<impl AsRef<Path>>,
252) -> Snark {
253    gen_snark::<ConcreteCircuit, ProverSHPLONK<_>, VerifierSHPLONK<_>>(params, pk, circuit, path)
254}
255
256/// Tries to deserialize a SNARK from the specified `path` using `bincode`.
257///
258/// WARNING: The user must keep track of whether the SNARK was generated using the GWC or SHPLONK multi-open scheme.
259pub fn read_snark(path: impl AsRef<Path>) -> Result<Snark, bincode::Error> {
260    let f = File::open(path).map_err(Box::<bincode::ErrorKind>::from)?;
261    bincode::deserialize_from(f)
262}
263
264pub trait NativeKzgAccumulationScheme:
265    PolynomialCommitmentScheme<
266        G1Affine,
267        NativeLoader,
268        VerifyingKey = KzgSuccinctVerifyingKey<G1Affine>,
269        Output = KzgAccumulator<G1Affine, NativeLoader>,
270    > + AccumulationScheme<
271        G1Affine,
272        NativeLoader,
273        Accumulator = KzgAccumulator<G1Affine, NativeLoader>,
274        VerifyingKey = KzgAsVerifyingKey,
275    > + CostEstimation<G1Affine, Input = Vec<Query<Rotation>>>
276{
277}
278
279impl NativeKzgAccumulationScheme for crate::GWC {}
280impl NativeKzgAccumulationScheme for crate::SHPLONK {}
281
282// copied from snark_verifier --example recursion
283pub fn gen_dummy_snark<ConcreteCircuit, AS>(
284    params: &ParamsKZG<Bn256>,
285    vk: Option<&VerifyingKey<G1Affine>>,
286    num_instance: Vec<usize>,
287    circuit_params: ConcreteCircuit::Params,
288) -> Snark
289where
290    ConcreteCircuit: CircuitExt<Fr>,
291    ConcreteCircuit::Params: Clone,
292    AS: NativeKzgAccumulationScheme,
293{
294    #[derive(Clone)]
295    struct CsProxy<F: Field, C: Circuit<F>> {
296        params: C::Params,
297        _marker: PhantomData<F>,
298    }
299
300    impl<F: Field, C: Circuit<F>> CsProxy<F, C> {
301        pub fn new(params: C::Params) -> Self {
302            Self { params, _marker: PhantomData }
303        }
304    }
305
306    impl<F: Field, C: CircuitExt<F>> Circuit<F> for CsProxy<F, C>
307    where
308        C::Params: Clone,
309    {
310        type Config = C::Config;
311        type FloorPlanner = C::FloorPlanner;
312        type Params = C::Params;
313
314        fn without_witnesses(&self) -> Self {
315            Self::new(self.params.clone())
316        }
317
318        fn params(&self) -> Self::Params {
319            self.params.clone()
320        }
321
322        fn configure_with_params(
323            meta: &mut ConstraintSystem<F>,
324            params: Self::Params,
325        ) -> Self::Config {
326            C::configure_with_params(meta, params)
327        }
328
329        fn configure(_: &mut ConstraintSystem<F>) -> Self::Config {
330            unreachable!("must use configure_with_params")
331        }
332
333        fn synthesize(
334            &self,
335            config: Self::Config,
336            mut layouter: impl Layouter<F>,
337        ) -> Result<(), Error> {
338            // when `C` has simple selectors, we tell `CsProxy` not to over-optimize the selectors (e.g., compressing them  all into one) by turning all selectors on in the first row
339            // currently this only works if all simple selector columns are used in the actual circuit and there are overlaps amongst all enabled selectors (i.e., the actual circuit will not optimize constraint system further)
340            layouter.assign_region(
341                || "",
342                |mut region| {
343                    for q in C::selectors(&config).iter() {
344                        q.enable(&mut region, 0)?;
345                    }
346                    Ok(())
347                },
348            )?;
349            Ok(())
350        }
351    }
352
353    let dummy_vk = vk
354        .is_none()
355        .then(|| keygen_vk(params, &CsProxy::<Fr, ConcreteCircuit>::new(circuit_params)).unwrap());
356
357    gen_dummy_snark_from_vk::<AS>(
358        params,
359        vk.or(dummy_vk.as_ref()).unwrap(),
360        num_instance,
361        ConcreteCircuit::accumulator_indices(),
362    )
363}
364
365/// Creates a dummy snark in the correct shape corresponding to the given verifying key.
366/// This dummy snark will **not** verify.
367/// This snark can be used as a placeholder input into an aggregation circuit expecting a snark
368/// with this verifying key.
369///
370/// Note that this function does not need to know the concrete `Circuit` type.
371pub fn gen_dummy_snark_from_vk<AS>(
372    params: &ParamsKZG<Bn256>,
373    vk: &VerifyingKey<G1Affine>,
374    num_instance: Vec<usize>,
375    accumulator_indices: Option<Vec<(usize, usize)>>,
376) -> Snark
377where
378    AS: NativeKzgAccumulationScheme,
379{
380    let protocol = compile(
381        params,
382        vk,
383        Config::kzg().with_num_instance(num_instance).with_accumulator_indices(accumulator_indices),
384    );
385    gen_dummy_snark_from_protocol::<AS>(protocol)
386}
387
388/// Creates a dummy snark in the correct shape corresponding to the given Plonk protocol.
389/// This dummy snark will **not** verify.
390/// This snark can be used as a placeholder input into an aggregation circuit expecting a snark
391/// with this protocol.
392///
393/// Note that this function does not need to know the concrete `Circuit` type.
394pub fn gen_dummy_snark_from_protocol<AS>(protocol: PlonkProtocol<G1Affine>) -> Snark
395where
396    AS: NativeKzgAccumulationScheme,
397{
398    let instances = protocol.num_instance.iter().map(|&n| vec![Fr::default(); n]).collect();
399    let proof = {
400        let mut transcript = PoseidonTranscript::<NativeLoader, _>::new::<SECURE_MDS>(Vec::new());
401        for _ in 0..protocol
402            .num_witness
403            .iter()
404            .chain(Some(&protocol.quotient.num_chunk()))
405            .sum::<usize>()
406        {
407            transcript.write_ec_point(G1Affine::default()).unwrap();
408        }
409        for _ in 0..protocol.evaluations.len() {
410            transcript.write_scalar(Fr::default()).unwrap();
411        }
412        let queries = PlonkProof::<G1Affine, NativeLoader, AS>::empty_queries(&protocol);
413        for _ in 0..AS::estimate_cost(&queries).num_commitment {
414            transcript.write_ec_point(G1Affine::default()).unwrap();
415        }
416        transcript.finalize()
417    };
418
419    Snark::new(protocol, instances, proof)
420}