arnaucube
arnaucube/arkeddsaPublic Self-hosted repository
add in-circuit EdDSA verification gadget

add in-circuit EdDSA verification gadget compatible with the out-circuit logic.
arnaucube git@arnaucube.com
Thu, 28 Nov 2024 22:00:43 +0100
commit

b409860439b81179cbfc197bdcc3b335248d1058

parent

0a81df83b07de326013791829d9d6e01639fd51f

M Cargo.toml → Cargo.toml

@@ -5,8 +5,7 @@ rust-version = "1.75.0"

version = "0.1.0" [dependencies] - -ark-crypto-primitives = {version = "^0.4.0", default-features = false, features = ["sponge", "crh"]} +ark-crypto-primitives = {version = "^0.4.0", default-features = false, features = ["sponge", "crh", "r1cs"]} ark-ec = "^0.4.0" ark-ed-on-bn254 = {version = "0.4.0"} ark-ff = "^0.4.0"

@@ -16,6 +15,11 @@ digest = "0.10"

rand = "0.8" rand_core = {version = "0.6", default-features = false} +# r1cs deps related under feature="r1cs" +ark-relations = { version = "^0.4.0", default-features = false, optional = true } +ark-r1cs-std = { version = "0.4.0", default-features = false, features = ["parallel"], optional = true } + + [dev-dependencies] ark-algebra-test-templates = "0.4.2" ark-ed-on-bls12-381 = {version = "0.4.0"}

@@ -26,3 +30,4 @@ sha2 = "0.10"

[features] default = [] +r1cs = ["ark-r1cs-std", "ark-relations"]
M README.md → README.md

@@ -3,3 +3,8 @@

Do not use in production. EDDSA signature scheme implementation with Poseidon hasher and ark-works backend. Additionally circom compatible `ed_on_bn254_twist` twist is available. + +The `r1cs` feature enables the in-circuit EdDSA verification. + +## test +To test including the constraints use the `r1cs` feature flag: `cargo test --features=r1cs`
M rust-toolchain → rust-toolchain

@@ -1,1 +1,1 @@

-1.75.0 +1.82.0
A src/constraints.rs

@@ -0,0 +1,90 @@

