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: Completed, auditors: [Federico], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8
9#include <algorithm>
10
11#include "../hmac/hmac.hpp"
14#include "ecdsa.hpp"
15
16namespace bb::crypto {
17
18template <typename Hash, typename Fq, typename Fr, typename G1>
19ecdsa_signature ecdsa_construct_signature(const std::string& message, const ecdsa_key_pair<Fr, G1>& account)
20{
22
23 // Hash the message and convert it to an element of Fr
24 Fr z = ecdsa_hash_message<Hash, Fr>(message);
25
26 // Derived secret `k` using deterministic derivation according to RFC6979
27 std::vector<uint8_t> pkey_buffer;
28 write(pkey_buffer, account.private_key);
29 Fr k = crypto::deterministic_nonce_rfc6979<Hash, Fr>(message, pkey_buffer);
30 secure_erase(pkey_buffer);
31
32 // Compute R = k * G. k is the secret RFC6979 nonce, so use the constant-time multiplication
33 // to defend against the Hamming-weight / bit-length timing leak in operator*.
34 typename G1::affine_element R(typename G1::element(G1::one).mul_const_time(k));
35
36 // Compute the signature
37 Fr r = Fr(R.x);
38 Fr s = (z + r * account.private_key) / k;
39 secure_erase_bytes(&k, sizeof(k));
40
41 // Ensure that the value of s is "low", i.e. s := min{ s, (|Fr| - s) }
42 const bool is_s_low = (uint256_t(s) < (uint256_t(Fr::modulus) + 1) / 2);
43 s = is_s_low ? s : -s;
44
45 Fr::serialize_to_buffer(r, &sig.r[0]);
46 Fr::serialize_to_buffer(s, &sig.s[0]);
47
48 // Compute recovery_id: given R = (x, y)
49 // 0: y is even && x < |Fr|
50 // 1: y is odd && x < |Fr|
51 // 2: y is even && |Fr| <= x < |Fq|
52 // 3: y is odd && |Fr| <= x < |Fq|
53 // v = offset + recovery_id
54 bool is_r_finite = (uint256_t(R.x) == uint256_t(r));
55 bool y_parity = uint256_t(R.y).get_bit(0);
56 // When s is negated (low-s normalization), the effective nonce point becomes -R,
57 // which has the opposite y-parity. Flip y_parity accordingly.
58 bool recovery_id = is_s_low ? y_parity : !y_parity;
59
60 int value = is_r_finite ? ECDSA_RECOVERY_ID_OFFSET + recovery_id
61 : ECDSA_RECOVERY_ID_OFFSET + recovery_id + ECDSA_R_FINITENESS_OFFSET;
62 BB_ASSERT_LTE(value, UINT8_MAX);
63 sig.v = static_cast<uint8_t>(value);
64
65 return sig;
66}
67
68template <typename Hash, typename Fq, typename Fr, typename G1>
69typename G1::affine_element ecdsa_recover_public_key(const std::string& message, const ecdsa_signature& sig)
70{
71 using serialize::read;
72 using AffineElement = G1::affine_element;
73
74 // Read the signature components
75 uint256_t r_uint;
76 uint256_t s_uint;
77 uint8_t v_uint;
79
80 const auto* r_buf = &sig.r[0];
81 const auto* s_buf = &sig.s[0];
82 const auto* v_buf = &sig.v;
83 read(r_buf, r_uint);
84 read(s_buf, s_uint);
85 read(v_buf, v_uint);
86 Fr r = Fr(r_uint);
87 Fr s = Fr(s_uint);
88
89 // Validate signature components according to specification
90 BB_ASSERT_LT(r_uint, mod, "r value exceeds the modulus");
91 BB_ASSERT_LT(s_uint, mod, "s value exceeds the modulus");
92 BB_ASSERT_LT(s_uint, (mod + 1) / 2, "s value is not less than curve order by 2");
93 BB_ASSERT(r_uint != 0, "r value is zero");
94 BB_ASSERT(s_uint != 0, "s value is zero");
95
96 // Check that v is in {27, 28, 29, 30} and then bring it back to the range {0, 1, 2, 3}
97 BB_ASSERT_GTE(v_uint, ECDSA_RECOVERY_ID_OFFSET, "v value is too small");
98 BB_ASSERT_LTE(v_uint, ECDSA_RECOVERY_ID_OFFSET + 3, "v value is too large");
99 bool is_r_finite = v_uint < ECDSA_RECOVERY_ID_OFFSET + 2;
100 v_uint -= ECDSA_RECOVERY_ID_OFFSET;
101
102 // Decompress the x-coordinate r_uint to get two possible R points
103 // The first uncompressed R point is selected when r < |Fr|
104 // The second uncompressed R point is selected when |Fr| <= r < |Fq|
105 // Note that the second condition occurs with very low probability, so it's highly unlikely.
106 auto uncompressed_points = AffineElement::from_compressed_unsafe(r_uint);
107 AffineElement point_R = uncompressed_points[is_r_finite ? 0 : 1];
108
109 // Negate the y-coordinate point of R based on the parity of v
110 bool y_parity_R = uint256_t(point_R.y).get_bit(0);
111 bool v_parity = v_uint & 1;
112 // Flip the sign of R.y if either v is even and R.y is odd, or v is odd and R.y is even
113 if ((v_parity && !y_parity_R) || (!v_parity && y_parity_R)) {
114 point_R.y = -point_R.y;
115 }
116
117 // Start key recovery algorithm
118 Fr z = ecdsa_hash_message<Hash, Fr>(message);
119
120 Fr r_inv = r.invert();
121
122 Fr u1 = -(z * r_inv);
123 Fr u2 = s * r_inv;
124
125 AffineElement recovered_public_key(point_R * u2 + G1::one * u1);
126 return recovered_public_key;
127}
128
129template <typename Hash, typename Fq, typename Fr, typename G1>
130bool ecdsa_verify_signature(const std::string& message,
131 const typename G1::affine_element& public_key,
132 const ecdsa_signature& sig)
133{
134 using serialize::read;
135 using Element = G1::element;
136 using AffineElement = G1::affine_element;
137
138 // Validate that the public key is on the curve and not the point at infinity
139 if ((!public_key.on_curve()) || (public_key.is_point_at_infinity())) {
140 return false;
141 }
142
143 // Deserialize the signature and validate the components
144 uint256_t r_uint;
145 uint256_t s_uint;
147
148 const auto* r_buf = &sig.r[0];
149 const auto* s_buf = &sig.s[0];
150 read(r_buf, r_uint);
151 read(s_buf, s_uint);
152
153 // We need to check that r and s are in Field according to specification
154 if (r_uint >= mod) {
155 vinfo("The r component of the signature exceeds the modulus of the scalar field of the curve.");
156 return false;
157 }
158 if (s_uint >= (mod + 1) / 2) {
159 vinfo("The s component of the signature exceeds (modulus + 1) / 2.");
160 return false;
161 }
162 if (r_uint == 0) {
163 vinfo("The r component of the signature is zero.");
164 return false;
165 }
166 if (s_uint == 0) {
167 vinfo("The s component of the signature is zero.");
168 return false;
169 }
170
171 // Hash the message
172 Fr z = ecdsa_hash_message<Hash, Fr>(message);
173
174 // Validate the signature
175 Fr r = Fr(r_uint);
176 Fr s = Fr(s_uint);
177
178 Fr s_inv = s.invert();
179
180 Fr u1 = z * s_inv;
181 Fr u2 = r * s_inv;
182
183 AffineElement R = (G1::one * u1) + (Element(public_key) * u2);
184 if (R.is_point_at_infinity()) {
185 vinfo("Result of the scalar multiplication is the point at infinity.");
186 return false;
187 }
188
189 uint256_t Rx(R.x);
190 Fr result(Rx);
191 return result == r;
192}
193
194template <typename Hash, typename Fr> Fr ecdsa_hash_message(const std::string& message)
195{
196 using serialize::read;
197 using serialize::write;
198
199 static_assert(Hash::OUTPUT_SIZE <= 32, "Hash output size is too large for our implementation of ECDSA.");
200
201 constexpr size_t MODULUS_BIT_LENGTH = Fr::modulus.get_msb() + 1;
202
203 std::vector<uint8_t> message_buffer;
204 std::ranges::copy(message, std::back_inserter(message_buffer));
205 auto ev = Hash::hash(message_buffer);
206
207 if (Hash::OUTPUT_SIZE * 8 > MODULUS_BIT_LENGTH) {
208 const uint8_t* ptr = ev.data();
209 uint256_t ev_uint;
210 read(ptr, ev_uint);
211
212 // Bit shift the hash output
213 ev_uint = ev_uint >> (Hash::OUTPUT_SIZE * 8 - MODULUS_BIT_LENGTH);
214
215 // Write the output to ev
216 uint8_t* ptr_write = ev.data();
217 write(ptr_write, ev_uint);
218 }
219
220 return Fr::serialize_from_buffer(&ev[0]);
221}
222} // namespace bb::crypto
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
constexpr bool get_bit(uint64_t bit_index) const
constexpr uint64_t get_msb() const
#define vinfo(...)
Definition log.hpp:94
bb::curve::BN254::Element Element
G1::affine_element ecdsa_recover_public_key(const std::string &message, const ecdsa_signature &sig)
void secure_erase(std::array< T, N > &buffer)
Definition hmac.hpp:26
void write(B &buf, schnorr_key_pair< grumpkin::fr, grumpkin::g1 > const &keypair)
Definition schnorr.hpp:55
void read(B &it, schnorr_key_pair< grumpkin::fr, grumpkin::g1 > &keypair)
Definition schnorr.hpp:49
Fr ecdsa_hash_message(const std::string &message)
ecdsa_signature ecdsa_construct_signature(const std::string &message, const ecdsa_key_pair< Fr, G1 > &account)
Generate the ECDSA for the message using the provided account key pair and hash function.
bool ecdsa_verify_signature(const std::string &message, const typename G1::affine_element &public_key, const ecdsa_signature &sig)
void secure_erase_bytes(void *ptr, size_t size)
Definition hmac.hpp:18
void read(auto &it, msgpack_concepts::HasMsgPack auto &obj)
Automatically derived read for any object that defines .msgpack() (implicitly defined by SERIALIZATIO...
void write(auto &buf, const msgpack_concepts::HasMsgPack auto &obj)
Automatically derived write for any object that defines .msgpack() (implicitly defined by SERIALIZATI...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::ScalarField Fr
std::array< uint8_t, 32 > r
Definition ecdsa.hpp:31
std::array< uint8_t, 32 > s
Definition ecdsa.hpp:32
static constexpr uint256_t modulus
constexpr field invert() const noexcept
static field serialize_from_buffer(const uint8_t *buffer)
static void serialize_to_buffer(const field &value, uint8_t *buffer)