#![allow(non_snake_case)]
use crate::{
CommitmentKey,
errors::SpartanError,
math::Math,
polys::{eq::EqPolynomial, multilinear::MultilinearPolynomial},
r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness, SparseMatrix},
sumcheck::SumcheckProof,
traits::{Engine, pcs::PCSEngineTrait, transcript::TranscriptEngineTrait},
};
use ff::Field;
use serde::{Deserialize, Serialize};
fn bind_matrix_row_vars<E: Engine>(
M: &SparseMatrix<E::Scalar>,
rx: &[E::Scalar],
num_cols: usize,
) -> Vec<E::Scalar> {
let mut evals = vec![E::Scalar::ZERO; num_cols];
for (row_idx, ptrs) in M.indptr.windows(2).enumerate() {
let row_weight = rx[row_idx];
if row_weight == E::Scalar::ZERO {
continue;
}
for idx in ptrs[0]..ptrs[1] {
let col = M.indices[idx];
let val = M.data[idx];
evals[col] += row_weight * val;
}
}
evals
}
fn evaluate_matrix_with_tables<E: Engine>(
M: &SparseMatrix<E::Scalar>,
T_x: &[E::Scalar],
T_y: &[E::Scalar],
) -> E::Scalar {
M.indptr
.windows(2)
.enumerate()
.map(|(row_idx, ptrs)| {
let tx = T_x[row_idx];
if tx == E::Scalar::ZERO {
return E::Scalar::ZERO;
}
let row_sum: E::Scalar = (ptrs[0]..ptrs[1])
.map(|idx| {
let col = M.indices[idx];
let val = M.data[idx];
T_y[col] * val
})
.sum();
tx * row_sum
})
.sum()
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct RelaxedR1CSSpartanProof<E: Engine> {
pub(crate) sc_proof_outer: SumcheckProof<E>,
pub(crate) claims_outer: (E::Scalar, E::Scalar, E::Scalar),
pub(crate) sc_proof_inner: SumcheckProof<E>,
pub(crate) v_W: Vec<E::Scalar>,
pub(crate) blind_W: E::Scalar,
pub(crate) v_E: Vec<E::Scalar>,
pub(crate) blind_E: E::Scalar,
}
impl<E: Engine> RelaxedR1CSSpartanProof<E> {
pub fn prove(
S: &R1CSShape<E>,
ck: &CommitmentKey<E>,
u: &E::Scalar,
X: &[E::Scalar],
W: &RelaxedR1CSWitness<E>,
transcript: &mut E::TE,
) -> Result<Self, SpartanError> {
transcript.absorb(b"u_relaxed", u);
transcript.absorb(b"X_relaxed", &X);
let num_cons = S.num_cons;
let num_vars = S.num_vars;
let num_rounds_x = num_cons.log_2();
let num_vars_padded = num_vars.next_power_of_two();
let num_rounds_y = num_vars_padded.log_2() + 1;
let z_len = num_vars_padded * 2;
let z_unpadded = [W.W.clone(), vec![*u], X.to_vec()].concat();
let (Az, Bz, Cz) = S.multiply_vec(&z_unpadded)?;
let tau = (0..num_rounds_x)
.map(|_| transcript.squeeze(b"t"))
.collect::<Result<Vec<_>, SpartanError>>()?;
let uCz_plus_E: Vec<E::Scalar> = Cz
.iter()
.zip(W.E.iter())
.map(|(cz_i, e_i)| *u * cz_i + e_i)
.collect();
let mut poly_Az = MultilinearPolynomial::new(Az);
let mut poly_Bz = MultilinearPolynomial::new(Bz);
let mut poly_uCzE = MultilinearPolynomial::new(uCz_plus_E);
let (sc_proof_outer, r_x, claims_outer) = SumcheckProof::prove_cubic_with_three_inputs(
&E::Scalar::ZERO,
tau,
&mut poly_Az,
&mut poly_Bz,
&mut poly_uCzE,
transcript,
)?;
let (claim_Az, claim_Bz, claim_uCzE) = (claims_outer[0], claims_outer[1], claims_outer[2]);
transcript.absorb(
b"claims_outer",
&[claim_Az, claim_Bz, claim_uCzE].as_slice(),
);
let r = transcript.squeeze(b"r")?;
let r_sq = r * r;
let evals_rx = EqPolynomial::evals_from_points(&r_x);
let claim_E: E::Scalar = W
.E
.iter()
.zip(evals_rx.iter())
.map(|(e_i, eq_i)| *e_i * eq_i)
.fold(E::Scalar::ZERO, |acc, v| acc + v);
let claim_inner_joint = claim_Az + r * claim_Bz + r_sq * (claim_uCzE - claim_E);
let num_cols = num_vars + 1 + S.num_io;
let evals_A = bind_matrix_row_vars::<E>(&S.A, &evals_rx, num_cols);
let evals_B = bind_matrix_row_vars::<E>(&S.B, &evals_rx, num_cols);
let evals_C = bind_matrix_row_vars::<E>(&S.C, &evals_rx, num_cols);
let mut poly_ABC: Vec<E::Scalar> = evals_A
.iter()
.zip(evals_B.iter())
.zip(evals_C.iter())
.map(|((a, b), c)| *a + r * b + r_sq * *u * c)
.collect();
poly_ABC.resize(z_len, E::Scalar::ZERO);
let mut poly_z = z_unpadded;
poly_z.resize(z_len, E::Scalar::ZERO);
let (sc_proof_inner, r_y, _claims_inner) = SumcheckProof::prove_quad(
&claim_inner_joint,
num_rounds_y,
&mut MultilinearPolynomial::new(poly_ABC),
&mut MultilinearPolynomial::new(poly_z),
transcript,
)?;
let (v_W, blind_W) = E::PCS::prove_direct(ck, &W.W, &W.r_W, &r_y[1..])?;
let (v_E, blind_E) = E::PCS::prove_direct(ck, &W.E, &W.r_E, &r_x)?;
transcript.absorb(b"v_W", &v_W.as_slice());
transcript.absorb(b"v_E", &v_E.as_slice());
Ok(Self {
sc_proof_outer,
claims_outer: (claim_Az, claim_Bz, claim_uCzE),
sc_proof_inner,
v_W,
blind_W,
v_E,
blind_E,
})
}
pub fn verify(
&self,
S: &R1CSShape<E>,
vk_ee: &<E::PCS as PCSEngineTrait<E>>::VerifierKey,
U: &RelaxedR1CSInstance<E>,
transcript: &mut E::TE,
) -> Result<(), SpartanError> {
transcript.absorb(b"u_relaxed", &U.u);
transcript.absorb(b"X_relaxed", &U.X.as_slice());
let num_cons = S.num_cons;
let num_vars = S.num_vars;
let num_rounds_x = num_cons.log_2();
let num_vars_padded = num_vars.next_power_of_two();
let num_rounds_y = num_vars_padded.log_2() + 1;
let tau = (0..num_rounds_x)
.map(|_| transcript.squeeze(b"t"))
.collect::<Result<EqPolynomial<_>, SpartanError>>()?;
let (claim_outer_final, r_x) =
self
.sc_proof_outer
.verify(E::Scalar::ZERO, num_rounds_x, 3, transcript)?;
let (claim_Az, claim_Bz, claim_uCzE) = self.claims_outer;
let taus_bound_rx = tau.evaluate(&r_x);
let claim_outer_expected = taus_bound_rx * (claim_Az * claim_Bz - claim_uCzE);
if claim_outer_final != claim_outer_expected {
return Err(SpartanError::InvalidSumcheckProof);
}
transcript.absorb(
b"claims_outer",
&[claim_Az, claim_Bz, claim_uCzE].as_slice(),
);
let r = transcript.squeeze(b"r")?;
let r_sq = r * r;
let eval_E = E::PCS::verify_direct(vk_ee, &U.comm_E, &self.v_E, &self.blind_E, &r_x)?;
let claim_inner_joint = claim_Az + r * claim_Bz + r_sq * (claim_uCzE - eval_E);
let (claim_inner_final, r_y) =
self
.sc_proof_inner
.verify(claim_inner_joint, num_rounds_y, 2, transcript)?;
let eval_W = E::PCS::verify_direct(vk_ee, &U.comm_W, &self.v_W, &self.blind_W, &r_y[1..])?;
let T_x = EqPolynomial::evals_from_points(&r_x);
let T_y = EqPolynomial::evals_from_points(&r_y);
let eval_Z = {
let mut sum = (E::Scalar::ONE - r_y[0]) * eval_W;
sum += U.u * T_y[num_vars];
for (j, x_j) in U.X.iter().enumerate() {
sum += *x_j * T_y[num_vars + 1 + j];
}
sum
};
let eval_A = evaluate_matrix_with_tables::<E>(&S.A, &T_x, &T_y);
let eval_B = evaluate_matrix_with_tables::<E>(&S.B, &T_x, &T_y);
let eval_C = evaluate_matrix_with_tables::<E>(&S.C, &T_x, &T_y);
let eval_ABC = eval_A + r * eval_B + r_sq * U.u * eval_C;
let claim_inner_expected = eval_ABC * eval_Z;
if claim_inner_final != claim_inner_expected {
return Err(SpartanError::InvalidSumcheckProof);
}
transcript.absorb(b"v_W", &self.v_W.as_slice());
transcript.absorb(b"v_E", &self.v_E.as_slice());
Ok(())
}
}
#[cfg(test)]
mod tests {
use ff::Field;
use tracing_subscriber::EnvFilter;
use super::*;
#[test]
fn test_relaxed_spartan_proof() {
let _ = tracing_subscriber::fmt()
.with_target(false)
.with_env_filter(EnvFilter::from_default_env())
.try_init();
type E = crate::provider::PallasHyraxEngine;
test_relaxed_spartan_with::<E>();
}
fn test_relaxed_spartan_with<E: Engine>() {
let num_cons = 4;
let num_vars = 4;
let num_io = 0;
let A = SparseMatrix {
indices: vec![0, 1, 2, 3],
indptr: vec![0, 1, 2, 3, 4],
data: vec![E::Scalar::ONE; 4],
cols: num_vars + 1 + num_io,
};
let B = A.clone();
let C = A.clone();
let shape = R1CSShape::<E>::new(num_cons, num_vars, num_io, A, B, C).unwrap();
let width = 32;
let (ck, vk_ee) = E::PCS::setup(b"test", shape.num_vars.max(shape.num_cons), width);
let (U, W) = shape.sample_random_instance_witness(&ck).unwrap();
shape.is_sat_relaxed(&ck, &U, &W).unwrap();
let mut transcript_p = E::TE::new(b"test_relaxed_spartan");
let proof =
RelaxedR1CSSpartanProof::prove(&shape, &ck, &U.u, &U.X, &W, &mut transcript_p).unwrap();
let mut transcript_v = E::TE::new(b"test_relaxed_spartan");
proof.verify(&shape, &vk_ee, &U, &mut transcript_v).unwrap();
}
}