Skip to content
This repository was archived by the owner on Feb 9, 2025. It is now read-only.

Commit 9d2764d

Browse files
chfastaxicgumb0rodiazethugo-dc
committed
secp256k1: Implement ECDSA recovery using EVMMAX
Implement Elliptic Curve Digital Signature Algorithm (ECDSA) Public Key Recovery algorithm for secp256k1 curve using EVMMAX primitives. This can be used to provide ecrecovery EVM precompile but also to verify signatures in Ethereum transactions. This work has been done at ETHPrague Hackathon https://devfolio.co/projects/evmmax-ecrecovery-bd49 Co-authored-by: Alex Beregszaszi <alex@rtfs.hu> Co-authored-by: Andrei Maiboroda <andrei@ethereum.org> Co-authored-by: rodiazet <rodiazet@ethereum.org> Co-authored-by: Hugo De La Cruz <jhugodc@gmail.com> Co-authored-by: pdobacz <5735525+pdobacz@users.noreply.github.com>
1 parent 9f4375a commit 9d2764d

File tree

3 files changed

+346
-0
lines changed

3 files changed

+346
-0
lines changed

lib/evmone_precompiles/secp256k1.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ namespace
1010
{
1111
const ModArith<uint256> Fp{FieldPrime};
1212
const auto B = Fp.to_mont(7);
13+
const auto B3 = Fp.to_mont(7 * 3);
14+
15+
constexpr Point G{0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798_u256,
16+
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8_u256};
1317
} // namespace
1418

1519
// FIXME: Change to "uncompress_point".
@@ -27,6 +31,33 @@ std::optional<uint256> calculate_y(
2731
return (candidate_parity == y_parity) ? *y : m.sub(0, *y);
2832
}
2933

34+
Point add(const Point& p, const Point& q) noexcept
35+
{
36+
if (p.is_inf())
37+
return q;
38+
if (q.is_inf())
39+
return p;
40+
41+
const auto pp = ecc::to_proj(Fp, p);
42+
const auto pq = ecc::to_proj(Fp, q);
43+
44+
// b3 == 21 for y^2 == x^3 + 7
45+
const auto r = ecc::add(Fp, pp, pq, B3);
46+
return ecc::to_affine(Fp, field_inv, r);
47+
}
48+
49+
Point mul(const Point& p, const uint256& c) noexcept
50+
{
51+
if (p.is_inf())
52+
return p;
53+
54+
if (c == 0)
55+
return {0, 0};
56+
57+
const auto r = ecc::mul(Fp, ecc::to_proj(Fp, p), c, B3);
58+
return ecc::to_affine(Fp, field_inv, r);
59+
}
60+
3061
evmc::address to_address(const Point& pt) noexcept
3162
{
3263
// This performs Ethereum's address hashing on an uncompressed pubkey.
@@ -41,6 +72,74 @@ evmc::address to_address(const Point& pt) noexcept
4172
return ret;
4273
}
4374

