/src/botan/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Classic McEliece Matrix Logic |
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 | | * |
7 | | * (C) 2023 Jack Lloyd |
8 | | * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity |
9 | | * |
10 | | * Botan is released under the Simplified BSD License (see license.txt) |
11 | | **/ |
12 | | |
13 | | #include <botan/internal/cmce_matrix.h> |
14 | | |
15 | | #include <botan/strong_type.h> |
16 | | |
17 | | namespace Botan { |
18 | | |
19 | | namespace { |
20 | | |
21 | | // Strong types for matrix used internally by Classic_McEliece_Matrix |
22 | | using CmceMatrixRow = Strong<secure_bitvector, struct CmceMatrixRow_>; |
23 | | using CmceMatrix = Strong<std::vector<CmceMatrixRow>, struct CmceMatrix_>; |
24 | | |
25 | | } // Anonymous namespace |
26 | | |
27 | | namespace { |
28 | | |
29 | 0 | CT::Mask<uint64_t> bit_at_mask(uint64_t val, size_t pos) { |
30 | 0 | return CT::Mask<uint64_t>::expand((static_cast<uint64_t>(1) << pos) & val); |
31 | 0 | } |
32 | | |
33 | | /// Swaps bit i with bit j in val |
34 | 0 | void swap_bits(uint64_t& val, size_t i, size_t j) { |
35 | 0 | uint64_t bit_i = (val >> i) & CT::value_barrier<uint64_t>(1); |
36 | 0 | uint64_t bit_j = (val >> j) & CT::value_barrier<uint64_t>(1); |
37 | 0 | uint64_t xor_sum = bit_i ^ bit_j; |
38 | 0 | val ^= (xor_sum << i); |
39 | 0 | val ^= (xor_sum << j); |
40 | 0 | } |
41 | | |
42 | 0 | size_t count_lsb_zeros(uint64_t n) { |
43 | 0 | size_t res = 0; |
44 | 0 | auto found_only_zeros = Botan::CT::Mask<uint64_t>::set(); |
45 | 0 | for(size_t bit_pos = 0; bit_pos < sizeof(uint64_t) * 8; ++bit_pos) { |
46 | 0 | auto bit_set_mask = bit_at_mask(n, bit_pos); |
47 | 0 | found_only_zeros &= ~bit_set_mask; |
48 | 0 | res += static_cast<size_t>(found_only_zeros.if_set_return(1)); |
49 | 0 | } |
50 | |
|
51 | 0 | return res; |
52 | 0 | } |
53 | | |
54 | | CmceMatrix init_matrix_with_alphas(const Classic_McEliece_Parameters& params, |
55 | | const Classic_McEliece_Field_Ordering& field_ordering, |
56 | 0 | const Classic_McEliece_Minimal_Polynomial& g) { |
57 | 0 | auto alphas = field_ordering.alphas(params.n()); |
58 | 0 | std::vector<Classic_McEliece_GF> inv_g_of_alpha; |
59 | 0 | inv_g_of_alpha.reserve(params.n()); |
60 | 0 | for(const auto& alpha : alphas) { |
61 | 0 | inv_g_of_alpha.push_back(g(alpha).inv()); |
62 | 0 | } |
63 | 0 | CmceMatrix mat(std::vector<CmceMatrixRow>(params.pk_no_rows(), CmceMatrixRow(params.n()))); |
64 | |
|
65 | 0 | for(size_t i = 0; i < params.t(); ++i) { |
66 | 0 | for(size_t j = 0; j < params.n(); ++j) { |
67 | 0 | for(size_t alpha_i_j_bit = 0; alpha_i_j_bit < params.m(); ++alpha_i_j_bit) { |
68 | 0 | mat[i * params.m() + alpha_i_j_bit][j] = (uint16_t(1) << alpha_i_j_bit) & inv_g_of_alpha[j].elem().get(); |
69 | 0 | } |
70 | 0 | } |
71 | | // Update for the next i so that: |
72 | | // inv_g_of_alpha[j] = h_i_j = alpha_j^i/g(alpha_j) |
73 | 0 | for(size_t j = 0; j < params.n(); ++j) { |
74 | 0 | inv_g_of_alpha.at(j) *= alphas.at(j); |
75 | 0 | } |
76 | 0 | } |
77 | |
|
78 | 0 | return mat; |
79 | 0 | } |
80 | | |
81 | 0 | std::optional<CmceColumnSelection> move_columns(CmceMatrix& mat, const Classic_McEliece_Parameters& params) { |
82 | 0 | BOTAN_ASSERT(mat.size() == params.pk_no_rows(), "Matrix has incorrect number of rows"); |
83 | 0 | BOTAN_ASSERT(mat.get().at(0).size() == params.n(), "Matrix has incorrect number of columns"); |
84 | 0 | static_assert(Classic_McEliece_Parameters::nu() == 64, "nu needs to be 64"); |
85 | |
|
86 | 0 | const size_t pos_offset = params.pk_no_rows() - Classic_McEliece_Parameters::mu(); |
87 | | |
88 | | // Get the area of the matrix that needs to be (potentially) swapped. |
89 | | // Its the sub m*t x nu matrix at column m*t - mu. For const time reasons, |
90 | | // the sub-matrix is represented as an array of uint64_ts, where the 1st |
91 | | // bit is the least significant bit |
92 | 0 | std::vector<uint64_t> matrix_swap_area; |
93 | 0 | matrix_swap_area.reserve(params.pk_no_rows()); |
94 | 0 | for(size_t i = 0; i < params.pk_no_rows(); ++i) { |
95 | 0 | matrix_swap_area.push_back(mat[i].subvector<uint64_t>(pos_offset)); |
96 | 0 | } |
97 | | |
98 | | // To find which columns need to be swapped to allow for a systematic matrix form, we need to |
99 | | // investigate how a gauss algorithm affects the last mu rows of the swap area. |
100 | 0 | std::array<uint64_t, Classic_McEliece_Parameters::mu()> sub_mat; |
101 | | |
102 | | // Extract the bottom mu x nu matrix at offset pos_offset |
103 | 0 | for(size_t i = 0; i < Classic_McEliece_Parameters::mu(); i++) { |
104 | 0 | sub_mat[i] = matrix_swap_area[pos_offset + i]; |
105 | 0 | } |
106 | |
|
107 | 0 | std::array<size_t, Classic_McEliece_Parameters::mu()> pivot_indices = {0}; // ctz_list |
108 | | |
109 | | // Identify the pivot indices, i.e., the indices of the leading ones for all rows |
110 | | // when transforming the matrix into semi-systematic form. This algorithm is a modified |
111 | | // Gauss algorithm. |
112 | 0 | for(size_t row_idx = 0; row_idx < Classic_McEliece_Parameters::mu(); ++row_idx) { |
113 | | // Identify pivots (index of first 1) by OR-ing all subsequent rows into row_acc |
114 | 0 | auto row_acc = sub_mat.at(row_idx); |
115 | 0 | for(size_t next_row = row_idx + 1; next_row < Classic_McEliece_Parameters::mu(); ++next_row) { |
116 | 0 | row_acc |= sub_mat.at(next_row); |
117 | 0 | } |
118 | |
|
119 | 0 | auto semi_systematic_form_failed = CT::Mask<uint64_t>::is_zero(row_acc); |
120 | 0 | if(semi_systematic_form_failed.as_choice().as_bool()) { |
121 | | // If the current row and all subsequent rows are zero |
122 | | // we cannot create a semi-systematic matrix |
123 | 0 | return std::nullopt; |
124 | 0 | } |
125 | | |
126 | | // Using the row accumulator we can predict the index of the pivot |
127 | | // bit for the current row, i.e., the first index where we can set |
128 | | // the bit to one row by adding any subsequent row |
129 | 0 | size_t current_pivot_idx = count_lsb_zeros(row_acc); |
130 | 0 | pivot_indices.at(row_idx) = current_pivot_idx; |
131 | | |
132 | | // Add subsequent rows to the current row, until the pivot |
133 | | // bit is set. |
134 | 0 | for(size_t next_row = row_idx + 1; next_row < Classic_McEliece_Parameters::mu(); ++next_row) { |
135 | | // Add next row if the pivot bit is still zero |
136 | 0 | auto add_next_row_mask = ~bit_at_mask(sub_mat.at(row_idx), current_pivot_idx); |
137 | 0 | sub_mat.at(row_idx) ^= add_next_row_mask.if_set_return(sub_mat.at(next_row)); |
138 | 0 | } |
139 | | |
140 | | // Add the (new) current row to all subsequent rows, where the leading |
141 | | // bit of the current bit is one. Therefore, the column of the leading |
142 | | // bit becomes zero. |
143 | | // Note: In normal gauss, we would also add the current row to rows |
144 | | // above the current one. However, here we only need to identify |
145 | | // the columns to swap. Therefore, we can ignore the upper rows. |
146 | 0 | for(size_t next_row = row_idx + 1; next_row < Classic_McEliece_Parameters::mu(); ++next_row) { |
147 | | // Add the current row to next_row if the pivot bit of next_row is set |
148 | 0 | auto add_to_next_row_mask = bit_at_mask(sub_mat.at(next_row), current_pivot_idx); |
149 | 0 | sub_mat.at(next_row) ^= add_to_next_row_mask.if_set_return(sub_mat.at(row_idx)); |
150 | 0 | } |
151 | 0 | } |
152 | | |
153 | | // Create pivot bitvector from the pivot index vector |
154 | 0 | CmceColumnSelection pivots(Classic_McEliece_Parameters::nu()); |
155 | 0 | for(auto pivot_idx : pivot_indices) { |
156 | 0 | for(size_t i = 0; i < Classic_McEliece_Parameters::nu(); ++i) { |
157 | 0 | auto mask_is_at_current_idx = Botan::CT::Mask<size_t>::is_equal(i, pivot_idx); |
158 | 0 | pivots.at(i) = mask_is_at_current_idx.select(1, pivots.at(i).as<size_t>()); |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | | // Swap the rows so the matrix can be transformed into systematic form |
163 | 0 | for(size_t mat_row = 0; mat_row < params.pk_no_rows(); ++mat_row) { |
164 | 0 | for(size_t col = 0; col < Classic_McEliece_Parameters::mu(); ++col) { |
165 | 0 | swap_bits(matrix_swap_area.at(mat_row), col, pivot_indices.at(col)); |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | // Reinsert the swapped columns into the matrix |
170 | 0 | for(size_t row = 0; row < params.pk_no_rows(); ++row) { |
171 | 0 | mat[row].subvector_replace(pos_offset, matrix_swap_area[row]); |
172 | 0 | } |
173 | |
|
174 | 0 | return pivots; |
175 | 0 | } |
176 | | |
177 | 0 | std::optional<CmceColumnSelection> apply_gauss(const Classic_McEliece_Parameters& params, CmceMatrix& mat) { |
178 | 0 | BOTAN_ASSERT(mat.size() == params.pk_no_rows(), "Matrix has incorrect number of rows"); |
179 | 0 | BOTAN_ASSERT(mat.get().at(0).size() == params.n(), "Matrix has incorrect number of columns"); |
180 | | // Initialized for systematic form instances |
181 | | // Is overridden for semi systematic instances |
182 | 0 | auto pivots = CmceColumnSelection({0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0}); |
183 | | |
184 | | // Gaussian Elimination |
185 | 0 | for(size_t diag_pos = 0; diag_pos < params.pk_no_rows(); ++diag_pos) { |
186 | 0 | if(params.is_f() && diag_pos == params.pk_no_rows() - params.mu()) { |
187 | 0 | auto ret_pivots = move_columns(mat, params); |
188 | 0 | bool move_columns_failed = !ret_pivots.has_value(); |
189 | 0 | CT::unpoison(move_columns_failed); |
190 | 0 | if(move_columns_failed) { |
191 | 0 | return std::nullopt; |
192 | 0 | } else { |
193 | 0 | pivots = std::move(ret_pivots.value()); |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | | // Iterates over all rows next_row under row diag_pos. If the bit at column |
198 | | // diag_pos differs between row diag_pos and row next_row, row next_row is added to row diag_pos. |
199 | | // This achieves that the respective bit at the diagonal becomes 1 |
200 | | // (if mat is systematic) |
201 | 0 | for(size_t next_row = diag_pos + 1; next_row < params.pk_no_rows(); ++next_row) { |
202 | 0 | mat[diag_pos].get().ct_conditional_xor(!mat[diag_pos].at(diag_pos).as_choice(), mat[next_row].get()); |
203 | 0 | } |
204 | | |
205 | | // If the current bit on the diagonal is not set at this point |
206 | | // the matrix is not systematic. We abort the computation in this case. |
207 | 0 | bool diag_bit_zero = !mat[diag_pos].at(diag_pos); |
208 | 0 | CT::unpoison(diag_bit_zero); |
209 | 0 | if(diag_bit_zero) { |
210 | 0 | return std::nullopt; |
211 | 0 | } |
212 | | |
213 | | // Now the new row is added to all other rows, where the |
214 | | // bit in the column of the current postion on the diagonal |
215 | | // is still one |
216 | 0 | for(size_t row = 0; row < params.pk_no_rows(); ++row) { |
217 | 0 | if(row != diag_pos) { |
218 | 0 | mat[row].get().ct_conditional_xor(mat[row].at(diag_pos).as_choice(), mat[diag_pos].get()); |
219 | 0 | } |
220 | 0 | } |
221 | 0 | } |
222 | | |
223 | 0 | return pivots; |
224 | 0 | } |
225 | | |
226 | 0 | std::vector<uint8_t> extract_pk_bytes_from_matrix(const Classic_McEliece_Parameters& params, const CmceMatrix& mat) { |
227 | | // Store T of the matrix (I_mt|T) as a linear vector to represent the |
228 | | // public key as defined in McEliece ISO 9.2.7 |
229 | 0 | std::vector<uint8_t> big_t(params.pk_size_bytes()); |
230 | 0 | auto big_t_stuffer = BufferStuffer(big_t); |
231 | |
|
232 | 0 | for(size_t row = 0; row < params.pk_no_rows(); ++row) { |
233 | 0 | mat[row].subvector(params.pk_no_rows()).to_bytes(big_t_stuffer.next(params.pk_row_size_bytes())); |
234 | 0 | } |
235 | |
|
236 | 0 | BOTAN_ASSERT_NOMSG(big_t_stuffer.full()); |
237 | |
|
238 | 0 | return big_t; |
239 | 0 | } |
240 | | |
241 | | } // namespace |
242 | | |
243 | | std::optional<std::pair<Classic_McEliece_Matrix, CmceColumnSelection>> Classic_McEliece_Matrix::create_matrix( |
244 | | const Classic_McEliece_Parameters& params, |
245 | | const Classic_McEliece_Field_Ordering& field_ordering, |
246 | 0 | const Classic_McEliece_Minimal_Polynomial& g) { |
247 | 0 | auto mat = init_matrix_with_alphas(params, field_ordering, g); |
248 | 0 | auto pivots = apply_gauss(params, mat); |
249 | |
|
250 | 0 | auto gauss_failed = !pivots.has_value(); |
251 | 0 | CT::unpoison(gauss_failed); |
252 | 0 | if(gauss_failed) { |
253 | 0 | return std::nullopt; |
254 | 0 | } |
255 | | |
256 | 0 | auto pk_mat_bytes = extract_pk_bytes_from_matrix(params, mat); |
257 | 0 | return std::make_pair(Classic_McEliece_Matrix(params, std::move(pk_mat_bytes)), pivots.value()); |
258 | 0 | } |
259 | | |
260 | | std::optional<std::pair<Classic_McEliece_Matrix, CmceColumnSelection>> |
261 | | Classic_McEliece_Matrix::create_matrix_and_apply_pivots(const Classic_McEliece_Parameters& params, |
262 | | Classic_McEliece_Field_Ordering& field_ordering, |
263 | 0 | const Classic_McEliece_Minimal_Polynomial& g) { |
264 | 0 | auto pk_matrix_and_pivots = create_matrix(params, field_ordering, g); |
265 | |
|
266 | 0 | bool matrix_creation_failed = !pk_matrix_and_pivots.has_value(); |
267 | 0 | CT::unpoison(matrix_creation_failed); |
268 | 0 | if(matrix_creation_failed) { |
269 | 0 | return std::nullopt; |
270 | 0 | } |
271 | | |
272 | 0 | auto& [_, pivots] = pk_matrix_and_pivots.value(); |
273 | |
|
274 | 0 | if(params.is_f()) { |
275 | 0 | field_ordering.permute_with_pivots(params, pivots); |
276 | 0 | } |
277 | |
|
278 | 0 | return pk_matrix_and_pivots; |
279 | 0 | } |
280 | | |
281 | 0 | CmceCodeWord Classic_McEliece_Matrix::mul(const Classic_McEliece_Parameters& params, const CmceErrorVector& e) const { |
282 | 0 | auto s = e.subvector<CmceCodeWord>(0, params.pk_no_rows()); |
283 | 0 | auto e_T = e.subvector(params.pk_no_rows()); |
284 | 0 | auto pk_slicer = BufferSlicer(m_mat_bytes); |
285 | |
|
286 | 0 | for(size_t i = 0; i < params.pk_no_rows(); ++i) { |
287 | 0 | auto pk_current_bytes = pk_slicer.take(params.pk_row_size_bytes()); |
288 | 0 | auto row = secure_bitvector(pk_current_bytes, params.n() - params.pk_no_rows()); |
289 | 0 | row &= e_T; |
290 | 0 | s[i] ^= row.has_odd_hamming_weight().as_bool(); |
291 | 0 | } |
292 | |
|
293 | 0 | BOTAN_ASSERT_NOMSG(pk_slicer.empty()); |
294 | 0 | return s; |
295 | 0 | } |
296 | | } // namespace Botan |