Coverage Report

Created: 2026-10-17 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fips203/src/sampling.rs
Line
Count
Source
1
use crate::types::Z;
2
use crate::Q;
3
use sha3::digest::XofReader;
4
5
6
/// Algorithm 7 `SampleNTT(B)` on page 23.
7
/// Takes a 32-byte seed and two indices as input and outputs a pseudorandom element of `𝑇_π‘ž`.
8
/// This implementation takes the `XofReader` directly.
9
///
10
/// Input: byte stream `B ∈ B^{34}`     β–· a 32-byte seed along with two indices <br>
11
/// Output: array `a_hat ∈ Z^{256}_q`    β–· the coefficients of the NTT of a polynomial
12
53.0k
pub(crate) fn sample_ntt(mut xof_reader: impl XofReader) -> [Z; 256] {
13
    //
14
53.0k
    let mut array_a_hat = [Z::default(); 256];
15
53.0k
    let mut c = [0u8; 3]; // Space for 3 random (byte) draws
16
17
    // Not needed as XofReader is passed into function.
18
    // 1: ctx ← XOF.Init()
19
    // 2: ctx ← XOF.Absorb(ctx, 𝐡)    β–· input the given byte array into XOF
20
21
    // 3: j ← 0
22
53.0k
    let mut j = 0usize;
23
24
    // This rejection sampling loop is solely dependent upon rho which crosses a trust boundary
25
    // in the clear. Thus, it does not need to be constant time.
26
    // 4: while j < 256 do
27
8.47M
    while j < 256 {
28
        //
29
        // 5: (ctx, 𝐢) ← XOF.Squeeze(ctx, 3)    β–· get a fresh 3-byte array 𝐢 from XOF
30
8.42M
        xof_reader.read(&mut c); // Draw 3 bytes
31
32
        // 6: 𝑑1 ← 𝐢[0] + 256 β‹… (𝐢[1] mod 16)    β–· 0 ≀ 𝑑1 < 2^{12}
33
8.42M
        let d1 = u16::from(c[0]) + 256 * (u16::from(c[1]) & 0x0F);
34
35
        // 7: 𝑑2 ← ⌊𝐢[1]/16βŒ‹ + 16 β‹… 𝐢[2]    β–· 0 ≀ 𝑑2 < 2^{12}
36
8.42M
        let d2 = (u16::from(c[1]) >> 4) + 16 * u16::from(c[2]);
37
38
        // 8: if d1 < q then
39
8.42M
        if d1 < Q {
40
6.81M
            //
41
6.81M
            // 9: a_hat[j] ← d1    β–· a_hat ∈ Z^{256}_q
42
6.81M
            array_a_hat[j].set_u16(d1);
43
6.81M
44
6.81M
            // 10: j ← j + 1
45
6.81M
            j += 1;
46
6.81M
47
6.81M
            // 11: end if
48
6.81M
        }
49
50
        // 12: if d2 < q and j < 256 then
51
        // Bitwise AND; `&&` would do as well, as this loop need not be constant time (see above)
52
        #[allow(clippy::needless_bitwise_bool)]
53
8.42M
        if (d2 < Q) & (j < 256) {
54
6.78M
            //
55
6.78M
            // 13: a_hat[j] ← d2
56
6.78M
            array_a_hat[j].set_u16(d2);
57
6.78M
58
6.78M
            // 14: j ← j + 1
59
6.78M
            j += 1;
60
6.78M
61
6.78M
            // 15: end if
62
6.78M
        }
63
64
        // 16: end while
65
    }
66
67
    // 17: return a_hat
68
53.0k
    array_a_hat
69
53.0k
}
70
71
72
/// Algorithm 8 `SamplePolyCBD_Ξ·(B)` on page 23.
73
/// Takes a seed as input and outputs a pseudorandom sample from the distribution `D_πœ‚(𝑅_π‘ž)`. <br>
74
/// This function is an optimized version that avoids the `BytesToBits` function (algorithm 4).
75
///
76
/// Input: byte array `B ∈ B^{64·η}` <br>
77
/// Output: array `f ∈ Z^{256}_q`
78
///
79
/// The output is written into `array_f` rather than returned by value, so the secret sample leaves
80
/// no copy outside the caller's buffer, which the caller wipes (FIPS 203 Β§3.3).
81
35.7k
pub(crate) fn sample_poly_cbd(byte_array_b: &[u8], array_f: &mut [Z; 256]) {
82
35.7k
    let eta = u32::try_from(byte_array_b.len()).unwrap() >> 6;
83
35.7k
    debug_assert_eq!(byte_array_b.len(), 64 * eta as usize, "Alg 8: byte array not 64 * eta");
84
35.7k
    let mut temp = 0;
85
35.7k
    let mut int_index = 0;
86
35.7k
    let mut bit_index = 0;
87
4.91M
    for byte in byte_array_b {
88
4.87M
        temp |= u32::from(*byte) << bit_index;
89
4.87M
        bit_index += 8;
90
14.0M
        while bit_index >= 2 * (eta as usize) {
91
9.13M
            let tmask_x = temp & ((1 << eta) - 1);
92
9.13M
            let x = count_ones(tmask_x);
93
9.13M
            let tmask_y = (temp >> eta) & ((1 << eta) - 1);
94
9.13M
            let y = count_ones(tmask_y);
95
9.13M
            let (mut xx, mut yy) = (Z::default(), Z::default());
96
9.13M
            xx.set_u16(x);
97
9.13M
            yy.set_u16(y);
98
9.13M
            array_f[int_index] = xx.sub(yy);
99
9.13M
            bit_index -= 2 * (eta as usize);
100
9.13M
            temp >>= 2 * (eta as usize);
101
9.13M
            int_index += 1;
102
9.13M
        }
103
    }
104
35.7k
}
105
106
107
// Count u8 ones in constant time (u32 helps perf)
108
#[allow(clippy::cast_possible_truncation)] // return x as u16
109
18.2M
fn count_ones(x: u32) -> u16 {
110
18.2M
    let x = (x & 0x5555_5555) + ((x >> 1) & 0x5555_5555);
111
18.2M
    let x = (x & 0x3333_3333) + ((x >> 2) & 0x3333_3333);
112
18.2M
    let x = (x & 0x0F0F_0F0F) + ((x >> 4) & 0x0F0F_0F0F);
113
18.2M
    x as u16
114
18.2M
}
115
116
117
// The original pseudocode for Algorithm 8 follows...
118
// Algorithm 8 `SamplePolyCBDΞ·(B)` on page 23.
119
// Takes a seed as input and outputs a pseudorandom sample from the distribution `D_πœ‚(𝑅_π‘ž)`.
120
//
121
// Input: byte array B ∈ B^{64·η}
122
// Output: array f ∈ Z^{256}_q
123
// 1: b ← BytesToBits(B)
124
// 2: for (i ← 0; i < 256; i ++)
125
// 3:   x ← βˆ‘_{j=0}^{Ξ·-1} b[2iΞ· + j] //
126
// 4:   y ← βˆ‘_{j=0}^{Ξ·-1} b[2iΞ· + Ξ· + j]
127
// 5:   f [i] ← x βˆ’ y mod q
128
// 6: end for
129
// 7: return f
130
// }