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

randombit / botan / 30143835648

25 Jul 2026 02:00AM UTC coverage: 89.429% (+0.01%) from 89.419%
30143835648

push

github

web-flow
Merge pull request #5758 from randombit/jack/hash2curve-query

Add EC_Group::hash_to_curve_supported and enforce RFC 9380 hash requirements

114789 of 128357 relevant lines covered (89.43%)

10704638.98 hits per line

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

89.71
/src/tests/test_ecc_h2c.cpp
1
/*
2
* (C) 2019,2020 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8

9
#if defined(BOTAN_HAS_ECC_GROUP)
10
   #include <botan/ec_group.h>
11
   #include <botan/internal/fmt.h>
12
#endif
13

14
#if defined(BOTAN_HAS_XMD)
15
   #include <botan/hash.h>
16
   #include <botan/internal/mem_utils.h>
17
   #include <botan/internal/xmd.h>
18
#endif
19

20
namespace Botan_Tests {
21

22
namespace {
23

24
#if defined(BOTAN_HAS_XMD)
25

26
class ECC_H2C_XMD_Tests final : public Text_Based_Test {
×
27
   public:
28
      ECC_H2C_XMD_Tests() : Text_Based_Test("pubkey/ec_h2c_xmd.vec", "Domain,Input,Output") {}
2✔
29

30
      bool clear_between_callbacks() const override { return false; }
20✔
31

32
      Test::Result run_one_test(const std::string& hash, const VarMap& vars) override {
20✔
33
         Test::Result result("ECC hash to curve XMD " + hash);
20✔
34

35
         const std::string domain = vars.get_req_str("Domain");
20✔
36
         const std::string input = vars.get_req_str("Input");
20✔
37
         const std::vector<uint8_t> expected = vars.get_req_bin("Output");
20✔
38

39
         auto hash_fn = Botan::HashFunction::create_or_throw(hash);
20✔
40

41
         std::vector<uint8_t> output(expected.size());
20✔
42
         Botan::expand_message_xmd(*hash_fn, output, Botan::as_span_of_bytes(input), Botan::as_span_of_bytes(domain));
20✔
43

44
         result.test_bin_eq("XMD output", output, expected);
20✔
45
         return result;
20✔
46
      }
60✔
47
};
48

49
BOTAN_REGISTER_TEST("ec_h2c", "ec_h2c_xmd", ECC_H2C_XMD_Tests);
50

51
#endif
52

53
#if defined(BOTAN_HAS_XMD) && defined(BOTAN_HAS_ECC_GROUP)
54

55
class ECC_H2S_Tests final : public Text_Based_Test {
×
56
   public:
57
      ECC_H2S_Tests() : Text_Based_Test("pubkey/ec_h2s.vec", "Hash,Domain,Input,Output") {}
2✔
58

59
      bool clear_between_callbacks() const override { return false; }
18✔
60

61
      bool skip_this_test(const std::string& group_id, const VarMap& /*vars*/) override {
18✔
62
         return !Botan::EC_Group::supports_named_group(group_id);
18✔
63
      }
64

65
      Test::Result run_one_test(const std::string& group_id, const VarMap& vars) override {
18✔
66
         Test::Result result("ECC hash to scalar " + group_id);
18✔
67

68
         const std::string hash_fn = vars.get_req_str("Hash");
18✔
69
         const std::string domain_str = vars.get_req_str("Domain");
18✔
70
         const std::string input_str = vars.get_req_str("Input");
18✔
71
         const std::vector<uint8_t> expected_value = vars.get_req_bin("Output");
18✔
72

73
         auto input = std::span{reinterpret_cast<const uint8_t*>(input_str.data()), input_str.size()};
18✔
74
         auto domain = std::span{reinterpret_cast<const uint8_t*>(domain_str.data()), domain_str.size()};
18✔
75

76
         const auto group = Botan::EC_Group::from_name(group_id);
18✔
77

78
         try {
18✔
79
            auto scalar = Botan::EC_Scalar::hash(group, hash_fn, input, domain).serialize();
18✔
80
            result.test_bin_eq("output", scalar, expected_value);
18✔
81
         } catch(Botan::Not_Implemented&) {
18✔
82
            result.test_note("Skipping due to not implemented");
×
83
         }
×
84

85
         return result;
36✔
86
      }
