use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView, RowMajorMatrixViewMut};
use crate::butterfly::ButterflyField;
use crate::encoder::padded_message_len;
use crate::lch::transform_cosets;
#[must_use]
pub fn interleaved_encode_batch<F: ButterflyField>(
columns: &[F],
log_message: usize,
log_inv_rate: usize,
) -> RowMajorMatrix<F> {
assert!(!columns.is_empty(), "at least one message column");
assert!(
log_message + log_inv_rate <= 1 << F::LOG_BITS,
"domain exceeds field dimension"
);
let rows = u32::try_from(log_message)
.ok()
.and_then(|bits| 1usize.checked_shl(bits))
.expect("message dimension overflows usize");
assert_eq!(columns.len() % rows, 0, "whole message columns");
let width = columns.len() / rows;
let len = columns.len();
let mut values = F::zero_vec(padded_message_len(len, log_inv_rate));
let source = RowMajorMatrixView::new(columns, rows);
let mut target = RowMajorMatrixViewMut::new(&mut values[..len], width);
source.transpose_into(&mut target);
transform_cosets::<F>(&mut values, width, log_message);
RowMajorMatrix::new(values, width)
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::vec::Vec;
use p3_binary_field::{BinaryField16, BinaryField32, BinaryField128, Ghash128, TowerLevel};
use p3_field::PrimeCharacteristicRing;
use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView, RowMajorMatrixViewMut};
use proptest::prelude::*;
use super::interleaved_encode_batch;
use crate::butterfly::ButterflyField;
use crate::lch::LchNtt;
use crate::naive::NaiveAdditiveNtt;
use crate::traits::AdditiveNtt;
const WIDTHS: [usize; 4] = [1, 3, 8, 16];
fn columns<F: TowerLevel>(log_message: usize, width: usize, seed: u64) -> Vec<F> {
(0..(width << log_message))
.map(|i| {
let bits = seed
.wrapping_mul(0x9e37_79b9_7f4a_7c15)
.wrapping_add(i as u64 + 1)
.wrapping_mul(0xbf58_476d_1ce4_e5b9);
F::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
})
.collect()
}
fn transpose_then_encode<F: ButterflyField, N: AdditiveNtt<F>>(
ntt: &N,
columns: &[F],
log_message: usize,
log_inv_rate: usize,
) -> RowMajorMatrix<F> {
let rows = 1usize << log_message;
let width = columns.len() / rows;
let mut values = F::zero_vec(columns.len() << log_inv_rate);
if width != 0 {
let source = RowMajorMatrixView::new(columns, rows);
let mut target = RowMajorMatrixViewMut::new(&mut values[..columns.len()], width);
source.transpose_into(&mut target);
}
ntt.ntt_batch(RowMajorMatrix::new(values, width))
}
fn check_against_the_two_pass<F: ButterflyField>(
log_message: usize,
width: usize,
log_inv_rate: usize,
seed: u64,
) {
let columns = columns::<F>(log_message, width, seed);
let expected =
transpose_then_encode(&LchNtt::<F>::default(), &columns, log_message, log_inv_rate);
let actual = interleaved_encode_batch::<F>(&columns, log_message, log_inv_rate);
assert_eq!(
actual, expected,
"log_message={log_message} width={width} rate={log_inv_rate}"
);
}
#[test]
fn the_fused_encoder_matches_the_two_pass_one() {
for width in WIDTHS {
for log_message in 0..=6 {
for log_inv_rate in 0..=3 {
check_against_the_two_pass::<BinaryField32>(
log_message,
width,
log_inv_rate,
7,
);
check_against_the_two_pass::<BinaryField128>(
log_message,
width,
log_inv_rate,
11,
);
check_against_the_two_pass::<Ghash128>(log_message, width, log_inv_rate, 13);
}
}
}
}
#[test]
fn the_fused_encoder_matches_the_reference_oracle() {
for width in [1usize, 3] {
for log_message in 0..=4 {
for log_inv_rate in 0..=2 {
let columns = columns::<BinaryField16>(log_message, width, 17);
let expected = transpose_then_encode(
&NaiveAdditiveNtt::<BinaryField16>::default(),
&columns,
log_message,
log_inv_rate,
);
let actual = interleaved_encode_batch::<BinaryField16>(
&columns,
log_message,
log_inv_rate,
);
let label = format!("log_message={log_message} width={width}");
assert_eq!(actual, expected, "{label} rate={log_inv_rate}");
}
}
}
}
#[test]
fn a_column_reappears_as_the_codeword_column() {
const LOG_MESSAGE: usize = 5;
const WIDTH: usize = 4;
let columns = columns::<BinaryField128>(LOG_MESSAGE, WIDTH, 23);
let codeword = interleaved_encode_batch::<BinaryField128>(&columns, LOG_MESSAGE, 1);
for (j, column) in columns
.as_chunks::<{ 1 << LOG_MESSAGE }>()
.0
.iter()
.enumerate()
{
let mut padded = column.to_vec();
padded.resize(padded.len() * 2, BinaryField128::ZERO);
let alone =
LchNtt::<BinaryField128>::default().ntt_batch(RowMajorMatrix::new(padded, 1));
for (i, value) in alone.values.iter().enumerate() {
assert_eq!(codeword.values[i * WIDTH + j], *value, "column={j} row={i}");
}
}
}
#[test]
#[should_panic = "at least one message column"]
fn a_message_with_no_column_is_refused() {
let _ = interleaved_encode_batch::<BinaryField32>(&[], 3, 1);
}
#[test]
#[should_panic = "message dimension overflows usize"]
fn a_message_dimension_past_the_address_space_is_refused() {
let values = columns::<BinaryField128>(1, 1, 0);
let _ = interleaved_encode_batch::<BinaryField128>(&values, 100, 1);
}
#[test]
#[should_panic = "whole message columns"]
fn a_partial_column_is_refused() {
let values = columns::<BinaryField32>(2, 3, 0);
let _ = interleaved_encode_batch::<BinaryField32>(&values[..9], 2, 0);
}
#[test]
#[should_panic = "domain exceeds field dimension"]
fn a_domain_past_the_level_dimension_is_refused() {
let values = columns::<BinaryField16>(15, 1, 0);
let _ = interleaved_encode_batch::<BinaryField16>(&values, 15, 2);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn random_shapes_match_the_two_pass_encoder(
log_message in 0usize..=7,
width in 1usize..=5,
log_inv_rate in 0usize..=2,
seed in any::<u64>(),
) {
check_against_the_two_pass::<BinaryField128>(log_message, width, log_inv_rate, seed);
check_against_the_two_pass::<Ghash128>(log_message, width, log_inv_rate, seed);
}
}
}