use nebu::Goldilocks;
use nox::TraceRow;
use hemera::constants::ROUND_CONSTANTS;
use hemera::field::{Goldilocks as HGold, MATRIX_DIAG_16};
use crate::types::{CCSInstance, CCSWitness, CommitError, SparseMatrix};
pub const Z_LEN_HASH: usize = 50;
pub(crate) const IDX_CONST: usize = 32;
pub(crate) const IDX_CAP_K: usize = 33;
pub(crate) const IDX_CAP_K1: usize = 41;
pub(crate) const IDX_Y: usize = 49;
pub struct HashAux {
pub rate: [Goldilocks; 8],
}
pub(crate) fn sk(i: usize) -> usize {
match i {
0 => 4, 1 => 5, 2 => 6, 3 => 7,
4 => 10, 5 => 11, 6 => 12, 7 => 13,
_ => IDX_CAP_K + (i - 8),
}
}
pub(crate) fn sk1(i: usize) -> usize {
match i {
0 => 20, 1 => 21, 2 => 22, 3 => 23,
4 => 26, 5 => 27, 6 => 28, 7 => 29,
_ => IDX_CAP_K1 + (i - 8),
}
}
fn neg(x: Goldilocks) -> Goldilocks {
Goldilocks::ZERO - x
}
fn hg(h: HGold) -> Goldilocks {
Goldilocks::new(h.as_canonical_u64())
}
fn field_inv(x: Goldilocks) -> Goldilocks {
hg(HGold::new(x.canonicalize().as_u64()).inv())
}
pub fn trivial_hash_ccs() -> CCSInstance {
CCSInstance {
matrices: vec![],
multisets: vec![],
coeffs: vec![],
num_rows: 16,
num_cols: Z_LEN_HASH,
}
}
pub fn partial_round_ccs(k: usize) -> CCSInstance {
debug_assert!((3..19).contains(&k), "k={k} not in partial-round range 3..19");
let pr = k - 3;
let rc = hg(ROUND_CONSTANTS[128 + pr]);
let mut diag = [Goldilocks::ZERO; 16];
for i in 0..16 {
diag[i] = hg(MATRIX_DIAG_16[i]);
}
let mut m_a = SparseMatrix::new(16, Z_LEN_HASH);
m_a.set(0, IDX_Y, Goldilocks::ONE);
let mut m_b = SparseMatrix::new(16, Z_LEN_HASH);
m_b.set(0, sk(0), Goldilocks::ONE);
m_b.set(0, IDX_CONST, rc);
let mut m_c = SparseMatrix::new(16, Z_LEN_HASH);
m_c.set(0, IDX_CONST, Goldilocks::ONE);
let mut m_d = SparseMatrix::new(16, Z_LEN_HASH);
m_d.set(1, sk1(0), Goldilocks::ONE);
m_d.set(1, IDX_Y, neg(diag[0]));
m_d.set(1, sk1(1), neg(Goldilocks::ONE));
m_d.set(1, sk(1), diag[1]);
for r in 2..16usize {
m_d.set(r, sk1(r), Goldilocks::ONE);
m_d.set(r, sk(r), neg(diag[r]));
m_d.set(r, sk1(1), neg(Goldilocks::ONE));
m_d.set(r, sk(1), diag[1]);
}
CCSInstance {
matrices: vec![m_a, m_b, m_c, m_d],
multisets: vec![
vec![0, 1], vec![2], vec![3], ],
coeffs: vec![
Goldilocks::ONE,
neg(Goldilocks::ONE),
Goldilocks::ONE,
],
num_rows: 16,
num_cols: Z_LEN_HASH,
}
}
pub fn hash_witness(
row_t: &TraceRow,
row_t1: &TraceRow,
cap_k: &[Goldilocks; 8],
cap_k1: &[Goldilocks; 8],
y: Goldilocks,
) -> CCSWitness {
let mut z = Vec::with_capacity(Z_LEN_HASH);
for &v in row_t.r().iter() {
z.push(Goldilocks::new(v).canonicalize());
}
for &v in row_t1.r().iter() {
z.push(Goldilocks::new(v).canonicalize());
}
z.push(Goldilocks::ONE);
for &c in cap_k.iter() { z.push(c); }
for &c in cap_k1.iter() { z.push(c); }
z.push(y);
CCSWitness { z }
}
pub fn build_hash_steps_from_trace(
trace: &[TraceRow],
aux: &[HashAux],
) -> Result<Vec<(CCSInstance, CCSWitness)>, CommitError> {
let mut steps = Vec::new();
let mut aux_idx = 0;
let mut i = 0;
while i < trace.len() {
if trace[i].r()[0] != 15 {
i += 1;
continue;
}
let block_start = i;
while i < trace.len() && trace[i].r()[0] == 15 {
i += 1;
}
let block = &trace[block_start..i];
if block.len() < 2 {
continue;
}
let ha = aux.get(aux_idx).ok_or(CommitError::TraceOverflow)?;
aux_idx += 1;
let mut rate_h = [HGold::ZERO; 8];
for (j, r) in ha.rate.iter().enumerate() {
rate_h[j] = HGold::new(r.canonicalize().as_u64());
}
let mut full_states: Vec<[Goldilocks; 16]> = Vec::with_capacity(24);
for state_h in hemera::StepSponge::absorb(&rate_h) {
let mut s = [Goldilocks::ZERO; 16];
for j in 0..16 {
s[j] = Goldilocks::new(state_h[j].as_canonical_u64());
}
full_states.push(s);
}
for pair in block.windows(2) {
let row_t = &pair[0];
let row_t1 = &pair[1];
let k = row_t.r()[14] as usize;
let state_k = &full_states[k];
let state_k1 = full_states.get(k + 1).unwrap_or(&full_states[23]);
let mut cap_k = [Goldilocks::ZERO; 8];
let mut cap_k1 = [Goldilocks::ZERO; 8];
cap_k.copy_from_slice(&state_k[8..16]);
cap_k1.copy_from_slice(&state_k1[8..16]);
let (instance, y) = if (3..19).contains(&k) {
let pr = k - 3;
let rc = hg(ROUND_CONSTANTS[128 + pr]);
let y = field_inv(state_k[0] + rc);
(partial_round_ccs(k), y)
} else {
(trivial_hash_ccs(), Goldilocks::ZERO)
};
let witness = hash_witness(row_t, row_t1, &cap_k, &cap_k1, y);
steps.push((instance, witness));
}
}
Ok(steps)
}
#[cfg(test)]
mod tests {
use super::*;
use hemera::field::Goldilocks as HGold;
fn zero_rate_states() -> Vec<[HGold; 16]> {
hemera::StepSponge::absorb(&[HGold::ZERO; 8]).collect()
}
fn make_witness_from_states(
state_k: &[HGold; 16],
state_k1: &[HGold; 16],
cap_k: &[Goldilocks; 8],
cap_k1: &[Goldilocks; 8],
y: Goldilocks,
) -> CCSWitness {
let mut z = vec![Goldilocks::ZERO; Z_LEN_HASH];
z[sk(0)] = hg(state_k[0]);
z[sk(1)] = hg(state_k[1]);
z[sk(2)] = hg(state_k[2]);
z[sk(3)] = hg(state_k[3]);
z[sk(4)] = hg(state_k[4]);
z[sk(5)] = hg(state_k[5]);
z[sk(6)] = hg(state_k[6]);
z[sk(7)] = hg(state_k[7]);
z[sk1(0)] = hg(state_k1[0]);
z[sk1(1)] = hg(state_k1[1]);
z[sk1(2)] = hg(state_k1[2]);
z[sk1(3)] = hg(state_k1[3]);
z[sk1(4)] = hg(state_k1[4]);
z[sk1(5)] = hg(state_k1[5]);
z[sk1(6)] = hg(state_k1[6]);
z[sk1(7)] = hg(state_k1[7]);
z[IDX_CONST] = Goldilocks::ONE;
for j in 0..8 { z[IDX_CAP_K + j] = cap_k[j]; }
for j in 0..8 { z[IDX_CAP_K1 + j] = cap_k1[j]; }
z[IDX_Y] = y;
CCSWitness { z }
}
#[test]
fn partial_round_ccs_satisfied_by_all_real_partial_rounds() {
let states = zero_rate_states();
for k in 3..19usize {
let state_k = states[k];
let state_k1 = states[k + 1];
let pr = k - 3;
let rc = hemera::constants::ROUND_CONSTANTS[128 + pr];
let mut cap_k = [Goldilocks::ZERO; 8];
let mut cap_k1 = [Goldilocks::ZERO; 8];
for j in 0..8 {
cap_k[j] = hg(state_k[8 + j]);
cap_k1[j] = hg(state_k1[8 + j]);
}
let y = field_inv(hg(state_k[0]) + hg(rc));
let witness = make_witness_from_states(&state_k, &state_k1, &cap_k, &cap_k1, y);
let ccs = partial_round_ccs(k);
assert!(ccs.is_satisfied_by(&witness),
"partial_round_ccs not satisfied at k={k}");
}
}
#[test]
fn partial_round_ccs_rejects_wrong_next_state() {
let states = zero_rate_states();
let k = 5usize;
let state_k = states[k];
let state_bad = states[k + 3];
let pr = k - 3;
let rc = hemera::constants::ROUND_CONSTANTS[128 + pr];
let mut cap_k = [Goldilocks::ZERO; 8];
let mut cap_bad = [Goldilocks::ZERO; 8];
for j in 0..8 {
cap_k[j] = hg(state_k[8 + j]);
cap_bad[j] = hg(state_bad[8 + j]);
}
let y = field_inv(hg(state_k[0]) + hg(rc));
let witness = make_witness_from_states(&state_k, &state_bad, &cap_k, &cap_bad, y);
let ccs = partial_round_ccs(k);
assert!(!ccs.is_satisfied_by(&witness),
"partial_round_ccs should reject wrong next state at k={k}");
}
#[test]
fn build_hash_steps_empty_trace() {
let steps = build_hash_steps_from_trace(&[], &[]).unwrap();
assert!(steps.is_empty());
}
#[test]
fn partial_round_ccs_spartan_prove_verify() {
use crate::spartan::prover::SpartanProver;
use crate::spartan::verifier::SpartanVerifier;
use crate::transcript::Transcript;
let states = zero_rate_states();
let k = 7usize;
let state_k = states[k];
let state_k1 = states[k + 1];
let pr = k - 3;
let rc = hemera::constants::ROUND_CONSTANTS[128 + pr];
let mut cap_k = [Goldilocks::ZERO; 8];
let mut cap_k1 = [Goldilocks::ZERO; 8];
for j in 0..8 {
cap_k[j] = hg(state_k[8 + j]);
cap_k1[j] = hg(state_k1[8 + j]);
}
let y = field_inv(hg(state_k[0]) + hg(rc));
let witness = make_witness_from_states(&state_k, &state_k1, &cap_k, &cap_k1, y);
let ccs = partial_round_ccs(k);
assert!(ccs.is_satisfied_by(&witness), "precondition: witness must satisfy CCS");
let mut pt = Transcript::new();
let proof = SpartanProver::prove(&ccs, &witness, &mut pt);
let mut vt = Transcript::new();
assert!(SpartanVerifier::verify(&ccs, &proof, &[Goldilocks::ZERO; 16], &mut vt).is_ok(),
"Spartan verify failed for partial round k={k}");
}
}