• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 11844561993

14 Nov 2024 07:58PM UTC coverage: 91.178% (+0.1%) from 91.072%
11844561993

Pull #4435

github

web-flow
Merge 81dcb29da into e430f157a
Pull Request #4435: Test duration values ​​are now presented in seconds with six digits of precision. Tests without time measurements have been edited.

91856 of 100744 relevant lines covered (91.18%)

9311006.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.87
/src/tests/test_crystals.cpp
1
/*
2
 * Tests for PQ Crystals
3
 * (C) 2024 Jack Lloyd
4
 * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity
5
 *
6
 * Botan is released under the Simplified BSD License (see license.txt)
7
 */
8

9
#include "tests.h"
10

11
#if defined(BOTAN_HAS_PQCRYSTALS)
12
   #include <botan/hex.h>
13

14
   #include <botan/internal/fmt.h>
15
   #include <botan/internal/pqcrystals.h>
16
   #include <botan/internal/pqcrystals_encoding.h>
17
   #include <botan/internal/pqcrystals_helpers.h>
18
   #include <botan/internal/stl_util.h>
19

20
namespace Botan_Tests {
21

22
namespace {
23

24
template <std::integral T>
25
consteval T gcd(T x, T y) {
26
   return Botan::extended_euclidean_algorithm<T>(x, y).gcd;
27
}
28

29
template <std::integral T>
30
consteval T v(T x, T y) {
31
   return Botan::extended_euclidean_algorithm<T>(x, y).v;
32
}
33

34
template <std::integral T>
35
consteval T u(T x, T y) {
36
   return Botan::extended_euclidean_algorithm<T>(x, y).u;
37
}
38

39
Test::Result test_extended_euclidean_algorithm() {
1✔
40
   Test::Result res("Extended Euclidean Algorithm");
1✔
41
   res.start_timer();
1✔
42

43
   // The wrapper template functions gcd<>(), v<>() and u<>() are workarounds
44
   // for an assumed bug in MSVC 19.38.33134 that does not accept the invocation
45
   // of the consteval function `extended_euclidean_algorithm` as a parameter to
46
   // `test_is_eq()`.
47
   //
48
   // The resulting error is:
49
   //    error C7595: 'Botan::extended_euclidean_algorithm': call to immediate function is not a constant expression
50
   //
51
   // What we'd actually want to write here:
52
   //    res.test_is_eq<uint32_t>("gcd(350, 294)", Botan::extended_euclidean_algorithm<uint32_t>(350, 294).gcd, 14);
53
   res.test_is_eq<uint32_t>("gcd(1337, 1337)", gcd<uint32_t>(1337, 1337), 1337);
1✔
54
   res.test_is_eq<uint32_t>("gcd(350, 294)", gcd<uint32_t>(350, 294), 14);
1✔
55
   res.test_is_eq<uint32_t>("gcd(294, 350)", gcd<uint32_t>(294, 350), 14);
1✔
56

57
   res.test_is_eq<uint16_t>("gcd(1337, 1337)", gcd<uint16_t>(1337, 1337), 1337);
1✔
58
   res.test_is_eq<uint16_t>("gcd(350, 294)", gcd<uint16_t>(350, 294), 14);
1✔
59
   res.test_is_eq<uint16_t>("gcd(294, 350)", gcd<uint16_t>(294, 350), 14);
1✔
60

61
   res.test_is_eq<uint16_t>("u(1337, 1337)", u<uint16_t>(1337, 1337), 0);
1✔
62
   res.test_is_eq<uint16_t>("v(1337, 1337)", v<uint16_t>(1337, 1337), 1);
1✔
63
   res.test_is_eq<uint16_t>("u(294, 350)", u<uint16_t>(294, 350), 6);
1✔
64

65
   res.test_is_eq<int16_t>("q^-1(3329) - Kyber::Q", Botan::modular_inverse<int16_t>(3329), -3327);
1✔
66
   res.test_is_eq<int32_t>("q^-1(8380417) - Dilithium::Q", Botan::modular_inverse<int32_t>(8380417), 58728449);
1✔
67

68
   res.end_timer();
1✔
69
   return res;
1✔
70
}
×
71

72
// Equivalent to Kyber's constants
73
struct Kyberish_Constants {
74
      using T = int16_t;
75
      static constexpr T N = 256;
76
      static constexpr T Q = 3329;
77
      static constexpr T F = 3303;
78
      static constexpr T ROOT_OF_UNITY = 17;
79
      static constexpr size_t NTT_Degree = 128;
80
};
81

82
// Equivalent to Dilithium's constants
83
struct Dilithiumish_Constants {
84
      using T = int32_t;
85
      static constexpr T N = 256;
86
      static constexpr T Q = 8380417;
87
      static constexpr T F = 8347681;
88
      static constexpr T ROOT_OF_UNITY = 1753;
89
      static constexpr size_t NTT_Degree = 256;
90
};
91

92
template <typename ConstsT>
93
class Mock_Trait final : public Botan::CRYSTALS::Trait_Base<ConstsT, Mock_Trait<ConstsT>> {
94
   public:
95
      using T = typename Botan::CRYSTALS::Trait_Base<ConstsT, Mock_Trait<ConstsT>>::T;
96
      using T2 = typename Botan::CRYSTALS::Trait_Base<ConstsT, Mock_Trait<ConstsT>>::T2;
97
      constexpr static auto N = Botan::CRYSTALS::Trait_Base<ConstsT, Mock_Trait<ConstsT>>::N;
98

99
      static T montgomery_reduce_coefficient(T2) {
100
         throw Botan_Tests::Test_Error("montgomery reduction not implemented");
101
      }
102

103
      static T barrett_reduce_coefficient(T) { throw Botan_Tests::Test_Error("barrett reduction not implemented"); }
104

105
      static void ntt(std::span<T, N>) { throw Botan_Tests::Test_Error("NTT not implemented"); }
106

107
      static void inverse_ntt(std::span<T, N>) { throw Botan_Tests::Test_Error("inverse NTT not implemented"); }
108

109
      static void poly_pointwise_montgomery(std::span<T, N>, std::span<T, N>, std::span<T, N>) {
110
         throw Botan_Tests::Test_Error("pointwise multiplication not implemented");
111
      }
112
};
113

114
using Kyberish_Trait = Mock_Trait<Kyberish_Constants>;
115

116
using Domain = Botan::CRYSTALS::Domain;
117

118
template <Domain D>
119
using Kyberish_Poly = Botan::CRYSTALS::Polynomial<Kyberish_Trait, D>;
120

121
template <Domain D>
122
using Kyberish_PolyVec = Botan::CRYSTALS::PolynomialVector<Kyberish_Trait, D>;
123

124
std::vector<Test::Result> test_polynomial_basics() {
1✔
125
   return {
1✔
126
      CHECK("polynomial owning storage",
127
            [](Test::Result& res) {
1✔
128
               res.start_timer();
1✔
129
               Kyberish_Poly<Domain::Normal> p;
1✔
130
               res.confirm("default constructed poly owns memory", p.owns_storage());
2✔
131
               for(auto coeff : p) {
257✔
132
                  res.test_is_eq<int16_t>("default constructed poly has 0 coefficients", coeff, 0);
512✔
133
               }
134

135
               Kyberish_Poly<Domain::NTT> p_ntt;
1✔
136
               res.confirm("default constructed poly owns memory (NTT)", p_ntt.owns_storage());
2✔
137
               for(auto coeff : p) {
257✔
138
                  res.test_is_eq<int16_t>("default constructed poly (NTT) has 0 coefficients", coeff, 0);
512✔
139
               }
140
               res.end_timer();
1✔
141
            }),
2✔
142

143
      CHECK("polynomial vector managing storage",
144
            [](Test::Result& res) {
1✔
145
               res.start_timer();
1✔
146
               Kyberish_PolyVec<Domain::Normal> polys(4);
1✔
147
               res.test_is_eq<size_t>("requested size", polys.size(), 4);
1✔
148

149
               for(const auto& poly : polys) {
5✔
150
                  res.confirm("poly embedded in vector does not own memory", !poly.owns_storage());
8✔
151
               }
152

153
               Kyberish_PolyVec<Domain::NTT> polys_ntt(4);
1✔
154
               res.test_is_eq<size_t>("requested size (NTT)", polys.size(), 4);
1✔
155

156
               for(const auto& poly : polys_ntt) {
5✔
157
                  res.confirm("poly (NTT) embedded in vector does not own memory", !poly.owns_storage());
8✔
158
               }
159
               res.end_timer();
1✔
160
            }),
1✔
161

162
      CHECK("cloned polynomials always manage their storge",
163
            [](Test::Result& res) {
1✔
164
               res.start_timer();
1✔
165
               Kyberish_Poly<Domain::Normal> p;
1✔
166
               auto p2 = p.clone();
1✔
167
               res.confirm("cloned poly owns memory", p2.owns_storage());
2✔
168

169
               Kyberish_PolyVec<Domain::Normal> pv(3);
1✔
170
               for(auto& poly : pv) {
4✔
171
                  res.require("poly in vector does not own memory", !poly.owns_storage());
3✔
172
                  auto pv2 = poly.clone();
3✔
173
                  res.confirm("cloned poly in vector owns memory", pv2.owns_storage());
6✔
174
               }
3✔
175

176
               auto pv2 = pv.clone();
1✔
177
               for(const auto& poly : pv2) {
4✔
178
                  res.confirm("cloned vector polynomial don't own memory", !poly.owns_storage());
6✔
179
               }
180

181
               Kyberish_Poly<Domain::NTT> p_ntt;
1✔
182
               auto p2_ntt = p_ntt.clone();
1✔
183
               res.confirm("cloned poly (NTT) owns memory", p2_ntt.owns_storage());
2✔
184

185
               Kyberish_PolyVec<Domain::NTT> pv_ntt(3);
1✔
186
               for(auto& poly : pv_ntt) {
4✔
187
                  res.require("poly (NTT) in vector does not own memory", !poly.owns_storage());
3✔
188
                  auto pv2_ntt = poly.clone();
3✔
189
                  res.confirm("cloned poly (NTT) in vector owns memory", pv2_ntt.owns_storage());
6✔
190
               }
3✔
191

192
               auto pv2_ntt = pv_ntt.clone();
1✔
193
               for(const auto& poly : pv2_ntt) {
4✔
194
                  res.confirm("cloned vector polynomial (NTT) don't own memory", !poly.owns_storage());
6✔
195
               }
196
               res.end_timer();
1✔
197
            }),
5✔
198

199
      CHECK("hamming weight of polynomials",
200
            [](Test::Result& res) {
1✔
201
               res.start_timer();
1✔
202
               Kyberish_Poly<Domain::Normal> p;
1✔
203
               res.test_is_eq<size_t>("hamming weight of 0", p.hamming_weight(), 0);
2✔
204

205
               p[0] = 1337;
1✔
206
               res.test_is_eq<size_t>("hamming weight of 1", p.hamming_weight(), 1);
2✔
207

208
               p[1] = 42;
1✔
209
               res.test_is_eq<size_t>("hamming weight of 2", p.hamming_weight(), 2);
2✔
210

211
               p[2] = 11;
1✔
212
               res.test_is_eq<size_t>("hamming weight of 3", p.hamming_weight(), 3);
2✔
213

214
               p[3] = 4;
1✔
215
               res.test_is_eq<size_t>("hamming weight of 4", p.hamming_weight(), 4);
2✔
216

217
               p[3] = 0;
1✔
218
               res.test_is_eq<size_t>("hamming weight of 3", p.hamming_weight(), 3);
2✔
219

220
               p[2] = 0;
1✔
221
               res.test_is_eq<size_t>("hamming weight of 2", p.hamming_weight(), 2);
2✔
222

223
               p[1] = 0;
1✔
224
               res.test_is_eq<size_t>("hamming weight of 1", p.hamming_weight(), 1);
2✔
225

226
               p[0] = 0;
1✔
227
               res.test_is_eq<size_t>("hamming weight of 0", p.hamming_weight(), 0);
2✔
228
               res.end_timer();
1✔
229
            }),
1✔
230

231
      CHECK("hamming weight of polynomial vectors",
232
            [](Test::Result& res) {
1✔
233
               res.start_timer();
1✔
234
               Kyberish_PolyVec<Domain::Normal> pv(3);
1✔
235
               res.test_is_eq<size_t>("hamming weight of 0", pv.hamming_weight(), 0);
2✔
236

237
               pv[0][0] = 1337;
1✔
238
               res.test_is_eq<size_t>("hamming weight of 1", pv.hamming_weight(), 1);
2✔
239

240
               pv[1][1] = 42;
1✔
241
               res.test_is_eq<size_t>("hamming weight of 2", pv.hamming_weight(), 2);
2✔
242

243
               pv[2][2] = 11;
1✔
244
               res.test_is_eq<size_t>("hamming weight of 3", pv.hamming_weight(), 3);
2✔
245

246
               pv[2][2] = 0;
1✔
247
               res.test_is_eq<size_t>("hamming weight of 2", pv.hamming_weight(), 2);
2✔
248

249
               pv[1][1] = 0;
1✔
250
               res.test_is_eq<size_t>("hamming weight of 1", pv.hamming_weight(), 1);
2✔
251

252
               pv[0][0] = 0;
1✔
253
               res.test_is_eq<size_t>("hamming weight of 0", pv.hamming_weight(), 0);
2✔
254
               res.end_timer();
1✔
255
            }),
1✔
256

257
      CHECK("value range validation",
258
            [](Test::Result& res) {
1✔
259
               res.start_timer();
1✔
260
               Kyberish_Poly<Domain::Normal> p;
1✔
261
               res.confirm("value range validation (all zero)", p.ct_validate_value_range(0, 1));
2✔
262

263
               p[0] = 1;
1✔
264
               p[32] = 1;
1✔
265
               p[172] = 1;
1✔
266
               res.confirm("value range validation", p.ct_validate_value_range(0, 1));
2✔
267

268
               p[11] = 2;
1✔
269
               res.confirm("value range validation", !p.ct_validate_value_range(0, 1));
2✔
270

271
               p[11] = -1;
1✔
272
               res.confirm("value range validation", !p.ct_validate_value_range(0, 1));
2✔
273
               res.end_timer();
1✔
274
            }),
1✔
275

276
      CHECK("value range validation for polynomial vectors",
277
            [](Test::Result& res) {
1✔
278
               res.start_timer();
1✔
279
               Kyberish_PolyVec<Domain::Normal> pv(3);
1✔
280
               res.confirm("value range validation (all zero)", pv.ct_validate_value_range(0, 1));
2✔
281

282
               pv[0][0] = 1;
1✔
283
               pv[1][32] = 1;
1✔
284
               pv[2][172] = 1;
1✔
285
               res.confirm("value range validation", pv.ct_validate_value_range(0, 1));
2✔
286

287
               pv[0][11] = 2;
1✔
288
               res.confirm("value range validation", !pv.ct_validate_value_range(0, 1));
2✔
289

290
               pv[0][11] = -1;
1✔
291
               res.confirm("value range validation", !pv.ct_validate_value_range(0, 1));
2✔
292
               res.end_timer();
1✔
293
            }),
1✔
294
   };
8✔
295
}
1✔
296

297
namespace {
298

299
class DeterministicXOF : public Botan::XOF {
1✔
300
   public:
301
      DeterministicXOF(std::span<const uint8_t> data) : m_data(data) {}
3✔
302

303
      std::string name() const override { return "DeterministicXOF"; }
×
304

305
      bool accepts_input() const override { return false; }
×
306

307
      std::unique_ptr<XOF> copy_state() const override { throw Botan_Tests::Test_Error("copy_state not implemented"); }
×
308

309
      std::unique_ptr<XOF> new_object() const override { throw Botan_Tests::Test_Error("new_object not implemented"); }
×
310

311
      size_t block_size() const override { return 1; }
×
312

313
      void start_msg(std::span<const uint8_t>, std::span<const uint8_t>) override {
×
314
         throw Botan_Tests::Test_Error("start_msg not implemented");
×
315
      }
316

317
      void add_data(std::span<const uint8_t>) override { throw Botan_Tests::Test_Error("add_data not implemented"); }
×
318

319
      void generate_bytes(std::span<uint8_t> output) override { m_data.copy_into(output); }
112✔
320

321
      void reset() override {}
×
322

323
   private:
324
      Botan::BufferSlicer m_data;
325
};
326

327
template <Botan::CRYSTALS::crystals_trait Trait, int32_t range>
328
void random_encoding_roundtrips(Test::Result& res, Botan::RandomNumberGenerator& rng, size_t expected_encoding_bits) {
22✔
329
   res.start_timer();
22✔
330
   using Poly = Botan::CRYSTALS::Polynomial<Trait, Domain::Normal>;
331
   using T = typename Trait::T;
332

333
   auto random_poly = [&rng]() -> Poly {
44✔
334
      Poly p;
22✔
335
      std::array<uint8_t, sizeof(T)> buf;
336
      for(auto& coeff : p) {
5,654✔
337
         rng.randomize(buf);
5,632✔
338
         coeff = static_cast<T>((Botan::load_be(buf) % (range + 1)));
5,632✔
339
      }
340
      return p;
22✔
341
   };
×
342

343
   const auto p = random_poly();
22✔
344
   std::vector<uint8_t> buffer((p.size() * expected_encoding_bits + 7) / 8);
22✔
345
   Botan::BufferStuffer stuffer(buffer);
22✔
346
   Botan::CRYSTALS::pack<range>(p, stuffer);
22✔
347
   res.confirm("encoded polynomial fills buffer", stuffer.full());
44✔
348

349
   Botan::BufferSlicer slicer(buffer);
22✔
350
   Poly p_unpacked;
22✔
351
   Botan::CRYSTALS::unpack<range>(p_unpacked, slicer);
22✔
352
   res.confirm("decoded polynomial reads all bytes", slicer.empty());
44✔
353

354
   p_unpacked -= p;
22✔
355
   res.test_eq("p = unpack(pack(p))", p_unpacked.hamming_weight(), 0);
22✔
356
   res.end_timer();
22✔
357
}
66✔
358

359
}  // namespace
360

361
std::vector<Test::Result> test_encoding() {
1✔
362
   const auto threebitencoding = Botan::hex_decode(
1✔
363
      "88C61AD158231A6B44638D68AC118D35A2B14634D688C61AD158231A6B44638D68AC118D"
364
      "35A2B14634D688C61AD158231A6B44638D68AC118D35A2B14634D688C61AD158231A6B44"
365
      "638D68AC118D35A2B14634D688C61AD158231A6B44638D68");
1✔
366

367
   const auto eightbitencoding = Botan::hex_decode(
1✔
368
      "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223"
369
      "2425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F4041424344454647"
370
      "48494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B"
371
      "6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F"
372
      "909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3"
373
      "B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7"
374
      "D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFB"
375
      "FCFDFEFF");
1✔
376

377
   const auto tenbitencoding = Botan::hex_decode(
1✔
378
      "00084080010828C0800310484081051868C08107208840820928A8C0820B30C840830D38"
379
      "E8C0830F40084184114828C1841350484185155868C18517608841861968A8C1861B70C8"
380
      "41871D78E8C1871F80084288218828C2882390484289259868C28927A088428A29A8A8C2"
381
      "8A2BB0C8428B2DB8E8C28B2FC008438C31C828C38C33D048438D35D868C38D37E088438E"
382
      "39E8A8C38E3BF0C8438F3DF8E8C38F3F00094490410829C4904310494491451869C49147"
383
      "208944924928A9C4924B30C944934D38E9C4934F40094594514829C59453504945955558"
384
      "69C59557608945965968A9C5965B70C945975D78E9C5975F80094698618829C698639049"
385
      "4699659869C69967A089469A69A8A9C69A6BB0C9469B6DB8E9C69B6FC009479C71C829C7"
386
      "9C73D049479D75D869C79D77E089479E79E8A9C79E7BF0C9479F7DF8E9C79F7F");
1✔
387

388
   return {
1✔
389
      CHECK("encode polynomial coefficients into buffer",
390
            [&](Test::Result& res) {
1✔
391
               res.start_timer();
1✔
392
               // value range is about 3 bits
393
               Kyberish_Poly<Domain::Normal> p1;
1✔
394
               for(size_t i = 0; i < p1.size(); ++i) {
257✔
395
                  p1[i] = static_cast<Kyberish_Constants::T>(i % 7);
256✔
396
               }
397

398
               std::vector<uint8_t> buffer1(96);
1✔
399
               Botan::BufferStuffer stuffer1(buffer1);
1✔
400
               Botan::CRYSTALS::pack<6>(p1, stuffer1);
1✔
401
               res.test_eq("3 bit encoding", buffer1, threebitencoding);
1✔
402

403
               // value range is exactly one byte
404
               Kyberish_Poly<Domain::Normal> p2;
1✔
405
               for(size_t i = 0; i < p2.size(); ++i) {
257✔
406
                  p2[i] = static_cast<Kyberish_Constants::T>(i);
256✔
407
               }
408

409
               std::vector<uint8_t> buffer2(256);
1✔
410
               Botan::BufferStuffer stuffer2(buffer2);
1✔
411
               Botan::CRYSTALS::pack<255>(p2, stuffer2);
1✔
412
               res.test_eq("8 bit encoding", buffer2, eightbitencoding);
1✔
413

414
               // value range for 10 bits, with mapping function
415
               std::vector<uint8_t> buffer3(p2.size() / 8 * 10 /* bits */);
1✔
416
               Botan::BufferStuffer stuffer3(buffer3);
1✔
417
               Botan::CRYSTALS::pack<512>(p2, stuffer3, [](int16_t x) -> uint16_t { return x * 2; });
257✔
418
               res.test_eq("10 bit encoding", buffer3, tenbitencoding);
1✔
419
               res.end_timer();
1✔
420
            }),
5✔
421

422
      CHECK("decode polynomial coefficients from buffer",
423
            [&](Test::Result& res) {
1✔
424
               res.start_timer();
1✔
425
               Kyberish_Poly<Domain::Normal> p1;
1✔
426
               Botan::BufferSlicer slicer1(threebitencoding);
1✔
427
               Botan::CRYSTALS::unpack<6>(p1, slicer1);
1✔
428
               res.require("read all bytes from 3-bit encoding", slicer1.empty());
1✔
429
               for(size_t i = 0; i < p1.size(); ++i) {
257✔
430
                  res.test_is_eq<int16_t>("decoded 3-bit coefficient", p1[i], i % 7);
512✔
431
               }
432

433
               Kyberish_Poly<Domain::Normal> p2;
1✔
434
               Botan::BufferSlicer slicer2(eightbitencoding);
1✔
435
               Botan::CRYSTALS::unpack<255>(p2, slicer2);
1✔
436
               res.require("read all bytes from 8-bit encoding", slicer2.empty());
1✔
437
               for(size_t i = 0; i < p2.size(); ++i) {
257✔
438
                  res.test_is_eq<size_t>("decoded 8-bit coefficient", p2[i], i);
512✔
439
               }
440

441
               Kyberish_Poly<Domain::Normal> p3;
1✔
442
               Botan::BufferSlicer slicer3(tenbitencoding);
1✔
443
               Botan::CRYSTALS::unpack<512>(p3, slicer3, [](uint16_t x) -> int16_t { return x / 2; });
257✔
444
               res.require("read all bytes from 10-bit encoding", slicer3.empty());
1✔
445
               for(size_t i = 0; i < p3.size(); ++i) {
257✔
446
                  res.test_is_eq<size_t>("decoded 10-bit coefficient with mapping", p3[i], i);
512✔
447
               }
448
               res.end_timer();
1✔
449
            }),
3✔
450

451
      CHECK("decode polynomial coefficients from XOF",
452
            [&](Test::Result& res) {
1✔
453
               res.start_timer();
1✔
454
               Kyberish_Poly<Domain::Normal> p1;
1✔
455
               DeterministicXOF xof1(threebitencoding);
1✔
456
               Botan::CRYSTALS::unpack<6>(p1, xof1);
1✔
457
               for(size_t i = 0; i < p1.size(); ++i) {
257✔
458
                  res.test_is_eq<int16_t>("decoded 3-bit coefficient", p1[i], i % 7);
512✔
459
               }
460

461
               Kyberish_Poly<Domain::Normal> p2;
1✔
462
               DeterministicXOF xof2(eightbitencoding);
1✔
463
               Botan::CRYSTALS::unpack<255>(p2, xof2);
1✔
464
               for(size_t i = 0; i < p2.size(); ++i) {
257✔
465
                  res.test_is_eq<size_t>("decoded 8-bit coefficient", p2[i], i);
512✔
466
               }
467

468
               Kyberish_Poly<Domain::Normal> p3;
1✔
469
               DeterministicXOF xof3(tenbitencoding);
1✔
470
               Botan::CRYSTALS::unpack<512>(p3, xof3, [](int16_t x) -> int16_t { return x / 2; });
257✔
471
               for(size_t i = 0; i < p3.size(); ++i) {
257✔
472
                  res.test_is_eq<size_t>("decoded 10-bit coefficient with mapping", p3[i], i);
512✔
473
               }
474
               res.end_timer();
1✔
475
            }),
4✔
476

477
      CHECK("random encoding roundtrips (0 to x)",
478
            [](Test::Result& res) {
1✔
479
               res.start_timer();
1✔
480
               auto rng = Test::new_rng("CRYSTALS encoding roundtrips");
1✔
481
               random_encoding_roundtrips<Kyberish_Trait, 3>(res, *rng, 2);
1✔
482
               random_encoding_roundtrips<Kyberish_Trait, 6>(res, *rng, 3);
1✔
483
               random_encoding_roundtrips<Kyberish_Trait, 12>(res, *rng, 4);
1✔
484
               random_encoding_roundtrips<Kyberish_Trait, 15>(res, *rng, 4);
1✔
485
               random_encoding_roundtrips<Kyberish_Trait, 31>(res, *rng, 5);
1✔
486
               random_encoding_roundtrips<Kyberish_Trait, 42>(res, *rng, 6);
1✔
487
               random_encoding_roundtrips<Kyberish_Trait, 128>(res, *rng, 8);
1✔
488
               random_encoding_roundtrips<Kyberish_Trait, 1337>(res, *rng, 11);
1✔
489
               res.end_timer();
1✔
490
            }),
1✔
491

492
      CHECK("random encoding roundtrips (Kyber ranges)",
493
            [](Test::Result& res) {
1✔
494
               res.start_timer();
1✔
495
               auto rng = Test::new_rng("CRYSTALS encoding roundtrips as used in kyber");
1✔
496
               random_encoding_roundtrips<Kyberish_Trait, 1>(res, *rng, 1);
1✔
497
               random_encoding_roundtrips<Kyberish_Trait, (1 << 4) - 1>(res, *rng, 4);
1✔
498
               random_encoding_roundtrips<Kyberish_Trait, (1 << 5) - 1>(res, *rng, 5);
1✔
499
               random_encoding_roundtrips<Kyberish_Trait, (1 << 10) - 1>(res, *rng, 10);
1✔
500
               random_encoding_roundtrips<Kyberish_Trait, (1 << 11) - 1>(res, *rng, 11);
1✔
501
               random_encoding_roundtrips<Kyberish_Trait, Kyberish_Constants::Q - 1>(res, *rng, 12);
1✔
502
               res.end_timer();
1✔
503
            }),
1✔
504

505
      CHECK("random encoding roundtrips (Dilithium ranges)",
506
            [](Test::Result& res) {
1✔
507
               res.start_timer();
1✔
508
               using Dilithiumish_Trait = Mock_Trait<Dilithiumish_Constants>;
1✔
509

510
               auto rng = Test::new_rng("CRYSTALS encoding roundtrips as used in kyber");
1✔
511
               constexpr auto t1 = 1023;
1✔
512
               constexpr auto gamma2_32 = 15;
1✔
513
               constexpr auto gamma2_88 = 43;
1✔
514
               constexpr auto gamma1_17 = 131072;
1✔
515
               constexpr auto gamma1_19 = 524288;
1✔
516
               constexpr auto eta2 = 2;
1✔
517
               constexpr auto eta4 = 4;
1✔
518
               constexpr auto twotothed = 4096;
1✔
519
               random_encoding_roundtrips<Dilithiumish_Trait, t1>(res, *rng, 10);
1✔
520
               random_encoding_roundtrips<Dilithiumish_Trait, gamma2_32>(res, *rng, 4);
1✔
521
               random_encoding_roundtrips<Dilithiumish_Trait, gamma2_88>(res, *rng, 6);
1✔
522
               random_encoding_roundtrips<Dilithiumish_Trait, 2 * gamma1_17 - 1>(res, *rng, 18);
1✔
523
               random_encoding_roundtrips<Dilithiumish_Trait, 2 * gamma1_19 - 1>(res, *rng, 20);
1✔
524
               random_encoding_roundtrips<Dilithiumish_Trait, 2 * eta2>(res, *rng, 3);
1✔
525
               random_encoding_roundtrips<Dilithiumish_Trait, 2 * eta4>(res, *rng, 4);
1✔
526
               random_encoding_roundtrips<Dilithiumish_Trait, 2 * twotothed - 1>(res, *rng, 13);
1✔
527
               res.end_timer();
1✔
528
            }),
1✔
529
   };
7✔
530
}
4✔
531

532
class MockedXOF {
533
   public:
534
      MockedXOF() : m_counter(0) {}
5✔
535

536
      template <size_t bytes>
537
      auto output() {
538
         std::array<uint8_t, bytes> result;
539
         for(uint8_t& byte : result) {
56✔
540
            byte = static_cast<uint8_t>(m_counter++);
39✔
541
         }
542
         return result;
17✔
543
      }
544

545
   private:
546
      size_t m_counter;
547
};
548

549
template <size_t bound>
550
using Mocked_Bounded_XOF = Botan::detail::Bounded_XOF<MockedXOF, bound>;
551

552
std::vector<Test::Result> test_bounded_xof() {
1✔
553
   return {
1✔
554
      CHECK("zero bound is reached immediately",
555
            [](Test::Result& result) {
1✔
556
               result.start_timer();
1✔
557
               Mocked_Bounded_XOF<0> xof;
1✔
558
               result.test_throws<Botan::Internal_Error>("output<1> throws", [&xof]() { xof.next_byte(); });
3✔
559
               result.end_timer();
1✔
560
            }),
1✔
561

562
      CHECK("bounded XOF with small bound",
563
            [](Test::Result& result) {
1✔
564
               result.start_timer();
1✔
565
               Mocked_Bounded_XOF<3> xof;
1✔
566
               result.test_is_eq("next_byte() returns 0", xof.next_byte(), uint8_t(0));
2✔
567
               result.test_is_eq("next_byte() returns 1", xof.next_byte(), uint8_t(1));
2✔
568
               result.test_is_eq("next_byte() returns 2", xof.next_byte(), uint8_t(2));
2✔
569
               result.test_throws<Botan::Internal_Error>("next_byte() throws", [&xof]() { xof.next_byte(); });
3✔
570
               result.end_timer();
1✔
571
            }),
1✔
572

573
      CHECK("filter bytes",
574
            [](Test::Result& result) {
1✔
575
               result.start_timer();
1✔
576
               auto filter = [](uint8_t byte) {
6✔
577
                  //test
578
                  return byte % 2 == 1;
5✔
579
               };
580

581
               Mocked_Bounded_XOF<5> xof;
1✔
582
               result.test_is_eq("next_byte() returns 1", xof.next_byte(filter), uint8_t(1));
2✔
583
               result.test_is_eq("next_byte() returns 3", xof.next_byte(filter), uint8_t(3));
2✔
584
               result.test_throws<Botan::Internal_Error>("next_byte() throws", [&]() { xof.next_byte(filter); });
3✔
585
               result.end_timer();
1✔
586
            }),
1✔
587

588
      CHECK("map bytes",
589
            [](Test::Result& result) {
1✔
590
               result.start_timer();
1✔
591
               auto map = [](auto bytes) { return Botan::load_be(bytes); };
4✔
592

593
               Mocked_Bounded_XOF<17> xof;
1✔
594
               result.test_is_eq("next returns 0x00010203", xof.next<4>(map), uint32_t(0x00010203));
1✔
595
               result.test_is_eq("next returns 0x04050607", xof.next<4>(map), uint32_t(0x04050607));
1✔
596
               result.test_is_eq("next returns 0x08090A0B", xof.next<4>(map), uint32_t(0x08090A0B));
1✔
597
               result.test_is_eq("next returns 0x0C0D0E0F", xof.next<4>(map), uint32_t(0x0C0D0E0F));
1✔
598
               result.test_throws<Botan::Internal_Error>("next() throws", [&]() { xof.next<4>(map); });
3✔
599
               result.end_timer();
1✔
600
            }),
1✔
601

602
      CHECK("map and filter bytes",
603
            [](Test::Result& result) {
1✔
604
               result.start_timer();
1✔
605
               auto map = [](std::array<uint8_t, 3> bytes) -> uint32_t { return bytes[0] + bytes[1] + bytes[2]; };
5✔
606
               auto filter = [](uint32_t number) { return number < 50; };
1✔
607

608
               Mocked_Bounded_XOF<17> xof;
1✔
609
               result.test_is_eq("next returns 3", xof.next<3>(map, filter), uint32_t(3));
2✔
610
               result.test_is_eq("next returns 12", xof.next<3>(map, filter), uint32_t(12));
2✔
611
               result.test_is_eq("next returns 21", xof.next<3>(map, filter), uint32_t(21));
2✔
612
               result.test_is_eq("next returns 30", xof.next<3>(map, filter), uint32_t(30));
2✔
613
               result.test_is_eq("next returns 39", xof.next<3>(map, filter), uint32_t(39));
2✔
614
               result.test_throws<Botan::Internal_Error>("next() throws", [&]() { xof.next<3>(map, filter); });
3✔
615
               result.end_timer();
1✔
616
            }),
1✔
617
   };
6✔
618
}
1✔
619

620
}  // namespace
621

622
BOTAN_REGISTER_TEST_FN(
623
   "pubkey", "crystals", test_extended_euclidean_algorithm, test_polynomial_basics, test_encoding, test_bounded_xof);
624

625
}  // namespace Botan_Tests
626

627
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc