diff --git a/folding-schemes/src/ccs/mod.rs b/folding-schemes/src/ccs/mod.rs index 9dd939d..00cc80c 100644 --- a/folding-schemes/src/ccs/mod.rs +++ b/folding-schemes/src/ccs/mod.rs @@ -1,7 +1,5 @@ -use ark_ec::CurveGroup; +use ark_ff::PrimeField; use ark_std::log2; -use ark_std::{One, Zero}; -use std::ops::Neg; use crate::utils::vec::*; use crate::Error; @@ -12,7 +10,7 @@ use r1cs::R1CS; /// CCS represents the Customizable Constraint Systems structure defined in /// the [CCS paper](https://eprint.iacr.org/2023/552) #[derive(Debug, Clone, Eq, PartialEq)] -pub struct CCS { +pub struct CCS { /// m: number of rows in M_i (such that M_i \in F^{m, n}) pub m: usize, /// n = |z|, number of cols in M_i @@ -31,25 +29,24 @@ pub struct CCS { pub s_prime: usize, /// vector of matrices - pub M: Vec>, + pub M: Vec>, /// vector of multisets pub S: Vec>, /// vector of coefficients - pub c: Vec, + pub c: Vec, } -impl CCS { +impl CCS { /// check that a CCS structure is satisfied by a z vector. Only for testing. - pub fn check_relation(&self, z: &[C::ScalarField]) -> Result<(), Error> { - let mut result = vec![C::ScalarField::zero(); self.m]; + pub fn check_relation(&self, z: &[F]) -> Result<(), Error> { + let mut result = vec![F::zero(); self.m]; for i in 0..self.q { // extract the needed M_j matrices out of S_i - let vec_M_j: Vec<&SparseMatrix> = - self.S[i].iter().map(|j| &self.M[*j]).collect(); + let vec_M_j: Vec<&SparseMatrix> = self.S[i].iter().map(|j| &self.M[*j]).collect(); // complete the hadamard chain - let mut hadamard_result = vec![C::ScalarField::one(); self.m]; + let mut hadamard_result = vec![F::one(); self.m]; for M_j in vec_M_j.into_iter() { hadamard_result = hadamard(&hadamard_result, &mat_vec_mul_sparse(M_j, z)?)?; } @@ -72,8 +69,8 @@ impl CCS { } } -impl CCS { - pub fn from_r1cs(r1cs: R1CS) -> Self { +impl CCS { + pub fn from_r1cs(r1cs: R1CS) -> Self { let m = r1cs.A.n_rows; let n = r1cs.A.n_cols; CCS { @@ -87,13 +84,13 @@ impl CCS { d: 2, S: vec![vec![0, 1], vec![2]], - c: vec![C::ScalarField::one(), C::ScalarField::one().neg()], + c: vec![F::one(), F::one().neg()], M: vec![r1cs.A, r1cs.B, r1cs.C], } } - pub fn to_r1cs(self) -> R1CS { - R1CS:: { + pub fn to_r1cs(self) -> R1CS { + R1CS:: { l: self.l, A: self.M[0].clone(), B: self.M[1].clone(), @@ -107,11 +104,11 @@ pub mod tests { use super::*; use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z}; use ark_ff::PrimeField; - use ark_pallas::Projective; + use ark_pallas::Fr; - pub fn get_test_ccs() -> CCS { - let r1cs = get_test_r1cs::(); - CCS::::from_r1cs(r1cs) + pub fn get_test_ccs() -> CCS { + let r1cs = get_test_r1cs::(); + CCS::::from_r1cs(r1cs) } pub fn get_test_z(input: usize) -> Vec { r1cs_get_test_z(input) @@ -120,7 +117,7 @@ pub mod tests { /// Test that a basic CCS relation can be satisfied #[test] fn test_ccs_relation() { - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let z = get_test_z(3); ccs.check_relation(&z).unwrap(); diff --git a/folding-schemes/src/commitment/ipa.rs b/folding-schemes/src/commitment/ipa.rs index c403a31..575de80 100644 --- a/folding-schemes/src/commitment/ipa.rs +++ b/folding-schemes/src/commitment/ipa.rs @@ -493,7 +493,7 @@ where /// there are some constraints saved. #[allow(clippy::too_many_arguments)] pub fn verify( - g: &Vec, // params.generators + g: &[GC], // params.generators h: &GC, // params.h x: &NonNativeFieldVar>, // evaluation point, challenge v: &NonNativeFieldVar>, // value at evaluation point diff --git a/folding-schemes/src/folding/circuits/nonnative/affine.rs b/folding-schemes/src/folding/circuits/nonnative/affine.rs index 33df868..05ee6b1 100644 --- a/folding-schemes/src/folding/circuits/nonnative/affine.rs +++ b/folding-schemes/src/folding/circuits/nonnative/affine.rs @@ -1,4 +1,5 @@ use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, fields::fp::FpVar, @@ -16,7 +17,7 @@ use super::uint::{nonnative_field_to_field_elements, NonNativeUintVar}; #[derive(Debug, Clone)] pub struct NonNativeAffineVar where - ::BaseField: ark_ff::PrimeField, + ::BaseField: PrimeField, { pub x: NonNativeUintVar, pub y: NonNativeUintVar, @@ -25,7 +26,7 @@ where impl AllocVar for NonNativeAffineVar where C: CurveGroup, - ::BaseField: ark_ff::PrimeField, + ::BaseField: PrimeField, { fn new_variable>( cs: impl Into>, @@ -49,7 +50,7 @@ where impl ToConstraintFieldGadget for NonNativeAffineVar where - ::BaseField: ark_ff::PrimeField, + ::BaseField: PrimeField, { // Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in // the circuit. @@ -66,7 +67,7 @@ pub fn nonnative_affine_to_field_elements( p: C, ) -> Result<(Vec, Vec), SynthesisError> where - ::BaseField: ark_ff::PrimeField, + ::BaseField: PrimeField, { let affine = p.into_affine(); if affine.is_zero() { @@ -83,7 +84,7 @@ where impl NonNativeAffineVar where - ::BaseField: ark_ff::PrimeField, + ::BaseField: PrimeField, { // A wrapper of `point_to_nonnative_limbs_custom_opt` with constraints-focused optimization // type (which is the default optimization type for arkworks' Groth16). diff --git a/folding-schemes/src/folding/circuits/sum_check.rs b/folding-schemes/src/folding/circuits/sum_check.rs index e0da76b..bf90fc3 100644 --- a/folding-schemes/src/folding/circuits/sum_check.rs +++ b/folding-schemes/src/folding/circuits/sum_check.rs @@ -1,10 +1,7 @@ use crate::utils::espresso::sum_check::SumCheck; use crate::utils::virtual_polynomial::VPAuxInfo; use crate::{ - transcript::{ - poseidon::{PoseidonTranscript, PoseidonTranscriptVar}, - TranscriptVar, - }, + transcript::{poseidon::PoseidonTranscript, TranscriptVar}, utils::sum_check::{structs::IOPProof, IOPSumCheck}, }; use ark_crypto_primitives::sponge::Absorb; @@ -150,7 +147,7 @@ impl SumCheckVerifierGadget { pub fn verify( iop_proof_var: &IOPProofVar, poly_aux_info_var: &VPAuxInfoVar, - transcript_var: &mut PoseidonTranscriptVar, + transcript_var: &mut impl TranscriptVar, ) -> Result<(Vec>, Vec>), SynthesisError> { let mut e_vars = vec![iop_proof_var.claim.clone()]; let mut r_vars: Vec> = Vec::new(); diff --git a/folding-schemes/src/folding/circuits/utils.rs b/folding-schemes/src/folding/circuits/utils.rs index 50b954a..5035e2a 100644 --- a/folding-schemes/src/folding/circuits/utils.rs +++ b/folding-schemes/src/folding/circuits/utils.rs @@ -12,7 +12,7 @@ pub struct EqEvalGadget { impl EqEvalGadget { /// Gadget to evaluate eq polynomial. /// Follows the implementation of `eq_eval` found in this crate. - pub fn eq_eval(x: Vec>, y: Vec>) -> Result, SynthesisError> { + pub fn eq_eval(x: &[FpVar], y: &[FpVar]) -> Result, SynthesisError> { if x.len() != y.len() { return Err(SynthesisError::Unsatisfiable); } @@ -30,16 +30,15 @@ impl EqEvalGadget { #[cfg(test)] mod tests { - - use crate::utils::virtual_polynomial::eq_eval; - - use super::EqEvalGadget; use ark_ff::Field; use ark_pallas::Fr; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; use ark_std::{test_rng, UniformRand}; + use super::EqEvalGadget; + use crate::utils::virtual_polynomial::eq_eval; + #[test] pub fn test_eq_eval_gadget() { let mut rng = test_rng(); @@ -57,19 +56,19 @@ mod tests { .map(|y| FpVar::::new_witness(cs.clone(), || Ok(y)).unwrap()) .collect(); let expected_eq_eval = eq_eval::(&x_vec, &y_vec).unwrap(); - let gadget_eq_eval: FpVar = EqEvalGadget::::eq_eval(x, y).unwrap(); + let gadget_eq_eval: FpVar = EqEvalGadget::::eq_eval(&x, &y).unwrap(); assert_eq!(expected_eq_eval, gadget_eq_eval.value().unwrap()); } let x: Vec> = vec![]; let y: Vec> = vec![]; - let gadget_eq_eval = EqEvalGadget::::eq_eval(x, y); + let gadget_eq_eval = EqEvalGadget::::eq_eval(&x, &y); assert!(gadget_eq_eval.is_err()); let x: Vec> = vec![]; let y: Vec> = vec![FpVar::::new_witness(cs.clone(), || Ok(&Fr::ONE)).unwrap()]; - let gadget_eq_eval = EqEvalGadget::::eq_eval(x, y); + let gadget_eq_eval = EqEvalGadget::::eq_eval(&x, &y); assert!(gadget_eq_eval.is_err()); } } diff --git a/folding-schemes/src/folding/hypernova/cccs.rs b/folding-schemes/src/folding/hypernova/cccs.rs index ad3b140..7c83e49 100644 --- a/folding-schemes/src/folding/hypernova/cccs.rs +++ b/folding-schemes/src/folding/hypernova/cccs.rs @@ -5,7 +5,7 @@ use ark_std::Zero; use std::ops::Add; use std::sync::Arc; -use ark_std::{rand::Rng, UniformRand}; +use ark_std::rand::Rng; use super::utils::compute_sum_Mz; use crate::ccs::CCS; @@ -35,13 +35,17 @@ pub struct CCCS { pub x: Vec, } -impl CCS { - pub fn to_cccs( +impl CCS { + pub fn to_cccs( &self, rng: &mut R, pedersen_params: &PedersenParams, z: &[C::ScalarField], - ) -> Result<(CCCS, Witness), Error> { + ) -> Result<(CCCS, Witness), Error> + where + // enforce that CCS's F is the C::ScalarField + C: CurveGroup, + { let w: Vec = z[(1 + self.l)..].to_vec(); let r_w = C::ScalarField::rand(rng); let C = Pedersen::::commit(pedersen_params, &w, &r_w)?; @@ -57,13 +61,12 @@ impl CCS { /// Computes q(x) = \sum^q c_i * \prod_{j \in S_i} ( \sum_{y \in {0,1}^s'} M_j(x, y) * z(y) ) /// polynomial over x - pub fn compute_q(&self, z: &Vec) -> VirtualPolynomial { + pub fn compute_q(&self, z: &[F]) -> VirtualPolynomial { let z_mle = vec_to_mle(self.s_prime, z); - let mut q = VirtualPolynomial::::new(self.s); + let mut q = VirtualPolynomial::::new(self.s); for i in 0..self.q { - let mut prod: VirtualPolynomial = - VirtualPolynomial::::new(self.s); + let mut prod: VirtualPolynomial = VirtualPolynomial::::new(self.s); for j in self.S[i].clone() { let M_j = matrix_to_mle(self.M[j].clone()); @@ -74,11 +77,9 @@ impl CCS { // If this is the first time we are adding something to this virtual polynomial, we need to // explicitly add the products using add_mle_list() // XXX is this true? improve API - prod.add_mle_list([Arc::new(sum_Mz)], C::ScalarField::one()) - .unwrap(); + prod.add_mle_list([Arc::new(sum_Mz)], F::one()).unwrap(); } else { - prod.mul_by_mle(Arc::new(sum_Mz), C::ScalarField::one()) - .unwrap(); + prod.mul_by_mle(Arc::new(sum_Mz), F::one()).unwrap(); } } // Multiply by the product by the coefficient c_i @@ -92,11 +93,7 @@ impl CCS { /// Computes Q(x) = eq(beta, x) * q(x) /// = eq(beta, x) * \sum^q c_i * \prod_{j \in S_i} ( \sum_{y \in {0,1}^s'} M_j(x, y) * z(y) ) /// polynomial over x - pub fn compute_Q( - &self, - z: &Vec, - beta: &[C::ScalarField], - ) -> VirtualPolynomial { + pub fn compute_Q(&self, z: &[F], beta: &[F]) -> VirtualPolynomial { let q = self.compute_q(z); q.build_f_hat(beta).unwrap() } @@ -107,7 +104,7 @@ impl CCCS { pub fn check_relation( &self, pedersen_params: &PedersenParams, - ccs: &CCS, + ccs: &CCS, w: &Witness, ) -> Result<(), Error> { // check that C is the commitment of w. Notice that this is not verifying a Pedersen @@ -139,7 +136,7 @@ pub mod tests { use ark_std::test_rng; use ark_std::UniformRand; - use ark_pallas::{Fr, Projective}; + use ark_pallas::Fr; /// Do some sanity checks on q(x). It's a multivariable polynomial and it should evaluate to zero inside the /// hypercube, but to not-zero outside the hypercube. @@ -147,7 +144,7 @@ pub mod tests { fn test_compute_q() { let mut rng = test_rng(); - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let z = get_test_z(3); let q = ccs.compute_q(&z); @@ -167,7 +164,7 @@ pub mod tests { fn test_compute_Q() { let mut rng = test_rng(); - let ccs: CCS = get_test_ccs(); + let ccs: CCS = get_test_ccs(); let z = get_test_z(3); ccs.check_relation(&z).unwrap(); @@ -201,7 +198,7 @@ pub mod tests { fn test_Q_against_q() { let mut rng = test_rng(); - let ccs: CCS = get_test_ccs(); + let ccs: CCS = get_test_ccs(); let z = get_test_z(3); ccs.check_relation(&z).unwrap(); diff --git a/folding-schemes/src/folding/hypernova/circuit.rs b/folding-schemes/src/folding/hypernova/circuit.rs index 7973345..4d0d447 100644 --- a/folding-schemes/src/folding/hypernova/circuit.rs +++ b/folding-schemes/src/folding/hypernova/circuit.rs @@ -1,286 +1,133 @@ -// hypernova nimfs verifier circuit -// see section 5 in https://eprint.iacr.org/2023/573.pdf - -use crate::{ccs::CCS, folding::circuits::utils::EqEvalGadget}; -use ark_ec::CurveGroup; +/// Implementation of [HyperNova](https://eprint.iacr.org/2023/573.pdf) NIMFS verifier circuit +use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::AllocVar, fields::{fp::FpVar, FieldVar}, - ToBitsGadget, }; use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; -use ark_std::Zero; -use std::marker::PhantomData; - -/// Gadget to compute $\sum_{j \in [t]} \gamma^{j} \cdot e_1 \cdot \sigma_j + \gamma^{t+1} \cdot e_2 \cdot \sum_{i=1}^{q} c_i * \prod_{j \in S_i} \theta_j$. -/// This is the sum computed by the verifier and laid out in section 5, step 5 of "A multi-folding scheme for CCS". -pub struct ComputeCFromSigmasAndThetasGadget { - _c: PhantomData, -} -impl ComputeCFromSigmasAndThetasGadget { - /// Computes the sum $\sum_{j}^{j + n} \gamma^{j} \cdot eq_eval \cdot \sigma_{j}$, where $n$ is the length of the `sigmas` vector - /// It corresponds to the first term of the sum that $\mathcal{V}$ has to compute at section 5, step 5 of "A multi-folding scheme for CCS". - /// - /// # Arguments - /// - `sigmas`: vector of $\sigma_j$ values - /// - `eq_eval`: the value of $\tilde{eq}(x_j, x^{\prime})$ - /// - `gamma`: value $\gamma$ - /// - `j`: the power at which we start to compute $\gamma^{j}$. This is needed in the context of multifolding. - /// - /// # Notes - /// In the context of multifolding, `j` corresponds to `ccs.t` in `compute_c_from_sigmas_and_thetas` - fn sum_muls_gamma_pows_eq_sigma( - gamma: FpVar, - eq_eval: FpVar, - sigmas: Vec>, - j: FpVar, - ) -> Result, SynthesisError> { - let mut result = FpVar::::zero(); - let mut gamma_pow = gamma.pow_le(&j.to_bits_le()?)?; - for sigma in sigmas { - result += gamma_pow.clone() * eq_eval.clone() * sigma; - gamma_pow *= gamma.clone(); - } - Ok(result) +use crate::ccs::CCS; +use crate::folding::circuits::utils::EqEvalGadget; + +/// computes c from the step 5 in section 5 of HyperNova, adapted to multiple LCCCS & CCCS +/// instances: +/// $$ +/// c = \sum_{i \in [\mu]} \left(\sum_{j \in [t]} \gamma^{i \cdot t + j} \cdot e_i \cdot \sigma_{i,j} \right) +/// + \sum_{k \in [\nu]} \gamma^{\mu \cdot t+k} \cdot e_k \cdot \left( \sum_{i=1}^q c_i \cdot \prod_{j \in S_i} +/// \theta_{k,j} \right) +/// $$ +#[allow(dead_code)] // TMP while the other circuits are not ready +#[allow(clippy::too_many_arguments)] +fn compute_c_gadget( + cs: ConstraintSystemRef, + ccs: &CCS, + vec_sigmas: Vec>>, + vec_thetas: Vec>>, + gamma: FpVar, + beta: Vec>, + vec_r_x: Vec>>, + vec_r_x_prime: Vec>, +) -> Result, SynthesisError> { + let mut e_lcccs = Vec::new(); + for r_x in vec_r_x.iter() { + e_lcccs.push(EqEvalGadget::eq_eval(r_x, &vec_r_x_prime)?); } - /// Computes $\sum_{i=1}^{q} c_i * \prod_{j \in S_i} theta_j$ - /// - /// # Arguments - /// - `c_i`: vector of $c_i$ values - /// - `thetas`: vector of pre-processed $\thetas[j]$ values corresponding to a particular `ccs.S[i]` - /// - /// # Notes - /// This is a part of the second term of the sum that $\mathcal{V}$ has to compute at section 5, step 5 of "A multi-folding scheme for CCS". - /// The first term is computed by `SumMulsGammaPowsEqSigmaGadget::sum_muls_gamma_pows_eq_sigma`. - /// This is a doct product between a vector of c_i values and a vector of pre-processed $\theta_j$ values, where $j$ is a value from $S_i$. - /// Hence, this requires some pre-processing of the $\theta_j$ values, before running this gadget. - fn sum_ci_mul_prod_thetaj( - c_i: Vec>, - thetas: Vec>>, - ) -> Result, SynthesisError> { - let mut result = FpVar::::zero(); - for (i, c_i) in c_i.iter().enumerate() { - let prod = &thetas[i].iter().fold(FpVar::one(), |acc, e| acc * e); - result += c_i * prod; + let mut c = FpVar::::zero(); + let mut current_gamma = FpVar::::one(); + for i in 0..vec_sigmas.len() { + for j in 0..ccs.t { + c += current_gamma.clone() * e_lcccs[i].clone() * vec_sigmas[i][j].clone(); + current_gamma *= gamma.clone(); } - Ok(result) } - /// Computes the sum that the verifier has to compute at section 5, step 5 of "A multi-folding scheme for CCS". - /// - /// # Arguments - /// - `cs`: constraint system - /// - `ccs`: the CCS instance - /// - `vec_sigmas`: vector of $\sigma_j$ values - /// - `vec_thetas`: vector of $\theta_j$ values - /// - `gamma`: value $\gamma$ - /// - `beta`: vector of $\beta_j$ values - /// - `vec_r_x`: vector of $r_{x_j}$ values - /// - `vec_r_x_prime`: vector of $r_{x_j}^{\prime}$ values - /// - /// # Notes - /// Arguments to this function are *almost* the same as the arguments to `compute_c_from_sigmas_and_thetas` in `utils.rs`. - #[allow(clippy::too_many_arguments)] - pub fn compute_c_from_sigmas_and_thetas( - cs: ConstraintSystemRef, - ccs: &CCS, - vec_sigmas: Vec>>, - vec_thetas: Vec>>, - gamma: FpVar, - beta: Vec>, - vec_r_x: Vec>>, - vec_r_x_prime: Vec>, - ) -> Result, SynthesisError> { - let mut c = - FpVar::::new_witness(cs.clone(), || Ok(C::ScalarField::zero()))?; - let t = FpVar::::new_witness(cs.clone(), || { - Ok(C::ScalarField::from(ccs.t as u64)) - })?; - - let mut e_lcccs = Vec::new(); - for r_x in vec_r_x.iter() { - let e_1 = EqEvalGadget::eq_eval(r_x.to_vec(), vec_r_x_prime.to_vec())?; - e_lcccs.push(e_1); - } - - for (i, sigmas) in vec_sigmas.iter().enumerate() { - let i_var = FpVar::::new_witness(cs.clone(), || { - Ok(C::ScalarField::from(i as u64)) - })?; - let pow = i_var * t.clone(); - c += Self::sum_muls_gamma_pows_eq_sigma( - gamma.clone(), - e_lcccs[i].clone(), - sigmas.to_vec(), - pow, - )?; - } - - let mu = FpVar::::new_witness(cs.clone(), || { - Ok(C::ScalarField::from(vec_sigmas.len() as u64)) - })?; - let e_2 = EqEvalGadget::eq_eval(beta, vec_r_x_prime)?; - for (k, thetas) in vec_thetas.iter().enumerate() { - // get prepared thetas. only step different from original `compute_c_from_sigmas_and_thetas` - let mut prepared_thetas = Vec::new(); - for i in 0..ccs.q { - let prepared: Vec> = - ccs.S[i].iter().map(|j| thetas[*j].clone()).collect(); - prepared_thetas.push(prepared.to_vec()); + let ccs_c = Vec::>::new_constant(cs.clone(), ccs.c.clone())?; + let e_k = EqEvalGadget::eq_eval(&beta, &vec_r_x_prime)?; + #[allow(clippy::needless_range_loop)] + for k in 0..vec_thetas.len() { + let mut sum = FpVar::::zero(); + for i in 0..ccs.q { + let mut prod = FpVar::::one(); + for j in ccs.S[i].clone() { + prod *= vec_thetas[k][j].clone(); } - - let c_i = Vec::>::new_witness(cs.clone(), || Ok(ccs.c.clone())) - .unwrap(); - let lhs = Self::sum_ci_mul_prod_thetaj(c_i.clone(), prepared_thetas.clone())?; - - // compute gamma^(t+1) - let pow = mu.clone() * t.clone() - + FpVar::::new_witness(cs.clone(), || { - Ok(C::ScalarField::from(k as u64)) - })?; - let gamma_t1 = gamma.pow_le(&pow.to_bits_le()?)?; - - c += gamma_t1.clone() * e_2.clone() * lhs.clone(); + sum += ccs_c[i].clone() * prod; } - - Ok(c) + c += current_gamma.clone() * e_k.clone() * sum; + current_gamma *= gamma.clone(); } + Ok(c) } #[cfg(test)] mod tests { - use super::ComputeCFromSigmasAndThetasGadget; + use ark_pallas::{Fr, Projective}; + use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar}; + use ark_relations::r1cs::ConstraintSystem; + use ark_std::{test_rng, UniformRand}; + + use super::*; use crate::{ ccs::{ tests::{get_test_ccs, get_test_z}, CCS, }, commitment::{pedersen::Pedersen, CommitmentScheme}, - folding::hypernova::utils::{ - compute_c_from_sigmas_and_thetas, compute_sigmas_and_thetas, sum_ci_mul_prod_thetaj, - sum_muls_gamma_pows_eq_sigma, - }, - utils::virtual_polynomial::eq_eval, + folding::hypernova::utils::{compute_c, compute_sigmas_and_thetas}, }; - use ark_pallas::{Fr, Projective}; - use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar}; - use ark_relations::r1cs::ConstraintSystem; - use ark_std::{test_rng, UniformRand}; #[test] - pub fn test_sum_muls_gamma_pow_eq_sigma_gadget() { - let mut rng = test_rng(); - let ccs: CCS = get_test_ccs(); - let z1 = get_test_z(3); - let z2 = get_test_z(4); - - let gamma: Fr = Fr::rand(&mut rng); - let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - - // Initialize a multifolding object - let (pedersen_params, _) = - Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); - let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); - let sigmas_thetas = - compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); - - let mut e_lcccs = Vec::new(); - for r_x in &vec![lcccs_instance.r_x] { - e_lcccs.push(eq_eval(r_x, &r_x_prime).unwrap()); + pub fn test_compute_c_gadget() { + // number of LCCCS & CCCS instances to fold in a single step + let mu = 32; + let nu = 42; + + let mut z_lcccs = Vec::new(); + for i in 0..mu { + let z = get_test_z(i + 3); + z_lcccs.push(z); } - - // Initialize cs and gamma - let cs = ConstraintSystem::::new_ref(); - let gamma_var = FpVar::::new_witness(cs.clone(), || Ok(gamma)).unwrap(); - - for (i, sigmas) in sigmas_thetas.0.iter().enumerate() { - let expected = - sum_muls_gamma_pows_eq_sigma(gamma, e_lcccs[i], sigmas, (i * ccs.t) as u64); - let sigmas_var = - Vec::>::new_witness(cs.clone(), || Ok(sigmas.clone())).unwrap(); - let eq_var = FpVar::::new_witness(cs.clone(), || Ok(e_lcccs[i])).unwrap(); - let pow = - FpVar::::new_witness(cs.clone(), || Ok(Fr::from((i * ccs.t) as u64))).unwrap(); - let computed = - ComputeCFromSigmasAndThetasGadget::::sum_muls_gamma_pows_eq_sigma( - gamma_var.clone(), - eq_var, - sigmas_var, - pow, - ) - .unwrap(); - assert_eq!(expected, computed.value().unwrap()); + let mut z_cccs = Vec::new(); + for i in 0..nu { + let z = get_test_z(i + 3); + z_cccs.push(z); } - } - #[test] - pub fn test_sum_ci_mul_prod_thetaj_gadget() { - let mut rng = test_rng(); - let ccs: CCS = get_test_ccs(); - let z1 = get_test_z(3); - let z2 = get_test_z(4); + let ccs: CCS = get_test_ccs(); + let mut rng = test_rng(); + let gamma: Fr = Fr::rand(&mut rng); + let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - // Initialize a multifolding object let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); - let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); - let sigmas_thetas = - compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); - let mut e_lcccs = Vec::new(); - for r_x in &vec![lcccs_instance.r_x] { - e_lcccs.push(eq_eval(r_x, &r_x_prime).unwrap()); + // Create the LCCCS instances out of z_lcccs + let mut lcccs_instances = Vec::new(); + for z_i in z_lcccs.iter() { + let (inst, _) = ccs.to_lcccs(&mut rng, &pedersen_params, z_i).unwrap(); + lcccs_instances.push(inst); } - - // Initialize cs - let cs = ConstraintSystem::::new_ref(); - let vec_thetas = sigmas_thetas.1; - for thetas in vec_thetas.iter() { - // sum c_i * prod theta_j - let expected = sum_ci_mul_prod_thetaj(&ccs, thetas); // from `compute_c_from_sigmas_and_thetas` - let mut prepared_thetas = Vec::new(); - for i in 0..ccs.q { - let prepared: Vec = ccs.S[i].iter().map(|j| thetas[*j]).collect(); - prepared_thetas - .push(Vec::>::new_witness(cs.clone(), || Ok(prepared)).unwrap()); - } - let computed = ComputeCFromSigmasAndThetasGadget::::sum_ci_mul_prod_thetaj( - Vec::>::new_witness(cs.clone(), || Ok(ccs.c.clone())).unwrap(), - prepared_thetas, - ) - .unwrap(); - assert_eq!(expected, computed.value().unwrap()); + // Create the CCCS instance out of z_cccs + let mut cccs_instances = Vec::new(); + for z_i in z_cccs.iter() { + let (inst, _) = ccs.to_cccs(&mut rng, &pedersen_params, z_i).unwrap(); + cccs_instances.push(inst); } - } - #[test] - pub fn test_compute_c_from_sigmas_and_thetas_gadget() { - let ccs: CCS = get_test_ccs(); - let z1 = get_test_z(3); - let z2 = get_test_z(4); - - let mut rng = test_rng(); - let gamma: Fr = Fr::rand(&mut rng); - let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - - // Initialize a multifolding object - let (pedersen_params, _) = - Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); - let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); - let sigmas_thetas = - compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); + let sigmas_thetas = compute_sigmas_and_thetas(&ccs, &z_lcccs, &z_cccs, &r_x_prime); - let expected_c = compute_c_from_sigmas_and_thetas( + let expected_c = compute_c( &ccs, &sigmas_thetas, gamma, &beta, - &vec![lcccs_instance.r_x.clone()], + &lcccs_instances + .iter() + .map(|lcccs| lcccs.r_x.clone()) + .collect(), &r_x_prime, ); @@ -295,17 +142,18 @@ mod tests { vec_thetas .push(Vec::>::new_witness(cs.clone(), || Ok(thetas.clone())).unwrap()); } - let vec_r_x = - vec![ - Vec::>::new_witness(cs.clone(), || Ok(lcccs_instance.r_x.clone())) - .unwrap(), - ]; + let vec_r_x: Vec>> = lcccs_instances + .iter() + .map(|lcccs| { + Vec::>::new_witness(cs.clone(), || Ok(lcccs.r_x.clone())).unwrap() + }) + .collect(); let vec_r_x_prime = Vec::>::new_witness(cs.clone(), || Ok(r_x_prime.clone())).unwrap(); let gamma_var = FpVar::::new_witness(cs.clone(), || Ok(gamma)).unwrap(); let beta_var = Vec::>::new_witness(cs.clone(), || Ok(beta.clone())).unwrap(); - let computed_c = ComputeCFromSigmasAndThetasGadget::compute_c_from_sigmas_and_thetas( - cs, + let computed_c = compute_c_gadget( + cs.clone(), &ccs, vec_sigmas, vec_thetas, diff --git a/folding-schemes/src/folding/hypernova/lcccs.rs b/folding-schemes/src/folding/hypernova/lcccs.rs index e7da6c2..9231b32 100644 --- a/folding-schemes/src/folding/hypernova/lcccs.rs +++ b/folding-schemes/src/folding/hypernova/lcccs.rs @@ -1,9 +1,10 @@ use ark_ec::CurveGroup; +use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; use ark_std::One; use std::sync::Arc; -use ark_std::{rand::Rng, UniformRand}; +use ark_std::rand::Rng; use super::cccs::Witness; use super::utils::{compute_all_sum_Mz_evals, compute_sum_Mz}; @@ -31,19 +32,23 @@ pub struct LCCCS { pub v: Vec, } -impl CCS { +impl CCS { /// Compute v_j values of the linearized committed CCS form /// Given `r`, compute: \sum_{y \in {0,1}^s'} M_j(r, y) * z(y) - fn compute_v_j(&self, z: &[C::ScalarField], r: &[C::ScalarField]) -> Vec { - compute_all_sum_Mz_evals(&self.M, &z.to_vec(), r, self.s_prime) + fn compute_v_j(&self, z: &[F], r: &[F]) -> Vec { + compute_all_sum_Mz_evals(&self.M, z, r, self.s_prime) } - pub fn to_lcccs( + pub fn to_lcccs( &self, rng: &mut R, pedersen_params: &PedersenParams, z: &[C::ScalarField], - ) -> Result<(LCCCS, Witness), Error> { + ) -> Result<(LCCCS, Witness), Error> + where + // enforce that CCS's F is the C::ScalarField + C: CurveGroup, + { let w: Vec = z[(1 + self.l)..].to_vec(); let r_w = C::ScalarField::rand(rng); let C = Pedersen::::commit(pedersen_params, &w, &r_w)?; @@ -68,8 +73,8 @@ impl LCCCS { /// Compute all L_j(x) polynomials pub fn compute_Ls( &self, - ccs: &CCS, - z: &Vec, + ccs: &CCS, + z: &[C::ScalarField], ) -> Vec> { let z_mle = vec_to_mle(ccs.s_prime, z); // Convert all matrices to MLE @@ -92,7 +97,7 @@ impl LCCCS { pub fn check_relation( &self, pedersen_params: &PedersenParams, - ccs: &CCS, + ccs: &CCS, w: &Witness, ) -> Result<(), Error> { // check that C is the commitment of w. Notice that this is not verifying a Pedersen diff --git a/folding-schemes/src/folding/hypernova/nimfs.rs b/folding-schemes/src/folding/hypernova/nimfs.rs index 8da8955..309fe74 100644 --- a/folding-schemes/src/folding/hypernova/nimfs.rs +++ b/folding-schemes/src/folding/hypernova/nimfs.rs @@ -7,7 +7,7 @@ use ark_std::{One, Zero}; use super::cccs::{Witness, CCCS}; use super::lcccs::LCCCS; -use super::utils::{compute_c_from_sigmas_and_thetas, compute_g, compute_sigmas_and_thetas}; +use super::utils::{compute_c, compute_g, compute_sigmas_and_thetas}; use crate::ccs::CCS; use crate::transcript::Transcript; use crate::utils::hypercube::BooleanHypercube; @@ -20,13 +20,13 @@ use std::fmt::Debug; use std::marker::PhantomData; /// Proof defines a multifolding proof -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Proof { pub sc_proof: SumCheckProof, pub sigmas_thetas: SigmasThetas, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct SigmasThetas(pub Vec>, pub Vec>); #[derive(Debug)] @@ -151,7 +151,7 @@ where #[allow(clippy::type_complexity)] pub fn prove( transcript: &mut impl Transcript, - ccs: &CCS, + ccs: &CCS, running_instances: &[LCCCS], new_instances: &[CCCS], w_lcccs: &[Witness], @@ -277,7 +277,7 @@ where /// Returns the folded LCCCS instance. pub fn verify( transcript: &mut impl Transcript, - ccs: &CCS, + ccs: &CCS, running_instances: &[LCCCS], new_instances: &[CCCS], proof: Proof, @@ -325,7 +325,7 @@ where let r_x_prime = sumcheck_subclaim.point.clone(); // Step 5: Finish verifying sumcheck (verify the claim c) - let c = compute_c_from_sigmas_and_thetas( + let c = compute_c( ccs, &proof.sigmas_thetas, gamma, @@ -336,6 +336,7 @@ where .collect(), &r_x_prime, ); + // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas if c != sumcheck_subclaim.expected_evaluation { return Err(Error::NotEqual); @@ -430,7 +431,7 @@ pub mod tests { let mut rng = test_rng(); // Create a basic CCS circuit - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); @@ -489,7 +490,7 @@ pub mod tests { pub fn test_multifolding_two_instances_multiple_steps() { let mut rng = test_rng(); - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); @@ -559,7 +560,7 @@ pub mod tests { let mut rng = test_rng(); // Create a basic CCS circuit - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); @@ -642,7 +643,7 @@ pub mod tests { let mut rng = test_rng(); // Create a basic CCS circuit - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); diff --git a/folding-schemes/src/folding/hypernova/utils.rs b/folding-schemes/src/folding/hypernova/utils.rs index 215ee1d..c713fcb 100644 --- a/folding-schemes/src/folding/hypernova/utils.rs +++ b/folding-schemes/src/folding/hypernova/utils.rs @@ -2,7 +2,6 @@ use ark_ec::CurveGroup; use ark_ff::{Field, PrimeField}; use ark_poly::DenseMultilinearExtension; use ark_poly::MultilinearExtension; -use ark_std::{One, Zero}; use std::ops::Add; use crate::utils::multilinear_polynomial::fix_variables; @@ -21,7 +20,7 @@ use crate::utils::virtual_polynomial::{eq_eval, VirtualPolynomial}; /// in 0..self.t pub fn compute_all_sum_Mz_evals( vec_M: &[SparseMatrix], - z: &Vec, + z: &[F], r: &[F], s_prime: usize, ) -> Vec { @@ -65,19 +64,19 @@ pub fn compute_sum_Mz( /// Compute the arrays of sigma_i and theta_i from step 4 corresponding to the LCCCS and CCCS /// instances -pub fn compute_sigmas_and_thetas( - ccs: &CCS, - z_lcccs: &[Vec], - z_cccs: &[Vec], - r_x_prime: &[C::ScalarField], -) -> SigmasThetas { - let mut sigmas: Vec> = Vec::new(); +pub fn compute_sigmas_and_thetas( + ccs: &CCS, + z_lcccs: &[Vec], + z_cccs: &[Vec], + r_x_prime: &[F], +) -> SigmasThetas { + let mut sigmas: Vec> = Vec::new(); for z_lcccs_i in z_lcccs { // sigmas let sigma_i = compute_all_sum_Mz_evals(&ccs.M, z_lcccs_i, r_x_prime, ccs.s_prime); sigmas.push(sigma_i); } - let mut thetas: Vec> = Vec::new(); + let mut thetas: Vec> = Vec::new(); for z_cccs_i in z_cccs { // thetas let theta_i = compute_all_sum_Mz_evals(&ccs.M, z_cccs_i, r_x_prime, ccs.s_prime); @@ -86,49 +85,23 @@ pub fn compute_sigmas_and_thetas( SigmasThetas(sigmas, thetas) } -/// Computes the sum $\sum_{j = 0}^{n} \gamma^{\text{pow} + j} \cdot eq_eval \cdot \sigma_{j}$ -/// `pow` corresponds to `i * ccs.t` in `compute_c_from_sigmas_and_thetas` -pub fn sum_muls_gamma_pows_eq_sigma( +/// computes c from the step 5 in section 5 of HyperNova, adapted to multiple LCCCS & CCCS +/// instances: +/// $$ +/// c = \sum_{i \in [\mu]} \left(\sum_{j \in [t]} \gamma^{i \cdot t + j} \cdot e_i \cdot \sigma_{i,j} \right) +/// + \sum_{k \in [\nu]} \gamma^{\mu \cdot t+k} \cdot e_k \cdot \left( \sum_{i=1}^q c_i \cdot \prod_{j \in S_i} +/// \theta_{k,j} \right) +/// $$ +pub fn compute_c( + ccs: &CCS, + st: &SigmasThetas, gamma: F, - eq_eval: F, - sigmas: &[F], - pow: u64, + beta: &[F], + vec_r_x: &Vec>, + r_x_prime: &[F], ) -> F { - let mut result = F::zero(); - for (j, sigma_j) in sigmas.iter().enumerate() { - let gamma_j = gamma.pow([(pow + (j as u64))]); - result += gamma_j * eq_eval * sigma_j; - } - result -} - -/// Computes $\sum_{i=1}^{q} c_i * \prod_{j \in S_i} theta_j$ -pub fn sum_ci_mul_prod_thetaj( - ccs: &CCS, - thetas: &[C::ScalarField], -) -> C::ScalarField { - let mut result = C::ScalarField::zero(); - for i in 0..ccs.q { - let mut prod = C::ScalarField::one(); - for j in ccs.S[i].clone() { - prod *= thetas[j]; - } - result += ccs.c[i] * prod; - } - result -} - -/// Compute the right-hand-side of step 5 of the multifolding scheme -pub fn compute_c_from_sigmas_and_thetas( - ccs: &CCS, - st: &SigmasThetas, - gamma: C::ScalarField, - beta: &[C::ScalarField], - vec_r_x: &Vec>, - r_x_prime: &[C::ScalarField], -) -> C::ScalarField { let (vec_sigmas, vec_thetas) = (st.0.clone(), st.1.clone()); - let mut c = C::ScalarField::zero(); + let mut c = F::zero(); let mut e_lcccs = Vec::new(); for r_x in vec_r_x { @@ -136,14 +109,24 @@ pub fn compute_c_from_sigmas_and_thetas( } for (i, sigmas) in vec_sigmas.iter().enumerate() { // (sum gamma^j * e_i * sigma_j) - c += sum_muls_gamma_pows_eq_sigma(gamma, e_lcccs[i], sigmas, (i * ccs.t) as u64); + for (j, sigma_j) in sigmas.iter().enumerate() { + let gamma_j = gamma.pow([((i * ccs.t + j) as u64)]); + c += gamma_j * e_lcccs[i] * sigma_j; + } } let mu = vec_sigmas.len(); let e2 = eq_eval(beta, r_x_prime).unwrap(); for (k, thetas) in vec_thetas.iter().enumerate() { // + gamma^{t+1} * e2 * sum c_i * prod theta_j - let lhs = sum_ci_mul_prod_thetaj(ccs, thetas); + let mut lhs = F::zero(); + for i in 0..ccs.q { + let mut prod = F::one(); + for j in ccs.S[i].clone() { + prod *= thetas[j]; + } + lhs += ccs.c[i] * prod; + } let gamma_t1 = gamma.pow([(mu * ccs.t + k) as u64]); c += gamma_t1 * e2 * lhs; } @@ -152,7 +135,7 @@ pub fn compute_c_from_sigmas_and_thetas( /// Compute g(x) polynomial for the given inputs. pub fn compute_g( - ccs: &CCS, + ccs: &CCS, running_instances: &[LCCCS], z_lcccs: &[Vec], z_cccs: &[Vec], @@ -205,7 +188,7 @@ pub mod tests { #[test] fn test_compute_sum_Mz_over_boolean_hypercube() { - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let z = get_test_z(3); ccs.check_relation(&z).unwrap(); let z_mle = dense_vec_to_mle(ccs.s_prime, &z); @@ -253,7 +236,7 @@ pub mod tests { let mut rng = test_rng(); // s = 2, s' = 3 - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let M = ccs.M[0].clone().to_dense(); let M_mle = matrix_to_mle(ccs.M[0].clone()); @@ -308,9 +291,9 @@ pub mod tests { // we expect g(r_x_prime) to be equal to: // c = (sum gamma^j * e1 * sigma_j) + gamma^{t+1} * e2 * sum c_i * prod theta_j - // from compute_c_from_sigmas_and_thetas + // from compute_c let expected_c = g.evaluate(&r_x_prime).unwrap(); - let c = compute_c_from_sigmas_and_thetas::( + let c = compute_c::( &ccs, &sigmas_thetas, gamma, diff --git a/folding-schemes/src/folding/protogalaxy/folding.rs b/folding-schemes/src/folding/protogalaxy/folding.rs index 9272a00..36a265c 100644 --- a/folding-schemes/src/folding/protogalaxy/folding.rs +++ b/folding-schemes/src/folding/protogalaxy/folding.rs @@ -288,7 +288,7 @@ where } // naive impl of pow_i for betas, assuming that betas=(b, b^2, b^4, ..., b^{2^{t-1}}) -fn pow_i(i: usize, betas: &Vec) -> F { +fn pow_i(i: usize, betas: &[F]) -> F { // WIP check if makes more sense to do it with ifs instead of arithmetic let n = 2_u64.pow(betas.len() as u32); diff --git a/folding-schemes/src/utils/espresso/sum_check/verifier.rs b/folding-schemes/src/utils/espresso/sum_check/verifier.rs index 9f6c35f..55fafb2 100644 --- a/folding-schemes/src/utils/espresso/sum_check/verifier.rs +++ b/folding-schemes/src/utils/espresso/sum_check/verifier.rs @@ -140,7 +140,6 @@ impl SumCheckVerifier for IOPVerifierState { let eval_at_zero: C::ScalarField = poly.coeffs[0]; let eval = eval_at_one + eval_at_zero; - println!("evaluations: {:?}, expected: {:?}", eval, expected); // the deferred check during the interactive phase: // 1. check if the received 'P(0) + P(1) = expected`. if eval != expected { diff --git a/folding-schemes/src/utils/mle.rs b/folding-schemes/src/utils/mle.rs index ceece42..10aa04b 100644 --- a/folding-schemes/src/utils/mle.rs +++ b/folding-schemes/src/utils/mle.rs @@ -34,26 +34,26 @@ pub fn matrix_to_mle(matrix: SparseMatrix) -> DenseMultilinear } /// Takes the n_vars and a dense vector and returns its dense MLE. -pub fn vec_to_mle(n_vars: usize, v: &Vec) -> DenseMultilinearExtension { +pub fn vec_to_mle(n_vars: usize, v: &[F]) -> DenseMultilinearExtension { let v_padded: Vec = if v.len() != (1 << n_vars) { // pad to 2^n_vars [ - v.clone(), + v.to_owned(), std::iter::repeat(F::zero()) .take((1 << n_vars) - v.len()) .collect(), ] .concat() } else { - v.clone() + v.to_owned() }; DenseMultilinearExtension::::from_evaluations_vec(n_vars, v_padded) } -pub fn dense_vec_to_mle(n_vars: usize, v: &Vec) -> DenseMultilinearExtension { +pub fn dense_vec_to_mle(n_vars: usize, v: &[F]) -> DenseMultilinearExtension { // Pad to 2^n_vars let v_padded: Vec = [ - v.clone(), + v.to_owned(), std::iter::repeat(F::zero()) .take((1 << n_vars) - v.len()) .collect(), diff --git a/folding-schemes/src/utils/vec.rs b/folding-schemes/src/utils/vec.rs index 47c58c3..9af3cc3 100644 --- a/folding-schemes/src/utils/vec.rs +++ b/folding-schemes/src/utils/vec.rs @@ -79,7 +79,7 @@ pub fn is_zero_vec(vec: &[F]) -> bool { vec.iter().all(|a| a.is_zero()) } -pub fn mat_vec_mul(M: &Vec>, z: &[F]) -> Result, Error> { +pub fn mat_vec_mul(M: &[Vec], z: &[F]) -> Result, Error> { if M.is_empty() { return Err(Error::Empty); } diff --git a/rust-toolchain b/rust-toolchain index dc87e8a..32a6ce3 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.74.0 +1.76.0