Coverage Report

Created: 2024-11-29 06:10

/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