75+
std::optional<Point> secp256k1_ecdsa_recover(
76+
const ethash::hash256& e, const uint256& r, const uint256& s, bool v) noexcept
77+
{
78+
// Follows
79+
// https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery
80+
81+
// 1. Validate r and s are within [1, n-1].
82+
if (r == 0 || r >= Order || s == 0 || s >= Order)
83+
return std::nullopt;
84+
85+
// 3. Hash of the message is already calculated in e.
86+
// 4. Convert hash e to z field element by doing z = e % n.
87+
// https://www.rfc-editor.org/rfc/rfc6979#section-2.3.2
88+
// We can do this by n - e because n > 2^255.
89+
static_assert(Order > 1_u256 << 255);
90+
auto z = intx::be::load<uint256>(e.bytes);
91+
if (z >= Order)
92+
z -= Order;
93+
94+
95+
const ModArith<uint256> n{Order};
96+
97+
// 5. Calculate u1 and u2.
98+
const auto r_n = n.to_mont(r);
99+
const auto r_inv = scalar_inv(n, r_n);
100+
101+
const auto z_mont = n.to_mont(z);
102+
const auto z_neg = n.sub(0, z_mont);
103+
const auto u1_mont = n.mul(z_neg, r_inv);
104+
const auto u1 = n.from_mont(u1_mont);
105+
106+
const auto s_mont = n.to_mont(s);
107+
const auto u2_mont = n.mul(s_mont, r_inv);
108+
const auto u2 = n.from_mont(u2_mont);
109+
110+
// 2. Calculate y coordinate of R from r and v.
111+
const auto r_mont = Fp.to_mont(r);
112+
const auto y_mont = calculate_y(Fp, r_mont, v);
113+
if (!y_mont.has_value())
114+
return std::nullopt;
115+
const auto y = Fp.from_mont(*y_mont);
116+
117+
// 6. Calculate public key point Q.
118+
const auto R = ecc::to_proj(Fp, {r, y});
119+
const auto pG = ecc::to_proj(Fp, G);
120+
const auto T1 = ecc::mul(Fp, pG, u1, B3);
121+
const auto T2 = ecc::mul(Fp, R, u2, B3);
122+
const auto pQ = ecc::add(Fp, T1, T2, B3);
123+
124+
const auto Q = ecc::to_affine(Fp, field_inv, pQ);
125+
126+
// Any other validity check needed?
127+
if (Q.is_inf())
128+
return std::nullopt;
129+
130+
return Q;
131+
}
132+
133+
std::optional<evmc::address> ecrecover(
134+
const ethash::hash256& e, const uint256& r, const uint256& s, bool v) noexcept
135+
{
136+
const auto point = secp256k1_ecdsa_recover(e, r, s, v);
137+
if (!point.has_value())
138+
return std::nullopt;
139+
140+
return to_address(*point);
141+
}
142+
44143
uint256 field_inv(const ModArith<uint256>& m, const uint256& x) noexcept
45144
{
46145
// Computes modular exponentiation

lib/evmone_precompiles/secp256k1.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma once
55

66
#include "ecc.hpp"
7+
#include <ethash/hash_types.hpp>
78
#include <evmc/evmc.hpp>
89
#include <optional>
910

@@ -46,7 +47,23 @@ uint256 scalar_inv(const ModArith<uint256>& m, const uint256& x) noexcept;
4647
std::optional<uint256> calculate_y(
4748
const ModArith<uint256>& m, const uint256& x, bool y_parity) noexcept;
4849

50+
/// Addition in secp256k1.
51+
///
52+
/// Computes P ⊕ Q for two points in affine coordinates on the secp256k1 curve,
53+
Point add(const Point& p, const Point& q) noexcept;
54+
55+
/// Scalar multiplication in secp256k1.
56+
///
57+
/// Computes [c]P for a point in affine coordinate on the secp256k1 curve,
58+
Point mul(const Point& p, const uint256& c) noexcept;
59+
4960
/// Convert the secp256k1 point (uncompressed public key) to Ethereum address.
5061
evmc::address to_address(const Point& pt) noexcept;
5162

63+
std::optional<Point> secp256k1_ecdsa_recover(
64+
const ethash::hash256& e, const uint256& r, const uint256& s, bool v) noexcept;
65+
66+
std::optional<evmc::address> ecrecover(
67+
const ethash::hash256& e, const uint256& r, const uint256& s, bool v) noexcept;
68+
5269
} // namespace evmmax::secp256k1

