use p3_binary_field::{BinaryChallenger, BinaryField128, Gf2, PackedGf2x64};
use p3_binary_pcs::{
BinaryPcsConfig, BinaryPcsParams, BooleanMultilinearPcs, BooleanPcs, GroupedCodewordMmcs,
};
use p3_challenger::HashChallenger;
use p3_commit::Mmcs;
use p3_field::PrimeCharacteristicRing;
use p3_keccak::Keccak256Hash;
use p3_merkle_tree::MerkleTreeMmcs;
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use p3_symmetric::{CompressionFunctionFromHasher, SerializingHasher};
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
type EF = BinaryField128;
type MyHash = SerializingHasher<Keccak256Hash>;
type MyCompress = CompressionFunctionFromHasher<Keccak256Hash, 2, 32>;
type MyMmcs = MerkleTreeMmcs<EF, u8, MyHash, MyCompress, 2, 32>;
type Grouped = GroupedCodewordMmcs<MyMmcs>;
type MyChallenger = BinaryChallenger<EF, HashChallenger<u8, Keccak256Hash, 32>>;
const LOG_BITS: usize = 13;
const LOG_TALL_BITS: usize = 17;
const ABSORBED: usize = 7;
const fn mmcs(cap_height: usize) -> MyMmcs {
MyMmcs::new(
MyHash::new(Keccak256Hash),
MyCompress::new(Keccak256Hash),
cap_height,
)
}
const fn challenger() -> MyChallenger {
MyChallenger::from_hasher(Vec::new(), Keccak256Hash)
}
const fn params() -> BinaryPcsParams {
BinaryPcsParams {
log_inv_rate: 2,
pow_bits: 0,
security_level: 40,
}
}
fn config(log_bits: usize, log_folding_factor: usize) -> BinaryPcsConfig {
BinaryPcsConfig::try_new_with_folding::<EF, EF>(
log_bits - ABSORBED,
params(),
log_folding_factor,
)
.unwrap()
}
fn witness(seed: u64, log_bits: usize) -> Vec<PackedGf2x64> {
let mut rng = SmallRng::seed_from_u64(seed);
(0..1 << (log_bits - 6))
.map(|_| PackedGf2x64::new(rng.random::<u64>()))
.collect()
}
fn embedded(bits: &[PackedGf2x64]) -> Poly<EF> {
Poly::new(
bits.iter()
.flat_map(|block| {
(0..PackedGf2x64::WIDTH).map(move |lane| {
if block.get(lane) == Gf2::ONE {
EF::ONE
} else {
EF::ZERO
}
})
})
.collect::<Vec<EF>>(),
)
}
fn points(seed: u64, log_bits: usize) -> Vec<Point<EF>> {
let mut rng = SmallRng::seed_from_u64(seed);
(0..2)
.map(|_| Point::<EF>::rand(&mut rng, log_bits))
.collect()
}
#[test]
fn a_boolean_opening_round_trips_at_every_cap_height() {
let bits = witness(0xCA90, LOG_BITS);
let points = points(0xCA91, LOG_BITS);
let reference = embedded(&bits);
for cap_height in [0usize, 2, 4] {
let config = config(LOG_BITS, 1);
let pcs = BooleanPcs::new(config, mmcs(cap_height), mmcs(cap_height), LOG_BITS).unwrap();
let mut prover_chal = challenger();
let (commitment, data) = pcs.commit_bits(&bits, &mut prover_chal).unwrap();
let (values, proof) = pcs.open_at_points(data, &points, &mut prover_chal).unwrap();
for (point, &value) in points.iter().zip(&values) {
assert_eq!(value, reference.eval_base(point), "cap {cap_height}");
}
let mut verifier_chal = challenger();
pcs.observe_commitment(&commitment, &mut verifier_chal);
pcs.verify_at_points(&commitment, &points, &values, &proof, &mut verifier_chal)
.unwrap_or_else(|error| panic!("cap {cap_height}: {error:?}"));
}
}
#[test]
fn a_boolean_opening_round_trips_over_grouped_leaves() {
let bits = witness(0x6700, LOG_BITS);
let points = points(0x6701, LOG_BITS);
let reference = embedded(&bits);
for log_folding_factor in [1usize, 2, 3] {
let config = config(LOG_BITS, log_folding_factor);
let pcs = BooleanPcs::new(
config,
Grouped::for_folding(mmcs(0), &config),
Grouped::for_folding(mmcs(0), &config),
LOG_BITS,
)
.unwrap();
let mut prover_chal = challenger();
let (commitment, data) = pcs.commit_bits(&bits, &mut prover_chal).unwrap();
let (values, proof) = pcs.open_at_points(data, &points, &mut prover_chal).unwrap();
for (point, &value) in points.iter().zip(&values) {
assert_eq!(
value,
reference.eval_base(point),
"arity {log_folding_factor}"
);
}
let mut verifier_chal = challenger();
pcs.observe_commitment(&commitment, &mut verifier_chal);
pcs.verify_at_points(&commitment, &points, &values, &proof, &mut verifier_chal)
.unwrap_or_else(|error| panic!("arity {log_folding_factor}: {error:?}"));
}
}
fn tall_opening<M>(config: BinaryPcsConfig, base: M, rounds: M, label: &str) -> usize
where
M: Mmcs<EF> + Clone,
M::Error: core::fmt::Debug,
MyChallenger: p3_challenger::CanObserve<M::Commitment>,
{
let bits = witness(0x5121, LOG_TALL_BITS);
let points = points(0x5122, LOG_TALL_BITS);
let reference = embedded(&bits);
let pcs = BooleanPcs::new(config, base, rounds, LOG_TALL_BITS).unwrap();
let mut chal = challenger();
let (commitment, data) = pcs.commit_bits(&bits, &mut chal).unwrap();
let (values, proof) = pcs.open_at_points(data, &points, &mut chal).unwrap();
for (point, &value) in points.iter().zip(&values) {
assert_eq!(value, reference.eval_base(point), "{label}");
}
let mut verifier_chal = challenger();
pcs.observe_commitment(&commitment, &mut verifier_chal);
pcs.verify_at_points(&commitment, &points, &values, &proof, &mut verifier_chal)
.unwrap_or_else(|error| panic!("{label}: {error:?}"));
let bytes = postcard::to_allocvec(&proof).unwrap().len();
eprintln!("boolean/merkle_layout/{label}: {bytes} bytes");
bytes
}
#[test]
fn every_merkle_layout_opens_the_same_committed_bits() {
for log_folding_factor in [1usize, 3] {
let config = config(LOG_TALL_BITS, log_folding_factor);
let baseline = tall_opening(config, mmcs(0), mmcs(0), "baseline");
let bound = baseline / 5;
let label = format!("arity {log_folding_factor}, cap 4");
let plain = tall_opening(config, mmcs(4), mmcs(4), &label);
assert!(
plain.abs_diff(baseline) < bound,
"{label}: {plain} vs {baseline}"
);
for cap_height in [0usize, 4] {
let label = format!("arity {log_folding_factor}, grouped, cap {cap_height}");
let grouped = tall_opening(
config,
Grouped::for_folding(mmcs(cap_height), &config),
Grouped::for_folding(mmcs(cap_height), &config),
&label,
);
assert!(
grouped.abs_diff(baseline) < bound,
"{label}: {grouped} vs {baseline}"
);
}
}
}