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
69template <typename Builder, typename Curve, typename Fq, typename Fr, typename G1>
71 const G1& public_key,
72 const ecdsa_signature<Builder>& sig)
73{
74 using bool_ct = stdlib::bool_t<Builder>;
75
76 BB_ASSERT_EQ(Fr::modulus.get_msb() + 1, 256UL, "The implementation assumes that the bit-length of Fr is 256 bits.");
77
78 // Fetch the context
79 Builder* builder = hashed_message.get_context();
80 builder = validate_context(builder, public_key.get_context());
82 BB_ASSERT_EQ(builder != nullptr, true, "At least one of the inputs should be non-constant.");
83
84 // Turn the hashed message into an element of Fr
85 // Note that we don't need to trim the length of the output of the hash function because the bit length of the
86 // scalar fields we work with (secp256k1, secp256r1) is equal to 256.
87 Fr z(hashed_message);
88
89 // Step 1.
90 bool_ct is_x_less_than_modulus = public_key.x().is_less_than(
91 Fq::modulus, "ECDSA input validation: x coordinate of the public key bigger than the base field modulus.");
92 bool_ct is_y_less_than_modulus = public_key.y().is_less_than(
93 Fq::modulus, "ECDSA input validation: y coordinate of the public key bigger than the base field modulus.");
94
95 // Step 2.
96 bool_ct is_point_at_infinity = public_key.is_point_at_infinity();
97
98 // Step 3.
99 // We conditionally select a public key whose x and y coordinates are smaller than the base field modulus. We need
100 // to do this to avoid circuit failures in the function validate_on_curve. Note that this doesn't allow any attack
101 // as the result of the verification takes into account whether the original point coordinates were valid or not.
102 typename Curve::AffineElementNative native_double_generator(Curve::GroupNative::one + Curve::GroupNative::one);
103 G1 double_generator(Fq(native_double_generator.x), Fq(native_double_generator.y), /*assert_on_curve=*/false);
104 G1 corrected_public_key = G1::conditional_assign(
105 is_point_at_infinity || !is_x_less_than_modulus || !is_y_less_than_modulus, double_generator, public_key);
106 bool_t<Builder> is_point_on_curve =
107 corrected_public_key.validate_on_curve(
108 "ECDSA input validation: the public key is not a point on the elliptic curve.", false) == Fq::zero();
109
110 // Step 4.
111 Fr r(sig.r);
112 bool_ct is_r_in_range = r.is_less_than(
113 Fr::modulus, "ECDSA input validation: the r component of the signature is bigger than Fr::modulus.");
114 bool_ct is_r_zero = r == Fr::zero();
115
116 // Step 5.
117 Fr s(sig.s);
118 bool_ct is_s_in_range =
119 s.is_less_than((Fr::modulus + 1) / 2,
120 "ECDSA input validation: the s component of the signature is bigger than (Fr::modulus + 1)/2.");
121 bool_ct is_s_zero = s == Fr::zero();
122
123 // Step 6.
124 // We conditionally select a non-zero scalar to perform the verification to avoid circuit failures when s = 0.
125 Fr corrected_s = Fr::conditional_assign(is_s_zero, Fr::one(), s);
126
127 Fr u1 = z.div_without_denominator_check(corrected_s);
128 Fr u2 = r.div_without_denominator_check(corrected_s);
129
130 // Default to true for paths without a u₂ restriction (secp256k1 via real GLV, generic batch_mul).
131 bool_ct is_u2_acceptable = bool_ct(true);
132
133 G1 result;
134 if constexpr (Curve::type == bb::CurveType::SECP256K1) {
135 result = G1::secp256k1_ecdsa_mul(corrected_public_key, u1, u2);
136 } else if constexpr (Curve::type == bb::CurveType::SECP256R1) {
137 // Substitute off-curve pubkeys with 2·G so `secp256r1_ecdsa_mul`'s `from_witness(u₂·Q)` doesn't fail.
138 // `is_point_on_curve` is already in the validity AND-chain, so rejection is unaffected.
139 G1 fake_glv_pubkey = G1::conditional_assign(!is_point_on_curve, double_generator, corrected_public_key);
140 // `secp256r1_ecdsa_mul` returns a wrong result for u₂ ∈ {0, ±1} (it substitutes internally to keep
141 // witness gen alive). `u2_is_acceptable` flags this and must flow into validity.
142 const auto mul_out = G1::secp256r1_ecdsa_mul(fake_glv_pubkey, u1, u2);
143 result = mul_out.result;
144 is_u2_acceptable = mul_out.u2_is_acceptable;
145 } else {
146 result = G1::batch_mul(
147 { G1::one(builder), corrected_public_key }, { u1, u2 }, /*max_num_bits=*/0, /*with_edgecases=*/false);
148 }
149
150 // Step 7.
151 bool_ct result_is_infinity = result.is_point_at_infinity();
152
153 // Step 8.
154 result.x().reduce_mod_target_modulus();
155
156 // Transfer Fq value result.x() to Fr (this is just moving from a C++ class to another)
157 Fr result_x_mod_r = Fr::unsafe_construct_from_limbs(result.x().get_limb(0).element,
158 result.x().get_limb(1).element,
159 result.x().get_limb(2).element,
160 result.x().get_limb(3).element);
161 // Copy maximum limb values from Fq to Fr: this is needed by the subtraction happening in the == operator
162 for (size_t idx = 0; idx < 4; idx++) {
163 result_x_mod_r.set_limb_max(idx, result.x().get_limb(idx).maximum_value);
164 }
165
166 // Check result.x() = r mod n AND that no other check failed
167 bool_ct x_matches = result_x_mod_r == r;
168 bool_ct is_signature_valid = x_matches && !is_point_at_infinity && !result_is_infinity && is_r_in_range &&
169 !is_r_zero && is_s_in_range && !is_s_zero && is_point_on_curve &&
170 is_x_less_than_modulus && is_y_less_than_modulus && is_u2_acceptable;
171
172 // Logging
173 if (is_signature_valid.get_value()) {
174 vinfo("ECDSA signature verification succeeded.");
175 } else {
176 vinfo("ECDSA signature verification failed");
177 }
178
179 return is_signature_valid;
180}
181
189template <typename Builder> void generate_ecdsa_verification_test_circuit(Builder& builder, size_t num_iterations)
190{
192
193 // Native types
194 using FrNative = typename Curve::ScalarFieldNative;
195 using FqNative = typename Curve::BaseFieldNative;
196 using G1Native = typename Curve::GroupNative;
197
198 // Stdlib types
199 using Fr = typename Curve::ScalarField;
200 using Fq = typename Curve::BaseField;
201 using G1 = typename Curve::Group;
202
203 std::string message_string = "Instructions unclear, ask again later.";
204
206 for (size_t i = 0; i < num_iterations; i++) {
207 // Generate unique signature for each iteration
208 account.private_key = FrNative::random_element(&engine);
209 account.public_key = G1Native::one * account.private_key;
210
211 crypto::ecdsa_signature signature =
212 crypto::ecdsa_construct_signature<crypto::Sha256Hasher, FqNative, FrNative, G1Native>(message_string,
213 account);
214
215 bool native_verification = crypto::ecdsa_verify_signature<crypto::Sha256Hasher, FqNative, FrNative, G1Native>(
216 message_string, account.public_key, signature);
217 BB_ASSERT_EQ(native_verification, true, "Native ECDSA verification failed while generating test circuit.");
218
219 std::vector<uint8_t> rr(signature.r.begin(), signature.r.end());
220 std::vector<uint8_t> ss(signature.s.begin(), signature.s.end());
221
222 G1 public_key = G1::from_witness(&builder, account.public_key);
223
225
226 // Compute H(m) natively and pass as witness (mirrors ACIR which takes pre-hashed message)
227 auto hash_arr = crypto::sha256(std::vector<uint8_t>(message_string.begin(), message_string.end()));
228 stdlib::byte_array<Builder> hashed_message(&builder, std::vector<uint8_t>(hash_arr.begin(), hash_arr.end()));
229
230 // Verify ecdsa signature
232 stdlib::ecdsa_verify_signature<Builder, Curve, Fq, Fr, G1>(hashed_message, public_key, sig);
233 result.assert_equal(bool_t<Builder>(true));
234 }
235}
236
237} // 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
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
@ SECP256R1
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
VectorField result