use p3_binary_dft::{AdditiveRsEncoder, LchNtt, NaiveAdditiveNtt};
use p3_binary_field::BinaryField128;
use p3_commit::{Encoder, Mmcs};
use p3_field::PrimeCharacteristicRing;
use p3_keccak::Keccak256Hash;
use p3_matrix::Matrix;
use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView};
use p3_merkle_tree::MerkleTreeMmcs;
use p3_multilinear_util::poly::Poly;
use p3_sumcheck::commit::{commit_base, write_stacked_message};
use p3_sumcheck::layout::{Layout, PrefixProver, SuffixProver, Table};
use p3_sumcheck::strategy::VariableOrder;
use p3_symmetric::{CompressionFunctionFromHasher, SerializingHasher};
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
type F = BinaryField128;
type MyHash = SerializingHasher<Keccak256Hash>;
type MyCompress = CompressionFunctionFromHasher<Keccak256Hash, 2, 32>;
type MyMmcs = MerkleTreeMmcs<F, u8, MyHash, MyCompress, 2, 32>;
const NUM_VARIABLES: usize = 8;
const FOLDING: usize = 2;
const LOG_INV_RATE: usize = 2;
const fn mmcs() -> MyMmcs {
MyMmcs::new(
MyHash::new(Keccak256Hash),
MyCompress::new(Keccak256Hash),
0,
)
}
fn table() -> Table<F> {
let mut rng = SmallRng::seed_from_u64(2);
Table::rand(&mut rng, 1, NUM_VARIABLES)
}
const MIXED_NUM_VARIABLES: usize = 9;
fn mixed_tables() -> Vec<Table<F>> {
let mut rng = SmallRng::seed_from_u64(3);
vec![
Table::rand(&mut rng, 1, 4),
Table::rand(&mut rng, 3, 6),
Table::rand(&mut rng, 2, 5),
]
}
fn hand_stacked(tables: &[Table<F>]) -> Vec<F> {
let mut values = Vec::with_capacity(1 << MIXED_NUM_VARIABLES);
for table in [&tables[1], &tables[2], &tables[0]] {
for column in table.iter_polys() {
values.extend_from_slice(column);
}
}
values.resize(1 << MIXED_NUM_VARIABLES, F::ZERO);
values
}
#[test]
fn commit_base_matches_hand_encoding() {
let mut rng = SmallRng::seed_from_u64(1);
let values: Vec<F> = (0..1 << NUM_VARIABLES).map(|_| rng.random()).collect();
let mmcs = mmcs();
for (num_variables, folding) in [(NUM_VARIABLES, FOLDING), (5, 0)] {
let values = &values[..1 << num_variables];
let message = RowMajorMatrixView::new(values, 1 << (num_variables - folding)).transpose();
let codeword = AdditiveRsEncoder::<F, NaiveAdditiveNtt<F>>::default()
.encode_batch(message, LOG_INV_RATE);
assert_eq!(
codeword.height(),
1 << (num_variables - folding + LOG_INV_RATE)
);
let (expected_root, _) = mmcs.commit_matrix(codeword);
let poly = Poly::new(values.to_vec());
let tower = commit_base(
&AdditiveRsEncoder::<F, LchNtt<F>>::default(),
&mmcs,
num_variables,
folding,
LOG_INV_RATE,
|message| write_stacked_message(VariableOrder::Prefix, &poly, folding, message),
);
let default = commit_base(
&AdditiveRsEncoder::<F>::default(),
&mmcs,
num_variables,
folding,
LOG_INV_RATE,
|message| write_stacked_message(VariableOrder::Prefix, &poly, folding, message),
);
for (name, root) in [("tower", tower.0), ("default", default.0)] {
assert_eq!(root, expected_root, "{name} folding={folding}");
}
}
}
#[test]
fn prefix_prover_commits_over_a_binary_field() {
let mmcs = mmcs();
let (_layout, root_fast, _data) = PrefixProver::<F, F>::commit(
&AdditiveRsEncoder::<F, LchNtt<F>>::default(),
&mmcs,
PrefixProver::<F, F>::new_witness(vec![table()], FOLDING),
FOLDING,
LOG_INV_RATE,
);
let (_layout_ref, root_ref, _data_ref) = PrefixProver::<F, F>::commit(
&AdditiveRsEncoder::<F, NaiveAdditiveNtt<F>>::default(),
&mmcs,
PrefixProver::<F, F>::new_witness(vec![table()], FOLDING),
FOLDING,
LOG_INV_RATE,
);
assert_eq!(root_fast, root_ref);
}
#[test]
fn suffix_prover_commits_the_hand_stacked_message() {
let mmcs = mmcs();
let expected_values = hand_stacked(&mixed_tables());
for folding in [0, 2, 4] {
let (_layout, root, _data) = SuffixProver::<F, F>::commit(
&AdditiveRsEncoder::<F>::default(),
&mmcs,
SuffixProver::<F, F>::new_witness(mixed_tables(), folding),
folding,
LOG_INV_RATE,
);
let (expected_root, _) = commit_base(
&AdditiveRsEncoder::<F>::default(),
&mmcs,
MIXED_NUM_VARIABLES,
folding,
LOG_INV_RATE,
|message| message.copy_from_slice(&expected_values),
);
assert_eq!(root, expected_root, "folding={folding}");
}
}
#[test]
#[ignore = "20-way naive-vs-fast binary NTT sweep; run from heavy CI"]
fn polynomial_commit_matches_naive_for_both_orders() {
let mut rng = SmallRng::seed_from_u64(19);
let values: Vec<F> = (0..1 << 8).map(|_| rng.random()).collect();
let mmcs = mmcs();
for folding in [0, 2, 4, 6] {
for rate in [1, 2, 3] {
let mut unfolded_root = None;
for order in [VariableOrder::Prefix, VariableOrder::Suffix] {
let reference_root = || {
let message = match order {
VariableOrder::Prefix => {
RowMajorMatrixView::new(&values, 1 << (8 - folding)).transpose()
}
VariableOrder::Suffix => RowMajorMatrix::new(values.clone(), 1 << folding),
};
let expected = AdditiveRsEncoder::<F, NaiveAdditiveNtt<F>>::default()
.encode_batch(message, rate);
mmcs.commit_matrix(expected).0
};
let expected_root = if folding == 0 {
unfolded_root.get_or_insert_with(reference_root).clone()
} else {
reference_root()
};
let poly = Poly::new(values.clone());
let (root, _) = commit_base(
&AdditiveRsEncoder::<F>::default(),
&mmcs,
poly.num_variables(),
folding,
rate,
|message| write_stacked_message(order, &poly, folding, message),
);
assert_eq!(root, expected_root);
}
}
}
}