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