use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, fields::{fp::FpVar, FieldVar}, R1CSVar, }; use ark_relations::r1cs::{Namespace, SynthesisError}; use core::{borrow::Borrow, marker::PhantomData}; use crate::utils::vec::SparseMatrix; pub trait MatrixGadget { fn mul_vector(&self, v: &[FV]) -> Result, SynthesisError>; } pub trait VectorGadget { fn add(&self, other: &Self) -> Result, SynthesisError>; fn mul_scalar(&self, other: &FV) -> Result, SynthesisError>; fn hadamard(&self, other: &Self) -> Result, SynthesisError>; } impl VectorGadget> for [FpVar] { fn add(&self, other: &Self) -> Result>, SynthesisError> { if self.len() != other.len() { return Err(SynthesisError::Unsatisfiable); } Ok(self.iter().zip(other.iter()).map(|(a, b)| a + b).collect()) } fn mul_scalar(&self, c: &FpVar) -> Result>, SynthesisError> { Ok(self.iter().map(|a| a * c).collect()) } fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { if self.len() != other.len() { return Err(SynthesisError::Unsatisfiable); } Ok(self.iter().zip(other.iter()).map(|(a, b)| a * b).collect()) } } #[derive(Debug, Clone)] pub struct SparseMatrixVar> { _f: PhantomData, _cf: PhantomData, _fv: PhantomData, pub n_rows: usize, pub n_cols: usize, // same format as the native SparseMatrix (which follows ark_relations::r1cs::Matrix format pub coeffs: Vec>, } impl AllocVar, CF> for SparseMatrixVar where F: PrimeField, CF: PrimeField, FV: AllocVar, { fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { f().and_then(|val| { let cs = cs.into(); let mut coeffs: Vec> = Vec::new(); for row in val.borrow().coeffs.iter() { let mut rowVar: Vec<(FV, usize)> = Vec::new(); for &(value, col_i) in row.iter() { let coeffVar = FV::new_variable(cs.clone(), || Ok(value), mode)?; rowVar.push((coeffVar, col_i)); } coeffs.push(rowVar); } Ok(Self { _f: PhantomData, _cf: PhantomData, _fv: PhantomData, n_rows: val.borrow().n_rows, n_cols: val.borrow().n_cols, coeffs, }) }) } } impl MatrixGadget> for SparseMatrixVar> { fn mul_vector(&self, v: &[FpVar]) -> Result>, SynthesisError> { Ok(self .coeffs .iter() .map(|row| { let products = row .iter() .map(|(value, col_i)| value * &v[*col_i]) .collect::>(); if products.is_constant() { FpVar::constant(products.value().unwrap_or_default().into_iter().sum()) } else { products.iter().sum() } }) .collect()) } } /// Interprets the given vector v as the evaluations of a dense multilinear extension of n_vars, /// and evaluates it at the given point. This method mimics the behavior of /// `utils/mle.rs#dense_vec_to_dense_mle` + `DenseMultilinearExtension::evaluate` but in R1CS /// constraints, since dense multilinear extensions are not supported in ark_r1cs_std. pub fn eval_mle( // n_vars indicates the number of variables in the MLE n_vars: usize, // v is the vector of the evaluations of the dense multilinear extension (MLE) v: Vec>, // point is the point at which we want to evaluate the MLE point: Vec>, ) -> FpVar { // pad to 2^n_vars let mut poly: Vec> = [ v.to_owned(), std::iter::repeat(FpVar::zero()) .take((1 << n_vars) - v.len()) .collect(), ] .concat(); for i in 1..n_vars + 1 { let r = point[i - 1].clone(); for b in 0..(1 << (n_vars - 1)) { let left = poly[b << 1].clone(); let right = poly[(b << 1) + 1].clone(); poly[b] = left.clone() + r.clone() * (right - left); } } poly[0].clone() }