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::fr;
26 using FqNative = Curve::fq;
27 using G1Native = Curve::g1;
28
29 // Stdlib types
30 using Fr = Curve::bigfr_ct;
31 using Fq = Curve::fq_ct;
32 using G1 = Curve::g1_bigfr_ct;
33 using bool_t = Curve::bool_ct;
34
35 // Reproducible signature
36 static constexpr FrNative private_key =
37 FrNative("0xd67abee717b3fc725adf59e2cc8cd916435c348b277dd814a34e3ceb279436c2");
38
51
53 bool random_signature)
54 {
56
57 account.private_key = random_signature ? FrNative::random_element() : private_key;
58 account.public_key = G1Native::one * account.private_key;
59
60 ecdsa_signature signature =
61 ecdsa_construct_signature<Sha256Hasher, FqNative, FrNative, G1Native>(message_string, account);
62
63 if (random_signature) {
64 // Logging in case of random signature
65 info("The private key used generate this signature is: ", private_key);
66 }
67
68 return { account, signature };
69 }
70
77 {
78 // Generate signature
79 ecdsa_signature signature;
80
81 FrNative fr_hash = FrNative::one();
82 FrNative k = FrNative::random_element();
83 typename G1Native::affine_element R = G1Native::one * k;
84 FqNative::serialize_to_buffer(R.x, &signature.r[0]);
85
86 FrNative r = FrNative::serialize_from_buffer(&signature.r[0]);
87 FrNative k_inverse = k.invert();
88 FrNative s = k_inverse * (fr_hash + r * private_key);
89 bool is_s_low = (static_cast<uint256_t>(s) < (FrNative::modulus + 1) / 2);
90 s = is_s_low ? s : -s;
91 FrNative::serialize_to_buffer(s, &signature.s[0]);
92
93 FqNative r_fq(R.x);
94 bool is_r_finite = (uint256_t(r_fq) == uint256_t(r));
95 bool y_parity = uint256_t(R.y).get_bit(0);
96 bool recovery_bit = y_parity ^ is_s_low;
97 constexpr uint8_t offset = 27;
98
99 int value =
100 offset + static_cast<int>(recovery_bit) + (static_cast<uint8_t>(2) * static_cast<int>(!is_r_finite));
101 BB_ASSERT_LTE(value, UINT8_MAX);
102 signature.v = static_cast<uint8_t>(value);
103
104 // Natively verify signature
105 FrNative s_inverse = s.invert();
106 typename G1Native::affine_element Q = G1Native::one * ((fr_hash * s_inverse) + (r * s_inverse * private_key));
107 BB_ASSERT_EQ(static_cast<uint512_t>(Q.x),
108 static_cast<uint512_t>(r),
109 "Signature with out of bounds message failed verification");
110
111 return signature;
112 }
113
114 std::string tampering(std::string message_string,
116 ecdsa_signature& signature,
117 TamperingMode mode)
118 {
119 std::string failure_msg;
120
121 switch (mode) {
123 // Invalidate the signature by changing r.
124 FrNative r = FrNative::serialize_from_buffer(&signature.r[0]);
125 r += FrNative::one();
126
127 FrNative::serialize_to_buffer(r, &signature.r[0]);
128 break;
129 }
131 // Invalidate the signature by changing s.
132 FrNative s = FrNative::serialize_from_buffer(&signature.s[0]);
133 s += FrNative::one();
134
135 FrNative::serialize_to_buffer(s, &signature.s[0]);
136 break;
137 }
139 // Invalidate the signature by changing s to -s.
140 FrNative s = FrNative::serialize_from_buffer(&signature.s[0]);
141 s = -s;
142
143 FrNative::serialize_to_buffer(s, &signature.s[0]);
144 failure_msg = "ECDSA input validation: the s component of the signature is bigger than Fr::modulus - s.: "
145 "hi limb."; // The second part of the message is added by the range constraint
146 break;
147 }
149 // Invalidate the circuit by passing a message whose hash is bigger than n
150 // (the message will be hard-coded in the circuit at a later point)
152
153 failure_msg = "ECDSA input validation: the hash of the message is bigger than the order of the elliptic "
154 "curve.: hi limb."; // The second part of the message is added by the range constraint
155 break;
156 }
158 // Invalidate signature by setting r to 0
159 signature.r = std::array<uint8_t, 32>{};
160
161 failure_msg = "ECDSA input validation: the r component of the signature is zero.";
162 break;
163 }
165 // Invalidate signature by setting s to 0
166 signature.s = std::array<uint8_t, 32>{};
167
168 failure_msg = "ECDSA input validation: the s component of the signature is zero.";
169 break;
170 }
172 // Invalidate the signature by making making u1 * G + u2 * P return the point at infinity
173
174 // Compute H(m)
175 std::vector<uint8_t> buffer;
176 std::ranges::copy(message_string, std::back_inserter(buffer));
178
179 // Override the public key: new public key is (-hash) * r^{-1} * G
180 FrNative fr_hash = FrNative::serialize_from_buffer(&hash[0]);
181 FrNative r = FrNative::serialize_from_buffer(&signature.r[0]);
182 FrNative r_inverse = r.invert();
183 FrNative modified_private_key = r_inverse * (-fr_hash);
184 account.public_key = G1Native::one * modified_private_key;
185
186 // Verify that the result is the point at infinity
187 auto P = G1Native::one * fr_hash + account.public_key * r;
188 BB_ASSERT_EQ(P.is_point_at_infinity(), true);
189
190 failure_msg = "ECDSA validation: the result of the batch multiplication is the point at infinity.";
191 break;
192 }
194 // Invalidate the circuit by passing a public key which is not on the curve
195 account.public_key.x = account.public_key.y;
196 BB_ASSERT_EQ(account.public_key.on_curve(), false);
197
198 failure_msg = "ECDSA input validation: the public key is not a point on the elliptic curve.";
199 break;
200 }
202 // Invalidate the circuit by passing a public key which is not on the curve
203 account.public_key.self_set_infinity();
204 BB_ASSERT_EQ(account.public_key.is_point_at_infinity(), true);
205
206 failure_msg = "ECDSA input validation: the public key is the point at infinity.";
207 break;
208 }
210 break;
211 }
212
213 // Natively verify that the tampering was successfull
214 bool is_signature_valid = ecdsa_verify_signature<Sha256Hasher, FqNative, FrNative, G1Native>(
215 message_string, account.public_key, signature);
217 // If either s >= (n+1)/2 or the result of the scalar multiplication is the point at infinity, then the
218 // verification function raises an error, we treat it as an invalid signature
219 is_signature_valid = false;
220 }
221
222 bool expected = mode == TamperingMode::None;
223 BB_ASSERT_EQ(is_signature_valid,
224 expected,
225 "Signature verification returned a different result from the expected one. If the signature was "
226 "randomly generated, there is a (very) small chance this is not a bug.");
227
228 return failure_msg;
229 }
230
232 Builder& builder, const ecdsa_key_pair<FrNative, G1Native>& account, const ecdsa_signature& signature)
233 {
234 // We construct the point via its x,y-coordinates to avoid the on curve check of G1::from_witness. In this way
235 // we test the on curve check of the ecdsa verification function
236 Fq x = Fq::from_witness(&builder, account.public_key.x);
237 Fq y = Fq::from_witness(&builder, account.public_key.y);
238 bool_t is_infinity(
239 stdlib::witness_t<Builder>(&builder, account.public_key.is_point_at_infinity() ? fr::one() : fr::zero()),
240 false);
241 G1 pub_key(x, y, is_infinity);
242 pub_key.set_free_witness_tag();
243 BB_ASSERT_EQ(pub_key.is_point_at_infinity().get_value(), account.public_key.is_point_at_infinity());
244
245 std::vector<uint8_t> rr(signature.r.begin(), signature.r.end());
246 std::vector<uint8_t> ss(signature.s.begin(), signature.s.end());
247
250
251 return { pub_key, sig };
252 }
253
255 const stdlib::byte_array<Builder>& hashed_message,
257 const ecdsa_signature& signature,
258 const bool signature_verification_result,
259 const bool circuit_checker_result,
260 const std::string failure_msg)
261
262 {
263 auto [public_key, sig] = create_stdlib_ecdsa_data(builder, account, signature);
264
265 // Verify signature
266 stdlib::bool_t<Builder> signature_result =
267 stdlib::ecdsa_verify_signature<Builder, Curve, Fq, Fr, G1>(hashed_message, public_key, sig);
268
269 // Enforce verification returns the expected result
270 signature_result.assert_equal(stdlib::bool_t<Builder>(signature_verification_result));
271
272 // Check native values
273 EXPECT_EQ(signature_result.get_value(), signature_verification_result);
274
275 // Log data
276 std::cerr << "num gates = " << builder.get_estimated_num_finalized_gates() << std::endl;
277 benchmark_info(Builder::NAME_STRING,
278 "ECDSA",
279 "Signature Verification Test",
280 "Gate Count",
281 builder.get_estimated_num_finalized_gates());
282
283 // Circuit checker
284 bool is_circuit_satisfied = CircuitChecker::check(builder);
285 EXPECT_EQ(is_circuit_satisfied, circuit_checker_result);
286
287 // Check the error
288 EXPECT_EQ(builder.err(), failure_msg);
289 }
290
292 std::vector<uint8_t>& message_bytes,
293 TamperingMode mode)
294 {
295 stdlib::byte_array<Builder> message(&builder, message_bytes);
296 stdlib::byte_array<Builder> hashed_message;
297
298 if (mode == TamperingMode::OutOfBoundsHash) {
299 // In this case the message is already hashed, so we mock the hashing constraints for consistency but
300 // hard-code the message
301 [[maybe_unused]] stdlib::byte_array<Builder> _ =
303
304 // Hard-coded witness
305 std::array<uint8_t, 32> hashed_message_witness;
306
307 // The hashed message is FrNative::modulus + 1
308 FqNative fr_hash = FqNative(FrNative::modulus + 1);
309 FqNative::serialize_to_buffer(fr_hash, &hashed_message_witness[0]);
310
311 hashed_message = stdlib::byte_array<Builder>(
312 &builder, std::vector<uint8_t>(hashed_message_witness.begin(), hashed_message_witness.end()));
313 } else {
314 hashed_message = static_cast<stdlib::byte_array<Builder>>(stdlib::SHA256<Builder>::hash(message));
315 }
316
317 return hashed_message;
318 }
319
320 void test_verify_signature(bool random_signature, TamperingMode mode)
321 {
322 // Map tampering mode to signature verification result
323 bool signature_verification_result =
325 // Map tampering mode to circuit checker result
326 bool circuit_checker_result =
327 (mode == TamperingMode::None) || (mode == TamperingMode::InvalidR) || (mode == TamperingMode::InvalidS);
328
329 std::string message_string = "Goblin";
330 std::vector<uint8_t> message_bytes(message_string.begin(), message_string.end());
331
332 auto [account, signature] = generate_dummy_ecdsa_data(message_string, /*random_signature=*/random_signature);
333
334 // Tamper with the signature
335 std::string failure_msg = tampering(message_string, account, signature, mode);
336
337 // Create ECDSA verification circuit
339
340 // Compute H(m)
341 stdlib::byte_array<Builder> hashed_message = construct_hashed_message(builder, message_bytes, mode);
342
343 // ECDSA verification
345 hashed_message,
346 account,
347 signature,
348 signature_verification_result,
349 circuit_checker_result,
350 failure_msg);
351 }
352
359 {
360 for (auto test : tests) {
361 // Keypair
363 account.private_key = FrNative::one(); // Dummy value, unused
364 account.public_key = typename G1Native::affine_element(test.x, test.y);
365
366 // Signature
367 std::array<uint8_t, 32> r;
368 std::array<uint8_t, 32> s;
369 uint8_t v = 0; // Dummy value, unused
370 FrNative::serialize_to_buffer(test.r, &r[0]);
371 FrNative::serialize_to_buffer(test.s, &s[0]);
372
373 // Create ECDSA verification circuit
375
376 // Compute H(m)
377 stdlib::byte_array<Builder> hashed_message =
379
380 // ECDSA verification
382 hashed_message,
383 account,
384 { r, s, v },
385 test.is_valid_signature,
386 test.is_circuit_satisfied,
387 test.failure_msg);
388 }
389 }
390};
391
392using Curves = testing::Types<stdlib::secp256k1<UltraCircuitBuilder>,
396
398
399TYPED_TEST(EcdsaTests, VerifyRandomSignature)
400{
401 TestFixture::test_verify_signature(/*random_signature=*/true, TestFixture::TamperingMode::None);
402}
403
404TYPED_TEST(EcdsaTests, VerifySignature)
405{
406 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::None);
407}
408
410{
411 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidR);
412}
413
415{
416 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidS);
417}
418
420{
421 // Disable asserts because native ecdsa verification raises an error if s >= (n+1)/2
423 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::HighS);
424}
425
427{
428 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::ZeroR);
429}
430
432{
433 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::ZeroS);
434}
435
436TYPED_TEST(EcdsaTests, InvalidPubKey)
437{
438 // Disable asserts because `validate_on_curve` raises an error in the `mult_madd` function:
439 // BB_ASSERT_EQ(remainder_1024.lo, uint512_t(0))
441 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidPubKey);
442}
443
444TYPED_TEST(EcdsaTests, InfinityPubKey)
445{
446 // Disable asserts to avoid errors trying to invert zero
448 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InfinityPubKey);
449}
450
451TYPED_TEST(EcdsaTests, OutOfBoundsHash)
452{
453 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::OutOfBoundsHash);
454}
455
456TYPED_TEST(EcdsaTests, InfinityScalarMul)
457{
458 // Disable asserts because native ecdsa verification raises an error if the result of the scalar multiplication is
459 // the point at infinity
461 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InfinityScalarMul);
462}
463
465{
466 if constexpr (TypeParam::type == bb::CurveType::SECP256K1) {
467 TestFixture::test_wycherproof(stdlib::secp256k1_tests);
468 } else {
469 TestFixture::test_wycherproof(stdlib::secp256r1_tests);
470 }
471}
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:88
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:163
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:32
Curve::bigfr_ct Fr
Curve::g1_bigfr_ct G1
static constexpr FrNative private_key
void test_verify_signature(bool random_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 circuit_checker_result, const std::string failure_msg)
Curve::fr FrNative
std::string tampering(std::string message_string, ecdsa_key_pair< FrNative, G1Native > &account, ecdsa_signature &signature, TamperingMode mode)
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)
std::pair< G1, stdlib::ecdsa_signature< Builder > > create_stdlib_ecdsa_data(Builder &builder, const ecdsa_key_pair< FrNative, G1Native > &account, const ecdsa_signature &signature)
stdlib::byte_array< Builder > construct_hashed_message(Builder &builder, std::vector< uint8_t > &message_bytes, TamperingMode mode)
Curve::fq FqNative
void test_wycherproof(std::vector< stdlib::WycherproofTest< CurveType > > tests)
Construct tests based on data fetched from the Wycherproof project.
Curve::fq_ct Fq
Curve::Builder Builder
ecdsa_signature generate_signature_out_of_bounds_hash()
Generate valid signature for the message Fr(1)
Curve::bool_ct bool_t
Curve::g1 G1Native
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
constexpr bool get_bit(uint64_t bit_index) const
static byte_array< Builder > hash(const byte_array_ct &input)
Definition sha256.cpp:308
Implements boolean logic in-circuit.
Definition bool.hpp:59
bool get_value() const
Definition bool.hpp:111
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:423
Represents a dynamic array of bytes in-circuit.
void benchmark_info(Args...)
Info used to store circuit statistics during CI/CD with concrete structure. Writes straight to log.
Definition log.hpp:109
void info(Args... args)
Definition log.hpp:74
AluTraceBuilder builder
Definition alu.test.cpp:123
ssize_t offset
Definition engine.cpp:36
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
void hash(State &state) noexcept
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.
TYPED_TEST_SUITE(ShpleminiTest, TestSettings)
TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
@ 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:20
std::array< uint8_t, 32 > r
Definition ecdsa.hpp:26
std::array< uint8_t, 32 > s
Definition ecdsa.hpp:27
static constexpr field one()
static constexpr field zero()