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