Skip to main content

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}