use p3_binary_field::TowerLevel;
use p3_commit::zero_padded;
use p3_matrix::Matrix;
use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView};
use p3_util::log2_strict_usize;
pub trait AdditiveNtt<F: TowerLevel> {
fn shifted_ntt_batch(&self, mat: RowMajorMatrix<F>, shift: F) -> RowMajorMatrix<F>;
fn shifted_intt_batch(&self, mat: RowMajorMatrix<F>, shift: F) -> RowMajorMatrix<F>;
fn ntt_batch(&self, mat: RowMajorMatrix<F>) -> RowMajorMatrix<F> {
self.shifted_ntt_batch(mat, F::ZERO)
}
fn ntt_batch_padded(&self, mat: RowMajorMatrix<F>, log_inv_rate: usize) -> RowMajorMatrix<F> {
let log_n = log2_strict_usize(mat.height());
assert!(log_inv_rate <= log_n, "padding exceeds matrix height");
self.ntt_batch(mat)
}
fn ntt_batch_borrowed(
&self,
mat: RowMajorMatrixView<'_, F>,
log_inv_rate: usize,
) -> RowMajorMatrix<F> {
self.ntt_batch_padded(zero_padded(mat, log_inv_rate), log_inv_rate)
}
fn intt_batch(&self, mat: RowMajorMatrix<F>) -> RowMajorMatrix<F> {
self.shifted_intt_batch(mat, F::ZERO)
}
fn lde_batch(&self, mat: RowMajorMatrix<F>, added_bits: usize) -> RowMajorMatrix<F> {
self.shifted_lde_batch(mat, added_bits, F::ZERO)
}
fn shifted_lde_batch(
&self,
mat: RowMajorMatrix<F>,
added_bits: usize,
shift: F,
) -> RowMajorMatrix<F> {
let log_n = log2_strict_usize(mat.height());
assert!(log_n <= 1 << F::LOG_BITS, "domain exceeds field dimension");
if added_bits == 0 {
return mat;
}
let coeffs = self.shifted_intt_batch(mat, shift);
let width = coeffs.width;
let len = coeffs.values.len();
let mut values = F::zero_vec(padded_len(len, added_bits));
values[..len].copy_from_slice(&coeffs.values);
self.shifted_ntt_batch(RowMajorMatrix::new(values, width), shift)
}
}
pub(crate) fn padded_len(len: usize, added_bits: usize) -> usize {
u32::try_from(added_bits)
.ok()
.and_then(|bits| len.checked_shl(bits))
.filter(|&padded| padded >> added_bits == len)
.expect("codeword length overflows usize")
}
#[cfg(test)]
mod tests {
use alloc::vec;
use p3_binary_field::{BinaryField8, BinaryField64, TowerLevel};
use p3_field::PrimeCharacteristicRing;
use p3_matrix::dense::RowMajorMatrix;
use super::{AdditiveNtt, padded_len};
use crate::LchNtt;
#[test]
fn a_borrowed_matrix_transforms_as_its_zero_padded_copy() {
let mat = RowMajorMatrix::new(
(0..32u64)
.map(|i| BinaryField64::from_repr(i.wrapping_mul(0x9e37_79b9_7f4a_7c15)))
.collect(),
4,
);
let ntt = LchNtt::<BinaryField64>::default();
for log_inv_rate in 0..=2 {
let mut padded = mat.clone();
padded
.values
.resize(mat.values.len() << log_inv_rate, BinaryField64::ZERO);
assert_eq!(
ntt.ntt_batch_borrowed(mat.as_view(), log_inv_rate),
ntt.ntt_batch_padded(padded, log_inv_rate),
"rate={log_inv_rate}"
);
}
}
#[test]
fn identity_lde_preserves_input_allocation() {
let mat = RowMajorMatrix::new(vec![BinaryField8::ONE; 32], 4);
let ptr = mat.values.as_ptr();
let result = LchNtt::default().lde_batch(mat, 0);
assert_eq!(result.values.as_ptr(), ptr);
assert_eq!(result.values, vec![BinaryField8::ONE; 32]);
}
#[test]
#[should_panic]
fn identity_lde_rejects_invalid_height() {
let _ = LchNtt::default().lde_batch(RowMajorMatrix::new(vec![BinaryField8::ONE; 3], 1), 0);
}
#[test]
#[should_panic]
fn identity_lde_rejects_domain_above_field_dimension() {
let _ =
LchNtt::default().lde_batch(RowMajorMatrix::new(vec![BinaryField8::ONE; 512], 1), 0);
}
#[test]
fn the_padded_length_is_the_message_length_shifted() {
assert_eq!(padded_len(48, 3), 384);
assert_eq!(padded_len(48, 0), 48);
assert_eq!(padded_len(0, 60), 0);
}
#[test]
#[should_panic = "codeword length overflows usize"]
fn the_padded_length_refuses_a_shift_past_the_word_size() {
let _ = padded_len(2, usize::BITS as usize);
}
#[test]
#[should_panic = "codeword length overflows usize"]
fn the_padded_length_refuses_a_value_that_overflows() {
let _ = padded_len(1 << (usize::BITS - 1), 1);
}
}