use core::marker::PhantomData;
use p3_binary_field::BinaryField128;
use p3_commit::Encoder;
use p3_field::PrimeCharacteristicRing;
use p3_matrix::Matrix;
use p3_matrix::dense::RowMajorMatrix;
use p3_maybe_rayon::prelude::*;
use p3_util::log2_strict_usize;
use crate::domain::domain_point;
use crate::poly::PolyBasisNtt;
use crate::traits::AdditiveNtt;
#[derive(Clone, Debug, Default)]
pub struct AdditiveRsEncoder<F, Ntt = PolyBasisNtt> {
ntt: Ntt,
_marker: PhantomData<F>,
}
impl<Ntt: AdditiveNtt<BinaryField128> + Sync> Encoder<BinaryField128>
for AdditiveRsEncoder<BinaryField128, Ntt>
{
fn encode_batch(
&self,
message: RowMajorMatrix<BinaryField128>,
log_inv_rate: usize,
) -> RowMajorMatrix<BinaryField128> {
if log_inv_rate == 0 {
return self.ntt.ntt_batch(message);
}
let width = message.width();
let len = message.values.len();
let padded_len = u32::try_from(log_inv_rate)
.ok()
.and_then(|rate| len.checked_shl(rate))
.filter(|&padded| padded >> log_inv_rate == len)
.expect("codeword length overflows usize");
let log_message_height = log2_strict_usize(message.height());
let mut values = BinaryField128::zero_vec(padded_len);
values
.par_chunks_mut(len)
.enumerate()
.for_each(|(c, chunk)| {
let shift = domain_point::<BinaryField128>(c << log_message_height);
let coset = self
.ntt
.shifted_ntt_batch(RowMajorMatrix::new(message.values.clone(), width), shift);
chunk.copy_from_slice(&coset.values);
});
RowMajorMatrix::new(values, width)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use p3_binary_field::{BinaryField128, TowerLevel};
use p3_commit::Encoder;
use p3_field::PrimeCharacteristicRing;
use p3_matrix::Matrix;
use p3_matrix::dense::RowMajorMatrix;
use proptest::prelude::*;
use rand::SeedableRng;
use rand::rngs::SmallRng;
use super::AdditiveRsEncoder;
use crate::naive::NaiveAdditiveNtt;
use crate::traits::AdditiveNtt;
type F = BinaryField128;
fn matrix(log_n: usize, width: usize, seed: u64) -> RowMajorMatrix<F> {
RowMajorMatrix::new(
(0..(width << log_n))
.map(|i| {
let bits = seed
.wrapping_mul(0x9e37_79b9_7f4a_7c15)
.wrapping_add(i as u64);
F::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
})
.collect(),
width,
)
}
#[test]
fn encodes_by_padding_and_transforming() {
let mut rng = SmallRng::seed_from_u64(1);
let message = RowMajorMatrix::<F>::rand(&mut rng, 1 << 5, 3);
let mut padded = message.clone();
padded.values.resize(message.values.len() * 4, F::ZERO);
let expected = NaiveAdditiveNtt::<F>::default().ntt_batch(padded);
let encoded = AdditiveRsEncoder::<F>::default().encode_batch(message, 2);
assert_eq!(encoded.height(), 1 << 7);
assert_eq!(encoded, expected);
}
#[test]
fn codeword_prefix_is_the_message_transform() {
let mut rng = SmallRng::seed_from_u64(2);
let message = RowMajorMatrix::<F>::rand(&mut rng, 1 << 5, 2);
let encoded = AdditiveRsEncoder::<F>::default().encode_batch(message.clone(), 1);
let direct = AdditiveRsEncoder::<F>::default().encode_batch(message, 0);
assert_eq!(&encoded.values[..direct.values.len()], &direct.values[..]);
}
#[test]
#[should_panic = "codeword length overflows usize"]
fn encode_batch_panics_when_the_codeword_length_overflows() {
let message = RowMajorMatrix::new(vec![F::ZERO; 2], 1);
let _ = AdditiveRsEncoder::<F>::default().encode_batch(message, usize::BITS as usize - 1);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn encode_batch_matches_padding_and_transforming(
log_n in 0usize..=6,
log_inv_rate in 0usize..=3,
width in 1usize..=4,
seed in any::<u64>(),
) {
let message = matrix(log_n, width, seed);
let mut padded = message.clone();
padded
.values
.resize(message.values.len() << log_inv_rate, F::ZERO);
let expected = NaiveAdditiveNtt::<F>::default().ntt_batch(padded);
let encoded = AdditiveRsEncoder::<F>::default().encode_batch(message, log_inv_rate);
prop_assert_eq!(encoded, expected);
}
}
}