Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecdsa.test.cpp
Go to the documentation of this file.
2#include "../../primitives/bigfield/bigfield.hpp"
3#include "../../primitives/biggroup/biggroup.hpp"
4#include "../../primitives/curves/secp256k1.hpp"
5#include "../../primitives/curves/secp256r1.hpp"
8#include "ecdsa.hpp"
10
11#include <gtest/gtest.h>
12
13#include <algorithm>
14
15using namespace bb;
16using namespace bb::crypto;
17
18template <class Curve> class EcdsaTests : public ::testing::Test {
19 public:
20 using Builder = Curve::Builder;
21 using CurveType =
23
24 // Native Types
25 using FrNative = Curve::ScalarFieldNative;
26 using FqNative = Curve::BaseFieldNative;
27 using G1Native = Curve::GroupNative;
28
29 // Stdlib types
34
35 // Reproducible signature
36 static constexpr FrNative private_key =
37 FrNative("0xd67abee717b3fc725adf59e2cc8cd916435c348b277dd814a34e3ceb279436c2");
38
52
54 bool random_signature)
55 {
57
58 account.private_key = random_signature ? FrNative::random_element() : private_key;
59 account.public_key = G1Native::one * account.private_key;
60
61 ecdsa_signature signature =
62 ecdsa_construct_signature<Sha256Hasher, FqNative, FrNative, G1Native>(message_string, account);
63
64 if (random_signature) {
65 // Logging in case of random signature
66 info("The private key used generate this signature is: ", account.private_key);
67 }
68
69 return { account, signature };
70 }
71
72 void tampering(std::string message_string,
74 ecdsa_signature& signature,
76 {
77 switch (mode) {
79 // Invalidate the circuit by passing a public key with x >= q
80 // Do nothing here, tampering happens in circuit
81 break;
82 }
84 // Invalidate the circuit by passing a public key with y >= q
85 // Do nothing here, tampering happens in circuit
86 break;
87 }
89 // Invalidate the signature by changing r.
90 FrNative r = FrNative::serialize_from_buffer(&signature.r[0]);
91 r += FrNative::one();
92
93 FrNative::serialize_to_buffer(r, &signature.r[0]);
94 break;
95 }
97 // Invalidate the signature by changing s.
98 FrNative s = FrNative::serialize_from_buffer(&signature.s[0]);
99 s += FrNative::one();
100
101 FrNative::serialize_to_buffer(s, &signature.s[0]);
102 break;
103 }
105 // Invalidate the signature by changing s to -s.
106 FrNative s = FrNative::serialize_from_buffer(&signature.s[0]);
107 s = -s;
108
109 FrNative::serialize_to_buffer(s, &signature.s[0]);
110 break;
111 }
113 // Invalidate signature by setting r to 0
114 signature.r = std::array<uint8_t, 32>{};
115 break;
116 }
118 // Invalidate signature by setting s to 0
119 signature.s = std::array<uint8_t, 32>{};
120 break;
121 }
123 // Invalidate the signature by making making u1 * G + u2 * P return the point at infinity
124
125 // Compute H(m)
126 std::vector<uint8_t> buffer;
127 std::ranges::copy(message_string, std::back_inserter(buffer));
128 auto hash = Sha256Hasher::hash(buffer);
129
130 // Override the public key: new public key is (-hash) * r^{-1} * G
131 FrNative fr_hash = FrNative::serialize_from_buffer(&hash[0]);
132 FrNative r = FrNative::serialize_from_buffer(&signature.r[0]);
133 FrNative r_inverse = r.invert();
134 FrNative modified_private_key = r_inverse * (-fr_hash);
135 account.public_key = G1Native::one * modified_private_key;
136
137 // Verify that the result is the point at infinity
138 auto P = G1Native::one * fr_hash + account.public_key * r;
139 BB_ASSERT_EQ(P.is_point_at_infinity(), true);
140 break;
141 }
143 // Invalidate the circuit by passing a public key which is not on the curve
144 account.public_key.x = account.public_key.y;
145 BB_ASSERT_EQ(account.public_key.on_curve(), false);
146 break;
147 }
149 // Invalidate the circuit by passing a public key which is not on the curve
150 account.public_key.self_set_infinity();
151 BB_ASSERT_EQ(account.public_key.is_point_at_infinity(), true);
152 break;
153 }
155 break;
156 }
157
158 // Natively verify that the tampering was successfull
159 bool is_signature_valid = ecdsa_verify_signature<Sha256Hasher, FqNative, FrNative, G1Native>(
160 message_string, account.public_key, signature);
162 // In these tampering modes nothing has changed and the tampering happens in circuit, so we override the
163 // result and set it to false
164 is_signature_valid = false;
165 }
166
167 bool expected = mode == TamperingMode::None;
168 BB_ASSERT_EQ(is_signature_valid,
169 expected,
170 "Signature verification returned a different result from the expected one. If the signature was "
171 "randomly generated, there is a (very) small chance this is not a bug.");
172 }
173
177 const ecdsa_signature& signature,
178 const TamperingMode mode)
179 {
180 // We construct the point via its x,y-coordinates with an explicit infinity flag.
181 // This avoids:
182 // 1. The on-curve check of G1::from_witness (so we can test invalid points)
183 // 2. The expensive infinity auto-detection of the 2-arg constructor
184 Fq x;
185 Fq y;
188 for (size_t idx = 0; idx < 32; idx++) {
189 stdlib::byte_array<Builder> to_write(stdlib::field_t<Builder>(0xff), /*length*/ 1);
190 x_bytes.write(to_write);
191 }
192 x = Fq(x_bytes);
193 y = Fq::from_witness(&builder, account.public_key.y);
196 for (size_t idx = 0; idx < 32; idx++) {
197 stdlib::byte_array<Builder> to_write(stdlib::field_t<Builder>(0xff), /*length*/ 1);
198 y_bytes.write(to_write);
199 }
200 x = Fq::from_witness(&builder, account.public_key.x);
201 y = Fq(y_bytes);
202 } else {
203 x = Fq::from_witness(&builder, account.public_key.x);
204 y = Fq::from_witness(&builder, account.public_key.y);
205 }
206
208 // Override coordinates to (0, 0) for infinity
209 x = Fq::from_witness(&builder, FqNative::zero());
210 y = Fq::from_witness(&builder, FqNative::zero());
211 }
212
213 // Create explicit infinity flag witness from the native point's infinity status
214 bool_t is_infinity(
215 stdlib::witness_t<Builder>(&builder, account.public_key.is_point_at_infinity() ? fr::one() : fr::zero()),
216 false);
217
218 // Use the test accessor to create element with explicit infinity flag (avoids expensive auto-detection)
219 G1 pub_key = stdlib::element_default::element_test_accessor::
220 create_element_with_explicit_infinity<Builder, Fq, Fr, G1Native>(
221 x, y, is_infinity, /*assert_on_curve=*/false);
222 pub_key.set_free_witness_tag();
223 BB_ASSERT_EQ(pub_key.is_point_at_infinity().get_value(), account.public_key.is_point_at_infinity());
224
225 std::vector<uint8_t> rr(signature.r.begin(), signature.r.end());
226 std::vector<uint8_t> ss(signature.s.begin(), signature.s.end());
227
230
231 return { pub_key, sig };
232 }
233
235 const stdlib::byte_array<Builder>& hashed_message,
237 const ecdsa_signature& signature,
238 const bool signature_verification_result,
239 const bool expected_circuit_result,
240 const TamperingMode mode)
241
242 {
243 auto [public_key, sig] = create_stdlib_ecdsa_data(builder, account, signature, mode);
244
245 // Verify signature
246 stdlib::bool_t<Builder> signature_result =
247 stdlib::ecdsa_verify_signature<Builder, Curve, Fq, Fr, G1>(hashed_message, public_key, sig);
248
249 // Enforce verification returns the expected result
250 signature_result.assert_equal(stdlib::bool_t<Builder>(signature_verification_result));
251
252 // Check native values
253 EXPECT_EQ(signature_result.get_value(), signature_verification_result);
254
255 // Circuit checker
256 bool is_circuit_satisfied = CircuitChecker::check(builder);
257 EXPECT_EQ(is_circuit_satisfied, expected_circuit_result);
258 }
259
260 void test_verify_signature(bool random_signature, TamperingMode mode)
261 {
262 // Map tampering mode to signature verification result
263 bool signature_verification_result = (mode == TamperingMode::None);
264
265 std::string message_string = "Goblin";
266 std::vector<uint8_t> message_bytes(message_string.begin(), message_string.end());
267 std::array<uint8_t, 32> hashed_message_bytes_ = Sha256Hasher::hash(message_bytes);
268 std::vector<uint8_t> hashed_message_bytes;
269 hashed_message_bytes.reserve(32);
270 for (auto byte : hashed_message_bytes_) {
271 hashed_message_bytes.emplace_back(byte);
272 }
273
274 auto [account, signature] = generate_dummy_ecdsa_data(message_string, /*random_signature=*/random_signature);
275
276 // Tamper with the signature
277 tampering(message_string, account, signature, mode);
278
279 // Create ECDSA verification circuit
281 stdlib::byte_array<Builder> hashed_message(&builder, hashed_message_bytes);
282
283 // ECDSA verification
285 builder, hashed_message, account, signature, signature_verification_result, true, mode);
286 }
287
288 // Test deterministic failure when P = ±G for secp256r1
289 void test_signature_generator(bool is_minus_generator, bool signature_result, bool expected_circuit_result)
290 {
291 std::string message_string = "Goblin";
292 std::vector<uint8_t> message_bytes(message_string.begin(), message_string.end());
293 std::array<uint8_t, 32> hashed_message_bytes_ = Sha256Hasher::hash(message_bytes);
294 std::vector<uint8_t> hashed_message_bytes;
295 hashed_message_bytes.reserve(32);
296 for (auto byte : hashed_message_bytes_) {
297 hashed_message_bytes.emplace_back(byte);
298 }
299
300 // Generate a signature with P = ±G
302
303 account.private_key = is_minus_generator ? FrNative(-1) : FrNative(1);
304 account.public_key = G1Native::one * account.private_key;
305
306 ecdsa_signature signature =
307 ecdsa_construct_signature<Sha256Hasher, FqNative, FrNative, G1Native>(message_string, account);
308
309 // Create ECDSA verification circuit
311 stdlib::byte_array<Builder> hashed_message(&builder, hashed_message_bytes);
312
313 // ECDSA verification
315 hashed_message,
316 account,
317 signature,
318 signature_result,
319 expected_circuit_result,
321 }
322
323 // This test covers a specific behaviour of the implementation. If either the x or y coordinate of the public key
324 // are bigger than the base field modulus, the ECDSA verification algorithm substitutes the public key with 2G to
325 // avoid circuit failures. This means that an attacker could craft a valid signature for 2G and submit such
326 // signature together with a public key with x coordinate overflowing. The ECDSA equation would then be satisfied
327 // because of the internal substitution. The attack should not be succesful because the result of the ECDSA
328 // verification algorithm takes into account whether either the x or y coordinate overflowed.
330 {
331 std::string message_string = "Goblin";
332 std::vector<uint8_t> message_bytes(message_string.begin(), message_string.end());
333 std::array<uint8_t, 32> hashed_message_bytes_ = Sha256Hasher::hash(message_bytes);
334 std::vector<uint8_t> hashed_message_bytes;
335 hashed_message_bytes.reserve(32);
336 for (auto byte : hashed_message_bytes_) {
337 hashed_message_bytes.emplace_back(byte);
338 }
339
340 // Generate a signature with P = 2G
342
343 account.private_key = FrNative(2);
344 account.public_key = G1Native::one * account.private_key;
345
346 ecdsa_signature signature =
347 ecdsa_construct_signature<Sha256Hasher, FqNative, FrNative, G1Native>(message_string, account);
348
349 // Tamper with the signature by making the x coordinate overflow
350 tampering(message_string, account, signature, TamperingMode::XCoordinateOverflow);
351
352 // Create ECDSA verification circuit
354 stdlib::byte_array<Builder> hashed_message(&builder, hashed_message_bytes);
355
356 // ECDSA verification
358 builder, hashed_message, account, signature, false, true, TamperingMode::XCoordinateOverflow);
359 }
360
367 {
368 for (auto test : tests) {
369 // Keypair
371 account.private_key = FrNative::one(); // Dummy value, unused
372 account.public_key = typename G1Native::affine_element(test.x, test.y);
373
374 // Signature
375 std::array<uint8_t, 32> r;
376 std::array<uint8_t, 32> s;
377 uint8_t v = 0; // Dummy value, unused
378 FrNative::serialize_to_buffer(test.r, &r[0]);
379 FrNative::serialize_to_buffer(test.s, &s[0]);
380
381 // Hashed message
382 std::array<uint8_t, 32> hashed_message_bytes_ = Sha256Hasher::hash(test.message);
383 std::vector<uint8_t> hashed_message_bytes;
384 hashed_message_bytes.reserve(32);
385 for (auto byte : hashed_message_bytes_) {
386 hashed_message_bytes.emplace_back(byte);
387 }
388
389 // Create ECDSA verification circuit
391 stdlib::byte_array<Builder> hashed_message(&builder, hashed_message_bytes);
392
393 // ECDSA verification
395 hashed_message,
396 account,
397 { r, s, v },
398 test.is_valid_signature,
399 test.is_circuit_satisfied,
401 }
402 }
403};
404
405using Curves = testing::Types<stdlib::secp256k1<UltraCircuitBuilder>,
409
411
412TYPED_TEST(EcdsaTests, VerifyRandomSignature)
413{
414 TestFixture::test_verify_signature(/*random_signature=*/true, TestFixture::TamperingMode::None);
415}
416
417TYPED_TEST(EcdsaTests, VerifySignature)
418{
419 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::None);
420}
421
422TYPED_TEST(EcdsaTests, XCoordinateOverflow)
423{
425 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::XCoordinateOverflow);
426}
427
428TYPED_TEST(EcdsaTests, YCoordinateOverflow)
429{
431 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::YCoordinateOverflow);
432}
433
435{
436 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidR);
437}
438
440{
441 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidS);
442}
443
445{
446 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::HighS);
447}
448
450{
451 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::ZeroR);
452}
453
455{
456 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::ZeroS);
457}
458
459TYPED_TEST(EcdsaTests, InvalidPubKey)
460{
461 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidPubKey);
462}
463
464TYPED_TEST(EcdsaTests, InfinityPubKey)
465{
466 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InfinityPubKey);
467}
468
469TYPED_TEST(EcdsaTests, InfinityScalarMul)
470{
471 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InfinityScalarMul);
472}
473
475{
476 if constexpr (TypeParam::type == bb::CurveType::SECP256K1) {
477 TestFixture::test_wycherproof(stdlib::secp256k1_tests);
478 } else {
479 TestFixture::test_wycherproof(stdlib::secp256r1_tests);
480 }
481}
482
483TYPED_TEST(EcdsaTests, SignatureDoubleGenerator)
484{
485 TestFixture::test_signature_double_generator();
486}
487
488TYPED_TEST(EcdsaTests, SignatureGenerator)
489{
490 // Both curves now verify correctly when the public key is ±G: secp256k1 via its GLV path, secp256r1 via the
491 // fake-GLV two-2-MSM path (each MSM's inputs are {G, ±T₁} or {Q, ±T₂}, which only collide if u_i = ±1 -- a
492 // negligible probability for honest signatures).
493 bool signature_result = true;
494 bool expected_circuit_result = true;
495
496 TestFixture::test_signature_generator(false, signature_result, expected_circuit_result);
497 TestFixture::test_signature_generator(true, signature_result, expected_circuit_result);
498}
499
500TEST(EcdsaTests, Secp256k1PointAtInfinityRegression)
501{
503
504 using FqNative = Curve::BaseFieldNative;
505 using FrNative = Curve::ScalarFieldNative;
506 using G1Native = Curve::GroupNative;
507
508 using Builder = Curve::Builder;
509 using FrStdlib = Curve::ScalarField;
510 using FqStdlib = Curve::BaseField;
511 using G1Stdlib = Curve::Group;
512
513 // Attack parameters for P = 5*G
514 // These are crafted so that u1*G + u2*P = O (point at infinity)
515 const std::array<uint8_t, 32> pub_x_bytes = { 0x2f, 0x8b, 0xde, 0x4d, 0x1a, 0x07, 0x20, 0x93, 0x55, 0xb4, 0xa7,
516 0x25, 0x0a, 0x5c, 0x51, 0x28, 0xe8, 0x8b, 0x84, 0xbd, 0xdc, 0x61,
517 0x9a, 0xb7, 0xcb, 0xa8, 0xd5, 0x69, 0xb2, 0x40, 0xef, 0xe4 };
518 const std::array<uint8_t, 32> pub_y_bytes = { 0xd8, 0xac, 0x22, 0x26, 0x36, 0xe5, 0xe3, 0xd6, 0xd4, 0xdb, 0xa9,
519 0xdd, 0xa6, 0xc9, 0xc4, 0x26, 0xf7, 0x88, 0x27, 0x1b, 0xab, 0x0d,
520 0x68, 0x40, 0xdc, 0xa8, 0x7d, 0x3a, 0xa6, 0xac, 0x62, 0xd6 };
521 const std::array<uint8_t, 32> r_bytes = { 0xa8, 0x41, 0x94, 0xc3, 0x71, 0xc6, 0x7b, 0xa2, 0x59, 0x2f, 0x59,
522 0xc6, 0x20, 0xad, 0x30, 0x4c, 0xb7, 0x6d, 0x7a, 0x88, 0x25, 0x6b,
523 0xb5, 0x0d, 0xc4, 0x1c, 0x66, 0x57, 0x44, 0xbf, 0x78, 0x61 };
524 const std::array<uint8_t, 32> s_bytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
525 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
526 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64 };
527 const std::array<uint8_t, 32> z_bytes = { 0xb6, 0xb8, 0x18, 0x2e, 0xc7, 0x1f, 0x95, 0xd4, 0x42, 0x13, 0x3f,
528 0x21, 0x5c, 0x9e, 0x0e, 0x7b, 0x55, 0x98, 0x0e, 0xf2, 0x02, 0x07,
529 0xf7, 0xaa, 0x2a, 0xbb, 0x7a, 0x7e, 0xe9, 0x1b, 0xab, 0x1f };
530
531 FqNative pub_x = FqNative::serialize_from_buffer(pub_x_bytes.data());
532 FqNative pub_y = FqNative::serialize_from_buffer(pub_y_bytes.data());
533 typename G1Native::affine_element public_key_native(pub_x, pub_y);
534 ASSERT_TRUE(public_key_native.on_curve()) << "Public key must be on curve";
535
536 // Native verification
537 const std::string message_string(z_bytes.begin(), z_bytes.end());
538 ecdsa_signature sig;
539 sig.r = r_bytes;
540 sig.s = s_bytes;
541 sig.v = 27;
542 bool native_verification =
543 ecdsa_verify_signature<Sha256Hasher, FqNative, FrNative, G1Native>(message_string, public_key_native, sig);
544
545 bool stdlib_verification;
546 {
548
549 const G1Stdlib public_key_ct = G1Stdlib::from_witness(&builder, public_key_native);
550
551 const std::vector<uint8_t> r_vec(r_bytes.begin(), r_bytes.end());
552 const std::vector<uint8_t> s_vec(s_bytes.begin(), s_bytes.end());
555
556 const std::vector<uint8_t> z_vec(z_bytes.begin(), z_bytes.end());
557 const stdlib::byte_array<Builder> hashed_message_ct(&builder, z_vec);
558
559 const stdlib::bool_t<Builder> signature_result =
560 stdlib::ecdsa_verify_signature<Builder, Curve, FqStdlib, FrStdlib, G1Stdlib>(
561 hashed_message_ct, public_key_ct, sig_ct);
562
563 stdlib_verification = signature_result.get_value();
564
565 const bool circuit_valid = CircuitChecker::check(builder);
566
567 // Circuit should be satisfied but signature should be rejected
568 ASSERT_TRUE(circuit_valid);
569 }
570
571 // Both native and stdlib should reject this invalid signature
572 EXPECT_FALSE(native_verification);
573 EXPECT_FALSE(stdlib_verification);
574 EXPECT_EQ(native_verification, stdlib_verification);
575}
576
577TEST(EcdsaTests, Secp256r1NativeStdlibDiscrepancyRegression)
578{
580
581 using FqNative = Curve::BaseFieldNative;
582 using FrNative = Curve::ScalarFieldNative;
583 using G1Native = Curve::GroupNative;
584
585 using Builder = Curve::Builder;
586 using FrStdlib = Curve::ScalarField;
587 using FqStdlib = Curve::BaseField;
588 using G1Stdlib = Curve::Group;
589
590 const std::array<uint8_t, 32> pub_x_bytes = { 0x79, 0x9f, 0x2a, 0xba, 0xfa, 0x27, 0x16, 0x4b, 0x09, 0x50, 0xf2,
591 0xc8, 0x82, 0xf0, 0xd1, 0x67, 0xe1, 0xd2, 0x16, 0x74, 0x87, 0xd5,
592 0x2e, 0xa7, 0x23, 0x0b, 0x5d, 0x96, 0xc2, 0xa8, 0x74, 0x00 };
593 const std::array<uint8_t, 32> pub_y_bytes = { 0xda, 0xa5, 0x79, 0xf4, 0xf1, 0x61, 0xe9, 0xdc, 0xa1, 0xa1, 0x34,
594 0x35, 0x92, 0x16, 0xb9, 0x35, 0xea, 0xd0, 0x97, 0x2d, 0x76, 0x3f,
595 0xe3, 0x33, 0xc7, 0x12, 0xee, 0x8d, 0x18, 0x4b, 0xd8, 0x11 };
596 const std::array<uint8_t, 32> r_bytes = { 0xb1, 0x99, 0xa1, 0x62, 0x72, 0x66, 0x61, 0xba, 0x23, 0x3c, 0xd6,
597 0xc6, 0x6e, 0x99, 0x0b, 0x01, 0x2e, 0x1e, 0x76, 0x04, 0xb1, 0x1f,
598 0x76, 0x19, 0x3b, 0x2a, 0xf5, 0xca, 0x36, 0xc1, 0x01, 0x76 };
599 const std::array<uint8_t, 32> s_bytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
600 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
601 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 };
602 const std::array<uint8_t, 32> z_bytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
603 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
604 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 };
605
606 const FqNative pub_x = FqNative::serialize_from_buffer(pub_x_bytes.data());
607 const FqNative pub_y = FqNative::serialize_from_buffer(pub_y_bytes.data());
608 const typename G1Native::affine_element public_key_native(pub_x, pub_y);
609 ASSERT_TRUE(public_key_native.on_curve()) << "Public key must be on curve";
610
611 // Native verification
612 const std::string message_string(z_bytes.begin(), z_bytes.end());
613 ecdsa_signature sig;
614 sig.r = r_bytes;
615 sig.s = s_bytes;
616 sig.v = 27;
617 bool native_verification =
618 ecdsa_verify_signature<Sha256Hasher, FqNative, FrNative, G1Native>(message_string, public_key_native, sig);
619
620 // Stdlib verification
622 const G1Stdlib public_key_ct = G1Stdlib::from_witness(&builder, public_key_native);
623
624 const std::vector<uint8_t> r_vec(r_bytes.begin(), r_bytes.end());
625 const std::vector<uint8_t> s_vec(s_bytes.begin(), s_bytes.end());
628
629 const std::vector<uint8_t> z_vec(z_bytes.begin(), z_bytes.end());
630 const stdlib::byte_array<Builder> hashed_message_ct(&builder, z_vec);
631
632 const stdlib::bool_t<Builder> signature_result =
633 stdlib::ecdsa_verify_signature<Builder, Curve, FqStdlib, FrStdlib, G1Stdlib>(
634 hashed_message_ct, public_key_ct, sig_ct);
635
636 bool stdlib_verification = signature_result.get_value();
637
638 const bool circuit_valid = CircuitChecker::check(builder);
639 ASSERT_TRUE(circuit_valid);
640
641 EXPECT_EQ(native_verification, stdlib_verification);
642}
643
644TEST(EcdsaTests, Secp256r1NafOverflowRegression)
645{
647
648 using FqNative = Curve::BaseFieldNative;
649 using G1Native = Curve::GroupNative;
650
651 using Builder = Curve::Builder;
652 using FrStdlib = Curve::ScalarField;
653 using FqStdlib = Curve::BaseField;
654 using G1Stdlib = Curve::Group;
655
656 const std::array<uint8_t, 32> pub_x_bytes = { 0xbd, 0xae, 0xdd, 0xff, 0x80, 0x69, 0x8b, 0xd0, 0xb5, 0xdb, 0x79,
657 0x10, 0xe1, 0xc6, 0x56, 0x9d, 0xc3, 0x4e, 0x77, 0x3b, 0xda, 0x69,
658 0x5e, 0x61, 0x5c, 0x87, 0xf5, 0x4e, 0x6a, 0x70, 0x7e, 0xd6 };
659 const std::array<uint8_t, 32> pub_y_bytes = { 0xc1, 0xb3, 0x69, 0x9c, 0x7e, 0xea, 0x97, 0xbe, 0x5e, 0x52, 0x3d,
660 0x47, 0x5c, 0x5f, 0x72, 0x00, 0x97, 0x2c, 0x61, 0x23, 0xf2, 0xcd,
661 0x3d, 0x59, 0x33, 0x54, 0xe7, 0x4d, 0x35, 0xd1, 0x85, 0x11 };
662 const std::array<uint8_t, 32> r_bytes = { 0x4d, 0xc6, 0x6f, 0x06, 0x26, 0x12, 0x5d, 0x49, 0xb5, 0xa7, 0x7d,
663 0x36, 0xc3, 0xf9, 0x7e, 0x9b, 0xb8, 0x29, 0x48, 0xf2, 0xbd, 0xdc,
664 0xf8, 0x43, 0xe5, 0xee, 0x13, 0x3c, 0xc8, 0x96, 0xf1, 0xd8 };
665 const std::array<uint8_t, 32> s_bytes = { 0x52, 0x59, 0x33, 0x34, 0x92, 0xf6, 0x68, 0x42, 0x1a, 0xe0, 0x63,
666 0x3f, 0x2d, 0x16, 0x92, 0x4b, 0x9f, 0x3b, 0xe9, 0x36, 0xee, 0xd3,
667 0xf1, 0x96, 0x28, 0x6e, 0x0a, 0x57, 0x5d, 0xe2, 0x9f, 0xb7 };
668 const std::array<uint8_t, 32> z_bytes = { 0x93, 0xd5, 0x0c, 0x3d, 0xd6, 0xd6, 0xc3, 0xf7, 0xf7, 0x55, 0x0e,
669 0x69, 0x76, 0x40, 0x2b, 0x89, 0xaa, 0xf2, 0xd6, 0x8a, 0x7a, 0x94,
670 0x80, 0x44, 0x69, 0xaa, 0x69, 0x03, 0x15, 0xb8, 0x64, 0x31 };
671
672 const FqNative pub_x = FqNative::serialize_from_buffer(pub_x_bytes.data());
673 const FqNative pub_y = FqNative::serialize_from_buffer(pub_y_bytes.data());
674 const typename G1Native::affine_element public_key_native(pub_x, pub_y);
675 ASSERT_TRUE(public_key_native.on_curve()) << "Public key must be on curve";
676
678 const G1Stdlib public_key_ct = G1Stdlib::from_witness(&builder, public_key_native);
679
680 const std::vector<uint8_t> r_vec(r_bytes.begin(), r_bytes.end());
681 const std::vector<uint8_t> s_vec(s_bytes.begin(), s_bytes.end());
684
685 const std::vector<uint8_t> z_vec(z_bytes.begin(), z_bytes.end());
686 const stdlib::byte_array<Builder> hashed_message_ct(&builder, z_vec);
687
688 const stdlib::bool_t<Builder> signature_result =
689 stdlib::ecdsa_verify_signature<Builder, Curve, FqStdlib, FrStdlib, G1Stdlib>(
690 hashed_message_ct, public_key_ct, sig_ct);
691
692 // Verification should succeed for this valid signature
693 EXPECT_TRUE(signature_result.get_value());
694
695 const bool circuit_valid = CircuitChecker::check(builder);
696 ASSERT_TRUE(circuit_valid);
697}
698
699// Regression for the `is_u2_acceptable` AND-term on `ecdsa_impl.hpp`'s validity flag. The fake-GLV
700// path substitutes u₂ ∈ {0, ±1} with u₂ = 2 (to avoid a ROM x-collision between Q and T₂), so the
701// MSM result is wrong; rejection relies on `is_u2_acceptable` being ANDed into the validity bit.
702// We construct an honest signature with s = r so u₂ ≡ 1, confirm native ECDSA accepts, and assert
703// the circuit rejects. Probability of hitting this branch honestly is ≈ 1/n ≈ 2⁻²⁵⁶.
704TEST(EcdsaTests, Secp256r1HonestU2EqualsOneRejected)
705{
707 using Builder = Curve::Builder;
708 using FqNative = Curve::BaseFieldNative;
709 using FrNative = Curve::ScalarFieldNative;
710 using G1Native = Curve::GroupNative;
711 using FrStdlib = Curve::ScalarField;
712 using FqStdlib = Curve::BaseField;
713 using G1Stdlib = Curve::Group;
714 using bool_t = stdlib::bool_t<Builder>;
715
716 // Pick k so r = (kG).x mod n is in low-s range (so s = r passes is_s_in_range).
717 const FrNative d("0xd67abee717b3fc725adf59e2cc8cd916435c348b277dd814a34e3ceb279436c2");
718 const G1Native::affine_element Q = G1Native::one * d;
719 FrNative k;
720 FrNative r;
721 const uint256_t half_n_plus_one = (uint256_t(FrNative::modulus) + uint256_t(1)) / uint256_t(2);
722 for (uint64_t seed = 1; seed < 100; ++seed) {
723 k = FrNative(uint256_t(seed));
724 const G1Native::affine_element R(G1Native::one * k);
725 r = FrNative(uint256_t(R.x));
726 if (r != FrNative::zero() && uint256_t(r) < half_n_plus_one) {
727 break;
728 }
729 }
730 ASSERT_NE(r, FrNative::zero());
731 ASSERT_LT(uint256_t(r), half_n_plus_one);
732
733 // s = r and z = r·(k − d) give s = k⁻¹·(z + r·d) = r, so u₂ = r/s ≡ 1.
734 const FrNative s = r;
735 const FrNative z = r * (k - d);
736 const FrNative s_inv = s.invert();
737 ASSERT_EQ(r * s_inv, FrNative::one());
738
739 // Native ECDSA accepts (computed manually since the existing helper SHA-hashes its input).
740 {
741 const FrNative u1 = z * s_inv;
742 const FrNative u2 = r * s_inv;
743 const G1Native::affine_element R_native(G1Native::one * u1 + G1Native::element(Q) * u2);
744 ASSERT_FALSE(R_native.is_point_at_infinity());
745 EXPECT_EQ(FrNative(uint256_t(R_native.x)), r);
746 }
747
749 G1Stdlib public_key_ct = G1Stdlib::from_witness(&builder, Q);
750
751 std::array<uint8_t, 32> r_bytes;
752 std::array<uint8_t, 32> s_bytes;
753 std::array<uint8_t, 32> z_bytes;
754 FrNative::serialize_to_buffer(r, r_bytes.data());
755 FrNative::serialize_to_buffer(s, s_bytes.data());
756 FqNative::serialize_to_buffer(FqNative(uint256_t(z)), z_bytes.data());
757
758 const std::vector<uint8_t> r_vec(r_bytes.begin(), r_bytes.end());
759 const std::vector<uint8_t> s_vec(s_bytes.begin(), s_bytes.end());
760 const std::vector<uint8_t> z_vec(z_bytes.begin(), z_bytes.end());
763 stdlib::byte_array<Builder> hashed_message(&builder, z_vec);
764
765 bool_t result = stdlib::ecdsa_verify_signature<Builder, Curve, FqStdlib, FrStdlib, G1Stdlib>(
766 hashed_message, public_key_ct, sig_ct);
767 EXPECT_FALSE(result.get_value()) << "circuit must reject honest u₂ ≡ 1";
768 EXPECT_TRUE(CircuitChecker::check(builder));
769}
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
Curve::ScalarFieldNative FrNative
Curve::BaseField Fq
void tampering(std::string message_string, ecdsa_key_pair< FrNative, G1Native > &account, ecdsa_signature &signature, TamperingMode mode)
void ecdsa_verification_circuit(Builder &builder, const stdlib::byte_array< Builder > &hashed_message, const ecdsa_key_pair< FrNative, G1Native > &account, const ecdsa_signature &signature, const bool signature_verification_result, const bool expected_circuit_result, const TamperingMode mode)
void test_signature_double_generator()
static constexpr FrNative private_key
Curve::BaseFieldNative FqNative
void test_verify_signature(bool random_signature, TamperingMode mode)
Curve::GroupNative G1Native
std::conditional_t< Curve::type==bb::CurveType::SECP256K1, bb::curve::SECP256K1, bb::curve::SECP256R1 > CurveType
std::pair< ecdsa_key_pair< FrNative, G1Native >, ecdsa_signature > generate_dummy_ecdsa_data(std::string message_string, bool random_signature)
void test_signature_generator(bool is_minus_generator, bool signature_result, bool expected_circuit_result)
Curve::Group G1
void test_wycherproof(std::vector< stdlib::WycherproofTest< CurveType > > tests)
Construct tests based on data fetched from the Wycherproof project.
Curve::Builder Builder
std::pair< G1, stdlib::ecdsa_signature< Builder > > create_stdlib_ecdsa_data(Builder &builder, const ecdsa_key_pair< FrNative, G1Native > &account, const ecdsa_signature &signature, const TamperingMode mode)
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
typename grumpkin::g1 Group
Definition grumpkin.hpp:62
Implements boolean logic in-circuit.
Definition bool.hpp:60
bool get_value() const
Definition bool.hpp:125
void assert_equal(const bool_t &rhs, std::string const &msg="bool_t::assert_equal") const
Implements copy constraint for bool_t elements.
Definition bool.cpp:433
Represents a dynamic array of bytes in-circuit.
byte_array & write(byte_array const &other)
Appends the contents of another byte_array (other) to the end of this one.
static byte_array constant_padding(Builder *parent_context, size_t num_bytes, uint8_t value=0)
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:60
const std::vector< WycherproofSecp256k1 > secp256k1_tests
Test for Secp256k1 ECDSA signatures taken from the Wycherproof project.
const std::vector< WycherproofSecp256r1 > secp256r1_tests
Test for Secp256r1 ECDSA signatures taken from the Wycherproof project.
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
@ SECP256K1
Definition types.hpp:10
::testing::Types< curve::BN254, curve::Grumpkin > Curves
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
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 field zero()
VectorField result