1use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView, RowMajorMatrixViewMut};
4
5use crate::butterfly::ButterflyField;
6use crate::encoder::padded_message_len;
7use crate::lch::transform_cosets;
8
9#[must_use]
41pub fn interleaved_encode_batch<F: ButterflyField>(
42 columns: &[F],
43 log_message: usize,
44 log_inv_rate: usize,
45) -> RowMajorMatrix<F> {
46 assert!(!columns.is_empty(), "at least one message column");
48 assert!(
49 log_message + log_inv_rate <= 1 << F::LOG_BITS,
50 "domain exceeds field dimension"
51 );
52
53 let rows = u32::try_from(log_message)
56 .ok()
57 .and_then(|bits| 1usize.checked_shl(bits))
58 .expect("message dimension overflows usize");
59 assert_eq!(columns.len() % rows, 0, "whole message columns");
60
61 let width = columns.len() / rows;
62 let len = columns.len();
63 let mut values = F::zero_vec(padded_message_len(len, log_inv_rate));
64
65 let source = RowMajorMatrixView::new(columns, rows);
67 let mut target = RowMajorMatrixViewMut::new(&mut values[..len], width);
68 source.transpose_into(&mut target);
69
70 transform_cosets::<F>(&mut values, width, log_message);
72
73 RowMajorMatrix::new(values, width)
74}
75
76#[cfg(test)]
77mod tests {
78 use alloc::format;
79 use alloc::vec::Vec;
80
81 use p3_binary_field::{BinaryField16, BinaryField32, BinaryField128, Ghash128, TowerLevel};
82 use p3_field::PrimeCharacteristicRing;
83 use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView, RowMajorMatrixViewMut};
84 use proptest::prelude::*;
85
86 use super::interleaved_encode_batch;
87 use crate::butterfly::ButterflyField;
88 use crate::lch::LchNtt;
89 use crate::naive::NaiveAdditiveNtt;
90 use crate::traits::AdditiveNtt;
91
92 const WIDTHS: [usize; 4] = [1, 3, 8, 16];
94
95 fn columns<F: TowerLevel>(log_message: usize, width: usize, seed: u64) -> Vec<F> {
97 (0..(width << log_message))
98 .map(|i| {
99 let bits = seed
100 .wrapping_mul(0x9e37_79b9_7f4a_7c15)
101 .wrapping_add(i as u64 + 1)
102 .wrapping_mul(0xbf58_476d_1ce4_e5b9);
103 F::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
104 })
105 .collect()
106 }
107
108 fn transpose_then_encode<F: ButterflyField, N: AdditiveNtt<F>>(
110 ntt: &N,
111 columns: &[F],
112 log_message: usize,
113 log_inv_rate: usize,
114 ) -> RowMajorMatrix<F> {
115 let rows = 1usize << log_message;
116 let width = columns.len() / rows;
117
118 let mut values = F::zero_vec(columns.len() << log_inv_rate);
119 if width != 0 {
120 let source = RowMajorMatrixView::new(columns, rows);
121 let mut target = RowMajorMatrixViewMut::new(&mut values[..columns.len()], width);
122 source.transpose_into(&mut target);
123 }
124 ntt.ntt_batch(RowMajorMatrix::new(values, width))
125 }
126
127 fn check_against_the_two_pass<F: ButterflyField>(
129 log_message: usize,
130 width: usize,
131 log_inv_rate: usize,
132 seed: u64,
133 ) {
134 let columns = columns::<F>(log_message, width, seed);
135 let expected =
136 transpose_then_encode(&LchNtt::<F>::default(), &columns, log_message, log_inv_rate);
137 let actual = interleaved_encode_batch::<F>(&columns, log_message, log_inv_rate);
138 assert_eq!(
139 actual, expected,
140 "log_message={log_message} width={width} rate={log_inv_rate}"
141 );
142 }
143
144 #[test]
145 fn the_fused_encoder_matches_the_two_pass_one() {
146 for width in WIDTHS {
148 for log_message in 0..=6 {
149 for log_inv_rate in 0..=3 {
150 check_against_the_two_pass::<BinaryField32>(
151 log_message,
152 width,
153 log_inv_rate,
154 7,
155 );
156 check_against_the_two_pass::<BinaryField128>(
157 log_message,
158 width,
159 log_inv_rate,
160 11,
161 );
162 check_against_the_two_pass::<Ghash128>(log_message, width, log_inv_rate, 13);
163 }
164 }
165 }
166 }
167
168 #[test]
169 fn the_fused_encoder_matches_the_reference_oracle() {
170 for width in [1usize, 3] {
173 for log_message in 0..=4 {
174 for log_inv_rate in 0..=2 {
175 let columns = columns::<BinaryField16>(log_message, width, 17);
176 let expected = transpose_then_encode(
177 &NaiveAdditiveNtt::<BinaryField16>::default(),
178 &columns,
179 log_message,
180 log_inv_rate,
181 );
182 let actual = interleaved_encode_batch::<BinaryField16>(
183 &columns,
184 log_message,
185 log_inv_rate,
186 );
187 let label = format!("log_message={log_message} width={width}");
188 assert_eq!(actual, expected, "{label} rate={log_inv_rate}");
189 }
190 }
191 }
192 }
193
194 #[test]
195 fn a_column_reappears_as_the_codeword_column() {
196 const LOG_MESSAGE: usize = 5;
203 const WIDTH: usize = 4;
204 let columns = columns::<BinaryField128>(LOG_MESSAGE, WIDTH, 23);
205 let codeword = interleaved_encode_batch::<BinaryField128>(&columns, LOG_MESSAGE, 1);
206
207 for (j, column) in columns
208 .as_chunks::<{ 1 << LOG_MESSAGE }>()
209 .0
210 .iter()
211 .enumerate()
212 {
213 let mut padded = column.to_vec();
215 padded.resize(padded.len() * 2, BinaryField128::ZERO);
216 let alone =
217 LchNtt::<BinaryField128>::default().ntt_batch(RowMajorMatrix::new(padded, 1));
218
219 for (i, value) in alone.values.iter().enumerate() {
220 assert_eq!(codeword.values[i * WIDTH + j], *value, "column={j} row={i}");
221 }
222 }
223 }
224
225 #[test]
226 #[should_panic = "at least one message column"]
227 fn a_message_with_no_column_is_refused() {
228 let _ = interleaved_encode_batch::<BinaryField32>(&[], 3, 1);
231 }
232
233 #[test]
234 #[should_panic = "message dimension overflows usize"]
235 fn a_message_dimension_past_the_address_space_is_refused() {
236 let values = columns::<BinaryField128>(1, 1, 0);
239 let _ = interleaved_encode_batch::<BinaryField128>(&values, 100, 1);
240 }
241
242 #[test]
243 #[should_panic = "whole message columns"]
244 fn a_partial_column_is_refused() {
245 let values = columns::<BinaryField32>(2, 3, 0);
247 let _ = interleaved_encode_batch::<BinaryField32>(&values[..9], 2, 0);
248 }
249
250 #[test]
251 #[should_panic = "domain exceeds field dimension"]
252 fn a_domain_past_the_level_dimension_is_refused() {
253 let values = columns::<BinaryField16>(15, 1, 0);
255 let _ = interleaved_encode_batch::<BinaryField16>(&values, 15, 2);
256 }
257
258 proptest! {
259 #![proptest_config(ProptestConfig::with_cases(32))]
260
261 #[test]
263 fn random_shapes_match_the_two_pass_encoder(
264 log_message in 0usize..=7,
265 width in 1usize..=5,
266 log_inv_rate in 0usize..=2,
267 seed in any::<u64>(),
268 ) {
269 check_against_the_two_pass::<BinaryField128>(log_message, width, log_inv_rate, seed);
270 check_against_the_two_pass::<Ghash128>(log_message, width, log_inv_rate, seed);
271 }
272 }
273}