Add utils::vec & pedersen modules (#5) - utils::vec module: port a mix of vec utils from nova-study, multifolding-poc and protogalaxy-poc repos - pedersen.rs: Pedersen commitment module - other: - update FoldingScheme trait interface: rm rng, update internal types naming as agreed in today's call - update Cargo.toml dev-dependencies imports, since bn254 - grumpkin is not ready yet, use bls12-377 - bw6-761 curve cycle - transcript module: add absorb_point method
arnaucube 17317030+arnaucube@users.noreply.github.com
Tue, 22 Aug 2023 14:48:10 +0200
7 files changed,
280 insertions(+),
33 deletions(-)
M
Cargo.toml
→
Cargo.toml
@@ -14,7 +14,8 @@ ark-r1cs-std = { version = "^0.4.0", default-features = false }
thiserror = "1.0" [dev-dependencies] -ark-bls12-381 = "0.4.0" +ark-bls12-377 = "0.4.0" +ark-bw6-761 = "0.4.0" [features] default = []
M
src/lib.rs
→
src/lib.rs
@@ -8,6 +8,8 @@ use thiserror::Error;
pub mod transcript; use transcript::Transcript; +pub mod pedersen; +pub mod utils; #[derive(Debug, Error)] pub enum Error {@@ -19,23 +21,21 @@ /// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined
/// over a cycle of curves (C1, C2), where: /// - C1 is the main curve, which ScalarField we use as our F for al the field operations /// - C2 is the auxiliary curve, which we use for the commitments, whose BaseField (for point -/// coordinates) are in the C1::ScalarField +/// coordinates) are in the C1::ScalarField. +/// In other words, C1.Fq == C2.Fr, and C1.Fr == C2.Fq. pub trait FoldingScheme<C1: CurveGroup, C2: CurveGroup>: Clone + Debug where C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>, C2::BaseField: PrimeField, { - // type PCS: PolynomialCommitmentScheme<C>; // maybe not needed, just PedersenCommitment type PreprocessorParam: Debug; type ProverParam: Debug; type VerifierParam: Debug; - type FreshInstance: Debug; - type PublicInput: Debug; + type Witness: Debug; type CommittedInstanceWithWitness: Debug; type CommittedInstance: Clone + Debug; fn preprocess( - // pcs_param: &<Self::CS as PolynomialCommitmentScheme<C>>::Param, prep_param: &Self::PreprocessorParam, ) -> Result<(Self::ProverParam, Self::VerifierParam), Error>;@@ -46,17 +46,15 @@
fn prove( pp: &Self::ProverParam, running_instance: &mut Self::CommittedInstanceWithWitness, - incomming_instances: &[Self::FreshInstance], - transcript: &mut impl Transcript<C1::ScalarField>, - rng: impl RngCore, + incomming_instances: &[Self::Witness], + transcript: &mut impl Transcript<C1>, ) -> Result<(), Error>; fn verify( vp: &Self::VerifierParam, running_instance: &mut Self::CommittedInstance, - incomming_instances: &[Self::PublicInput], - transcript: &mut impl Transcript<C1::ScalarField>, - rng: impl RngCore, + incomming_instances: &[Self::CommittedInstance], + transcript: &mut impl Transcript<C1>, ) -> Result<(), Error>; }@@ -72,14 +70,14 @@
fn prove( pp: &Self::ProverParam, running_instance: &Self::CommittedInstanceWithWitness, - transcript: &mut impl Transcript<C::ScalarField>, + transcript: &mut impl Transcript<C>, rng: impl RngCore, ) -> Result<(), Error>; fn verify( vp: &Self::VerifierParam, running_instance: &Self::CommittedInstance, - transcript: &mut impl Transcript<C::ScalarField>, + transcript: &mut impl Transcript<C>, rng: impl RngCore, ) -> Result<(), Error>; }
A
src/pedersen.rs
@@ -0,0 +1,127 @@
+use ark_ec::{CurveGroup, Group}; +use ark_std::{rand::Rng, UniformRand}; +use std::marker::PhantomData; + +use crate::utils::vec::{vec_add, vec_scalar_mul}; + +use crate::transcript::Transcript; +use ark_crypto_primitives::sponge::Absorb; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Proof<C: CurveGroup> { + R: C, + u: Vec<C::ScalarField>, + r_u: C::ScalarField, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Params<C: CurveGroup> { + h: C, + pub generators: Vec<C::Affine>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Pedersen<C: CurveGroup> +where + <C as Group>::ScalarField: Absorb, +{ + _c: PhantomData<C>, +} + +impl<C: CurveGroup> Pedersen<C> +where + <C as Group>::ScalarField: Absorb, +{ + pub fn new_params<R: Rng>(rng: &mut R, max: usize) -> Params<C> { + let generators: Vec<C::Affine> = std::iter::repeat_with(|| C::Affine::rand(rng)) + .take(max.next_power_of_two()) + .collect(); + let params: Params<C> = Params::<C> { + h: C::rand(rng), + generators, + }; + params + } + + pub fn commit(params: &Params<C>, v: &Vec<C::ScalarField>, r: &C::ScalarField) -> C { + // h⋅r + <g, v> + params.h.mul(r) + C::msm(¶ms.generators[..v.len()], v).unwrap() + } + + pub fn prove( + params: &Params<C>, + transcript: &mut impl Transcript<C>, + cm: &C, + v: &Vec<C::ScalarField>, + r: &C::ScalarField, + ) -> Proof<C> { + transcript.absorb_point(cm); + let r1 = transcript.get_challenge(); + let d = transcript.get_challenges(v.len()); + + // R = h⋅r_1 + <g, d> + let R: C = params.h.mul(r1) + C::msm(¶ms.generators[..d.len()], &d).unwrap(); + + transcript.absorb_point(&R); + let e = transcript.get_challenge(); + + // u = d + v⋅e + let u = vec_add(&vec_scalar_mul(v, &e), &d); + // r_u = e⋅r + r_1 + let r_u = e * r + r1; + + Proof::<C> { R, u, r_u } + } + + pub fn verify( + params: &Params<C>, + transcript: &mut impl Transcript<C>, + cm: C, + proof: Proof<C>, + ) -> bool { + transcript.absorb_point(&cm); + transcript.get_challenge(); // r_1 + transcript.get_challenges(proof.u.len()); // d + transcript.absorb_point(&proof.R); + let e = transcript.get_challenge(); + + // check that: R + cm == h⋅r_u + <g, u> + let lhs = proof.R + cm.mul(e); + let rhs = params.h.mul(proof.r_u) + + C::msm(¶ms.generators[..proof.u.len()], &proof.u).unwrap(); + if lhs != rhs { + return false; + } + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript}; + + use ark_bls12_377::{Fr, G1Projective}; + + #[test] + fn test_pedersen_vector() { + let mut rng = ark_std::test_rng(); + + const n: usize = 10; + // setup params + let params = Pedersen::<G1Projective>::new_params(&mut rng, n); + let poseidon_config = poseidon_test_config::<Fr>(); + + // init Prover's transcript + let mut transcript_p = PoseidonTranscript::<G1Projective>::new(&poseidon_config); + // init Verifier's transcript + let mut transcript_v = PoseidonTranscript::<G1Projective>::new(&poseidon_config); + + let v: Vec<Fr> = vec![Fr::rand(&mut rng); n]; + let r: Fr = Fr::rand(&mut rng); + let cm = Pedersen::<G1Projective>::commit(¶ms, &v, &r); + let proof = Pedersen::<G1Projective>::prove(¶ms, &mut transcript_p, &cm, &v, &r); + let v = Pedersen::<G1Projective>::verify(¶ms, &mut transcript_v, cm, proof); + assert!(v); + } +}
M
src/transcript/mod.rs
→
src/transcript/mod.rs
@@ -1,14 +1,15 @@
-use ark_ff::PrimeField; +use ark_ec::CurveGroup; use ark_std::fmt::Debug; pub mod poseidon; -pub trait Transcript<F: PrimeField> { +pub trait Transcript<C: CurveGroup> { type TranscriptConfig: Debug; fn new(config: &Self::TranscriptConfig) -> Self; - fn absorb(&mut self, v: &F); - fn absorb_vec(&mut self, v: &[F]); - fn get_challenge(&mut self) -> F; - fn get_challenges(&mut self, n: usize) -> Vec<F>; + fn absorb(&mut self, v: &C::ScalarField); + fn absorb_vec(&mut self, v: &[C::ScalarField]); + fn absorb_point(&mut self, v: &C); + fn get_challenge(&mut self) -> C::ScalarField; + fn get_challenges(&mut self, n: usize) -> Vec<C::ScalarField>; }
M
src/transcript/poseidon.rs
→
src/transcript/poseidon.rs
@@ -3,42 +3,75 @@ constraints::CryptographicSpongeVar,
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, }; -use ark_ff::PrimeField; +use ark_ec::{AffineRepr, CurveGroup, Group}; +use ark_ff::{BigInteger, Field, PrimeField}; use ark_r1cs_std::fields::fp::FpVar; use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use crate::transcript::Transcript; /// PoseidonTranscript implements the Transcript trait using the Poseidon hash -pub struct PoseidonTranscript<F: PrimeField + Absorb> { - sponge: PoseidonSponge<F>, +pub struct PoseidonTranscript<C: CurveGroup> +where + <C as Group>::ScalarField: Absorb, +{ + sponge: PoseidonSponge<C::ScalarField>, } -impl<F: PrimeField + Absorb> Transcript<F> for PoseidonTranscript<F> { - type TranscriptConfig = PoseidonConfig<F>; +impl<C: CurveGroup> Transcript<C> for PoseidonTranscript<C> +where + <C as Group>::ScalarField: Absorb, +{ + type TranscriptConfig = PoseidonConfig<C::ScalarField>; fn new(poseidon_config: &Self::TranscriptConfig) -> Self { - let sponge = PoseidonSponge::<F>::new(poseidon_config); + let sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config); Self { sponge } } - fn absorb(&mut self, v: &F) { + fn absorb(&mut self, v: &C::ScalarField) { self.sponge.absorb(&v); } - fn absorb_vec(&mut self, v: &[F]) { + fn absorb_vec(&mut self, v: &[C::ScalarField]) { self.sponge.absorb(&v); } - fn get_challenge(&mut self) -> F { + fn absorb_point(&mut self, p: &C) { + self.sponge.absorb(&prepare_point(p)); + } + fn get_challenge(&mut self) -> C::ScalarField { let c = self.sponge.squeeze_field_elements(1); self.sponge.absorb(&c[0]); c[0] } - fn get_challenges(&mut self, n: usize) -> Vec<F> { + fn get_challenges(&mut self, n: usize) -> Vec<C::ScalarField> { let c = self.sponge.squeeze_field_elements(n); self.sponge.absorb(&c); c } } +// 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()), + ] +} + /// PoseidonTranscriptVar implements the gadget compatible with PoseidonTranscript pub struct PoseidonTranscriptVar<F: PrimeField> { sponge: PoseidonSpongeVar<F>,@@ -67,9 +100,9 @@ }
} #[cfg(test)] -mod tests { +pub mod tests { use super::*; - use ark_bls12_381::Fr; + use ark_bls12_377::{Fr, G1Projective}; use ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem;@@ -105,7 +138,7 @@ #[test]
fn test_transcript_and_transcriptvar() { // use 'native' transcript let config = poseidon_test_config::<Fr>(); - let mut tr = PoseidonTranscript::<Fr>::new(&config); + let mut tr = PoseidonTranscript::<G1Projective>::new(&config); tr.absorb(&Fr::from(42_u32)); let c = tr.get_challenge();
A
src/utils/vec.rs
@@ -0,0 +1,86 @@
+use ark_ff::PrimeField; +use ark_std::cfg_iter; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SparseMatrix<F: PrimeField> { + pub n_rows: usize, + pub n_cols: usize, + pub coeffs: Vec<(usize, usize, F)>, +} + +pub fn dense_matrix_to_sparse<F: PrimeField>(m: Vec<Vec<F>>) -> SparseMatrix<F> { + let mut r = SparseMatrix::<F> { + n_rows: m.len(), + n_cols: m[0].len(), + coeffs: Vec::new(), + }; + for (i, m_i) in m.iter().enumerate() { + for (j, m_ij) in m_i.iter().enumerate() { + if !m_ij.is_zero() { + r.coeffs.push((i, j, *m_ij)); + } + } + } + r +} + +pub fn vec_add<F: PrimeField>(a: &[F], b: &[F]) -> Vec<F> { + assert_eq!(a.len(), b.len()); + let mut r: Vec<F> = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] + b[i]; + } + r +} + +pub fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Vec<F> { + assert_eq!(a.len(), b.len()); + let mut r: Vec<F> = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] - b[i]; + } + r +} + +pub fn vec_scalar_mul<F: PrimeField>(vec: &[F], c: &F) -> Vec<F> { + let mut result = vec![F::zero(); vec.len()]; + for (i, a) in vec.iter().enumerate() { + result[i] = *a * c; + } + result +} + +pub fn is_zero_vec<F: PrimeField>(vec: &[F]) -> bool { + for e in vec { + if !e.is_zero() { + return false; + } + } + true +} + +pub fn mat_vec_mul<F: PrimeField>(M: &Vec<Vec<F>>, z: &[F]) -> Vec<F> { + assert!(!M.is_empty()); + assert_eq!(M[0].len(), z.len()); + + let mut r: Vec<F> = vec![F::zero(); M.len()]; + for (i, M_i) in M.iter().enumerate() { + for (j, M_ij) in M_i.iter().enumerate() { + r[i] += *M_ij * z[j]; + } + } + r +} + +pub fn mat_vec_mul_sparse<F: PrimeField>(matrix: &SparseMatrix<F>, vector: &[F]) -> Vec<F> { + let mut res = vec![F::zero(); matrix.n_cols]; + for &(row, col, value) in matrix.coeffs.iter() { + res[row] += value * vector[col]; + } + res +} + +pub fn hadamard<F: PrimeField>(a: &[F], b: &[F]) -> Vec<F> { + assert_eq!(a.len(), b.len()); + cfg_iter!(a).zip(b).map(|(a, b)| *a * b).collect() +}