mirror of
https://github.com/arnaucube/arkeddsa.git
synced 2026-01-10 15:31:29 +01:00
add in-circuit EdDSA verification gadget
add in-circuit EdDSA verification gadget compatible with the out-circuit logic.
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.75.0
|
||||
1.82.0
|
||||
|
||||
90
src/constraints.rs
Normal file
90
src/constraints.rs
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -103,3 +103,18 @@ fn test_twist() {
|
||||
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>;
|
||||
}
|
||||
|
||||
33
src/eddsa.rs
33
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 @@ impl SecretKey {
|
||||
|
||||
/// `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 @@ where
|
||||
&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 @@ where
|
||||
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 @@ where
|
||||
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();
|
||||
|
||||
|
||||
14
src/lib.rs
14
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 @@ mod test {
|
||||
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]
|
||||
|
||||
@@ -6,8 +6,8 @@ use ark_serialize::CanonicalSerialize;
|
||||
/// `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> {
|
||||
|
||||
Reference in New Issue
Block a user