use crate::field::{Field, Fp, Fp2};
use crate::merkle::{verify_path, Digest, MerkleTree, Transcript};
use crate::poly::{eq_table, horner, kernel_to_mono, mle_eval, mobius, ntt, restrict};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Basis {
Kernel,
Monomial,
}
#[derive(Clone, Copy, Debug)]
pub struct Params {
pub basis: Basis,
pub m: usize,
pub log_inv_rate: usize,
pub s: usize,
pub queries: usize,
}
impl Params {
pub fn n(&self) -> usize {
1 << (self.m + self.log_inv_rate)
}
fn omega(&self) -> Fp {
Fp::two_adic_root((self.m + self.log_inv_rate) as u32)
}
fn check(&self) {
assert!(self.s <= self.m);
assert!(self.log_inv_rate >= 1);
assert!(self.m + self.log_inv_rate <= 32);
assert!(self.queries >= 1);
}
}
pub struct ProverData {
table: Vec<Fp>,
w0: Vec<Fp>,
tree0: MerkleTree,
}
#[derive(Clone, Debug)]
pub struct Opening<F> {
pub a: F, pub b: F, pub path: Vec<Digest>,
}
#[derive(Clone, Debug)]
pub struct QueryProof {
pub level0: Opening<Fp>,
pub levels: Vec<Opening<Fp2>>, }
#[derive(Clone, Debug)]
pub struct Proof {
pub sumcheck: Vec<[Fp2; 3]>,
pub roots: Vec<Digest>, pub g: Vec<Fp2>,
pub queries: Vec<QueryProof>,
}
impl Proof {
pub fn size_bytes(&self) -> usize {
let mut s = self.sumcheck.len() * 3 * 16 + self.roots.len() * 32 + self.g.len() * 16;
for q in &self.queries {
s += 16 + 32 * q.level0.path.len();
for o in &q.levels {
s += 32 + 32 * o.path.len();
}
}
s
}
}
pub fn commit(p: &Params, table: &[Fp]) -> (Digest, ProverData) {
p.check();
assert_eq!(table.len(), 1 << p.m);
let mut w0 = table.to_vec();
to_coefficients(p.basis, &mut w0);
w0.resize(p.n(), Fp::ZERO);
ntt(&mut w0, p.omega());
let tree0 = MerkleTree::new(&w0);
let root = tree0.root();
(root, ProverData { table: table.to_vec(), w0, tree0 })
}
pub fn to_coefficients<F: Field>(basis: Basis, v: &mut [F]) {
match basis {
Basis::Kernel => kernel_to_mono(v),
Basis::Monomial => mobius(v),
}
}
#[inline(always)]
fn fold_pair(basis: Basis, a: Fp2, b: Fp2, inv_2x: Fp, t: Fp2, inv2: Fp) -> Fp2 {
let we = (a + b) * inv2;
let wo = (a - b) * inv_2x;
match basis {
Basis::Kernel => (wo - we) + t * (we.double() - wo),
Basis::Monomial => we + t * wo,
}
}
fn fold_word<F: Field + Into<Fp2>>(basis: Basis, w: &[F], t: Fp2, inv_x0: &[Fp], level: usize, inv2: Fp) -> Vec<Fp2> {
let h = w.len() / 2;
(0..h)
.map(|i| {
let inv_2x = inv_x0[i << level] * inv2;
fold_pair(basis, w[i].into(), w[i + h].into(), inv_2x, t, inv2)
})
.collect()
}
fn eq1(a: Fp2, c: Fp2) -> Fp2 {
a * c + (Fp2::ONE - a) * (Fp2::ONE - c)
}
fn interp3(h: &[Fp2; 3], t: Fp2) -> Fp2 {
let one = Fp2::ONE;
let two = one.double();
let inv2: Fp2 = Fp::new(2).inv().into();
let l0 = (t - one) * (t - two) * inv2;
let l1 = -(t * (t - two));
let l2 = t * (t - one) * inv2;
l0 * h[0] + l1 * h[1] + l2 * h[2]
}
fn init_transcript(p: &Params, root: &Digest, z: &[Fp2], v: Fp2) -> Transcript {
let mut tr = Transcript::new(b"kbfold-pcs-v1");
let params = [p.m as u64, p.log_inv_rate as u64, p.s as u64, p.queries as u64, (p.basis == Basis::Kernel) as u64];
let pb: Vec<u8> = params.iter().flat_map(|x| x.to_le_bytes()).collect();
tr.absorb_bytes(b"params", &pb);
tr.absorb_bytes(b"root0", root);
tr.absorb_field(b"point", z);
tr.absorb_field(b"value", &[v]);
tr
}
pub fn open(p: &Params, pd: &ProverData, z: &[Fp2]) -> (Fp2, Proof) {
open_inner(p, pd, z, None)
}
fn open_inner(p: &Params, pd: &ProverData, z: &[Fp2], cheat: Option<Fp2>) -> (Fp2, Proof) {
p.check();
assert_eq!(z.len(), p.m);
let n = p.n();
let inv2 = Fp::new(2).inv();
let omega_inv = p.omega().inv();
let mut inv_x0 = Vec::with_capacity(n / 2);
let mut acc = Fp::ONE;
for _ in 0..n / 2 {
inv_x0.push(acc);
acc = acc * omega_inv;
}
let mut a: Vec<Fp2> = pd.table.iter().map(|&x| x.into()).collect();
let mut e = eq_table(z);
let mut v = a.iter().zip(&e).fold(Fp2::ZERO, |s, (&x, &y)| s + x * y);
if let Some(d) = cheat {
v = v + d;
}
let mut claim = v;
let mut tr = init_transcript(p, &pd.tree0.root(), z, v);
let mut sumcheck = Vec::with_capacity(p.s);
let mut ts = Vec::with_capacity(p.s);
let mut roots = Vec::new();
let mut words: Vec<Vec<Fp2>> = Vec::new(); let mut trees: Vec<MerkleTree> = Vec::new();
for j in 0..p.s {
let mut h = [Fp2::ZERO; 3];
for (ap, ep) in a.chunks_exact(2).zip(e.chunks_exact(2)) {
h[0] = h[0] + ap[0] * ep[0];
h[1] = h[1] + ap[1] * ep[1];
h[2] = h[2] + (ap[1].double() - ap[0]) * (ep[1].double() - ep[0]);
}
if cheat.is_some() {
let d = (claim - (h[0] + h[1])) * Fp::new(2).inv();
for x in h.iter_mut() {
*x = *x + d;
}
}
tr.absorb_field(b"sumcheck", &h);
sumcheck.push(h);
let t = tr.challenge_ext();
claim = interp3(&h, t);
ts.push(t);
a = restrict(&a, t);
e = restrict(&e, t);
if j + 1 < p.s {
let next = if j == 0 {
fold_word(p.basis, &pd.w0, t, &inv_x0, 0, inv2)
} else {
fold_word(p.basis, &words[j - 1], t, &inv_x0, j, inv2)
};
let tree = MerkleTree::new(&next);
tr.absorb_bytes(b"root", &tree.root());
roots.push(tree.root());
words.push(next);
trees.push(tree);
}
}
let mut g = a;
if cheat.is_some() {
let prefix = ts.iter().zip(z).fold(Fp2::ONE, |acc, (&t, &zj)| acc * eq1(t, zj));
let e0 = prefix * eq_table(&z[p.s..])[0];
let cur = prefix * mle_eval(&g, &z[p.s..]);
g[0] = g[0] + (claim - cur) * e0.inv();
}
tr.absorb_field(b"final", &g);
let mut queries = Vec::with_capacity(p.queries);
for _ in 0..p.queries {
let i0 = tr.challenge_index(n);
let l0 = i0 % (n / 2);
let level0 = Opening { a: pd.w0[l0], b: pd.w0[l0 + n / 2], path: pd.tree0.path(l0) };
let mut levels = Vec::new();
for j in 1..p.s {
let nj = n >> j;
let l = i0 % (nj / 2);
let w = &words[j - 1];
levels.push(Opening { a: w[l], b: w[l + nj / 2], path: trees[j - 1].path(l) });
}
queries.push(QueryProof { level0, levels });
}
(v, Proof { sumcheck, roots, g, queries })
}
pub fn verify(p: &Params, root: &Digest, z: &[Fp2], v: Fp2, proof: &Proof) -> bool {
verify_detail(p, root, z, v, proof).is_ok()
}
pub fn verify_detail(p: &Params, root: &Digest, z: &[Fp2], v: Fp2, proof: &Proof) -> Result<(), &'static str> {
p.check();
if z.len() != p.m
|| proof.sumcheck.len() != p.s
|| proof.roots.len() != p.s.saturating_sub(1)
|| proof.g.len() != 1 << (p.m - p.s)
|| proof.queries.len() != p.queries
{
return Err("shape");
}
let n = p.n();
let omega = p.omega();
let inv2 = Fp::new(2).inv();
let mut tr = init_transcript(p, root, z, v);
let mut claim = v;
let mut ts = Vec::with_capacity(p.s);
for j in 0..p.s {
let h = &proof.sumcheck[j];
if h[0] + h[1] != claim {
return Err("sumcheck");
}
tr.absorb_field(b"sumcheck", h);
let t = tr.challenge_ext();
claim = interp3(h, t);
ts.push(t);
if j + 1 < p.s {
tr.absorb_bytes(b"root", &proof.roots[j]);
}
}
tr.absorb_field(b"final", &proof.g);
let prefix = ts.iter().zip(z).fold(Fp2::ONE, |acc, (&t, &zj)| acc * eq1(t, zj));
if claim != prefix * mle_eval(&proof.g, &z[p.s..]) {
return Err("closure");
}
let mut gc = proof.g.clone();
to_coefficients(p.basis, &mut gc);
for q in &proof.queries {
let i0 = tr.challenge_index(n);
if q.levels.len() != p.s.saturating_sub(1) {
return Err("query");
}
let l0 = i0 % (n / 2);
if !verify_path(root, l0, &q.level0.a, &q.level0.b, &q.level0.path) {
return Err("query");
}
if p.s == 0 {
let x = omega.pow(i0 as u64);
let val: Fp2 = if i0 < n / 2 { q.level0.a.into() } else { q.level0.b.into() };
if val != horner(&gc, x.into()) {
return Err("query");
}
continue;
}
let mut pair: (Fp2, Fp2) = (q.level0.a.into(), q.level0.b.into());
let mut l = l0;
for j in 0..p.s {
let nj = n >> j;
let x = omega.pow((l as u64) << j); let inv_2x = (x.double()).inv();
let folded = fold_pair(p.basis, pair.0, pair.1, inv_2x, ts[j], inv2);
if j + 1 < p.s {
let o = &q.levels[j];
let nn = nj / 2; let ln = l % (nn / 2);
if !verify_path(&proof.roots[j], ln, &o.a, &o.b, &o.path) {
return Err("query");
}
let expected = if l < nn / 2 { o.a } else { o.b };
if folded != expected {
return Err("query");
}
pair = (o.a, o.b);
l = ln;
} else {
let y = omega.pow((l as u64) << (j + 1)); if folded != horner(&gc, y.into()) {
return Err("query");
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn consistent_sumcheck_cheater_is_rejected() {
let mut x = 99u64;
let mut rnd = || {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
Fp::new(x)
};
for (m, s) in [(6, 3), (8, 5), (8, 8), (10, 6)] {
for basis in [Basis::Kernel, Basis::Monomial] {
let p = Params { basis, m, log_inv_rate: 2, s, queries: 20 };
let table: Vec<Fp> = (0..1 << m).map(|_| rnd()).collect();
let z: Vec<Fp2> = (0..m).map(|_| Fp2(rnd(), rnd())).collect();
let (root, pd) = commit(&p, &table);
let (v, proof) = open_inner(&p, &pd, &z, Some(Fp2::ONE));
assert_eq!(verify_detail(&p, &root, &z, v, &proof), Err("query"), "m={m} s={s}");
let (v, proof) = open(&p, &pd, &z);
assert!(verify(&p, &root, &z, v, &proof));
}
}
}
}