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