mirror of
https://github.com/arnaucube/sigmabus-poc.git
synced 2026-01-13 01:11:34 +01:00
Add Sigmabus PoC implementation
This commit is contained in:
167
src/circuits.rs
Normal file
167
src/circuits.rs
Normal 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
16
src/lib.rs
Normal 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
189
src/sigmabus.rs
Normal 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(¶ms.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(¶ms.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(¶ms.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(¶ms.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, ¶ms, &mut transcript_p, x).unwrap();
|
||||
|
||||
// verify Sigmabus proof for X==x*G
|
||||
let mut transcript_v = PoseidonTranscript::<G1Projective>::new(&poseidon_config);
|
||||
Sigmabus::<Bn254>::verify(¶ms, &mut transcript_v, proof, X).unwrap();
|
||||
}
|
||||
}
|
||||
90
src/transcript.rs
Normal file
90
src/transcript.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user