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: Complete, auditors: [Federico], commit: 05a381f8b31ae4648e480f1369e911b148216e8b}
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8
13
14namespace bb::stdlib {
15
16namespace {
18}
19
66template <typename Builder, typename Curve, typename Fq, typename Fr, typename G1>
68 const G1& public_key,
69 const ecdsa_signature<Builder>& sig)
70{
71 using bool_ct = stdlib::bool_t<Builder>;
72
73 BB_ASSERT_EQ(Fr::modulus.get_msb() + 1, 256UL, "The implementation assumes that the bit-length of Fr is 256 bits.");
74
75 // Fetch the context
76 Builder* builder = hashed_message.get_context();
77 builder = validate_context(builder, public_key.get_context());
79 BB_ASSERT_EQ(builder != nullptr, true, "At least one of the inputs should be non-constant.");
80
81 // Turn the hashed message into an element of Fr
82 // Note that we don't need to trim the length of the output of the hash function because the bit length of the
83 // scalar fields we work with (secp256k1, secp256r1) is equal to 256.
84 Fr z(hashed_message);
85
86 // Step 1.
87 bool_ct is_x_less_than_modulus = public_key.x().is_less_than(
88 Fq::modulus, "ECDSA input validation: x coordinate of the public key bigger than the base field modulus.");
89 bool_ct is_y_less_than_modulus = public_key.y().is_less_than(
90 Fq::modulus, "ECDSA input validation: y coordinate of the public key bigger than the base field modulus.");
91
92 // Step 2.
93 bool_ct is_point_at_infinity = public_key.is_point_at_infinity();
94
95 // Step 3.
96 // We conditionally select a public key whose x and y coordinates are smaller than the base field modulus. We need
97 // to do this to avoid circuit failures in the function validate_on_curve. Note that this doesn't allow any attack
98 // as the result of the verification takes into account whether the original point coordinates were valid or not.
99 typename Curve::AffineElementNative native_double_generator(Curve::GroupNative::one + Curve::GroupNative::one);
100 G1 double_generator(Fq(native_double_generator.x), Fq(native_double_generator.y), /*assert_on_curve=*/false);
101 G1 corrected_public_key = G1::conditional_assign(
102 is_point_at_infinity || !is_x_less_than_modulus || !is_y_less_than_modulus, double_generator, public_key);
103 bool_t<Builder> is_point_on_curve =
104 corrected_public_key.validate_on_curve(
105 "ECDSA input validation: the public key is not a point on the elliptic curve.", false) == Fq::zero();
106
107 // Step 4.
108 Fr r(sig.r);
109 bool_ct is_r_in_range = r.is_less_than(
110 Fr::modulus, "ECDSA input validation: the r component of the signature is bigger than Fr::modulus.");
111 bool_ct is_r_zero = r == Fr::zero();
112
113 // Step 5.
114 Fr s(sig.s);
115 bool_ct is_s_in_range =
116 s.is_less_than((Fr::modulus + 1) / 2,
117 "ECDSA input validation: the s component of the signature is bigger than (Fr::modulus + 1)/2.");
118 bool_ct is_s_zero = s == Fr::zero();
119
120 // Step 6.
121 // We conditionally select a non-zero scalar to perform the verification to avoid circuit failures when s = 0.
122 Fr corrected_s = Fr::conditional_assign(is_s_zero, Fr::one(), s);
123
124 Fr u1 = z.div_without_denominator_check(corrected_s);
125 Fr u2 = r.div_without_denominator_check(corrected_s);
126
127 G1 result;
128 if constexpr (Curve::type == bb::CurveType::SECP256K1) {
129 result = G1::secp256k1_ecdsa_mul(corrected_public_key, u1, u2);
130 } else {
131 // This error comes from the lookup tables used in batch_mul. We could get rid of it by setting with_edgecase =
132 // true. However, this would increase the gate count, and it would handle a case that should not appear in
133 // general: someone using plus or minus the generator as a public key.
134 if ((corrected_public_key.get_value().x == Curve::GroupNative::affine_one.x) && (!builder->failed())) {
135 builder->failure("ECDSA input validation: the public key is equal to plus or minus the generator point.");
136 }
137 result = G1::batch_mul(
138 { G1::one(builder), corrected_public_key }, { u1, u2 }, /*max_num_bits=*/0, /*with_edgecases=*/false);
139 }
140
141 // Step 7.
142 bool_ct result_is_infinity = result.is_point_at_infinity();
143
144 // Step 8.
145 result.x().reduce_mod_target_modulus();
146
147 // Transfer Fq value result.x() to Fr (this is just moving from a C++ class to another)
148 Fr result_x_mod_r = Fr::unsafe_construct_from_limbs(result.x().binary_basis_limbs[0].element,
149 result.x().binary_basis_limbs[1].element,
150 result.x().binary_basis_limbs[2].element,
151 result.x().binary_basis_limbs[3].element);
152 // Copy maximum limb values from Fq to Fr: this is needed by the subtraction happening in the == operator
153 for (size_t idx = 0; idx < 4; idx++) {
154 result_x_mod_r.binary_basis_limbs[idx].maximum_value = result.x().binary_basis_limbs[idx].maximum_value;
155 }
156
157 // Check result.x() = r mod n AND that no other check failed
158 bool_ct x_matches = result_x_mod_r == r;
159 bool_ct is_signature_valid = x_matches && !is_point_at_infinity && !result_is_infinity && is_r_in_range &&
160 !is_r_zero && is_s_in_range && !is_s_zero && is_point_on_curve &&
161 is_x_less_than_modulus && is_y_less_than_modulus;
162
163 // Logging
164 if (is_signature_valid.get_value()) {
165 vinfo("ECDSA signature verification succeeded.");
166 } else {
167 vinfo("ECDSA signature verification failed");
168 }
169
170 return is_signature_valid;
171}
172
180template <typename Builder> void generate_ecdsa_verification_test_circuit(Builder& builder, size_t num_iterations)
181{
183
184 // Native types
185 using FrNative = typename Curve::ScalarFieldNative;
186 using FqNative = typename Curve::BaseFieldNative;
187 using G1Native = typename Curve::GroupNative;
188
189 // Stdlib types
190 using Fr = typename Curve::ScalarField;
191 using Fq = typename Curve::BaseField;
192 using G1 = typename Curve::Group;
193
194 std::string message_string = "Instructions unclear, ask again later.";
195
197 for (size_t i = 0; i < num_iterations; i++) {
198 // Generate unique signature for each iteration
199 account.private_key = FrNative::random_element(&engine);
200 account.public_key = G1Native::one * account.private_key;
201
202 crypto::ecdsa_signature signature =
203 crypto::ecdsa_construct_signature<crypto::Sha256Hasher, FqNative, FrNative, G1Native>(message_string,
204 account);
205
206 bool native_verification = crypto::ecdsa_verify_signature<crypto::Sha256Hasher, FqNative, FrNative, G1Native>(
207 message_string, account.public_key, signature);
208 BB_ASSERT_EQ(native_verification, true, "Native ECDSA verification failed while generating test circuit.");
209
210 std::vector<uint8_t> rr(signature.r.begin(), signature.r.end());
211 std::vector<uint8_t> ss(signature.s.begin(), signature.s.end());
212
213 G1 public_key = G1::from_witness(&builder, account.public_key);
214
216
217 // Compute H(m) natively and pass as witness (mirrors ACIR which takes pre-hashed message)
218 auto hash_arr = crypto::sha256(std::vector<uint8_t>(message_string.begin(), message_string.end()));
219 stdlib::byte_array<Builder> hashed_message(&builder, std::vector<uint8_t>(hash_arr.begin(), hash_arr.end()));
220
221 // Verify ecdsa signature
222 bool_t<Builder> result =
223 stdlib::ecdsa_verify_signature<Builder, Curve, Fq, Fr, G1>(hashed_message, public_key, sig);
224 result.assert_equal(bool_t<Builder>(true));
225 }
226}
227
228} // namespace bb::stdlib
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
typename grumpkin::g1 Group
Definition grumpkin.hpp:62
Implements boolean logic in-circuit.
Definition bool.hpp:60
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.
Builder * get_context() const
#define vinfo(...)
Definition log.hpp:94
AluTraceBuilder builder
Definition alu.test.cpp:124
numeric::RNG & engine
Sha256Hash sha256(const ByteContainer &input)
SHA-256 hash function (FIPS 180-4)
Definition sha256.cpp:150
constexpr T get_msb(const T in)
Definition get_msb.hpp:50
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:245
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...
T * validate_context(T *ptr)
Definition field.hpp:17
@ SECP256K1
Definition types.hpp:10
Curve::AffineElement G1
grumpkin::fq Fq
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
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