use alloc::string::String;
use alloc::{format, vec};
use p3_binary_field::BinaryField128;
use p3_field::PrimeCharacteristicRing;
use p3_keccak::Keccak256Hash;
use p3_matrix::dense::RowMajorMatrix;
use p3_multilinear_util::poly::Poly;
use p3_sumcheck::ring_switch::bits::BitRingSwitchProofError;
use p3_sumcheck::{OpeningBatch, PrescribedPointPcs, TableSpec};
use p3_symmetric::CryptographicHasher;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::evaluate::{BLOCKS_PER_TASK, packed_column_sums};
use super::gather::GATHER_GROUP;
use super::plan::{ClaimPlan, ColumnClaim, column_claims};
use super::*;
use crate::params::BinaryPcsParams;
use crate::test_util::{MyChallenger, MyMmcs, challenger, mmcs};
type EF = BinaryField128;
fn reduction_error<E: core::fmt::Debug>(
error: BooleanTraceError<EF, E>,
) -> BitRingSwitchProofError {
match error {
BooleanTraceCommitmentError::Boolean(BooleanPcsError::ReductionProof(inner)) => inner,
other => panic!("{other:?}"),
}
}
const FIXTURE_WIDTH: usize = 2;
fn table(seed: u64, log_height: usize) -> Table<EF> {
let rows = 1usize << log_height;
let mut rng = SmallRng::seed_from_u64(seed);
let cells = (0..FIXTURE_WIDTH * rows)
.map(|_| EF::from_bool(rng.random::<bool>()))
.collect();
Table::new(RowMajorMatrix::new(cells, rows))
}
fn table_with_width(seed: u64, log_height: usize, width: usize) -> Table<EF> {
let rows = 1usize << log_height;
let mut rng = SmallRng::seed_from_u64(seed);
let cells = (0..width * rows)
.map(|_| EF::from_bool(rng.random::<bool>()))
.collect();
Table::new(RowMajorMatrix::new(cells, rows))
}
fn packed_table(table: &Table<EF>) -> Table<EF> {
let height = 1usize << table.num_variables();
let words = (0..height.div_ceil(WORD_BITS))
.flat_map(|block| {
(0..table.num_polys()).map(move |column| {
(0..WORD_BITS).fold(0u64, |word, lane| {
let row = block * WORD_BITS + lane;
if row < height && table.column(column).value(row) == EF::ONE {
word | (1u64 << lane)
} else {
word
}
})
})
})
.collect();
Table::from_packed_bits(
RowMajorMatrix::new(words, table.num_polys()),
table.num_variables(),
)
}
#[test]
fn packed_column_sums_match_a_per_bit_sum() {
let blocks_past_tasks = (3 * BLOCKS_PER_TASK + 1) * WORD_BITS;
for (height, width, seed) in [
(1, 1, 1),
(4, 1, 2),
(32, 5, 3),
(64, 3, 4),
(5 * WORD_BITS, 1, 5),
(blocks_past_tasks, 7, 6),
] {
let mut rng = SmallRng::seed_from_u64(seed);
let used = if height < WORD_BITS {
(1u64 << height) - 1
} else {
u64::MAX
};
let words = (0..height.div_ceil(WORD_BITS) * width)
.map(|_| rng.random::<u64>() & used)
.collect::<Vec<_>>();
let row_weights = (0..height).map(|_| rng.random()).collect::<Vec<EF>>();
let expected = (0..width)
.map(|column| {
(0..height)
.filter(|&row| {
(words[(row / WORD_BITS) * width + column] >> (row % WORD_BITS)) & 1 == 1
})
.map(|row| row_weights[row])
.sum::<EF>()
})
.collect::<Vec<_>>();
let words = RowMajorMatrix::new(words, width);
assert_eq!(
packed_column_sums(&words, &row_weights),
expected,
"{height}x{width}"
);
}
}
#[test]
fn gathered_bits_place_every_cell_at_its_slot() {
let shapes = [
TableShape::new(7, 2 * GATHER_GROUP + 3),
TableShape::new(6, 5),
TableShape::new(4, GATHER_GROUP + 1),
TableShape::new(3, 3),
];
let dense = shapes
.iter()
.enumerate()
.map(|(seed, shape)| table_with_width(seed as u64, shape.num_variables(), shape.width()))
.collect::<Vec<_>>();
let tables = alloc::vec![
packed_table(&dense[0]),
dense[1].clone(),
packed_table(&dense[2]),
dense[3].clone(),
];
let scheme = pcs(&shapes);
let mut expected = alloc::vec![0u64; 1 << (scheme.num_variables() - 6)];
for placement in scheme.placements(&shapes).unwrap() {
let table = &dense[placement.idx()];
let column_len = 1usize << table.num_variables();
for (column, selector) in placement.selectors().iter().enumerate() {
for row in 0..column_len {
if table.column(column).value(row) == EF::ONE {
let index = selector.index() * column_len + row;
expected[index / WORD_BITS] |= 1 << (index % WORD_BITS);
}
}
}
}
let gathered = scheme.gather_bits(&tables).unwrap();
let gathered = gathered
.iter()
.map(|word| word.to_bits())
.collect::<Vec<_>>();
assert_eq!(gathered, expected);
}
#[test]
fn a_gather_refuses_the_first_non_boolean_column_in_placement_order() {
let shapes = [TableShape::new(3, 2), TableShape::new(7, 4)];
let scheme = pcs(&shapes);
let refused = |bad: &[(usize, usize)]| {
let tables = shapes
.iter()
.enumerate()
.map(|(index, shape)| {
let rows = 1 << shape.num_variables();
let table = table_with_width(index as u64, shape.num_variables(), shape.width());
let mut cells = table.iter_polys().flatten().copied().collect::<Vec<_>>();
for &(_, column) in bad.iter().filter(|&&(table, _)| table == index) {
cells[column * rows + rows / 2] = EF::GENERATOR;
}
Table::new(RowMajorMatrix::new(cells, rows))
})
.collect::<Vec<_>>();
match scheme.gather_bits(&tables) {
Err(BooleanTraceCommitmentError::NonBooleanCell { table, column }) => (table, column),
_ => panic!("a non-Boolean cell must be refused"),
}
};
assert_eq!(refused(&[(0, 1), (1, 3)]), (1, 3));
assert_eq!(refused(&[(1, 3), (1, 1)]), (1, 1));
assert_eq!(refused(&[(0, 1), (0, 0)]), (0, 0));
}
fn pcs(shapes: &[TableShape]) -> BooleanTracePcs<EF, MyMmcs, MyMmcs> {
let params = BinaryPcsParams {
log_inv_rate: 2,
pow_bits: 0,
security_level: 40,
};
let (arity, _) = plan_stacked_layout(shapes);
let config = BinaryPcsConfig::try_new::<EF, EF>(arity - 7, params).unwrap();
BooleanTracePcs::new(config, mmcs(), mmcs(), arity).unwrap()
}
fn protocol(shapes: &[TableShape]) -> OpeningProtocol {
successor_protocol(shapes, &[])
}
fn successor_protocol(shapes: &[TableShape], next: &[usize]) -> OpeningProtocol {
OpeningProtocol::new(
shapes
.iter()
.map(|shape| {
TableSpec::new(
*shape,
alloc::vec![OpeningBatch::new(
(0..shape.width()).collect(),
next.to_vec()
)],
)
})
.collect(),
)
}
fn both_views_protocol(shape: TableShape, num_batches: usize) -> OpeningProtocol {
OpeningProtocol::new(vec![TableSpec::new(
shape,
(0..num_batches)
.map(|_| OpeningBatch::new((0..shape.width()).collect(), (0..shape.width()).collect()))
.collect(),
)])
}
fn forged_successor_proof(
scheme: &BooleanTracePcs<EF, MyMmcs, MyMmcs>,
data: BooleanTraceData<EF, MyMmcs>,
shape: TableShape,
points: &[Point<EF>],
tampered: Option<usize>,
challenger: &mut MyChallenger,
) -> BooleanTraceProof<EF, MyMmcs, MyMmcs> {
type Scheme = BooleanTracePcs<EF, MyMmcs, MyMmcs>;
let BooleanTraceCommitmentData { inner, tables } = data;
let width = shape.width();
let mut values = Vec::new();
for point in points {
let (current, successor) = Scheme::evaluate_views(&tables[0], point, true);
values.extend(current);
values.extend(successor);
}
if let Some(at) = tampered {
values[at] += EF::ONE;
}
let batch_shape = ColumnBatchShape {
table_variables: shape.num_variables(),
width,
num_batches: points.len(),
next: true,
};
let mut transcript = ColumnBatchProverTranscript::new(challenger, batch_shape);
let mut openings = Vec::with_capacity(points.len());
for (point, batch_values) in points.iter().zip(values.chunks_exact(2 * width)) {
let (current, successor) = batch_values.split_at(width);
let column_point = transcript.batch(point, current, successor);
let mut lifted_point = column_point;
lifted_point.extend(point);
openings.push(BitOpening {
point: lifted_point,
row_variables: shape.num_variables(),
current: true,
next: true,
});
}
transcript.finish();
let (_, opening) = scheme
.inner
.open_readings(inner, &openings, challenger)
.unwrap();
BooleanTraceCommitmentProof { values, opening }
}
fn successor_reading(column: &[EF], point: &Point<EF>) -> EF {
let rows = column.len();
let eq = Poly::new_from_point(point.as_slice(), EF::ONE);
(0..rows)
.map(|row| eq.as_slice()[row] * column[(row + 1).min(rows - 1)])
.sum()
}
#[test]
fn an_opened_column_is_the_column_the_table_holds() {
let shapes = [
TableShape::new(10, FIXTURE_WIDTH),
TableShape::new(8, FIXTURE_WIDTH),
TableShape::new(4, FIXTURE_WIDTH),
];
let tables = alloc::vec![table(0xB100, 10), table(0xB101, 8), table(0xB103, 4)];
let scheme = pcs(&shapes);
let protocol = protocol(&shapes);
assert_eq!(scheme.num_variables(), 12);
let mut rng = SmallRng::seed_from_u64(0xB102);
let points = alloc::vec![
Point::<EF>::rand(&mut rng, 10),
Point::<EF>::rand(&mut rng, 8),
Point::<EF>::rand(&mut rng, 4),
];
let mut prover_chal = challenger();
let (commitment, data) = scheme.commit(tables.clone(), &mut prover_chal).unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let evals = scheme
.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
.unwrap();
assert_eq!(evals.len(), shapes.len());
for (index, batch) in evals.iter().enumerate() {
assert!(batch.next().is_empty());
for (column, &value) in batch.current().iter().enumerate() {
let reference = Poly::new(tables[index].poly(column).as_slice().to_vec());
assert_eq!(
value,
reference.eval_base(&points[index]),
"{index}/{column}"
);
}
}
}
#[test]
fn packed_table_round_trip_matches_dense_openings() {
for (log_height, width, seed) in [(5, 8, 0xB10A), (10, FIXTURE_WIDTH, 0xB10C)] {
let shapes = [TableShape::new(log_height, width)];
let dense = table_with_width(seed, log_height, width);
let packed = packed_table(&dense);
let scheme = pcs(&shapes);
let protocol = protocol(&shapes);
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(seed + 1), log_height);
let mut dense_challenger = challenger();
let (dense_commitment, dense_data) =
scheme.commit(vec![dense], &mut dense_challenger).unwrap();
let dense_proof = scheme
.open_at(
dense_data,
&protocol,
core::slice::from_ref(&point),
&mut dense_challenger,
)
.unwrap();
let dense_after_open = p3_challenger::CanSample::<EF>::sample(&mut dense_challenger);
let mut packed_challenger = challenger();
let (packed_commitment, packed_data) =
scheme.commit(vec![packed], &mut packed_challenger).unwrap();
let packed_proof = scheme
.open_at(
packed_data,
&protocol,
core::slice::from_ref(&point),
&mut packed_challenger,
)
.unwrap();
let packed_after_open = p3_challenger::CanSample::<EF>::sample(&mut packed_challenger);
assert_eq!(dense_commitment, packed_commitment);
assert_eq!(
postcard::to_allocvec(&dense_proof).unwrap(),
postcard::to_allocvec(&packed_proof).unwrap()
);
assert_eq!(dense_after_open, packed_after_open);
let mut verifier = challenger();
scheme.observe_commitment(&packed_commitment, &mut verifier);
scheme
.verify_at(
&packed_commitment,
&packed_proof,
&protocol,
core::slice::from_ref(&point),
&mut verifier,
)
.unwrap();
}
}
#[test]
fn packed_mixed_heights_and_widths_match_dense_proof_bytes() {
let shapes = [
TableShape::new(6, 3),
TableShape::new(4, 5),
TableShape::new(7, 2),
];
let dense = vec![
table_with_width(0xB10C, 6, 3),
table_with_width(0xB10D, 4, 5),
table_with_width(0xB10E, 7, 2),
];
let packed = dense.iter().map(packed_table).collect::<Vec<_>>();
let scheme = pcs(&shapes);
let protocol = protocol(&shapes);
let mut rng = SmallRng::seed_from_u64(0xB10F);
let points = shapes
.iter()
.map(|shape| Point::<EF>::rand(&mut rng, shape.num_variables()))
.collect::<Vec<_>>();
let mut dense_challenger = challenger();
let (dense_commitment, dense_data) = scheme.commit(dense, &mut dense_challenger).unwrap();
let dense_proof = scheme
.open_at(dense_data, &protocol, &points, &mut dense_challenger)
.unwrap();
let dense_after_open = p3_challenger::CanSample::<EF>::sample(&mut dense_challenger);
let mut packed_challenger = challenger();
let (packed_commitment, packed_data) = scheme.commit(packed, &mut packed_challenger).unwrap();
let packed_proof = scheme
.open_at(packed_data, &protocol, &points, &mut packed_challenger)
.unwrap();
let packed_after_open = p3_challenger::CanSample::<EF>::sample(&mut packed_challenger);
assert_eq!(dense_commitment, packed_commitment);
assert_eq!(
postcard::to_allocvec(&dense_proof).unwrap(),
postcard::to_allocvec(&packed_proof).unwrap()
);
assert_eq!(dense_after_open, packed_after_open);
}
#[test]
fn the_sampled_path_draws_the_same_points_on_both_sides() {
let shapes = [TableShape::new(10, FIXTURE_WIDTH)];
let scheme = pcs(&shapes);
let protocol = protocol(&shapes);
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(alloc::vec![table(0xB200, 10)], &mut prover_chal)
.unwrap();
let proof = scheme
.open(data, protocol.clone(), &mut prover_chal)
.unwrap();
let mut verifier_chal = challenger();
scheme
.verify(&commitment, &proof, &mut verifier_chal, protocol.clone())
.unwrap();
let mut double_bound = challenger();
scheme.observe_commitment(&commitment, &mut double_bound);
let refused = scheme
.verify(&commitment, &proof, &mut double_bound, protocol)
.unwrap_err();
assert_eq!(
reduction_error(refused),
BitRingSwitchProofError::ClaimMismatch
);
}
#[test]
fn a_disagreeing_shape_or_point_count_is_refused() {
let shapes = [TableShape::new(10, FIXTURE_WIDTH)];
let scheme = pcs(&shapes);
let narrow = [TableShape::new(10, 1)];
let mut chal = challenger();
let (_, data) = scheme
.commit(alloc::vec![table(0xB300, 10)], &mut chal)
.unwrap();
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB301), 10);
let Err(error) = scheme.open_at(
data,
&protocol(&narrow),
core::slice::from_ref(&point),
&mut chal,
) else {
panic!("a shape set stacking elsewhere describes another commitment")
};
let BooleanTraceCommitmentError::TableShapeMismatch {
table: table_index,
expected,
actual,
} = error
else {
panic!("the retained source shape must be checked before stacking")
};
assert_eq!(table_index, 0);
assert_eq!(expected, TableShape::new(10, 1));
assert_eq!(actual, TableShape::new(10, FIXTURE_WIDTH));
let (_, data) = scheme
.commit(alloc::vec![table(0xB300, 10)], &mut chal)
.unwrap();
let Err(error) = scheme.open_at(data, &protocol(&shapes), &[], &mut chal) else {
panic!("a batch with no point is opened nowhere")
};
assert!(matches!(
error,
BooleanTraceCommitmentError::PointCount {
expected: 1,
actual: 0
}
));
}
#[test]
fn a_proof_short_of_one_value_is_refused() {
let shapes = [TableShape::new(10, FIXTURE_WIDTH)];
let scheme = pcs(&shapes);
let protocol = protocol(&shapes);
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(alloc::vec![table(0xB400, 10)], &mut prover_chal)
.unwrap();
let points = alloc::vec![Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB401), 10)];
let mut proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
proof.values.pop();
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let Err(error) = scheme.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
else {
panic!("a proof answering for one column cannot answer for two")
};
assert!(matches!(
error,
BooleanTraceCommitmentError::ValueCount {
expected: 2,
actual: 1
}
));
}
#[test]
fn the_claim_plan_writes_every_value_position_exactly_once() {
for (width, current, next) in [
(3, vec![0, 1, 2], vec![]),
(3, vec![2, 0, 1], vec![1, 2]),
(2, vec![0, 1], vec![1, 0]),
(3, vec![], vec![2, 0]),
(4, vec![3, 1], vec![1, 3, 0]),
] {
let protocol = OpeningProtocol::new(vec![TableSpec::new(
TableShape::new(8, width),
vec![OpeningBatch::new(current.clone(), next.clone())],
)]);
let len = value_count(&protocol);
assert_eq!(len, current.len() + next.len());
assert!(
ClaimPlan::new(column_claims(&protocol), len).is_some(),
"{current:?} / {next:?}"
);
}
let protocol = OpeningProtocol::new(vec![
TableSpec::new(
TableShape::new(8, 3),
vec![
OpeningBatch::new(vec![2, 0, 1], vec![1, 2]),
OpeningBatch::new(vec![0], vec![2, 0]),
],
),
TableSpec::new(
TableShape::new(6, 2),
vec![OpeningBatch::new(vec![1, 0], vec![0])],
),
]);
let len = value_count(&protocol);
assert_eq!(len, 5 + 3 + 3);
assert!(ClaimPlan::new(column_claims(&protocol), len).is_some());
let collided = ColumnClaim {
table: 0,
column: 0,
opening: 0,
current_at: Some(0),
next_at: Some(0),
};
assert!(ClaimPlan::new(vec![collided], 1).is_none());
assert!(ClaimPlan::new(vec![], 1).is_none());
}
#[test]
fn a_single_table_batch_uses_one_reduction_for_all_columns() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(vec![0, 1, 2], Vec::new())],
)]);
let table = table_with_width(0xB500, 8, 3);
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB501), 8);
let mut prover_chal = challenger();
let (_, data) = scheme.commit(vec![table], &mut prover_chal).unwrap();
let proof = scheme
.open_at(data, &protocol, &[point], &mut prover_chal)
.unwrap();
assert_eq!(proof.values.len(), 3);
assert_eq!(proof.opening.reduction.claims.len(), 1);
}
#[test]
fn each_route_emits_the_reductions_its_plan_counts() {
let shape = TableShape::new(8, 3);
let two_tables = [shape, TableShape::new(6, 2)];
let reordered = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(vec![2, 0], Vec::new())],
)]);
for (shapes, protocol, batched, reductions, seed) in [
(vec![shape], protocol(&[shape]), true, 1, 0xB700),
(vec![shape], both_views_protocol(shape, 2), true, 2, 0xB710),
(vec![shape], reordered, false, 2, 0xB720),
(
vec![shape],
successor_protocol(&[shape], &[1]),
false,
3,
0xB730,
),
(two_tables.to_vec(), protocol(&two_tables), false, 5, 0xB740),
] {
let route = OpeningRoute::new(&protocol);
assert_eq!(
matches!(route, OpeningRoute::Batched(_)),
batched,
"{seed:#x}"
);
assert_eq!(route.num_reductions(), reductions, "{seed:#x}");
let scheme = pcs(&shapes);
let tables = shapes
.iter()
.enumerate()
.map(|(index, shape)| {
table_with_width(seed + index as u64, shape.num_variables(), shape.width())
})
.collect();
let mut rng = SmallRng::seed_from_u64(seed + 0xF);
let points = protocol
.iter_openings()
.map(|(table, _)| Point::<EF>::rand(&mut rng, shapes[table].num_variables()))
.collect::<Vec<_>>();
let mut prover_chal = challenger();
let (_, data) = scheme.commit(tables, &mut prover_chal).unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
assert_eq!(
proof.opening.reduction.claims.len(),
route.num_reductions(),
"{seed:#x}"
);
}
}
#[test]
fn batched_values_are_bound_before_the_column_point() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(vec![0, 1, 2], Vec::new())],
)]);
let table = table_with_width(0xB502, 8, 3);
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB503), 8);
let mut prover_chal = challenger();
let (commitment, data) = scheme.commit(vec![table], &mut prover_chal).unwrap();
let mut proof = scheme
.open_at(
data,
&protocol,
core::slice::from_ref(&point),
&mut prover_chal,
)
.unwrap();
proof.values[0] += EF::ONE;
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let refused = scheme
.verify_at(&commitment, &proof, &protocol, &[point], &mut verifier_chal)
.unwrap_err();
assert_eq!(
reduction_error(refused),
BitRingSwitchProofError::ClaimMismatch
);
}
#[test]
fn complete_batches_support_multiple_points_and_width_one() {
let shape = TableShape::new(8, 1);
let scheme = pcs(&[shape]);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![
OpeningBatch::new(vec![0], Vec::new()),
OpeningBatch::new(vec![0], Vec::new()),
],
)]);
let table = table_with_width(0xB504, 8, 1);
let mut rng = SmallRng::seed_from_u64(0xB505);
let points = vec![Point::<EF>::rand(&mut rng, 8), Point::rand(&mut rng, 8)];
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(vec![table.clone()], &mut prover_chal)
.unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
assert_eq!(proof.values.len(), 2);
assert_eq!(proof.opening.reduction.claims.len(), 2);
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let evals = scheme
.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
.unwrap();
for (point, eval) in points.iter().zip(evals) {
let reference = Poly::new(table.poly(0).as_slice().to_vec());
assert_eq!(eval.current()[0], reference.eval_base(point));
}
}
#[test]
fn optimized_security_charges_batches_and_column_coordinates() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![
OpeningBatch::new(vec![0, 1, 2], Vec::new()),
OpeningBatch::new(vec![0, 1, 2], Vec::new()),
],
)]);
let security = <BooleanTracePcs<EF, MyMmcs, MyMmcs> as PrescribedPointPcs<
EF,
MyChallenger,
>>::prescribed_security(&scheme, &protocol)
.unwrap();
let batching = security
.terms
.iter()
.find(|term| term.label == "column-batching")
.unwrap();
assert!((batching.bits.bits() - 126.0).abs() < 1e-9);
let ring_switch = security
.terms
.iter()
.find(|term| term.label == "bit-ring-switch")
.unwrap();
assert!(ring_switch.bits.bits().is_finite());
}
#[test]
fn optimized_security_rejects_a_protocol_with_the_wrong_stacked_arity() {
let committed_shape = TableShape::new(8, 3);
let scheme = pcs(&[committed_shape]);
let protocol_shape = TableShape::new(8, 2);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
protocol_shape,
vec![OpeningBatch::new(vec![0, 1], Vec::new())],
)]);
let security = <BooleanTracePcs<EF, MyMmcs, MyMmcs> as PrescribedPointPcs<
EF,
MyChallenger,
>>::prescribed_security(&scheme, &protocol);
assert!(security.is_none());
}
#[test]
fn non_power_two_complete_batches_round_trip_below_and_above_packing_width() {
for (width, log_height, num_batches, seed) in [
(3, 6, 2, 0xB506),
(3, 8, 2, 0xB507),
(5, 6, 1, 0xB508),
(5, 8, 1, 0xB509),
] {
let shape = TableShape::new(log_height, width);
let scheme = pcs(&[shape]);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
(0..num_batches)
.map(|_| OpeningBatch::new((0..width).collect(), Vec::new()))
.collect(),
)]);
let table = table_with_width(seed, log_height, width);
let mut rng = SmallRng::seed_from_u64(seed + 1);
let points = (0..num_batches)
.map(|_| Point::<EF>::rand(&mut rng, log_height))
.collect::<Vec<_>>();
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(vec![table.clone()], &mut prover_chal)
.unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
assert_eq!(proof.opening.reduction.claims.len(), num_batches);
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let evals = scheme
.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
.unwrap();
for (batch, point) in points.iter().enumerate() {
for column in 0..width {
let reference = Poly::new(table.poly(column).as_slice().to_vec());
assert_eq!(evals[batch].current()[column], reference.eval_base(point));
}
}
}
}
#[test]
fn optimized_proofs_reject_each_column_tampering_reduction_tampering_and_point_reordering() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![
OpeningBatch::new(vec![0, 1, 2], Vec::new()),
OpeningBatch::new(vec![0, 1, 2], Vec::new()),
],
)]);
let table = table_with_width(0xB50A, 8, 3);
let mut rng = SmallRng::seed_from_u64(0xB50B);
let points = vec![Point::<EF>::rand(&mut rng, 8), Point::rand(&mut rng, 8)];
let mut prover_chal = challenger();
let (commitment, data) = scheme.commit(vec![table], &mut prover_chal).unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
scheme
.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
.unwrap();
for index in 0..proof.values.len() {
let mut tampered = proof.clone();
tampered.values[index] += EF::ONE;
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let refused = scheme
.verify_at(
&commitment,
&tampered,
&protocol,
&points,
&mut verifier_chal,
)
.unwrap_err();
assert_eq!(
reduction_error(refused),
BitRingSwitchProofError::ClaimMismatch,
"value {index}"
);
}
let mut tampered = proof.clone();
tampered.opening.reduction.claims.pop();
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let refused = scheme
.verify_at(
&commitment,
&tampered,
&protocol,
&points,
&mut verifier_chal,
)
.unwrap_err();
assert!(
matches!(
refused,
BooleanTraceCommitmentError::Boolean(BooleanPcsError::ClaimCount {
expected: 2,
values: 2,
reductions: 1
})
),
"{refused:?}"
);
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let reordered = vec![points[1].clone(), points[0].clone()];
let refused = scheme
.verify_at(
&commitment,
&proof,
&protocol,
&reordered,
&mut verifier_chal,
)
.unwrap_err();
assert_eq!(
reduction_error(refused),
BitRingSwitchProofError::ClaimMismatch
);
}
#[test]
fn optimized_shape_errors_leave_the_prover_transcript_untouched() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(vec![0, 1, 2], vec![0])],
)]);
let mut prover_chal = challenger();
let (_, data) = scheme
.commit(vec![table_with_width(0xB50C, 8, 3)], &mut prover_chal)
.unwrap();
let mut expected = prover_chal.clone();
let short_point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB50D), 7);
let error = match scheme.open_at(data, &protocol, &[short_point], &mut prover_chal) {
Ok(_) => panic!("a point with the wrong arity must be rejected"),
Err(error) => error,
};
assert!(matches!(
error,
BooleanTraceCommitmentError::PointArity { table: 0, .. }
));
assert_eq!(
p3_challenger::CanSample::<EF>::sample(&mut prover_chal),
p3_challenger::CanSample::<EF>::sample(&mut expected),
);
let valid_protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(vec![0, 1, 2], Vec::new())],
)]);
let mut prover_chal = challenger();
let (_, data) = scheme
.commit(vec![table_with_width(0xB510, 8, 3)], &mut prover_chal)
.unwrap();
let mut expected = prover_chal.clone();
let bad_point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB511), 7);
let error = match scheme.open_at(data, &valid_protocol, &[bad_point], &mut prover_chal) {
Ok(_) => panic!("a point with the wrong arity must be rejected"),
Err(error) => error,
};
assert!(matches!(
error,
BooleanTraceCommitmentError::PointArity { table: 0, .. }
));
assert_eq!(
p3_challenger::CanSample::<EF>::sample(&mut prover_chal),
p3_challenger::CanSample::<EF>::sample(&mut expected),
);
let narrow_shape = TableShape::new(8, 2);
let narrow_protocol = OpeningProtocol::new(vec![TableSpec::new(
narrow_shape,
vec![OpeningBatch::new(vec![0, 1], Vec::new())],
)]);
let mut prover_chal = challenger();
let (_, data) = scheme
.commit(vec![table_with_width(0xB512, 8, 3)], &mut prover_chal)
.unwrap();
let mut expected = prover_chal.clone();
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB513), 8);
let error = match scheme.open_at(data, &narrow_protocol, &[point], &mut prover_chal) {
Ok(_) => panic!("a protocol with the wrong stacked arity must be rejected"),
Err(error) => error,
};
assert!(matches!(
error,
BooleanTraceCommitmentError::TableShapeMismatch { .. }
));
assert_eq!(
p3_challenger::CanSample::<EF>::sample(&mut prover_chal),
p3_challenger::CanSample::<EF>::sample(&mut expected),
);
}
#[test]
fn source_shape_errors_precede_sampling_for_open_and_open_at() {
for (committed_shapes, protocol_shapes, expected) in [
(
vec![TableShape::new(8, 3)],
vec![TableShape::new(8, 4)],
"shape",
),
(
vec![TableShape::new(8, 3)],
vec![TableShape::new(9, 2)],
"shape",
),
(
vec![TableShape::new(8, 2), TableShape::new(8, 2)],
vec![TableShape::new(8, 4)],
"count",
),
] {
let scheme = pcs(&committed_shapes);
let tables = committed_shapes
.iter()
.enumerate()
.map(|(index, shape)| {
table_with_width(0xB520 + index as u64, shape.num_variables(), shape.width())
})
.collect();
let sampled_protocol = protocol(&protocol_shapes);
let mut sampled_challenger = challenger();
let (_, data) = scheme.commit(tables, &mut sampled_challenger).unwrap();
let mut expected_challenger = sampled_challenger.clone();
let error = match scheme.open(data, sampled_protocol, &mut sampled_challenger) {
Ok(_) => panic!("a source shape mismatch must be rejected before sampling"),
Err(error) => error,
};
match expected {
"shape" => assert!(matches!(
error,
BooleanTraceCommitmentError::TableShapeMismatch { .. }
)),
"count" => assert!(matches!(
error,
BooleanTraceCommitmentError::TableCountMismatch { .. }
)),
_ => unreachable!(),
}
assert_eq!(
p3_challenger::CanSample::<EF>::sample(&mut sampled_challenger),
p3_challenger::CanSample::<EF>::sample(&mut expected_challenger),
);
let protocol = protocol(&protocol_shapes);
let points = protocol_shapes
.iter()
.map(|shape| {
Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB530), shape.num_variables())
})
.collect::<Vec<_>>();
let mut challenger = challenger();
let (_, data) = scheme
.commit(
committed_shapes
.iter()
.enumerate()
.map(|(index, shape)| {
table_with_width(
0xB540 + index as u64,
shape.num_variables(),
shape.width(),
)
})
.collect(),
&mut challenger,
)
.unwrap();
let mut expected_challenger = challenger.clone();
let error = match scheme.open_at(data, &protocol, &points, &mut challenger) {
Ok(_) => panic!("a source shape mismatch must be rejected before opening"),
Err(error) => error,
};
assert!(matches!(
(expected, error),
(
"shape",
BooleanTraceCommitmentError::TableShapeMismatch { .. }
) | (
"count",
BooleanTraceCommitmentError::TableCountMismatch { .. }
)
));
assert_eq!(
p3_challenger::CanSample::<EF>::sample(&mut challenger),
p3_challenger::CanSample::<EF>::sample(&mut expected_challenger),
);
}
}
#[test]
fn reordered_subset_batches_use_the_fallback_column_route() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(vec![2, 0], Vec::new())],
)]);
let table = table_with_width(0xB50E, 8, 3);
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB50F), 8);
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(vec![table.clone()], &mut prover_chal)
.unwrap();
let proof = scheme
.open_at(
data,
&protocol,
core::slice::from_ref(&point),
&mut prover_chal,
)
.unwrap();
assert_eq!(proof.values.len(), 2);
assert_eq!(proof.opening.reduction.claims.len(), 2);
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let evals = scheme
.verify_at(
&commitment,
&proof,
&protocol,
core::slice::from_ref(&point),
&mut verifier_chal,
)
.unwrap();
assert_eq!(
evals[0].current(),
&[
Poly::new(table.poly(2).as_slice().to_vec()).eval_base(&point),
Poly::new(table.poly(0).as_slice().to_vec()).eval_base(&point),
]
);
}
#[test]
fn complete_successor_batches_round_trip_around_the_packing_width() {
for (log_height, width, num_batches, seed) in [
(3, 32, 2, 0xB600),
(7, 8, 1, 0xB602),
(9, 4, 3, 0xB604),
(9, 1, 2, 0xB606),
(8, 3, 2, 0xB608),
] {
let shape = TableShape::new(log_height, width);
let scheme = pcs(&[shape]);
let protocol = both_views_protocol(shape, num_batches);
let table = table_with_width(seed, log_height, width);
let mut rng = SmallRng::seed_from_u64(seed + 1);
let points = (0..num_batches)
.map(|_| Point::<EF>::rand(&mut rng, log_height))
.collect::<Vec<_>>();
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(vec![table.clone()], &mut prover_chal)
.unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
assert_eq!(proof.values.len(), 2 * width * num_batches);
assert_eq!(proof.opening.reduction.claims.len(), num_batches);
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let evals = scheme
.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
.unwrap();
for (batch, point) in points.iter().enumerate() {
for column in 0..width {
let cells = table.poly(column);
let reference = Poly::new(cells.as_slice().to_vec());
assert_eq!(
evals[batch].current()[column],
reference.eval_base(point),
"{batch}/{column}"
);
assert_eq!(
evals[batch].next()[column],
successor_reading(cells.as_slice(), point),
"{batch}/{column} next"
);
}
}
}
}
#[test]
fn packed_successor_tables_match_dense_proof_bytes() {
for (log_height, width, seed) in [(5, 8, 0xB60A), (10, FIXTURE_WIDTH, 0xB60C)] {
let shape = TableShape::new(log_height, width);
let dense = table_with_width(seed, log_height, width);
let packed = packed_table(&dense);
let scheme = pcs(&[shape]);
let protocol = both_views_protocol(shape, 1);
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(seed + 1), log_height);
let mut dense_challenger = challenger();
let (dense_commitment, dense_data) =
scheme.commit(vec![dense], &mut dense_challenger).unwrap();
let dense_proof = scheme
.open_at(
dense_data,
&protocol,
core::slice::from_ref(&point),
&mut dense_challenger,
)
.unwrap();
let dense_after_open = p3_challenger::CanSample::<EF>::sample(&mut dense_challenger);
let mut packed_challenger = challenger();
let (packed_commitment, packed_data) =
scheme.commit(vec![packed], &mut packed_challenger).unwrap();
let packed_proof = scheme
.open_at(
packed_data,
&protocol,
core::slice::from_ref(&point),
&mut packed_challenger,
)
.unwrap();
let packed_after_open = p3_challenger::CanSample::<EF>::sample(&mut packed_challenger);
assert_eq!(dense_commitment, packed_commitment);
assert_eq!(
postcard::to_allocvec(&dense_proof).unwrap(),
postcard::to_allocvec(&packed_proof).unwrap()
);
assert_eq!(dense_after_open, packed_after_open);
let mut verifier = challenger();
scheme.observe_commitment(&packed_commitment, &mut verifier);
scheme
.verify_at(
&packed_commitment,
&packed_proof,
&protocol,
core::slice::from_ref(&point),
&mut verifier,
)
.unwrap();
}
}
#[test]
fn partial_successor_views_use_the_fallback_column_route() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let table = table_with_width(0xB60E, 8, 3);
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB60F), 8);
for (current, next, claims) in [
(vec![0, 1, 2], vec![1], 3),
(vec![0], vec![1], 2),
(vec![0, 1], vec![1], 2),
(vec![2, 0], vec![0], 2),
] {
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(current.clone(), next.clone())],
)]);
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(vec![table.clone()], &mut prover_chal)
.unwrap();
let proof = scheme
.open_at(
data,
&protocol,
core::slice::from_ref(&point),
&mut prover_chal,
)
.unwrap();
assert_eq!(proof.values.len(), current.len() + next.len());
assert_eq!(proof.opening.reduction.claims.len(), claims);
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let evals = scheme
.verify_at(
&commitment,
&proof,
&protocol,
core::slice::from_ref(&point),
&mut verifier_chal,
)
.unwrap();
for (&column, &value) in current.iter().zip(evals[0].current()) {
let reference = Poly::new(table.poly(column).as_slice().to_vec());
assert_eq!(value, reference.eval_base(&point), "current {column}");
}
for (&column, &value) in next.iter().zip(evals[0].next()) {
let cells = table.poly(column);
assert_eq!(
value,
successor_reading(cells.as_slice(), &point),
"next {column}"
);
}
}
}
#[test]
fn reordered_successor_views_keep_each_side_in_its_own_order() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let table = table_with_width(0xB618, 8, 3);
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB619), 8);
for (current, next, claims) in [(vec![0, 1], vec![1, 0], 2), (vec![2, 0, 1], vec![1, 2], 3)] {
let protocol = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(current.clone(), next.clone())],
)]);
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(vec![table.clone()], &mut prover_chal)
.unwrap();
let proof = scheme
.open_at(
data,
&protocol,
core::slice::from_ref(&point),
&mut prover_chal,
)
.unwrap();
assert_eq!(proof.values.len(), current.len() + next.len());
assert_eq!(proof.opening.reduction.claims.len(), claims);
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let evals = scheme
.verify_at(
&commitment,
&proof,
&protocol,
core::slice::from_ref(&point),
&mut verifier_chal,
)
.unwrap();
let current_reference = current
.iter()
.map(|&column| Poly::new(table.poly(column).as_slice().to_vec()).eval_base(&point))
.collect::<Vec<_>>();
let next_reference = next
.iter()
.map(|&column| successor_reading(table.poly(column).as_slice(), &point))
.collect::<Vec<_>>();
assert_eq!(evals[0].current(), current_reference, "current {current:?}");
assert_eq!(evals[0].next(), next_reference, "next {next:?}");
}
}
#[test]
fn a_second_table_is_read_one_row_ahead_at_its_own_point() {
let shapes = [
TableShape::new(10, FIXTURE_WIDTH),
TableShape::new(8, FIXTURE_WIDTH),
];
let tables = vec![table(0xB610, 10), table(0xB611, 8)];
let scheme = pcs(&shapes);
let protocol = successor_protocol(&shapes, &[1]);
let mut rng = SmallRng::seed_from_u64(0xB612);
let points = vec![
Point::<EF>::rand(&mut rng, 10),
Point::<EF>::rand(&mut rng, 8),
];
let mut prover_chal = challenger();
let (commitment, data) = scheme.commit(tables.clone(), &mut prover_chal).unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let evals = scheme
.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
.unwrap();
for (index, batch) in evals.iter().enumerate() {
for (column, &value) in batch.current().iter().enumerate() {
let reference = Poly::new(tables[index].poly(column).as_slice().to_vec());
assert_eq!(
value,
reference.eval_base(&points[index]),
"{index}/{column}"
);
}
let cells = tables[index].poly(1);
assert_eq!(
batch.next(),
&[successor_reading(cells.as_slice(), &points[index])],
"{index} next"
);
}
}
#[test]
fn a_tampered_successor_value_is_rejected_on_either_route() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let table = table_with_width(0xB614, 8, 3);
let point = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xB615), 8);
for (per_column, protocol) in [
(false, both_views_protocol(shape, 1)),
(
true,
OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(vec![0, 1, 2], vec![1])],
)]),
),
] {
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(vec![table.clone()], &mut prover_chal)
.unwrap();
let proof = scheme
.open_at(
data,
&protocol,
core::slice::from_ref(&point),
&mut prover_chal,
)
.unwrap();
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
scheme
.verify_at(
&commitment,
&proof,
&protocol,
core::slice::from_ref(&point),
&mut verifier_chal,
)
.unwrap();
for index in 0..proof.values.len() {
let mut tampered = proof.clone();
tampered.values[index] += EF::ONE;
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
let refused = scheme
.verify_at(
&commitment,
&tampered,
&protocol,
core::slice::from_ref(&point),
&mut verifier_chal,
)
.unwrap_err();
let expected = if per_column && index >= shape.width() {
BitRingSwitchProofError::SuccessorClaimMismatch
} else {
BitRingSwitchProofError::ClaimMismatch
};
assert_eq!(reduction_error(refused), expected, "value {index}");
}
}
}
#[test]
fn successor_security_charges_both_views_of_every_batch() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let security = <BooleanTracePcs<EF, MyMmcs, MyMmcs> as PrescribedPointPcs<
EF,
MyChallenger,
>>::prescribed_security(&scheme, &both_views_protocol(shape, 2))
.expect("a successor view is answered, so it is priced");
let bits = |label: &str| {
security
.terms
.iter()
.find(|term| term.label == label)
.unwrap_or_else(|| panic!("missing {label}"))
.bits
.bits()
};
assert!((bits("column-batching") - 125.0).abs() < 1e-9);
assert!(bits("bit-ring-switch").is_finite());
let fallback = OpeningProtocol::new(vec![TableSpec::new(
shape,
vec![OpeningBatch::new(vec![0, 1, 2], vec![1])],
)]);
let security = <BooleanTracePcs<EF, MyMmcs, MyMmcs> as PrescribedPointPcs<
EF,
MyChallenger,
>>::prescribed_security(&scheme, &fallback)
.expect("a per-column successor protocol is priced");
assert!(
security
.terms
.iter()
.all(|term| term.label != "column-batching")
);
}
fn pinned_digest<T: Serialize>(value: &T) -> String {
let bytes = postcard::to_allocvec(value).expect("postcard serialization must not fail");
Keccak256Hash
.hash_iter(bytes)
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}
fn term_bits(security: &PrescribedOpeningSecurity, label: &str) -> f64 {
security
.terms
.iter()
.find(|term| term.label == label)
.unwrap_or_else(|| panic!("missing {label}"))
.bits
.bits()
}
#[test]
fn opening_routes_and_their_security_are_pinned() {
{
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let protocol = both_views_protocol(shape, 2);
let table = table_with_width(0xB900, 8, 3);
let mut rng = SmallRng::seed_from_u64(0xB901);
let points = vec![Point::<EF>::rand(&mut rng, 8), Point::rand(&mut rng, 8)];
let mut prover_chal = challenger();
let (commitment, data) = scheme.commit(vec![table], &mut prover_chal).unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
let after_open: EF = prover_chal.sample_algebra_element();
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
scheme
.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
.unwrap();
let after_verify: EF = verifier_chal.sample_algebra_element();
assert_eq!(after_open, after_verify);
assert_eq!(
pinned_digest(&(&proof, after_open)),
"95bb155239e73df6f128fb1eac4a8971107886a49fb194ef27c2ba01bc35962e"
);
let security = <BooleanTracePcs<EF, MyMmcs, MyMmcs> as PrescribedPointPcs<
EF,
MyChallenger,
>>::prescribed_security(&scheme, &protocol)
.unwrap();
assert_eq!(security.terms.len(), 4);
assert!((term_bits(&security, "column-batching") - 125.0).abs() < 1e-9);
assert!((term_bits(&security, "binary-pcs-opening") - 121.97763218697155).abs() < 1e-9);
assert!((term_bits(&security, "bit-ring-switch") - 124.09310940439148).abs() < 1e-9);
assert!((term_bits(&security, "bit-ring-switch-claim-batching") - 128.0).abs() < 1e-9);
assert!((security.log2_max_candidates - 0.0).abs() < 1e-9);
}
{
let shapes = [TableShape::new(10, 2)];
let scheme = pcs(&shapes);
let protocol = protocol(&shapes);
let table = table_with_width(0xB910, 10, 2);
let mut prover_chal = challenger();
let (commitment, data) = scheme.commit(vec![table], &mut prover_chal).unwrap();
let proof = scheme
.open(data, protocol.clone(), &mut prover_chal)
.unwrap();
let after_open: EF = prover_chal.sample_algebra_element();
let mut verifier_chal = challenger();
scheme
.verify(&commitment, &proof, &mut verifier_chal, protocol.clone())
.unwrap();
let after_verify: EF = verifier_chal.sample_algebra_element();
assert_eq!(after_open, after_verify);
assert_eq!(
pinned_digest(&(&proof, after_open)),
"f53d7dbf97b73103bc07fc16aa2051e00e51b1edccabe4aea741275bcfb58394"
);
let security = <BooleanTracePcs<EF, MyMmcs, MyMmcs> as PrescribedPointPcs<
EF,
MyChallenger,
>>::prescribed_security(&scheme, &protocol)
.unwrap();
assert_eq!(security.terms.len(), 3);
assert!((term_bits(&security, "column-batching") - 128.0).abs() < 1e-9);
assert!((term_bits(&security, "binary-pcs-opening") - 120.95560588064154).abs() < 1e-9);
assert!((term_bits(&security, "bit-ring-switch") - 124.09310940439148).abs() < 1e-9);
assert!((security.log2_max_candidates - 0.0).abs() < 1e-9);
}
{
let shapes = [
TableShape::new(6, 3),
TableShape::new(4, 5),
TableShape::new(7, 2),
];
let scheme = pcs(&shapes);
let protocol = OpeningProtocol::new(vec![
TableSpec::new(shapes[0], vec![OpeningBatch::new(vec![2, 0], vec![1])]),
TableSpec::new(
shapes[1],
vec![OpeningBatch::new(
(0..shapes[1].width()).collect(),
Vec::new(),
)],
),
TableSpec::new(
shapes[2],
vec![
OpeningBatch::new(vec![1], vec![0, 1]),
OpeningBatch::new(Vec::new(), vec![1]),
],
),
]);
let tables = vec![
table_with_width(0xB920, 6, 3),
table_with_width(0xB921, 4, 5),
table_with_width(0xB922, 7, 2),
];
let mut rng = SmallRng::seed_from_u64(0xB923);
let points = vec![
Point::<EF>::rand(&mut rng, 6),
Point::<EF>::rand(&mut rng, 4),
Point::<EF>::rand(&mut rng, 7),
Point::<EF>::rand(&mut rng, 7),
];
let mut prover_chal = challenger();
let (commitment, data) = scheme.commit(tables, &mut prover_chal).unwrap();
let proof = scheme
.open_at(data, &protocol, &points, &mut prover_chal)
.unwrap();
let after_open: EF = prover_chal.sample_algebra_element();
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
scheme
.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
.unwrap();
let after_verify: EF = verifier_chal.sample_algebra_element();
assert_eq!(after_open, after_verify);
assert_eq!(
pinned_digest(&(&proof, after_open)),
"7bff6b57739b0e69f24e7ece7dc4ced96212c9a1f0503262547f8d309a9484b5"
);
let security = <BooleanTracePcs<EF, MyMmcs, MyMmcs> as PrescribedPointPcs<
EF,
MyChallenger,
>>::prescribed_security(&scheme, &protocol)
.unwrap();
assert_eq!(security.terms.len(), 3);
assert!((term_bits(&security, "binary-pcs-opening") - 121.97763218697155).abs() < 1e-9);
assert!((term_bits(&security, "bit-ring-switch") - 124.2995602818589).abs() < 1e-9);
assert!(
(term_bits(&security, "bit-ring-switch-claim-batching") - 124.67807190511263).abs()
< 1e-9
);
assert!((security.log2_max_candidates - 0.0).abs() < 1e-9);
}
}
#[test]
fn a_forged_next_value_is_rejected_at_every_position() {
let shape = TableShape::new(8, 3);
let scheme = pcs(&[shape]);
let protocol = both_views_protocol(shape, 2);
let mut rng = SmallRng::seed_from_u64(0xB617);
let points = vec![Point::<EF>::rand(&mut rng, 8), Point::rand(&mut rng, 8)];
let forge = |tampered: Option<usize>| {
let mut prover_chal = challenger();
let (commitment, data) = scheme
.commit(vec![table_with_width(0xB616, 8, 3)], &mut prover_chal)
.unwrap();
let proof =
forged_successor_proof(&scheme, data, shape, &points, tampered, &mut prover_chal);
let mut verifier_chal = challenger();
scheme.observe_commitment(&commitment, &mut verifier_chal);
scheme.verify_at(&commitment, &proof, &protocol, &points, &mut verifier_chal)
};
forge(None).unwrap();
for batch in 0..points.len() {
for column in 0..shape.width() {
let at = batch * 2 * shape.width() + shape.width() + column;
assert_eq!(
reduction_error(forge(Some(at)).unwrap_err()),
BitRingSwitchProofError::SuccessorClaimMismatch,
"next value {at}"
);
}
}
}