diff --git a/Cargo.toml b/Cargo.toml index 7724955..ca5a9f2 100644 --- a/Cargo.toml +++ b/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 = [] diff --git a/src/lib.rs b/src/lib.rs index 44aeb0b..de53ca6 100644 --- a/src/lib.rs +++ b/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 @@ pub enum Error { /// 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: Clone + Debug where C1: CurveGroup, C2::BaseField: PrimeField, { - // type PCS: PolynomialCommitmentScheme; // 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: &>::Param, prep_param: &Self::PreprocessorParam, ) -> Result<(Self::ProverParam, Self::VerifierParam), Error>; @@ -46,17 +46,15 @@ where fn prove( pp: &Self::ProverParam, running_instance: &mut Self::CommittedInstanceWithWitness, - incomming_instances: &[Self::FreshInstance], - transcript: &mut impl Transcript, - rng: impl RngCore, + incomming_instances: &[Self::Witness], + transcript: &mut impl Transcript, ) -> Result<(), Error>; fn verify( vp: &Self::VerifierParam, running_instance: &mut Self::CommittedInstance, - incomming_instances: &[Self::PublicInput], - transcript: &mut impl Transcript, - rng: impl RngCore, + incomming_instances: &[Self::CommittedInstance], + transcript: &mut impl Transcript, ) -> Result<(), Error>; } @@ -72,14 +70,14 @@ pub trait Decider: Clone + Debug { fn prove( pp: &Self::ProverParam, running_instance: &Self::CommittedInstanceWithWitness, - transcript: &mut impl Transcript, + transcript: &mut impl Transcript, rng: impl RngCore, ) -> Result<(), Error>; fn verify( vp: &Self::VerifierParam, running_instance: &Self::CommittedInstance, - transcript: &mut impl Transcript, + transcript: &mut impl Transcript, rng: impl RngCore, ) -> Result<(), Error>; } diff --git a/src/pedersen.rs b/src/pedersen.rs new file mode 100644 index 0000000..e21406a --- /dev/null +++ b/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 { + R: C, + u: Vec, + r_u: C::ScalarField, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Params { + h: C, + pub generators: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Pedersen +where + ::ScalarField: Absorb, +{ + _c: PhantomData, +} + +impl Pedersen +where + ::ScalarField: Absorb, +{ + pub fn new_params(rng: &mut R, max: usize) -> Params { + let generators: Vec = std::iter::repeat_with(|| C::Affine::rand(rng)) + .take(max.next_power_of_two()) + .collect(); + let params: Params = Params:: { + h: C::rand(rng), + generators, + }; + params + } + + pub fn commit(params: &Params, v: &Vec, r: &C::ScalarField) -> C { + // h⋅r + + params.h.mul(r) + C::msm(¶ms.generators[..v.len()], v).unwrap() + } + + pub fn prove( + params: &Params, + transcript: &mut impl Transcript, + cm: &C, + v: &Vec, + r: &C::ScalarField, + ) -> Proof { + transcript.absorb_point(cm); + let r1 = transcript.get_challenge(); + let d = transcript.get_challenges(v.len()); + + // R = h⋅r_1 + + 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:: { R, u, r_u } + } + + pub fn verify( + params: &Params, + transcript: &mut impl Transcript, + cm: C, + proof: Proof, + ) -> 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 + + 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::::new_params(&mut rng, n); + let poseidon_config = poseidon_test_config::(); + + // init Prover's transcript + let mut transcript_p = PoseidonTranscript::::new(&poseidon_config); + // init Verifier's transcript + let mut transcript_v = PoseidonTranscript::::new(&poseidon_config); + + let v: Vec = vec![Fr::rand(&mut rng); n]; + let r: Fr = Fr::rand(&mut rng); + let cm = Pedersen::::commit(¶ms, &v, &r); + let proof = Pedersen::::prove(¶ms, &mut transcript_p, &cm, &v, &r); + let v = Pedersen::::verify(¶ms, &mut transcript_v, cm, proof); + assert!(v); + } +} diff --git a/src/transcript/mod.rs b/src/transcript/mod.rs index cc1424f..420e6bc 100644 --- a/src/transcript/mod.rs +++ b/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 { +pub trait Transcript { 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; + 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; } diff --git a/src/transcript/poseidon.rs b/src/transcript/poseidon.rs index 4636ca3..29f2e4d 100644 --- a/src/transcript/poseidon.rs +++ b/src/transcript/poseidon.rs @@ -3,42 +3,75 @@ use ark_crypto_primitives::sponge::{ 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 { - sponge: PoseidonSponge, +pub struct PoseidonTranscript +where + ::ScalarField: Absorb, +{ + sponge: PoseidonSponge, } -impl Transcript for PoseidonTranscript { - type TranscriptConfig = PoseidonConfig; +impl Transcript for PoseidonTranscript +where + ::ScalarField: Absorb, +{ + type TranscriptConfig = PoseidonConfig; fn new(poseidon_config: &Self::TranscriptConfig) -> Self { - let sponge = PoseidonSponge::::new(poseidon_config); + let sponge = PoseidonSponge::::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 { + fn get_challenges(&mut self, n: usize) -> Vec { 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(p: &C) -> Vec { + 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 { sponge: PoseidonSpongeVar, @@ -67,9 +100,9 @@ impl PoseidonTranscriptVar { } #[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 @@ mod tests { fn test_transcript_and_transcriptvar() { // use 'native' transcript let config = poseidon_test_config::(); - let mut tr = PoseidonTranscript::::new(&config); + let mut tr = PoseidonTranscript::::new(&config); tr.absorb(&Fr::from(42_u32)); let c = tr.get_challenge(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..63e55ca --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod vec; diff --git a/src/utils/vec.rs b/src/utils/vec.rs new file mode 100644 index 0000000..1b3d1b5 --- /dev/null +++ b/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 { + pub n_rows: usize, + pub n_cols: usize, + pub coeffs: Vec<(usize, usize, F)>, +} + +pub fn dense_matrix_to_sparse(m: Vec>) -> SparseMatrix { + let mut r = SparseMatrix:: { + 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(a: &[F], b: &[F]) -> Vec { + assert_eq!(a.len(), b.len()); + let mut r: Vec = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] + b[i]; + } + r +} + +pub fn vec_sub(a: &[F], b: &[F]) -> Vec { + assert_eq!(a.len(), b.len()); + let mut r: Vec = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] - b[i]; + } + r +} + +pub fn vec_scalar_mul(vec: &[F], c: &F) -> Vec { + 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(vec: &[F]) -> bool { + for e in vec { + if !e.is_zero() { + return false; + } + } + true +} + +pub fn mat_vec_mul(M: &Vec>, z: &[F]) -> Vec { + assert!(!M.is_empty()); + assert_eq!(M[0].len(), z.len()); + + let mut r: Vec = 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(matrix: &SparseMatrix, vector: &[F]) -> Vec { + 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(a: &[F], b: &[F]) -> Vec { + assert_eq!(a.len(), b.len()); + cfg_iter!(a).zip(b).map(|(a, b)| *a * b).collect() +}