447 lines
13 KiB
Solidity
447 lines
13 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
/**
|
|
** @title Elliptic Curve Library
|
|
** @dev Library providing arithmetic operations over elliptic curves.
|
|
** This library does not check whether the inserted points belong to the curve
|
|
** `isOnCurve` function should be used by the library user to check the aforementioned statement.
|
|
** @author Witnet Foundation
|
|
*/
|
|
library EllipticCurve {
|
|
// Pre-computed constant for 2 ** 255
|
|
uint256 private constant U255_MAX_PLUS_1 =
|
|
57896044618658097711785492504343953926634992332820282019728792003956564819968;
|
|
|
|
/// @dev Modular euclidean inverse of a number (mod p).
|
|
/// @param _x The number
|
|
/// @param _pp The modulus
|
|
/// @return q such that x*q = 1 (mod _pp)
|
|
function invMod(uint256 _x, uint256 _pp) internal pure returns (uint256) {
|
|
require(_x != 0 && _x != _pp && _pp != 0, "Invalid number");
|
|
uint256 q = 0;
|
|
uint256 newT = 1;
|
|
uint256 r = _pp;
|
|
uint256 t;
|
|
while (_x != 0) {
|
|
t = r / _x;
|
|
(q, newT) = (newT, addmod(q, (_pp - mulmod(t, newT, _pp)), _pp));
|
|
(r, _x) = (_x, r - t * _x);
|
|
}
|
|
|
|
return q;
|
|
}
|
|
|
|
/// @dev Modular exponentiation, b^e % _pp.
|
|
/// Source: https://github.com/androlo/standard-contracts/blob/master/contracts/src/crypto/ECCMath.sol
|
|
/// @param _base base
|
|
/// @param _exp exponent
|
|
/// @param _pp modulus
|
|
/// @return r such that r = b**e (mod _pp)
|
|
function expMod(
|
|
uint256 _base,
|
|
uint256 _exp,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256)
|
|
{
|
|
require(_pp != 0, "EllipticCurve: modulus is zero");
|
|
|
|
if (_base == 0) return 0;
|
|
if (_exp == 0) return 1;
|
|
|
|
uint256 r = 1;
|
|
uint256 bit = U255_MAX_PLUS_1;
|
|
assembly {
|
|
for {
|
|
|
|
} gt(bit, 0) {
|
|
|
|
} {
|
|
r := mulmod(
|
|
mulmod(r, r, _pp),
|
|
exp(_base, iszero(iszero(and(_exp, bit)))),
|
|
_pp
|
|
)
|
|
r := mulmod(
|
|
mulmod(r, r, _pp),
|
|
exp(_base, iszero(iszero(and(_exp, div(bit, 2))))),
|
|
_pp
|
|
)
|
|
r := mulmod(
|
|
mulmod(r, r, _pp),
|
|
exp(_base, iszero(iszero(and(_exp, div(bit, 4))))),
|
|
_pp
|
|
)
|
|
r := mulmod(
|
|
mulmod(r, r, _pp),
|
|
exp(_base, iszero(iszero(and(_exp, div(bit, 8))))),
|
|
_pp
|
|
)
|
|
bit := div(bit, 16)
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/// @dev Converts a point (x, y, z) expressed in Jacobian coordinates to affine coordinates (x', y', 1).
|
|
/// @param _x coordinate x
|
|
/// @param _y coordinate y
|
|
/// @param _z coordinate z
|
|
/// @param _pp the modulus
|
|
/// @return (x', y') affine coordinates
|
|
function toAffine(
|
|
uint256 _x,
|
|
uint256 _y,
|
|
uint256 _z,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256, uint256)
|
|
{
|
|
uint256 zInv = invMod(_z, _pp);
|
|
uint256 zInv2 = mulmod(zInv, zInv, _pp);
|
|
uint256 x2 = mulmod(_x, zInv2, _pp);
|
|
uint256 y2 = mulmod(_y, mulmod(zInv, zInv2, _pp), _pp);
|
|
|
|
return (x2, y2);
|
|
}
|
|
|
|
/// @dev Derives the y coordinate from a compressed-format point x [[SEC-1]](https://www.secg.org/SEC1-Ver-1.0.pdf).
|
|
/// @param _prefix parity byte (0x02 even, 0x03 odd)
|
|
/// @param _x coordinate x
|
|
/// @param _aa constant of curve
|
|
/// @param _bb constant of curve
|
|
/// @param _pp the modulus
|
|
/// @return y coordinate y
|
|
function deriveY(
|
|
uint8 _prefix,
|
|
uint256 _x,
|
|
uint256 _aa,
|
|
uint256 _bb,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256)
|
|
{
|
|
require(
|
|
_prefix == 0x02 || _prefix == 0x03,
|
|
"EllipticCurve:innvalid compressed EC point prefix"
|
|
);
|
|
|
|
// x^3 + ax + b
|
|
uint256 y2 = addmod(
|
|
mulmod(_x, mulmod(_x, _x, _pp), _pp),
|
|
addmod(mulmod(_x, _aa, _pp), _bb, _pp),
|
|
_pp
|
|
);
|
|
y2 = expMod(y2, (_pp + 1) / 4, _pp);
|
|
// uint256 cmp = yBit ^ y_ & 1;
|
|
uint256 y = (y2 + _prefix) % 2 == 0 ? y2 : _pp - y2;
|
|
|
|
return y;
|
|
}
|
|
|
|
/// @dev Check whether point (x,y) is on curve defined by a, b, and _pp.
|
|
/// @param _x coordinate x of P1
|
|
/// @param _y coordinate y of P1
|
|
/// @param _aa constant of curve
|
|
/// @param _bb constant of curve
|
|
/// @param _pp the modulus
|
|
/// @return true if x,y in the curve, false else
|
|
function isOnCurve(
|
|
uint _x,
|
|
uint _y,
|
|
uint _aa,
|
|
uint _bb,
|
|
uint _pp
|
|
)
|
|
internal pure
|
|
returns (bool)
|
|
{
|
|
if (0 == _x || _x >= _pp || 0 == _y || _y >= _pp) {
|
|
return false;
|
|
}
|
|
// y^2
|
|
uint lhs = mulmod(_y, _y, _pp);
|
|
// x^3
|
|
uint rhs = mulmod(mulmod(_x, _x, _pp), _x, _pp);
|
|
if (_aa != 0) {
|
|
// x^3 + a*x
|
|
rhs = addmod(rhs, mulmod(_x, _aa, _pp), _pp);
|
|
}
|
|
if (_bb != 0) {
|
|
// x^3 + a*x + b
|
|
rhs = addmod(rhs, _bb, _pp);
|
|
}
|
|
|
|
return lhs == rhs;
|
|
}
|
|
|
|
/// @dev Calculate inverse (x, -y) of point (x, y).
|
|
/// @param _x coordinate x of P1
|
|
/// @param _y coordinate y of P1
|
|
/// @param _pp the modulus
|
|
/// @return (x, -y)
|
|
function ecInv(
|
|
uint256 _x,
|
|
uint256 _y,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256, uint256)
|
|
{
|
|
return (_x, (_pp - _y) % _pp);
|
|
}
|
|
|
|
/// @dev Add two points (x1, y1) and (x2, y2) in affine coordinates.
|
|
/// @param _x1 coordinate x of P1
|
|
/// @param _y1 coordinate y of P1
|
|
/// @param _x2 coordinate x of P2
|
|
/// @param _y2 coordinate y of P2
|
|
/// @param _aa constant of the curve
|
|
/// @param _pp the modulus
|
|
/// @return (qx, qy) = P1+P2 in affine coordinates
|
|
function ecAdd(
|
|
uint256 _x1,
|
|
uint256 _y1,
|
|
uint256 _x2,
|
|
uint256 _y2,
|
|
uint256 _aa,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256, uint256)
|
|
{
|
|
uint x = 0;
|
|
uint y = 0;
|
|
uint z = 0;
|
|
|
|
// Double if x1==x2 else add
|
|
if (_x1 == _x2) {
|
|
// y1 = -y2 mod p
|
|
if (addmod(_y1, _y2, _pp) == 0) {
|
|
return (0, 0);
|
|
} else {
|
|
// P1 = P2
|
|
(x, y, z) = jacDouble(_x1, _y1, 1, _aa, _pp);
|
|
}
|
|
} else {
|
|
(x, y, z) = jacAdd(_x1, _y1, 1, _x2, _y2, 1, _pp);
|
|
}
|
|
// Get back to affine
|
|
return toAffine(x, y, z, _pp);
|
|
}
|
|
|
|
/// @dev Substract two points (x1, y1) and (x2, y2) in affine coordinates.
|
|
/// @param _x1 coordinate x of P1
|
|
/// @param _y1 coordinate y of P1
|
|
/// @param _x2 coordinate x of P2
|
|
/// @param _y2 coordinate y of P2
|
|
/// @param _aa constant of the curve
|
|
/// @param _pp the modulus
|
|
/// @return (qx, qy) = P1-P2 in affine coordinates
|
|
function ecSub(
|
|
uint256 _x1,
|
|
uint256 _y1,
|
|
uint256 _x2,
|
|
uint256 _y2,
|
|
uint256 _aa,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256, uint256)
|
|
{
|
|
// invert square
|
|
(uint256 x, uint256 y) = ecInv(_x2, _y2, _pp);
|
|
// P1-square
|
|
return ecAdd(_x1, _y1, x, y, _aa, _pp);
|
|
}
|
|
|
|
/// @dev Multiply point (x1, y1, z1) times d in affine coordinates.
|
|
/// @param _k scalar to multiply
|
|
/// @param _x coordinate x of P1
|
|
/// @param _y coordinate y of P1
|
|
/// @param _aa constant of the curve
|
|
/// @param _pp the modulus
|
|
/// @return (qx, qy) = d*P in affine coordinates
|
|
function ecMul(
|
|
uint256 _k,
|
|
uint256 _x,
|
|
uint256 _y,
|
|
uint256 _aa,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256, uint256)
|
|
{
|
|
// Jacobian multiplication
|
|
(uint256 x1, uint256 y1, uint256 z1) = jacMul(_k, _x, _y, 1, _aa, _pp);
|
|
// Get back to affine
|
|
return toAffine(x1, y1, z1, _pp);
|
|
}
|
|
|
|
/// @dev Adds two points (x1, y1, z1) and (x2 y2, z2).
|
|
/// @param _x1 coordinate x of P1
|
|
/// @param _y1 coordinate y of P1
|
|
/// @param _z1 coordinate z of P1
|
|
/// @param _x2 coordinate x of square
|
|
/// @param _y2 coordinate y of square
|
|
/// @param _z2 coordinate z of square
|
|
/// @param _pp the modulus
|
|
/// @return (qx, qy, qz) P1+square in Jacobian
|
|
function jacAdd(
|
|
uint256 _x1,
|
|
uint256 _y1,
|
|
uint256 _z1,
|
|
uint256 _x2,
|
|
uint256 _y2,
|
|
uint256 _z2,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256, uint256, uint256)
|
|
{
|
|
if (_x1 == 0 && _y1 == 0) return (_x2, _y2, _z2);
|
|
if (_x2 == 0 && _y2 == 0) return (_x1, _y1, _z1);
|
|
|
|
// We follow the equations described in https://pdfs.semanticscholar.org/5c64/29952e08025a9649c2b0ba32518e9a7fb5c2.pdf Section 5
|
|
uint[4] memory zs; // z1^2, z1^3, z2^2, z2^3
|
|
zs[0] = mulmod(_z1, _z1, _pp);
|
|
zs[1] = mulmod(_z1, zs[0], _pp);
|
|
zs[2] = mulmod(_z2, _z2, _pp);
|
|
zs[3] = mulmod(_z2, zs[2], _pp);
|
|
|
|
// u1, s1, u2, s2
|
|
zs = [
|
|
mulmod(_x1, zs[2], _pp),
|
|
mulmod(_y1, zs[3], _pp),
|
|
mulmod(_x2, zs[0], _pp),
|
|
mulmod(_y2, zs[1], _pp)
|
|
];
|
|
|
|
// In case of zs[0] == zs[2] && zs[1] == zs[3], double function should be used
|
|
require(
|
|
zs[0] != zs[2] || zs[1] != zs[3],
|
|
"Use jacDouble function instead"
|
|
);
|
|
|
|
uint[4] memory hr;
|
|
//h
|
|
hr[0] = addmod(zs[2], _pp - zs[0], _pp);
|
|
//r
|
|
hr[1] = addmod(zs[3], _pp - zs[1], _pp);
|
|
//h^2
|
|
hr[2] = mulmod(hr[0], hr[0], _pp);
|
|
// h^3
|
|
hr[3] = mulmod(hr[2], hr[0], _pp);
|
|
// qx = -h^3 -2u1h^2+r^2
|
|
uint256 qx = addmod(mulmod(hr[1], hr[1], _pp), _pp - hr[3], _pp);
|
|
qx = addmod(qx, _pp - mulmod(2, mulmod(zs[0], hr[2], _pp), _pp), _pp);
|
|
// qy = -s1*z1*h^3+r(u1*h^2 -x^3)
|
|
uint256 qy = mulmod(
|
|
hr[1],
|
|
addmod(mulmod(zs[0], hr[2], _pp), _pp - qx, _pp),
|
|
_pp
|
|
);
|
|
qy = addmod(qy, _pp - mulmod(zs[1], hr[3], _pp), _pp);
|
|
// qz = h*z1*z2
|
|
uint256 qz = mulmod(hr[0], mulmod(_z1, _z2, _pp), _pp);
|
|
return (qx, qy, qz);
|
|
}
|
|
|
|
/// @dev Doubles a points (x, y, z).
|
|
/// @param _x coordinate x of P1
|
|
/// @param _y coordinate y of P1
|
|
/// @param _z coordinate z of P1
|
|
/// @param _aa the a scalar in the curve equation
|
|
/// @param _pp the modulus
|
|
/// @return (qx, qy, qz) 2P in Jacobian
|
|
function jacDouble(
|
|
uint256 _x,
|
|
uint256 _y,
|
|
uint256 _z,
|
|
uint256 _aa,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256, uint256, uint256)
|
|
{
|
|
if (_z == 0) return (_x, _y, _z);
|
|
|
|
// We follow the equations described in https://pdfs.semanticscholar.org/5c64/29952e08025a9649c2b0ba32518e9a7fb5c2.pdf Section 5
|
|
// Note: there is a bug in the paper regarding the m parameter, M=3*(x1^2)+a*(z1^4)
|
|
// x, y, z at this point represent the squares of _x, _y, _z
|
|
uint256 x = mulmod(_x, _x, _pp); //x1^2
|
|
uint256 y = mulmod(_y, _y, _pp); //y1^2
|
|
uint256 z = mulmod(_z, _z, _pp); //z1^2
|
|
|
|
// s
|
|
uint s = mulmod(4, mulmod(_x, y, _pp), _pp);
|
|
// m
|
|
uint m = addmod(
|
|
mulmod(3, x, _pp),
|
|
mulmod(_aa, mulmod(z, z, _pp), _pp),
|
|
_pp
|
|
);
|
|
|
|
// x, y, z at this point will be reassigned and rather represent qx, qy, qz from the paper
|
|
// This allows to reduce the gas cost and stack footprint of the algorithm
|
|
// qx
|
|
x = addmod(mulmod(m, m, _pp), _pp - addmod(s, s, _pp), _pp);
|
|
// qy = -8*y1^4 + M(S-T)
|
|
y = addmod(
|
|
mulmod(m, addmod(s, _pp - x, _pp), _pp),
|
|
_pp - mulmod(8, mulmod(y, y, _pp), _pp),
|
|
_pp
|
|
);
|
|
// qz = 2*y1*z1
|
|
z = mulmod(2, mulmod(_y, _z, _pp), _pp);
|
|
|
|
return (x, y, z);
|
|
}
|
|
|
|
/// @dev Multiply point (x, y, z) times d.
|
|
/// @param _d scalar to multiply
|
|
/// @param _x coordinate x of P1
|
|
/// @param _y coordinate y of P1
|
|
/// @param _z coordinate z of P1
|
|
/// @param _aa constant of curve
|
|
/// @param _pp the modulus
|
|
/// @return (qx, qy, qz) d*P1 in Jacobian
|
|
function jacMul(
|
|
uint256 _d,
|
|
uint256 _x,
|
|
uint256 _y,
|
|
uint256 _z,
|
|
uint256 _aa,
|
|
uint256 _pp
|
|
)
|
|
internal pure
|
|
returns (uint256, uint256, uint256)
|
|
{
|
|
// Early return in case that `_d == 0`
|
|
if (_d == 0) {
|
|
return (_x, _y, _z);
|
|
}
|
|
|
|
uint256 remaining = _d;
|
|
uint256 qx = 0;
|
|
uint256 qy = 0;
|
|
uint256 qz = 1;
|
|
|
|
// Double and add algorithm
|
|
while (remaining != 0) {
|
|
if ((remaining & 1) != 0) {
|
|
(qx, qy, qz) = jacAdd(qx, qy, qz, _x, _y, _z, _pp);
|
|
}
|
|
remaining = remaining / 2;
|
|
(_x, _y, _z) = jacDouble(_x, _y, _z, _aa, _pp);
|
|
}
|
|
return (qx, qy, qz);
|
|
}
|
|
}
|