sigmabus-poc/src/circuits.rs
src/circuits.rsRaw
1use ark_crypto_primitives::crh::{
2 poseidon::constraints::{CRHGadget, CRHParametersVar},
3 CRHSchemeGadget,
4};
5use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
6use ark_ec::{AffineRepr, CurveGroup};
7use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, fields::fp::FpVar};
8use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
9use core::marker::PhantomData;
10
11// CF (ConstraintField)
12pub type CF<C> = <<C as CurveGroup>::Affine as AffineRepr>::ScalarField;
13
14#[derive(Debug, Clone)]
15pub struct GenZKCircuit<C: CurveGroup> {
16 pub _c: PhantomData<C>,
17 pub poseidon_config: PoseidonConfig<C::ScalarField>,
18 // public
19 pub cm: C::ScalarField,
20 pub s: C::ScalarField,
21 pub r_h: C::ScalarField,
22 pub c: C::ScalarField,
23 // private
24 pub x: C::ScalarField,
25 pub r: C::ScalarField,
26 pub o_h: C::ScalarField,
27}
28impl<C: CurveGroup> ConstraintSynthesizer<CF<C>> for GenZKCircuit<C>
29where
30 C::ScalarField: Absorb,
31{
32 fn generate_constraints(self, cs: ConstraintSystemRef<CF<C>>) -> Result<(), SynthesisError> {
33 // public inputs
34 let cmVar = FpVar::<C::ScalarField>::new_input(cs.clone(), || Ok(self.cm))?;
35 let sVar = FpVar::<C::ScalarField>::new_input(cs.clone(), || Ok(self.s))?;
36 let r_hVar = FpVar::<C::ScalarField>::new_input(cs.clone(), || Ok(self.r_h))?;
37 let cVar = FpVar::<C::ScalarField>::new_input(cs.clone(), || Ok(self.c))?;
38
39 // private inputs
40 let xVar = FpVar::<C::ScalarField>::new_witness(cs.clone(), || Ok(self.x))?;
41 let rVar = FpVar::<C::ScalarField>::new_witness(cs.clone(), || Ok(self.r))?;
42 let o_hVar = FpVar::<C::ScalarField>::new_witness(cs.clone(), || Ok(self.o_h))?;
43
44 let crh_params =
45 CRHParametersVar::<C::ScalarField>::new_witness(
46 cs.clone(),
47 || Ok(self.poseidon_config),
48 )
49 .unwrap();
50
51 Self::check(&crh_params, cmVar, sVar, r_hVar, cVar, xVar, rVar, o_hVar)?;
52
53 Ok(())
54 }
55}
56
57impl<C: CurveGroup> GenZKCircuit<C>
58where
59 C::ScalarField: Absorb,
60{
61 #[allow(clippy::too_many_arguments)]
62 pub fn check(
63 crh_params: &CRHParametersVar<C::ScalarField>,
64 // public inputs:
65 cm: FpVar<C::ScalarField>,
66 s: FpVar<C::ScalarField>,
67 r_h: FpVar<C::ScalarField>,
68 c: FpVar<C::ScalarField>,
69 // private inputs:
70 x: FpVar<C::ScalarField>,
71 r: FpVar<C::ScalarField>,
72 o_h: FpVar<C::ScalarField>,
73 ) -> Result<(), SynthesisError> {
74 // cm == Commit(x) (Poseidon)
75 let computed_cm = CRHGadget::<C::ScalarField>::evaluate(crh_params, &[x.clone()]).unwrap();
76 computed_cm.enforce_equal(&cm)?;
77
78 // r_h == HCommit(r, o_h) (Poseidon)
79 let computed_r_h =
80 CRHGadget::<C::ScalarField>::evaluate(crh_params, &[r.clone(), o_h.clone()]).unwrap();
81 computed_r_h.enforce_equal(&r_h)?;
82
83 // s == r + c * x
84 s.enforce_equal(&(r + (c * x)))?;
85 Ok(())
86 }
87}
88
89// Note: since at the v0.4.0 of ark_curves the bn254 curve does not have the constraints
90// implemented, for the following tests we use the pallas curve.
91#[cfg(test)]
92pub mod tests {
93 use super::*;
94 use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge};
95 use ark_ec::Group;
96 use ark_ff::{BigInteger, PrimeField};
97 use ark_r1cs_std::{
98 alloc::AllocVar,
99 boolean::Boolean,
100 fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
101 groups::GroupOpsBounds,
102 prelude::CurveVar,
103 };
104 use ark_relations::r1cs::ConstraintSystem;
105 use ark_std::UniformRand;
106 use std::ops::Mul;
107
108 // import pallas curve
109 use ark_pallas::{Fq, Fr, Projective};
110 // instead of ark_pallas::constraints::GVar we use a custom non-native version of it:
111 use ark_pallas::PallasConfig;
112 use ark_r1cs_std::groups::curves::short_weierstrass::ProjectiveVar;
113 pub type NonNativePallasGVar = ProjectiveVar<PallasConfig, NonNativeFieldVar<Fq, Fr>>;
114
115 use crate::sigmabus::SigmaProof;
116 use crate::transcript::{tests::poseidon_test_config, PoseidonTranscript};
117
118 #[test]
119 fn test_gen_zk() {
120 let mut rng = ark_std::test_rng();
121
122 let poseidon_config = poseidon_test_config::<Fr>();
123 let mut transcript = PoseidonTranscript::<Projective>::new(&poseidon_config);
124
125 let x = Fr::rand(&mut rng);
126
127 // the next lines do the same that is done in Sigmabus::prove but here in the test to have
128 // access to the internal values:
129 let mut sponge = PoseidonSponge::<Fr>::new(&poseidon_config);
130 sponge.absorb(&x);
131 let cm: Fr = sponge.squeeze_field_elements(1)[0];
132 transcript.absorb(&cm);
133
134 let r = Fr::rand(&mut rng);
135 let o_h = Fr::rand(&mut rng);
136
137 let R = Projective::generator().mul(r);
138
139 let mut sponge = PoseidonSponge::<Fr>::new(&poseidon_config);
140 sponge.absorb(&vec![r, o_h]);
141
142 let r_h: Fr = sponge.squeeze_field_elements(1)[0];
143
144 transcript.absorb_point(&R);
145 transcript.absorb(&r_h);
146 let c = transcript.get_challenge();
147
148 let s = r + c * x;
149 let sigma_proof = SigmaProof { s, R, r_h };
150 // end of Sigmabus::prove
151
152 let cs = ConstraintSystem::<Fr>::new_ref();
153
154 // public inputs
155 let cmVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(cm)).unwrap();
156 let sVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(sigma_proof.s)).unwrap();
157 let r_hVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(sigma_proof.r_h)).unwrap();
158 let cVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(c)).unwrap();
159 // private inputs
160 let xVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(x)).unwrap();
161 let rVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(r)).unwrap();
162 let o_hVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(o_h)).unwrap();
163
164 let crh_params =
165 CRHParametersVar::<Fr>::new_witness(cs.clone(), || Ok(poseidon_config)).unwrap();
166
167 // GenZK
168 GenZKCircuit::<Projective>::check(
169 &crh_params,
170 cmVar,
171 sVar,
172 r_hVar,
173 cVar,
174 xVar,
175 rVar,
176 o_hVar,
177 )
178 .unwrap();
179 assert!(cs.is_satisfied().unwrap());
180 dbg!(cs.num_constraints());
181 }
182
183 // This circuit implements the x*G operation that Sigmabus proves, but here we do it in the
184 // 'naive' way, which is computing it non-natively.
185 struct NonNativeScalarMulCircuit<
186 C: CurveGroup,
187 GC: CurveVar<C, C::ScalarField>,
188 FV: FieldVar<C::BaseField, C::ScalarField>,
189 > {
190 _gc: PhantomData<GC>,
191 _fv: PhantomData<FV>,
192 pub x: C::ScalarField,
193 pub X: C,
194 }
195 impl<C, GC, FV> ConstraintSynthesizer<CF<C>> for NonNativeScalarMulCircuit<C, GC, FV>
196 where
197 C: CurveGroup,
198 GC: CurveVar<C, C::ScalarField>,
199 FV: FieldVar<C::BaseField, C::ScalarField>,
200 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
201 {
202 fn generate_constraints(
203 self,
204 cs: ConstraintSystemRef<CF<C>>,
205 ) -> Result<(), SynthesisError> {
206 let G = GC::new_constant(cs.clone(), C::generator())?;
207 let x_bits = Vec::<Boolean<CF<C>>>::new_input(cs.clone(), || {
208 Ok(self.x.into_bigint().to_bits_le())
209 })?;
210 let X = GC::new_input(cs.clone(), || Ok(self.X))?;
211
212 let xG = G.scalar_mul_le(x_bits.iter())?;
213 xG.enforce_equal(&X)?;
214
215 Ok(())
216 }
217 }
218
219 #[test]
220 fn test_nonnative_num_constraints() {
221 let mut rng = ark_std::test_rng();
222
223 // compute X = x * G
224 let x = Fr::rand(&mut rng);
225 let X = Projective::generator().mul(x);
226
227 let cs = ConstraintSystem::<Fr>::new_ref();
228 let nonnative_scalarmul_circuit = NonNativeScalarMulCircuit::<
229 Projective,
230 NonNativePallasGVar,
231 NonNativeFieldVar<Fq, Fr>,
232 > {
233 _gc: PhantomData,
234 _fv: PhantomData,
235 x,
236 X,
237 };
238
239 assert!(cs.is_satisfied().unwrap());
240 dbg!(cs.num_constraints());
241 }
242}