ark-ec-blind-signatures/src/constraints.rs
src/constraints.rsRaw
1use crate::{Parameters, PublicKey, Signature};
2
3use ark_ec::ProjectiveCurve;
4use ark_ed_on_bn254::{constraints::EdwardsVar, EdwardsParameters, FqParameters};
5use ark_ff::{fields::Fp256, to_bytes, PrimeField};
6use ark_r1cs_std::{
7 alloc::{AllocVar, AllocationMode},
8 bits::uint8::UInt8,
9 boolean::Boolean,
10 eq::EqGadget,
11 fields::fp::FpVar,
12 groups::GroupOpsBounds,
13 prelude::CurveVar,
14 ToBitsGadget,
15};
16use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
17use ark_std::ops::Mul;
18
19use core::{borrow::Borrow, marker::PhantomData};
20use derivative::Derivative;
21
22// hash
23use arkworks_native_gadgets::poseidon as poseidon_native;
24// use arkworks_r1cs_gadgets::poseidon;
25use arkworks_r1cs_gadgets::poseidon::{FieldHasherGadget, PoseidonGadget};
26
27use crate::ConstraintF;
28
29#[derive(Derivative)]
30#[derivative(
31 Debug(bound = "C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>"),
32 Clone(bound = "C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>")
33)]
34pub struct PublicKeyVar<C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>>
35where
36 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
37{
38 pub_key: GC,
39 #[doc(hidden)]
40 _group: PhantomData<*const C>,
41}
42
43impl<C, GC> AllocVar<PublicKey<C>, ConstraintF<C>> for PublicKeyVar<C, GC>
44where
45 C: ProjectiveCurve,
46 GC: CurveVar<C, ConstraintF<C>>,
47 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
48{
49 fn new_variable<T: Borrow<PublicKey<C>>>(
50 cs: impl Into<Namespace<ConstraintF<C>>>,
51 f: impl FnOnce() -> Result<T, SynthesisError>,
52 mode: AllocationMode,
53 ) -> Result<Self, SynthesisError> {
54 let pub_key = GC::new_variable(cs, f, mode)?;
55 Ok(Self {
56 pub_key,
57 _group: PhantomData,
58 })
59 }
60}
61
62#[derive(Derivative)]
63#[derivative(
64 Debug(bound = "C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>"),
65 Clone(bound = "C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>")
66)]
67pub struct SignatureVar<C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>>
68where
69 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
70{
71 s: Vec<UInt8<ConstraintF<C>>>,
72 r: GC,
73 _curve: PhantomData<C>,
74}
75
76impl<C, GC> AllocVar<Signature<C>, ConstraintF<C>> for SignatureVar<C, GC>
77where
78 C: ProjectiveCurve,
79 GC: CurveVar<C, ConstraintF<C>>,
80 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
81{
82 fn new_variable<T: Borrow<Signature<C>>>(
83 cs: impl Into<Namespace<ConstraintF<C>>>,
84 f: impl FnOnce() -> Result<T, SynthesisError>,
85 mode: AllocationMode,
86 ) -> Result<Self, SynthesisError> {
87 f().and_then(|val| {
88 let cs = cs.into();
89 // let s = val.borrow().s;
90 let mut s = Vec::<UInt8<ConstraintF<C>>>::new();
91 let s_bytes = to_bytes![val.borrow().s].unwrap();
92 #[allow(clippy::needless_range_loop)]
93 for i in 0..s_bytes.len() {
94 s.push(UInt8::<ConstraintF<C>>::new_variable(
95 cs.clone(),
96 || Ok(s_bytes[i]),
97 mode,
98 )?);
99 }
100
101 let r = GC::new_variable(cs, || Ok(val.borrow().r), mode)?;
102
103 Ok(Self {
104 s,
105 r,
106 _curve: PhantomData,
107 })
108 })
109 }
110}
111
112#[derive(Clone)]
113pub struct ParametersVar<C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>>
114where
115 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
116{
117 generator: GC,
118 _curve: PhantomData<C>,
119}
120
121impl<C, GC> AllocVar<Parameters<C>, ConstraintF<C>> for ParametersVar<C, GC>
122where
123 C: ProjectiveCurve,
124 GC: CurveVar<C, ConstraintF<C>>,
125 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
126{
127 fn new_variable<T: Borrow<Parameters<C>>>(
128 cs: impl Into<Namespace<ConstraintF<C>>>,
129 f: impl FnOnce() -> Result<T, SynthesisError>,
130 mode: AllocationMode,
131 ) -> Result<Self, SynthesisError> {
132 f().and_then(|val| {
133 let cs = cs.into();
134 let generator = GC::new_variable(cs, || Ok(val.borrow().generator), mode)?;
135 Ok(Self {
136 generator,
137 _curve: PhantomData,
138 })
139 })
140 }
141}
142
143pub struct BlindSigVerifyGadget<C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>>
144where
145 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
146{
147 _params: Parameters<C>, // TODO review if needed, maybe delete
148 _gc: PhantomData<GC>,
149}
150
151impl<C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>> BlindSigVerifyGadget<C, GC>
152where
153 C: ProjectiveCurve,
154 GC: CurveVar<C, ConstraintF<C>>,
155 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
156 ark_r1cs_std::groups::curves::twisted_edwards::AffineVar<
157 EdwardsParameters,
158 FpVar<Fp256<FqParameters>>,
159 >: From<GC>,
160 <C as ProjectiveCurve>::BaseField: PrimeField,
161 FpVar<<C as ProjectiveCurve>::BaseField>: Mul<FpVar<Fp256<FqParameters>>>,
162 FpVar<<C as ProjectiveCurve>::BaseField>: From<FpVar<Fp256<FqParameters>>>,
163{
164 fn verify(
165 parameters: &ParametersVar<C, GC>,
166 poseidon_hash: &PoseidonGadget<ConstraintF<C>>,
167 m: FpVar<ConstraintF<C>>,
168 s: &SignatureVar<C, GC>,
169 q: &PublicKeyVar<C, GC>,
170 ) -> Result<Boolean<ConstraintF<C>>, SynthesisError> {
171 let s_s = s.s.clone();
172
173 let sG = parameters
174 .generator
175 .scalar_mul_le(s_s.to_bits_le()?.iter())?;
176
177 // Note: in a circuit that aggregates multiple verifications, the hashing step could be
178 // done outside the signature verification, once for all 1 votes and once for all 0 votes,
179 // saving lots of constraints
180 let hm = poseidon_hash.hash(&[m])?;
181 let r = EdwardsVar::from(s.r.clone()); // WIP
182 let rx_fpvar: FpVar<ConstraintF<C>> = r.x.into();
183
184 // G * s == R + Q * (R.x * H(m))
185 let Q_rx_hm_0 = q.pub_key.scalar_mul_le(rx_fpvar.to_bits_le()?.iter())?;
186 let Q_rx_hm = Q_rx_hm_0.scalar_mul_le(hm.to_bits_le()?.iter())?;
187 let RHS = s.r.clone() + Q_rx_hm;
188
189 sG.is_eq(&RHS)
190 }
191}
192
193// example of circuit using BlindSigVerifyGadget
194#[derive(Clone)]
195pub struct BlindSigVerifyCircuit<C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>>
196where
197 <C as ProjectiveCurve>::BaseField: PrimeField,
198{
199 _group: PhantomData<*const GC>,
200 pub params: Parameters<C>,
201 pub poseidon_hash_native: poseidon_native::Poseidon<ConstraintF<C>>,
202 pub signature: Option<Signature<C>>,
203 pub pub_key: Option<PublicKey<C>>,
204 pub message: Option<ConstraintF<C>>,
205}
206
207impl<C: ProjectiveCurve, GC: CurveVar<C, ConstraintF<C>>> ConstraintSynthesizer<ConstraintF<C>>
208 for BlindSigVerifyCircuit<C, GC>
209where
210 C: ProjectiveCurve,
211 GC: CurveVar<C, ConstraintF<C>>,
212 for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
213 ark_r1cs_std::groups::curves::twisted_edwards::AffineVar<
214 EdwardsParameters,
215 FpVar<Fp256<FqParameters>>,
216 >: From<GC>,
217 <C as ProjectiveCurve>::BaseField: PrimeField,
218 FpVar<<C as ProjectiveCurve>::BaseField>: Mul<FpVar<Fp256<FqParameters>>>,
219 FpVar<<C as ProjectiveCurve>::BaseField>: From<FpVar<Fp256<FqParameters>>>,
220{
221 #[tracing::instrument(target = "r1cs", skip(self, cs))]
222 fn generate_constraints(
223 self,
224 cs: ConstraintSystemRef<ConstraintF<C>>,
225 ) -> Result<(), SynthesisError> {
226 let parameters =
227 ParametersVar::new_constant(ark_relations::ns!(cs, "parameters"), &self.params)?;
228
229 let pub_key =
230 PublicKeyVar::<C, GC>::new_input(ark_relations::ns!(cs, "public key"), || {
231 self.pub_key.ok_or(SynthesisError::AssignmentMissing)
232 })?;
233 let m = FpVar::<ConstraintF<C>>::new_input(ark_relations::ns!(cs, "message"), || {
234 self.message.ok_or(SynthesisError::AssignmentMissing)
235 })?;
236 let signature =
237 SignatureVar::<C, GC>::new_witness(ark_relations::ns!(cs, "signature"), || {
238 self.signature.ok_or(SynthesisError::AssignmentMissing)
239 })?;
240 #[allow(clippy::redundant_clone)]
241 let poseidon_hash = PoseidonGadget::<ConstraintF<C>>::from_native(
242 &mut cs.clone(),
243 self.poseidon_hash_native,
244 )
245 .unwrap();
246
247 let v = BlindSigVerifyGadget::<C, GC>::verify(
248 ¶meters,
249 &poseidon_hash,
250 m,
251 &signature,
252 &pub_key,
253 )?;
254 v.enforce_equal(&Boolean::TRUE)
255 }
256}
257
258#[cfg(test)]
259mod test {
260 use super::*;
261 use crate::{poseidon_setup_params, BlindSigScheme};
262 use ark_ed_on_bn254::constraints::EdwardsVar as BabyJubJubVar;
263 use ark_ed_on_bn254::EdwardsProjective as BabyJubJub;
264
265 use arkworks_native_gadgets::poseidon;
266 use arkworks_utils::Curve;
267
268 use ark_relations::r1cs::ConstraintSystem;
269
270 type Fq = <BabyJubJub as ProjectiveCurve>::BaseField;
271 // type Fr = <BabyJubJub as ProjectiveCurve>::ScalarField;
272 type S = BlindSigScheme<BabyJubJub>;
273
274 fn generate_native_data(
275 poseidon_hash: &poseidon::Poseidon<Fq>,
276 ) -> (
277 Parameters<BabyJubJub>,
278 PublicKey<BabyJubJub>,
279 Fq,
280 Signature<BabyJubJub>,
281 ) {
282 let mut rng = ark_std::test_rng();
283 let params = S::setup();
284 let (pk, sk) = S::keygen(¶ms, &mut rng);
285 let (k, signer_r) = S::new_request_params(¶ms, &mut rng);
286 let m = Fq::from(1234);
287 let (m_blinded, u) = S::blind(¶ms, &mut rng, &poseidon_hash, m, signer_r).unwrap();
288 let s_blinded = S::blind_sign(sk, k, m_blinded);
289 let s = S::unblind(s_blinded, u);
290 let verified = S::verify(¶ms, &poseidon_hash, m, s.clone(), pk);
291 assert!(verified);
292 (params, pk, m, s)
293 }
294
295 #[test]
296 fn test_verify() {
297 let poseidon_params = poseidon_setup_params::<Fq>(Curve::Bn254, 5, 3);
298 let poseidon_hash = poseidon::Poseidon::new(poseidon_params);
299
300 // create signature using native-rust lib
301 let (params, pk, m, s) = generate_native_data(&poseidon_hash);
302
303 // use the constraint system to verify the signature
304 type SG = BlindSigVerifyGadget<BabyJubJub, BabyJubJubVar>;
305 let cs = ConstraintSystem::<Fq>::new_ref();
306
307 let params_var =
308 ParametersVar::<BabyJubJub, BabyJubJubVar>::new_constant(cs.clone(), params).unwrap();
309 let signature_var =
310 SignatureVar::<BabyJubJub, BabyJubJubVar>::new_witness(cs.clone(), || Ok(&s)).unwrap();
311 let pk_var =
312 PublicKeyVar::<BabyJubJub, BabyJubJubVar>::new_witness(cs.clone(), || Ok(&pk)).unwrap();
313 let m_var = FpVar::<Fq>::new_witness(cs.clone(), || Ok(&m)).unwrap();
314 let poseidon_hash_var =
315 PoseidonGadget::<Fq>::from_native(&mut cs.clone(), poseidon_hash).unwrap();
316
317 let valid_sig = SG::verify(
318 ¶ms_var,
319 &poseidon_hash_var,
320 m_var,
321 &signature_var,
322 &pk_var,
323 )
324 .unwrap();
325 valid_sig.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
326 assert!(cs.is_satisfied().unwrap());
327 }
328
329 #[test]
330 fn test_constraint_system() {
331 let poseidon_params = poseidon_setup_params::<Fq>(Curve::Bn254, 5, 3);
332 let poseidon_hash = poseidon::Poseidon::new(poseidon_params);
333
334 // create signature using native-rust lib
335 let (params, pk, m, s) = generate_native_data(&poseidon_hash);
336
337 // use the constraint system to verify the signature
338 let circuit = BlindSigVerifyCircuit::<BabyJubJub, BabyJubJubVar> {
339 params,
340 poseidon_hash_native: poseidon_hash,
341 signature: Some(s),
342 pub_key: Some(pk),
343 message: Some(m),
344 _group: PhantomData,
345 };
346 let cs = ConstraintSystem::<Fq>::new_ref();
347 circuit.generate_constraints(cs.clone()).unwrap();
348 let is_satisfied = cs.is_satisfied().unwrap();
349 assert!(is_satisfied);
350 println!("num_cnstraints={:?}", cs.num_constraints());
351 }
352}