/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 | | // } |