1use nc_polynomial::{PolynomialError, RingContext, RingElem};
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum CodecError {
35 MessageTooLong {
37 message_bits: usize,
38 capacity_bits: usize,
39 },
40 DecodeLengthTooLong {
42 requested_bits: usize,
43 capacity_bits: usize,
44 },
45 InvalidCompressionBits(u8),
47 CompressedLengthMismatch { expected: usize, actual: usize },
49 Polynomial(PolynomialError),
51}
52
53impl core::fmt::Display for CodecError {
54 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55 match self {
56 Self::MessageTooLong {
57 message_bits,
58 capacity_bits,
59 } => write!(
60 f,
61 "message requires {message_bits} bits but ring capacity is {capacity_bits} bits"
62 ),
63 Self::DecodeLengthTooLong {
64 requested_bits,
65 capacity_bits,
66 } => write!(
67 f,
68 "decode requested {requested_bits} bits but ring capacity is {capacity_bits} bits"
69 ),
70 Self::InvalidCompressionBits(bits) => write!(
71 f,
72 "invalid compression width {bits}, expected a value in 1..=16"
73 ),
74 Self::CompressedLengthMismatch { expected, actual } => write!(
75 f,
76 "compressed coefficient length mismatch: expected {expected}, got {actual}"
77 ),
78 Self::Polynomial(err) => write!(f, "polynomial operation failed: {err}"),
79 }
80 }
81}
82
83impl std::error::Error for CodecError {}
84
85impl From<PolynomialError> for CodecError {
86 fn from(value: PolynomialError) -> Self {
87 Self::Polynomial(value)
88 }
89}
90
91pub fn encode_message_scaled_bits_example(
95 ctx: &RingContext,
96 message: &[u8],
97) -> Result<RingElem, CodecError> {
98 let capacity_bits = ctx.max_degree();
101 let message_bits = message.len().saturating_mul(8);
102 if message_bits > capacity_bits {
103 return Err(CodecError::MessageTooLong {
104 message_bits,
105 capacity_bits,
106 });
107 }
108
109 let mut coeffs = vec![0_u64; ctx.max_degree() + 1];
111 let one_value = (ctx.modulus() + 1) / 2;
113
114 for (byte_idx, &byte) in message.iter().enumerate() {
118 for bit_idx in 0..8 {
119 if (byte >> bit_idx) & 1 == 1 {
120 coeffs[byte_idx * 8 + bit_idx] = one_value;
121 }
122 }
123 }
124
125 Ok(ctx.element(&coeffs)?)
127}
128
129pub fn decode_message_scaled_bits_example(
133 element: &RingElem,
134 output_len_bytes: usize,
135) -> Result<Vec<u8>, CodecError> {
136 let capacity_bits = element.params().max_degree();
138 let requested_bits = output_len_bytes.saturating_mul(8);
139 if requested_bits > capacity_bits {
140 return Err(CodecError::DecodeLengthTooLong {
141 requested_bits,
142 capacity_bits,
143 });
144 }
145
146 let q = element.params().modulus();
147 let lower = q / 4;
150 let upper = (3 * q) / 4;
151
152 let mut out = vec![0_u8; output_len_bytes];
153 for bit_index in 0..requested_bits {
154 let coeff = element.coefficients()[bit_index];
155 let bit = coeff >= lower && coeff < upper;
156 if bit {
157 out[bit_index / 8] |= 1_u8 << (bit_index % 8);
159 }
160 }
161
162 Ok(out)
163}
164
165pub fn compress_coefficient_example(value: u64, modulus: u64, bits: u8) -> Result<u16, CodecError> {
167 validate_compression_bits(bits)?;
168
169 let levels = 1_u128 << bits;
171 let q = modulus as u128;
172 let v = (value % modulus) as u128;
173
174 let compressed = ((v * levels + (q / 2)) / q) % levels;
176 Ok(compressed as u16)
177}
178
179pub fn decompress_coefficient_example(
181 compressed: u16,
182 modulus: u64,
183 bits: u8,
184) -> Result<u64, CodecError> {
185 validate_compression_bits(bits)?;
186
187 let levels = 1_u128 << bits;
188 let q = modulus as u128;
189 let c = (compressed as u128) % levels;
190
191 let decompressed = (c * q + (levels / 2)) / levels;
193 Ok((decompressed % q) as u64)
194}
195
196pub fn compress_ring_elem_example(element: &RingElem, bits: u8) -> Result<Vec<u16>, CodecError> {
198 validate_compression_bits(bits)?;
199
200 let q = element.params().modulus();
201 let mut packed = Vec::with_capacity(element.coefficients().len());
203 for &coeff in element.coefficients() {
204 packed.push(compress_coefficient_example(coeff, q, bits)?);
205 }
206 Ok(packed)
207}
208
209pub fn decompress_ring_elem_example(
211 ctx: &RingContext,
212 packed: &[u16],
213 bits: u8,
214) -> Result<RingElem, CodecError> {
215 validate_compression_bits(bits)?;
216
217 let expected = ctx.max_degree() + 1;
219 if packed.len() != expected {
220 return Err(CodecError::CompressedLengthMismatch {
221 expected,
222 actual: packed.len(),
223 });
224 }
225
226 let mut coeffs = vec![0_u64; expected];
228 for (index, &coeff) in packed.iter().enumerate() {
229 coeffs[index] = decompress_coefficient_example(coeff, ctx.modulus(), bits)?;
230 }
231
232 Ok(ctx.element(&coeffs)?)
233}
234
235fn validate_compression_bits(bits: u8) -> Result<(), CodecError> {
236 if !(1..=16).contains(&bits) {
237 return Err(CodecError::InvalidCompressionBits(bits));
238 }
239 Ok(())
240}
241
242#[cfg(test)]
243mod tests {
244 use super::{
245 CodecError, compress_coefficient_example, compress_ring_elem_example,
246 decode_message_scaled_bits_example, decompress_coefficient_example,
247 decompress_ring_elem_example, encode_message_scaled_bits_example,
248 };
249 use nc_polynomial::RingContext;
250
251 fn ctx() -> RingContext {
252 let max_degree = 32;
253 let mut modulus_poly = vec![0_u64; max_degree + 1];
254 modulus_poly[0] = 1;
255 modulus_poly[max_degree] = 1;
256
257 RingContext::from_parts(max_degree, 998_244_353, &modulus_poly, 3)
258 .expect("context should build")
259 }
260
261 #[test]
262 fn message_scaled_bits_round_trip() {
263 let ctx = ctx();
264 let message = [0xA5_u8, 0x5A, 0xC3, 0x3C];
265
266 let encoded =
268 encode_message_scaled_bits_example(&ctx, &message).expect("encoding should work");
269 let decoded = decode_message_scaled_bits_example(&encoded, message.len())
270 .expect("decoding should work");
271
272 assert_eq!(decoded, message);
273 }
274
275 #[test]
276 fn message_encoding_rejects_oversized_payload() {
277 let ctx = ctx();
278 let oversized = [0_u8; 5]; let err = encode_message_scaled_bits_example(&ctx, &oversized).expect_err("expected error");
281 assert_eq!(
282 err,
283 CodecError::MessageTooLong {
284 message_bits: 40,
285 capacity_bits: 32,
286 }
287 );
288 }
289
290 #[test]
291 fn message_decoding_rejects_oversized_request() {
292 let ctx = ctx();
293 let encoded = encode_message_scaled_bits_example(&ctx, &[0xAA, 0x55, 0x11, 0xEE])
294 .expect("encoding should work");
295
296 let err = decode_message_scaled_bits_example(&encoded, 5).expect_err("expected error");
297 assert_eq!(
298 err,
299 CodecError::DecodeLengthTooLong {
300 requested_bits: 40,
301 capacity_bits: 32,
302 }
303 );
304 }
305
306 #[test]
307 fn coefficient_compression_round_trips_with_bounded_error() {
308 let modulus = 998_244_353_u64;
309 let bits = 10_u8;
310 let tolerance = modulus / (1_u64 << bits);
312
313 for value in [
314 0_u64,
315 1,
316 2,
317 17,
318 31_337,
319 modulus / 3,
320 modulus - 2,
321 modulus - 1,
322 ] {
323 let compressed =
324 compress_coefficient_example(value, modulus, bits).expect("compress should work");
325 let decompressed = decompress_coefficient_example(compressed, modulus, bits)
326 .expect("decompress should work");
327
328 let direct_distance = value.abs_diff(decompressed);
330 let wraparound_distance = modulus - direct_distance;
331 let distance = direct_distance.min(wraparound_distance);
332 assert!(distance <= tolerance + 1);
333 }
334 }
335
336 #[test]
337 fn ring_elem_compression_round_trip_shape_and_domain() {
338 let ctx = ctx();
339 let elem = ctx
340 .element(&[
341 1, 17, 999, 1_337, 7, 5, 3, 2, 11, 13, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,
342 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
343 ])
344 .expect("element should build");
345
346 let packed = compress_ring_elem_example(&elem, 11).expect("compression should work");
348 assert_eq!(packed.len(), ctx.max_degree() + 1);
349
350 let decoded =
352 decompress_ring_elem_example(&ctx, &packed, 11).expect("decompression should work");
353 assert_eq!(decoded.coefficients().len(), ctx.max_degree() + 1);
354 assert!(decoded.coefficients().iter().all(|&c| c < ctx.modulus()));
355 }
356
357 #[test]
358 fn compression_rejects_invalid_bit_width() {
359 let err = compress_coefficient_example(5, 17, 0).expect_err("expected error");
360 assert_eq!(err, CodecError::InvalidCompressionBits(0));
361
362 let err = decompress_coefficient_example(5, 17, 17).expect_err("expected error");
363 assert_eq!(err, CodecError::InvalidCompressionBits(17));
364 }
365}