Skip to content

Commit

Permalink
Finishing up arithmetic - still untested
Browse files Browse the repository at this point in the history
  • Loading branch information
pmerkleplant committed Dec 2, 2023
1 parent cc6390d commit 8b86dc3
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 128 deletions.
267 changes: 160 additions & 107 deletions src/curves/Secp256k1Arithmetic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ library Secp256k1Arithmetic {
///
/// @dev Note that point at infinity is represented via:
/// point.x = point.y = type(uint).max
///
/// @dev Note that the point at infinity serves as identity element with
/// following rules:
/// ∀ p ∊ Point: p.isOnCurve() → (p + PointAtInfinity() = p)
/// ∀ x ∊ Uint: [s]PointAtInfinity() = PointAtInfinity()
function PointAtInfinity() internal pure returns (Point memory) {
return Point(type(uint).max, type(uint).max);
}
Expand All @@ -126,14 +131,15 @@ library Secp256k1Arithmetic {
/// where:
/// a = 0
/// b = 7
///
/// @dev Note that the point at infinity is also on the curve.
function isOnCurve(Point memory point) internal pure returns (bool) {
// TODO: Point at infinity on curve?
if (point.isPointAtInfinity()) {
return true;
}

uint left = mulmod(point.y, point.y, P);
// Note that adding a * x can be waived as ∀x: a * x = 0.
// Note that adding a * x can be waived as ∀ x: a * x = 0.
uint right =
addmod(mulmod(point.x, mulmod(point.x, point.x, P), P), B, P);

Expand All @@ -150,6 +156,7 @@ library Secp256k1Arithmetic {
return point.y & 1;
}

/// @dev Returns whether point `point` equals point `other`.
function equals(Point memory point, Point memory other)
internal
pure
Expand All @@ -159,36 +166,56 @@ library Secp256k1Arithmetic {
}

//----------------------------------
// Addition
// Arithmetic

/// @dev Returns new point being the sum of points `point` and `other`.
/// @dev Returns a new point being the sum of points `point` and `other`.
///
/// @dev TODO Note about performance. intoPoint() conversion is expensive.
/// Also created new point struct in memory.
///
/// @dev Reverts if:
/// - Point not on curve
function add(Point memory point, Point memory other)
internal
pure
returns (Point memory)
{
// Revert if any point not on curve.
if (!point.isOnCurve()) {
revert("PointNotOnCurve(point)");
}
if (!other.isOnCurve()) {
revert("PointNotOnCurve(other)");
}

// Catch addition with identity, ie point at infinity.
if (point.isPointAtInfinity()) {
return other;
}
if (other.isPointAtInfinity()) {
return point;
}

JacobianPoint memory jSum;
JacobianPoint memory jPoint = point.toJacobianPoint();

if (point.equals(other)) {
jSum = jPoint.double();
jSum = _jDouble(jPoint);
} else {
jSum = jPoint.add(other.toJacobianPoint());
jSum = _jAdd(jPoint, other.toJacobianPoint());
}

return jSum.intoPoint();
}

/// @dev Returns a new point being the product of point `point` and scalar
/// `scalar`.
///
/// @dev TODO Note about performance. intoPoint() conversion is expensive.
/// Also created new point struct in memory.
///
/// @dev Reverts if:
/// - Point not on curve
function mul(Point memory point, uint scalar)
internal
pure
Expand All @@ -202,102 +229,16 @@ library Secp256k1Arithmetic {
return PointAtInfinity();
}

return point.toJacobianPoint().jMul(scalar).intoPoint();
}

//--------------------------------------------------------------------------
// Jacobian Point
//
// Current functionality implemented from TODO: [ecmul] by Jordi.

// DANGER: Very dangerous.

function jMul(JacobianPoint memory jPoint, uint scalar)
internal
pure
returns (JacobianPoint memory)
{
if (scalar == 0) {
return ZeroPoint().toJacobianPoint();
}

JacobianPoint memory copy = jPoint;
JacobianPoint memory product = ZeroPoint().toJacobianPoint();

while (scalar != 0) {
if (scalar & 1 == 1) {
product = product.add(copy);
}
scalar /= 2;
copy = copy.double();
}

return product;
}

function add(JacobianPoint memory jPoint, JacobianPoint memory other)
internal
pure
returns (JacobianPoint memory)
{
if ((jPoint.x | jPoint.y) == 0) {
return other;
}
if ((other.x | other.y) == 0) {
return jPoint;
}

JacobianPoint memory sum;
uint l;
uint lz;
uint da;
uint db;

if (jPoint.x == other.x && jPoint.y == other.y) {
(l, lz) = _mul(jPoint.x, jPoint.z, jPoint.x, jPoint.z);
(l, lz) = _mul(l, lz, 3, 1);
(l, lz) = _add(l, lz, A, 1);

(da, db) = _mul(jPoint.y, jPoint.z, 2, 1);
} else {
(l, lz) = _sub(other.y, other.z, jPoint.y, jPoint.z);
(da, db) = _sub(other.x, other.z, jPoint.x, jPoint.z);
}

(l, lz) = _div(l, lz, da, db);

(sum.x, da) = _mul(l, lz, l, lz);
(sum.x, da) = _sub(sum.x, da, jPoint.x, jPoint.z);
(sum.x, da) = _sub(sum.x, da, other.x, other.z);

(sum.y, db) = _sub(jPoint.x, jPoint.z, sum.x, da);
(sum.y, db) = _mul(sum.y, db, l, lz);
(sum.y, db) = _sub(sum.y, db, jPoint.y, jPoint.z);

if (da != db) {
sum.x = mulmod(sum.x, db, P);
sum.y = mulmod(sum.y, da, P);
sum.z = mulmod(da, db, P);
} else {
sum.z = da;
}
JacobianPoint memory jProduct;
JacobianPoint memory jPoint = point.toJacobianPoint();

return sum;
}
jProduct = _jMul(jPoint, scalar);

function double(JacobianPoint memory jPoint)
internal
pure
returns (JacobianPoint memory)
{
return jPoint.add(jPoint);
return jProduct.intoPoint();
}

//--------------------------------------------------------------------------
// (De)Serialization

//----------------------------------
// Point
// Type Conversion

/// @dev Returns point `point` as Jacobian point.
function toJacobianPoint(Point memory point)
Expand All @@ -308,8 +249,16 @@ library Secp256k1Arithmetic {
return JacobianPoint(point.x, point.y, 1);
}

//----------------------------------
//--------------------------------------------------------------------------
// Jacobian Point
//
// TODO: Jacobian arithmetic is private for now as they provide less
// security guarantees.
// If you need Jacobian functionality exposed, eg for performance
// gains, let me know!

//----------------------------------
// Type Conversion

/// @dev Mutates Jacobian point `jPoint` to Affine point.
function intoPoint(JacobianPoint memory jPoint)
Expand Down Expand Up @@ -349,10 +298,6 @@ library Secp256k1Arithmetic {
//--------------------------------------------------------------------------
// Utils

// @todo Use Fermats Little Theorem. While generally less performant, it is
// cheaper on EVM due to the modexp precompile.
// See "Speeding up Elliptic Curve Computations for Ethereum Account Abstraction" page 4.

/// @dev Returns the modular inverse of `x` for modulo `P`.
///
/// The modular inverse of `x` is x⁻¹ such that x * x⁻¹ ≡ 1 (mod P).
Expand All @@ -364,11 +309,16 @@ library Secp256k1Arithmetic {
///
/// @custom:invariant Terminates in finite time.
function modularInverseOf(uint x) internal pure returns (uint) {
// TODO: Refactor to use Fermats Little Theorem.
// While generally less performant due to the modexp precompile
// pricing its less cheaper in EVM context.
// For more info, see page 4 in "Speeding up Elliptic Curve Computations for Ethereum Account Abstraction".

if (x == 0) {
revert("Modular inverse of zero does not exist");
}
if (x >= P) {
revert("TODO(modularInverse: x >= P)");
revert("NotAFieldElement(x)");
}

uint t;
Expand Down Expand Up @@ -415,16 +365,23 @@ library Secp256k1Arithmetic {
return t;
}

/// @dev Returns whether `xInv` is the modular inverse of `x`.
///
/// @dev Note that there is no modular inverse for zero.
///
/// @dev Reverts if:
/// - x not in [1, P)
/// - xInv not in [1, P)
function areModularInverse(uint x, uint xInv)
internal
pure
returns (bool)
{
if (x == 0 || xInv == 0) {
revert("Modular inverse of zero does not exist");
if (x >= P) {
revert("NotAFieldElement(x)");
}
if (x >= P || xInv >= P) {
revert("TODO(modularInverse: x >= P)");
if (xInv >= P) {
revert("NotAFieldElement(xInv)");
}

return mulmod(x, xInv, P) == 1;
Expand All @@ -433,6 +390,102 @@ library Secp256k1Arithmetic {
//--------------------------------------------------------------------------
// Private Functions

//----------------------------------
// Affine Point
//
// Functionality stolen from Jordi Baylina's [ecsol](https://github.com/jbaylina/ecsol/blob/c2256afad126b7500e6f879a9369b100e47d435d/ec.sol).

/// @dev Assumptions:
/// - Each point is either on the curve or the zero point
/// - No point is the point at infinity
function _jAdd(JacobianPoint memory jPoint, JacobianPoint memory other)
private
pure
returns (JacobianPoint memory)
{
if ((jPoint.x | jPoint.y) == 0) {
return other;
}
if ((other.x | other.y) == 0) {
return jPoint;
}

JacobianPoint memory sum;
uint l;
uint lz;
uint da;
uint db;

if (jPoint.x == other.x && jPoint.y == other.y) {
(l, lz) = _mul(jPoint.x, jPoint.z, jPoint.x, jPoint.z);
(l, lz) = _mul(l, lz, 3, 1);
(l, lz) = _add(l, lz, A, 1);

(da, db) = _mul(jPoint.y, jPoint.z, 2, 1);
} else {
(l, lz) = _sub(other.y, other.z, jPoint.y, jPoint.z);
(da, db) = _sub(other.x, other.z, jPoint.x, jPoint.z);
}

(l, lz) = _div(l, lz, da, db);

(sum.x, da) = _mul(l, lz, l, lz);
(sum.x, da) = _sub(sum.x, da, jPoint.x, jPoint.z);
(sum.x, da) = _sub(sum.x, da, other.x, other.z);

(sum.y, db) = _sub(jPoint.x, jPoint.z, sum.x, da);
(sum.y, db) = _mul(sum.y, db, l, lz);
(sum.y, db) = _sub(sum.y, db, jPoint.y, jPoint.z);

if (da != db) {
sum.x = mulmod(sum.x, db, P);
sum.y = mulmod(sum.y, da, P);
sum.z = mulmod(da, db, P);
} else {
sum.z = da;
}

return sum;
}

/// @dev Assumptions:
/// - Point is on the curve
/// - Point is not the point at infinity
function _jDouble(JacobianPoint memory jPoint)
internal
pure
returns (JacobianPoint memory)
{
// TODO: There are faster doubling formulas.
return _jAdd(jPoint, jPoint);
}

/// @dev Assumptions:
/// - Point is on the curve
/// - Point is not the point at infinity
/// - Scalar is not zero
function _jMul(JacobianPoint memory jPoint, uint scalar)
private
pure
returns (JacobianPoint memory)
{
JacobianPoint memory copy = jPoint;
JacobianPoint memory product = ZeroPoint().toJacobianPoint();

while (scalar != 0) {
if (scalar & 1 == 1) {
product = _jAdd(product, copy);
}
scalar /= 2;
copy = _jDouble(copy);
}

return product;
}

//----------------------------------
// Helpers

function _add(uint x1, uint z1, uint x2, uint z2)
private
pure
Expand Down
Loading

0 comments on commit 8b86dc3

Please sign in to comment.