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