36✔
87
};
88

89
BOTAN_REGISTER_TEST("ec_h2c", "ec_h2s_kat", ECC_H2S_Tests);
90

91
class ECC_H2C_Hash_Check_Tests final : public Test {
1✔
92
   public:
93
      std::vector<Test::Result> run() override {
1✔
94
         Test::Result result("ECC hash to curve hash strength checks");
1✔
95

96
         reject(result, "secp256r1", "SHA-1");
2✔
97
         reject(result, "secp256r1", "MD5");
2✔
98
         reject(result, "secp384r1", "SHA-256");
2✔
99
         reject(result, "secp521r1", "SHA-384");
2✔
100
         reject(result, "brainpool512r1", "SHA-384");
2✔
101

102
         accept(result, "secp224r1", "SHA-224");
2✔
103
         accept(result, "secp256r1", "SHA-256");
2✔
104
         accept(result, "secp384r1", "SHA-384");
2✔
105
         accept(result, "secp521r1", "SHA-512");
2✔
106

107
         return {result};
3✔
108
      }
2✔
109

110
   private:
111
      static bool can_test(const std::string& group_id, const std::string& hash_fn) {
9✔
112
         return Botan::EC_Group::supports_named_group(group_id) && Botan::HashFunction::create(hash_fn) != nullptr;
18✔
113
      }
114

115
      static void reject(Test::Result& result, const std::string& group_id, const std::string& hash_fn) {
5✔
116
         if(!can_test(group_id, hash_fn)) {
5✔
117
            return;
×
118
         }
119

120
         const auto group = Botan::EC_Group::from_name(group_id);
5✔
121
         const std::string domain = "QUUX-V01-CS02";
5✔
122
         const std::vector<uint8_t> input(13);
5✔
123

124
         result.test_is_false("hash_to_curve_supported rejects " + hash_fn + " with " + group_id,
20✔
125
                              group.hash_to_curve_supported(hash_fn));
5✔
126

127
         result.test_throws<Botan::Invalid_Argument>("EC_Scalar::hash rejects " + hash_fn + " with " + group_id, [&]() {
20✔
128
            Botan::EC_Scalar::hash(group, hash_fn, input, Botan::as_span_of_bytes(domain));
5✔
129
         });
×
130

131
         try {
5✔
132
            Botan::EC_AffinePoint::hash_to_curve_ro(group, hash_fn, input, Botan::as_span_of_bytes(domain));
5✔
133
            result.test_failure("hash_to_curve_ro accepted " + hash_fn + " with " + group_id);
×
134
         } catch(Botan::Invalid_Argument&) {
5✔
135
            result.test_success("hash_to_curve_ro rejects " + hash_fn + " with " + group_id);
20✔
136
         } catch(Botan::Not_Implemented&) {
5✔
137
            result.test_note("Skipping due to not implemented");
×
138
         }
×
139
      }
5✔
140

141
      static void accept(Test::Result& result, const std::string& group_id, const std::string& hash_fn) {
4✔
142
         if(!can_test(group_id, hash_fn)) {
4✔
143
            return;
×
144
         }
145

146
         const auto group = Botan::EC_Group::from_name(group_id);
4✔
147
         const std::string domain = "QUUX-V01-CS02";
4✔
148
         const std::vector<uint8_t> input(13);
4✔
149

150
         result.test_no_throw("EC_Scalar::hash accepts " + hash_fn + " with " + group_id, [&]() {
16✔
151
            Botan::EC_Scalar::hash(group, hash_fn, input, Botan::as_span_of_bytes(domain));
4✔
152
         });
4✔
153
      }
4✔
154
};
155

156
BOTAN_REGISTER_TEST("ec_h2c", "ec_h2c_hash_checks", ECC_H2C_Hash_Check_Tests);
157

158
#endif
159

160
#if defined(BOTAN_HAS_EC_HASH_TO_CURVE)
161

