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