Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecdsa_impl.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#pragma once
8
13
14namespace bb::stdlib {
15
16namespace {
18}
19
33template <typename Builder, typename Curve, typename Fq, typename Fr, typename G1>
35 const G1& public_key,
36 const ecdsa_signature<Builder>& sig)
37{
38 auto check_smaller_than = [](const uint512_t& value,
39 const uint512_t& max_value,
40 const std::string& value_label,
41 const std::string& max_value_label) {
42 std::string msg =
43 "The " + value_label + " is bigger than " + max_value_label + ". This will produce an unsatisfied circuit.";
44 if (value >= max_value) {
45 info(msg);
46 }
47 };
48
49 auto check_is_not_zero = [](const uint512_t& value, const std::string& label) {
50 std::string msg = "The " + label + " is equal to zero. This will produce an unsatisfied circuit.";
51 if (value == 0) {
52 info(msg);
53 }
54 };
55
56 // H(m) < n
57 uint512_t hash_value = static_cast<uint512_t>(Fr(hashed_message).get_value());
58 check_smaller_than(hash_value, Fr::modulus, "hash of the message", "order of the elliptic curve");
59
60 // P \in E
61 if (!public_key.get_value().on_curve()) {
62 info("The public key is not a point on the elliptic curve. This will produce an unsatisfied circuit.");
63 }
64
65 // 0 < r < n
66 uint512_t r_value = static_cast<uint512_t>(Fr(sig.r).get_value());
67 check_smaller_than(r_value, Fr::modulus, "r component of the signature", "order of the elliptic curve");
68 check_is_not_zero(r_value, "r component of the signature");
69
70 // 0 < s < (n+1)/2
71 uint512_t s_value = static_cast<uint512_t>(Fr(sig.s).get_value());
72 check_smaller_than(s_value, (Fr::modulus + 1) / 2, "s component of the signature", "order of the elliptic curve");
73 check_is_not_zero(s_value, "s component of the signature");
74}
75
127template <typename Builder, typename Curve, typename Fq, typename Fr, typename G1>
129 const G1& public_key,
130 const ecdsa_signature<Builder>& sig)
131{
132 // Validate inputs for debugging
133 validate_inputs<Builder, Curve, Fq, Fr>(hashed_message, public_key, sig);
134
135 // Fetch the context
136 bool message_is_not_constant = hashed_message.get_context() != nullptr;
137 bool public_key_is_not_constant = public_key.get_context() != nullptr;
138 bool sig_is_not_constant = sig.get_context() != nullptr;
139 BB_ASSERT_EQ(message_is_not_constant || public_key_is_not_constant || sig_is_not_constant,
140 true,
141 "At least one of the inputs should be non-constant.");
142
143 Builder* ctx = nullptr;
144 if (message_is_not_constant) {
145 ctx = hashed_message.get_context();
146 } else if (public_key_is_not_constant) {
147 ctx = public_key.get_context();
148 } else if (sig_is_not_constant) {
149 ctx = sig.get_context();
150 } else {
152 "At least one of the inputs passed should be non-constant. Assert failed to catch this condition.");
153 }
154
155 // Turn the hashed message into an element of Fr
156 // The assertion means that an honest prover has a small probability of not being able to generate a valid proof if
157 // H(m) >= n. Enforcing this condition introduces a small number of gates, and ensures that signatures cannot be
158 // forged by finding a collision of H modulo n. While finding such a collision is supposed to be hard even modulo n,
159 // we protect against this case with this cheap check.
160 Fr z(hashed_message);
161 z.assert_is_in_field();
162
163 // Step 1.
164 public_key.validate_on_curve();
165
166 // Step 2.
167 Fr r(sig.r);
168 r.assert_is_in_field(); // r < n
169 r.assert_is_not_equal(Fr::zero()); // 0 < r
170
171 // Step 3.
172 Fr s(sig.s);
173 s.assert_less_than((Fr::modulus + 1) / 2); // s < (n+1) / 2
174 s.assert_is_not_equal(Fr::zero()); // 0 < s
175
176 // Step 4.
177 Fr u1 = z.div_without_denominator_check(s);
178 Fr u2 = r.div_without_denominator_check(s);
179
180 G1 result;
181 if constexpr (Curve::type == bb::CurveType::SECP256K1) {
182 result = G1::secp256k1_ecdsa_mul(public_key, u1, u2);
183 } else {
184 result = G1::batch_mul({ G1::one(ctx), public_key }, { u1, u2 });
185 }
186
187 // Step 5.
188 if (result.get_value().is_point_at_infinity()) {
189 info("The result of the batch multiplication is the point at infinity. This will produce an unsatisfied "
190 "circuit.");
191 }
192 result.is_point_at_infinity().assert_equal(bool_t<Builder>(false));
193
194 // Step 6.
195 // We reduce result.x to 2^s, where s is the smallest s.t. 2^s > q. It is cheap in terms of constraints, and avoids
196 // possible edge cases
197 result.x.self_reduce();
198
199 // Transfer Fq value result.x to Fr (this is just moving from a C++ class to another)
200 Fr result_x_mod_r = Fr::unsafe_construct_from_limbs(result.x.binary_basis_limbs[0].element,
201 result.x.binary_basis_limbs[1].element,
202 result.x.binary_basis_limbs[2].element,
203 result.x.binary_basis_limbs[3].element);
204 // Copy maximum limb values from Fq to Fr: this is needed by the subtraction happening in the == operator
205 for (size_t idx = 0; idx < 4; idx++) {
206 result_x_mod_r.binary_basis_limbs[idx].maximum_value = result.x.binary_basis_limbs[idx].maximum_value;
207 }
208
209 // Check result.x = r mod n
210 bool_t<Builder> is_signature_valid = result_x_mod_r == r;
211
212 // Logging
213 if (is_signature_valid.get_value()) {
214 info("Signature verification succeeded.");
215 } else {
216 info("Signature verification failed");
217 }
218
219 return is_signature_valid;
220}
221
229template <typename Builder> void generate_ecdsa_verification_test_circuit(Builder& builder, size_t num_iterations)
230{
232
233 // Native types
234 using FrNative = typename Curve::fr;
235 using FqNative = typename Curve::fq;
236 using G1Native = typename Curve::g1;
237
238 // Stdlib types
239 using Fr = typename Curve::bigfr_ct;
240 using Fq = typename Curve::fq_ct;
241 using G1 = typename Curve::g1_bigfr_ct;
242
243 std::string message_string = "Instructions unclear, ask again later.";
244
246 for (size_t i = 0; i < num_iterations; i++) {
247 // Generate unique signature for each iteration
248 account.private_key = FrNative::random_element(&engine);
249 account.public_key = G1Native::one * account.private_key;
250
251 crypto::ecdsa_signature signature =
252 crypto::ecdsa_construct_signature<crypto::Sha256Hasher, FqNative, FrNative, G1Native>(message_string,
253 account);
254
255 bool native_verification = crypto::ecdsa_verify_signature<crypto::Sha256Hasher, FqNative, FrNative, G1Native>(
256 message_string, account.public_key, signature);
257 BB_ASSERT_EQ(native_verification, true, "Native ECDSA verification failed while generating test circuit.");
258
259 std::vector<uint8_t> rr(signature.r.begin(), signature.r.end());
260 std::vector<uint8_t> ss(signature.s.begin(), signature.s.end());
261
262 G1 public_key = G1::from_witness(&builder, account.public_key);
263
265
266 byte_array<Builder> message(&builder, message_string);
267
268 // Compute H(m)
269 stdlib::byte_array<Builder> hashed_message =
271
272 // Verify ecdsa signature
273 bool_t<Builder> result =
274 stdlib::ecdsa_verify_signature<Builder, Curve, Fq, Fr, G1>(hashed_message, public_key, sig);
275 result.assert_equal(bool_t<Builder>(true));
276 }
277}
278
279} // namespace bb::stdlib
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:59
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:109
Represents a dynamic array of bytes in-circuit.
Builder * get_context() const
void info(Args... args)
Definition log.hpp:70
AluTraceBuilder builder
Definition alu.test.cpp:123
numeric::RNG & engine
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:190
void generate_ecdsa_verification_test_circuit(Builder &builder, size_t num_iterations)
Generate a simple ecdsa verification circuit for testing purposes.
bool_t< Builder > ecdsa_verify_signature(const stdlib::byte_array< Builder > &hashed_message, const G1 &public_key, const ecdsa_signature< Builder > &sig)
Verify ECDSA signature. Returns bool_t(true/false) depending on whether the signature is valid or not...
void validate_inputs(const stdlib::byte_array< Builder > &hashed_message, const G1 &public_key, const ecdsa_signature< Builder > &sig)
Validate the inputs used by the verification function and return messages if they produce an invalid ...
@ SECP256K1
Definition types.hpp:10
Curve::ScalarField Fr
Curve::AffineElement G1
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 uint256_t modulus
static constexpr field zero()
stdlib::byte_array< Builder > s
Definition ecdsa.hpp:16
Builder * get_context() const
Definition ecdsa.hpp:18
stdlib::byte_array< Builder > r
Definition ecdsa.hpp:15
void throw_or_abort(std::string const &err)