1use core::marker::PhantomData;
4
5use p3_binary_field::{
6 BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, Poly64, TowerLevel,
7};
8use p3_commit::Encoder;
9use p3_matrix::Matrix;
10use p3_matrix::dense::RowMajorMatrix;
11use p3_util::log2_strict_usize;
12
13use crate::lch::LchNtt;
14use crate::poly::PolyBasisNtt;
15use crate::traits::AdditiveNtt;
16
17pub(crate) fn padded_message_len(len: usize, log_inv_rate: usize) -> usize {
23 u32::try_from(log_inv_rate)
26 .ok()
27 .and_then(|rate| len.checked_shl(rate))
28 .filter(|&padded| padded >> log_inv_rate == len)
29 .expect("codeword length overflows usize")
30}
31
32#[derive(Clone, Debug, Default)]
40pub struct AdditiveRsEncoder<F, Ntt = PolyBasisNtt> {
41 ntt: Ntt,
42 _marker: PhantomData<F>,
43}
44
45impl<F, Ntt> AdditiveRsEncoder<F, Ntt> {
46 pub const fn new(ntt: Ntt) -> Self {
48 Self {
49 ntt,
50 _marker: PhantomData,
51 }
52 }
53}
54
55pub trait EncodableLevel: TowerLevel {
60 type Encoder: Encoder<Self> + Default + Sync;
62}
63
64impl EncodableLevel for BinaryField128 {
67 type Encoder = AdditiveRsEncoder<Self, PolyBasisNtt>;
68}
69
70impl EncodableLevel for BinaryField64 {
71 type Encoder = AdditiveRsEncoder<Self, LchNtt<Self>>;
72}
73
74impl EncodableLevel for Poly64 {
75 type Encoder = AdditiveRsEncoder<Self, LchNtt<Self>>;
76}
77
78impl EncodableLevel for BinaryField32 {
79 type Encoder = AdditiveRsEncoder<Self, LchNtt<Self>>;
80}
81
82impl EncodableLevel for BinaryField16 {
83 type Encoder = AdditiveRsEncoder<Self, LchNtt<Self>>;
84}
85
86impl EncodableLevel for BinaryField8 {
87 type Encoder = AdditiveRsEncoder<Self, LchNtt<Self>>;
88}
89
90fn encode_by_padding<F, Ntt>(
98 ntt: &Ntt,
99 mut message: RowMajorMatrix<F>,
100 log_inv_rate: usize,
101) -> RowMajorMatrix<F>
102where
103 F: TowerLevel,
104 Ntt: AdditiveNtt<F> + Sync,
105{
106 if log_inv_rate == 0 {
107 return ntt.ntt_batch(message);
108 }
109
110 let padded_len = padded_message_len(message.values.len(), log_inv_rate);
112 let _ = log2_strict_usize(message.height());
113 message.values.resize(padded_len, F::ZERO);
114 ntt.ntt_batch_padded(message, log_inv_rate)
115}
116
117macro_rules! impl_additive_rs_encoder {
122 ($($field:ty),* $(,)?) => {$(
123 impl<Ntt: AdditiveNtt<$field> + Sync> Encoder<$field> for AdditiveRsEncoder<$field, Ntt> {
124 fn encode_batch(
125 &self,
126 message: RowMajorMatrix<$field>,
127 log_inv_rate: usize,
128 ) -> RowMajorMatrix<$field> {
129 encode_by_padding(&self.ntt, message, log_inv_rate)
130 }
131
132 fn encode_batch_padded(
133 &self,
134 message: RowMajorMatrix<$field>,
135 log_inv_rate: usize,
136 ) -> RowMajorMatrix<$field> {
137 self.ntt.ntt_batch_padded(message, log_inv_rate)
138 }
139 }
140 )*};
141}
142
143impl_additive_rs_encoder!(
145 BinaryField128,
146 BinaryField64,
147 Poly64,
148 BinaryField32,
149 BinaryField16,
150 BinaryField8,
151);
152
153#[cfg(test)]
154mod tests {
155 use alloc::vec;
156
157 use p3_binary_field::{BinaryField128, TowerLevel};
158 use p3_commit::Encoder;
159 use p3_field::PrimeCharacteristicRing;
160 use p3_matrix::Matrix;
161 use p3_matrix::dense::RowMajorMatrix;
162 use proptest::prelude::*;
163 use rand::SeedableRng;
164 use rand::rngs::SmallRng;
165
166 use super::{AdditiveRsEncoder, padded_message_len};
167 use crate::naive::NaiveAdditiveNtt;
168 use crate::traits::AdditiveNtt;
169
170 type F = BinaryField128;
171
172 fn matrix(log_n: usize, width: usize, seed: u64) -> RowMajorMatrix<F> {
174 RowMajorMatrix::new(
175 (0..(width << log_n))
176 .map(|i| {
177 let bits = seed
178 .wrapping_mul(0x9e37_79b9_7f4a_7c15)
179 .wrapping_add(i as u64);
180 F::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
181 })
182 .collect(),
183 width,
184 )
185 }
186
187 #[test]
189 fn encodes_by_padding_and_transforming() {
190 let mut rng = SmallRng::seed_from_u64(1);
191 let message = RowMajorMatrix::<F>::rand(&mut rng, 1 << 5, 3);
192
193 let mut padded = message.clone();
194 padded.values.resize(message.values.len() * 4, F::ZERO);
195 let expected = NaiveAdditiveNtt::<F>::default().ntt_batch(padded);
196
197 let encoded = AdditiveRsEncoder::<F>::default().encode_batch(message, 2);
198 assert_eq!(encoded.height(), 1 << 7);
199 assert_eq!(encoded, expected);
200 }
201
202 #[test]
205 fn codeword_prefix_is_the_message_transform() {
206 let mut rng = SmallRng::seed_from_u64(2);
207 let message = RowMajorMatrix::<F>::rand(&mut rng, 1 << 5, 2);
208
209 let encoded = AdditiveRsEncoder::<F>::default().encode_batch(message.clone(), 1);
210 let direct = AdditiveRsEncoder::<F>::default().encode_batch(message, 0);
211 assert_eq!(&encoded.values[..direct.values.len()], &direct.values[..]);
212 }
213
214 #[test]
215 #[should_panic = "codeword length overflows usize"]
216 fn encode_batch_panics_when_the_codeword_length_overflows() {
217 let message = RowMajorMatrix::new(vec![F::ZERO; 2], 1);
218 let _ = AdditiveRsEncoder::<F>::default().encode_batch(message, usize::BITS as usize - 1);
219 }
220
221 #[test]
222 fn padded_encoding_matches_naive() {
223 for width in [1, 4, 16, 64] {
224 for rate in [0, 1, 2, 3] {
225 let mut mat = matrix(4, width, 13);
226 mat.values.resize(mat.values.len() << rate, F::ZERO);
227 let expected = NaiveAdditiveNtt::default().ntt_batch(mat.clone());
228 assert_eq!(
229 AdditiveRsEncoder::<F>::default().encode_batch_padded(mat, rate),
230 expected
231 );
232 }
233 }
234 }
235
236 #[test]
237 #[should_panic = "padding exceeds matrix height"]
238 fn padded_encoding_rejects_excessive_padding() {
239 let _ = AdditiveRsEncoder::<F>::default().encode_batch_padded(matrix(2, 4, 0), 3);
240 }
241
242 #[test]
243 #[should_panic]
244 fn padded_encoding_rejects_non_power_of_two_height() {
245 let mat = RowMajorMatrix::new(vec![F::ZERO; 12], 4);
246 let _ = AdditiveRsEncoder::<F>::default().encode_batch_padded(mat, 1);
247 }
248
249 #[test]
250 fn the_padded_length_is_the_message_length_shifted() {
251 assert_eq!(padded_message_len(48, 3), 384);
253
254 assert_eq!(padded_message_len(48, 0), 48);
256
257 assert_eq!(padded_message_len(0, 60), 0);
259 }
260
261 #[test]
262 #[should_panic = "codeword length overflows usize"]
263 fn the_padded_length_refuses_a_shift_past_the_word_size() {
264 let _ = padded_message_len(2, usize::BITS as usize);
266 }
267
268 #[test]
269 #[should_panic = "codeword length overflows usize"]
270 fn the_padded_length_refuses_a_value_that_overflows() {
271 let _ = padded_message_len(1 << (usize::BITS - 1), 1);
275 }
276
277 proptest! {
278 #![proptest_config(ProptestConfig::with_cases(32))]
279
280 #[test]
284 fn encode_batch_matches_padding_and_transforming(
285 log_n in 0usize..=6,
286 log_inv_rate in 0usize..=3,
287 width in 1usize..=4,
288 seed in any::<u64>(),
289 ) {
290 let message = matrix(log_n, width, seed);
291
292 let mut padded = message.clone();
293 padded
294 .values
295 .resize(message.values.len() << log_inv_rate, F::ZERO);
296 let expected = NaiveAdditiveNtt::<F>::default().ntt_batch(padded);
297
298 let encoded = AdditiveRsEncoder::<F>::default().encode_batch(message, log_inv_rate);
299 prop_assert_eq!(encoded, expected);
300 }
301 }
302}