From 3775f39d3468b2bc03eed96e1ee320d8078cb676 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 22 Nov 2022 11:41:16 +0100 Subject: [PATCH] Add Schnorr Blind Sig verif r1cs constraints impl --- README.md | 8 +- src/schnorr_blind/constraints.rs | 422 +++++++++++++++++++++++++++++++ src/schnorr_blind/mod.rs | 2 +- 3 files changed, 428 insertions(+), 4 deletions(-) create mode 100644 src/schnorr_blind/constraints.rs diff --git a/README.md b/README.md index c4bbaf9..012c35a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # ark-ec-blind-signatures [![Test](https://github.com/aragonzkresearch/ark-ec-blind-signatures/workflows/Test/badge.svg)](https://github.com/aragonzkresearch/ark-ec-blind-signatures/actions?query=workflow%3ATest) [![Clippy](https://github.com/aragonzkresearch/ark-ec-blind-signatures/workflows/Clippy/badge.svg)](https://github.com/aragonzkresearch/ark-ec-blind-signatures/actions?query=workflow%3AClippy) -Blind signatures over elliptic curve implementation (native & r1cs constraints) using [arkworks](https://github.com/arkworks-rs). +> Warning: experimental code, do not use in production. -[Blind signature](https://en.wikipedia.org/wiki/Blind_signature) over elliptic curves, based on *"[New Blind Signature Schemes Based on the (Elliptic Curve) Discrete Logarithm Problem](https://sci-hub.st/10.1109/iccke.2013.6682844)"* paper by Hamid Mala & Nafiseh Nezhadansari. +[Blind signature](https://en.wikipedia.org/wiki/Blind_signature) over elliptic curve implementation (native & r1cs constraints) using [arkworks](https://github.com/arkworks-rs). +Schemes implemented: +- mala_nezhadansari: *"[New Blind Signature Schemes Based on the (Elliptic Curve) Discrete Logarithm Problem](https://sci-hub.st/10.1109/iccke.2013.6682844)"* paper by Hamid Mala, Nafiseh Nezhadansari. Note that in this scheme signatures are malleable. Number of constraints for the verification: 9785. +- shcnorr_blind: *"[Blind Schnorr Signatures and Signed ElGamal Encryption in the Algebraic Group Model](https://eprint.iacr.org/2019/877)"* paper by Georg Fuchsbauer, Antoine Plouviez, and Yannick Seurin. Number of constraints for the verification: 6052. -> Warning: experimental code, do not use in production. Target: Groth16 over Bn254 (for Ethereum), ed-on-bn254 ([BabyJubJub](https://github.com/barryWhiteHat/baby_jubjub)) for the signatures. diff --git a/src/schnorr_blind/constraints.rs b/src/schnorr_blind/constraints.rs new file mode 100644 index 0000000..502b020 --- /dev/null +++ b/src/schnorr_blind/constraints.rs @@ -0,0 +1,422 @@ +use crate::schnorr_blind::{ConstraintF, Parameters, PublicKey, Signature}; + +use ark_ec::ProjectiveCurve; +use ark_ed_on_bn254::{constraints::EdwardsVar, EdwardsParameters, FqParameters}; +use ark_ff::{fields::Fp256, to_bytes, PrimeField}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + bits::uint8::UInt8, + boolean::Boolean, + eq::EqGadget, + fields::fp::FpVar, + groups::GroupOpsBounds, + prelude::CurveVar, + ToBitsGadget, +}; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::ops::Mul; + +use core::{borrow::Borrow, marker::PhantomData}; +use derivative::Derivative; + +// hash +use arkworks_native_gadgets::poseidon as poseidon_native; +// use arkworks_r1cs_gadgets::poseidon; +use arkworks_r1cs_gadgets::poseidon::{FieldHasherGadget, PoseidonGadget}; + +#[derive(Derivative)] +#[derivative( + Debug(bound = "C: ProjectiveCurve, GC: CurveVar>"), + Clone(bound = "C: ProjectiveCurve, GC: CurveVar>") +)] +pub struct PublicKeyVar>> +where + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + pub pub_key: GC, + #[doc(hidden)] + _group: PhantomData<*const C>, +} + +impl AllocVar, ConstraintF> for PublicKeyVar +where + C: ProjectiveCurve, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let pub_key = GC::new_variable(cs, f, mode)?; + Ok(Self { + pub_key, + _group: PhantomData, + }) + } +} + +#[derive(Clone, Debug)] +pub struct Msg(pub [ConstraintF; MSG_LEN]); + +#[derive(Derivative)] +#[derivative( + Debug(bound = "C: ProjectiveCurve, GC: CurveVar>"), + Clone(bound = "C: ProjectiveCurve, GC: CurveVar>") +)] +pub struct MsgVar>> +where + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + m: [FpVar>; MSG_LEN], + _gc: PhantomData, +} +impl MsgVar +where + C: ProjectiveCurve, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + pub fn new(m: [FpVar>; MSG_LEN]) -> Self { + Self { + m, + _gc: PhantomData, + } + } +} + +impl AllocVar, ConstraintF> + for MsgVar +where + C: ProjectiveCurve, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|m| { + let m = m.borrow(); + let cs = cs.into(); + let msg_vec: Vec>> = + Vec::new_variable(cs, || Ok(m.clone().0), mode)?; + let m: [FpVar>; MSG_LEN] = + msg_vec + .try_into() + .unwrap_or_else(|v: Vec>>| { + // WIP + panic!( + "Expected Vec of length: {}, actual length: {}", + MSG_LEN, + v.len() + ) + }); + Ok(Self { + m, + _gc: PhantomData, + }) + }) + } +} + +#[derive(Derivative)] +#[derivative( + Debug(bound = "C: ProjectiveCurve, GC: CurveVar>"), + Clone(bound = "C: ProjectiveCurve, GC: CurveVar>") +)] +pub struct SignatureVar>> +where + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + s: Vec>>, + r: GC, + _curve: PhantomData, +} + +impl AllocVar, ConstraintF> for SignatureVar +where + C: ProjectiveCurve, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + // let s = val.borrow().s; + let mut s = Vec::>>::new(); + let s_bytes = to_bytes![val.borrow().s].unwrap(); + #[allow(clippy::needless_range_loop)] + for i in 0..s_bytes.len() { + s.push(UInt8::>::new_variable( + cs.clone(), + || Ok(s_bytes[i]), + mode, + )?); + } + + let r = GC::new_variable(cs, || Ok(val.borrow().r), mode)?; + + Ok(Self { + s, + r, + _curve: PhantomData, + }) + }) + } +} + +#[derive(Clone)] +pub struct ParametersVar>> +where + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + generator: GC, + _curve: PhantomData, +} + +impl AllocVar, ConstraintF> for ParametersVar +where + C: ProjectiveCurve, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + let generator = GC::new_variable(cs, || Ok(val.borrow().generator), mode)?; + Ok(Self { + generator, + _curve: PhantomData, + }) + }) + } +} + +pub struct BlindSigVerifyGadget< + const MSG_LEN: usize, + C: ProjectiveCurve, + GC: CurveVar>, +> where + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + _params: Parameters, // TODO review if needed, maybe delete + _gc: PhantomData, +} + +impl>> + BlindSigVerifyGadget +where + C: ProjectiveCurve, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, + ark_r1cs_std::groups::curves::twisted_edwards::AffineVar< + EdwardsParameters, + FpVar>, + >: From, + ::BaseField: PrimeField, + FpVar<::BaseField>: Mul>>, + FpVar<::BaseField>: From>>, +{ + pub fn verify( + parameters: &ParametersVar, + poseidon_hash: &PoseidonGadget>, + m: &MsgVar, + s: &SignatureVar, + q: &PublicKeyVar, + ) -> Result>, SynthesisError> { + let sG = parameters + .generator + .scalar_mul_le(s.s.to_bits_le()?.iter())?; + + // Note: in a circuit that aggregates multiple verifications, the hashing step could be + // done outside the signature verification, once for all 1 votes and once for all 0 votes, + // saving lots of constraints + let r = EdwardsVar::from(s.r.clone()); // WIP + let hm = poseidon_hash.hash(&m.m)?; + let to_hash = [r.x.into(), r.y.into(), hm]; + let h = poseidon_hash.hash(&to_hash)?; + + // G * s == R + H(R, m) * Q + let RHS = s.r.clone() + q.pub_key.scalar_mul_le(h.to_bits_le()?.iter())?; + + sG.is_eq(&RHS) + } +} + +// example of circuit using BlindSigVerifyGadget to verify a single blind signature +#[derive(Clone)] +pub struct BlindSigVerifyCircuit< + const MSG_LEN: usize, + C: ProjectiveCurve, + GC: CurveVar>, +> where + ::BaseField: PrimeField, +{ + _group: PhantomData<*const GC>, + pub params: Parameters, + pub poseidon_hash_native: poseidon_native::Poseidon>, + pub signature: Option>, + pub pub_key: Option>, + pub message: Option>, +} + +impl>> + ConstraintSynthesizer> for BlindSigVerifyCircuit +where + C: ProjectiveCurve, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, + ark_r1cs_std::groups::curves::twisted_edwards::AffineVar< + EdwardsParameters, + FpVar>, + >: From, + ::BaseField: PrimeField, + FpVar<::BaseField>: Mul>>, + FpVar<::BaseField>: From>>, +{ + #[tracing::instrument(target = "r1cs", skip(self, cs))] + fn generate_constraints( + self, + cs: ConstraintSystemRef>, + ) -> Result<(), SynthesisError> { + let parameters = + ParametersVar::new_constant(ark_relations::ns!(cs, "parameters"), &self.params)?; + + let pub_key = + PublicKeyVar::::new_input(ark_relations::ns!(cs, "public key"), || { + self.pub_key.ok_or(SynthesisError::AssignmentMissing) + })?; + let m = MsgVar::::new_input(ark_relations::ns!(cs, "message"), || { + self.message.ok_or(SynthesisError::AssignmentMissing) + })?; + let signature = + SignatureVar::::new_witness(ark_relations::ns!(cs, "signature"), || { + self.signature.ok_or(SynthesisError::AssignmentMissing) + })?; + #[allow(clippy::redundant_clone)] + let poseidon_hash = PoseidonGadget::>::from_native( + &mut cs.clone(), + self.poseidon_hash_native, + ) + .unwrap(); + + let v = BlindSigVerifyGadget::::verify( + ¶meters, + &poseidon_hash, + &m, + &signature, + &pub_key, + )?; + v.enforce_equal(&Boolean::TRUE) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::schnorr_blind::{poseidon_setup_params, BlindSigScheme}; + use ark_ed_on_bn254::constraints::EdwardsVar as BabyJubJubVar; + use ark_ed_on_bn254::EdwardsProjective as BabyJubJub; + + use arkworks_native_gadgets::poseidon; + use arkworks_utils::Curve; + + use ark_relations::r1cs::ConstraintSystem; + + type Fq = ::BaseField; + // type Fr = ::ScalarField; + type S = BlindSigScheme; + + fn generate_single_sig_native_data( + poseidon_hash: &poseidon::Poseidon, + ) -> ( + Parameters, + PublicKey, + Msg<3, BabyJubJub>, + Signature, + ) { + let mut rng = ark_std::test_rng(); + let params = S::setup(); + let (pk, sk) = S::keygen(¶ms, &mut rng); + let (k, signer_r) = S::new_request_params(¶ms, &mut rng); + let m = [Fq::from(1234), Fq::from(5689), Fq::from(3456)]; + let (m_blinded, u) = S::blind(¶ms, &mut rng, &poseidon_hash, &m, pk, signer_r).unwrap(); + let s_blinded = S::blind_sign(sk, k, m_blinded); + let s = S::unblind(s_blinded, &u); + let verified = S::verify(¶ms, &poseidon_hash, &m, s.clone(), pk); + assert!(verified); + (params, pk, Msg(m), s) + } + + #[test] + fn test_single_verify() { + let poseidon_params = poseidon_setup_params::(Curve::Bn254, 5, 4); + let poseidon_hash = poseidon::Poseidon::new(poseidon_params); + const MSG_LEN: usize = 3; + + // create signature using native-rust lib + let (params, pk, m, s) = generate_single_sig_native_data(&poseidon_hash); + + // use the constraint system to verify the signature + type SG = BlindSigVerifyGadget; + let cs = ConstraintSystem::::new_ref(); + + let params_var = + ParametersVar::::new_constant(cs.clone(), params).unwrap(); + let signature_var = + SignatureVar::::new_witness(cs.clone(), || Ok(&s)).unwrap(); + let pk_var = + PublicKeyVar::::new_witness(cs.clone(), || Ok(&pk)).unwrap(); + let m_var = MsgVar::::new_witness(cs.clone(), || Ok(m)) + .unwrap(); + let poseidon_hash_var = + PoseidonGadget::::from_native(&mut cs.clone(), poseidon_hash).unwrap(); + + let valid_sig = SG::verify( + ¶ms_var, + &poseidon_hash_var, + &m_var, + &signature_var, + &pk_var, + ) + .unwrap(); + valid_sig.enforce_equal(&Boolean::::TRUE).unwrap(); + assert!(cs.is_satisfied().unwrap()); + } + + #[test] + fn test_single_verify_constraint_system() { + let poseidon_params = poseidon_setup_params::(Curve::Bn254, 5, 4); + let poseidon_hash = poseidon::Poseidon::new(poseidon_params); + const MSG_LEN: usize = 3; + + // create signature using native-rust lib + let (params, pk, m, s) = generate_single_sig_native_data(&poseidon_hash); + + // use the constraint system to verify the signature + let circuit = BlindSigVerifyCircuit:: { + params, + poseidon_hash_native: poseidon_hash.clone(), + signature: Some(s), + pub_key: Some(pk), + message: Some(m), + _group: PhantomData, + }; + let cs = ConstraintSystem::::new_ref(); + circuit.generate_constraints(cs.clone()).unwrap(); + let is_satisfied = cs.is_satisfied().unwrap(); + assert!(is_satisfied); + println!("num_constraints={:?}", cs.num_constraints()); + } +} diff --git a/src/schnorr_blind/mod.rs b/src/schnorr_blind/mod.rs index 8bb4364..8db30ea 100644 --- a/src/schnorr_blind/mod.rs +++ b/src/schnorr_blind/mod.rs @@ -2,7 +2,7 @@ #![allow(clippy::many_single_char_names)] // #[cfg(feature="r1cs")] -// pub mod constraints; +pub mod constraints; use ark_ec::{models::twisted_edwards_extended::GroupAffine, AffineCurve, ProjectiveCurve};