Add Sigmabus PoC implementation

This commit is contained in:
2023-09-29 15:16:58 +02:00
commit f87a71f6a6
8 changed files with 1188 additions and 0 deletions

167
src/circuits.rs Normal file
View File

@@ -0,0 +1,167 @@
use ark_crypto_primitives::crh::{
poseidon::constraints::{CRHGadget, CRHParametersVar},
CRHSchemeGadget,
};
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
use ark_ec::{AffineRepr, CurveGroup};
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, fields::fp::FpVar};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
use core::marker::PhantomData;
// CF (ConstraintField)
pub type CF<C> = <<C as CurveGroup>::Affine as AffineRepr>::ScalarField;
#[derive(Debug, Clone)]
pub struct GenZKCircuit<C: CurveGroup> {
pub _c: PhantomData<C>,
pub poseidon_config: PoseidonConfig<C::ScalarField>,
// public
pub cm: C::ScalarField,
pub s: C::ScalarField,
pub r_h: C::ScalarField,
pub c: C::ScalarField,
// private
pub x: C::ScalarField,
pub r: C::ScalarField,
pub o_h: C::ScalarField,
}
impl<C: CurveGroup> ConstraintSynthesizer<CF<C>> for GenZKCircuit<C>
where
C::ScalarField: Absorb,
{
fn generate_constraints(self, cs: ConstraintSystemRef<CF<C>>) -> Result<(), SynthesisError> {
// public inputs
let cmVar = FpVar::<C::ScalarField>::new_input(cs.clone(), || Ok(self.cm))?;
let sVar = FpVar::<C::ScalarField>::new_input(cs.clone(), || Ok(self.s))?;
let r_hVar = FpVar::<C::ScalarField>::new_input(cs.clone(), || Ok(self.r_h))?;
let cVar = FpVar::<C::ScalarField>::new_input(cs.clone(), || Ok(self.c))?;
// private inputs
let xVar = FpVar::<C::ScalarField>::new_witness(cs.clone(), || Ok(self.x))?;
let rVar = FpVar::<C::ScalarField>::new_witness(cs.clone(), || Ok(self.r))?;
let o_hVar = FpVar::<C::ScalarField>::new_witness(cs.clone(), || Ok(self.o_h))?;
let crh_params =
CRHParametersVar::<C::ScalarField>::new_witness(
cs.clone(),
|| Ok(self.poseidon_config),
)
.unwrap();
Self::check(&crh_params, cmVar, sVar, r_hVar, cVar, xVar, rVar, o_hVar)?;
Ok(())
}
}
impl<C: CurveGroup> GenZKCircuit<C>
where
C::ScalarField: Absorb,
{
#[allow(clippy::too_many_arguments)]
pub fn check(
crh_params: &CRHParametersVar<C::ScalarField>,
// public inputs:
cm: FpVar<C::ScalarField>,
s: FpVar<C::ScalarField>,
r_h: FpVar<C::ScalarField>,
c: FpVar<C::ScalarField>,
// private inputs:
x: FpVar<C::ScalarField>,
r: FpVar<C::ScalarField>,
o_h: FpVar<C::ScalarField>,
) -> Result<(), SynthesisError> {
// cm == Commit(x) (Poseidon)
let computed_cm = CRHGadget::<C::ScalarField>::evaluate(crh_params, &[x.clone()]).unwrap();
computed_cm.enforce_equal(&cm)?;
// r_h == HCommit(r, o_h) (Poseidon)
let computed_r_h =
CRHGadget::<C::ScalarField>::evaluate(crh_params, &[r.clone(), o_h.clone()]).unwrap();
computed_r_h.enforce_equal(&r_h)?;
// s == r + c * x
s.enforce_equal(&(r + (c * x)))?;
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use ark_bn254::{Fr, G1Projective};
use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge};
use ark_ec::Group;
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar};
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
use std::ops::Mul;
use crate::sigmabus::SigmaProof;
use crate::transcript::{tests::poseidon_test_config, PoseidonTranscript};
#[test]
fn test_gen_zk() {
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_test_config::<Fr>();
let mut transcript = PoseidonTranscript::<G1Projective>::new(&poseidon_config);
let x = Fr::rand(&mut rng);
// the next lines do the same that is done in Sigmabus::prove but here in the test to have
// access to the internal values:
let mut sponge = PoseidonSponge::<Fr>::new(&poseidon_config);
sponge.absorb(&x);
let cm: Fr = sponge.squeeze_field_elements(1)[0];
transcript.absorb(&cm);
let r = Fr::rand(&mut rng);
let o_h = Fr::rand(&mut rng);
let R = G1Projective::generator().mul(r);
let mut sponge = PoseidonSponge::<Fr>::new(&poseidon_config);
sponge.absorb(&vec![r, o_h]);
let r_h: Fr = sponge.squeeze_field_elements(1)[0];
transcript.absorb_point(&R);
transcript.absorb(&r_h);
let c = transcript.get_challenge();
let s = r + c * x;
let sigma_proof = SigmaProof { s, R, r_h };
// end of Sigmabus::prove
let cs = ConstraintSystem::<Fr>::new_ref();
// public inputs
let cmVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(cm)).unwrap();
let sVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(sigma_proof.s)).unwrap();
let r_hVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(sigma_proof.r_h)).unwrap();
let cVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(c)).unwrap();
// private inputs
let xVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(x)).unwrap();
let rVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(r)).unwrap();
let o_hVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(o_h)).unwrap();
let crh_params =
CRHParametersVar::<Fr>::new_witness(cs.clone(), || Ok(poseidon_config)).unwrap();
// GenZK
GenZKCircuit::<G1Projective>::check(
&crh_params,
cmVar,
sVar,
r_hVar,
cVar,
xVar,
rVar,
o_hVar,
)
.unwrap();
assert!(cs.is_satisfied().unwrap());
dbg!("num_constraints={:?}", cs.num_constraints());
}
}

