/src/botan/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Classic McEliece Field Ordering Generation |
3 | | * Based on the public domain reference implementation by the designers |
4 | | * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) |
5 | | * |
6 | | * (C) 2023 Jack Lloyd |
7 | | * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity |
8 | | * |
9 | | * Botan is released under the Simplified BSD License (see license.txt) |
10 | | **/ |
11 | | #include <botan/internal/cmce_field_ordering.h> |
12 | | |
13 | | #include <botan/cmce.h> |
14 | | #include <botan/mem_ops.h> |
15 | | #include <botan/internal/loadstor.h> |
16 | | |
17 | | #include <numeric> |
18 | | #include <utility> |
19 | | #include <vector> |
20 | | |
21 | | namespace Botan { |
22 | | |
23 | | namespace CMCE_CT { |
24 | | |
25 | | template <std::unsigned_integral T1, std::unsigned_integral T2> |
26 | | requires(sizeof(T1) <= 8 && sizeof(T2) <= 8) |
27 | 0 | void cond_swap_pair(CT::Mask<uint64_t> cond_mask, std::pair<T1, T2>& a, std::pair<T1, T2>& b) { |
28 | 0 | cond_mask.conditional_swap(a.first, b.first); |
29 | 0 | cond_mask.conditional_swap(a.second, b.second); |
30 | 0 | } Unexecuted instantiation: _ZN5Botan7CMCE_CT14cond_swap_pairITkNSt3__117unsigned_integralEjTkNS2_17unsigned_integralEtQaalestT_Li8ElestT0_Li8EEEvNS_2CT4MaskImEERNS2_4pairIS3_S4_EESA_ Unexecuted instantiation: _ZN5Botan7CMCE_CT14cond_swap_pairITkNSt3__117unsigned_integralEtTkNS2_17unsigned_integralEtQaalestT_Li8ElestT0_Li8EEEvNS_2CT4MaskImEERNS2_4pairIS3_S4_EESA_ |
31 | | |
32 | | template <std::unsigned_integral T1, std::unsigned_integral T2> |
33 | 0 | void compare_and_swap_pair(std::span<std::pair<T1, T2>> a, size_t i, size_t k, size_t l) { |
34 | 0 | static_assert(sizeof(T1) <= sizeof(uint64_t) && sizeof(T2) <= sizeof(uint64_t), |
35 | 0 | "Types T1 and T2 must be at most 64 bits wide"); |
36 | 0 | if((i & k) == 0) { // i and k do not depend on secret data |
37 | 0 | auto swap_required_mask = CT::Mask<uint64_t>::is_lt(a[l].first, a[i].first); |
38 | 0 | cond_swap_pair(swap_required_mask, a[i], a[l]); |
39 | 0 | } else { |
40 | 0 | auto swap_required_mask = CT::Mask<uint64_t>::is_gt(a[l].first, a[i].first); |
41 | 0 | cond_swap_pair(swap_required_mask, a[i], a[l]); |
42 | 0 | } |
43 | 0 | } Unexecuted instantiation: _ZN5Botan7CMCE_CT21compare_and_swap_pairITkNSt3__117unsigned_integralEjTkNS2_17unsigned_integralEtEEvNS2_4spanINS2_4pairIT_T0_EELm18446744073709551615EEEmmm Unexecuted instantiation: _ZN5Botan7CMCE_CT21compare_and_swap_pairITkNSt3__117unsigned_integralEtTkNS2_17unsigned_integralEtEEvNS2_4spanINS2_4pairIT_T0_EELm18446744073709551615EEEmmm |
44 | | |
45 | | // Sorts a vector of pairs after the first element |
46 | | template <std::unsigned_integral T1, std::unsigned_integral T2> |
47 | 0 | void bitonic_sort_pair(std::span<std::pair<T1, T2>> a) { |
48 | 0 | const size_t n = a.size(); |
49 | 0 | BOTAN_ARG_CHECK(is_power_of_2(n), "Input vector size must be a power of 2"); |
50 | |
|
51 | 0 | for(size_t k = 2; k <= n; k *= 2) { |
52 | 0 | for(size_t j = k / 2; j > 0; j /= 2) { |
53 | 0 | for(size_t i = 0; i < n; ++i) { |
54 | 0 | const size_t l = i ^ j; |
55 | 0 | if(l > i) { |
56 | 0 | compare_and_swap_pair(a, i, k, l); |
57 | 0 | } |
58 | 0 | } |
59 | 0 | } |
60 | 0 | } |
61 | 0 | } Unexecuted instantiation: _ZN5Botan7CMCE_CT17bitonic_sort_pairITkNSt3__117unsigned_integralEjTkNS2_17unsigned_integralEtEEvNS2_4spanINS2_4pairIT_T0_EELm18446744073709551615EEE Unexecuted instantiation: _ZN5Botan7CMCE_CT17bitonic_sort_pairITkNSt3__117unsigned_integralEtTkNS2_17unsigned_integralEtEEvNS2_4spanINS2_4pairIT_T0_EELm18446744073709551615EEE |
62 | | |
63 | | template <std::unsigned_integral T> |
64 | 0 | T min(const T& a, const T& b) { |
65 | 0 | auto mask = CT::Mask<T>::is_lt(a, b); |
66 | 0 | return mask.select(a, b); |
67 | 0 | } |
68 | | |
69 | | } // namespace CMCE_CT |
70 | | |
71 | | namespace { |
72 | | template <std::unsigned_integral T1, std::unsigned_integral T2> |
73 | 0 | std::vector<std::pair<T1, T2>> zip(std::span<const T1> vec_1, std::span<const T2> vec_2) { |
74 | 0 | BOTAN_ARG_CHECK(vec_1.size() == vec_2.size(), "Vectors' dimensions do not match"); |
75 | 0 | std::vector<std::pair<T1, T2>> vec_zipped; |
76 | 0 | vec_zipped.reserve(vec_1.size()); |
77 | 0 | for(size_t i = 0; i < vec_1.size(); ++i) { |
78 | 0 | vec_zipped.push_back(std::make_pair(vec_1[i], vec_2[i])); |
79 | 0 | } |
80 | 0 | return vec_zipped; |
81 | 0 | } |
82 | | |
83 | | template <std::unsigned_integral T1, std::unsigned_integral T2> |
84 | 0 | std::pair<secure_vector<T1>, secure_vector<T2>> unzip(const std::span<std::pair<T1, T2>>& vec_zipped) { |
85 | 0 | std::pair<secure_vector<T1>, secure_vector<T2>> res; |
86 | |
|
87 | 0 | res.first.reserve(vec_zipped.size()); |
88 | 0 | res.second.reserve(vec_zipped.size()); |
89 | |
|
90 | 0 | for(const auto& [elem1, elem2] : vec_zipped) { |
91 | 0 | res.first.push_back(elem1); |
92 | 0 | res.second.push_back(elem2); |
93 | 0 | } |
94 | 0 | return res; |
95 | 0 | } |
96 | | |
97 | | /// @returns (vec[0],0), ..., (vec[n-1],n-1) |
98 | 0 | std::vector<std::pair<uint32_t, uint16_t>> enumerate(std::span<const uint32_t> vec) { |
99 | 0 | BOTAN_DEBUG_ASSERT(vec.size() < std::numeric_limits<uint16_t>::max()); |
100 | |
|
101 | 0 | std::vector<std::pair<uint32_t, uint16_t>> enumerated; |
102 | |
|
103 | 0 | std::transform(vec.begin(), vec.end(), std::back_inserter(enumerated), [ctr = uint16_t(0)](uint32_t elem) mutable { |
104 | 0 | return std::make_pair(elem, ctr++); |
105 | 0 | }); |
106 | |
|
107 | 0 | return enumerated; |
108 | 0 | } |
109 | | |
110 | | /** |
111 | | * @brief Create permutation pi as in (Section 8.2, Step 3). |
112 | | * |
113 | | * @param a The vector that is sorted |
114 | | * |
115 | | * @return (pi sorted after a, a sorted after pi) |
116 | | */ |
117 | 0 | std::pair<secure_vector<uint32_t>, CmcePermutation> create_pi(secure_vector<uint32_t> a) { |
118 | 0 | auto a_pi_zipped = enumerate(a); |
119 | 0 | CMCE_CT::bitonic_sort_pair(std::span(a_pi_zipped)); |
120 | |
|
121 | 0 | CmcePermutation pi_sorted; |
122 | 0 | std::tie(a, pi_sorted.get()) = unzip(std::span(a_pi_zipped)); |
123 | |
|
124 | 0 | return std::make_pair(a, pi_sorted); |
125 | 0 | } |
126 | | |
127 | | /** |
128 | | * @brief Create a GF element from pi as in (Section 8.2, Step 4). |
129 | | * Corresponds to the reverse bits of pi. |
130 | | */ |
131 | 0 | Classic_McEliece_GF from_pi(CmcePermutationElement pi_elem, CmceGfMod modulus, size_t m) { |
132 | 0 | auto reversed_bits = ct_reverse_bits(pi_elem.get()); |
133 | 0 | reversed_bits >>= (sizeof(uint16_t) * 8 - m); |
134 | 0 | return Classic_McEliece_GF(CmceGfElem(reversed_bits), modulus); |
135 | 0 | } |
136 | | |
137 | | /** |
138 | | * @brief Part of field ordering generation according to ISO 9.2.10 |
139 | | */ |
140 | 0 | secure_vector<uint16_t> composeinv(std::span<const uint16_t> c, std::span<const uint16_t> pi) { |
141 | 0 | auto pi_c_zipped = zip(pi, c); |
142 | 0 | CMCE_CT::bitonic_sort_pair(std::span(pi_c_zipped)); |
143 | | // Extract c from the sorted vector |
144 | 0 | secure_vector<uint16_t> c_sorted; |
145 | 0 | std::transform(pi_c_zipped.begin(), pi_c_zipped.end(), std::back_inserter(c_sorted), [](const auto& pair) { |
146 | 0 | return pair.second; |
147 | 0 | }); |
148 | |
|
149 | 0 | return c_sorted; |
150 | 0 | } |
151 | | |
152 | | // p,q = composeinv(p,q),composeinv(q,p) |
153 | 0 | void simultaneous_composeinv(secure_vector<uint16_t>& p, secure_vector<uint16_t>& q) { |
154 | 0 | auto p_new = composeinv(p, q); |
155 | 0 | q = composeinv(q, p); |
156 | 0 | p = std::move(p_new); |
157 | 0 | } |
158 | | |
159 | | /** |
160 | | * @brief Generate control bits as in ISO 9.2.10. |
161 | | * |
162 | | * TODO: This function can be optimized (see Classic McEliece reference implementation) |
163 | | */ |
164 | 0 | secure_vector<uint16_t> generate_control_bits_internal(const secure_vector<uint16_t>& pi) { |
165 | 0 | const auto n = pi.size(); |
166 | 0 | BOTAN_ASSERT_NOMSG(is_power_of_2(n)); |
167 | 0 | const size_t m = ceil_log2(n); |
168 | |
|
169 | 0 | if(m == 1) { |
170 | 0 | return secure_vector<uint16_t>({pi.at(0)}); |
171 | 0 | } |
172 | 0 | secure_vector<uint16_t> p(n); |
173 | 0 | for(size_t x = 0; x < n; ++x) { |
174 | 0 | p.at(x) = pi.at(x ^ 1); |
175 | 0 | } |
176 | 0 | secure_vector<uint16_t> q(n); |
177 | 0 | for(size_t x = 0; x < n; ++x) { |
178 | 0 | q.at(x) = pi.at(x) ^ 1; |
179 | 0 | } |
180 | |
|
181 | 0 | secure_vector<uint16_t> range_n(n); |
182 | 0 | std::iota(range_n.begin(), range_n.end(), 0); |
183 | 0 | auto piinv = composeinv(range_n, pi); |
184 | |
|
185 | 0 | simultaneous_composeinv(p, q); |
186 | |
|
187 | 0 | secure_vector<uint16_t> c(n); |
188 | 0 | for(uint16_t x = 0; static_cast<size_t>(x) < n; ++x) { |
189 | 0 | c.at(x) = CMCE_CT::min(x, p.at(x)); |
190 | 0 | } |
191 | |
|
192 | 0 | simultaneous_composeinv(p, q); |
193 | |
|
194 | 0 | for(size_t i = 1; i < m - 1; ++i) { |
195 | 0 | auto cp = composeinv(c, q); |
196 | 0 | simultaneous_composeinv(p, q); |
197 | 0 | for(size_t x = 0; x < n; ++x) { |
198 | 0 | c.at(x) = CMCE_CT::min(c.at(x), cp.at(x)); |
199 | 0 | } |
200 | 0 | } |
201 | |
|
202 | 0 | secure_vector<uint16_t> f(n / 2); |
203 | 0 | for(size_t j = 0; j < n / 2; ++j) { |
204 | 0 | f.at(j) = c.at(2 * j) % 2; |
205 | 0 | } |
206 | |
|
207 | 0 | secure_vector<uint16_t> big_f(n); |
208 | 0 | for(uint16_t x = 0; size_t(x) < n; ++x) { |
209 | 0 | big_f.at(x) = x ^ f.at(x / 2); |
210 | 0 | } |
211 | |
|
212 | 0 | auto fpi = composeinv(big_f, piinv); |
213 | |
|
214 | 0 | secure_vector<uint16_t> l(n / 2); |
215 | 0 | for(size_t k = 0; k < n / 2; ++k) { |
216 | 0 | l.at(k) = fpi.at(2 * k) % 2; |
217 | 0 | } |
218 | |
|
219 | 0 | secure_vector<uint16_t> big_l(n); |
220 | 0 | for(uint16_t y = 0; size_t(y) < n; ++y) { |
221 | 0 | big_l.at(y) = y ^ l.at(y / 2); |
222 | 0 | } |
223 | |
|
224 | 0 | auto big_m = composeinv(fpi, big_l); |
225 | |
|
226 | 0 | secure_vector<uint16_t> subm0(n / 2); |
227 | 0 | secure_vector<uint16_t> subm1(n / 2); |
228 | 0 | for(size_t j = 0; j < n / 2; ++j) { |
229 | 0 | subm0.at(j) = big_m.at(2 * j) / 2; |
230 | 0 | subm1.at(j) = big_m.at(2 * j + 1) / 2; |
231 | 0 | } |
232 | |
|
233 | 0 | auto subz0 = generate_control_bits_internal(subm0); |
234 | 0 | auto subz1 = generate_control_bits_internal(subm1); |
235 | |
|
236 | 0 | secure_vector<uint16_t> z(subz0.size() + subz1.size()); |
237 | 0 | for(size_t j = 0; j < subz0.size(); ++j) { |
238 | 0 | z.at(2 * j) = subz0.at(j); |
239 | 0 | z.at(2 * j + 1) = subz1.at(j); |
240 | 0 | } |
241 | |
|
242 | 0 | return concat(f, z, l); |
243 | 0 | } |
244 | | |
245 | 0 | CT::Choice ct_has_adjacent_duplicates(std::span<const uint32_t> vec) { |
246 | 0 | CT::Mask<uint32_t> mask = CT::Mask<uint32_t>::cleared(); |
247 | 0 | for(size_t i = 0; i < vec.size() - 1; ++i) { |
248 | 0 | mask |= CT::Mask<uint32_t>::is_equal(vec[i], vec[i + 1]); |
249 | 0 | } |
250 | 0 | return mask.as_choice(); |
251 | 0 | } |
252 | | |
253 | | } // anonymous namespace |
254 | | |
255 | | std::optional<Classic_McEliece_Field_Ordering> Classic_McEliece_Field_Ordering::create_field_ordering( |
256 | 0 | const Classic_McEliece_Parameters& params, StrongSpan<const CmceOrderingBits> random_bits) { |
257 | 0 | BOTAN_ARG_CHECK(random_bits.size() == (params.sigma2() * params.q()) / 8, "Wrong random bits size"); |
258 | |
|
259 | 0 | auto a = load_le<secure_vector<uint32_t>>(random_bits); // contains a_0, a_1, ... |
260 | 0 | auto [sorted_a, pi] = create_pi(std::move(a)); |
261 | 0 | if(ct_has_adjacent_duplicates(sorted_a).as_bool()) { |
262 | 0 | return std::nullopt; |
263 | 0 | } |
264 | | |
265 | 0 | return Classic_McEliece_Field_Ordering(std::move(pi), params.poly_f()); |
266 | 0 | } |
267 | | |
268 | 0 | std::vector<Classic_McEliece_GF> Classic_McEliece_Field_Ordering::alphas(size_t n) const { |
269 | 0 | BOTAN_ASSERT_NOMSG(m_poly_f.get() != 0); |
270 | 0 | BOTAN_ASSERT_NOMSG(m_pi.size() >= n); |
271 | |
|
272 | 0 | std::vector<Classic_McEliece_GF> n_alphas_vec; |
273 | |
|
274 | 0 | std::transform(m_pi.begin(), m_pi.begin() + n, std::back_inserter(n_alphas_vec), [this](uint16_t pi_elem) { |
275 | 0 | return from_pi(CmcePermutationElement(pi_elem), m_poly_f, Classic_McEliece_GF::log_q_from_mod(m_poly_f)); |
276 | 0 | }); |
277 | |
|
278 | 0 | return n_alphas_vec; |
279 | 0 | } |
280 | | |
281 | 0 | secure_bitvector Classic_McEliece_Field_Ordering::alphas_control_bits() const { |
282 | | // Each vector element contains one bit of the control bits |
283 | 0 | const auto control_bits_as_words = generate_control_bits_internal(m_pi.get()); |
284 | 0 | auto control_bits = secure_bitvector(control_bits_as_words.size()); |
285 | 0 | for(size_t i = 0; i < control_bits.size(); ++i) { |
286 | 0 | control_bits.at(i) = control_bits_as_words.at(i); |
287 | 0 | } |
288 | |
|
289 | 0 | return control_bits; |
290 | 0 | } |
291 | | |
292 | | // Based on the Python code "permutation(c)" from Bernstein |
293 | | // "Verified fast formulas for control bits for permutation networks" |
294 | | Classic_McEliece_Field_Ordering Classic_McEliece_Field_Ordering::create_from_control_bits( |
295 | 0 | const Classic_McEliece_Parameters& params, const secure_bitvector& control_bits) { |
296 | 0 | BOTAN_ASSERT_NOMSG(control_bits.size() == (2 * params.m() - 1) << (params.m() - 1)); |
297 | 0 | const uint16_t n = uint16_t(1) << params.m(); |
298 | 0 | CmcePermutation pi(n); |
299 | 0 | std::iota(pi.begin(), pi.end(), 0); |
300 | 0 | for(size_t i = 0; i < 2 * params.m() - 1; ++i) { |
301 | 0 | const size_t gap = size_t(1) << std::min(i, 2 * params.m() - 2 - i); |
302 | 0 | for(size_t j = 0; j < size_t(n / 2); ++j) { |
303 | 0 | const size_t pos = (j % gap) + 2 * gap * (j / gap); |
304 | 0 | auto mask = CT::Mask<uint16_t>::expand(control_bits[i * n / 2 + j]); |
305 | 0 | mask.conditional_swap(pi[pos], pi[pos + gap]); |
306 | 0 | } |
307 | 0 | } |
308 | |
|
309 | 0 | return Classic_McEliece_Field_Ordering(std::move(pi), params.poly_f()); |
310 | 0 | } |
311 | | |
312 | | void Classic_McEliece_Field_Ordering::permute_with_pivots(const Classic_McEliece_Parameters& params, |
313 | 0 | const CmceColumnSelection& pivots) { |
314 | 0 | auto col_offset = params.pk_no_rows() - Classic_McEliece_Parameters::mu(); |
315 | |
|
316 | 0 | for(size_t p_idx = 1; p_idx <= Classic_McEliece_Parameters::mu(); ++p_idx) { |
317 | 0 | size_t p_counter = 0; |
318 | 0 | for(size_t col = 0; col < Classic_McEliece_Parameters::nu(); ++col) { |
319 | 0 | auto mask_is_pivot_set = CT::Mask<size_t>::expand(pivots.at(col)); |
320 | 0 | p_counter += CT::Mask<size_t>::expand(pivots.at(col)).if_set_return(1); |
321 | 0 | auto mask_is_current_pivot = CT::Mask<size_t>::is_equal(p_idx, p_counter); |
322 | 0 | (mask_is_pivot_set & mask_is_current_pivot) |
323 | 0 | .conditional_swap(m_pi.get().at(col_offset + col), m_pi.get().at(col_offset + p_idx - 1)); |
324 | 0 | } |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | | } // namespace Botan |