Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecdsa.test.cpp
Go to the documentation of this file.
1#include "ecdsa.hpp"
9#include <gtest/gtest.h>
10
11using namespace bb;
12using namespace bb::crypto;
13
14// Templated test fixture for ECDSA operations on different curves
15template <typename EcdsaTestParams> class EcdsaNativeTests : public ::testing::Test {
16 public:
19 using Fr = typename Curve::ScalarField;
20 using Fq = typename Curve::BaseField;
21 using G1 = typename Curve::Group;
23
24 // Generate a random keypair for the curve
26 {
29 account.public_key = G1::one * account.private_key;
30 return account;
31 }
32
33 // Create a valid signature for the given message and account
34 static ecdsa_signature create_valid_signature(const std::string& message, const ecdsa_key_pair<Fr, G1>& account)
35 {
36 return ecdsa_construct_signature<Hasher, Fq, Fr, G1>(message, account);
37 }
38
39 // Verify a signature
40 static bool verify_signature(const std::string& message,
41 const AffineElement& public_key,
42 const ecdsa_signature& sig)
43 {
44 return ecdsa_verify_signature<Hasher, Fq, Fr, G1>(message, public_key, sig);
45 }
46
47 // Recover public key from signature (only works for curves with recovery support)
48 static AffineElement recover_public_key(const std::string& message, const ecdsa_signature& sig)
49 {
50 return ecdsa_recover_public_key<Hasher, Fq, Fr, G1>(message, sig);
51 }
52
53 // Fetch Wycherproof test cases for the curve
54 template <typename T>
56 requires(T::has_wycheproof_tests)
57 {
59 return secp256k1_tests;
61 return secp256r1_tests;
62 }
63 }
64};
65
66// Define curve wrapper structs to match the pattern
76
86
92 static constexpr bool supports_recovery = false;
93 static constexpr bool has_wycheproof_tests = false;
94};
95
96template <typename Curve, typename Hasher_> struct EcdsaTestParams {
97 public:
99 using Hasher = Hasher_;
100};
101
102// Define the list of curve types to test
103using Params = ::testing::Types<EcdsaTestParams<secp256k1_curve, Sha256Hasher>,
112
113// Register the test suite
115
116// ================================================================================
117// POSITIVE TESTS: Valid signatures should pass verification
118// ================================================================================
119
120TYPED_TEST(EcdsaNativeTests, VerifyValidSignature)
121{
122 std::string message = "The quick brown dog jumped over the lazy fox.";
123
124 auto account = TestFixture::generate_keypair();
125 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
126 bool result = TestFixture::verify_signature(message, account.public_key, signature);
127
128 EXPECT_TRUE(result);
129}
130
131TYPED_TEST(EcdsaNativeTests, RecoverPublicKey)
132{
133 using Curve = TypeParam::CurveType;
134
135 std::string message = "The quick brown dog jumped over the lazy fox.";
136
137 if constexpr (Curve::supports_recovery) {
138 auto account = TestFixture::generate_keypair();
139 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
140
141 // Verify the signature is valid
142 bool result = TestFixture::verify_signature(message, account.public_key, signature);
143 EXPECT_TRUE(result);
144
145 // Recover the public key and check it matches
146 auto recovered_public_key = TestFixture::recover_public_key(message, signature);
147 EXPECT_EQ(recovered_public_key, account.public_key);
148 } else {
149 GTEST_SKIP() << "Public key recovery not supported for this curve";
150 }
151}
152
153// ================================================================================
154// NEGATIVE TESTS: Invalid signatures should be rejected
155// ================================================================================
156
158{
159 using serialize::write;
160
161 std::string message = "Test message";
162 auto account = TestFixture::generate_keypair();
163 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
164
165 // Set r = 0
166 uint256_t zero_r = 0;
167 auto* r_ptr = &signature.r[0];
168 write(r_ptr, zero_r);
169
170 bool result = TestFixture::verify_signature(message, account.public_key, signature);
171 EXPECT_FALSE(result);
172}
173
174TYPED_TEST(EcdsaNativeTests, RejectROverflowModulus)
175{
176 using serialize::read;
177 using serialize::write;
178 using Fr = typename TestFixture::Fr;
179
180 std::string message = "Test message";
181 auto account = TestFixture::generate_keypair();
182 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
183
184 // Set r = 1 + Fr::modulus (overflow)
185 uint256_t overflowing_r = uint256_t(1) + uint256_t(Fr::modulus);
186 auto* r_write_ptr = &signature.r[0];
187 write(r_write_ptr, overflowing_r);
188
189 bool result = TestFixture::verify_signature(message, account.public_key, signature);
190 EXPECT_FALSE(result);
191}
192
194{
195 using serialize::write;
196
197 std::string message = "Test message";
198 auto account = TestFixture::generate_keypair();
199 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
200
201 // Set s = 0
202 uint256_t zero_s = 0;
203 auto* s_ptr = &signature.s[0];
204 write(s_ptr, zero_s);
205
206 bool result = TestFixture::verify_signature(message, account.public_key, signature);
207 EXPECT_FALSE(result);
208}
209
211{
212 using serialize::read;
213 using serialize::write;
214 using Fr = typename TestFixture::Fr;
215
216 std::string message = "Test message";
217 auto account = TestFixture::generate_keypair();
218 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
219
220 // Set s to high s (should be rejected)
221 Fr s = Fr::serialize_from_buffer(&signature.s[0]);
222 Fr::serialize_to_buffer(-s, &signature.s[0]);
223
224 bool result = TestFixture::verify_signature(message, account.public_key, signature);
225 EXPECT_FALSE(result);
226}
227
228TYPED_TEST(EcdsaNativeTests, RejectInvalidPublicKey)
229{
230 using Fq = typename TestFixture::Fq;
231 using AffineElement = typename TestFixture::AffineElement;
232
233 std::string message = "Test message";
234 auto account = TestFixture::generate_keypair();
235 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
236
237 // Create a point not on the curve by taking a valid point and modifying y
238 AffineElement invalid_pubkey = account.public_key;
239 invalid_pubkey.y = invalid_pubkey.y + Fq::one();
240
241 bool result = TestFixture::verify_signature(message, invalid_pubkey, signature);
242 EXPECT_FALSE(result);
243}
244
245TYPED_TEST(EcdsaNativeTests, RejectInfinityPublicKey)
246{
247 using AffineElement = typename TestFixture::AffineElement;
248
249 std::string message = "Test message";
250 auto account = TestFixture::generate_keypair();
251 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
252
253 // Use point at infinity as public key
254 AffineElement infinity_pubkey = AffineElement::infinity();
255
256 bool result = TestFixture::verify_signature(message, infinity_pubkey, signature);
257 EXPECT_FALSE(result);
258}
259
260TYPED_TEST(EcdsaNativeTests, RejectInfinityResult)
261{
262 using Fr = typename TestFixture::Fr;
263 using G1 = typename TestFixture::G1;
264
265 std::string message = "Test message";
266 auto account = TestFixture::generate_keypair();
267 ecdsa_signature signature = TestFixture::create_valid_signature(message, account);
268
269 // Compute H(m)
270 std::vector<uint8_t> buffer;
271 std::ranges::copy(message, std::back_inserter(buffer));
272 auto hash = Sha256Hasher::hash(buffer);
273
274 // Override the public key: new public key is (-hash) * r^{-1} * G
275 Fr fr_hash = Fr::serialize_from_buffer(hash.data());
276 Fr r = Fr::serialize_from_buffer(&signature.r[0]);
277 Fr r_inverse = r.invert();
278 Fr modified_private_key = r_inverse * (-fr_hash);
279 account.public_key = G1::one * modified_private_key;
280
281 // Verify that the result is the point at infinity
282 auto P = G1::one * fr_hash + account.public_key * r;
283 BB_ASSERT_EQ(P.is_point_at_infinity(), true);
284
285 bool result = TestFixture::verify_signature(message, account.public_key, signature);
286 EXPECT_FALSE(result);
287}
288
290{
291 using Curve = TypeParam::CurveType;
292 using AffineElement = TestFixture::AffineElement;
293 using Fr = TestFixture::Fr;
294
295 if constexpr (Curve::has_wycheproof_tests) {
296 for (const auto& test_case : TestFixture::template get_wycheproof_test_cases<Curve>()) {
297 std::string message_string(test_case.message.begin(), test_case.message.end());
298 std::array<uint8_t, 32> r;
299 std::array<uint8_t, 32> s;
300 Fr::serialize_to_buffer(test_case.r, &r[0]);
301 Fr::serialize_to_buffer(test_case.s, &s[0]);
302 ecdsa_signature sig = { r, s, ECDSA_RECOVERY_ID_OFFSET };
303
304 bool is_signature_valid = ecdsa_verify_signature<Sha256Hasher,
305 typename Curve::BaseField,
306 typename Curve::ScalarField,
307 typename Curve::Group>(
308 message_string, AffineElement(test_case.x, test_case.y), sig);
309
310 EXPECT_EQ(is_signature_valid, test_case.is_valid_signature) << "Test case: " << test_case.comment;
311 }
312 } else {
313 GTEST_SKIP() << "Wycheproof tests not available for this curve";
314 }
315}
316
317// ================================================================================
318// STANDALONE TESTS: Non-templated tests for specific scenarios
319// ================================================================================
320
322{
323 auto [actual, expected] = msgpack_roundtrip(ecdsa_signature{});
324 EXPECT_EQ(actual, expected);
325}
326
327TEST(ecdsa, verify_signature_secp256r1_sha256_NIST_1)
328{
329 /*
330 Msg =
331 5905238877c77421f73e43ee3da6f2d9e2ccad5fc942dcec0cbd25482935faaf416983fe165b1a045ee2bcd2e6dca3bdf46c4310a7461f9a37960ca672d3feb5473e253605fb1ddfd28065b53cb5858a8ad28175bf9bd386a5e471ea7a65c17cc934a9d791e91491eb3754d03799790fe2d308d16146d5c9b0d0debd97d79ce8
332 d = 519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464
333 Qx = 1ccbe91c075fc7f4f033bfa248db8fccd3565de94bbfb12f3c59ff46c271bf83
334 Qy = ce4014c68811f9a21a1fdb2c0e6113e06db7ca93b7404e78dc7ccd5ca89a4ca9
335 k = 94a1bbb14b906a61a280f245f9e93c7f3b4a6247824f5d33b9670787642a68de
336 R = f3ac8061b514795b8843e3d6629527ed2afd6b1f6a555a7acabb5e6f79c8c2ac
337 S = 740887e535fa594e879389d9d408c8e2cd4f4894bda8872ab6ebf098305d9c4e
338 */
339
340 secp256r1::fq P_x = secp256r1::fq(0x3c59ff46c271bf83, 0xd3565de94bbfb12f, 0xf033bfa248db8fcc, 0x1ccbe91c075fc7f4)
342 secp256r1::fq P_y = secp256r1::fq(0xdc7ccd5ca89a4ca9, 0x6db7ca93b7404e78, 0x1a1fdb2c0e6113e0, 0xce4014c68811f9a2)
344
345 secp256r1::g1::affine_element public_key(P_x, P_y);
346 std::array<uint8_t, 32> r{
347 0xf3, 0xac, 0x80, 0x61, 0xb5, 0x14, 0x79, 0x5b, 0x88, 0x43, 0xe3, 0xd6, 0x62, 0x95, 0x27, 0xed,
348 0x2a, 0xfd, 0x6b, 0x1f, 0x6a, 0x55, 0x5a, 0x7a, 0xca, 0xbb, 0x5e, 0x6f, 0x79, 0xc8, 0xc2, 0xac,
349 };
350
351 std::array<uint8_t, 32> s{
352 0x74, 0x08, 0x87, 0xe5, 0x35, 0xfa, 0x59, 0x4e, 0x87, 0x93, 0x89, 0xd9, 0xd4, 0x08, 0xc8, 0xe2,
353 0xcd, 0x4f, 0x48, 0x94, 0xbd, 0xa8, 0x87, 0x2a, 0xb6, 0xeb, 0xf0, 0x98, 0x30, 0x5d, 0x9c, 0x4e,
354 };
355
356 ecdsa_signature sig{ r, s, 27 };
357 std::vector<uint8_t> message_vec = utils::hex_to_bytes(
358 "5905238877c77421f73e43ee3da6f2d9e2ccad5fc942dcec0cbd25482935faaf416983fe165b1a045ee2bcd2e6dca3bdf46"
359 "c4310a7461f9a37960ca672d3feb5473e253605fb1ddfd28065b53cb5858a8ad28175bf9bd386a5e471ea7a65c17cc934a9"
360 "d791e91491eb3754d03799790fe2d308d16146d5c9b0d0debd97d79ce8");
361 std::string message(message_vec.begin(), message_vec.end());
362
363 bool result =
364 ecdsa_verify_signature<Sha256Hasher, secp256r1::fq, secp256r1::fr, secp256r1::g1>(message, public_key, sig);
365 EXPECT_EQ(result, true);
366}
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
static ecdsa_signature create_valid_signature(const std::string &message, const ecdsa_key_pair< Fr, G1 > &account)
typename Curve::BaseField Fq
typename Curve::AffineElement AffineElement
static AffineElement recover_public_key(const std::string &message, const ecdsa_signature &sig)
typename Curve::ScalarField Fr
static ecdsa_key_pair< Fr, G1 > generate_keypair()
typename EcdsaTestParams::CurveType Curve
static bool verify_signature(const std::string &message, const AffineElement &public_key, const ecdsa_signature &sig)
typename EcdsaTestParams::Hasher Hasher
static auto get_wycheproof_test_cases()
typename Curve::Group G1
bb::fq BaseField
Definition bn254.hpp:19
typename bb::g1 Group
Definition bn254.hpp:20
typename Group::affine_element AffineElement
Definition bn254.hpp:22
bb::fr ScalarField
Definition bn254.hpp:18
group class. Represents an elliptic curve group element. Group is parametrised by Fq and Fr
Definition group.hpp:36
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:42
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:50
const std::vector< WycherproofSecp256r1 > secp256r1_tests
Test for Secp256r1 ECDSA signatures taken from the Wycherproof project.
void write(B &buf, SchnorrProofOfPossession< G1, Hash > const &proof_of_possession)
bool ecdsa_verify_signature(const std::string &message, const typename G1::affine_element &public_key, const ecdsa_signature &sig)
const std::vector< WycherproofSecp256k1 > secp256k1_tests
Test for Secp256k1 ECDSA signatures taken from the Wycherproof project.
bb::group< bb::fr, bb::fq, G1Params > g1
Definition grumpkin.hpp:47
field< FrParams > fr
group< fq, fr, G1Params > g1
field< FqParams > fq
field< FrParams > fr
group< fq, fr, G1Params > g1
field< FqParams > fq
std::vector< uint8_t > hex_to_bytes(const std::string &hex)
Routine to transform hexstring to vector of bytes.
Definition utils.cpp:5
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
curve::Grumpkin Curve
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
void read(auto &it, msgpack_concepts::HasMsgPack auto &obj)
Automatically derived read for any object that defines .msgpack() (implicitly defined by MSGPACK_FIEL...
void write(auto &buf, const msgpack_concepts::HasMsgPack auto &obj)
Automatically derived write for any object that defines .msgpack() (implicitly defined by MSGPACK_FIE...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::AffineElement G1
static auto hash(const B &message)
Definition hashers.hpp:36
G1::affine_element public_key
Definition ecdsa.hpp:24
std::array< uint8_t, 32 > r
Definition ecdsa.hpp:31
std::array< uint8_t, 32 > s
Definition ecdsa.hpp:32
static constexpr field one()
static constexpr uint256_t modulus
BB_INLINE constexpr field to_montgomery_form() const noexcept
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
static field serialize_from_buffer(const uint8_t *buffer)
static void serialize_to_buffer(const field &value, uint8_t *buffer)
static constexpr bool has_wycheproof_tests
static constexpr bool supports_recovery
static constexpr bool supports_recovery
static constexpr bool has_wycheproof_tests
static constexpr bool supports_recovery
static constexpr bool has_wycheproof_tests
std::pair< T, T > msgpack_roundtrip(const T &object)