+/// This file implements the EdDSA verification in-circuit. +use ark_crypto_primitives::sponge::{ + constraints::CryptographicSpongeVar, + poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, +}; +use ark_ec::CurveGroup; +use ark_ff::Field; +use ark_relations::r1cs::ConstraintSystemRef; + +use ark_r1cs_std::{ + boolean::Boolean, fields::fp::FpVar, fields::nonnative::NonNativeFieldVar, groups::CurveVar, + ToBitsGadget, ToConstraintFieldGadget, +}; + +use crate::Error; + +/// CF stands for ConstraintField +pub type CF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField; + +/// gadget to compute the EdDSA verification in-circuit +pub fn verify<C, GC>( + cs: ConstraintSystemRef<CF<C>>, + poseidon_config: PoseidonConfig<CF<C>>, + pk: GC, + sig: (GC, NonNativeFieldVar<C::ScalarField, CF<C>>), + msg: FpVar<CF<C>>, +) -> Result<Boolean<CF<C>>, Error> +where + C: CurveGroup, + GC: CurveVar<C, CF<C>> + ToConstraintFieldGadget<CF<C>>, +{ + let (r, s): (GC, NonNativeFieldVar<C::ScalarField, CF<C>>) = sig; + + let r_xy = r.to_constraint_field().unwrap(); + let pk_xy = pk.to_constraint_field().unwrap(); + + let mut poseidon = PoseidonSpongeVar::new(cs.clone(), &poseidon_config); + poseidon.absorb(&r_xy).unwrap(); + poseidon.absorb(&pk_xy).unwrap(); + poseidon.absorb(&msg).unwrap(); + let k = poseidon.squeeze_field_elements(1).unwrap(); + let k = k.first().unwrap(); + + let kx_b = pk.scalar_mul_le(k.to_bits_le().unwrap().iter()).unwrap(); + + let g = GC::new_constant(cs.clone(), C::generator()).unwrap(); + let s_b = g.scalar_mul_le(s.to_bits_le().unwrap().iter()).unwrap(); + let r_rec: GC = s_b - kx_b; + Ok(r_rec.is_eq(&r).unwrap()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ed_on_bn254_twist::{ + constraints::EdwardsVar as GVar, BaseField as Fq, EdwardsConfig, EdwardsProjective as G, + ScalarField as Fr, + }; + use ark_ff::PrimeField; + use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, fields::nonnative::NonNativeFieldVar}; + use ark_relations::r1cs::ConstraintSystem; + use rand_core::OsRng; + + use crate::test::poseidon_config; + use crate::SigningKey; + + #[test] + fn gadget_verify() { + let poseidon_config = poseidon_config::<Fq>(4, 8, 60); + let sk = SigningKey::<EdwardsConfig>::generate::<blake2::Blake2b512>(&mut OsRng).unwrap(); + let msg_raw = b"xxx yyy <<< zzz >>> bunny"; + let msg = Fq::from_le_bytes_mod_order(msg_raw); + let sig = sk.sign::<blake2::Blake2b512>(&poseidon_config, &msg); + let pk = sk.public_key(); + pk.verify(&poseidon_config, &msg, &sig).unwrap(); + + let cs = ConstraintSystem::<Fq>::new_ref(); + let pk_var: GVar = GVar::new_witness(cs.clone(), || Ok(pk.0)).unwrap(); + let r_var: GVar = GVar::new_witness(cs.clone(), || Ok(sig.r)).unwrap(); + let s_var = NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(sig.s)).unwrap(); + let msg_var = FpVar::<Fq>::new_witness(cs.clone(), || Ok(msg)).unwrap(); + + let res = verify::<G, GVar>(cs.clone(), poseidon_config, pk_var, (r_var, s_var), msg_var) + .unwrap(); + res.enforce_equal(&Boolean::<Fq>::TRUE).unwrap(); + + dbg!(cs.num_constraints()); + assert!(cs.is_satisfied().unwrap()); + } +}
M src/ed_on_bn254_twist.rs → src/ed_on_bn254_twist.rs

@@ -103,3 +103,18 @@ let v1 = (v0 * x).into_affine();

let v2 = untwist(u1); assert_eq!(v1, v2); } + +// constraints related logic +#[cfg(feature = "r1cs")] +pub mod constraints { + use ark_r1cs_std::fields::fp::FpVar; + use ark_r1cs_std::groups::curves::twisted_edwards::AffineVar; + + use crate::ed_on_bn254_twist::{BaseField as Fq, EdwardsConfig}; + + /// A variable that is the R1CS equivalent of `crate::Fq`. + pub type FqVar = FpVar<Fq>; + + /// A variable that is the R1CS equivalent of `crate::EdwardsAffine`. + pub type EdwardsVar = AffineVar<EdwardsConfig, FqVar>; +}
M src/eddsa.rs → src/eddsa.rs

@@ -7,7 +7,7 @@ use ark_ec::{

twisted_edwards::{Affine, TECurveConfig}, AffineRepr, }; -use ark_ff::PrimeField; +use ark_ff::{BigInteger, PrimeField}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use digest::Digest; use digest::OutputSizeUser;

@@ -45,7 +45,7 @@ }

/// `PublicKey` is EdDSA signature verification key #[derive(Copy, Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] -pub struct PublicKey<TE: TECurveConfig>(Affine<TE>); +pub struct PublicKey<TE: TECurveConfig>(pub Affine<TE>); impl<TE: TECurveConfig> PublicKey<TE> { pub fn xy(&self) -> (&TE::BaseField, &TE::BaseField) {

@@ -121,18 +121,20 @@ pub fn public_key(&self) -> &PublicKey<TE> {

&self.public_key } - pub fn sign<D: Digest, E: Absorb>( + pub fn sign<D: Digest>( &self, poseidon: &PoseidonConfig<TE::BaseField>, - message: &[E], + message: &TE::BaseField, ) -> Signature<TE> { let (x, prefix) = self.secret_key.expand::<TE::ScalarField, D>(); let mut h = D::new(); h.update(prefix); - message - .iter() - .for_each(|m| h.update(m.to_sponge_bytes_as_vec())); + let msg_bytes = message.into_bigint().to_bytes_le(); + let mut msg32: [u8; 32] = [0; 32]; + msg32[..msg_bytes.len()].copy_from_slice(&msg_bytes[..]); + h.update(msg32); + let r: TE::ScalarField = crate::from_digest(h); let sig_r: Affine<TE> = (Affine::<TE>::generator() * r).into();

@@ -144,10 +146,12 @@ poseidon.absorb(sig_r_y);

let (pk_x, pk_y) = self.public_key.0.xy().unwrap(); poseidon.absorb(pk_x); poseidon.absorb(pk_y); - message.iter().for_each(|m| poseidon.absorb(m)); + poseidon.absorb(message); - let k = poseidon.squeeze_field_elements::<TE::ScalarField>(1); + // use poseidon over Fq, so that it can be done too in-circuit + let k = poseidon.squeeze_field_elements::<TE::BaseField>(1); let k = k.first().unwrap(); + let k = TE::ScalarField::from_le_bytes_mod_order(&k.into_bigint().to_bytes_le()); let sig_s = (x * k) + r;

@@ -169,10 +173,10 @@ impl<TE: TECurveConfig + Clone> PublicKey<TE>

where TE::BaseField: PrimeField + Absorb, { - pub fn verify<E: Absorb>( + pub fn verify( &self, poseidon: &PoseidonConfig<TE::BaseField>, - message: &[E], + message: &TE::BaseField, signature: &Signature<TE>, ) -> Result<(), Error> { let mut poseidon = PoseidonSponge::new(poseidon);

@@ -183,12 +187,13 @@ poseidon.absorb(sig_r_y);

let (pk_x, pk_y) = self.0.xy().unwrap(); poseidon.absorb(pk_x); poseidon.absorb(pk_y); - message.iter().for_each(|m| poseidon.absorb(m)); + poseidon.absorb(message); - let k = poseidon.squeeze_field_elements::<TE::ScalarField>(1); + // use poseidon over Fq, so that it can be done too in-circuit + let k = poseidon.squeeze_field_elements::<TE::BaseField>(1); let k = k.first().unwrap(); - let kx_b = self.0 * k; + let kx_b = self.0.mul_bigint(k.into_bigint()); let s_b = Affine::<TE>::generator() * signature.s(); let r_rec: Affine<TE> = (s_b - kx_b).into();
M src/lib.rs → src/lib.rs

@@ -2,6 +2,9 @@ pub mod ed_on_bn254_twist;

pub mod eddsa; pub mod signature; +#[cfg(feature = "r1cs")] +pub mod constraints; + use ark_ff::PrimeField; use digest::Digest; pub use eddsa::*;

@@ -60,14 +63,13 @@ fn run_test<TE: TECurveConfig + Clone, D: Digest>()

where TE::BaseField: Absorb + PrimeField, { - let poseidon = poseidon_config(4, 8, 55); + let poseidon = poseidon_config::<TE::BaseField>(4, 8, 55); let signing_key = SigningKey::<TE>::generate::<D>(&mut OsRng).unwrap(); - let message = b"xxx yyy <<< zzz >>> bunny"; - let signature = signing_key.sign::<D, _>(&poseidon, &message[..]); + let message_raw = b"xxx yyy <<< zzz >>> bunny"; + let message = TE::BaseField::from_le_bytes_mod_order(message_raw); + let signature = signing_key.sign::<D>(&poseidon, &message); let public_key = signing_key.public_key(); - public_key - .verify::<_>(&poseidon, &message[..], &signature) - .unwrap(); + public_key.verify(&poseidon, &message, &signature).unwrap(); } #[test]
M src/signature.rs → src/signature.rs

@@ -6,8 +6,8 @@

/// `SignatureComponents` contains the realized parts of a signature #[derive(Copy, Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct Signature<TE: TECurveConfig + Clone> { - r: Affine<TE>, - s: TE::ScalarField, + pub r: Affine<TE>, + pub s: TE::ScalarField, } impl<TE: TECurveConfig + Clone> Signature<TE> {