162
class ECC_H2C_Tests final : public Text_Based_Test {
×
163
   public:
164
      ECC_H2C_Tests() : Text_Based_Test("pubkey/ec_h2c.vec", "Group,Hash,Domain,Input,Point") {}
2✔
165

166
      bool clear_between_callbacks() const override { return false; }
83✔
167

168
      bool skip_this_test(const std::string& /*header*/, const VarMap& vars) override {
83✔
169
         return !Botan::EC_Group::supports_named_group(vars.get_req_str("Group"));
83✔
170
      }
171

172
      Test::Result run_one_test(const std::string& method, const VarMap& vars) override {
83✔
173
         const std::string group_id = vars.get_req_str("Group");
83✔
174

175
         Test::Result result("ECC hash to curve " + method + " " + group_id);
332✔
176

177
         const std::string hash_fn = vars.get_req_str("Hash");
83✔
178
         const std::string domain_str = vars.get_req_str("Domain");
83✔
179
         const std::vector<uint8_t> input = vars.get_req_bin("Input");
83✔
180
         const std::vector<uint8_t> expected_point = vars.get_req_bin("Point");
83✔
181
         const bool random_oracle = method.find("-RO") != std::string::npos;
83✔
182

183
         auto domain = std::span{reinterpret_cast<const uint8_t*>(domain_str.data()), domain_str.size()};
83✔
184

185
         const auto group = Botan::EC_Group::from_name(group_id);
83✔
186

187
         try {
83✔
188
            std::vector<uint8_t> pt;
83✔
189
            if(random_oracle) {
83✔
190
               pt = Botan::EC_AffinePoint::hash_to_curve_ro(group, hash_fn, input, domain).serialize_uncompressed();
80✔
191
            } else {
192
               pt = Botan::EC_AffinePoint::hash_to_curve_nu(group, hash_fn, input, domain).serialize_uncompressed();
86✔
193
            }
194

195
            result.test_bin_eq("Generated point serialization", pt, expected_point);
83✔
196
            result.test_is_true("hash_to_curve_supported", group.hash_to_curve_supported(hash_fn));
83✔
197
         } catch(Botan::Not_Implemented&) {
83✔
198
            result.test_is_false("hash_to_curve_supported", group.hash_to_curve_supported(hash_fn));
×
199
            result.test_note("Skipping due to not implemented");
×
200
         }
×
201

202
         return result;
166✔
203
      }
249✔
204
};
205

206
BOTAN_REGISTER_TEST("ec_h2c", "ec_h2c_kat", ECC_H2C_Tests);
207

208
class ECC_H2C_Supported_Tests final : public Test {
1✔
209
   public:
210
      std::vector<Test::Result> run() override {
1✔
211
         Test::Result result("EC_Group::hash_to_curve_supported");
1✔
212

213
         const std::vector<uint8_t> input{1, 2, 3};
1✔
214
         const std::string domain_sep = "hash_to_curve_supported test";
1✔
215

216
         const std::vector<std::string> hash_fns{
1✔
217
            "SHA-1", "SHA-256", "SHA-384", "SHA-512", "SHAKE-128(256)", "NotARealHash"};
8✔
218

219
         for(const auto& group_name : Botan::EC_Group::known_named_groups()) {
29✔
220
            const auto group = Botan::EC_Group::from_name(group_name);
28✔
221

222
            for(const auto& hash_fn : hash_fns) {
196✔
223
               const bool supported = group.hash_to_curve_supported(hash_fn);
168✔
224

225
               bool ro_worked = false;
168✔
226
               try {
168✔
227
                  Botan::EC_AffinePoint::hash_to_curve_ro(group, hash_fn, input, domain_sep);
168✔
228
                  ro_worked = true;
13✔
229
               } catch(Botan::Exception&) {}
155✔
230

231
               bool nu_worked = false;
168✔
232
               try {
168✔
233
                  Botan::EC_AffinePoint::hash_to_curve_nu(group, hash_fn, input, domain_sep);
168✔
234
                  nu_worked = true;
13✔
235
               } catch(Botan::Exception&) {}
155✔
236

237
               result.test_is_true(Botan::fmt("{}/{} prediction matches behavior", group_name, hash_fn),
336✔
238
                                   supported == ro_worked && supported == nu_worked);
168✔
239
            }
240
         }
28✔
241

242
         return {result};
3✔
243
      }
5✔
244
};
245

246
BOTAN_REGISTER_TEST("ec_h2c", "ec_h2c_supported", ECC_H2C_Supported_Tests);
247

248
#endif
249

250
}  // namespace
251

252
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc