Skip to content

Commit 95c4131

Browse files
committed
fix: check if points are on the curve before addition
1 parent fc69013 commit 95c4131

File tree

2 files changed

+291
-2
lines changed

2 files changed

+291
-2
lines changed

contracts/src/libs/proving/Delta.sol

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,27 @@ library Delta {
2020
uint256 y;
2121
}
2222

23-
/// @notice The constant of the secp256k1 (K-256) elliptic curve.
23+
/// @notice The x-coordinate of the curve generator point.
24+
uint256 internal constant _GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798;
25+
26+
/// @notice The y-coordinate of the curve generator point.
27+
uint256 internal constant _GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8;
28+
29+
// @notice The coefficient a of th secp256k1 (K-256) elliptic curve (y² = x³ + ax + b).
2430
uint256 internal constant _AA = 0;
2531

26-
/// @notice The modulus of the secp256k1 (K-256) elliptic curve.
32+
// @notice The coefficient b of th secp256k1 (K-256) elliptic curve (y² = x³ + ax + b).
33+
uint256 internal constant _BB = 7;
34+
35+
/// @notice The field prime modulus (2^256 - 2^32 - 977) of the secp256k1 (K-256) elliptic curve (y² = x³ + ax + b).
2736
uint256 internal constant _PP = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
2837

2938
/// @notice Thrown if the recovered delta public key doesn't match the delta instance.
3039
error DeltaMismatch(address expected, address actual);
3140

41+
/// @notice Thrown when a provided point is not on the curve.
42+
error PointNotOnCurve(CurvePoint point);
43+
3244
/// @notice Returns the elliptic curve point representing the zero delta.
3345
/// @return zeroDelta The zero delta.
3446
function zero() internal pure returns (CurvePoint memory zeroDelta) {
@@ -40,9 +52,23 @@ library Delta {
4052
/// @param p2 The second curve point.
4153
/// @return sum The resulting curve point.
4254
function add(CurvePoint memory p1, CurvePoint memory p2) internal pure returns (CurvePoint memory sum) {
55+
if (!isZeroDelta(p1) && !EllipticCurve.isOnCurve({_x: p1.x, _y: p1.y, _aa: _AA, _bb: _BB, _pp: _PP})) {
56+
revert PointNotOnCurve(p1);
57+
}
58+
if (!isZeroDelta(p2) && !EllipticCurve.isOnCurve({_x: p2.x, _y: p2.y, _aa: _AA, _bb: _BB, _pp: _PP})) {
59+
revert PointNotOnCurve(p2);
60+
}
61+
4362
(sum.x, sum.y) = EllipticCurve.ecAdd({_x1: p1.x, _y1: p1.y, _x2: p2.x, _y2: p2.y, _aa: _AA, _pp: _PP});
4463
}
4564

65+
/// @notice Returns whether a point is the zero delta or not.
66+
/// @param p The point to check.
67+
/// @return isZero Whether the point is the zero delta or not.
68+
function isZeroDelta(CurvePoint memory p) internal pure returns (bool isZero) {
69+
isZero = p.x == 0 && p.y == 0;
70+
}
71+
4672
/// @notice Converts an elliptic curve point to an Ethereum account address.
4773
/// @param delta The elliptic curve point.
4874
/// @return account The associated account.
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.30;
3+
4+
import {EllipticCurve} from "@elliptic-curve-solidity/contracts/EllipticCurve.sol";
5+
import {Test} from "forge-std/Test.sol";
6+
7+
import {Delta} from "../../src/libs/proving/Delta.sol";
8+
9+
/**
10+
* @title EllipticCurvePropertiesTest
11+
* @dev Property-based tests for EllipticCurve.ecAdd using Foundry fuzzing.
12+
*
13+
* These tests verify GROUP PROPERTIES with points VERIFIED to be on the curve.
14+
* Points are generated via scalar multiplication: P = k × G
15+
* This guarantees all test points satisfy the curve equation y² = x³ + ax + b
16+
*
17+
* Run with: forge test -vv
18+
* Run with more fuzz runs: forge test --fuzz-runs 10000 -vv
19+
*/
20+
contract EllipticCurvePropertiesTest is Test {
21+
using EllipticCurve for *;
22+
23+
/* GROUP PROPERTY TESTS (Points Verified On Curve) */
24+
25+
/// @notice GROUP PROPERTY: Commutativity - P + Q = Q + P
26+
/// @dev Uses secp256k1 with points VERIFIED to be on the curve
27+
function testGroupProperty_Commutativity_Secp256k1(uint256 scalar1, uint256 scalar2) public pure {
28+
// Generate two points on secp256k1 by scalar multiplication of G
29+
scalar1 = bound(scalar1, 1, 20); // Keep small for performance
30+
scalar2 = bound(scalar2, 1, 20);
31+
32+
// P1 = scalar1 * G
33+
(uint256 x1, uint256 y1) =
34+
EllipticCurve.ecMul({_k: scalar1, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
35+
36+
// P2 = scalar2 * G
37+
(uint256 x2, uint256 y2) =
38+
EllipticCurve.ecMul({_k: scalar2, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
39+
40+
// Verify both points are on curve
41+
assertTrue(
42+
EllipticCurve.isOnCurve({_x: x1, _y: y1, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}),
43+
"P1 must be on curve"
44+
);
45+
assertTrue(
46+
EllipticCurve.isOnCurve({_x: x2, _y: y2, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}),
47+
"P2 must be on curve"
48+
);
49+
50+
// Test commutativity: P1 + P2 = P2 + P1
51+
(uint256 resultX1, uint256 resultY1) =
52+
EllipticCurve.ecAdd({_x1: x1, _y1: y1, _x2: x2, _y2: y2, _aa: Delta._AA, _pp: Delta._PP});
53+
(uint256 resultX2, uint256 resultY2) =
54+
EllipticCurve.ecAdd({_x1: x2, _y1: y2, _x2: x1, _y2: y1, _aa: Delta._AA, _pp: Delta._PP});
55+
56+
assertEq(resultX1, resultX2, "GROUP PROPERTY: P + Q must equal Q + P");
57+
assertEq(resultY1, resultY2, "GROUP PROPERTY: P + Q must equal Q + P");
58+
}
59+
60+
/// @notice GROUP PROPERTY: Identity - P + O = P
61+
function testGroupProperty_Identity_Secp256k1(uint256 scalar) public pure {
62+
scalar = bound(scalar, 1, 20);
63+
64+
// P = scalar * G (guaranteed on curve)
65+
(uint256 x, uint256 y) =
66+
EllipticCurve.ecMul({_k: scalar, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
67+
68+
// Verify P is on curve
69+
assertTrue(
70+
EllipticCurve.isOnCurve({_x: x, _y: y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}),
71+
"P must be on curve"
72+
);
73+
74+
// P + O should equal P
75+
(uint256 resultX, uint256 resultY) =
76+
EllipticCurve.ecAdd({_x1: x, _y1: y, _x2: 0, _y2: 0, _aa: Delta._AA, _pp: Delta._PP});
77+
78+
assertEq(resultX, x, "GROUP PROPERTY: P + O must equal P");
79+
assertEq(resultY, y, "GROUP PROPERTY: P + O must equal P");
80+
}
81+
82+
/// @notice GROUP PROPERTY: Inverse - P + (-P) = O
83+
function testGroupProperty_Inverse_Secp256k1(uint256 scalar) public pure {
84+
scalar = bound(scalar, 1, 20);
85+
86+
// P = scalar * G (guaranteed on curve)
87+
(uint256 x, uint256 y) =
88+
EllipticCurve.ecMul({_k: scalar, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
89+
90+
// Verify P is on curve
91+
assertTrue(
92+
EllipticCurve.isOnCurve({_x: x, _y: y, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}),
93+
"P must be on curve"
94+
);
95+
96+
// Get -P
97+
(uint256 invX, uint256 invY) = EllipticCurve.ecInv({_x: x, _y: y, _pp: Delta._PP});
98+
99+
// Verify -P is on curve
100+
assertTrue(
101+
EllipticCurve.isOnCurve({_x: invX, _y: invY, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}),
102+
"-P must be on curve"
103+
);
104+
105+
// P + (-P) should equal O
106+
(uint256 resultX, uint256 resultY) =
107+
EllipticCurve.ecAdd({_x1: x, _y1: y, _x2: invX, _y2: invY, _aa: Delta._AA, _pp: Delta._PP});
108+
109+
assertTrue(
110+
Delta.isZeroDelta(Delta.CurvePoint(resultX, resultY)),
111+
"GROUP PROPERTY: P + (-P) must equal point at infinity"
112+
);
113+
}
114+
115+
/// @notice GROUP PROPERTY: Associativity - (P + Q) + R = P + (Q + R)
116+
function testGroupProperty_Associativity_Secp256k1(uint256 scalar1, uint256 scalar2, uint256 scalar3) public pure {
117+
scalar1 = bound(scalar1, 1, 10); // Keep small for performance
118+
scalar2 = bound(scalar2, 1, 10);
119+
scalar3 = bound(scalar3, 1, 10);
120+
121+
// Generate three points on curve
122+
(uint256 x1, uint256 y1) =
123+
EllipticCurve.ecMul({_k: scalar1, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
124+
(uint256 x2, uint256 y2) =
125+
EllipticCurve.ecMul({_k: scalar2, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
126+
(uint256 x3, uint256 y3) =
127+
EllipticCurve.ecMul({_k: scalar3, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
128+
129+
// Verify all points are on curve
130+
assertTrue(EllipticCurve.isOnCurve({_x: x1, _y: y1, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}));
131+
assertTrue(EllipticCurve.isOnCurve({_x: x2, _y: y2, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}));
132+
assertTrue(EllipticCurve.isOnCurve({_x: x3, _y: y3, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}));
133+
134+
// Compute (P + Q) + R
135+
(uint256 pqX, uint256 pqY) =
136+
EllipticCurve.ecAdd({_x1: x1, _y1: y1, _x2: x2, _y2: y2, _aa: Delta._AA, _pp: Delta._PP});
137+
(uint256 resultX1, uint256 resultY1) =
138+
EllipticCurve.ecAdd({_x1: pqX, _y1: pqY, _x2: x3, _y2: y3, _aa: Delta._AA, _pp: Delta._PP});
139+
140+
// Compute P + (Q + R)
141+
(uint256 qrX, uint256 qrY) =
142+
EllipticCurve.ecAdd({_x1: x2, _y1: y2, _x2: x3, _y2: y3, _aa: Delta._AA, _pp: Delta._PP});
143+
144+
(uint256 resultX2, uint256 resultY2) =
145+
EllipticCurve.ecAdd({_x1: x1, _y1: y1, _x2: qrX, _y2: qrY, _aa: Delta._AA, _pp: Delta._PP});
146+
147+
assertEq(resultX1, resultX2, "GROUP PROPERTY: Associativity must hold");
148+
assertEq(resultY1, resultY2, "GROUP PROPERTY: Associativity must hold");
149+
}
150+
151+
/// @notice GROUP PROPERTY: Closure - If P, Q on curve, then P+Q on curve or at infinity
152+
function testGroupProperty_Closure_Secp256k1(uint256 scalar1, uint256 scalar2) public pure {
153+
scalar1 = bound(scalar1, 1, 20);
154+
scalar2 = bound(scalar2, 1, 20);
155+
156+
// Generate two points on curve
157+
(uint256 x1, uint256 y1) =
158+
EllipticCurve.ecMul({_k: scalar1, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
159+
(uint256 x2, uint256 y2) =
160+
EllipticCurve.ecMul({_k: scalar2, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
161+
162+
// Verify inputs are on curve
163+
assertTrue(EllipticCurve.isOnCurve({_x: x1, _y: y1, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}));
164+
assertTrue(EllipticCurve.isOnCurve({_x: x2, _y: y2, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}));
165+
166+
// Add points
167+
(uint256 resultX, uint256 resultY) =
168+
EllipticCurve.ecAdd({_x1: x1, _y1: y1, _x2: x2, _y2: y2, _aa: Delta._AA, _pp: Delta._PP});
169+
170+
// Result must be on curve or at infinity
171+
bool atInfinity = Delta.isZeroDelta(Delta.CurvePoint(resultX, resultY));
172+
bool onCurve =
173+
EllipticCurve.isOnCurve({_x: resultX, _y: resultY, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP});
174+
175+
assertTrue(atInfinity || onCurve, "GROUP PROPERTY: Closure - result must be on curve or at infinity");
176+
}
177+
178+
/* PRECONDITION TESTS (UNREDUCED COORDINATES) */
179+
180+
/// @notice PRECONDITION TEST: Demonstrates correct behavior with reduced coordinates
181+
function test_Precondition_ReducedCoordinates() public pure {
182+
// Use a small prime: p = 7, curve parameter a = 2
183+
uint256 pp = 7;
184+
uint256 aa = 2;
185+
186+
// Point P: (3, 5), Point Q: (3, 2) where Q is inverse of P since 2 + 5 = 7 ≡ 0 (mod 7)
187+
(uint256 rx, uint256 ry) = EllipticCurve.ecAdd({_x1: 3, _y1: 5, _x2: 3, _y2: 2, _aa: aa, _pp: pp});
188+
189+
// P + (-P) should equal point at infinity
190+
assertTrue(
191+
Delta.isZeroDelta(Delta.CurvePoint(rx, ry)),
192+
"With REDUCED coordinates: (3,5) + (3,2) correctly gives point at infinity"
193+
);
194+
}
195+
196+
/* CONCRETE TESTS (SECP256K1) */
197+
198+
/// @notice Concrete: G + G = 2G
199+
function test_Concrete_Doubling_Secp256k1() public pure {
200+
(uint256 rx, uint256 ry) = EllipticCurve.ecAdd({
201+
_x1: Delta._GX, _y1: Delta._GY, _x2: Delta._GX, _y2: Delta._GY, _aa: Delta._AA, _pp: Delta._PP
202+
});
203+
204+
// Expected 2G
205+
assertEq(rx, 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5);
206+
assertEq(ry, 0x1ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a);
207+
208+
// Verify result is on curve
209+
assertTrue(EllipticCurve.isOnCurve({_x: rx, _y: ry, _aa: Delta._AA, _bb: Delta._BB, _pp: Delta._PP}));
210+
}
211+
212+
/// @notice Concrete: G + O = G
213+
function test_Concrete_Identity_Secp256k1() public pure {
214+
(uint256 rx, uint256 ry) =
215+
EllipticCurve.ecAdd({_x1: Delta._GX, _y1: Delta._GY, _x2: 0, _y2: 0, _aa: Delta._AA, _pp: Delta._PP});
216+
217+
assertEq(rx, Delta._GX);
218+
assertEq(ry, Delta._GY);
219+
}
220+
221+
/// @notice Concrete: G + (-G) = O
222+
function test_Concrete_Inverse_Secp256k1() public pure {
223+
(uint256 invX, uint256 invY) = EllipticCurve.ecInv({_x: Delta._GX, _y: Delta._GY, _pp: Delta._PP});
224+
225+
(uint256 rx, uint256 ry) =
226+
EllipticCurve.ecAdd({_x1: Delta._GX, _y1: Delta._GY, _x2: invX, _y2: invY, _aa: Delta._AA, _pp: Delta._PP});
227+
228+
assertTrue(Delta.isZeroDelta(Delta.CurvePoint(rx, ry)), "G + (-G) should be point at infinity");
229+
}
230+
231+
/// @notice Concrete: (G + 2G) + 3G = G + (2G + 3G) = 6G
232+
function test_Concrete_Associativity_Secp256k1() public pure {
233+
// Compute 2G, 3G
234+
235+
(uint256 p2GX, uint256 p2GY) =
236+
EllipticCurve.ecMul({_k: 2, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
237+
(uint256 p3GX, uint256 p3GY) =
238+
EllipticCurve.ecMul({_k: 3, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
239+
240+
// Compute (G + 2G) + 3G
241+
(uint256 pG2GX, uint256 pG2GY) =
242+
EllipticCurve.ecAdd({_x1: Delta._GX, _y1: Delta._GY, _x2: p2GX, _y2: p2GY, _aa: Delta._AA, _pp: Delta._PP});
243+
(uint256 result1X, uint256 result1Y) =
244+
EllipticCurve.ecAdd({_x1: pG2GX, _y1: pG2GY, _x2: p3GX, _y2: p3GY, _aa: Delta._AA, _pp: Delta._PP});
245+
246+
// Compute G + (2G + 3G)
247+
(uint256 p2G3GX, uint256 p2G3GY) =
248+
EllipticCurve.ecAdd({_x1: p2GX, _y1: p2GY, _x2: p3GX, _y2: p3GY, _aa: Delta._AA, _pp: Delta._PP});
249+
(uint256 result2X, uint256 result2Y) = EllipticCurve.ecAdd({
250+
_x1: Delta._GX, _y1: Delta._GY, _x2: p2G3GX, _y2: p2G3GY, _aa: Delta._AA, _pp: Delta._PP
251+
});
252+
253+
// Both should equal 6G
254+
(uint256 g6x, uint256 g6y) =
255+
EllipticCurve.ecMul({_k: 6, _x: Delta._GX, _y: Delta._GY, _aa: Delta._AA, _pp: Delta._PP});
256+
257+
assertEq(result1X, result2X, "Associativity: (G+2G)+3G = G+(2G+3G)");
258+
259+
assertEq(result1Y, result2Y, "Associativity: (G+2G)+3G = G+(2G+3G)");
260+
assertEq(result1X, g6x, "Result should be 6G");
261+
assertEq(result1Y, g6y, "Result should be 6G");
262+
}
263+
}

0 commit comments

Comments
 (0)