diff --git a/contracts/EllipticCurve.sol b/contracts/EllipticCurve.sol deleted file mode 100644 index 0b5f548..0000000 --- a/contracts/EllipticCurve.sol +++ /dev/null @@ -1,420 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/** - * @title EllipticCurve - * - * @author Tilman Drerup; - * - * @notice Implements elliptic curve math; Parametrized for SECP256R1. - * - * Includes components of code by Andreas Olofsson, Alexander Vlasov - * (https://github.com/BANKEX/CurveArithmetics), and Avi Asayag - * (https://github.com/orbs-network/elliptic-curve-solidity) - * - * @dev NOTE: To disambiguate public keys when verifying signatures, activate - * condition 'rs[1] > lowSmax' in validateSignature(). - */ -library EllipticCurve { - // Set parameters for curve. - uint constant a = - 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; - uint constant b = - 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; - uint constant gx = - 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; - uint constant gy = - 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; - uint constant p = - 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; - uint constant n = - 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; - - uint constant lowSmax = - 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0; - - /** - * @dev Inverse of u in the field of modulo m. - */ - function inverseMod(uint u, uint m) internal pure returns (uint) { - if (u == 0 || u == m || m == 0) return 0; - if (u > m) u = u % m; - - int t1; - int t2 = 1; - uint r1 = m; - uint r2 = u; - uint q; - unchecked { - while (r2 != 0) { - q = r1 / r2; - (t1, t2, r1, r2) = (t2, t1 - int(q) * t2, r2, r1 - q * r2); - } - - if (t1 < 0) return (m - uint(-t1)); - - return uint(t1); - } - } - - /** - * @dev Transform affine coordinates into projective coordinates. - */ - function toProjectivePoint( - uint x0, - uint y0 - ) internal pure returns (uint[3] memory P) { - unchecked { - P[2] = addmod(0, 1, p); - P[0] = mulmod(x0, P[2], p); - P[1] = mulmod(y0, P[2], p); - } - } - - /** - * @dev Add two points in affine coordinates and return projective point. - */ - function addAndReturnProjectivePoint( - uint x1, - uint y1, - uint x2, - uint y2 - ) internal pure returns (uint[3] memory P) { - uint x; - uint y; - unchecked { - (x, y) = add(x1, y1, x2, y2); - } - P = toProjectivePoint(x, y); - } - - /** - * @dev Transform from projective to affine coordinates. - */ - function toAffinePoint( - uint x0, - uint y0, - uint z0 - ) internal pure returns (uint x1, uint y1) { - uint z0Inv; - unchecked { - z0Inv = inverseMod(z0, p); - x1 = mulmod(x0, z0Inv, p); - y1 = mulmod(y0, z0Inv, p); - } - } - - /** - * @dev Return the zero curve in projective coordinates. - */ - function zeroProj() internal pure returns (uint x, uint y, uint z) { - return (0, 1, 0); - } - - /** - * @dev Return the zero curve in affine coordinates. - */ - function zeroAffine() internal pure returns (uint x, uint y) { - return (0, 0); - } - - /** - * @dev Check if the curve is the zero curve. - */ - function isZeroCurve(uint x0, uint y0) internal pure returns (bool isZero) { - if (x0 == 0 && y0 == 0) { - return true; - } - return false; - } - - /** - * @dev Check if a point in affine coordinates is on the curve. - */ - function isOnCurve(uint x, uint y) internal pure returns (bool) { - if (0 == x || x == p || 0 == y || y == p) { - return false; - } - unchecked { - uint LHS = mulmod(y, y, p); // y^2 - uint RHS = mulmod(mulmod(x, x, p), x, p); // x^3 - - if (a != 0) { - RHS = addmod(RHS, mulmod(x, a, p), p); // x^3 + a*x - } - if (b != 0) { - RHS = addmod(RHS, b, p); // x^3 + a*x + b - } - - return LHS == RHS; - } - } - - /** - * @dev Double an elliptic curve point in projective coordinates. See - * https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates - */ - function twiceProj( - uint x0, - uint y0, - uint z0 - ) internal pure returns (uint x1, uint y1, uint z1) { - uint t; - uint u; - uint v; - uint w; - - if (isZeroCurve(x0, y0)) { - return zeroProj(); - } - unchecked { - u = mulmod(y0, z0, p); - u = mulmod(u, 2, p); - - v = mulmod(u, x0, p); - v = mulmod(v, y0, p); - v = mulmod(v, 2, p); - - x0 = mulmod(x0, x0, p); - t = mulmod(x0, 3, p); - - z0 = mulmod(z0, z0, p); - z0 = mulmod(z0, a, p); - t = addmod(t, z0, p); - - w = mulmod(t, t, p); - x0 = mulmod(2, v, p); - w = addmod(w, p - x0, p); - - x0 = addmod(v, p - w, p); - x0 = mulmod(t, x0, p); - y0 = mulmod(y0, u, p); - y0 = mulmod(y0, y0, p); - y0 = mulmod(2, y0, p); - y1 = addmod(x0, p - y0, p); - - x1 = mulmod(u, w, p); - - z1 = mulmod(u, u, p); - z1 = mulmod(z1, u, p); - } - } - - /** - * @dev Add two elliptic curve points in projective coordinates. See - * https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates - */ - function addProj( - uint x0, - uint y0, - uint z0, - uint x1, - uint y1, - uint z1 - ) internal pure returns (uint x2, uint y2, uint z2) { - uint t0; - uint t1; - uint u0; - uint u1; - - if (isZeroCurve(x0, y0)) { - return (x1, y1, z1); - } else if (isZeroCurve(x1, y1)) { - return (x0, y0, z0); - } - unchecked { - t0 = mulmod(y0, z1, p); - t1 = mulmod(y1, z0, p); - - u0 = mulmod(x0, z1, p); - u1 = mulmod(x1, z0, p); - } - if (u0 == u1) { - if (t0 == t1) { - return twiceProj(x0, y0, z0); - } else { - return zeroProj(); - } - } - unchecked { - (x2, y2, z2) = addProj2(mulmod(z0, z1, p), u0, u1, t1, t0); - } - } - - /** - * @dev Helper function that splits addProj to avoid too many local variables. - */ - function addProj2( - uint v, - uint u0, - uint u1, - uint t1, - uint t0 - ) private pure returns (uint x2, uint y2, uint z2) { - uint u; - uint u2; - uint u3; - uint w; - uint t; - - unchecked { - t = addmod(t0, p - t1, p); - u = addmod(u0, p - u1, p); - u2 = mulmod(u, u, p); - - w = mulmod(t, t, p); - w = mulmod(w, v, p); - u1 = addmod(u1, u0, p); - u1 = mulmod(u1, u2, p); - w = addmod(w, p - u1, p); - - x2 = mulmod(u, w, p); - - u3 = mulmod(u2, u, p); - u0 = mulmod(u0, u2, p); - u0 = addmod(u0, p - w, p); - t = mulmod(t, u0, p); - t0 = mulmod(t0, u3, p); - - y2 = addmod(t, p - t0, p); - - z2 = mulmod(u3, v, p); - } - } - - /** - * @dev Add two elliptic curve points in affine coordinates. - */ - function add( - uint x0, - uint y0, - uint x1, - uint y1 - ) internal pure returns (uint, uint) { - uint z0; - - (x0, y0, z0) = addProj(x0, y0, 1, x1, y1, 1); - - return toAffinePoint(x0, y0, z0); - } - - /** - * @dev Double an elliptic curve point in affine coordinates. - */ - function twice(uint x0, uint y0) internal pure returns (uint, uint) { - uint z0; - - (x0, y0, z0) = twiceProj(x0, y0, 1); - - return toAffinePoint(x0, y0, z0); - } - - /** - * @dev Multiply an elliptic curve point by a 2 power base (i.e., (2^exp)*P)). - */ - function multiplyPowerBase2( - uint x0, - uint y0, - uint exp - ) internal pure returns (uint, uint) { - uint base2X = x0; - uint base2Y = y0; - uint base2Z = 1; - - for (uint i = 0; i < exp; i++) { - (base2X, base2Y, base2Z) = twiceProj(base2X, base2Y, base2Z); - } - - return toAffinePoint(base2X, base2Y, base2Z); - } - - /** - * @dev Multiply an elliptic curve point by a scalar. - */ - function multiplyScalar( - uint x0, - uint y0, - uint scalar - ) internal pure returns (uint x1, uint y1) { - if (scalar == 0) { - return zeroAffine(); - } else if (scalar == 1) { - return (x0, y0); - } else if (scalar == 2) { - return twice(x0, y0); - } - - uint base2X = x0; - uint base2Y = y0; - uint base2Z = 1; - uint z1 = 1; - x1 = x0; - y1 = y0; - - if (scalar % 2 == 0) { - x1 = y1 = 0; - } - - scalar = scalar >> 1; - - while (scalar > 0) { - (base2X, base2Y, base2Z) = twiceProj(base2X, base2Y, base2Z); - - if (scalar % 2 == 1) { - (x1, y1, z1) = addProj(base2X, base2Y, base2Z, x1, y1, z1); - } - - scalar = scalar >> 1; - } - - return toAffinePoint(x1, y1, z1); - } - - /** - * @dev Multiply the curve's generator point by a scalar. - */ - function multipleGeneratorByScalar( - uint scalar - ) internal pure returns (uint, uint) { - return multiplyScalar(gx, gy, scalar); - } - - /** - * @dev Validate combination of message, signature, and public key. - */ - function validateSignature( - bytes32 message, - uint[2] memory rs, - uint[2] memory Q - ) internal pure returns (bool) { - // To disambiguate between public key solutions, include comment below. - if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) { - // || rs[1] > lowSmax) - return false; - } - if (!isOnCurve(Q[0], Q[1])) { - return false; - } - - uint x1; - uint x2; - uint y1; - uint y2; - - uint sInv = inverseMod(rs[1], n); - (x1, y1) = multiplyScalar(gx, gy, mulmod(uint(message), sInv, n)); - (x2, y2) = multiplyScalar(Q[0], Q[1], mulmod(rs[0], sInv, n)); - uint[3] memory P = addAndReturnProjectivePoint(x1, y1, x2, y2); - - if (P[2] == 0) { - return false; - } - - uint Px = inverseMod(P[2], p); - unchecked { - Px = mulmod(P[0], mulmod(Px, Px, p), p); - } - - return Px % n == rs[0]; - } -} diff --git a/contracts/Elliptic_ZZ.sol b/contracts/Elliptic_ZZ.sol new file mode 100644 index 0000000..222fc53 --- /dev/null +++ b/contracts/Elliptic_ZZ.sol @@ -0,0 +1,1617 @@ +//********************************************************************************************/ +// ___ _ ___ _ _ _ _ +// | __| _ ___ __| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__ +// | _| '_/ -_|_-< ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \ +// |_||_| \___/__/_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/ +// |__/|_| +///* Copyright (C) 2022 - Renaud Dubois - This file is part of FCL (Fresh CryptoLib) project +///* License: This software is licensed under MIT License +///* This Code may be reused including license and copyright notice. +///* See LICENSE file at the root folder of the project. +///* FILE: Elliptic_ZZ.sol +///* +///* +///* DESCRIPTION: modified XYZZ system coordinates for EVM elliptic point multiplication +///* optimization +///* +//**************************************************************************************/ +//* WARNING: this code SHALL not be used for non prime order curves for security reasons. +// SPDX-License-Identifier: MIT +//Also contain for validating and benchmarking purpose projective curve implementation from +/** + * @title EllipticCurve + * + * @author Tilman Drerup; + * + * @notice Implements elliptic curve math; Parametrized for SECP256R1. + * + * Includes components of code by Andreas Olofsson, Alexander Vlasov + * (https://github.com/BANKEX/CurveArithmetics), and Avi Asayag + * (https://github.com/orbs-network/elliptic-curve-solidity) + * + * @dev NOTE: To disambiguate public keys when verifying signatures, activate + * condition 'rs[1] > lowSmax' in validateSignature(). + */ + + // Benchmark results: + // ECDSA verification using Tilman Drerup : 1.1M gas/verification + // ECDSA verification using FCL, no precomputation: 290K gas + //ECDSA verificaiton using FCL with precomputations: 151K gas + +pragma solidity ^0.8.0; + +import {Base64URL} from "./Base64URL.sol"; +import "solmate/src/utils/SSTORE2.sol"; + import "hardhat/console.sol"; + +library Ec_ZZ { + // Set parameters for curve. + //curve bitsize + uint constant curve_S1=255; + //curve prime field modulus + uint constant p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + //short weierstrass first coefficient + uint constant a = + 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; + //short weierstrass second coefficient + uint constant b = + 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; + //generating point affine coordinates + uint constant gx = + 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; + uint constant gy = + 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; + //curve order (number of points) + uint constant n = + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + /* -2 mod p constant, used to speed up doubling (avoid negation)*/ + uint constant minus_2 = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFD; + uint constant minus_2modn = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F; + uint constant minus_1= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + //inversion mod n via a^(n-2) using little Fermat theorem + function inverseModn_Hard_back(uint u, uint m) public returns (uint res){ + unchecked{ + res=u; + + for(uint index=0;index<255;index++){ + + assembly{ + res:=mulmod(res,res,n) + let bit:=and(shr(sub(254,index),minus_2modn),1) + res:=add(mul(res,sub(1,bit)), mul(bit, mulmod(res,u,m))) + } + } + }} + + //inversion mod n via a^(n-2), use of precompiled using little Fermat theorem + function inverseModn_Hard(uint256 u, uint256 m) public returns (uint256 result) { + uint[6] memory pointer; + assembly { + + // Define length of base, exponent and modulus. 0x20 == 32 bytes + mstore(pointer, 0x20) + mstore(add(pointer, 0x20), 0x20) + mstore(add(pointer, 0x40), 0x20) + // Define variables base, exponent and modulus + mstore(add(pointer, 0x60), u) + mstore(add(pointer, 0x80), minus_2modn) + mstore(add(pointer, 0xa0), m) + + // Call the precompiled contract 0x05 = ModExp + if iszero(call(not(0), 0x05, 0, pointer, 0xc0, pointer, 0x20)) { + revert(0, 0) + } + result:=mload(pointer) + } + + } + + //inversion mod nusing little Fermat theorem via a^(n-2), use of precompiled + function inverseModp_Hard(uint256 u, uint256 m) public returns (uint256 result) { + uint[6] memory pointer; + assembly { + + // Define length of base, exponent and modulus. 0x20 == 32 bytes + mstore(pointer, 0x20) + mstore(add(pointer, 0x20), 0x20) + mstore(add(pointer, 0x40), 0x20) + // Define variables base, exponent and modulus + mstore(add(pointer, 0x60), u) + mstore(add(pointer, 0x80), minus_2) + mstore(add(pointer, 0xa0), m) + + + // Call the precompiled contract 0x05 = ModExp + if iszero(call(not(0), 0x05, 0, pointer, 0xc0, pointer, 0x20)) { + revert(0, 0) + } + result:=mload(pointer) + } + + } + + + /** + * @dev Inverse of u in the field of modulo m. + */ + /* + function inverseMod(uint u, uint m) internal returns (uint) { + if (u == 0 || u == m || m == 0) return 0; + if (u > m) u = u % m; + + int t1; + int t2 = 1; + uint r1 = m; + uint r2 = u; + uint q; + + unchecked { + while (r2 != 0) { + + q = r1 / r2; + (t1, t2, r1, r2) = (t2, t1 - int(q) * t2, r2, r1 - q * r2); + + } + + if (t1 < 0) return (m - uint(-t1)); + + return uint(t1); + } + } +*/ + + /** + * @dev Transform affine coordinates into projective coordinates. + */ + function toProjectivePoint( + uint x0, + uint y0 + ) internal pure returns (uint[3] memory P) { + unchecked { + P[2] = addmod(0, 1, p); + P[0] = mulmod(x0, P[2], p); + P[1] = mulmod(y0, P[2], p); + } + } + + + function ecAff_SetZZ( + uint x0, + uint y0 + ) internal pure returns (uint[4] memory P) { + unchecked { + P[2] = 1; //ZZ + P[3] = 1; //ZZZ + P[0] = x0; + P[1] = y0; + } + } + +/* https://hyperelliptic.org/EFD/g1p/auto-shortw-xyzz-3.html#addition-add-2008-s*/ + function ecZZ_SetAff( uint x, + uint y, + uint zz, + uint zzz) internal returns (uint x1, uint y1) + { + uint zzzInv = inverseModp_Hard(zzz, p); //1/zzz + y1=mulmod(y,zzzInv,p);//Y/zzz + uint b=mulmod(zz, zzzInv,p); //1/z + zzzInv= mulmod(b,b,p); //1/zz + x1=mulmod(x,zzzInv,p);//X/zz + } + + + + /* Chudnosky doubling*/ + /* The "dbl-2008-s-1" doubling formulas */ + + function ecZZ_Dbl( + uint x, + uint y, + uint zz, + uint zzz + ) internal pure returns (uint P0, uint P1,uint P2,uint P3) + { + unchecked{ + //use output as temporary to reduce RAM usage + //P0=mulmod(2, y, p); //U = 2*Y1 + + + //P2=mulmod(P0,P0,p); // V=U^2 + assembly{ + P0:=mulmod(2, y, p) //U = 2*Y1 + P2:=mulmod(P0,P0,p) // V=U^2 + P3:=mulmod(x, P2,p)// S = X1*V + P1:=mulmod(P0, P2,p) // W=UV + P2:=mulmod(P2, zz, p) //zz3=V*ZZ1 + zz:=mulmod(3, mulmod(addmod(x,sub(p,zz),p), addmod(x,zz,p),p) ,p) //M=3*(X1-ZZ1)*(X1+ZZ1), use zz to reduce RAM usage + P0:=addmod(mulmod(zz,zz,p), mulmod(minus_2, P3,p),p) //X3=M^2-2S + x:=mulmod(zz,addmod(P3, sub(p,P0),p),p)//M(S-X3) + P3:=mulmod(P1,zzz,p)//zzz3=W*zzz1 + P1:=addmod(x, sub(p, mulmod(P1, y,p)),p )//Y3= M(S-X3)-W*Y1 + + } + + //P3=mulmod(x, P2,p); // S = X1*V + //P1=mulmod(P0, P2,p); // W=UV + + // P2=mulmod(P2, zz, p); //zz3=V*ZZ1 + + //zz=mulmod(addmod(x,p-zz,p), addmod(x,zz,p),p);//M=3*(X1-ZZ1)*(X1+ZZ1), use zz to reduce RAM usage + + + + // P0=addmod(mulmod(zz,zz,p), mulmod(minus_2, P3,p),p);//X3=M^2-2S + + // P3=addmod(P3, p-P0,p);//S-X3 + // x=mulmod(zz,addmod(P3, p-P0,p),p);//M(S-X3) + // P3=mulmod(P1,zzz,p);//zzz3=W*zzz1 + + // P1=addmod(x, p-mulmod(P1, y,p),p );//Y3= M(S-X3)-W*Y1 + } + + return (P0, P1, P2, P3); + } + + /** + * @dev add a ZZ point with a normalized point and greedy formulae + */ + + //tbd: + // return -x1 and -Y1 in double + function ecZZ_AddN( + uint x1, + uint y1, + uint zz1, + uint zzz1, + uint x2, + uint y2) internal pure returns (uint P0, uint P1,uint P2,uint P3) + { + unchecked{ + if(y1==0){ + return (x2,y2,1,1); + } + + // y1=p-y1;//-Y1 + //U2 = X2*ZZ1 + //x2=mulmod(x2, zz1,p); + //S2 = Y2*ZZZ1, y2 free + //y2=mulmod(y2, zzz1,p); + //R = S2-Y1 + //y2=addmod(mulmod(y2, zzz1,p),y1,p); + //P = U2-X1 + //x2=addmod(mulmod(x2, zz1,p),p-x1,p); + + assembly{ + y1:=sub(p, y1) + y2:=addmod(mulmod(y2, zzz1,p),y1,p) + x2:=addmod(mulmod(x2, zz1,p),sub(p,x1),p) + P0:=mulmod(x2, x2, p) + P1:=mulmod(P0,x2,p) + P2:=mulmod(zz1,P0,p) // W=UV + P3:= mulmod(zzz1,P1,p) //zz3=V*ZZ1 + zz1:=mulmod(x1, P0, p) + P0:=addmod(addmod(mulmod(y2,y2, p), sub(p,P1),p ), mulmod(minus_2, zz1,p) ,p ) + P1:=addmod(mulmod(addmod(zz1, sub(p,P0),p), y2, p), mulmod(y1, P1,p),p) + } + /* + P0=mulmod(x2, x2, p);//PP = P^2 + + P1=mulmod(P0,x2,p); //PPP = P*PP + + P2=mulmod(zz1,P0,p); //ZZ3 = ZZ1*PP + + P3= mulmod(zzz1,P1,p); //ZZZ3 = ZZZ1*PPP + + zz1=mulmod(x1, P0, p); //Q = X1*PP, x1 free + */ + //X3 = R^2-PPP-2*Q + //P0= addmod(mulmod(y2,y2, p), p-P1,p ); //R^2-PPP + //zzz1=mulmod(minus_2, zz1,p);//-2*Q + //P0=addmod(addmod(mulmod(y2,y2, p), p-P1,p ), mulmod(minus_2, zz1,p) ,p );//R^2-PPP-2*Q + //Y3 = R*(Q-X3)-Y1*PPP + //x1= mulmod(addmod(zz1, p-P0,p), y2, p);//R*(Q-X3) + //P1=addmod(mulmod(addmod(zz1, p-P0,p), y2, p), mulmod(y1, P1,p),p) ; + } + return (P0, P1, P2, P3); + } + + /** + * @dev Add two points in affine coordinates and return projective point. + */ + function addAndReturnProjectivePoint( + uint x1, + uint y1, + uint x2, + uint y2 + ) internal returns (uint[3] memory P) { + uint x; + uint y; + unchecked { + (x, y) = add(x1, y1, x2, y2); + } + P = toProjectivePoint(x, y); + } + + /** + * @dev Transform from projective to affine coordinates. + */ + function toAffinePoint( + uint x0, + uint y0, + uint z0 + ) internal returns (uint x1, uint y1) { + uint z0Inv; + unchecked { + z0Inv = inverseModp_Hard(z0, p); + x1 = mulmod(x0, z0Inv, p); + y1 = mulmod(y0, z0Inv, p); + } + } + + + + /** + * @dev Return the zero curve in projective coordinates. + */ + function zeroProj() internal pure returns (uint x, uint y, uint z) { + return (0, 0, 0); + } + + /** + * @dev Return the zero curve in chudnosky coordinates. + */ + function ecZZ_SetZero() internal pure returns (uint x, uint y, uint zz, uint zzz) { + return (0, 0, 0, 0); + } + + function ecZZ_IsZero (uint x0, uint y0, uint zz0, uint zzz0) internal pure returns (bool) + { + if ( (y0 == 0) ) { + return true; + } + return false; + } + /** + * @dev Return the zero curve in affine coordinates. Compatible with the double formulae (no special case) + */ + function ecAff_SetZero() internal pure returns (uint x, uint y) { + return (0, 0); + } + + function ecAff_IsZero(uint x, uint y) internal pure returns (bool flag) { + return (y==0); + } + + + /** + * @dev Check if the curve is the zero curve in affine rep. + */ + function isZeroCurve_affine(uint x0, uint y0) internal pure returns (bool isZero) { + if (y0 == 0 ) { + return true; + } + return false; + } + + function isZeroCurve_proj(uint x0, uint y0, uint z0) internal pure returns (bool isZero) { + if ( (y0 == 0) ) { + return true; + } + return false; + } + + + /** + * @dev Check if a point in affine coordinates is on the curve. + */ + function ecAff_isOnCurve(uint x, uint y) internal pure returns (bool) { + if (0 == x || x == p || 0 == y || y == p) { + return false; + } + unchecked { + uint LHS = mulmod(y, y, p); // y^2 + uint RHS = mulmod(mulmod(x, x, p), x, p); // x^3 + + if (a != 0) { + RHS = addmod(RHS, mulmod(x, a, p), p); // x^3 + a*x + } + if (b != 0) { + RHS = addmod(RHS, b, p); // x^3 + a*x + b + } + + return LHS == RHS; + } + } + + + /** + * @dev Double an elliptic curve point in projective coordinates. See + * https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates + */ + function twiceProj( + uint x0, + uint y0, + uint z0 + ) internal pure returns (uint x1, uint y1, uint z1) { + uint t; + uint u; + uint v; + uint w; + + if (isZeroCurve_proj(x0, y0, z0)) { + return zeroProj(); + } + unchecked { + u = mulmod(y0, z0, p); + u = mulmod(u, 2, p); + + v = mulmod(u, x0, p); + v = mulmod(v, y0, p); + v = mulmod(v, 2, p); + + x0 = mulmod(x0, x0, p); + t = mulmod(x0, 3, p); + + z0 = mulmod(z0, z0, p); + z0 = mulmod(z0, a, p); + t = addmod(t, z0, p); + + w = mulmod(t, t, p); + x0 = mulmod(2, v, p); + w = addmod(w, p - x0, p); + + x0 = addmod(v, p - w, p); + x0 = mulmod(t, x0, p); + y0 = mulmod(y0, u, p); + y0 = mulmod(y0, y0, p); + y0 = mulmod(2, y0, p); + y1 = addmod(x0, p - y0, p); + + x1 = mulmod(u, w, p); + + z1 = mulmod(u, u, p); + z1 = mulmod(z1, u, p); + } + } + + /** + * @dev Add two elliptic curve points in projective coordinates. See + * https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates + */ + function addProj( + uint x0, + uint y0, + uint z0, + uint x1, + uint y1, + uint z1 + ) internal pure returns (uint x2, uint y2, uint z2) { + uint t0; + uint t1; + uint u0; + uint u1; + + if (isZeroCurve_proj(x0, y0, z0)) { + return (x1, y1, z1); + + } else if (isZeroCurve_proj(x1, y1, z1)) { + return (x0, y0, z0); + } + + unchecked { + t0 = mulmod(y0, z1, p); + t1 = mulmod(y1, z0, p); + + u0 = mulmod(x0, z1, p); + u1 = mulmod(x1, z0, p); + } + if (u0 == u1) { + if (t0 == t1) { + return twiceProj(x0, y0, z0); + } else { + return zeroProj(); + } + } + unchecked { + (x2, y2, z2) = addProj2(mulmod(z0, z1, p), u0, u1, t1, t0); + } + + + } + + /** + * @dev Helper function that splits addProj to avoid too many local variables. + */ + function addProj2( + uint v, + uint u0, + uint u1, + uint t1, + uint t0 + ) private pure returns (uint x2, uint y2, uint z2) { + uint u; + uint u2; + uint u3; + uint w; + uint t; + + unchecked { + t = addmod(t0, p - t1, p); + u = addmod(u0, p - u1, p); + u2 = mulmod(u, u, p); + + w = mulmod(t, t, p); + w = mulmod(w, v, p); + u1 = addmod(u1, u0, p); + u1 = mulmod(u1, u2, p); + w = addmod(w, p - u1, p); + + x2 = mulmod(u, w, p); + + u3 = mulmod(u2, u, p); + u0 = mulmod(u0, u2, p); + u0 = addmod(u0, p - w, p); + t = mulmod(t, u0, p); + t0 = mulmod(t0, u3, p); + + y2 = addmod(t, p - t0, p); + + z2 = mulmod(u3, v, p); + } + } + + /** + * @dev Add two elliptic curve points in affine coordinates. + */ + function add( + uint x0, + uint y0, + uint x1, + uint y1 + ) internal returns (uint, uint) { + uint zz0; + uint zzz0; + + if(ecAff_IsZero(x0,y0)) return (x1,y1); + if(ecAff_IsZero(x1,y1)) return (x1,y1); + + (x0, y0, zz0, zzz0) = ecZZ_AddN(x0, y0, 1,1, x1, y1); + + return ecZZ_SetAff(x0, y0, zz0, zzz0); + } + + /** + * @dev Double an elliptic curve point in affine coordinates. + */ + function twice(uint x0, uint y0) internal returns (uint, uint) { + uint z0; + + (x0, y0, z0) = twiceProj(x0, y0, 1); + + return toAffinePoint(x0, y0, z0); + } + + /** + * @dev Multiply an elliptic curve point by a 2 power base (i.e., (2^exp)*P)). + */ + function multiplyPowerBase2( + uint x0, + uint y0, + uint exp + ) internal returns (uint, uint) { + uint base2X = x0; + uint base2Y = y0; + uint base2Z = 1; + + for (uint i = 0; i < exp; i++) { + (base2X, base2Y, base2Z) = twiceProj(base2X, base2Y, base2Z); + } + + return toAffinePoint(base2X, base2Y, base2Z); + } + + /** + * @dev Multiply an elliptic curve point by a scalar. + */ + function multiplyScalar( + uint x0, + uint y0, + uint scalar + ) internal returns (uint x1, uint y1) { + if (scalar == 0) { + return ecAff_SetZero(); + } else if (scalar == 1) { + return (x0, y0); + } else if (scalar == 2) { + return twice(x0, y0); + } + + uint base2X = x0; + uint base2Y = y0; + uint base2Z = 1; + uint z1 = 1; + x1 = x0; + y1 = y0; + + if (scalar % 2 == 0) { + x1 =0; + y1=1; + } + + unchecked{ + scalar = scalar >> 1; + + while (scalar > 0) { + (base2X, base2Y, base2Z) = twiceProj(base2X, base2Y, base2Z); + + if (scalar % 2 == 1) { + (x1, y1, z1) = addProj(base2X, base2Y, base2Z, x1, y1, z1); + } + + scalar = scalar >> 1; + } + } + return toAffinePoint(x1, y1, z1); + } + + /** + * @dev Double and Add, MSB to LSB version + */ + function ecZZ_mul( + uint x0, + uint y0, + uint scalar + ) internal returns (uint x1, uint y1) { + if (scalar == 0) { + return ecAff_SetZero(); + } + + uint zzZZ; uint zzZZZ; + + uint bit_mul=(1<>1; + index=index-1; + } + + //R=P + x1=x0;y1=y0;zzZZ=1;zzZZZ=1; + index=index-1; bit_mul=bit_mul>>1; + + while (index >= 0) { + + + (x1, y1, zzZZ, zzZZZ) = ecZZ_Dbl(x1, y1, zzZZ, zzZZZ); + + if (bit_mul &scalar != 0) { + (x1, y1, zzZZ, zzZZZ)= ecZZ_AddN(x1, y1, zzZZ, zzZZZ, x0, y0); + } + + bit_mul = bit_mul >> 1; + index=index-1; + } + } + + + return ecZZ_SetAff(x1, y1, zzZZ, zzZZZ); + } + + function NormalizedX( uint Gx0, uint Gy0, uint Gz0) internal returns(uint px) + { + if (Gz0 == 0) { + return 0; + } + + uint Px = inverseModp_Hard(Gz0, p); + unchecked { + Px = mulmod(Gx0, Px, p); + } + return Px; + } + + /** + * @dev Double base multiplication using windowing and Shamir's trick + */ + function ec_mulmuladd_W( + uint Gx0, + uint Gy0, + uint Qx0, + uint Qy0, + uint scalar_u, + uint scalar_v + ) internal pure returns (uint[3] memory R) { + unchecked{ + //1. Precomputation steps: 2 bits window+shamir + // precompute all aG+bQ in [0..3][0..3] + uint [3][16] memory Window; + + (Window[0][0], Window[0][1], Window[0][2])= zeroProj(); + + + Window[1][0]=Gx0; + Window[1][1]=Gy0; + Window[1][2]=1; + + Window[2][0]=Qx0; + Window[2][1]=Qy0; + Window[2][2]=1; + + (Window[3][0], Window[3][1], Window[3][2])=addProj(Gx0, Gy0, 1, Qx0, Qy0, 1); //3:G+Q + (Window[4][0], Window[4][1], Window[4][2])=twiceProj(Gx0, Gy0, 1);//4:2G + (Window[5][0], Window[5][1], Window[5][2])=addProj(Gx0, Gy0, 1, Window[4][0], Window[4][1], Window[4][2]); //5:3G + (Window[6][0], Window[6][1], Window[6][2])=addProj(Qx0, Qy0, 1, Window[4][0], Window[4][1], Window[4][2]); //6:2G+Q + (Window[7][0], Window[7][1], Window[7][2])=addProj(Qx0, Qy0, 1, Window[5][0], Window[5][1], Window[5][2]); //7:3G+Q + (Window[8][0], Window[8][1], Window[8][2])=twiceProj(Qx0, Qy0, 1);//8:2Q + + (Window[9][0], Window[9][1], Window[9][2])=addProj(Gx0, Gy0, 1, Window[8][0], Window[8][1], Window[8][2]);//9:2Q+G + (Window[10][0], Window[10][1], Window[10][2])=addProj(Qx0, Qy0, 1, Window[8][0], Window[8][1], Window[8][2]);//10:3Q + (Window[11][0], Window[11][1], Window[11][2])=addProj(Gx0, Gy0, 1, Window[10][0], Window[10][1], Window[10][2]); //11:3Q+G + (Window[12][0], Window[12][1], Window[12][2])=addProj(Window[8][0], Window[8][1], Window[8][2] , Window[4][0], Window[4][1], Window[4][2]); //12:2Q+2G + (Window[13][0], Window[13][1], Window[13][2])=addProj(Window[8][0], Window[8][1], Window[8][2] , Window[5][0], Window[5][1], Window[5][2]); //13:2Q+3G + (Window[14][0], Window[14][1], Window[14][2])=addProj(Window[10][0], Window[10][1], Window[10][2], Window[4][0], Window[4][1], Window[4][2]); //14:3Q+2G + (Window[15][0], Window[15][1], Window[15][2])=addProj(Window[10][0], Window[10][1], Window[10][2], Window[5][0], Window[5][1], Window[5][2]); //15:3Q+3G + + + //initialize R with infinity point + (R[0],R[1],R[2])= zeroProj(); + + uint quadbit=1; + //2. loop over scalars from MSB to LSB: + + for(uint8 i=0;i<128;i++) + { + uint8 rshift=255-(2*i); + (R[0],R[1],R[2])=twiceProj(R[0],R[1],R[2]);//double + (R[0],R[1],R[2])=twiceProj(R[0],R[1],R[2]);//double + + //compute quadruple (8*v1 +4*u1+ 2*v0 + u0) + quadbit=8*((scalar_v>>rshift)&1)+ 4*((scalar_u>>rshift)&1)+ 2*((scalar_v>>(rshift-1))&1)+ ((scalar_u >>(rshift-1))&1); + + + (R[0],R[1],R[2])=addProj(R[0],R[1],R[2], Window[quadbit][0], Window[quadbit][1], Window[quadbit][2]); + + } + } + + return R; + } + + + function ec_mulmuladd_S( + uint Gx0, + uint Gy0, + uint Qx0, + uint Qy0, + uint scalar_u, + uint scalar_v + ) internal returns (uint[3] memory R) { + + uint[3] memory H;//G+Q + (H[0],H[1], H[2] )=addProj(Gx0, Gy0, 1, Qx0, Qy0, 1); + + + + uint dibit; + + //initialize R with infinity point + (R[0],R[1],R[2])= zeroProj(); + + unchecked { + for(uint index=0;index<256;index ++) + { + + uint i=255-index; + + + (R[0],R[1],R[2])=twiceProj(R[0],R[1],R[2]);//double + + if (isZeroCurve_proj(R[0],R[1],R[2])){ + } + + dibit=((scalar_u>>i)&1)+2*((scalar_v>>i)&1); + + if(dibit==1){ + (R[0],R[1],R[2])= addProj(R[0],R[1],R[2],Gx0, Gy0, 1); + } + if(dibit==2){ + (R[0],R[1],R[2])= addProj(R[0],R[1],R[2],Qx0, Qy0, 1); + } + if(dibit==3){ + + (R[0],R[1],R[2])= addProj(R[0],R[1],R[2],H[0],H[1], H[2]); + } + } + } + return R; + } + + struct Indexing_t{ + uint dibit; + uint index; + uint i; + + } + + + function ecZZ_mulmuladd_S( + uint Gx0, + uint Gy0, + uint Qx0, + uint Qy0, + uint scalar_u, + uint scalar_v + ) internal returns (uint R0, uint R1) { + Indexing_t memory Ind; + + uint[2] memory R; + uint[2] memory H;//G+Q + (H[0], H[1] )=add(Gx0, Gy0, Qx0, Qy0); + + + Ind.index=0; + { + Ind.i=255-Ind.index; + + while( ( ((scalar_u>>Ind.i)&1)+2*((scalar_v>>Ind.i)&1) ) ==0){ + Ind.index=Ind.index+1; + Ind.i=255-Ind.index; + } + Ind.dibit=((scalar_u>>Ind.i)&1)+2*((scalar_v>>Ind.i)&1); + if(Ind.dibit==1){ + (R0 ,R1 ,R[0], R[1])= (Gx0, Gy0,1,1); + } + if(Ind.dibit==2){ + (R0 ,R1,R[0], R[1])= (Qx0, Qy0,1,1); + } + if(Ind.dibit==3){ + (R0 ,R1,R[0], R[1])= (H[0], H[1], 1, 1); + } + + Ind.index=Ind.index+1; + Ind.i=255-Ind.index; + } + + unchecked { + for(;Ind.index<256;Ind.index ++) + { + + Ind.i=255-Ind.index; + + (R0 ,R1,R[0], R[1])=ecZZ_Dbl(R0 ,R1,R[0], R[1]);//double + + + + Ind.dibit=((scalar_u>>Ind.i)&1)+2*((scalar_v>>Ind.i)&1); + + if(Ind.dibit==1){ + (R0 ,R1,R[0], R[1])= ecZZ_AddN(R0 ,R1,R[0],R[1], Gx0, Gy0); + } + if(Ind.dibit==2){ + (R0 ,R1,R[0], R[1])= ecZZ_AddN(R0 ,R1,R[0],R[1], Qx0, Qy0); + } + if(Ind.dibit==3){ + + (R0 ,R1,R[0], R[1])= ecZZ_AddN(R0 ,R1,R[0],R[1], H[0], H[1]); + } + } + } + (R0,R1)=ecZZ_SetAff(R0 ,R1,R[0], R[1]); + return (R0 ,R1); + } + + function ecZZ_mulmuladd_S_asm( + uint Q0, uint Q1,// Point G and Q stored in one memory for stack optimization + uint scalar_u, + uint scalar_v + ) internal returns (uint X) { + uint zz; + uint zzz; + uint Y; + uint index=255; + uint[6] memory T; + + + if(scalar_u==0 && scalar_v==0) return 0; + + (T[3], T[4] )=add(gx,gy,Q0, Q1); + + + + while( ( ((scalar_u>>index)&1)+2*((scalar_v>>index)&1) ) ==0){ + index=index-1; + } + + zz =((scalar_u>>index)&1)+2*((scalar_v>>index)&1); + if(zz==1){ + (X,Y) = (gx, gy); + } + if(zz==2){ + (X,Y) = (Q0, Q1); + } + if(zz==3){ + (X,Y) = (T[3], T[4]); + } + + index=index-1; + console.log("index=", index); + //index=1<= n || rs[1] == 0) { + return false; + } + + + if (!ecAff_isOnCurve(Q[0], Q[1])) { + return false; + } + + + + uint sInv = inverseModn_Hard(rs[1], n); + uint scalar_u=mulmod(uint(message), sInv, n); + uint scalar_v= mulmod(rs[0], sInv, n); + + + //test_ecZZ_formulae(message, rs, Q); + //test_Shamir(message, rs, Q); + uint x1; + uint y1; + + /* + // without Optim + uint x2; + uint y2; + + (x1, y1) = multiplyScalar(gx, gy, scalar_u); + (x2, y2) = multiplyScalar(Q[0], Q[1], scalar_v); + uint[3] memory PAff = addAndReturnProjectivePoint(x1, y1, x2, y2); + + return PAff[0] % n == rs[0]; + */ + //Shamir 2 dimensions, windowed + //(x1, y1,scalar_u)=ec_mulmuladd_W(gx, gy, Q[0], Q[1],scalar_u, scalar_v); + + //Shamir 2 dimensions + //(x1, y1)=ecZZ_mulmuladd_S(gx, gy, Q[0], Q[1],scalar_u, scalar_v); + + x1=ecZZ_mulmuladd_S_asm(Q[0], Q[1],scalar_u, scalar_v); + + //console.log("res Shamir monobit XYZZ mulmuladd:",x1); + + assembly{ + x1:=addmod(x1,sub(n,mload(rs)), n) + } + //return true; + return x1 == 0; + + } + + /* validating signatures using a precomputed table of multiples of P and Q stored in Shamir8*/ + function validateSignature_Precomputed( + bytes32 message, + uint[2] memory rs, + + uint[2][256] memory Shamir8 + ) internal returns (bool) { + + uint[2] memory Q; + Q[0]=Shamir8[4][0];//extract Q from Shamir8 + Q[1]=Shamir8[4][1]; + + + if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) { + return false; + } + /* + if (!isOnCurve(Q[0], Q[1])) { + return false; + } + */ + + + uint sInv = inverseModn_Hard(rs[1], n); + uint scalar_u=mulmod(uint(message), sInv, n); + uint scalar_v= mulmod(rs[0], sInv, n); + uint x1; + uint x2; + uint y1; + uint y2; + + + //test_ecZZ_formulae(); + + //Shamir 8 dimensions + (x1, y1)=ecZZ_mulmuladd_S8(scalar_u, scalar_v, Shamir8); + console.log("res Shamir 8dim precomputed XYZZ mulmuladd:",x1); + //uint[3] memory P = ec_mulmuladd_W(gx, gy, Q[0], Q[1],scalar_u ,scalar_v ); + //uint Px=NormalizedX(P[0], P[1], P[2]); + + return x1 % n == rs[0]; + } + + + /* validating signatures using a precomputed table of multiples of P and Q stored in sstore2 at address Shamir8*/ + + function validateSignature_Precomputed_ss2( + bytes32 message, + uint[2] memory rs, + address Shamir8 + ) internal returns (bool) { + + + if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) { + return false; + } + /* tbd or not: check Q + if (!isOnCurve(Q[0], Q[1])) { + return false; + } + */ + + uint sInv = inverseModn_Hard(rs[1], n); + uint scalar_u=mulmod(uint(message), sInv, n); + uint scalar_v= mulmod(rs[0], sInv, n); + uint[2] memory P; + + + //Shamir 8 dimensions + P=ecZZ_mulmuladd_S8_ss2(scalar_u, scalar_v, Shamir8); + console.log("res Shamir 8dim precomputed XYZZ mulmuladd:",P[0]); + //uint[3] memory P = ec_mulmuladd_W(gx, gy, Q[0], Q[1],scalar_u ,scalar_v ); + //uint Px=NormalizedX(P[0], P[1], P[2]); + + return P[0] % n == rs[0]; + } + + + /* validating signatures using a precomputed table of multiples of P and Q stored in contract at address Shamir8*/ + function validateSignature_Precomputed_extcode( + bytes32 message, + uint[2] memory rs, + address Shamir8 + ) internal returns (bool) { + + + if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) { + return false; + } + /* tbd or not: check Q + if (!isOnCurve(Q[0], Q[1])) { + return false; + } + */ + +// uint sInv = inverseMod(rs[1], n); + uint sInv =inverseModn_Hard(rs[1], n); + // console.log("******************************inv=",sInv); + //uint scalar_u=mulmod(uint(message), sInv, n); + //uint scalar_v= mulmod(rs[0], sInv, n); + uint X; + + + //Shamir 8 dimensions + X=ecZZ_mulmuladd_S8_extcode(mulmod(uint(message), sInv, n), mulmod(rs[0], sInv, n), Shamir8); + // console.log("res Shamir 8dim precomputed XYZZ mulmuladd:",X); + //uint[3] memory P = ec_mulmuladd_W(gx, gy, Q[0], Q[1],scalar_u ,scalar_v ); + //uint Px=NormalizedX(P[0], P[1], P[2]); + + assembly{ + X:=addmod(X,sub(n,mload(rs)), n) + } + //return true; + return X == 0; + + } + + + + + +} diff --git a/contracts/FCL_elliptic.sol b/contracts/FCL_elliptic.sol new file mode 100644 index 0000000..a8979a9 --- /dev/null +++ b/contracts/FCL_elliptic.sol @@ -0,0 +1,558 @@ +//********************************************************************************************/ +// ___ _ ___ _ _ _ _ +// | __| _ ___ __| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__ +// | _| '_/ -_|_-< ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \ +// |_||_| \___/__/_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/ +// |__/|_| +///* Copyright (C) 2022 - Renaud Dubois - This file is part of FCL (Fresh CryptoLib) project +///* License: This software is licensed under MIT License +///* This Code may be reused including license and copyright notice. +///* See LICENSE file at the root folder of the project. +///* FILE: FCL_elliptic.sol +///* +///* +///* DESCRIPTION: modified XYZZ system coordinates for EVM elliptic point multiplication +///* optimization +///* +//**************************************************************************************/ +//* WARNING: this code SHALL not be used for non prime order curves for security reasons. +// Code is optimized for a=-3 only curves with prime order, constant like -1, -2 shall be replaced +// if ever used for other curve than sec256R1 +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//import "hardhat/console.sol"; + +library FCL_Elliptic_ZZ { + // Set parameters for curve sec256r1. + + //curve prime field modulus + uint constant p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + //short weierstrass first coefficient + uint constant a = + 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; + //short weierstrass second coefficient + uint constant b = + 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; + //generating point affine coordinates + uint constant gx = + 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; + uint constant gy = + 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; + //curve order (number of points) + uint constant n = + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + /* -2 mod p constant, used to speed up inversion and doubling (avoid negation)*/ + uint constant minus_2 = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFD; + /* -2 mod n constant, used to speed up inversion*/ + uint constant minus_2modn = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F; + + uint constant minus_1= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + /** + /* inversion mod n via a^(n-2), use of precompiled using little Fermat theorem*/ + function FCL_nModInv(uint256 u) public returns (uint256 result) { + uint[6] memory pointer; + assembly { + + // Define length of base, exponent and modulus. 0x20 == 32 bytes + mstore(pointer, 0x20) + mstore(add(pointer, 0x20), 0x20) + mstore(add(pointer, 0x40), 0x20) + // Define variables base, exponent and modulus + mstore(add(pointer, 0x60), u) + mstore(add(pointer, 0x80), minus_2modn) + mstore(add(pointer, 0xa0), n) + + // Call the precompiled contract 0x05 = ModExp + if iszero(call(not(0), 0x05, 0, pointer, 0xc0, pointer, 0x20)) { + revert(0, 0) + } + result:=mload(pointer) + } + + } + /** + /* @dev inversion mod nusing little Fermat theorem via a^(n-2), use of precompiled*/ + function FCL_pModInv(uint256 u) public returns (uint256 result) { + uint[6] memory pointer; + assembly { + // Define length of base, exponent and modulus. 0x20 == 32 bytes + mstore(pointer, 0x20) + mstore(add(pointer, 0x20), 0x20) + mstore(add(pointer, 0x40), 0x20) + // Define variables base, exponent and modulus + mstore(add(pointer, 0x60), u) + mstore(add(pointer, 0x80), minus_2) + mstore(add(pointer, 0xa0), p) + + // Call the precompiled contract 0x05 = ModExp + if iszero(call(not(0), 0x05, 0, pointer, 0xc0, pointer, 0x20)) { + revert(0, 0) + } + result:=mload(pointer) + } + } + + /** + /* @dev Convert from affine rep to XYZZ rep*/ + function ecAff_SetZZ( + uint x0, + uint y0 + ) internal pure returns (uint[4] memory P) { + unchecked { + P[2] = 1; //ZZ + P[3] = 1; //ZZZ + P[0] = x0; + P[1] = y0; + } + } + + /** + /* @dev Convert from XYZZ rep to affine rep*/ + /* https://hyperelliptic.org/EFD/g1p/auto-shortw-xyzz-3.html#addition-add-2008-s*/ + function ecZZ_SetAff( uint x, + uint y, + uint zz, + uint zzz) internal returns (uint x1, uint y1) + { + uint zzzInv = FCL_pModInv(zzz); //1/zzz + y1=mulmod(y,zzzInv,p);//Y/zzz + uint b=mulmod(zz, zzzInv,p); //1/z + zzzInv= mulmod(b,b,p); //1/zz + x1=mulmod(x,zzzInv,p);//X/zz + } + + + + /** + /* @dev Sutherland2008 doubling*/ + /* The "dbl-2008-s-1" doubling formulas */ + function ecZZ_Dbl( + uint x, + uint y, + uint zz, + uint zzz + ) internal pure returns (uint P0, uint P1,uint P2,uint P3) + { + unchecked{ + assembly{ + P0:=mulmod(2, y, p) //U = 2*Y1 + P2:=mulmod(P0,P0,p) // V=U^2 + P3:=mulmod(x, P2,p)// S = X1*V + P1:=mulmod(P0, P2,p) // W=UV + P2:=mulmod(P2, zz, p) //zz3=V*ZZ1 + zz:=mulmod(3, mulmod(addmod(x,sub(p,zz),p), addmod(x,zz,p),p) ,p) //M=3*(X1-ZZ1)*(X1+ZZ1) + P0:=addmod(mulmod(zz,zz,p), mulmod(minus_2, P3,p),p) //X3=M^2-2S + x:=mulmod(zz,addmod(P3, sub(p,P0),p),p)//M(S-X3) + P3:=mulmod(P1,zzz,p)//zzz3=W*zzz1 + P1:=addmod(x, sub(p, mulmod(P1, y,p)),p )//Y3= M(S-X3)-W*Y1 + } + } + return (P0, P1, P2, P3); + } + + /** + * @dev Sutherland2008 add a ZZ point with a normalized point and greedy formulae + * warning: assume that P1(x1,y1)!=P2(x2,y2), true in multiplication loop with prime order (cofactor 1) + */ + + //tbd: return -x1 and -Y1 in double to avoid two substractions + function ecZZ_AddN( + uint x1, + uint y1, + uint zz1, + uint zzz1, + uint x2, + uint y2) internal pure returns (uint P0, uint P1,uint P2,uint P3) + { + unchecked{ + if(y1==0){ + return (x2,y2,1,1); + } + + assembly{ + y1:=sub(p, y1) + y2:=addmod(mulmod(y2, zzz1,p),y1,p) + x2:=addmod(mulmod(x2, zz1,p),sub(p,x1),p) + P0:=mulmod(x2, x2, p)//PP = P^2 + P1:=mulmod(P0,x2,p)//PPP = P*PP + P2:=mulmod(zz1,P0,p) ////ZZ3 = ZZ1*PP + P3:= mulmod(zzz1,P1,p) ////ZZZ3 = ZZZ1*PPP + zz1:=mulmod(x1, P0, p)//Q = X1*PP + P0:=addmod(addmod(mulmod(y2,y2, p), sub(p,P1),p ), mulmod(minus_2, zz1,p) ,p )//R^2-PPP-2*Q + P1:=addmod(mulmod(addmod(zz1, sub(p,P0),p), y2, p), mulmod(y1, P1,p),p)//R*(Q-X3) + } + //end assembly + }//end unchecked + return (P0, P1, P2, P3); + } + + /** + * @dev Return the zero curve in XYZZ coordinates. + */ + function ecZZ_SetZero() internal pure returns (uint x, uint y, uint zz, uint zzz) { + return (0, 0, 0, 0); + } + /** + * @dev Check if point is the neutral of the curve + */ + function ecZZ_IsZero (uint x0, uint y0, uint zz0, uint zzz0) internal pure returns (bool) + { + if ( (y0 == 0) ) { + return true; + } + return false; + } + /** + * @dev Return the zero curve in affine coordinates. Compatible with the double formulae (no special case) + */ + function ecAff_SetZero() internal pure returns (uint x, uint y) { + return (0, 0); + } + + /** + * @dev Check if the curve is the zero curve in affine rep. + */ + function ecAff_IsZero(uint x, uint y) internal pure returns (bool flag) { + return (y==0); + } + + /** + * @dev Check if a point in affine coordinates is on the curve (reject Neutral that is indeed on the curve). + */ + function ecAff_isOnCurve(uint x, uint y) internal pure returns (bool) { + if (0 == x || x == p || 0 == y || y == p) { + return false; + } + unchecked { + uint LHS = mulmod(y, y, p); // y^2 + uint RHS = addmod(mulmod(mulmod(x, x, p), x, p), mulmod(x, a, p), p); // x^3+ax + RHS = addmod(RHS, b, p); // x^3 + a*x + b + + return LHS == RHS; + } + } + + + /** + * @dev Add two elliptic curve points in affine coordinates. + */ + function ecAff_add( + uint x0, + uint y0, + uint x1, + uint y1 + ) internal returns (uint, uint) { + uint zz0; + uint zzz0; + + if(ecAff_IsZero(x0,y0)) return (x1,y1); + if(ecAff_IsZero(x1,y1)) return (x1,y1); + + (x0, y0, zz0, zzz0) = ecZZ_AddN(x0, y0, 1,1, x1, y1); + + return ecZZ_SetAff(x0, y0, zz0, zzz0); + } + + /** + * @dev Computation of uG+vQ using Strauss-Shamir's trick, G basepoint, Q public key + */ + function ecZZ_mulmuladd_S_asm( + uint Q0, uint Q1,// Point G and Q stored in one memory for stack optimization + uint scalar_u, + uint scalar_v + ) internal returns (uint X) { + uint zz; + uint zzz; + uint Y; + uint index=255; + uint[6] memory T; + uint H0; + uint H1; + if(scalar_u==0 && scalar_v==0) return 0; + + (H0,H1 )=ecAff_add(gx,gy,Q0, Q1);//will not work if Q=P, obvious forbidden private key + + while( ( ((scalar_u>>index)&1)+2*((scalar_v>>index)&1) ) ==0){ + index=index-1; + } + + unchecked { + assembly{ + zz:=add( shl(1, and(shr(index, scalar_v),1)), and(shr(index, scalar_u),1) ) + + if eq(zz,1) { + X:=gx + Y:=gy + } + if eq(zz,2) { + X:=Q0 + Y:=Q1 + } + if eq(zz,3) { + X:=H0 + Y:= H1 + } + + index:=sub(index,1) + zz:=1 + zzz:=1 + + for { index := index } gt( minus_1, index) { index := sub(index, 1) } + { + // inlined EcZZ_Dbl + let T1:=mulmod(2, Y, p) //U = 2*Y1, y free + let T2:=mulmod(T1,T1,p) // V=U^2 + let T3:=mulmod(X, T2,p)// S = X1*V + T1:=mulmod(T1, T2,p) // W=UV + let T4:=mulmod(3, mulmod(addmod(X,sub(p,zz),p), addmod(X,zz,p),p) ,p) //M=3*(X1-ZZ1)*(X1+ZZ1) + zzz:=mulmod(T1,zzz,p)//zzz3=W*zzz1 + zz:=mulmod(T2, zz, p) //zz3=V*ZZ1, V free + + X:=addmod(mulmod(T4,T4,p), mulmod(minus_2, T3,p),p) //X3=M^2-2S + //T2:=mulmod(T4,addmod(T3, sub(p, X),p),p)//M(S-X3) + T2:=mulmod(T4,addmod(X, sub(p, T3),p),p)//-M(S-X3)=M(X3-S) + + //Y:= addmod(T2, sub(p, mulmod(T1, Y ,p)),p )//Y3= M(S-X3)-W*Y1 + Y:= addmod(mulmod(T1, Y ,p), T2,p )//-Y3= W*Y1-M(S-X3), we replace Y by -Y to avoid a sub in ecAdd + + + //value of dibit + T4:=add( shl(1, and(shr(index, scalar_v),1)), and(shr(index, scalar_u),1) ) + + if iszero(T4){ + Y:=sub(p,Y)//restore the -Y inversion + continue + }// if T4!=0 + { + if eq(T4,1) { + T1:=gx + T2:=gy + + } + if eq(T4,2) { + T1:=Q0 + T2:=Q1 + } + if eq(T4,3) { + T1:=H0 + T2:= H1 + } + + // inlined EcZZ_AddN + //T3:=sub(p, Y) + //T3:=Y + let y2:=addmod(mulmod(T2, zzz,p),Y,p) + T2:=addmod(mulmod(T1, zz,p),sub(p,X),p) + + T4:=mulmod(T2, T2, p)//PP + let TT1:=mulmod(T4,T2,p)//PPP, this one could be spared, but adding this register spare gas + zz:=mulmod(zz,T4,p) + zzz:= mulmod(zzz,TT1,p) //zz3=V*ZZ1 + T2:=mulmod(X, T4, p) + T4:=addmod(addmod(mulmod(y2,y2, p), sub(p,TT1),p ), mulmod(minus_2, T2,p) ,p ) + Y:=addmod(mulmod(addmod(T2, sub(p,T4),p), y2, p), mulmod(Y, TT1,p),p) + + X:=T4 + } + + }//end loop + mstore(add(T, 0x60),zz) + //(X,Y)=ecZZ_SetAff(X,Y,zz, zzz); + //T[0] = inverseModp_Hard(T[0], p); //1/zzz, inline modular inversion using precompile: + // Define length of base, exponent and modulus. 0x20 == 32 bytes + mstore(T, 0x20) + mstore(add(T, 0x20), 0x20) + mstore(add(T, 0x40), 0x20) + // Define variables base, exponent and modulus + //mstore(add(pointer, 0x60), u) + mstore(add(T, 0x80), minus_2) + mstore(add(T, 0xa0), p) + + // Call the precompiled contract 0x05 = ModExp + if iszero(call(not(0), 0x05, 0, T, 0xc0, T, 0x20)) { + revert(0, 0) + } + + //Y:=mulmod(Y,zzz,p)//Y/zzz + //zz :=mulmod(zz, mload(T),p) //1/z + //zz:= mulmod(zz,zz,p) //1/zz + X:=mulmod(X,mload(T),p)//X/zz + } //end assembly + }//end unchecked + + return X; + } + + + //8 dimensions Shamir's trick, using precomputations stored in Shamir8, stored as Bytecode of an external + //contract at given address dataPointer + //(thx to Lakhdar https://github.com/Kelvyne for EVM storage explanations and tricks) + // the external tool to generate tables from public key is in the /sage directory + function ecZZ_mulmuladd_S8_extcode(uint scalar_u, uint scalar_v, address dataPointer) + internal returns(uint X/*, uint Y*/) + { + uint zz; // third and coordinates of the point + + uint[6] memory T; + zz=256;//start index + + unchecked{ + + while(T[0]==0) + { + zz=zz-1; + //tbd case of msb octobit is null + T[0]=64*(128*((scalar_v>>zz)&1)+64*((scalar_v>>(zz-64))&1)+ + 32*((scalar_v>>(zz-128))&1)+16*((scalar_v>>(zz-192))&1)+ + 8*((scalar_u>>zz)&1)+4*((scalar_u>>(zz-64))&1)+2*((scalar_u>>(zz-128))&1)+((scalar_u>>(zz-192))&1)); + } + assembly{ + + extcodecopy(dataPointer, T, mload(T), 64) + X:= mload(T) + let Y:= mload(add(T,32)) + let zzz:=1 + zz:=1 + + //loop over 1/4 of scalars thx to Shamir's trick over 8 points + for { let index := 254 } gt(index, 191) { index := sub(index, 1) } + { + let ind:=index + // inlined EcZZ_Dbl + let y:=mulmod(2, Y, p) //U = 2*Y1, y free + let T2:=mulmod(y,y,p) // V=U^2 + let T3:=mulmod(X, T2,p)// S = X1*V + let T1:=mulmod(y, T2,p) // W=UV + let T4:=mulmod(3, mulmod(addmod(X,sub(p,zz),p), addmod(X,zz,p),p) ,p) //M=3*(X1-ZZ1)*(X1+ZZ1) + zzz:=mulmod(T1,zzz,p)//zzz3=W*zzz1 + + X:=addmod(mulmod(T4,T4,p), mulmod(minus_2, T3,p),p) //X3=M^2-2S + y:=mulmod(T4,addmod(T3, sub(p, X),p),p)//M(S-X3) + Y:= addmod(y, sub(p, mulmod(T1, Y ,p)),p )//Y3= M(S-X3)-W*Y1 + zz:=mulmod(T2, zz, p) //zz3=V*ZZ1 + + /* compute element to access in precomputed table */ + T4:= add( shl(13, and(shr(ind, scalar_v),1)), shl(9, and(shr(ind, scalar_u),1)) ) + ind:=sub(index, 64) + T4:=add(T4, add( shl(12, and(shr(ind, scalar_v),1)), shl(8, and(shr(ind, scalar_u),1)) )) + ind:=sub(index, 128) + T4:=add(T4,add( shl(11, and(shr(ind, scalar_v),1)), shl(7, and(shr(ind, scalar_u),1)) )) + ind:=sub(index, 192) + T4:=add(T4,add( shl(10, and(shr(ind, scalar_v),1)), shl(6, and(shr(ind, scalar_u),1)) )) + + //tbd: check validity of formulae with (0,1) to remove conditional jump + if iszero(T4){ + continue + } + { + /* Access to precomputed table using extcodecopy hack */ + extcodecopy(dataPointer, T,T4, 64) + + // inlined EcZZ_AddN + y:=sub(p, Y) + let y2:=addmod(mulmod(mload(add(T,32)), zzz,p),y,p) + T2:=addmod(mulmod(mload(T), zz,p),sub(p,X),p) + T4:=mulmod(T2, T2, p) + T1:=mulmod(T4,T2,p) + T2:=mulmod(zz,T4,p) // W=UV + zzz:= mulmod(zzz,T1,p) //zz3=V*ZZ1 + let zz1:=mulmod(X, T4, p) + T4:=addmod(addmod(mulmod(y2,y2, p), sub(p,T1),p ), mulmod(minus_2, zz1,p) ,p ) + Y:=addmod(mulmod(addmod(zz1, sub(p,T4),p), y2, p), mulmod(y, T1,p),p) + zz:=T2 + X:=T4 + } + }//end loop + mstore(add(T, 0x60),zz) + + //(X,Y)=ecZZ_SetAff(X,Y,zz, zzz); + //T[0] = inverseModp_Hard(T[0], p); //1/zzz, inline modular inversion using precompile: + // Define length of base, exponent and modulus. 0x20 == 32 bytes + mstore(T, 0x20) + mstore(add(T, 0x20), 0x20) + mstore(add(T, 0x40), 0x20) + // Define variables base, exponent and modulus + //mstore(add(pointer, 0x60), u) + mstore(add(T, 0x80), minus_2) + mstore(add(T, 0xa0), p) + + // Call the precompiled contract 0x05 = ModExp + if iszero(call(not(0), 0x05, 0, T, 0xc0, T, 0x20)) { + revert(0, 0) + } + + zz:=mload(T) + X:=mulmod(X,zz,p)//X/zz + } + }//end unchecked + } + + /** + * @dev ECDSA verification, given , signature, and public key. + */ + function ecdsa_verify( + bytes32 message, + uint[2] memory rs, + uint[2] memory Q + ) internal returns (bool) { + if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) { + return false; + } + + + if (!ecAff_isOnCurve(Q[0], Q[1])) { + return false; + } + + uint sInv = FCL_nModInv(rs[1]); + uint scalar_u=mulmod(uint(message), sInv, n); + uint scalar_v= mulmod(rs[0], sInv, n); + uint x1; + + x1=ecZZ_mulmuladd_S_asm(Q[0], Q[1],scalar_u, scalar_v); + + assembly{ + x1:=addmod(x1,sub(n,mload(rs)), n) + } + //return true; + return x1 == 0; + + } + + /** + * @dev ECDSA verification using a precomputed table of multiples of P and Q stored in contract at address Shamir8 + generation of contract bytecode for precomputations is done using sagemath code + (see sage directory, WebAuthn_precompute.sage) + */ + + function ecdsa_precomputed_verify( + bytes32 message, + uint[2] memory rs, + address Shamir8 + ) internal returns (bool) { + if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) { + return false; + } + /* Q is pushed via bytecode assumed to be correct + if (!isOnCurve(Q[0], Q[1])) { + return false; + }*/ + + uint sInv =FCL_nModInv(rs[1]); + uint X; + + //Shamir 8 dimensions + X=ecZZ_mulmuladd_S8_extcode(mulmod(uint(message), sInv, n), mulmod(rs[0], sInv, n), Shamir8); + + assembly{ + X:=addmod(X,sub(n,mload(rs)), n) + } + //return true; + return X == 0; + + }//end ecdsa_precomputed_verify() +}//EOF + + diff --git a/contracts/Webauthn.sol b/contracts/Webauthn.sol index 2cde841..5ffb6e0 100644 --- a/contracts/Webauthn.sol +++ b/contracts/Webauthn.sol @@ -2,9 +2,11 @@ pragma solidity ^0.8.0; import {Base64URL} from "./Base64URL.sol"; -import {EllipticCurve} from "./EllipticCurve.sol"; +import {FCL_Elliptic_ZZ} from "./FCL_elliptic.sol"; import "hardhat/console.sol"; + + error InvalidAuthenticatorData(); error InvalidClientData(); error InvalidSignature(); @@ -12,6 +14,19 @@ error InvalidSignature(); contract Webauthn { uint256 public counter; + + function ecdsa_verif( bytes32 hash, uint[2] memory rs, + uint[2] memory Q) public returns (bool) + { + // bytes32 message = sha256(verifyData); + console.log("hash=", uint(hash)); + console.log("rs0=", rs[0]); + + bool result=FCL_Elliptic_ZZ.ecdsa_verify(bytes32(hash), rs, Q); + console.log("result= %s", result); + + } + function checkSignature( bytes memory authenticatorData, bytes1 authenticatorDataFlagMask, @@ -20,7 +35,7 @@ contract Webauthn { uint clientChallengeDataOffset, uint[2] memory rs, uint[2] memory Q - ) public view returns (bool) { + ) public returns (bool) { // Let the caller check if User Presence (0x01) or User Verification (0x04) are set if ( (authenticatorData[32] & authenticatorDataFlagMask) != @@ -64,10 +79,21 @@ contract Webauthn { verifyData, authenticatorData.length ); + + /* + uint8 tmp=verifyData[0]; + console.log("verifyData:", tmp); + */ bytes32 message = sha256(verifyData); - return EllipticCurve.validateSignature(message, rs, Q); + //bool result=Ec_ZZ.validateSignature(message, rs, Q); + bool result=FCL_Elliptic_ZZ.ecdsa_verify(message, rs, Q); + console.log("result= %s", result); + + return result; } + + function validate( bytes memory authenticatorData, bytes1 authenticatorDataFlagMask, @@ -93,9 +119,6 @@ contract Webauthn { counter++; } - /* - The following function has been written by Alex Beregszaszi (@axic), use it under the terms of the MIT license - */ function copyBytes( bytes memory _from, uint _fromOffset, diff --git a/contracts/Webauthn_prec.sol b/contracts/Webauthn_prec.sol new file mode 100644 index 0000000..a049f30 --- /dev/null +++ b/contracts/Webauthn_prec.sol @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import {Base64URL} from "./Base64URL.sol"; +import {Ec_ZZ} from "./Elliptic_ZZ.sol"; + +import {FCL_Elliptic_ZZ} from "./FCL_elliptic.sol"; + +import "hardhat/console.sol"; + +import "solmate/src/utils/SSTORE2.sol"; + + + +error InvalidAuthenticatorData(); +error InvalidClientData(); +error InvalidSignature(); + +contract replaceable{ + string constant x="replace me please"; +} + +contract BytecodeTable { + //precomputations + + + function Reader(address selfe) public + { + uint[2] memory px; + + + console.log("address=",uint256(uint160(selfe))); + + + + for(uint i=0;i<256;i++){ + uint offset=64*i; + assembly{ + extcodecopy(selfe, px, offset, 64) + } + //console.log("Test on curve of point ",i, px[0], px[1]); + //console.log(Ec_ZZ.ecAff_isOnCurve(px[0], px[1])); + } + } +} + + +contract Webauthn_prec3{ + + address dataPointer; + uint256 public counter; + + constructor( address Precompute_contract) + { + dataPointer=Precompute_contract; + } + + + + function checkSignature_prec( + bytes memory authenticatorData, + bytes1 authenticatorDataFlagMask, + bytes memory clientData, + bytes32 clientChallenge, + uint clientChallengeDataOffset, + uint[2] memory rs + ) public returns (bool) { + // Let the caller check if User Presence (0x01) or User Verification (0x04) are set + if ( + (authenticatorData[32] & authenticatorDataFlagMask) != + authenticatorDataFlagMask + ) { + revert InvalidAuthenticatorData(); + } + // Verify that clientData commits to the expected client challenge + string memory challengeEncoded = Base64URL.encode32( + abi.encodePacked(clientChallenge) + ); + bytes memory challengeExtracted = new bytes( + bytes(challengeEncoded).length + ); + copyBytes( + clientData, + clientChallengeDataOffset, + challengeExtracted.length, + challengeExtracted, + 0 + ); + if ( + keccak256(abi.encodePacked(bytes(challengeEncoded))) != + keccak256(abi.encodePacked(challengeExtracted)) + ) { + revert InvalidClientData(); + } + // Verify the signature over sha256(authenticatorData || sha256(clientData)) + bytes memory verifyData = new bytes(authenticatorData.length + 32); + copyBytes( + authenticatorData, + 0, + authenticatorData.length, + verifyData, + 0 + ); + copyBytes( + abi.encodePacked(sha256(clientData)), + 0, + 32, + verifyData, + authenticatorData.length + ); + bytes32 message = sha256(verifyData); + bool result=FCL_Elliptic_ZZ.ecdsa_precomputed_verify(message, rs, dataPointer); + console.log("result= %s", result); + + return result; + } + + function validate_prec( + bytes memory authenticatorData, + bytes1 authenticatorDataFlagMask, + bytes memory clientData, + bytes32 clientChallenge, + uint clientChallengeDataOffset, + uint[2] memory rs + + ) public { + if ( + !checkSignature_prec( + authenticatorData, + authenticatorDataFlagMask, + clientData, + clientChallenge, + clientChallengeDataOffset, + rs + ) + ) { + revert InvalidSignature(); + } + counter++; + } + + + function copyBytes( + bytes memory _from, + uint _fromOffset, + uint _length, + bytes memory _to, + uint _toOffset + ) internal pure returns (bytes memory _copiedBytes) { + uint minLength = _length + _toOffset; + require(_to.length >= minLength); // Buffer too small. Should be a better way? + uint i = 32 + _fromOffset; // NOTE: the offset 32 is added to skip the `size` field of both bytes variables + uint j = 32 + _toOffset; + while (i < (32 + _fromOffset + _length)) { + assembly { + let tmp := mload(add(_from, i)) + mstore(add(_to, j), tmp) + } + i += 32; + j += 32; + } + return _to; + } + + + + +} + + +contract Webauthn_prec2 { + + address dataPointer; + uint256 public counter; + + + constructor( bytes memory Shamir8_ss2) + { + uint taille; + assembly{ + taille:=mload(Shamir8_ss2) + } + + console.log("Ecriture de la table input dans le contrat, taille :", taille); + + dataPointer=SSTORE2.write(Shamir8_ss2); + bytes memory ec_point=new bytes(64); + + uint256[2] memory xy; + + uint offset; + uint endoffset; + uint px; + uint py; + address cpy_datap=dataPointer; + + /* + console.log("Relecture de la table :", taille); + + + for(uint i=0;i<256;i++) + { + offset=64*i; + endoffset=offset+64; + + ec_point=SSTORE2.read(dataPointer, 64*i, 64*i+64); + assembly{ + px:=mload(add(ec_point,32)) + py:=mload(add(ec_point,64)) + + } + + console.log("Read1 :", px,py); + console.log(Ec_ZZ.ecAff_isOnCurve(px, py)); + } */ + } + + + + function checkSignature_prec( + bytes memory authenticatorData, + bytes1 authenticatorDataFlagMask, + bytes memory clientData, + bytes32 clientChallenge, + uint clientChallengeDataOffset, + uint[2] memory rs + ) public returns (bool) { + // Let the caller check if User Presence (0x01) or User Verification (0x04) are set + if ( + (authenticatorData[32] & authenticatorDataFlagMask) != + authenticatorDataFlagMask + ) { + revert InvalidAuthenticatorData(); + } + // Verify that clientData commits to the expected client challenge + string memory challengeEncoded = Base64URL.encode32( + abi.encodePacked(clientChallenge) + ); + bytes memory challengeExtracted = new bytes( + bytes(challengeEncoded).length + ); + copyBytes( + clientData, + clientChallengeDataOffset, + challengeExtracted.length, + challengeExtracted, + 0 + ); + if ( + keccak256(abi.encodePacked(bytes(challengeEncoded))) != + keccak256(abi.encodePacked(challengeExtracted)) + ) { + revert InvalidClientData(); + } + // Verify the signature over sha256(authenticatorData || sha256(clientData)) + bytes memory verifyData = new bytes(authenticatorData.length + 32); + copyBytes( + authenticatorData, + 0, + authenticatorData.length, + verifyData, + 0 + ); + copyBytes( + abi.encodePacked(sha256(clientData)), + 0, + 32, + verifyData, + authenticatorData.length + ); + bytes32 message = sha256(verifyData); + bool result=Ec_ZZ.validateSignature_Precomputed_ss2(message, rs, dataPointer); + console.log("result= %s", result); + + return result; + } + + function validate_prec( + bytes memory authenticatorData, + bytes1 authenticatorDataFlagMask, + bytes memory clientData, + bytes32 clientChallenge, + uint clientChallengeDataOffset, + uint[2] memory rs + + ) public { + if ( + !checkSignature_prec( + authenticatorData, + authenticatorDataFlagMask, + clientData, + clientChallenge, + clientChallengeDataOffset, + rs + ) + ) { + revert InvalidSignature(); + } + counter++; + } + + + function copyBytes( + bytes memory _from, + uint _fromOffset, + uint _length, + bytes memory _to, + uint _toOffset + ) internal pure returns (bytes memory _copiedBytes) { + uint minLength = _length + _toOffset; + require(_to.length >= minLength); // Buffer too small. Should be a better way? + uint i = 32 + _fromOffset; // NOTE: the offset 32 is added to skip the `size` field of both bytes variables + uint j = 32 + _toOffset; + while (i < (32 + _fromOffset + _length)) { + assembly { + let tmp := mload(add(_from, i)) + mstore(add(_to, j), tmp) + } + i += 32; + j += 32; + } + return _to; + } + + + +} + +contract Webauthn_prec { + uint256 public counter; + + + //precomputations + uint[2][256] public Shamir8; + + constructor(uint[2] memory Q){ + + console.log("Precompute for Public key:", Q[0], Q[1]); + + Shamir8=Ec_ZZ.Precalc_Shamir8_part1(Q); + console.log("Precompute part 1 done"); + } + + + + function deploy_part2(uint[2] memory Q) public returns ( uint[2][256] memory res) + { + unchecked{ + + uint[2][128] memory Prec2=Ec_ZZ.Precalc_Shamir8_part2(Q); + + for(uint i=128;i<256;i++) + { + Shamir8[i][0]=Prec2[i-128][0]; + Shamir8[i][1]=Prec2[i-128][1]; + } + + console.log("Precompute part 2 done:"); + + } + + + /* 256 point, 2 coordinates, 32 bytes each =16384 bytes*/ + //bytes memory Shamir8_ss2= new bytes(1); + + for(uint i=0; i<256; i++) + { + console.log("\"", Shamir8[i][0],"\","); + console.log("\"", Shamir8[i][1],"\","); + //res[i][0]=Shamir8[i][0]; + //res[i][1]=Shamir8[i][1]; + } + + //cette ligne pète + //dataPointer=SSTORE2.write(Shamir8_ss2); + return res=Shamir8; + } + + function checkSignature_prec( + bytes memory authenticatorData, + bytes1 authenticatorDataFlagMask, + bytes memory clientData, + bytes32 clientChallenge, + uint clientChallengeDataOffset, + uint[2] memory rs, + uint[2][256] memory Shamir8 + ) public returns (bool) { + // Let the caller check if User Presence (0x01) or User Verification (0x04) are set + if ( + (authenticatorData[32] & authenticatorDataFlagMask) != + authenticatorDataFlagMask + ) { + revert InvalidAuthenticatorData(); + } + // Verify that clientData commits to the expected client challenge + string memory challengeEncoded = Base64URL.encode32( + abi.encodePacked(clientChallenge) + ); + bytes memory challengeExtracted = new bytes( + bytes(challengeEncoded).length + ); + copyBytes( + clientData, + clientChallengeDataOffset, + challengeExtracted.length, + challengeExtracted, + 0 + ); + if ( + keccak256(abi.encodePacked(bytes(challengeEncoded))) != + keccak256(abi.encodePacked(challengeExtracted)) + ) { + revert InvalidClientData(); + } + // Verify the signature over sha256(authenticatorData || sha256(clientData)) + bytes memory verifyData = new bytes(authenticatorData.length + 32); + copyBytes( + authenticatorData, + 0, + authenticatorData.length, + verifyData, + 0 + ); + copyBytes( + abi.encodePacked(sha256(clientData)), + 0, + 32, + verifyData, + authenticatorData.length + ); + bytes32 message = sha256(verifyData); + bool result=Ec_ZZ.validateSignature_Precomputed(message, rs, Shamir8); + console.log("result= %s", result); + + return result; + } + + function validate_prec( + bytes memory authenticatorData, + bytes1 authenticatorDataFlagMask, + bytes memory clientData, + bytes32 clientChallenge, + uint clientChallengeDataOffset, + uint[2] memory rs + + ) public { + if ( + !checkSignature_prec( + authenticatorData, + authenticatorDataFlagMask, + clientData, + clientChallenge, + clientChallengeDataOffset, + rs, + Shamir8 + ) + ) { + revert InvalidSignature(); + } + counter++; + } + + + function copyBytes( + bytes memory _from, + uint _fromOffset, + uint _length, + bytes memory _to, + uint _toOffset + ) internal pure returns (bytes memory _copiedBytes) { + uint minLength = _length + _toOffset; + require(_to.length >= minLength); // Buffer too small. Should be a better way? + uint i = 32 + _fromOffset; // NOTE: the offset 32 is added to skip the `size` field of both bytes variables + uint j = 32 + _toOffset; + while (i < (32 + _fromOffset + _length)) { + assembly { + let tmp := mload(add(_from, i)) + mstore(add(_to, j), tmp) + } + i += 32; + j += 32; + } + return _to; + } +} diff --git a/hardhat.config.js b/hardhat.config.js index bffbd29..475b2d0 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -17,6 +17,9 @@ task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.17", + settings: { + viaIR: true, // add this + }, preprocess: { eachLine: removeConsoleLog((hre) => hre.network.name !== 'hardhat' && hre.network.name !== 'localhost'), }, diff --git a/package-lock.json b/package-lock.json index 52a7d2c..2ea2cd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1312,6 +1312,24 @@ "safer-buffer": "~2.1.0" } }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + } + } + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -2057,6 +2075,22 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-secp256r1": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/ecdsa-secp256r1/-/ecdsa-secp256r1-1.3.3.tgz", + "integrity": "sha512-7JkHQ43iv9vT1U9tyGuxcE4+SMF/da+YiIMRpcwmbHmJQmqfFUuT6c7LKMasJ9soEpwFL0b9JyNkRjQ+vjVgpQ==", + "requires": { + "asn1.js": "^5.0.1", + "bn.js": "^4.11.8" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + } + } + }, "elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -3536,8 +3570,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -4089,8 +4122,7 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", @@ -4931,8 +4963,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sc-istanbul": { "version": "0.4.6", diff --git a/package.json b/package.json index 2a67941..147e555 100644 --- a/package.json +++ b/package.json @@ -19,5 +19,8 @@ "prettier-plugin-solidity": "^1.1.2", "solidity-coverage": "^0.8.2", "typechain": "^8.1.1" + }, + "dependencies": { + "ecdsa-secp256r1": "^1.3.3" } } diff --git a/sage/Webauthn_precompute.sage b/sage/Webauthn_precompute.sage new file mode 100644 index 0000000..9f87ff0 --- /dev/null +++ b/sage/Webauthn_precompute.sage @@ -0,0 +1,94 @@ +#//********************************************************************************************/ +#// ___ _ ___ _ _ _ _ +#// | __| _ ___ __| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__ +#// | _| '_/ -_|_-< ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \ +#// |_||_| \___/__/_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/ +#// |__/|_| +#///* Copyright (C) 2022 - Renaud Dubois - This file is part of FCL (Fresh CryptoLib) project */ +#///* License: This software is licensed under MIT License */ +#///* See LICENSE file at the root folder of the project. */ +#///* FILE: Webauthn_precompute.sage */ +#///* */ +#///* */ +#///* DESCRIPTION: precompute a 8 dimensional table for Shamir's trick from a public key +#///* +#//**************************************************************************************/ + + +def Init_Curve(curve_characteristic,curve_a, curve_b,Gx, Gy, curve_Order): + Fp=GF(curve_characteristic); #Initialize Prime field of Point + Fq=GF(curve_Order); #Initialize Prime field of scalars + Curve=EllipticCurve(Fp, [curve_a, curve_b]); #Initialize Elliptic curve + curve_Generator=Curve([Gx, Gy]); + + return [Curve,curve_Generator]; + +#//Curve secp256r1, aka p256 +#//curve prime field modulus +sec256p_p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; +#//short weierstrass first coefficient +sec256p_a =0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; +#//short weierstrass second coefficient +sec256p_b =0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; +#//generating point affine coordinates +sec256p_gx =0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; +sec256p_gy =0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; +#//curve order (number of points) +sec256p_n =0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + +#//Init +secp256r1, G = Init_Curve(sec256p_p, sec256p_a, sec256p_b, sec256p_gx, sec256p_gy, sec256p_n); + + +#//example public point from webauthn.js +Q=secp256r1([114874632398302156264159990279427641021947882640101801130664833947273521181002,32136952818958550240756825111900051564117520891182470183735244184006536587423]); + +def Precompute_Pubkey(Q, Curve): + Pow64_PQ=[ Q for i in range(0,16)]; + Prec=[ Curve(0) for i in range(0,256)]; + + + Pow64_PQ[0]=Curve([sec256p_gx, sec256p_gy]); + Pow64_PQ[4]=Q; + + for j in [1..3]: + Pow64_PQ[j]=2^64*Pow64_PQ[j-1]; + Pow64_PQ[j+4]=2^64*Pow64_PQ[j+3]; + + Prec[0]=Curve(0); + + for i in range(1,256): + Prec[i]=Curve(0); + for j in [0..7]: + if( (i&(1<