1use alloc::vec::Vec;
4
5use p3_binary_field::TowerLevel;
6use p3_matrix::Matrix;
7use p3_matrix::dense::RowMajorMatrix;
8use p3_maybe_rayon::prelude::*;
9use p3_util::log2_strict_usize;
10
11use crate::butterfly::ButterflyField;
12use crate::encoder::padded_message_len;
13use crate::lch::transform_stages;
14
15const fn closed_layers(log_bits: usize) -> usize {
21 1 << log_bits
22}
23
24const NARROW_MESSAGE_BYTES: usize = 128 * 1024;
33
34const PARALLEL_WIDEN_BYTES: usize = 4 * 1024 * 1024;
41
42fn widen<S: TowerLevel, F: TowerLevel + From<S>>(values: &[S]) -> Vec<F> {
44 if size_of::<F>() * values.len() < PARALLEL_WIDEN_BYTES {
45 return values.iter().map(|&value| F::from(value)).collect();
46 }
47 values.par_iter().map(|&value| F::from(value)).collect()
48}
49
50fn split_ntt_batch<S, F>(mut mat: RowMajorMatrix<S>, head: usize) -> RowMajorMatrix<F>
75where
76 S: ButterflyField,
77 F: ButterflyField + From<S>,
78{
79 let width = mat.width();
80 let log_n = log2_strict_usize(mat.height());
81 assert!(log_n <= 1 << F::LOG_BITS, "domain exceeds field dimension");
82 assert!(
83 head <= closed_layers(S::LOG_BITS).min(log_n),
84 "the subfield is not closed under that many layers"
85 );
86 let low = log_n - head;
87
88 transform_stages::<S, false>(&mut mat.values, width << low, head, S::ZERO);
90
91 let mut values = widen::<S, F>(&mat.values);
92
93 transform_stages::<F, false>(&mut values, width, low, F::ZERO);
95
96 RowMajorMatrix::new(values, width)
97}
98
99#[must_use]
111pub fn subfield_ntt_batch<S, F>(mat: RowMajorMatrix<S>) -> RowMajorMatrix<F>
112where
113 S: ButterflyField,
114 F: ButterflyField + From<S>,
115{
116 let log_n = log2_strict_usize(mat.height());
117
118 let head = if size_of::<S>() * mat.values.len() >= NARROW_MESSAGE_BYTES {
121 closed_layers(S::LOG_BITS).min(log_n)
122 } else {
123 0
124 };
125 split_ntt_batch(mat, head)
126}
127
128#[must_use]
140pub fn subfield_encode_batch<S, F>(
141 mut message: RowMajorMatrix<S>,
142 log_inv_rate: usize,
143) -> RowMajorMatrix<F>
144where
145 S: ButterflyField,
146 F: ButterflyField + From<S>,
147{
148 let padded_len = padded_message_len(message.values.len(), log_inv_rate);
149 let _ = log2_strict_usize(message.height());
150
151 message.values.resize(padded_len, S::ZERO);
152 subfield_ntt_batch(message)
153}
154
155#[cfg(test)]
156mod tests {
157 use alloc::format;
158
159 use p3_binary_field::{
160 BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, Gf2, TowerLevel,
161 };
162 use p3_field::PrimeCharacteristicRing;
163 use p3_matrix::dense::RowMajorMatrix;
164 use proptest::prelude::*;
165
166 use super::{closed_layers, split_ntt_batch, subfield_encode_batch, subfield_ntt_batch, widen};
167 use crate::butterfly::ButterflyField;
168 use crate::domain::{domain_point, subspace_polynomial};
169 use crate::lch::LchNtt;
170 use crate::naive::NaiveAdditiveNtt;
171 use crate::traits::AdditiveNtt;
172
173 const WIDTHS: [usize; 4] = [1, 3, 4, 16];
175
176 fn matrix<S: TowerLevel>(log_n: usize, width: usize, seed: u64) -> RowMajorMatrix<S> {
178 RowMajorMatrix::new(
179 (0..(width << log_n))
180 .map(|i| {
181 let bits = seed
182 .wrapping_mul(0x9e37_79b9_7f4a_7c15)
183 .wrapping_add(i as u64 + 1)
184 .wrapping_mul(0xbf58_476d_1ce4_e5b9);
185 S::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
186 })
187 .collect(),
188 width,
189 )
190 }
191
192 fn check_against_the_wide_transform<S, F>(log_n: usize, width: usize, seed: u64)
194 where
195 S: ButterflyField,
196 F: ButterflyField + From<S>,
197 {
198 let message = matrix::<S>(log_n, width, seed);
199
200 let wide = RowMajorMatrix::new(widen::<S, F>(&message.values), width);
202 let expected = LchNtt::<F>::default().ntt_batch(wide);
203
204 for head in 0..=closed_layers(S::LOG_BITS).min(log_n) {
205 let label = format!("log_n={log_n} width={width} seed={seed} head={head}");
206 assert_eq!(
207 split_ntt_batch::<S, F>(message.clone(), head),
208 expected,
209 "{label}"
210 );
211 }
212
213 assert_eq!(subfield_ntt_batch::<S, F>(message), expected);
215 }
216
217 #[test]
218 fn a_byte_message_transforms_like_the_widened_one() {
219 for width in WIDTHS {
221 for log_n in 0..=10 {
222 check_against_the_wide_transform::<BinaryField8, BinaryField128>(log_n, width, 7);
223 check_against_the_wide_transform::<BinaryField8, BinaryField64>(log_n, width, 11);
224 check_against_the_wide_transform::<BinaryField8, BinaryField32>(log_n, width, 13);
225 }
226 }
227 }
228
229 #[test]
230 fn a_bit_message_transforms_like_the_widened_one() {
231 for width in WIDTHS {
233 for log_n in 0..=8 {
234 check_against_the_wide_transform::<Gf2, BinaryField128>(log_n, width, 17);
235 }
236 }
237 }
238
239 #[test]
240 fn a_two_and_four_byte_message_transforms_like_the_widened_one() {
241 for width in WIDTHS {
243 for log_n in 0..=10 {
244 check_against_the_wide_transform::<BinaryField16, BinaryField128>(log_n, width, 19);
245 check_against_the_wide_transform::<BinaryField32, BinaryField128>(log_n, width, 23);
246 }
247 }
248 }
249
250 #[test]
251 fn the_widest_subfield_is_the_alphabet_itself() {
252 for log_n in 0..=6 {
254 check_against_the_wide_transform::<BinaryField64, BinaryField64>(log_n, 3, 29);
255 }
256 }
257
258 #[test]
259 #[should_panic = "the subfield is not closed under that many layers"]
260 fn a_split_deeper_than_the_subfield_allows_is_refused() {
261 let message = matrix::<BinaryField8>(10, 1, 0);
264 let _ = split_ntt_batch::<BinaryField8, BinaryField128>(message, 9);
265 }
266
267 #[test]
268 fn a_message_below_the_narrow_threshold_runs_no_narrow_layer() {
269 let message = matrix::<BinaryField8>(6, 1, 41);
273 assert_eq!(
274 subfield_ntt_batch::<BinaryField8, BinaryField128>(message.clone()),
275 split_ntt_batch::<BinaryField8, BinaryField128>(message, 0)
276 );
277 }
278
279 fn check_against_the_oracle(log_n: usize, width: usize) {
281 let message = matrix::<BinaryField8>(log_n, width, 31);
282 let wide = RowMajorMatrix::new(widen::<_, BinaryField128>(&message.values), width);
283 let expected = NaiveAdditiveNtt::default().ntt_batch(wide);
284
285 let head = closed_layers(BinaryField8::LOG_BITS).min(log_n);
287 assert_eq!(
288 split_ntt_batch::<_, BinaryField128>(message, head),
289 expected,
290 "log_n={log_n} width={width}"
291 );
292 }
293
294 #[test]
295 fn a_byte_message_matches_the_reference_oracle() {
296 for width in [1usize, 3] {
299 for log_n in 0..=6 {
300 check_against_the_oracle(log_n, width);
301 }
302 }
303 }
304
305 #[test]
306 fn a_byte_message_past_the_closed_layers_matches_the_reference_oracle() {
307 for log_n in [9usize, 10] {
313 check_against_the_oracle(log_n, 1);
314 }
315 }
316
317 #[test]
318 fn the_closed_layer_count_is_the_subfield_bit_width() {
319 assert_eq!(closed_layers(Gf2::LOG_BITS), 1);
325 assert_eq!(closed_layers(BinaryField8::LOG_BITS), 8);
326 assert_eq!(closed_layers(BinaryField128::LOG_BITS), 128);
327 }
328
329 #[test]
330 fn the_closed_layers_really_keep_their_twiddles_in_the_subfield() {
331 const LOG_N: usize = 12;
334 let head = closed_layers(BinaryField8::LOG_BITS);
335
336 for layer in LOG_N - head..LOG_N {
337 for block in 0..1usize << (LOG_N - 1 - layer) {
339 let t = subspace_polynomial::<BinaryField128>(layer, BinaryField128::ZERO)
340 + domain_point::<BinaryField128>(block << 1);
341 assert!(
342 t.to_repr() <= u128::from(u8::MAX),
343 "layer={layer} block={block}"
344 );
345 }
346 }
347
348 let t = domain_point::<BinaryField128>(1 << (head - 1) << 1);
350 assert!(
351 t.to_repr() > u128::from(u8::MAX),
352 "the head could be one layer deeper"
353 );
354 }
355
356 #[test]
357 fn encoding_pads_the_coefficients_at_the_narrow_width() {
358 for width in WIDTHS {
360 for log_inv_rate in 0..=3 {
361 let message = matrix::<BinaryField8>(4, width, 37);
362
363 let mut padded = widen::<_, BinaryField128>(&message.values);
365 padded.resize(padded.len() << log_inv_rate, BinaryField128::ZERO);
366 let expected = LchNtt::<BinaryField128>::default()
367 .ntt_batch(RowMajorMatrix::new(padded, width));
368
369 let actual = subfield_encode_batch::<_, BinaryField128>(message, log_inv_rate);
370 assert_eq!(actual, expected, "width={width} rate={log_inv_rate}");
371 }
372 }
373 }
374
375 #[test]
376 #[should_panic = "codeword length overflows usize"]
377 fn encoding_rejects_a_codeword_length_past_the_address_space() {
378 let message = matrix::<BinaryField8>(1, 1, 0);
379 let _ = subfield_encode_batch::<_, BinaryField128>(message, usize::BITS as usize - 1);
380 }
381
382 #[test]
383 #[should_panic = "domain exceeds field dimension"]
384 fn a_domain_past_the_alphabet_dimension_is_refused() {
385 let message = matrix::<BinaryField8>(9, 1, 0);
387 let _ = subfield_ntt_batch::<BinaryField8, BinaryField8>(message);
388 }
389
390 proptest! {
391 #![proptest_config(ProptestConfig::with_cases(32))]
392
393 #[test]
395 fn random_subfield_messages_transform_like_the_widened_ones(
396 log_n in 0usize..=9,
397 width in 1usize..=5,
398 seed in any::<u64>(),
399 ) {
400 check_against_the_wide_transform::<Gf2, BinaryField128>(log_n, width, seed);
401 check_against_the_wide_transform::<BinaryField8, BinaryField128>(log_n, width, seed);
402 check_against_the_wide_transform::<BinaryField16, BinaryField64>(log_n, width, seed);
403 }
404 }
405}