use p3_binary_field::{BinaryChallenger, BinaryField8, BinaryField128};
use p3_binary_pcs::{BinaryPcs, BinaryPcsConfig, BinaryPcsParams};
use p3_challenger::HashChallenger;
use p3_commit::MultilinearPcs;
use p3_keccak::Keccak256Hash;
use p3_matrix::dense::RowMajorMatrix;
use p3_merkle_tree::MerkleTreeMmcs;
use p3_sumcheck::layout::{Layout, SuffixProver, Table};
use p3_sumcheck::univariate_skip::{
BinaryZerocheck, Conjunction, ZerocheckClaim, ZerocheckError, embed_bits,
};
use p3_sumcheck::{OpeningBatch, OpeningProtocol, PrescribedPointPcs, TableShape, TableSpec};
use p3_symmetric::{CompressionFunctionFromHasher, SerializingHasher};
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
type A = BinaryField8;
type F = BinaryField128;
type MyHash = SerializingHasher<Keccak256Hash>;
type MyCompress = CompressionFunctionFromHasher<Keccak256Hash, 2, 32>;
type MyMmcs = MerkleTreeMmcs<F, u8, MyHash, MyCompress, 2, 32>;
type MyChallenger = BinaryChallenger<F, HashChallenger<u8, Keccak256Hash, 32>>;
type MyPcs = BinaryPcs<F, F, MyMmcs, MyMmcs>;
const LOG_SKIP: usize = 6;
const LOG_HEIGHT: usize = 10;
const ARITY: usize = 3;
const LOG_STACKED: usize = LOG_HEIGHT + 2;
type Operands = [Vec<u8>; ARITY];
#[derive(Debug)]
struct Closing {
opened: Vec<F>,
claim: ZerocheckClaim<F>,
}
impl Closing {
fn discharge(&self) -> Result<(), ZerocheckError> {
self.claim.discharge(&self.opened)
}
}
const fn mmcs() -> MyMmcs {
MyMmcs::new(
MyHash::new(Keccak256Hash),
MyCompress::new(Keccak256Hash),
0,
)
}
const fn challenger() -> MyChallenger {
MyChallenger::from_hasher(Vec::new(), Keccak256Hash)
}
fn pcs() -> MyPcs {
BinaryPcs::new(
BinaryPcsConfig::try_new::<F, F>(
LOG_STACKED,
BinaryPcsParams {
log_inv_rate: 2,
pow_bits: 0,
security_level: 40,
},
)
.unwrap(),
mmcs(),
mmcs(),
)
.unwrap()
}
fn protocol() -> OpeningProtocol {
OpeningProtocol::new(vec![TableSpec::new(
TableShape::new(LOG_HEIGHT, ARITY),
vec![OpeningBatch::new((0..ARITY).collect(), vec![])],
)])
}
fn witness(seed: u64, num_rows: usize, row_bytes: usize) -> Operands {
let mut rng = SmallRng::seed_from_u64(seed);
let a = (0..num_rows * row_bytes)
.map(|_| rng.random::<u8>())
.collect::<Vec<_>>();
let b = (0..num_rows * row_bytes)
.map(|_| rng.random::<u8>())
.collect::<Vec<_>>();
let c = a.iter().zip(&b).map(|(&x, &y)| x & y).collect::<Vec<_>>();
[a, b, c]
}
fn table(operands: &Operands) -> Table<F> {
let evals = operands
.iter()
.flat_map(|packed| embed_bits::<F>(packed).into_evals())
.collect::<Vec<_>>();
Table::new(RowMajorMatrix::new(evals, 1 << LOG_HEIGHT))
}
fn run(
committed: &Operands,
proven: &Operands,
check: &BinaryZerocheck<A, Conjunction>,
) -> Result<Closing, ZerocheckError> {
let scheme = pcs();
let protocol = protocol();
let mut prover_challenger = challenger();
let (commitment, prover_data) = scheme
.commit(
SuffixProver::<F, F>::new_witness(vec![table(committed)], 0),
&mut prover_challenger,
)
.unwrap();
let packed = [
proven[0].as_slice(),
proven[1].as_slice(),
proven[2].as_slice(),
];
let (proof, prover_claim) = check.prove::<F, _>(&packed, LOG_HEIGHT, &mut prover_challenger);
let opening = scheme
.open_at(
prover_data,
&protocol,
core::slice::from_ref(&prover_claim.point),
&mut prover_challenger,
)
.unwrap();
let mut verifier_challenger = challenger();
scheme.observe_commitment(&commitment, &mut verifier_challenger);
let claim = check.verify::<F, _>(&proof, LOG_HEIGHT, &mut verifier_challenger)?;
assert_eq!(claim.point, prover_claim.point);
let opened = scheme
.verify_at(
&commitment,
&opening,
&protocol,
core::slice::from_ref(&claim.point),
&mut verifier_challenger,
)
.unwrap();
Ok(Closing {
opened: opened[0].current().to_vec(),
claim,
})
}
#[test]
fn the_commitment_discharges_the_claim_the_zerocheck_leaves() {
let check = BinaryZerocheck::<A, _>::new(LOG_SKIP, Conjunction, 0).unwrap();
let num_rows = 1 << (LOG_HEIGHT - LOG_SKIP);
let operands = witness(0xC0117, num_rows, check.round().row_bytes());
let closing = run(&operands, &operands, &check).unwrap();
assert_eq!(closing.opened.len(), ARITY);
closing.discharge().unwrap();
}
#[test]
fn grinding_carries_through_the_committed_chain() {
let check = BinaryZerocheck::<A, _>::new(LOG_SKIP, Conjunction, 4).unwrap();
let num_rows = 1 << (LOG_HEIGHT - LOG_SKIP);
let operands = witness(0x6D1, num_rows, check.round().row_bytes());
let closing = run(&operands, &operands, &check).unwrap();
closing.discharge().unwrap();
}
#[test]
fn a_broken_constraint_is_refused_before_any_opening() {
let check = BinaryZerocheck::<A, _>::new(LOG_SKIP, Conjunction, 0).unwrap();
let num_rows = 1 << (LOG_HEIGHT - LOG_SKIP);
let mut operands = witness(0xBAD, num_rows, check.round().row_bytes());
operands[2][0] ^= 1;
assert_eq!(
run(&operands, &operands, &check).unwrap_err(),
ZerocheckError::BlendConstraintMismatch
);
}
#[test]
fn a_claim_about_another_witness_does_not_open_against_this_commitment() {
let check = BinaryZerocheck::<A, _>::new(LOG_SKIP, Conjunction, 0).unwrap();
let num_rows = 1 << (LOG_HEIGHT - LOG_SKIP);
let row_bytes = check.round().row_bytes();
let committed = witness(0x1, num_rows, row_bytes);
let proven = witness(0x2, num_rows, row_bytes);
let closing = run(&committed, &proven, &check).unwrap();
assert_eq!(
closing.discharge().unwrap_err(),
ZerocheckError::OpeningsDoNotMatchClaim
);
}