quantica/ml_dsa/encode.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4// Index-based loops in this module deliberately mirror the reference
5// algorithm (parallel-array access `a[i]`/`b[i]`, or a position-indexed
6// packing/reduction state) and keep the constant-time data layout explicit;
7// the `iter().enumerate()` rewrite would obscure it. Documented, single-lint
8// allow per CLAUDE.md §6 (scoped to this module, not a blanket suppression).
9#![allow(clippy::needless_range_loop)]
10// Complete FIPS-algorithm / countermeasure family: with the module now
11// crate-internal (API re-tiering, v0.2), not every entry point has an
12// in-crate caller in every feature combination. All of them light up under
13// --all-features and are exercised by the KAT suites; keeping the full,
14// audited family beats pruning it per-combo. Documented allow (CLAUDE.md §6).
15#![allow(dead_code)]
16
17//! Encoding and decoding algorithms for ML-DSA (FIPS 204, Algorithms 9-28).
18//!
19//! Provides bit-level packing and unpacking of polynomials and polynomial
20//! vectors into compact byte representations, as well as the Power2Round
21//! decomposition used during key generation.
22//!
23//! All vector functions accept slices (`&[[i32; N]]`) instead of `&Vec<...>`
24//! to avoid requiring heap-allocated containers.
25//!
26//! # Stability (hazmat tier)
27//!
28//! This module is part of the `hazmat` expert building-block layer: it is
29//! public only with the `hazmat` Cargo feature, for KAT/test-vector tooling
30//! and research. Read each function's invariants before use. **No stability,
31//! misuse-resistance, or side-channel promise** applies to this tier — its
32//! API may change in any release. The typed facade is the guaranteed API.
33
34use super::ntt::mod_q;
35use super::params::{D, MAX_K, MAX_L, N, Params, Q};
36use alloc::vec::Vec;
37
38// ============================================================
39// Bit/byte conversion utilities (Algorithms 9-13)
40// ============================================================
41
42/// Pack a polynomial whose coefficients lie in [0, b].
43///
44/// Implements Algorithm 16 of FIPS 204 (SimpleBitPack). Each coefficient
45/// is stored using `bitlen(b)` bits in little-endian bit order.
46///
47/// - `w`: input polynomial with coefficients in [0, b].
48/// - `b`: upper bound on coefficient values.
49/// - `out`: output buffer (must have length >= N * bitlen(b) / 8).
50pub fn simple_bit_pack(w: &[i32; N], b: u32, out: &mut [u8]) {
51 let bits = 32 - b.leading_zeros() as usize; // bitlen(b)
52 let mut bit_pos = 0usize;
53 // Clear output
54 for byte in out.iter_mut() {
55 *byte = 0;
56 }
57 for i in 0..N {
58 let val = w[i] as u32;
59 for bit in 0..bits {
60 if (val >> bit) & 1 == 1 {
61 out[bit_pos / 8] |= 1 << (bit_pos % 8);
62 }
63 bit_pos += 1;
64 }
65 }
66}
67
68/// Unpack a polynomial whose coefficients lie in [0, b].
69///
70/// Implements Algorithm 18 of FIPS 204 (SimpleBitUnpack). Inverse of
71/// [`simple_bit_pack`].
72///
73/// - `data`: packed byte data.
74/// - `b`: upper bound on coefficient values.
75/// - `w`: output polynomial (filled with decoded coefficients).
76pub fn simple_bit_unpack(data: &[u8], b: u32, w: &mut [i32; N]) {
77 let bits = 32 - b.leading_zeros() as usize;
78 let mut bit_pos = 0usize;
79 for i in 0..N {
80 let mut val = 0u32;
81 for bit in 0..bits {
82 if (data[bit_pos / 8] >> (bit_pos % 8)) & 1 == 1 {
83 val |= 1 << bit;
84 }
85 bit_pos += 1;
86 }
87 w[i] = val as i32;
88 }
89}
90
91/// Pack a polynomial whose coefficients lie in [-a, b].
92///
93/// Implements Algorithm 17 of FIPS 204 (BitPack). Stores each coefficient
94/// as `(b - coeff)`, mapping the range [-a, b] to [0, a+b], then packs
95/// using `bitlen(a+b)` bits per coefficient.
96///
97/// - `w`: input polynomial with coefficients in [-a, b].
98/// - `a`: magnitude of the negative bound.
99/// - `b`: positive bound.
100/// - `out`: output buffer (must have length >= N * bitlen(a+b) / 8).
101pub fn bit_pack(w: &[i32; N], a: u32, b: u32, out: &mut [u8]) {
102 let range = a + b;
103 let bits = 32 - range.leading_zeros() as usize;
104 let mut bit_pos = 0usize;
105 for byte in out.iter_mut() {
106 *byte = 0;
107 }
108 for i in 0..N {
109 let val = (b as i32 - w[i]) as u32;
110 for bit in 0..bits {
111 if (val >> bit) & 1 == 1 {
112 out[bit_pos / 8] |= 1 << (bit_pos % 8);
113 }
114 bit_pos += 1;
115 }
116 }
117}
118
119/// Unpack a polynomial whose coefficients lie in [-a, b].
120///
121/// Implements Algorithm 19 of FIPS 204 (BitUnpack). Inverse of [`bit_pack`].
122///
123/// - `data`: packed byte data.
124/// - `a`: magnitude of the negative bound.
125/// - `b`: positive bound.
126/// - `w`: output polynomial (filled with decoded coefficients in [-a, b]).
127pub fn bit_unpack(data: &[u8], a: u32, b: u32, w: &mut [i32; N]) {
128 let range = a + b;
129 let bits = 32 - range.leading_zeros() as usize;
130 let mut bit_pos = 0usize;
131 for i in 0..N {
132 let mut val = 0u32;
133 for bit in 0..bits {
134 if (data[bit_pos / 8] >> (bit_pos % 8)) & 1 == 1 {
135 val |= 1 << bit;
136 }
137 bit_pos += 1;
138 }
139 w[i] = b as i32 - val as i32;
140 }
141}
142
143/// Pack a hint vector into bytes.
144///
145/// Implements Algorithm 20 of FIPS 204 (HintBitPack). The hint vector `h`
146/// consists of k binary polynomials with at most `omega` total non-zero
147/// entries. The output uses `omega + k` bytes: the first `omega` bytes
148/// store the indices of non-zero coefficients, and the last k bytes store
149/// cumulative index counts.
150///
151/// - `h`: hint vector (k polynomials with entries in {0, 1}).
152/// - `out`: output buffer (must have length omega + k).
153pub fn hint_bit_pack<P: Params>(h: &[[i32; N]], out: &mut [u8]) {
154 let omega = P::OMEGA;
155 let k = P::K;
156 // out has length omega + k
157 for byte in out.iter_mut() {
158 *byte = 0;
159 }
160 let mut idx = 0usize;
161 for i in 0..k {
162 for j in 0..N {
163 if h[i][j] != 0 {
164 out[idx] = j as u8;
165 idx += 1;
166 }
167 }
168 out[omega + i] = idx as u8;
169 }
170}
171
172/// Unpack a hint vector from bytes.
173///
174/// Implements Algorithm 21 of FIPS 204 (HintBitUnpack). Inverse of
175/// [`hint_bit_pack`]. Performs validity checks on the encoding: indices
176/// must be strictly increasing within each polynomial, and cumulative
177/// counts must be non-decreasing and within bounds.
178///
179/// Returns `None` if the encoding is malformed.
180pub fn hint_bit_unpack<P: Params>(data: &[u8]) -> Option<[[i32; N]; MAX_K]> {
181 let omega = P::OMEGA;
182 let k = P::K;
183 let mut h = [[0i32; N]; MAX_K];
184 let mut idx = 0usize;
185 for i in 0..k {
186 let upper = data[omega + i] as usize;
187 if upper < idx || upper > omega {
188 return None;
189 }
190 let first = idx;
191 while idx < upper {
192 // Check ordering (indices must be strictly increasing within each polynomial)
193 if idx > first && data[idx] <= data[idx - 1] {
194 return None;
195 }
196 let j = data[idx] as usize;
197 if j >= N {
198 return None;
199 }
200 h[i][j] = 1;
201 idx += 1;
202 }
203 }
204 // Check remaining bytes are zero
205 while idx < omega {
206 if data[idx] != 0 {
207 return None;
208 }
209 idx += 1;
210 }
211 Some(h)
212}
213
214/// Encode a public key as bytes.
215///
216/// Implements Algorithm 22 of FIPS 204 (pkEncode). The public key consists
217/// of the 32-byte seed `rho` followed by the k polynomials of `t1`, each
218/// packed with 10 bits per coefficient (since t1 values lie in [0, 1023]).
219///
220/// - `rho`: 32-byte public seed for matrix A generation.
221/// - `t1`: vector of k polynomials (the high bits of t).
222///
223/// Returns a byte vector of length `P::PK_LEN`.
224pub fn pk_encode<P: Params>(rho: &[u8; 32], t1: &[[i32; N]]) -> Vec<u8> {
225 let k = P::K;
226 // t1 coefficients are in [0, 2^{bitlen(q-1)-d} - 1] = [0, 2^10 - 1] = [0, 1023]
227 let coeff_bits = 10; // bitlen(q-1) - d = 23 - 13
228 let poly_bytes = N * coeff_bits / 8; // 256 * 10 / 8 = 320
229 let mut pk = vec![0u8; P::PK_LEN];
230 pk[..32].copy_from_slice(rho);
231 for i in 0..k {
232 let offset = 32 + i * poly_bytes;
233 simple_bit_pack(&t1[i], 1023, &mut pk[offset..offset + poly_bytes]);
234 }
235 pk
236}
237
238/// Decode a public key from bytes.
239///
240/// Implements Algorithm 23 of FIPS 204 (pkDecode). Inverse of [`pk_encode`].
241///
242/// Returns `(rho, t1)` where `rho` is the 32-byte seed and `t1` is the
243/// fixed array of k polynomials with coefficients in [0, 1023].
244pub fn pk_decode<P: Params>(pk: &[u8]) -> ([u8; 32], [[i32; N]; MAX_K]) {
245 let k = P::K;
246 let poly_bytes = 320; // 256 * 10 / 8
247 let mut rho = [0u8; 32];
248 rho.copy_from_slice(&pk[..32]);
249 let mut t1 = [[0i32; N]; MAX_K];
250 for i in 0..k {
251 let offset = 32 + i * poly_bytes;
252 simple_bit_unpack(&pk[offset..offset + poly_bytes], 1023, &mut t1[i]);
253 }
254 (rho, t1)
255}
256
257/// Encode a secret key as bytes.
258///
259/// Implements Algorithm 24 of FIPS 204 (skEncode). The secret key is the
260/// concatenation of `rho` (32 bytes), `K` (32 bytes), `tr` (64 bytes),
261/// the l polynomials of `s1` and k polynomials of `s2` (each packed with
262/// `bitlen(2*eta)` bits per coefficient), and the k polynomials of `t0`
263/// (each packed with 13 bits per coefficient).
264///
265/// - `rho`: 32-byte public seed.
266/// - `k_seed`: 32-byte secret seed K.
267/// - `tr`: 64-byte hash of the public key.
268/// - `s1`: secret vector of l polynomials with coefficients in [-eta, eta].
269/// - `s2`: secret vector of k polynomials with coefficients in [-eta, eta].
270/// - `t0`: low-order bits vector (k polynomials from Power2Round).
271///
272/// Returns a byte vector of length `P::SK_LEN`.
273pub fn sk_encode<P: Params>(
274 rho: &[u8; 32],
275 k_seed: &[u8; 32],
276 tr: &[u8; 64],
277 s1: &[[i32; N]],
278 s2: &[[i32; N]],
279 t0: &[[i32; N]],
280) -> Vec<u8> {
281 let eta = P::ETA as u32;
282 let l = P::L;
283 let k = P::K;
284 let eta_bits = P::BITLEN_2ETA;
285 let poly_eta_bytes = N * eta_bits / 8;
286 let d = D;
287 let poly_t0_bytes = N * d / 8; // 256 * 13 / 8 = 416
288
289 let mut sk = vec![0u8; P::SK_LEN];
290 let mut offset = 0;
291
292 sk[offset..offset + 32].copy_from_slice(rho);
293 offset += 32;
294 sk[offset..offset + 32].copy_from_slice(k_seed);
295 offset += 32;
296 sk[offset..offset + 64].copy_from_slice(tr);
297 offset += 64;
298
299 for i in 0..l {
300 bit_pack(&s1[i], eta, eta, &mut sk[offset..offset + poly_eta_bytes]);
301 offset += poly_eta_bytes;
302 }
303 for i in 0..k {
304 bit_pack(&s2[i], eta, eta, &mut sk[offset..offset + poly_eta_bytes]);
305 offset += poly_eta_bytes;
306 }
307 for i in 0..k {
308 // t0 coefficients are in [-(2^{d-1}-1), 2^{d-1}] = [-4095, 4096]
309 // We use bit_pack with a=2^{d-1}-1=4095 and b=2^{d-1}=4096
310 bit_pack(&t0[i], 4095, 4096, &mut sk[offset..offset + poly_t0_bytes]);
311 offset += poly_t0_bytes;
312 }
313
314 sk
315}
316
317/// Decoded ML-DSA secret-key components `(rho, K, tr, s1, s2, t0)` (FIPS 204 skDecode).
318pub type DecodedSecretKey = (
319 [u8; 32],
320 [u8; 32],
321 [u8; 64],
322 [[i32; N]; MAX_L],
323 [[i32; N]; MAX_K],
324 [[i32; N]; MAX_K],
325);
326
327/// Decoded ML-DSA signature components `(c_tilde, z, h)` (FIPS 204 sigDecode).
328pub type DecodedSignature = (Vec<u8>, [[i32; N]; MAX_L], [[i32; N]; MAX_K]);
329
330/// Decode a secret key from bytes.
331///
332/// Implements Algorithm 25 of FIPS 204 (skDecode). Inverse of [`sk_encode`].
333///
334/// Returns `(rho, K, tr, s1, s2, t0)` using fixed arrays.
335pub fn sk_decode<P: Params>(sk: &[u8]) -> DecodedSecretKey {
336 let eta = P::ETA as u32;
337 let l = P::L;
338 let k = P::K;
339 let eta_bits = P::BITLEN_2ETA;
340 let poly_eta_bytes = N * eta_bits / 8;
341 let d = D;
342 let poly_t0_bytes = N * d / 8;
343
344 let mut offset = 0;
345 let mut rho = [0u8; 32];
346 rho.copy_from_slice(&sk[offset..offset + 32]);
347 offset += 32;
348 let mut k_seed = [0u8; 32];
349 k_seed.copy_from_slice(&sk[offset..offset + 32]);
350 offset += 32;
351 let mut tr = [0u8; 64];
352 tr.copy_from_slice(&sk[offset..offset + 64]);
353 offset += 64;
354
355 let mut s1 = [[0i32; N]; MAX_L];
356 for i in 0..l {
357 bit_unpack(&sk[offset..offset + poly_eta_bytes], eta, eta, &mut s1[i]);
358 offset += poly_eta_bytes;
359 }
360 let mut s2 = [[0i32; N]; MAX_K];
361 for i in 0..k {
362 bit_unpack(&sk[offset..offset + poly_eta_bytes], eta, eta, &mut s2[i]);
363 offset += poly_eta_bytes;
364 }
365 let mut t0 = [[0i32; N]; MAX_K];
366 for i in 0..k {
367 bit_unpack(&sk[offset..offset + poly_t0_bytes], 4095, 4096, &mut t0[i]);
368 offset += poly_t0_bytes;
369 }
370
371 (rho, k_seed, tr, s1, s2, t0)
372}
373
374/// Decode only the seeds (rho, K, tr) from a secret key, without
375/// unpacking any polynomial. Returns 128 bytes of stack.
376pub fn sk_decode_seeds<P: Params>(sk: &[u8]) -> ([u8; 32], [u8; 32], [u8; 64]) {
377 let mut rho = [0u8; 32];
378 rho.copy_from_slice(&sk[..32]);
379 let mut k_seed = [0u8; 32];
380 k_seed.copy_from_slice(&sk[32..64]);
381 let mut tr = [0u8; 64];
382 tr.copy_from_slice(&sk[64..128]);
383 (rho, k_seed, tr)
384}
385
386/// Decode a single polynomial of s1 from the packed secret key.
387///
388/// `idx` must be in `0..P::L`.
389pub fn sk_decode_s1<P: Params>(sk: &[u8], idx: usize, out: &mut [i32; N]) {
390 let eta = P::ETA as u32;
391 let eta_bits = P::BITLEN_2ETA;
392 let poly_eta_bytes = N * eta_bits / 8;
393 let base = 128; // rho(32) + K(32) + tr(64)
394 let offset = base + idx * poly_eta_bytes;
395 bit_unpack(&sk[offset..offset + poly_eta_bytes], eta, eta, out);
396}
397
398/// Decode a single polynomial of s2 from the packed secret key.
399///
400/// `idx` must be in `0..P::K`.
401pub fn sk_decode_s2<P: Params>(sk: &[u8], idx: usize, out: &mut [i32; N]) {
402 let eta = P::ETA as u32;
403 let l = P::L;
404 let eta_bits = P::BITLEN_2ETA;
405 let poly_eta_bytes = N * eta_bits / 8;
406 let base = 128 + l * poly_eta_bytes;
407 let offset = base + idx * poly_eta_bytes;
408 bit_unpack(&sk[offset..offset + poly_eta_bytes], eta, eta, out);
409}
410
411/// Decode a single polynomial of t0 from the packed secret key.
412///
413/// `idx` must be in `0..P::K`.
414pub fn sk_decode_t0<P: Params>(sk: &[u8], idx: usize, out: &mut [i32; N]) {
415 let l = P::L;
416 let k = P::K;
417 let eta_bits = P::BITLEN_2ETA;
418 let poly_eta_bytes = N * eta_bits / 8;
419 let d = D;
420 let poly_t0_bytes = N * d / 8;
421 let base = 128 + (l + k) * poly_eta_bytes;
422 let offset = base + idx * poly_t0_bytes;
423 bit_unpack(&sk[offset..offset + poly_t0_bytes], 4095, 4096, out);
424}
425
426/// Encode a signature as bytes.
427///
428/// Implements Algorithm 26 of FIPS 204 (sigEncode). The signature is the
429/// concatenation of `c_tilde` (lambda/4 bytes), the l polynomials of `z`
430/// (each packed with `1 + bitlen(gamma1-1)` bits per coefficient), and the
431/// hint vector `h` (packed via [`hint_bit_pack`]).
432///
433/// - `c_tilde`: commitment hash (lambda/4 bytes).
434/// - `z`: response vector (l polynomials with coefficients in [-(gamma1-1), gamma1]).
435/// - `h`: hint vector (k binary polynomials).
436///
437/// Returns a byte vector of length `P::SIG_LEN`.
438pub fn sig_encode<P: Params>(c_tilde: &[u8], z: &[[i32; N]], h: &[[i32; N]]) -> Vec<u8> {
439 let l = P::L;
440 let gamma1 = P::GAMMA1 as u32;
441 let gamma1_bits = P::BITLEN_GAMMA1_MINUS1 + 1; // bitlen(gamma1-1)+1 = 1+bitlen(gamma1-1)
442 let poly_z_bytes = N * gamma1_bits / 8;
443 let c_tilde_len = P::LAMBDA / 4;
444
445 let mut sig = vec![0u8; P::SIG_LEN];
446 let mut offset = 0;
447
448 sig[offset..offset + c_tilde_len].copy_from_slice(&c_tilde[..c_tilde_len]);
449 offset += c_tilde_len;
450
451 for i in 0..l {
452 // z coefficients are in [-(gamma1-1), gamma1], so a=gamma1-1, b=gamma1
453 bit_pack(&z[i], gamma1 - 1, gamma1, &mut sig[offset..offset + poly_z_bytes]);
454 offset += poly_z_bytes;
455 }
456
457 hint_bit_pack::<P>(h, &mut sig[offset..]);
458
459 sig
460}
461
462/// Decode a signature from bytes.
463///
464/// Implements Algorithm 27 of FIPS 204 (sigDecode). Inverse of [`sig_encode`].
465///
466/// Returns `Some((c_tilde, z, h))` on success, or `None` if the hint
467/// encoding is malformed. Uses fixed arrays instead of Vec.
468pub fn sig_decode<P: Params>(sig: &[u8]) -> Option<DecodedSignature> {
469 let l = P::L;
470 let gamma1 = P::GAMMA1 as u32;
471 let gamma1_bits = P::BITLEN_GAMMA1_MINUS1 + 1;
472 let poly_z_bytes = N * gamma1_bits / 8;
473 let c_tilde_len = P::LAMBDA / 4;
474
475 let mut offset = 0;
476 let c_tilde = sig[offset..offset + c_tilde_len].to_vec();
477 offset += c_tilde_len;
478
479 let mut z = [[0i32; N]; MAX_L];
480 for i in 0..l {
481 bit_unpack(&sig[offset..offset + poly_z_bytes], gamma1 - 1, gamma1, &mut z[i]);
482 offset += poly_z_bytes;
483 }
484
485 let h = hint_bit_unpack::<P>(&sig[offset..])?;
486
487 Some((c_tilde, z, h))
488}
489
490/// Encode the high-order bits vector w1 as bytes.
491///
492/// Implements Algorithm 28 of FIPS 204 (w1Encode). The w1 coefficients lie
493/// in [0, (q-1)/(2*gamma2) - 1] and are packed using [`simple_bit_pack`].
494/// The encoded output is hashed together with the message digest to form
495/// the commitment hash c_tilde during signing and verification.
496///
497/// - `w1`: vector of k polynomials (high bits from Decompose).
498///
499/// Returns the packed byte representation of w1.
500pub fn w1_encode<P: Params>(w1: &[[i32; N]]) -> Vec<u8> {
501 let k = P::K;
502 let gamma2 = P::GAMMA2;
503 // w1 coefficients are in [0, (q-1)/(2*gamma2)]
504 let max_w1 = ((Q - 1) / (2 * gamma2) - 1) as u32;
505 let bits = 32 - max_w1.leading_zeros() as usize;
506 let poly_bytes = N * bits / 8;
507 let mut out = vec![0u8; k * poly_bytes];
508 for i in 0..k {
509 let offset = i * poly_bytes;
510 simple_bit_pack(&w1[i], max_w1, &mut out[offset..offset + poly_bytes]);
511 }
512 out
513}
514
515/// Decompose a coefficient into high and low parts using a power-of-2 divisor.
516///
517/// Implements Algorithm 35 of FIPS 204 (Power2Round). Splits `r` into
518/// `(r1, r0)` such that `r = r1 * 2^d + r0` with `r0` in the centered
519/// range `[-(2^{d-1} - 1), 2^{d-1}]`.
520///
521/// Used during key generation to compress the public vector t into t1
522/// (stored in the public key) and t0 (stored in the secret key).
523pub fn power2round(r: i32) -> (i32, i32) {
524 let rp = mod_q(r);
525 // r0 = r mod+ 2^d (centered modular reduction)
526 let two_d = 1i32 << D;
527 let half = two_d >> 1;
528 let mut r0v = rp % two_d; // [0, 2^d - 1]
529 if r0v > half {
530 r0v -= two_d;
531 }
532 let r1 = (rp - r0v) / two_d;
533 (r1, r0v)
534}
535
536/// Apply [`power2round`] to every coefficient of a polynomial vector.
537///
538/// Returns `(t1, t0)` as fixed arrays where each element satisfies
539/// `t[i][j] = t1[i][j] * 2^d + t0[i][j]`.
540pub fn power2round_vec(t: &[[i32; N]], len: usize) -> ([[i32; N]; MAX_K], [[i32; N]; MAX_K]) {
541 let mut t1 = [[0i32; N]; MAX_K];
542 let mut t0 = [[0i32; N]; MAX_K];
543 for i in 0..len {
544 for j in 0..N {
545 let (r1, r0) = power2round(t[i][j]);
546 t1[i][j] = r1;
547 t0[i][j] = r0;
548 }
549 }
550 (t1, t0)
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556
557 #[test]
558 fn test_simple_bit_pack_unpack() {
559 let mut w = [0i32; N];
560 for i in 0..N {
561 w[i] = (i as i32 * 3) % 1024;
562 }
563 let mut buf = [0u8; 320]; // 256 * 10 / 8
564 simple_bit_pack(&w, 1023, &mut buf);
565 let mut w2 = [0i32; N];
566 simple_bit_unpack(&buf, 1023, &mut w2);
567 assert_eq!(w, w2);
568 }
569
570 #[test]
571 fn test_bit_pack_unpack() {
572 let mut w = [0i32; N];
573 for i in 0..N {
574 w[i] = (i as i32 % 5) - 2; // values in [-2, 2]
575 }
576 // a=2, b=2, a+b=4, bitlen(4)=3, 256*3/8=96
577 let mut buf = [0u8; 96];
578 bit_pack(&w, 2, 2, &mut buf);
579 let mut w2 = [0i32; N];
580 bit_unpack(&buf, 2, 2, &mut w2);
581 assert_eq!(w, w2);
582 }
583
584 #[test]
585 fn test_power2round() {
586 let (r1, r0) = power2round(1234567);
587 assert_eq!(r1 * (1 << D) + r0, 1234567);
588 assert!(r0.abs() <= (1 << (D - 1)));
589 }
590}