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
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: ", private_key);
67 }
68
69 return { account, signature };
70 }
71
72 std::string tampering(std::string message_string,
74 ecdsa_signature& signature,
76 {
77 std::string failure_msg;
78
79 switch (mode) {
81 // Invalidate the circuit by passing a public key with x >= q
82 // Do nothing here, tampering happens in circuit
83 failure_msg = "ECDSA input validation: coordinate(s) of the public key bigger than the base field modulus. "
84 "(x coordinate): hi limb.";
85 break;
86 }
88 // Invalidate the circuit by passing a public key with y >= q
89 // Do nothing here, tampering happens in circuit
90 failure_msg = "ECDSA input validation: coordinate(s) of the public key bigger than the base field modulus. "
91 "(y coordinate): hi limb.";
92 break;
93 }
95 // Invalidate the signature by changing r.
96 FrNative r = FrNative::serialize_from_buffer(&signature.r[0]);
97 r += FrNative::one();
98
99 FrNative::serialize_to_buffer(r, &signature.r[0]);
100 break;
101 }
103 // Invalidate the signature by changing s.
104 FrNative s = FrNative::serialize_from_buffer(&signature.s[0]);
105 s += FrNative::one();
106
107 FrNative::serialize_to_buffer(s, &signature.s[0]);
108 break;
109 }
111 // Invalidate the signature by changing s to -s.
112 FrNative s = FrNative::serialize_from_buffer(&signature.s[0]);
113 s = -s;
114
115 FrNative::serialize_to_buffer(s, &signature.s[0]);
116 failure_msg =
117 "ECDSA input validation: the s component of the signature is bigger than (Fr::modulus + 1)/2.: "
118 "hi limb."; // The second part of the message is added by the range constraint
119 break;
120 }
122 // Invalidate signature by setting r to 0
123 signature.r = std::array<uint8_t, 32>{};
124
125 failure_msg = "ECDSA input validation: the r component of the signature is zero.";
126 break;
127 }
129 // Invalidate signature by setting s to 0
130 signature.s = std::array<uint8_t, 32>{};
131
132 failure_msg = "ECDSA input validation: the s component of the signature is zero.";
133 break;
134 }
136 // Invalidate the signature by making making u1 * G + u2 * P return the point at infinity
137
138 // Compute H(m)
139 std::vector<uint8_t> buffer;
140 std::ranges::copy(message_string, std::back_inserter(buffer));
142
143 // Override the public key: new public key is (-hash) * r^{-1} * G
144 FrNative fr_hash = FrNative::serialize_from_buffer(&hash[0]);
145 FrNative r = FrNative::serialize_from_buffer(&signature.r[0]);
146 FrNative r_inverse = r.invert();
147 FrNative modified_private_key = r_inverse * (-fr_hash);
148 account.public_key = G1Native::one * modified_private_key;
149
150 // Verify that the result is the point at infinity
151 auto P = G1Native::one * fr_hash + account.public_key * r;
152 BB_ASSERT_EQ(P.is_point_at_infinity(), true);
153
154 failure_msg = "ECDSA validation: the result of the batch multiplication is the point at infinity.";
155 break;
156 }
158 // Invalidate the circuit by passing a public key which is not on the curve
159 account.public_key.x = account.public_key.y;
160 BB_ASSERT_EQ(account.public_key.on_curve(), false);
161
162 failure_msg = "ECDSA input validation: the public key is not a point on the elliptic curve.";
163 break;
164 }
166 // Invalidate the circuit by passing a public key which is not on the curve
167 account.public_key.self_set_infinity();
168 BB_ASSERT_EQ(account.public_key.is_point_at_infinity(), true);
169
170 failure_msg = "ECDSA input validation: the public key is the point at infinity.";
171 break;
172 }
174 break;
175 }
176
177 // Natively verify that the tampering was successfull
178 bool is_signature_valid = ecdsa_verify_signature<Sha256Hasher, FqNative, FrNative, G1Native>(
179 message_string, account.public_key, signature);
181 // If either s >= (n+1)/2 or the result of the scalar multiplication is the point at infinity, then the
182 // verification function raises an error, we treat it as an invalid signature
183 is_signature_valid = false;
184 }
186 // In these tampering modes nothing has changed and the tampering happens in circuit, so we override the
187 // result and set it to false
188 is_signature_valid = false;
189 }
190
191 bool expected = mode == TamperingMode::None;
192 BB_ASSERT_EQ(is_signature_valid,
193 expected,
194 "Signature verification returned a different result from the expected one. If the signature was "
195 "randomly generated, there is a (very) small chance this is not a bug.");
196
197 return failure_msg;
198 }
199
203 const ecdsa_signature& signature,
204 const TamperingMode mode)
205 {
206 // We construct the point via its x,y-coordinates to avoid the on curve check of G1::from_witness. In this way
207 // we test the on curve check of the ecdsa verification function
208 Fq x = Fq::from_witness(&builder, account.public_key.x);
209 Fq y = Fq::from_witness(&builder, account.public_key.y);
211 // To test the case in which one of the two coordinates is above the modulus of the base field, we need to
212 // override the limbs of the coordinates
213 uint256_t max_uint = (static_cast<uint256_t>(1) << 256) - 1;
214 for (size_t idx = 0; idx < 4; idx++) {
216 ? x.binary_basis_limbs[idx].element.get_witness_index()
217 : y.binary_basis_limbs[idx].element.get_witness_index(),
218 bb::fr(max_uint.slice(64 * idx, 64 * (idx + 1))));
219 }
220 }
221 bool_t is_infinity(
222 stdlib::witness_t<Builder>(&builder, account.public_key.is_point_at_infinity() ? fr::one() : fr::zero()),
223 false);
224 G1 pub_key(x, y, is_infinity, /*assert_on_curve=*/false);
225 pub_key.set_free_witness_tag();
226 BB_ASSERT_EQ(pub_key.is_point_at_infinity().get_value(), account.public_key.is_point_at_infinity());
227
228 std::vector<uint8_t> rr(signature.r.begin(), signature.r.end());
229 std::vector<uint8_t> ss(signature.s.begin(), signature.s.end());
230
233
234 return { pub_key, sig };
235 }
236
238 const stdlib::byte_array<Builder>& hashed_message,
240 const ecdsa_signature& signature,
241 const bool signature_verification_result,
242 const bool circuit_checker_result,
243 const std::string failure_msg,
244 const TamperingMode mode)
245
246 {
247 auto [public_key, sig] = create_stdlib_ecdsa_data(builder, account, signature, mode);
248
249 // Verify signature
250 stdlib::bool_t<Builder> signature_result =
251 stdlib::ecdsa_verify_signature<Builder, Curve, Fq, Fr, G1>(hashed_message, public_key, sig);
252
253 // Enforce verification returns the expected result
254 signature_result.assert_equal(stdlib::bool_t<Builder>(signature_verification_result));
255
256 // Check native values
257 EXPECT_EQ(signature_result.get_value(), signature_verification_result);
258
259 // Log data
260 size_t finalized_num_gates = builder.get_num_finalized_gates_inefficient();
261 info("num gates = ", finalized_num_gates);
262 benchmark_info(Builder::NAME_STRING, "ECDSA", "Signature Verification Test", "Gate Count", finalized_num_gates);
263
264 // Circuit checker
265 bool is_circuit_satisfied = CircuitChecker::check(builder);
266 EXPECT_EQ(is_circuit_satisfied, circuit_checker_result);
267
268 // Check the error
269 EXPECT_EQ(builder.err(), failure_msg);
270
271 return finalized_num_gates;
272 }
273
274 size_t test_verify_signature(bool random_signature, TamperingMode mode)
275 {
276 // Map tampering mode to signature verification result
277 bool signature_verification_result = (mode == TamperingMode::None) || (mode == TamperingMode::HighS);
278 // Map tampering mode to circuit checker result
279 bool circuit_checker_result =
281
282 std::string message_string = "Goblin";
283 std::vector<uint8_t> message_bytes(message_string.begin(), message_string.end());
284 std::array<uint8_t, 32> hashed_message_bytes_ = Sha256Hasher::hash(message_bytes);
285 std::vector<uint8_t> hashed_message_bytes;
286 hashed_message_bytes.reserve(32);
287 for (auto byte : hashed_message_bytes_) {
288 hashed_message_bytes.emplace_back(byte);
289 }
290
291 auto [account, signature] = generate_dummy_ecdsa_data(message_string, /*random_signature=*/random_signature);
292
293 // Tamper with the signature
294 std::string failure_msg = tampering(message_string, account, signature, mode);
295
296 // Create ECDSA verification circuit
298 stdlib::byte_array<Builder> hashed_message(&builder, hashed_message_bytes);
299
300 // ECDSA verification
302 hashed_message,
303 account,
304 signature,
305 signature_verification_result,
306 circuit_checker_result,
307 failure_msg,
308 mode);
309 }
310
317 {
318 for (auto test : tests) {
319 // Keypair
321 account.private_key = FrNative::one(); // Dummy value, unused
322 account.public_key = typename G1Native::affine_element(test.x, test.y);
323
324 // Signature
325 std::array<uint8_t, 32> r;
326 std::array<uint8_t, 32> s;
327 uint8_t v = 0; // Dummy value, unused
328 FrNative::serialize_to_buffer(test.r, &r[0]);
329 FrNative::serialize_to_buffer(test.s, &s[0]);
330
331 // Hashed message
332 std::array<uint8_t, 32> hashed_message_bytes_ = Sha256Hasher::hash(test.message);
333 std::vector<uint8_t> hashed_message_bytes;
334 hashed_message_bytes.reserve(32);
335 for (auto byte : hashed_message_bytes_) {
336 hashed_message_bytes.emplace_back(byte);
337 }
338
339 // Create ECDSA verification circuit
341 stdlib::byte_array<Builder> hashed_message(&builder, hashed_message_bytes);
342
343 // ECDSA verification
345 hashed_message,
346 account,
347 { r, s, v },
348 test.is_valid_signature,
349 test.is_circuit_satisfied,
350 test.failure_msg,
352 }
353 }
354};
355
356using Curves = testing::Types<stdlib::secp256k1<UltraCircuitBuilder>,
360
362
363TYPED_TEST(EcdsaTests, VerifyRandomSignature)
364{
365 TestFixture::test_verify_signature(/*random_signature=*/true, TestFixture::TamperingMode::None);
366}
367
368TYPED_TEST(EcdsaTests, VerifySignature)
369{
370 using Curve = TypeParam;
371
372 size_t finalized_num_gates =
373 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::None);
374 static constexpr size_t NUM_GATES_SECP256K1 = 41965;
375 static constexpr size_t NUM_GATES_SECP256R1 = IsMegaBuilder<typename Curve::Builder> ? 72014 : 72012;
376 BB_ASSERT_EQ(finalized_num_gates,
377 Curve::type == bb::CurveType::SECP256K1 ? NUM_GATES_SECP256K1 : NUM_GATES_SECP256R1,
378 "There has been a change in the number of gates for ECDSA verification");
379}
380
381TYPED_TEST(EcdsaTests, XCoordinateOverflow)
382{
384 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::XCoordinateOverflow);
385}
386
387TYPED_TEST(EcdsaTests, YCoordinateOverflow)
388{
390 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::YCoordinateOverflow);
391}
392
394{
395 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidR);
396}
397
399{
400 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidS);
401}
402
404{
405 // Disable asserts because native ecdsa verification raises an error if s >= (n+1)/2
407 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::HighS);
408}
409
411{
412 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::ZeroR);
413}
414
416{
417 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::ZeroS);
418}
419
420TYPED_TEST(EcdsaTests, InvalidPubKey)
421{
422 // Disable asserts because `validate_on_curve` raises an error in the `mult_madd` function:
423 // BB_ASSERT_EQ(remainder_1024.lo, uint512_t(0))
425 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InvalidPubKey);
426}
427
428TYPED_TEST(EcdsaTests, InfinityPubKey)
429{
430 // Disable asserts to avoid errors trying to invert zero
432 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InfinityPubKey);
433}
434
435TYPED_TEST(EcdsaTests, InfinityScalarMul)
436{
437 // Disable asserts because native ecdsa verification raises an error if the result of the scalar multiplication is
438 // the point at infinity
440 TestFixture::test_verify_signature(/*random_signature=*/false, TestFixture::TamperingMode::InfinityScalarMul);
441}
442
444{
445 if constexpr (TypeParam::type == bb::CurveType::SECP256K1) {
446 TestFixture::test_wycherproof(stdlib::secp256k1_tests);
447 } else {
448 TestFixture::test_wycherproof(stdlib::secp256r1_tests);
449 }
450}
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:77
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:32
size_t 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, const TamperingMode mode)
Curve::bigfr_ct Fr
Curve::g1_bigfr_ct G1
static constexpr FrNative private_key
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)
size_t test_verify_signature(bool random_signature, 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
Curve::bool_ct bool_t
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)
Curve::g1 G1Native
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
constexpr uint256_t slice(uint64_t start, uint64_t end) const
Implements boolean logic in-circuit.
Definition bool.hpp:59
bool get_value() const
Definition bool.hpp:124
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:421
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:110
void info(Args... args)
Definition log.hpp:75
AluTraceBuilder builder
Definition alu.test.cpp:124
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.
Definition api.hpp:5
TYPED_TEST_SUITE(ShpleminiTest, TestSettings)
field< Bn254FrParams > fr
Definition fr.hpp:174
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()