16
src/lib.rs Normal file
View File

@@ -0,0 +1,16 @@
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
/// Proof of concept implementation of [Sigmabus](https://eprint.iacr.org/2023/1406) as described in section 3 of the paper, using Groth16's zkSNARK scheme.
pub mod circuits;
pub mod sigmabus;
pub mod transcript;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("SigmaProof verification failed")]
SigmaFail,
#[error("GenZK verification failed")]
GenZKFail,
}

189
src/sigmabus.rs Normal file
View File

@@ -0,0 +1,189 @@
use ark_crypto_primitives::{
crh::{poseidon::CRH, CRHScheme},
snark::SNARK,
sponge::{poseidon::PoseidonConfig, Absorb},
};
use ark_ec::{pairing::Pairing, CurveGroup, Group};
use ark_groth16::{Groth16, Proof as Groth16Proof};
use ark_std::{
rand::{CryptoRng, Rng},
UniformRand, Zero,
};
use std::marker::PhantomData;
use std::ops::Mul;
use crate::circuits::GenZKCircuit;
use crate::transcript::PoseidonTranscript;
use crate::Error;
/// Proof represents the Sigmabus proof
pub struct Proof<E: Pairing> {
cm: E::ScalarField,
sigma_proof: SigmaProof<E::G1>,
zkproof: Groth16Proof<E>,
}
/// SigmaProof represents the Sigma protocol proof (not Sigmabus proof)
pub struct SigmaProof<C: CurveGroup> {
pub s: C::ScalarField,
pub R: C,
pub r_h: C::ScalarField,
}
pub struct Params<E: Pairing> {
_e: PhantomData<E>,
poseidon_config: PoseidonConfig<E::ScalarField>,
pk: <Groth16<E> as SNARK<E::ScalarField>>::ProvingKey,
vk: <Groth16<E> as SNARK<E::ScalarField>>::VerifyingKey,
}
/// Sigmabus implements [Sigmabus](https://eprint.iacr.org/2023/1406) prover & verifier for proving
/// X=x*G as described in section 3 of the paper, using Groth16's zkSNARK scheme.
pub struct Sigmabus<E: Pairing> {
_e: PhantomData<E>,
}
impl<E: Pairing> Sigmabus<E>
where
E::ScalarField: Absorb,
{
pub fn setup<R: Rng + CryptoRng>(
rng: &mut R,
poseidon_config: &PoseidonConfig<E::ScalarField>,
) -> Params<E> {
let circuit = GenZKCircuit::<E::G1> {
_c: PhantomData,
poseidon_config: poseidon_config.clone(),
// public
cm: E::ScalarField::zero(),
s: E::ScalarField::zero(),
r_h: E::ScalarField::zero(),
c: E::ScalarField::zero(),
// private
x: E::ScalarField::zero(),
r: E::ScalarField::zero(),
o_h: E::ScalarField::zero(),
};
// generate the snark proof
let (pk, vk) = Groth16::<E>::circuit_specific_setup(circuit.clone(), rng).unwrap();
Params::<E> {
_e: PhantomData,
poseidon_config: poseidon_config.clone(),
pk,
vk,
}
}
pub fn prove<R: Rng + CryptoRng>(
rng: &mut R,
params: &Params<E>,
transcript: &mut PoseidonTranscript<E::G1>,
x: E::ScalarField,
) -> Result<Proof<E>, Error> {
// cm
let cm: E::ScalarField =
CRH::<E::ScalarField>::evaluate(&params.poseidon_config, [x]).unwrap();
transcript.absorb(&cm);
let r = E::ScalarField::rand(rng);
let o_h = E::ScalarField::rand(rng);
let R = E::G1::generator().mul(r);
let r_h: E::ScalarField =
CRH::<E::ScalarField>::evaluate(&params.poseidon_config, [r, o_h]).unwrap();
transcript.absorb_point(&R);
transcript.absorb(&r_h);
let c = transcript.get_challenge();
let s = r + c * x;
let circuit = GenZKCircuit::<E::G1> {
_c: PhantomData,
poseidon_config: params.poseidon_config.clone(),
// public
cm,
s,
r_h,
c,
// private
x,
r,
o_h,
};
// generate the snark proof
let zkproof = Groth16::<E>::prove(&params.pk, circuit.clone(), rng).unwrap();
Ok(Proof {
cm,
sigma_proof: SigmaProof { s, R, r_h },
zkproof,
})
}
pub fn verify(
params: &Params<E>,
transcript: &mut PoseidonTranscript<E::G1>,
proof: Proof<E>,
X: E::G1,
) -> Result<(), Error> {
let lhs = E::G1::generator().mul(proof.sigma_proof.s);
transcript.absorb(&proof.cm);
transcript.absorb_point(&proof.sigma_proof.R);
transcript.absorb(&proof.sigma_proof.r_h);
let c = transcript.get_challenge();
let rhs = proof.sigma_proof.R + X.mul(c);
if lhs != rhs {
return Err(Error::SigmaFail);
}
// verify zkSNARK proof
let public_input = [proof.cm, proof.sigma_proof.s, proof.sigma_proof.r_h, c];
let valid_proof = Groth16::<E>::verify(&params.vk, &public_input, &proof.zkproof).unwrap();
if !valid_proof {
return Err(Error::GenZKFail);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ark_bn254::{Bn254, Fr, G1Projective};
use ark_std::rand::{RngCore, SeedableRng};
use ark_std::test_rng;
use crate::transcript::tests::poseidon_test_config;
#[test]
fn test_sigmabus_prove_verify() {
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
let poseidon_config = poseidon_test_config::<Fr>();
// generate the trusted setup
let params = Sigmabus::<Bn254>::setup(&mut rng, &poseidon_config);
// compute X = x * G
let x = Fr::rand(&mut rng);
let X = G1Projective::generator().mul(x);
let mut transcript_p = PoseidonTranscript::<G1Projective>::new(&poseidon_config);
// generate Sigmabus proof for X==x*G
let proof = Sigmabus::<Bn254>::prove(&mut rng, &params, &mut transcript_p, x).unwrap();
// verify Sigmabus proof for X==x*G
let mut transcript_v = PoseidonTranscript::<G1Projective>::new(&poseidon_config);
Sigmabus::<Bn254>::verify(&params, &mut transcript_v, proof, X).unwrap();
}
}

90
src/transcript.rs Normal file
View File

@@ -0,0 +1,90 @@
use ark_crypto_primitives::sponge::{
poseidon::{PoseidonConfig, PoseidonSponge},
Absorb, CryptographicSponge,
};
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, Field, PrimeField};
pub struct PoseidonTranscript<C: CurveGroup>
where
<C as Group>::ScalarField: Absorb,
{
sponge: PoseidonSponge<C::ScalarField>,
}
impl<C: CurveGroup> PoseidonTranscript<C>
where
<C as Group>::ScalarField: Absorb,
{
pub fn new(poseidon_config: &PoseidonConfig<C::ScalarField>) -> Self {
let sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
Self { sponge }
}
pub fn absorb(&mut self, v: &C::ScalarField) {
self.sponge.absorb(&v);
}
pub fn absorb_point(&mut self, p: &C) {
self.sponge.absorb(&prepare_point(p));
}
pub fn get_challenge(&mut self) -> C::ScalarField {
let c = self.sponge.squeeze_field_elements(1);
self.sponge.absorb(&c[0]);
c[0]
}
}
// Returns the point coordinates in Fr, so it can be absrobed by the transcript. It does not work
// over bytes in order to have a logic that can be reproduced in-circuit.
fn prepare_point<C: CurveGroup>(p: &C) -> Vec<C::ScalarField> {
let binding = p.into_affine();
let p_coords = &binding.xy().unwrap();
let x_bi = p_coords
.0
.to_base_prime_field_elements()
.next()
.expect("a")
.into_bigint();
let y_bi = p_coords
.1
.to_base_prime_field_elements()
.next()
.expect("a")
.into_bigint();
vec![
C::ScalarField::from_le_bytes_mod_order(x_bi.to_bytes_le().as_ref()),
C::ScalarField::from_le_bytes_mod_order(y_bi.to_bytes_le().as_ref()),
]
}
#[cfg(test)]
pub mod tests {
use super::*;
use ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds;
/// WARNING the method poseidon_test_config is for tests only
#[cfg(test)]
pub fn poseidon_test_config<F: PrimeField>() -> PoseidonConfig<F> {
let full_rounds = 8;
let partial_rounds = 31;
let alpha = 5;
let rate = 2;
let (ark, mds) = find_poseidon_ark_and_mds::<F>(
F::MODULUS_BIT_SIZE as u64,
rate,
full_rounds,
partial_rounds,
0,
);
PoseidonConfig::new(
full_rounds as usize,
partial_rounds as usize,
alpha,
mds,
ark,
rate,
1,
)
}
}