Browse Source

Merge pull request #1 from arnaucube/main

add in-circuit EdDSA verification gadget
main
kilic 4 months ago
committed by GitHub
parent
commit
6a38d459b2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
8 changed files with 181 additions and 55 deletions
  1. +2
    -1
      .gitignore
  2. +7
    -2
      Cargo.toml
  3. +5
    -0
      README.md
  4. +1
    -1
      rust-toolchain
  5. +90
    -0
      src/constraints.rs
  6. +15
    -0
      src/ed_on_bn254_twist.rs
  7. +28
    -23
      src/eddsa.rs
  8. +33
    -28
      src/lib.rs

+ 2
- 1
.gitignore

@ -1,2 +1,3 @@
/target
*.py
*.py
Cargo.lock

+ 7
- 2
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"]

+ 5
- 0
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`

+ 1
- 1
rust-toolchain

@ -1 +1 @@
1.75.0
1.82.0

+ 90
- 0
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_r1cs_std::{
boolean::Boolean, fields::fp::FpVar, fields::nonnative::NonNativeFieldVar, groups::CurveVar,
ToBitsGadget, ToConstraintFieldGadget,
};
use ark_relations::r1cs::ConstraintSystemRef;
/// 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>>,
) -> ark_relations::r1cs::Result<Boolean<CF<C>>>
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()?;
let pk_xy = pk.to_constraint_field()?;
let mut poseidon = PoseidonSpongeVar::new(cs.clone(), &poseidon_config);
poseidon.absorb(&r_xy)?;
poseidon.absorb(&pk_xy)?;
poseidon.absorb(&msg)?;
let k = poseidon.squeeze_field_elements(1)?;
let k = k
.first()
.ok_or(ark_relations::r1cs::SynthesisError::Unsatisfiable)?;
let kx_b = pk.scalar_mul_le(k.to_bits_le()?.iter())?;
let g = GC::new_constant(cs.clone(), C::generator())?;
let s_b = g.scalar_mul_le(s.to_bits_le()?.iter())?;
let r_rec: GC = s_b - kx_b;
Ok(r_rec.is_eq(&r)?)
}
#[cfg(test)]
mod tests {
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 super::*;
use crate::ed_on_bn254_twist::{
constraints::EdwardsVar as GVar, BaseField as Fq, EdwardsConfig, EdwardsProjective as G,
ScalarField as Fr,
};
use crate::{poseidon_config, 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)
.unwrap();
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.as_ref())).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());
}
}

+ 15
- 0
src/ed_on_bn254_twist.rs

@ -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>;
}

+ 28
- 23
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;
@ -48,8 +48,8 @@ impl SecretKey {
pub struct PublicKey<TE: TECurveConfig>(Affine<TE>);
impl<TE: TECurveConfig> PublicKey<TE> {
pub fn xy(&self) -> (&TE::BaseField, &TE::BaseField) {
self.as_ref().xy().unwrap()
pub fn xy(&self) -> Result<(&TE::BaseField, &TE::BaseField), Error> {
self.as_ref().xy().ok_or(Error::Coordinates)
}
pub fn to_bytes(&self) -> Vec<u8> {
@ -121,37 +121,41 @@ where
&self.public_key
}
pub fn sign<D: Digest, E: Absorb>(
pub fn sign<D: Digest>(
&self,
poseidon: &PoseidonConfig<TE::BaseField>,
message: &[E],
) -> Signature<TE> {
message: &TE::BaseField,
) -> Result<Signature<TE>, Error> {
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();
let mut poseidon = PoseidonSponge::new(poseidon);
let (sig_r_x, sig_r_y) = sig_r.xy().unwrap();
let (sig_r_x, sig_r_y) = sig_r.xy().ok_or(Error::Coordinates)?;
poseidon.absorb(sig_r_x);
poseidon.absorb(sig_r_y);
let (pk_x, pk_y) = self.public_key.0.xy().unwrap();
let (pk_x, pk_y) = self.public_key.0.xy().ok_or(Error::Coordinates)?;
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);
let k = k.first().unwrap();
// 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().ok_or(Error::BadDigestOutput)?;
let k = TE::ScalarField::from_le_bytes_mod_order(&k.into_bigint().to_bytes_le());
let sig_s = (x * k) + r;
Signature::new(sig_r, sig_s)
Ok(Signature::new(sig_r, sig_s))
}
}
@ -169,26 +173,27 @@ impl PublicKey
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);
let (sig_r_x, sig_r_y) = signature.r().xy().unwrap();
let (sig_r_x, sig_r_y) = signature.r().xy().ok_or(Error::Coordinates)?;
poseidon.absorb(sig_r_x);
poseidon.absorb(sig_r_y);
let (pk_x, pk_y) = self.0.xy().unwrap();
let (pk_x, pk_y) = self.0.xy().ok_or(Error::Coordinates)?;
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);
let k = k.first().unwrap();
// 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().ok_or(Error::BadDigestOutput)?;
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();

+ 33
- 28
src/lib.rs

@ -1,9 +1,15 @@
use ark_ff::PrimeField;
use digest::Digest;
impl ark_std::error::Error for Error {}
use ark_crypto_primitives::sponge::poseidon::{find_poseidon_ark_and_mds, PoseidonConfig};
pub mod ed_on_bn254_twist;
pub mod eddsa;
pub mod signature;
use ark_ff::PrimeField;
use digest::Digest;
#[cfg(feature = "r1cs")]
pub mod constraints;
pub use eddsa::*;
pub(crate) fn from_digest<F: PrimeField, D: Digest>(digest: D) -> F {
@ -13,6 +19,7 @@ pub(crate) fn from_digest(digest: D) -> F {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Error {
Coordinates,
Verify,
BadDigestOutput,
}
@ -20,54 +27,52 @@ pub enum Error {
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
Error::Coordinates => write!(f, "Could not obtain the coordinates of a point"),
Error::Verify => write!(f, "Signature verification failed"),
Error::BadDigestOutput => write!(f, "Bad digest output size"),
}
}
}
impl ark_std::error::Error for Error {}
/// Generates poseidon constants and returns the config
pub fn poseidon_config<F: PrimeField>(
rate: usize,
full_rounds: usize,
partial_rounds: usize,
) -> PoseidonConfig<F> {
let prime_bits = F::MODULUS_BIT_SIZE as u64;
let (ark, mds) = find_poseidon_ark_and_mds(
prime_bits,
rate,
full_rounds as u64,
partial_rounds as u64,
0,
);
PoseidonConfig::new(full_rounds, partial_rounds, 5, mds, ark, rate, 1)
}
#[cfg(test)]
mod test {
use crate::SigningKey;
use ark_crypto_primitives::sponge::poseidon::{find_poseidon_ark_and_mds, PoseidonConfig};
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::twisted_edwards::TECurveConfig;
use ark_ff::PrimeField;
use digest::Digest;
use rand_core::OsRng;
/// Generates poseidon constants and returns the config
pub fn poseidon_config<F: PrimeField>(
rate: usize,
full_rounds: usize,
partial_rounds: usize,
) -> PoseidonConfig<F> {
let prime_bits = F::MODULUS_BIT_SIZE as u64;
let (ark, mds) = find_poseidon_ark_and_mds(
prime_bits,
rate,
full_rounds as u64,
partial_rounds as u64,
0,
);
PoseidonConfig::new(full_rounds, partial_rounds, 5, mds, ark, rate, 1)
}
use super::poseidon_config;
use crate::SigningKey;
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).unwrap();
let public_key = signing_key.public_key();
public_key
.verify::<_>(&poseidon, &message[..], &signature)
.unwrap();
public_key.verify(&poseidon, &message, &signature).unwrap();
}
#[test]

Loading…
Cancel
Save