use hemera::hash as hemera_hash;
use lens::brakedown::Brakedown;
use nebu::Goldilocks;
use crate::ccs::selector::is_satisfied;
use crate::spartan::verifier::SpartanVerifier;
use crate::transcript::Transcript;
use crate::types::{
Accumulator, CCSInstance, CCSWitness, Proof, ProofParams, SparseMatrix, Statement,
};
use crate::{decide, fold};
#[derive(Clone, Debug)]
pub struct SparseGraph {
pub n: usize,
pub edges: Vec<(usize, usize, Goldilocks)>, }
impl SparseGraph {
pub fn empty(n: usize) -> Self {
Self {
n,
edges: Vec::new(),
}
}
pub fn add(&mut self, row: usize, col: usize, w: Goldilocks) {
debug_assert!(row < self.n && col < self.n);
self.edges.push((row, col, w));
}
pub fn commitment(&self) -> [u8; 32] {
let mut buf = Vec::with_capacity(8 + self.edges.len() * 24);
buf.extend_from_slice(&(self.n as u64).to_le_bytes());
for &(r, c, w) in &self.edges {
buf.extend_from_slice(&(r as u64).to_le_bytes());
buf.extend_from_slice(&(c as u64).to_le_bytes());
buf.extend_from_slice(&w.as_u64().to_le_bytes());
}
*hemera_hash(&buf)
.as_bytes()
.first_chunk::<32>()
.unwrap_or(&[0u8; 32])
}
}
pub fn spmv_native(graph: &SparseGraph, x: &[Goldilocks]) -> Vec<Goldilocks> {
assert_eq!(x.len(), graph.n);
let mut y = vec![Goldilocks::ZERO; graph.n];
for &(r, c, w) in &graph.edges {
y[r] += w * x[c];
}
y
}
pub fn spmv_ccs(graph: &SparseGraph) -> CCSInstance {
let n = graph.n;
let rows = n.next_power_of_two().max(1);
let cols = (2 * n).next_power_of_two().max(2);
let mut m = SparseMatrix::new(rows, cols);
let neg_one = Goldilocks::ZERO - Goldilocks::ONE;
for &(r, c, w) in &graph.edges {
m.set(r, c, w);
}
for i in 0..n {
m.set(i, n + i, neg_one);
}
CCSInstance {
matrices: vec![m],
multisets: vec![vec![0]],
coeffs: vec![Goldilocks::ONE],
num_rows: rows,
num_cols: cols,
}
}
pub fn spmv_witness(graph: &SparseGraph, x: &[Goldilocks], y: &[Goldilocks]) -> CCSWitness {
let n = graph.n;
let cols = (2 * n).next_power_of_two().max(2);
let mut z = vec![Goldilocks::ZERO; cols];
z[..n].copy_from_slice(x);
z[n..2 * n].copy_from_slice(y);
CCSWitness { z }
}
#[derive(Debug, PartialEq, Eq)]
pub enum SpmvError {
DimMismatch,
Unsatisfied,
FoldFailed,
DecideFailed,
VerifyFailed,
}
#[derive(Clone, Debug)]
pub struct SpmvStatement {
pub graph_commit: [u8; 32],
pub x_hash: [u8; 32],
pub y_hash: [u8; 32],
pub n: usize,
}
impl SpmvStatement {
pub fn new(graph: &SparseGraph, x: &[Goldilocks], y: &[Goldilocks]) -> Self {
Self {
graph_commit: graph.commitment(),
x_hash: vec_hash(x),
y_hash: vec_hash(y),
n: graph.n,
}
}
pub fn to_zheng(&self) -> Statement {
let mut input = [0u8; 32];
input[..8].copy_from_slice(&(self.n as u64).to_le_bytes());
Statement {
program_hash: {
let mut h = [0u8; 32];
let tag = b"zheng-spmv-v0";
h[..tag.len()].copy_from_slice(tag);
h
},
input_hash: input,
output_hash: {
let mut b = [0u8; 32];
let mut buf = [0u8; 96];
buf[..32].copy_from_slice(&self.graph_commit);
buf[32..64].copy_from_slice(&self.x_hash);
buf[64..].copy_from_slice(&self.y_hash);
let h = hemera_hash(&buf);
b.copy_from_slice(h.as_bytes().first_chunk::<32>().unwrap_or(&[0u8; 32]));
b
},
focus_bound: self.n as u64,
}
}
}
pub struct SpmvProof {
pub proof: Proof,
pub statement: SpmvStatement,
}
pub fn prove_spmv(
graph: &SparseGraph,
x: &[Goldilocks],
y: &[Goldilocks],
) -> Result<SpmvProof, SpmvError> {
if x.len() != graph.n || y.len() != graph.n {
return Err(SpmvError::DimMismatch);
}
let y_check = spmv_native(graph, x);
if y_check != y {
return Err(SpmvError::Unsatisfied);
}
let instance = spmv_ccs(graph);
let witness = spmv_witness(graph, x, y);
if !is_satisfied(&instance, &witness) {
return Err(SpmvError::Unsatisfied);
}
let stmt = SpmvStatement::new(graph, x, y);
let zheng_stmt = stmt.to_zheng();
let mut acc = blank_acc(&instance);
let mut t = Transcript::new();
fold(&mut acc, &instance, &witness, &mut t).map_err(|_| SpmvError::FoldFailed)?;
let proof =
decide(&acc, &zheng_stmt, &ProofParams::default()).map_err(|_| SpmvError::DecideFailed)?;
Ok(SpmvProof {
proof,
statement: stmt,
})
}
pub fn verify_spmv(
graph: &SparseGraph,
x: &[Goldilocks],
y: &[Goldilocks],
proof: &SpmvProof,
) -> bool {
if x.len() != graph.n || y.len() != graph.n {
return false;
}
if graph.commitment() != proof.statement.graph_commit {
return false;
}
if vec_hash(x) != proof.statement.x_hash || vec_hash(y) != proof.statement.y_hash {
return false;
}
if spmv_native(graph, x) != y {
return false;
}
let instance = spmv_ccs(graph);
let witness = spmv_witness(graph, x, y);
if !is_satisfied(&instance, &witness) {
return false;
}
let zheng_stmt = proof.statement.to_zheng();
let mut acc = blank_acc(&instance);
let mut t = Transcript::new();
if fold(&mut acc, &instance, &witness, &mut t).is_err() {
return false;
}
let mut vt = Transcript::new_recursive();
vt.absorb_statement(&zheng_stmt);
vt.absorb(acc.witness_commitment.as_bytes());
for &e in &acc.error_evals {
vt.absorb(&e.as_u64().to_le_bytes());
}
vt.absorb(&acc.step_count.to_le_bytes());
SpartanVerifier::verify(
&acc.committed_instance,
&proof.proof,
&acc.error_evals,
&mut vt,
)
.is_ok()
}
fn blank_acc(instance: &CCSInstance) -> Accumulator {
let z = vec![Goldilocks::ZERO; instance.num_cols.max(64)];
Accumulator {
committed_instance: instance.clone(),
folded_witness: CCSWitness { z: z.clone() },
witness_commitment: Brakedown::commit_raw(&z),
error_evals: vec![Goldilocks::ZERO; instance.num_rows],
step_count: 0,
}
}
fn vec_hash(v: &[Goldilocks]) -> [u8; 32] {
let mut buf = Vec::with_capacity(8 + v.len() * 8);
buf.extend_from_slice(&(v.len() as u64).to_le_bytes());
for x in v {
buf.extend_from_slice(&x.as_u64().to_le_bytes());
}
*hemera_hash(&buf)
.as_bytes()
.first_chunk::<32>()
.unwrap_or(&[0u8; 32])
}
#[cfg(test)]
mod tests {
use super::*;
fn g(v: u64) -> Goldilocks {
Goldilocks::new(v)
}
#[test]
fn spmv_native_2x2() {
let mut graph = SparseGraph::empty(2);
graph.add(0, 0, g(2));
graph.add(0, 1, g(3));
graph.add(1, 0, g(4));
let x = vec![g(5), g(6)];
let y = spmv_native(&graph, &x);
assert_eq!(y[0], g(28));
assert_eq!(y[1], g(20));
}
#[test]
fn spmv_ccs_satisfied() {
let mut graph = SparseGraph::empty(3);
graph.add(0, 1, g(2));
graph.add(1, 2, g(3));
graph.add(2, 0, g(5));
graph.add(2, 2, g(1));
let x = vec![g(1), g(2), g(3)];
let y = spmv_native(&graph, &x);
let ccs = spmv_ccs(&graph);
let w = spmv_witness(&graph, &x, &y);
assert!(is_satisfied(&ccs, &w));
let mut y_bad = y.clone();
y_bad[0] += g(1);
let w_bad = spmv_witness(&graph, &x, &y_bad);
assert!(!is_satisfied(&ccs, &w_bad));
}
#[test]
fn prove_verify_spmv() {
let mut graph = SparseGraph::empty(4);
graph.add(0, 1, g(1));
graph.add(1, 2, g(2));
graph.add(2, 3, g(3));
graph.add(3, 0, g(4));
graph.add(1, 1, g(1));
let x = vec![g(1), g(2), g(3), g(4)];
let y = spmv_native(&graph, &x);
let proof = prove_spmv(&graph, &x, &y).unwrap();
assert!(verify_spmv(&graph, &x, &y, &proof));
let mut y2 = y.clone();
y2[0] = g(0);
assert!(!verify_spmv(&graph, &x, &y2, &proof));
}
}