dcrypt_algorithms/poly/
serialize.rs1#[cfg(feature = "alloc")]
4extern crate alloc;
5#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7
8use super::params::Modulus;
9use super::polynomial::Polynomial;
10use crate::error::{Error, Result};
11
12pub trait CoefficientPacker<M: Modulus> {
14 fn pack_coeffs(poly: &Polynomial<M>, bits_per_coeff: usize) -> Result<Vec<u8>>;
16}
17
18pub trait CoefficientUnpacker<M: Modulus> {
20 fn unpack_coeffs(bytes: &[u8], bits_per_coeff: usize) -> Result<Polynomial<M>>;
22}
23
24pub struct DefaultCoefficientSerde;
26
27impl<M: Modulus> CoefficientPacker<M> for DefaultCoefficientSerde {
28 fn pack_coeffs(poly: &Polynomial<M>, bits_per_coeff: usize) -> Result<Vec<u8>> {
29 let num_bytes = packed_length::<M>(bits_per_coeff)?;
30 let mut packed = vec![0u8; num_bytes];
31 DefaultCoefficientSerde::pack_coeffs_into(poly, bits_per_coeff, &mut packed)?;
32 Ok(packed)
33 }
34}
35
36fn packed_length<M: Modulus>(bits_per_coeff: usize) -> Result<usize> {
37 if bits_per_coeff == 0 || bits_per_coeff > 32 {
38 return Err(Error::Parameter {
39 name: "coefficient packing".into(),
40 reason: format!(
41 "bits_per_coeff must be in range [1, 32], got {}",
42 bits_per_coeff
43 )
44 .into(),
45 });
46 }
47 Ok((M::N * bits_per_coeff).div_ceil(8))
48}
49
50impl<M: Modulus> CoefficientUnpacker<M> for DefaultCoefficientSerde {
51 fn unpack_coeffs(bytes: &[u8], bits_per_coeff: usize) -> Result<Polynomial<M>> {
52 if bits_per_coeff == 0 || bits_per_coeff > 32 {
53 return Err(Error::Parameter {
54 name: "coefficient unpacking".into(),
55 reason: format!(
56 "bits_per_coeff must be in range [1, 32], got {}",
57 bits_per_coeff
58 )
59 .into(),
60 });
61 }
62
63 let n = M::N;
64 let total_bits = n * bits_per_coeff;
65 let required_bytes = total_bits.div_ceil(8); if bytes.len() < required_bytes {
68 return Err(Error::Parameter {
69 name: "coefficient unpacking".into(),
70 reason: format!(
71 "insufficient bytes: expected {}, got {}",
72 required_bytes,
73 bytes.len()
74 )
75 .into(),
76 });
77 }
78
79 let mut poly = Polynomial::<M>::zero();
80 let coeffs = poly.as_mut_coeffs_slice();
81 let mask = (1u32 << bits_per_coeff) - 1;
82
83 let mut bit_pos = 0;
84 for coeff in coeffs.iter_mut().take(n) {
86 let mut coeff_value = 0u32;
87
88 for bit in 0..bits_per_coeff {
90 let byte_idx = bit_pos / 8;
91 let bit_idx = bit_pos % 8;
92 coeff_value |= (((bytes[byte_idx] >> bit_idx) & 1) as u32) << bit;
93 bit_pos += 1;
94 }
95
96 *coeff = coeff_value & mask;
97 }
98
99 Ok(poly)
100 }
101}
102
103#[allow(clippy::manual_div_ceil)]
105pub const fn bytes_required(bits_per_coeff: usize, n: usize) -> usize {
106 (n * bits_per_coeff + 7) / 8
109}
110
111impl DefaultCoefficientSerde {
113 pub fn pack_coeffs_into<M: Modulus>(
118 poly: &Polynomial<M>,
119 bits_per_coeff: usize,
120 packed: &mut [u8],
121 ) -> Result<()> {
122 let expected = packed_length::<M>(bits_per_coeff)?;
123 if packed.len() != expected {
124 return Err(Error::Parameter {
125 name: "coefficient packing output".into(),
126 reason: format!("expected {expected} bytes, got {}", packed.len()).into(),
127 });
128 }
129 packed.fill(0);
130 let mask = if bits_per_coeff == 32 {
131 u32::MAX
132 } else {
133 (1u32 << bits_per_coeff) - 1
134 };
135 let mut bit_pos = 0;
136 for &coeff in poly.as_coeffs_slice().iter().take(M::N) {
137 let masked_coeff = coeff & mask;
138 for bit in 0..bits_per_coeff {
139 packed[bit_pos / 8] |= (((masked_coeff >> bit) & 1) as u8) << (bit_pos % 8);
140 bit_pos += 1;
141 }
142 }
143 Ok(())
144 }
145
146 pub fn pack_10bit<M: Modulus>(poly: &Polynomial<M>) -> Result<Vec<u8>> {
148 let n = M::N;
149 let mut packed = vec![0u8; (n * 10) / 8];
150 let coeffs = poly.as_coeffs_slice();
151
152 for i in (0..n).step_by(4) {
153 let c0 = coeffs[i] & 0x3FF;
154 let c1 = coeffs[i + 1] & 0x3FF;
155 let c2 = coeffs[i + 2] & 0x3FF;
156 let c3 = coeffs[i + 3] & 0x3FF;
157
158 let idx = (i * 10) / 8;
159 packed[idx] = c0 as u8;
160 packed[idx + 1] = ((c0 >> 8) | (c1 << 2)) as u8;
161 packed[idx + 2] = ((c1 >> 6) | (c2 << 4)) as u8;
162 packed[idx + 3] = ((c2 >> 4) | (c3 << 6)) as u8;
163 packed[idx + 4] = (c3 >> 2) as u8;
164 }
165
166 Ok(packed)
167 }
168
169 pub fn unpack_10bit<M: Modulus>(bytes: &[u8]) -> Result<Polynomial<M>> {
171 let n = M::N;
172 if bytes.len() < (n * 10) / 8 {
173 return Err(Error::Parameter {
174 name: "10-bit unpacking".into(),
175 reason: format!(
176 "insufficient bytes: expected {}, got {}",
177 (n * 10) / 8,
178 bytes.len()
179 )
180 .into(),
181 });
182 }
183
184 let mut poly = Polynomial::<M>::zero();
185 let coeffs = poly.as_mut_coeffs_slice();
186
187 for i in (0..n).step_by(4) {
188 let idx = (i * 10) / 8;
189 coeffs[i] = (bytes[idx] as u32) | ((bytes[idx + 1] as u32 & 0x03) << 8);
190 coeffs[i + 1] = ((bytes[idx + 1] as u32) >> 2) | ((bytes[idx + 2] as u32 & 0x0F) << 6);
191 coeffs[i + 2] = ((bytes[idx + 2] as u32) >> 4) | ((bytes[idx + 3] as u32 & 0x3F) << 4);
192 coeffs[i + 3] = ((bytes[idx + 3] as u32) >> 6) | ((bytes[idx + 4] as u32) << 2);
193 }
194
195 Ok(poly)
196 }
197
198 pub fn pack_13bit<M: Modulus>(poly: &Polynomial<M>) -> Result<Vec<u8>> {
200 let n = M::N;
201 let mut packed = vec![0u8; (n * 13) / 8];
202 let coeffs = poly.as_coeffs_slice();
203
204 for i in (0..n).step_by(8) {
205 let idx = (i * 13) / 8;
206
207 packed[idx] = coeffs[i] as u8;
209 packed[idx + 1] = ((coeffs[i] >> 8) | (coeffs[i + 1] << 5)) as u8;
210 packed[idx + 2] = (coeffs[i + 1] >> 3) as u8;
211 packed[idx + 3] = ((coeffs[i + 1] >> 11) | (coeffs[i + 2] << 2)) as u8;
212 packed[idx + 4] = ((coeffs[i + 2] >> 6) | (coeffs[i + 3] << 7)) as u8;
213 packed[idx + 5] = (coeffs[i + 3] >> 1) as u8;
214 packed[idx + 6] = ((coeffs[i + 3] >> 9) | (coeffs[i + 4] << 4)) as u8;
215 packed[idx + 7] = (coeffs[i + 4] >> 4) as u8;
216 packed[idx + 8] = ((coeffs[i + 4] >> 12) | (coeffs[i + 5] << 1)) as u8;
217 packed[idx + 9] = ((coeffs[i + 5] >> 7) | (coeffs[i + 6] << 6)) as u8;
218 packed[idx + 10] = (coeffs[i + 6] >> 2) as u8;
219 packed[idx + 11] = ((coeffs[i + 6] >> 10) | (coeffs[i + 7] << 3)) as u8;
220 packed[idx + 12] = (coeffs[i + 7] >> 5) as u8;
221 }
222
223 Ok(packed)
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use dcrypt_internal::random::{ChaCha20Rng, RngCore};
231
232 #[derive(Clone)]
233 struct TestModulus;
234 impl Modulus for TestModulus {
235 const Q: u32 = 3329;
236 const N: usize = 256;
237 }
238
239 #[test]
240 fn test_pack_unpack_roundtrip() {
241 let mut rng = ChaCha20Rng::from_seed([42u8; 32]);
242
243 for bits in [10, 12, 13, 23] {
245 let mask = (1u32 << bits) - 1;
246
247 let mut poly = Polynomial::<TestModulus>::zero();
249 for i in 0..TestModulus::N {
250 poly.coeffs[i] = rng.next_u32() & mask;
251 }
252
253 let packed = DefaultCoefficientSerde::pack_coeffs(&poly, bits).unwrap();
255 let unpacked =
256 <DefaultCoefficientSerde as CoefficientUnpacker<TestModulus>>::unpack_coeffs(
257 &packed, bits,
258 )
259 .unwrap();
260
261 for i in 0..TestModulus::N {
263 assert_eq!(
264 poly.coeffs[i], unpacked.coeffs[i],
265 "Mismatch at index {} for {} bits",
266 i, bits
267 );
268 }
269 }
270 }
271
272 #[test]
273 fn test_bytes_required() {
274 assert_eq!(bytes_required(10, 256), 320);
275 assert_eq!(bytes_required(12, 256), 384);
276 assert_eq!(bytes_required(13, 256), 416); assert_eq!(bytes_required(23, 256), 736); }
279
280 #[test]
281 fn test_optimized_10bit() {
282 let mut rng = ChaCha20Rng::from_seed([42u8; 32]);
283
284 let mut poly = Polynomial::<TestModulus>::zero();
286 for i in 0..TestModulus::N {
287 poly.coeffs[i] = rng.next_u32() & 0x3FF;
288 }
289
290 let packed_opt = DefaultCoefficientSerde::pack_10bit(&poly).unwrap();
292 let packed_gen = DefaultCoefficientSerde::pack_coeffs(&poly, 10).unwrap();
293 assert_eq!(packed_opt, packed_gen);
294
295 let unpacked_opt =
297 DefaultCoefficientSerde::unpack_10bit::<TestModulus>(&packed_opt).unwrap();
298 let unpacked_gen =
299 <DefaultCoefficientSerde as CoefficientUnpacker<TestModulus>>::unpack_coeffs(
300 &packed_gen,
301 10,
302 )
303 .unwrap();
304
305 for i in 0..TestModulus::N {
306 assert_eq!(unpacked_opt.coeffs[i], unpacked_gen.coeffs[i]);
307 assert_eq!(unpacked_opt.coeffs[i], poly.coeffs[i]);
308 }
309 }
310
311 #[test]
312 fn test_invalid_parameters() {
313 let poly = Polynomial::<TestModulus>::zero();
314
315 assert!(DefaultCoefficientSerde::pack_coeffs(&poly, 0).is_err());
317 assert!(DefaultCoefficientSerde::pack_coeffs(&poly, 33).is_err());
318
319 let short_bytes = vec![0u8; 10];
321 assert!(
322 <DefaultCoefficientSerde as CoefficientUnpacker<TestModulus>>::unpack_coeffs(
323 &short_bytes,
324 10
325 )
326 .is_err()
327 );
328 }
329}