test/unittests/evmmax_secp256k1_test.cpp

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,233 @@ TEST(secp256k1, point_to_address)
150150
// https://etherscan.io/address/0x3f17f1962b36e491b30a40b2405849e597ba5fb5
151151
EXPECT_EQ(to_address(Point{}), 0x3f17f1962B36e491b30A40b2405849e597Ba5FB5_address);
152152
}
153+
154+
TEST(evmmax, secp256k1_calculate_u1)
155+
{
156+
// u1 = -zr^(-1)
157+
const auto z = 0x31d6fb860f6d12cee6e5b640646089bd5883d586e43de3dedc75695c11ac2da9_u256;
158+
const auto r = 0x71cd6bfc24665312ff489aba9279710a560eda74aca333bf298785dc3cd72f6e_u256;
159+
const auto expected = 0xd80ea4db5200c96e969270ab7c105e16abb9fc18a6e01cc99575dd3f5ce41eed_u256;
160+
161+
const evmmax::ModArith m{evmmax::secp256k1::FieldPrime};
162+
const auto z_mont = m.to_mont(z);
163+
const auto r_mont = m.to_mont(r);
164+
const auto r_inv = field_inv(m, r_mont);
165+
const auto z_neg = m.sub(0, z_mont);
166+
const auto u1_mont = m.mul(z_neg, r_inv);
167+
const auto u1 = m.from_mont(u1_mont);
168+
EXPECT_EQ(u1, expected);
169+
}
170+
171+
TEST(evmmax, secp256k1_calculate_u2)
172+
{
173+
// u2 = sr^(-1)
174+
const auto r = 0x27bc00995393e969525f2d02e731437402aa12a9a09125d1e322d62f05a2b54f_u256;
175+
const auto s = 0x7ce91fc325f28e78a016fa674a80d85581cc278d15453ea2fede2471b1adaada_u256;
176+
const auto expected = 0xf888ea06899abc190fa37a165c98e6d4b00b13c50db1d1c34f38f0ab8fd9c29b_u256;
177+
178+
const evmmax::ModArith m{evmmax::secp256k1::FieldPrime};
179+
const auto s_mont = m.to_mont(s);
180+
const auto r_mont = m.to_mont(r);
181+
const auto r_inv = field_inv(m, r_mont);
182+
const auto u2_mont = m.mul(s_mont, r_inv);
183+
const auto u2 = m.from_mont(u2_mont);
184+
EXPECT_EQ(u2, expected);
185+
}
186+
187+
TEST(evmmax, secp256k1_hash_to_number)
188+
{
189+
const auto max_h = ~uint256{};
190+
const auto hm = max_h % evmmax::secp256k1::FieldPrime;
191+
192+
// Optimized mod.
193+
const auto hm2 = max_h - evmmax::secp256k1::FieldPrime;
194+
EXPECT_EQ(hm2, hm);
195+
}
196+
197+
TEST(evmmax, secp256k1_pt_add_inf)
198+
{
199+
const Point p1{0x18f4057699e2d9679421de8f4e11d7df9fa4b9e7cb841ea48aed75f1567b9731_u256,
200+
0x6db5b7ecd8e226c06f538d15173267bf1e78acc02bb856e83b3d6daec6a68144_u256};
201+
const Point inf;
202+
ASSERT_TRUE(inf.is_inf());
203+
204+
EXPECT_EQ(add(p1, inf), p1);
205+
EXPECT_EQ(add(inf, p1), p1);
206+
EXPECT_EQ(add(inf, inf), inf);
207+
}
208+
209+
TEST(evmmax, secp256k1_pt_add)
210+
{
211+
const evmmax::ModArith s{evmmax::secp256k1::FieldPrime};
212+
213+
const Point p1{0x18f4057699e2d9679421de8f4e11d7df9fa4b9e7cb841ea48aed75f1567b9731_u256,
214+
0x6db5b7ecd8e226c06f538d15173267bf1e78acc02bb856e83b3d6daec6a68144_u256};
215+
const Point p2{0xf929e07c83d65da3569113ae03998d13359ba982216285a686f4d66e721a0beb_u256,
216+
0xb6d73966107b10526e2e140c17f343ee0a373351f2b1408923151b027f55b82_u256};
217+
const Point p3{0xf929e07c83d65da3569113ae03998d13359ba982216285a686f4d66e721a0beb_u256,
218+
0xf4928c699ef84efad91d1ebf3e80cbc11f5c8ccae0d4ebf76dceae4ed80aa0ad_u256};
219+
const Point p4{
220+
0x1_u256, 0xbde70df51939b94c9c24979fa7dd04ebd9b3572da7802290438af2a681895441_u256};
221+
222+
{
223+
const Point e = {0x40468d7704db3d11961ab9c222e35919d7e5d1baef59e0f46255d66bec3bd1d3_u256,
224+
0x6fff88d9f575236b6cc5c74e7d074832a460c2792fba888aea7b9986429dd7f7_u256};
225+
EXPECT_EQ(add(p1, p2), e);
226+
}
227+
{
228+
const Point e = {0xd8e7b42b8c82e185bf0669ce0754697a6eb46c156497d5d1971bd6a23f38ed9e_u256,
229+
0x628c3107fc73c92e7b8c534e239257fb2de95bd6b965dc1021f636da086a7e99_u256};
230+
EXPECT_EQ(add(p1, p1), e);
231+
}
232+
{
233+
const Point e = {0xdf592d726f42759020da10d3106db3880e514c783d6970d2a9085fb16879b37f_u256,
234+
0x10aa0ef9fe224e3797792b4b286b9f63542d4c11fe26d449a845b9db0f5993f9_u256};
235+
EXPECT_EQ(add(p1, p3), e);
236+
}
237+
{
238+
const Point e = {0x12a5fd099bcd30e7290e58d63f8d5008287239500e6d0108020040497c5cb9c9_u256,
239+
0x7f6bd83b5ac46e3b59e24af3bc9bfbb213ed13e21d754e4950ae635961742574_u256};
240+
EXPECT_EQ(add(p1, p4), e);
241+
}
242+
}
243+
244+
TEST(evmmax, secp256k1_pt_mul_inf)
245+
{
246+
const Point p1{0x18f4057699e2d9679421de8f4e11d7df9fa4b9e7cb841ea48aed75f1567b9731_u256,
247+
0x6db5b7ecd8e226c06f538d15173267bf1e78acc02bb856e83b3d6daec6a68144_u256};
248+
const Point inf;
249+
ASSERT_TRUE(inf.is_inf());
250+
251+
EXPECT_EQ(mul(p1, 0), inf);
252+
EXPECT_EQ(mul(p1, evmmax::secp256k1::Order), inf);
253+
EXPECT_EQ(mul(inf, 0), inf);
254+
EXPECT_EQ(mul(inf, 1), inf);
255+
EXPECT_EQ(mul(inf, evmmax::secp256k1::Order - 1), inf);
256+
EXPECT_EQ(mul(inf, evmmax::secp256k1::Order), inf);
257+
}
258+
259+
TEST(evmmax, secp256k1_pt_mul)
260+
{
261+
const evmmax::ModArith s{evmmax::secp256k1::FieldPrime};
262+
263+
const Point p1{0x18f4057699e2d9679421de8f4e11d7df9fa4b9e7cb841ea48aed75f1567b9731_u256,
264+
0x6db5b7ecd8e226c06f538d15173267bf1e78acc02bb856e83b3d6daec6a68144_u256};
265+
266+
{
267+
const auto d{100000000000000000000_u256};
268+
const Point e{0x4c34e6dc48badd579d1ce4702fd490fb98fa0e666417bfc2d4ff8e957d99c565_u256,
269+
0xb53da5be179d80c7f07226ba79b6bce643d89496b37d6bc2d111b009e37cc28b_u256};
270+
auto r = mul(p1, d);
271+
EXPECT_EQ(r, e);
272+
}
273+
274+
{
275+
const auto d{100000000000000000000000000000000_u256};
276+
const Point e{0xf86902594c8a4e4fc5f6dfb27886784271302c6bab3dc4350a0fe7c5b056af66_u256,
277+
0xb5748aa8f9122bfdcbf5846f6f8ec76f41626642a3f2ea0f483c92bf915847ad_u256};
278+
auto r = mul(p1, d);
279+
EXPECT_EQ(r, e);
280+
}
281+
282+
{
283+
const auto u1 = 0xd17a4c1f283fa5d67656ea81367b520eaa689207e5665620d4f51c7cf85fa220_u256;
284+
const Point G{0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798_u256,
285+
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8_u256};
286+
const Point e{0x39cb41b2567f68137aae52e99dbe91cd38d9faa3ba6be536a04355b63a7964fe_u256,
287+
0xf31e6abd08cbd8e4896c9e0304b25000edcd52a9f6d2bac7cfbdad2c835c9a35_u256};
288+
auto r = mul(G, u1);
289+
EXPECT_EQ(r, e);
290+
}
291+
}
292+
293+
294+
struct TestCaseECR
295+
{
296+
evmc::bytes32 hash;
297+
uint256 r;
298+
uint256 s;
299+
bool parity = false;
300+
Point pubkey;
301+
};
302+
303+
static const TestCaseECR test_cases_ecr[] = {
304+
{0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c_bytes32,
305+
0x7af9e73057870458f03c143483bc5fcb6f39d01c9b26d28ed9f3fe23714f6628_u256,
306+
0x3134a4ba8fafe11b351a720538398a5635e235c0b3258dce19942000731079ec_u256, false,
307+
{0x43ec87f8ee6f58605d947dac51b5e4cfe26705f509e5dad058212aadda180835_u256,
308+
0x90ebad786ce091f5af1719bf30ee236a4e6ce8a7ab6c36a16c93c6177aa109df_u256}},
309+
};
310+
311+
TEST(evmmax, ecr)
312+
{
313+
for (const auto& t : test_cases_ecr)
314+
{
315+
const auto h = std::bit_cast<ethash::hash256>(t.hash);
316+
const auto result = secp256k1_ecdsa_recover(h, t.r, t.s, t.parity);
317+
ASSERT_TRUE(result.has_value());
318+
EXPECT_EQ(result->x, t.pubkey.x);
319+
EXPECT_EQ(result->y, t.pubkey.y);
320+
// EXPECT_EQ(*result, t.pubkey);
321+
}
322+
}
323+
324+
325+
struct TestCaseECRecovery
326+
{
327+
bytes input;
328+
bytes expected_output;
329+
};
330+
331+
static const TestCaseECRecovery test_cases[] = {
332+
{"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"_hex,
333+
"000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"_hex},
334+
{"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001b7af9e73057870458f03c143483bc5fcb6f39d01c9b26d28ed9f3fe23714f66283134a4ba8fafe11b351a720538398a5635e235c0b3258dce19942000731079ec"_hex,
335+
"0000000000000000000000009a04aede774152f135315670f562c19c5726df2c"_hex},
336+
// z >= Order
337+
{"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141000000000000000000000000000000000000000000000000000000000000001b7af9e73057870458f03c143483bc5fcb6f39d01c9b26d28ed9f3fe23714f66283134a4ba8fafe11b351a720538398a5635e235c0b3258dce19942000731079ec"_hex,
338+
"000000000000000000000000b32CF3C8616537a28583FC00D29a3e8C9614cD61"_hex},
339+
{"6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9000000000000000000000000000000000000000000000000000000000000001b79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9"_hex,
340+
{}},
341+
{"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"_hex,
342+
{}},
343+
{"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f0000000000000000000000000000000000000000000000000000000000000000"_hex,
344+
{}},
345+
// r >= Order
346+
{"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001cfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"_hex,
347+
{}},
348+
// s >= Order
349+
{"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"_hex,
350+
{}},
351+
};
352+
353+
TEST(evmmax, ecrecovery)
354+
{
355+
for (const auto& t : test_cases)
356+
{
357+
ASSERT_EQ(t.input.size(), 128);
358+
359+
ethash::hash256 hash;
360+
std::memcpy(hash.bytes, t.input.data(), 32);
361+
const auto v{be::unsafe::load<uint256>(&t.input[32])};
362+
ASSERT_TRUE(v == 27 || v == 28);
363+
const auto r{be::unsafe::load<uint256>(&t.input[64])};
364+
const auto s{be::unsafe::load<uint256>(&t.input[96])};
365+
const bool parity = v == 28;
366+
367+
const auto result = ecrecover(hash, r, s, parity);
368+
369+
if (t.expected_output.empty())
370+
{
371+
EXPECT_FALSE(result.has_value());
372+
}
373+
else
374+
{
375+
ASSERT_EQ(t.expected_output.size(), 32);
376+
evmc::address e;
377+
memcpy(&e.bytes[0], &t.expected_output[12], 20);
378+
ASSERT_TRUE(result.has_value());
379+
EXPECT_EQ(*result, e);
380+
}
381+
}
382+
}

0 commit comments

Comments
 (0)