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

Commit 099a229

Browse files
authored
Merge PR #688: precompiles: Implement secp256k1 ECDSA recovery using EVMMAX
Implement Elliptic Curve Digital Signature Algorithm (ECDSA) Public Key Recovery algorithm for secp256k1 curve using [EVMMAX](https://eips.ethereum.org/EIPS/eip-6690) 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
2 parents 78ad5fe + 7f0d0e0 commit 099a229

File tree

11 files changed

+1528
-4
lines changed

11 files changed

+1528
-4
lines changed

lib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ find_package(intx CONFIG REQUIRED)
77

88
add_subdirectory(evmmax)
99
add_subdirectory(evmone)
10+
add_subdirectory(evmone_precompiles)

lib/evmmax/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ add_library(evmmax STATIC)
66
add_library(evmone::evmmax ALIAS evmmax)
77
target_compile_features(evmmax PUBLIC cxx_std_20)
88
target_include_directories(evmmax PUBLIC ${PROJECT_SOURCE_DIR}/include)
9-
target_link_libraries(evmmax PRIVATE intx::intx)
9+
target_link_libraries(evmmax PUBLIC intx::intx PRIVATE evmc::evmc_cpp)
1010
target_sources(
1111
evmmax PRIVATE
1212
${PROJECT_SOURCE_DIR}/include/evmmax/evmmax.hpp
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# evmone: Fast Ethereum Virtual Machine implementation
2+
# Copyright 2023 The evmone Authors.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
add_library(evmone_precompiles STATIC)
6+
add_library(evmone::precompiles ALIAS evmone_precompiles)
7+
target_link_libraries(evmone_precompiles PUBLIC evmc::evmc_cpp PRIVATE evmone::evmmax)
8+
target_sources(
9+
evmone_precompiles PRIVATE
10+
ecc.hpp
11+
secp256k1.cpp
12+
secp256k1.hpp
13+
)

lib/evmone_precompiles/ecc.hpp

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2023 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
#pragma once
5+
6+
#include <evmmax/evmmax.hpp>
7+
8+
namespace evmmax::ecc
9+
{
10+
11+
/// The affine (two coordinates) point on an Elliptic Curve over a prime field.
12+
template <typename IntT>
13+
struct Point
14+
{
15+
IntT x = 0;
16+
IntT y = 0;
17+
18+
friend constexpr bool operator==(const Point& a, const Point& b) noexcept
19+
{
20+
// TODO(intx): C++20 operator<=> = default does not work for uint256.
21+
return a.x == b.x && a.y == b.y;
22+
}
23+
24+
/// Checks if the point represents the special "infinity" value.
25+
[[nodiscard]] constexpr bool is_inf() const noexcept { return *this == Point{}; }
26+
};
27+
28+
static_assert(Point<unsigned>{}.is_inf());
29+
30+
template <typename IntT>
31+
struct ProjPoint
32+
{
33+
IntT x = 0;
34+
IntT y = 1;
35+
IntT z = 0;
36+
37+
/// Checks if the point represents the special "infinity" value.
38+
[[nodiscard]] constexpr bool is_inf() const noexcept { return x == 0 && z == 0; }
39+
};
40+
41+
static_assert(ProjPoint<unsigned>{}.is_inf());
42+
43+
template <typename IntT>
44+
using InvFn = IntT (*)(const ModArith<IntT>&, const IntT& x) noexcept;
45+
46+
/// Converts an affine point to a projected point with coordinates in Montgomery form.
47+
template <typename IntT>
48+
inline ProjPoint<IntT> to_proj(const ModArith<IntT>& s, const Point<IntT>& p) noexcept
49+
{
50+
// FIXME: Add to_mont(1) to ModArith?
51+
// FIXME: Handle inf
52+
return {s.to_mont(p.x), s.to_mont(p.y), s.to_mont(1)};
53+
}
54+
55+
/// Converts a projected point to an affine point.
56+
template <typename IntT>
57+
inline Point<IntT> to_affine(
58+
const ModArith<IntT>& s, InvFn<IntT> inv, const ProjPoint<IntT>& p) noexcept
59+
{
60+
// FIXME: Split to_affine() and to/from_mont(). This is not good idea.
61+
// FIXME: Add tests for inf.
62+
const auto z_inv = inv(s, p.z);
63+
return {s.from_mont(s.mul(p.x, z_inv)), s.from_mont(s.mul(p.y, z_inv))};
64+
}
65+
66+
template <typename IntT, int A = 0>
67+
ProjPoint<IntT> add(const evmmax::ModArith<IntT>& s, const ProjPoint<IntT>& p,
68+
const ProjPoint<IntT>& q, const IntT& b3) noexcept
69+
{
70+
static_assert(A == 0, "point addition procedure is simplified for a = 0");
71+
72+
// Joost Renes and Craig Costello and Lejla Batina
73+
// "Complete addition formulas for prime order elliptic curves"
74+
// Cryptology ePrint Archive, Paper 2015/1060
75+
// https://eprint.iacr.org/2015/1060
76+
// Algorithm 7.
77+
78+
const auto& x1 = p.x;
79+
const auto& y1 = p.y;
80+
const auto& z1 = p.z;
81+
const auto& x2 = q.x;
82+
const auto& y2 = q.y;
83+
const auto& z2 = q.z;
84+
IntT x3;
85+
IntT y3;
86+
IntT z3;
87+
IntT t0;
88+
IntT t1;
89+
IntT t2;
90+
IntT t3;
91+
IntT t4;
92+
93+
t0 = s.mul(x1, x2); // 1
94+
t1 = s.mul(y1, y2); // 2
95+
t2 = s.mul(z1, z2); // 3
96+
t3 = s.add(x1, y1); // 4
97+
t4 = s.add(x2, y2); // 5
98+
t3 = s.mul(t3, t4); // 6
99+
t4 = s.add(t0, t1); // 7
100+
t3 = s.sub(t3, t4); // 8
101+
t4 = s.add(y1, z1); // 9
102+
x3 = s.add(y2, z2); // 10
103+
t4 = s.mul(t4, x3); // 11
104+
x3 = s.add(t1, t2); // 12
105+
t4 = s.sub(t4, x3); // 13
106+
x3 = s.add(x1, z1); // 14
107+
y3 = s.add(x2, z2); // 15
108+
x3 = s.mul(x3, y3); // 16
109+
y3 = s.add(t0, t2); // 17
110+
y3 = s.sub(x3, y3); // 18
111+
x3 = s.add(t0, t0); // 19
112+
t0 = s.add(x3, t0); // 20
113+
t2 = s.mul(b3, t2); // 21
114+
z3 = s.add(t1, t2); // 22
115+
t1 = s.sub(t1, t2); // 23
116+
y3 = s.mul(b3, y3); // 24
117+
x3 = s.mul(t4, y3); // 25
118+
t2 = s.mul(t3, t1); // 26
119+
x3 = s.sub(t2, x3); // 27
120+
y3 = s.mul(y3, t0); // 28
121+
t1 = s.mul(t1, z3); // 29
122+
y3 = s.add(t1, y3); // 30
123+
t0 = s.mul(t0, t3); // 31
124+
z3 = s.mul(z3, t4); // 32
125+
z3 = s.add(z3, t0); // 33
126+
127+
return {x3, y3, z3};
128+
}
129+
130+
131+
template <typename IntT, int A = 0>
132+
ProjPoint<IntT> dbl(
133+
const evmmax::ModArith<IntT>& s, const ProjPoint<IntT>& p, const IntT& b3) noexcept
134+
{
135+
static_assert(A == 0, "point doubling procedure is simplified for a = 0");
136+
137+
// Joost Renes and Craig Costello and Lejla Batina
138+
// "Complete addition formulas for prime order elliptic curves"
139+
// Cryptology ePrint Archive, Paper 2015/1060
140+
// https://eprint.iacr.org/2015/1060
141+
// Algorithm 9.
142+
143+
const auto& x = p.x;
144+
const auto& y = p.y;
145+
const auto& z = p.z;
146+
IntT x3;
147+
IntT y3;
148+
IntT z3;
149+
IntT t0;
150+
IntT t1;
151+
IntT t2;
152+
153+
t0 = s.mul(y, y); // 1
154+
z3 = s.add(t0, t0); // 2
155+
z3 = s.add(z3, z3); // 3
156+
z3 = s.add(z3, z3); // 4
157+
t1 = s.mul(y, z); // 5
158+
t2 = s.mul(z, z); // 6
159+
t2 = s.mul(b3, t2); // 7
160+
x3 = s.mul(t2, z3); // 8
161+
y3 = s.add(t0, t2); // 9
162+
z3 = s.mul(t1, z3); // 10
163+
t1 = s.add(t2, t2); // 11
164+
t2 = s.add(t1, t2); // 12
165+
t0 = s.sub(t0, t2); // 13
166+
y3 = s.mul(t0, y3); // 14
167+
y3 = s.add(x3, y3); // 15
168+
t1 = s.mul(x, y); // 16
169+
x3 = s.mul(t0, t1); // 17
170+
x3 = s.add(x3, x3); // 18
171+
172+
return {x3, y3, z3};
173+
}
174+
175+
template <typename IntT, int A = 0>
176+
ProjPoint<IntT> mul(const evmmax::ModArith<IntT>& s, const ProjPoint<IntT>& z, const IntT& c,
177+
const IntT& b3) noexcept
178+
{
179+
ProjPoint<IntT> p;
180+
auto q = z;
181+
auto first_significant_met = false;
182+
183+
for (int i = 255; i >= 0; --i)
184+
{
185+
const auto d = c & (IntT{1} << i);
186+
if (d == 0)
187+
{
188+
if (first_significant_met)
189+
{
190+
q = ecc::add(s, p, q, b3);
191+
p = ecc::dbl(s, p, b3);
192+
}
193+
}
194+
else
195+
{
196+
p = ecc::add(s, p, q, b3);
197+
q = ecc::dbl(s, q, b3);
198+
first_significant_met = true;
199+
}
200+
}
201+
202+
return p;
203+
}
204+
205+
206+
} // namespace evmmax::ecc

lib/evmone_precompiles/expmod.tmpl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{{- $exponent := "" -}}
2+
{{- range $exponent = $.Chain }}{{ end -}}
3+
4+
uint256 expmod_(const ModArith<uint256>& m, const uint256& x) noexcept
5+
{
6+
// Computes modular exponentiation
7+
// x^{{ printf "%#x" $exponent }}
8+
// Operations: {{ .Ops.Doubles }} squares {{ .Ops.Adds }} multiplies
9+
// Generated by {{ .Meta.Module }} {{ .Meta.ReleaseTag }}.
10+
//
11+
// Exponentiation computation is derived from the addition chain:
12+
//
13+
{{- range lines (format .Script) }}
14+
// {{ . }}
15+
{{- end }}
16+
17+
// Allocate Temporaries.
18+
uint256 z;
19+
{{- range .Program.Temporaries }}
20+
uint256 {{ . }};
21+
{{- end }}
22+
23+
{{ range $i := .Program.Instructions }}
24+
// {{ printf "Step %d: %s = x^%#x" $i.Output.Index $i.Output (index $.Chain $i.Output.Index) }}
25+
{{- with add $i.Op }}
26+
{{ $i.Output }} = m.mul({{ .X }}, {{ .Y }});
27+
{{ end -}}
28+
29+
{{- with double $i.Op }}
30+
{{ $i.Output }} = m.mul({{ .X }}, {{ .X }});
31+
{{ end -}}
32+
33+
{{- with shift $i.Op -}}
34+
{{- $first := 0 -}}
35+
{{- if ne $i.Output.Identifier .X.Identifier }}
36+
{{ $i.Output }} = m.mul({{ .X }}, {{ .X }});
37+
{{- $first = 1 -}}
38+
{{- end }}
39+
for (int i = {{ $first }}; i < {{ .S }}; ++i)
40+
{{ $i.Output }} = m.mul({{ $i.Output }}, {{ $i.Output }});
41+
{{ end -}}
42+
{{- end }}
43+
return z;
44+
}

0 commit comments

Comments
 (0)