use nebu::Goldilocks;
use super::coins::{Coins, label};
use crate::types::{CCSInstance, SparseMatrix};
pub const PER_TYPE: usize = 3;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Gate {
Product,
ProductSbox,
}
impl Gate {
pub fn of(instance: &CCSInstance) -> Result<Self, String> {
let one = Goldilocks::ONE;
let neg = -Goldilocks::ONE;
match (
instance.matrices.len(),
instance.multisets.as_slice(),
instance.coeffs.as_slice(),
) {
(3, [a, b], [c0, c1]) if *a == [0, 1] && *b == [2] && (*c0, *c1) == (one, neg) => {
Ok(Self::Product)
}
(5, [a, b, c, d], [c0, c1, c2, c3])
if *a == [0, 1]
&& *b == [2]
&& *c == [3; 7]
&& *d == [4]
&& (*c0, *c1, *c2, *c3) == (one, neg, one, neg) =>
{
Ok(Self::ProductSbox)
}
_ => Err("veil: unsupported CCS gate".into()),
}
}
fn types(self) -> &'static [Kind] {
match self {
Self::Product => &[Kind::T0, Kind::T1, Kind::TB],
Self::ProductSbox => &[Kind::T0, Kind::T1, Kind::TB, Kind::TA, Kind::TE],
}
}
}
#[derive(Clone, Copy, Debug)]
enum Kind {
T0,
T1,
TB,
TA,
TE,
}
impl Kind {
fn columns(self) -> usize {
match self {
Self::TE => 2,
_ => 1,
}
}
}
pub(crate) struct Extension {
fill: Vec<(usize, Option<usize>)>,
}
fn empty_row(i: &CCSInstance, r: usize) -> bool {
i.matrices
.iter()
.all(|m| m.entries.get(r).is_none_or(Vec::is_empty))
}
pub(crate) fn extend(base: &CCSInstance) -> Result<(CCSInstance, Extension), String> {
let gate = Gate::of(base)?;
let kinds = gate.types();
let rows_needed = kinds.len() * PER_TYPE;
let cols_needed: usize = kinds.iter().map(|k| k.columns() * PER_TYPE).sum();
let trailing = (0..base.num_rows)
.rev()
.take_while(|&r| empty_row(base, r))
.count();
let num_rows = if trailing >= rows_needed {
base.num_rows
} else {
(base.num_rows + rows_needed).next_power_of_two()
};
let first_row = num_rows - rows_needed;
let first_col = base.num_cols;
let num_cols = (base.num_cols + cols_needed).next_power_of_two();
let mut matrices: Vec<SparseMatrix> = base
.matrices
.iter()
.map(|m| {
let mut entries = m.entries.clone();
entries.resize(num_rows, vec![]);
SparseMatrix {
rows: num_rows,
cols: num_cols,
entries,
}
})
.collect();
let (one, neg) = (Goldilocks::ONE, -Goldilocks::ONE);
let mut row = first_row;
let mut col = first_col;
let mut fill = Vec::with_capacity(cols_needed);
for &kind in kinds {
for _ in 0..PER_TYPE {
let s = col;
let mut set = |j: usize, c: usize, v: Goldilocks| matrices[j].entries[row].push((c, v));
match kind {
Kind::T0 => set(0, s, one),
Kind::T1 => set(1, s, one),
Kind::TB => {
set(0, s, one);
set(1, 0, one);
set(2, s, one);
}
Kind::TA => {
set(2, s, one);
set(4, s, neg);
}
Kind::TE => {
set(3, s, one);
set(4, s + 1, one);
}
}
fill.push((s, None));
if let Kind::TE = kind {
fill.push((s + 1, Some(s)));
}
col += kind.columns();
row += 1;
}
}
let instance = CCSInstance {
matrices,
multisets: base.multisets.clone(),
coeffs: base.coeffs.clone(),
num_rows,
num_cols,
};
Ok((instance, Extension { fill }))
}
impl Extension {
pub fn witness(&self, base: &[Goldilocks], num_cols: usize, coins: &Coins) -> Vec<Goldilocks> {
let mut z = base.to_vec();
z.resize(num_cols, Goldilocks::ZERO);
let free = self.fill.iter().filter(|f| f.1.is_none()).count();
let mut values = coins.bases(label::PAD_FILL, free).into_iter();
for &(c, power_of) in &self.fill {
z[c] = match power_of {
None => values.next().expect("one value per free slot"),
Some(s) => z[s].pow7(),
};
}
z
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::execution::ExecutionNoun as N;
use crate::execution::private::prepare_execution;
fn pair(a: N, b: N) -> N {
N::Pair(Box::new(a), Box::new(b))
}
#[test]
fn extended_relations_are_satisfied_by_every_mask() {
let mul = pair(
N::Atom(7),
pair(pair(N::Atom(0), N::Atom(2)), pair(N::Atom(0), N::Atom(6))),
);
let hash = pair(N::Atom(15), pair(N::Atom(0), N::Atom(2)));
for (program, gate) in [(mul, Gate::Product), (hash, Gate::ProductSbox)] {
let (_, prepared, z) = prepare_execution(&program, &[3, 4], &[], 1000).unwrap();
let base = &prepared.relation.instance;
assert_eq!(Gate::of(base).unwrap(), gate);
let (ext, fill) = extend(base).unwrap();
assert!(ext.num_rows.is_power_of_two() && ext.num_cols.is_power_of_two());
let z: Vec<_> = z.into_iter().map(Goldilocks::new).collect();
for seed in 0..4u8 {
let w = fill.witness(&z, ext.num_cols, &Coins::seeded([seed; 32]));
assert!(ext.is_satisfied_by(&crate::types::CCSWitness { z: w }));
}
if gate == Gate::ProductSbox {
let mut w = fill.witness(&z, ext.num_cols, &Coins::seeded([9; 32]));
let (t, _) = fill.fill.iter().find(|f| f.1.is_some()).unwrap();
w[*t] += Goldilocks::ONE;
assert!(!ext.is_satisfied_by(&crate::types::CCSWitness { z: w }));
}
}
}
}