diff --git a/src/libraries/Base64.sol b/src/libraries/Base64.sol index 0552d04..5a5cc2b 100644 --- a/src/libraries/Base64.sol +++ b/src/libraries/Base64.sol @@ -69,11 +69,7 @@ library Base64 { mstore(afterPtr, 0x00) // Run over the input, 3 bytes at a time - for { - - } lt(dataPtr, endPtr) { - - } { + for {} lt(dataPtr, endPtr) {} { // Advance 3 bytes dataPtr := add(dataPtr, 3) let input := mload(dataPtr) @@ -109,12 +105,10 @@ library Base64 { mstore8(sub(resultPtr, 1), 0x3d) mstore8(sub(resultPtr, 2), 0x3d) } - case 2 { - mstore8(sub(resultPtr, 1), 0x3d) - } + case 2 { mstore8(sub(resultPtr, 1), 0x3d) } } } return result; } -} \ No newline at end of file +} diff --git a/src/libraries/Base64Url.sol b/src/libraries/Base64Url.sol new file mode 100644 index 0000000..7febb5b --- /dev/null +++ b/src/libraries/Base64Url.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +/** + * @dev Encode (without '=' padding) + * @author evmbrahmin, adapted from hiromin's Base64URL libraries + */ +library Base64Url { + /** + * @dev Base64Url Encoding Table + */ + string internal constant ENCODING_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + function encode(bytes memory data) internal pure returns (string memory) { + if (data.length == 0) return ""; + + // Load the table into memory + string memory table = ENCODING_TABLE; + + string memory result = new string(4 * ((data.length + 2) / 3)); + + // @solidity memory-safe-assembly + assembly { + let tablePtr := add(table, 1) + let resultPtr := add(result, 32) + + for { + let dataPtr := data + let endPtr := add(data, mload(data)) + } lt(dataPtr, endPtr) {} { + dataPtr := add(dataPtr, 3) + let input := mload(dataPtr) + + mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) + resultPtr := add(resultPtr, 1) + + mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) + resultPtr := add(resultPtr, 1) + + mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) + resultPtr := add(resultPtr, 1) + + mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) + resultPtr := add(resultPtr, 1) + } + + // Remove the padding adjustment logic + switch mod(mload(data), 3) + case 1 { + // Adjust for the last byte of data + resultPtr := sub(resultPtr, 2) + } + case 2 { + // Adjust for the last two bytes of data + resultPtr := sub(resultPtr, 1) + } + + // Set the correct length of the result string + mstore(result, sub(resultPtr, add(result, 32))) + } + + return result; + } +} diff --git a/src/libraries/FCL_ecdsa.sol b/src/libraries/FCL_ecdsa.sol index a993539..c8c5493 100644 --- a/src/libraries/FCL_ecdsa.sol +++ b/src/libraries/FCL_ecdsa.sol @@ -20,16 +20,13 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; - import {FCL_Elliptic_ZZ} from "./FCL_elliptic.sol"; - - library FCL_ecdsa { // Set parameters for curve sec256r1.public - //curve order (number of points) + //curve order (number of points) uint256 constant n = FCL_Elliptic_ZZ.n; - + /** * @dev ECDSA verification, given , signature, and public key. */ @@ -37,12 +34,11 @@ library FCL_ecdsa { /** * @dev ECDSA verification, given , signature, and public key, no calldata version */ - function ecdsa_verify(bytes32 message, uint256 r, uint256 s, uint256 Qx, uint256 Qy) internal view returns (bool){ - + function ecdsa_verify(bytes32 message, uint256 r, uint256 s, uint256 Qx, uint256 Qy) internal view returns (bool) { if (r == 0 || r >= FCL_Elliptic_ZZ.n || s == 0 || s >= FCL_Elliptic_ZZ.n) { return false; } - + if (!FCL_Elliptic_ZZ.ecAff_isOnCurve(Qx, Qy)) { return false; } @@ -55,33 +51,32 @@ library FCL_ecdsa { x1 = FCL_Elliptic_ZZ.ecZZ_mulmuladd_S_asm(Qx, Qy, scalar_u, scalar_v); - x1= addmod(x1, n-r,n ); - + x1 = addmod(x1, n - r, n); + return x1 == 0; } - function ec_recover_r1(uint256 h, uint256 v, uint256 r, uint256 s) internal view returns (address) - { - if (r == 0 || r >= FCL_Elliptic_ZZ.n || s == 0 || s >= FCL_Elliptic_ZZ.n) { + function ec_recover_r1(uint256 h, uint256 v, uint256 r, uint256 s) internal view returns (address) { + if (r == 0 || r >= FCL_Elliptic_ZZ.n || s == 0 || s >= FCL_Elliptic_ZZ.n) { return address(0); } - uint256 y=FCL_Elliptic_ZZ.ec_Decompress(r, v-27); - uint256 rinv=FCL_Elliptic_ZZ.FCL_nModInv(r); - uint256 u1=mulmod(FCL_Elliptic_ZZ.n-addmod(0,h,FCL_Elliptic_ZZ.n), rinv,FCL_Elliptic_ZZ.n);//-hr^-1 - uint256 u2=mulmod(s, rinv,FCL_Elliptic_ZZ.n);//sr^-1 + uint256 y = FCL_Elliptic_ZZ.ec_Decompress(r, v - 27); + uint256 rinv = FCL_Elliptic_ZZ.FCL_nModInv(r); + uint256 u1 = mulmod(FCL_Elliptic_ZZ.n - addmod(0, h, FCL_Elliptic_ZZ.n), rinv, FCL_Elliptic_ZZ.n); //-hr^-1 + uint256 u2 = mulmod(s, rinv, FCL_Elliptic_ZZ.n); //sr^-1 uint256 Qx; uint256 Qy; - (Qx,Qy)=FCL_Elliptic_ZZ.ecZZ_mulmuladd(r,y, u1, u2); + (Qx, Qy) = FCL_Elliptic_ZZ.ecZZ_mulmuladd(r, y, u1, u2); return address(uint160(uint256(keccak256(abi.encodePacked(Qx, Qy))))); } function ecdsa_precomputed_verify(bytes32 message, uint256 r, uint256 s, address Shamir8) - internal view + internal + view returns (bool) { - if (r == 0 || r >= n || s == 0 || s >= n) { return false; } @@ -97,13 +92,14 @@ library FCL_ecdsa { //Shamir 8 dimensions X = FCL_Elliptic_ZZ.ecZZ_mulmuladd_S8_extcode(mulmod(uint256(message), sInv, n), mulmod(r, sInv, n), Shamir8); - X= addmod(X, n-r,n ); + X = addmod(X, n - r, n); return X == 0; } //end ecdsa_precomputed_verify() - function ecdsa_precomputed_verify(bytes32 message, uint256[2] calldata rs, address Shamir8) - internal view + function ecdsa_precomputed_verify(bytes32 message, uint256[2] calldata rs, address Shamir8) + internal + view returns (bool) { uint256 r = rs[0]; @@ -123,9 +119,8 @@ library FCL_ecdsa { //Shamir 8 dimensions X = FCL_Elliptic_ZZ.ecZZ_mulmuladd_S8_extcode(mulmod(uint256(message), sInv, n), mulmod(r, sInv, n), Shamir8); - X= addmod(X, n-r,n ); + X = addmod(X, n - r, n); return X == 0; } //end ecdsa_precomputed_verify() - -} \ No newline at end of file +} diff --git a/src/libraries/FCL_elliptic.sol b/src/libraries/FCL_elliptic.sol index 737d68b..ec4cb45 100644 --- a/src/libraries/FCL_elliptic.sol +++ b/src/libraries/FCL_elliptic.sol @@ -44,10 +44,10 @@ library FCL_Elliptic_ZZ { uint256 constant minus_1 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; //P+1 div 4 - uint256 constant pp1div4=0x3fffffffc0000000400000000000000000000000400000000000000000000000; + uint256 constant pp1div4 = 0x3fffffffc0000000400000000000000000000000400000000000000000000000; //arbitrary constant to express no quadratic residuosity - uint256 constant _NOTSQUARE=0xFFFFFFFF00000002000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; - uint256 constant _NOTONCURVE=0xFFFFFFFF00000003000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + uint256 constant _NOTSQUARE = 0xFFFFFFFF00000002000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + uint256 constant _NOTONCURVE = 0xFFFFFFFF00000003000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; /** * /* inversion mod n via a^(n-2), use of precompiled using little Fermat theorem @@ -92,92 +92,102 @@ library FCL_Elliptic_ZZ { } //Coron projective shuffling, take as input alpha as blinding factor - function ecZZ_Coronize(uint256 alpha, uint256 x, uint256 y, uint256 zz, uint256 zzz) internal pure returns (uint256 x3, uint256 y3, uint256 zz3, uint256 zzz3) - { - - uint256 alpha2=mulmod(alpha,alpha,p); - - x3=mulmod(alpha2, x,p); //alpha^-2.x - y3=mulmod(mulmod(alpha, alpha2,p), y,p); - - zz3=mulmod(zz,alpha2,p);//alpha^2 zz - zzz3=mulmod(zzz,mulmod(alpha, alpha2,p),p);//alpha^3 zzz - + function ecZZ_Coronize(uint256 alpha, uint256 x, uint256 y, uint256 zz, uint256 zzz) + internal + pure + returns (uint256 x3, uint256 y3, uint256 zz3, uint256 zzz3) + { + uint256 alpha2 = mulmod(alpha, alpha, p); + + x3 = mulmod(alpha2, x, p); //alpha^-2.x + y3 = mulmod(mulmod(alpha, alpha2, p), y, p); + + zz3 = mulmod(zz, alpha2, p); //alpha^2 zz + zzz3 = mulmod(zzz, mulmod(alpha, alpha2, p), p); //alpha^3 zzz + return (x3, y3, zz3, zzz3); - } - - - function ecZZ_Add(uint256 x1, uint256 y1, uint256 zz1, uint256 zzz1, uint256 x2, uint256 y2, uint256 zz2, uint256 zzz2) internal pure returns (uint256 x3, uint256 y3, uint256 zz3, uint256 zzz3) - { - uint256 u1=mulmod(x1,zz2,p); // U1 = X1*ZZ2 - uint256 u2=mulmod(x2, zz1,p); // U2 = X2*ZZ1 - u2=addmod(u2, p-u1, p);// P = U2-U1 - x1=mulmod(u2, u2, p);//PP - x2=mulmod(x1, u2, p);//PPP - - zz3=mulmod(x1, mulmod(zz1, zz2, p),p);//ZZ3 = ZZ1*ZZ2*PP - zzz3=mulmod(zzz1, mulmod(zzz2, x2, p),p);//ZZZ3 = ZZZ1*ZZZ2*PPP - - zz1=mulmod(y1, zzz2,p); // S1 = Y1*ZZZ2 - zz2=mulmod(y2, zzz1, p); // S2 = Y2*ZZZ1 - zz2=addmod(zz2, p-zz1, p);//R = S2-S1 - zzz1=mulmod(u1, x1,p); //Q = U1*PP - x3= addmod(addmod(mulmod(zz2, zz2, p), p-x2,p), mulmod(minus_2, zzz1,p),p); //X3 = R2-PPP-2*Q - y3=addmod( mulmod(zz2, addmod(zzz1, p-x3, p),p), p-mulmod(zz1, x2, p),p);//R*(Q-X3)-S1*PPP - - return (x3, y3, zz3, zzz3); - } - -/// @notice Calculate one modular square root of a given integer. Assume that p=3 mod 4. -/// @dev Uses the ModExp precompiled contract at address 0x05 for fast computation using little Fermat theorem -/// @param self The integer of which to find the modular inverse -/// @return result The modular inverse of the input integer. If the modular inverse doesn't exist, it revert the tx - -function SqrtMod(uint256 self) internal view returns (uint256 result){ - assembly ("memory-safe") { - // load the free memory pointer value - let pointer := mload(0x40) - - // Define length of base (Bsize) - mstore(pointer, 0x20) - // Define the exponent size (Esize) - mstore(add(pointer, 0x20), 0x20) - // Define the modulus size (Msize) - mstore(add(pointer, 0x40), 0x20) - // Define variables base (B) - mstore(add(pointer, 0x60), self) - // Define the exponent (E) - mstore(add(pointer, 0x80), pp1div4) - // We save the point of the last argument, it will be override by the result - // of the precompile call in order to avoid paying for the memory expansion properly - let _result := add(pointer, 0xa0) - // Define the modulus (M) - mstore(_result, p) - - // Call the precompiled ModExp (0x05) https://www.evm.codes/precompiled#0x05 - if iszero( - staticcall( - not(0), // amount of gas to send - MODEXP_PRECOMPILE, // target - pointer, // argsOffset - 0xc0, // argsSize (6 * 32 bytes) - _result, // retOffset (we override M to avoid paying for the memory expansion) - 0x20 // retSize (32 bytes) - ) - ) { revert(0, 0) } - - result := mload(_result) -// result :=addmod(result,0,p) - } - if(mulmod(result,result,p)!=self){ - result=_NOTSQUARE; - } - - return result; -} + } + + function ecZZ_Add( + uint256 x1, + uint256 y1, + uint256 zz1, + uint256 zzz1, + uint256 x2, + uint256 y2, + uint256 zz2, + uint256 zzz2 + ) internal pure returns (uint256 x3, uint256 y3, uint256 zz3, uint256 zzz3) { + uint256 u1 = mulmod(x1, zz2, p); // U1 = X1*ZZ2 + uint256 u2 = mulmod(x2, zz1, p); // U2 = X2*ZZ1 + u2 = addmod(u2, p - u1, p); // P = U2-U1 + x1 = mulmod(u2, u2, p); //PP + x2 = mulmod(x1, u2, p); //PPP + + zz3 = mulmod(x1, mulmod(zz1, zz2, p), p); //ZZ3 = ZZ1*ZZ2*PP + zzz3 = mulmod(zzz1, mulmod(zzz2, x2, p), p); //ZZZ3 = ZZZ1*ZZZ2*PPP + + zz1 = mulmod(y1, zzz2, p); // S1 = Y1*ZZZ2 + zz2 = mulmod(y2, zzz1, p); // S2 = Y2*ZZZ1 + zz2 = addmod(zz2, p - zz1, p); //R = S2-S1 + zzz1 = mulmod(u1, x1, p); //Q = U1*PP + x3 = addmod(addmod(mulmod(zz2, zz2, p), p - x2, p), mulmod(minus_2, zzz1, p), p); //X3 = R2-PPP-2*Q + y3 = addmod(mulmod(zz2, addmod(zzz1, p - x3, p), p), p - mulmod(zz1, x2, p), p); //R*(Q-X3)-S1*PPP + + return (x3, y3, zz3, zzz3); + } + + /// @notice Calculate one modular square root of a given integer. Assume that p=3 mod 4. + /// @dev Uses the ModExp precompiled contract at address 0x05 for fast computation using little Fermat theorem + /// @param self The integer of which to find the modular inverse + /// @return result The modular inverse of the input integer. If the modular inverse doesn't exist, it revert the tx + + function SqrtMod(uint256 self) internal view returns (uint256 result) { + assembly ("memory-safe") { + // load the free memory pointer value + let pointer := mload(0x40) + + // Define length of base (Bsize) + mstore(pointer, 0x20) + // Define the exponent size (Esize) + mstore(add(pointer, 0x20), 0x20) + // Define the modulus size (Msize) + mstore(add(pointer, 0x40), 0x20) + // Define variables base (B) + mstore(add(pointer, 0x60), self) + // Define the exponent (E) + mstore(add(pointer, 0x80), pp1div4) + // We save the point of the last argument, it will be override by the result + // of the precompile call in order to avoid paying for the memory expansion properly + let _result := add(pointer, 0xa0) + // Define the modulus (M) + mstore(_result, p) + + // Call the precompiled ModExp (0x05) https://www.evm.codes/precompiled#0x05 + if iszero( + staticcall( + not(0), // amount of gas to send + MODEXP_PRECOMPILE, // target + pointer, // argsOffset + 0xc0, // argsSize (6 * 32 bytes) + _result, // retOffset (we override M to avoid paying for the memory expansion) + 0x20 // retSize (32 bytes) + ) + ) { revert(0, 0) } + + result := mload(_result) + // result :=addmod(result,0,p) + } + if (mulmod(result, result, p) != self) { + result = _NOTSQUARE; + } + + return result; + } /** * /* @dev Convert from affine rep to XYZZ rep */ + function ecAff_SetZZ(uint256 x0, uint256 y0) internal pure returns (uint256[4] memory P) { unchecked { P[2] = 1; //ZZ @@ -187,17 +197,16 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ } } - function ec_Decompress(uint256 x, uint256 parity) internal view returns(uint256 y){ + function ec_Decompress(uint256 x, uint256 parity) internal view returns (uint256 y) { + uint256 y2 = mulmod(x, mulmod(x, x, p), p); //x3 + y2 = addmod(b, addmod(y2, mulmod(x, a, p), p), p); //x3+ax+b - uint256 y2=mulmod(x,mulmod(x,x,p),p);//x3 - y2=addmod(b,addmod(y2,mulmod(x,a,p),p),p);//x3+ax+b - - y=SqrtMod(y2); - if(y==_NOTSQUARE){ - return _NOTONCURVE; + y = SqrtMod(y2); + if (y == _NOTSQUARE) { + return _NOTONCURVE; } - if((y&1)!=(parity&1)){ - y=p-y; + if ((y & 1) != (parity & 1)) { + y = p - y; } } @@ -205,7 +214,11 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ * /* @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(uint256 x, uint256 y, uint256 zz, uint256 zzz) internal view returns (uint256 x1, uint256 y1) { + function ecZZ_SetAff(uint256 x, uint256 y, uint256 zz, uint256 zzz) + internal + view + returns (uint256 x1, uint256 y1) + { uint256 zzzInv = FCL_pModInv(zzz); //1/zzz y1 = mulmod(y, zzzInv, p); //Y/zzz uint256 _b = mulmod(zz, zzzInv, p); //1/z @@ -328,10 +341,9 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ if (ecAff_IsZero(x0, y0)) return (x1, y1); if (ecAff_IsZero(x1, y1)) return (x0, y0); - if((x0==x1)&&(y0==y1)) { - (x0, y0, zz0, zzz0) = ecZZ_Dbl(x0, y0,1,1); - } - else{ + if ((x0 == x1) && (y0 == y1)) { + (x0, y0, zz0, zzz0) = ecZZ_Dbl(x0, y0, 1, 1); + } else { (x0, y0, zz0, zzz0) = ecZZ_AddN(x0, y0, 1, 1, x1, y1); } @@ -340,8 +352,9 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ /** * @dev Computation of uG+vQ using Strauss-Shamir's trick, G basepoint, Q public key - * Returns only x for ECDSA use - * */ + * Returns only x for ECDSA use + * + */ function ecZZ_mulmuladd_S_asm( uint256 Q0, uint256 Q1, //affine rep for input point Q @@ -358,11 +371,12 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ unchecked { if (scalar_u == 0 && scalar_v == 0) return 0; - (H0, H1) = ecAff_add(gx, gy, Q0, Q1); - if((H0==0)&&(H1==0))//handling Q=-G - { - scalar_u=addmod(scalar_u, n-scalar_v, n); - scalar_v=0; + (H0, H1) = ecAff_add(gx, gy, Q0, Q1); + if ( + (H0 == 0) && (H1 == 0) //handling Q=-G + ) { + scalar_u = addmod(scalar_u, n - scalar_v, n); + scalar_v = 0; if (scalar_u == 0 && scalar_v == 0) return 0; } assembly { @@ -499,11 +513,11 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ return X; } - /** * @dev Computation of uG+vQ using Strauss-Shamir's trick, G basepoint, Q public key - * Returns affine representation of point (normalized) - * */ + * Returns affine representation of point (normalized) + * + */ function ecZZ_mulmuladd( uint256 Q0, uint256 Q1, //affine rep for input point Q @@ -515,9 +529,9 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ uint256 index = 255; uint256[6] memory T; uint256[2] memory H; - + unchecked { - if (scalar_u == 0 && scalar_v == 0) return (0,0); + if (scalar_u == 0 && scalar_v == 0) return (0, 0); (H[0], H[1]) = ecAff_add(gx, gy, Q0, Q1); //will not work if Q=P, obvious forbidden private key @@ -537,7 +551,7 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ Y := Q1 } if eq(zz, 3) { - Y := mload(add(H,32)) + Y := mload(add(H, 32)) X := mload(H) } @@ -578,7 +592,7 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ } if eq(T4, 3) { T1 := mload(H) - T2 := mload(add(H,32)) + T2 := mload(add(H, 32)) } if iszero(zz) { X := T1 @@ -644,14 +658,14 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ // Call the precompiled contract 0x05 = ModExp if iszero(staticcall(not(0), 0x05, T, 0xc0, T, 0x20)) { revert(0, 0) } - Y:=mulmod(Y,mload(T),p)//Y/zzz - zz :=mulmod(zz, mload(T),p) //1/z - zz:= mulmod(zz,zz,p) //1/zz + Y := mulmod(Y, mload(T), p) //Y/zzz + zz := mulmod(zz, mload(T), p) //1/z + zz := mulmod(zz, zz, p) //1/zz X := mulmod(X, zz, p) //X/zz } //end assembly } //end unchecked - return (X,Y); + return (X, Y); } //8 dimensions Shamir's trick, using precomputations stored in Shamir8, stored as Bytecode of an external @@ -659,7 +673,8 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ //(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(uint256 scalar_u, uint256 scalar_v, address dataPointer) - internal view + internal + view returns (uint256 X /*, uint Y*/ ) { unchecked { @@ -800,11 +815,10 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ } //end unchecked } - - // improving the extcodecopy trick : append array at end of contract function ecZZ_mulmuladd_S8_hackmem(uint256 scalar_u, uint256 scalar_v, uint256 dataPointer) - internal view + internal + view returns (uint256 X /*, uint Y*/ ) { uint256 zz; // third and coordinates of the point @@ -905,7 +919,6 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ } //end unchecked } - /** * @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 @@ -919,7 +932,8 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ */ function ecdsa_precomputed_hackmem(bytes32 message, uint256[2] calldata rs, uint256 endcontract) - internal view + internal + view returns (bool) { uint256 r = rs[0]; @@ -943,7 +957,4 @@ function SqrtMod(uint256 self) internal view returns (uint256 result){ } return X == 0; } //end ecdsa_precomputed_verify() - - - -} //EOF \ No newline at end of file +} diff --git a/src/libraries/LibString.sol b/src/libraries/LibString.sol index 6805a12..e8cf27f 100644 --- a/src/libraries/LibString.sol +++ b/src/libraries/LibString.sol @@ -147,11 +147,7 @@ library LibString { /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, /// giving a total length of `length * 2` bytes. /// Reverts if `length` is too small for the output to contain all the digits. - function toHexStringNoPrefix(uint256 value, uint256 length) - internal - pure - returns (string memory str) - { + function toHexStringNoPrefix(uint256 value, uint256 length) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes @@ -626,11 +622,7 @@ library LibString { /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from left to right. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. - function indexOf(string memory subject, string memory search) - internal - pure - returns (uint256 result) - { + function indexOf(string memory subject, string memory search) internal pure returns (uint256 result) { result = indexOf(subject, search, 0); } @@ -674,11 +666,7 @@ library LibString { /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from right to left. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. - function lastIndexOf(string memory subject, string memory search) - internal - pure - returns (uint256 result) - { + function lastIndexOf(string memory subject, string memory search) internal pure returns (uint256 result) { result = lastIndexOf(subject, search, uint256(int256(-1))); } @@ -688,11 +676,7 @@ library LibString { } /// @dev Returns whether `subject` starts with `search`. - function startsWith(string memory subject, string memory search) - internal - pure - returns (bool result) - { + function startsWith(string memory subject, string memory search) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let searchLength := mload(search) @@ -709,11 +693,7 @@ library LibString { } /// @dev Returns whether `subject` ends with `search`. - function endsWith(string memory subject, string memory search) - internal - pure - returns (bool result) - { + function endsWith(string memory subject, string memory search) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let searchLength := mload(search) @@ -737,11 +717,7 @@ library LibString { } /// @dev Returns `subject` repeated `times`. - function repeat(string memory subject, uint256 times) - internal - pure - returns (string memory result) - { + function repeat(string memory subject, uint256 times) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) @@ -771,11 +747,7 @@ library LibString { /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). /// `start` and `end` are byte offsets. - function slice(string memory subject, uint256 start, uint256 end) - internal - pure - returns (string memory result) - { + function slice(string memory subject, uint256 start, uint256 end) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) @@ -804,21 +776,13 @@ library LibString { /// @dev Returns a copy of `subject` sliced from `start` to the end of the string. /// `start` is a byte offset. - function slice(string memory subject, uint256 start) - internal - pure - returns (string memory result) - { + function slice(string memory subject, uint256 start) internal pure returns (string memory result) { result = slice(subject, start, uint256(int256(-1))); } /// @dev Returns all the indices of `search` in `subject`. /// The indices are byte offsets. - function indicesOf(string memory subject, string memory search) - internal - pure - returns (uint256[] memory result) - { + function indicesOf(string memory subject, string memory search) internal pure returns (uint256[] memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) @@ -873,11 +837,7 @@ library LibString { } /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string. - function split(string memory subject, string memory delimiter) - internal - pure - returns (string[] memory result) - { + function split(string memory subject, string memory delimiter) internal pure returns (string[] memory result) { uint256[] memory indices = indicesOf(subject, delimiter); /// @solidity memory-safe-assembly assembly { @@ -922,11 +882,7 @@ library LibString { /// @dev Returns a concatenated string of `a` and `b`. /// Cheaper than `string.concat()` and does not de-align the free memory pointer. - function concat(string memory a, string memory b) - internal - pure - returns (string memory result) - { + function concat(string memory a, string memory b) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let w := not(0x1f) @@ -960,11 +916,7 @@ library LibString { /// @dev Returns a copy of the string in either lowercase or UPPERCASE. /// WARNING! This function is only compatible with 7-bit ASCII strings. - function toCase(string memory subject, bool toUpper) - internal - pure - returns (string memory result) - { + function toCase(string memory subject, bool toUpper) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let length := mload(subject) @@ -1075,11 +1027,7 @@ library LibString { /// @dev Escapes the string to be used within double-quotes in a JSON. /// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes. - function escapeJSON(string memory s, bool addDoubleQuotes) - internal - pure - returns (string memory result) - { + function escapeJSON(string memory s, bool addDoubleQuotes) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let end := add(s, mload(s)) @@ -1213,10 +1161,7 @@ library LibString { result := mul( // Load the length and the bytes of `a` and `b`. - or( - shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))), - mload(sub(add(b, 0x1e), aLength)) - ), + or(shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))), mload(sub(add(b, 0x1e), aLength))), // `totalLength != 0 && totalLength < 31`. Abuses underflow. // Assumes that the lengths are valid and within the block gas limit. lt(sub(add(aLength, mload(b)), 1), 0x1e) @@ -1227,11 +1172,7 @@ library LibString { /// @dev Unpacks strings packed using {packTwo}. /// Returns the empty strings if `packed` is `bytes32(0)`. /// If `packed` is not an output of {packTwo}, the output behavior is undefined. - function unpackTwo(bytes32 packed) - internal - pure - returns (string memory resultA, string memory resultB) - { + function unpackTwo(bytes32 packed) internal pure returns (string memory resultA, string memory resultB) { /// @solidity memory-safe-assembly assembly { // Grab the free memory pointer. @@ -1266,4 +1207,4 @@ library LibString { return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize))) } } -} \ No newline at end of file +} diff --git a/src/libraries/Secp256r1.sol b/src/libraries/Secp256r1.sol new file mode 100644 index 0000000..ab7dfa2 --- /dev/null +++ b/src/libraries/Secp256r1.sol @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0; +// +// Heavily inspired from +// https://github.com/maxrobot/elliptic-solidity/blob/master/contracts/Secp256r1.sol +// https://github.com/tdrerup/elliptic-curve-solidity/blob/master/contracts/curves/EllipticCurve.sol +// modified to use precompile 0x05 modexp +// and modified jacobian double +// optimisations to avoid to an from from affine and jacobian coordinates +// + +struct PassKeyId { + uint256 pubKeyX; + uint256 pubKeyY; + string keyId; +} + +struct JPoint { + uint256 x; + uint256 y; + uint256 z; +} + +library Secp256r1 { + uint256 constant gx = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; + uint256 constant gy = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; + uint256 public constant pp = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + + uint256 public constant nn = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + uint256 constant a = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; + uint256 constant b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; + uint256 constant MOST_SIGNIFICANT = 0xc000000000000000000000000000000000000000000000000000000000000000; + + /* + * Verify + * @description - verifies that a public key has signed a given message + * @param X - public key coordinate X + * @param Y - public key coordinate Y + * @param R - signature half R + * @param S - signature half S + * @param input - hashed message + */ + function Verify(PassKeyId memory passKey, uint256 r, uint256 s, uint256 e) internal view returns (bool) { + if (r == 0 || s == 0 || r >= nn || s >= nn) { + /* testing null signature, otherwise (0,0) is valid for any message*/ + return false; + } + + JPoint[16] memory points = _preComputeJacobianPoints(passKey); + return VerifyWithPrecompute(points, r, s, e); + } + + function VerifyWithPrecompute(JPoint[16] memory points, uint256 r, uint256 s, uint256 e) + internal + view + returns (bool) + { + if (r >= nn || s >= nn) { + return false; + } + + uint256 w = _primemod(s, nn); + + uint256 u1 = mulmod(e, w, nn); + uint256 u2 = mulmod(r, w, nn); + + uint256 x; + uint256 y; + + (x, y) = ShamirMultJacobian(points, u1, u2); + return (x == r); + } + + /* + * Strauss Shamir trick for EC multiplication + * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method + * we optimise on this a bit to do with 2 bits at a time rather than a single bit + * the individual points for a single pass are precomputed + * overall this reduces the number of additions while keeping the same number of doublings + */ + function ShamirMultJacobian(JPoint[16] memory points, uint256 u1, uint256 u2) + internal + view + returns (uint256, uint256) + { + uint256 x = 0; + uint256 y = 0; + uint256 z = 0; + uint256 bits = 128; + uint256 index = 0; + + while (bits > 0) { + if (z > 0) { + (x, y, z) = _modifiedJacobianDouble(x, y, z); + (x, y, z) = _modifiedJacobianDouble(x, y, z); + } + index = ((u1 & MOST_SIGNIFICANT) >> 252) | ((u2 & MOST_SIGNIFICANT) >> 254); + if (index > 0) { + (x, y, z) = _jAdd(x, y, z, points[index].x, points[index].y, points[index].z); + } + u1 <<= 2; + u2 <<= 2; + bits--; + } + (x, y) = _affineFromJacobian(x, y, z); + return (x, y); + } + + function _preComputeJacobianPoints(PassKeyId memory passKey) internal pure returns (JPoint[16] memory points) { + // JPoint[] memory u1Points = new JPoint[](4); + // u1Points[0] = JPoint(0, 0, 0); + // u1Points[1] = JPoint(gx, gy, 1); // u1 + // u1Points[2] = _jPointDouble(u1Points[1]); + // u1Points[3] = _jPointAdd(u1Points[1], u1Points[2]); + // avoiding this intermediate step by using it in a single array below + // these are pre computed points for u1 + + // JPoint[16] memory points; + points[0] = JPoint(0, 0, 0); + points[1] = JPoint(passKey.pubKeyX, passKey.pubKeyY, 1); // u2 + points[2] = _jPointDouble(points[1]); + points[3] = _jPointAdd(points[1], points[2]); + + points[4] = JPoint(gx, gy, 1); // u1Points[1] + points[5] = _jPointAdd(points[4], points[1]); + points[6] = _jPointAdd(points[4], points[2]); + points[7] = _jPointAdd(points[4], points[3]); + + points[8] = _jPointDouble(points[4]); // u1Points[2] + points[9] = _jPointAdd(points[8], points[1]); + points[10] = _jPointAdd(points[8], points[2]); + points[11] = _jPointAdd(points[8], points[3]); + + points[12] = _jPointAdd(points[4], points[8]); // u1Points[3] + points[13] = _jPointAdd(points[12], points[1]); + points[14] = _jPointAdd(points[12], points[2]); + points[15] = _jPointAdd(points[12], points[3]); + } + + function _jPointAdd(JPoint memory p1, JPoint memory p2) internal pure returns (JPoint memory) { + uint256 x; + uint256 y; + uint256 z; + (x, y, z) = _jAdd(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z); + return JPoint(x, y, z); + } + + function _jPointDouble(JPoint memory p) internal pure returns (JPoint memory) { + uint256 x; + uint256 y; + uint256 z; + (x, y, z) = _modifiedJacobianDouble(p.x, p.y, p.z); + return JPoint(x, y, z); + } + + /* _affineFromJacobian + * @desription returns affine coordinates from a jacobian input follows + * golang elliptic/crypto library + */ + function _affineFromJacobian(uint256 x, uint256 y, uint256 z) internal view returns (uint256 ax, uint256 ay) { + if (z == 0) { + return (0, 0); + } + + uint256 zinv = _primemod(z, pp); + uint256 zinvsq = mulmod(zinv, zinv, pp); + + ax = mulmod(x, zinvsq, pp); + ay = mulmod(y, mulmod(zinvsq, zinv, pp), pp); + } + /* + * _jAdd + * @description performs double Jacobian as defined below: + * https://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-3/doubling/mdbl-2007-bl.op3 + */ + + function _jAdd(uint256 p1, uint256 p2, uint256 p3, uint256 q1, uint256 q2, uint256 q3) + internal + pure + returns (uint256 r1, uint256 r2, uint256 r3) + { + if (p3 == 0) { + r1 = q1; + r2 = q2; + r3 = q3; + + return (r1, r2, r3); + } else if (q3 == 0) { + r1 = p1; + r2 = p2; + r3 = p3; + + return (r1, r2, r3); + } + + assembly { + let pd := 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF + let z1z1 := mulmod(p3, p3, pd) // Z1Z1 = Z1^2 + let z2z2 := mulmod(q3, q3, pd) // Z2Z2 = Z2^2 + + let u1 := mulmod(p1, z2z2, pd) // U1 = X1*Z2Z2 + let u2 := mulmod(q1, z1z1, pd) // U2 = X2*Z1Z1 + + let s1 := mulmod(p2, mulmod(z2z2, q3, pd), pd) // S1 = Y1*Z2*Z2Z2 + let s2 := mulmod(q2, mulmod(z1z1, p3, pd), pd) // S2 = Y2*Z1*Z1Z1 + + let p3q3 := addmod(p3, q3, pd) + + if lt(u2, u1) { u2 := add(pd, u2) } // u2 = u2+pd + + let h := sub(u2, u1) // H = U2-U1 + + let i := mulmod(0x02, h, pd) + i := mulmod(i, i, pd) // I = (2*H)^2 + + let j := mulmod(h, i, pd) // J = H*I + if lt(s2, s1) { s2 := add(pd, s2) } // u2 = u2+pd + + let rr := mulmod(0x02, sub(s2, s1), pd) // r = 2*(S2-S1) + r1 := mulmod(rr, rr, pd) // X3 = R^2 + + let v := mulmod(u1, i, pd) // V = U1*I + let j2v := addmod(j, mulmod(0x02, v, pd), pd) + if lt(r1, j2v) { r1 := add(pd, r1) } // X3 = X3+pd + + r1 := sub(r1, j2v) + + // Y3 = r*(V-X3)-2*S1*J + let s12j := mulmod(mulmod(0x02, s1, pd), j, pd) + + if lt(v, r1) { v := add(pd, v) } + r2 := mulmod(rr, sub(v, r1), pd) + + if lt(r2, s12j) { r2 := add(pd, r2) } + r2 := sub(r2, s12j) + + // Z3 = ((Z1+Z2)^2-Z1Z1-Z2Z2)*H + z1z1 := addmod(z1z1, z2z2, pd) + j2v := mulmod(p3q3, p3q3, pd) + if lt(j2v, z1z1) { j2v := add(pd, j2v) } + r3 := mulmod(sub(j2v, z1z1), h, pd) + } + return (r1, r2, r3); + } + + // Point doubling on the modified jacobian coordinates + // http://point-at-infinity.org/ecc/Prime_Curve_Modified_Jacobian_Coordinates.html + function _modifiedJacobianDouble(uint256 x, uint256 y, uint256 z) + internal + pure + returns (uint256 x3, uint256 y3, uint256 z3) + { + assembly { + let pd := 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF + let z2 := mulmod(z, z, pd) + let az4 := + mulmod(0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC, mulmod(z2, z2, pd), pd) + let y2 := mulmod(y, y, pd) + let s := mulmod(0x04, mulmod(x, y2, pd), pd) + let u := mulmod(0x08, mulmod(y2, y2, pd), pd) + let m := addmod(mulmod(0x03, mulmod(x, x, pd), pd), az4, pd) + let twos := mulmod(0x02, s, pd) + let m2 := mulmod(m, m, pd) + if lt(m2, twos) { m2 := add(pd, m2) } + x3 := sub(m2, twos) + if lt(s, x3) { s := add(pd, s) } + y3 := mulmod(m, sub(s, x3), pd) + if lt(y3, u) { y3 := add(pd, y3) } + y3 := sub(y3, u) + z3 := mulmod(0x02, mulmod(y, z, pd), pd) + } + } + + // Fermats little theorem https://en.wikipedia.org/wiki/Fermat%27s_little_theorem + // a^(p-1) = 1 mod p + // a^(-1) ≅ a^(p-2) (mod p) + // we then use the precompile bigModExp to compute a^(-1) + function _primemod(uint256 value, uint256 p) internal view returns (uint256 ret) { + ret = modexp(value, p - 2, p); + return ret; + } + + // Wrapper for built-in BigNumber_modexp (contract 0x5) as described here. https://github.com/ethereum/EIPs/pull/198 + function modexp(uint256 _base, uint256 _exp, uint256 _mod) internal view returns (uint256 ret) { + // bigModExp(_base, _exp, _mod); + assembly { + if gt(_base, _mod) { _base := mod(_base, _mod) } + // Free memory pointer is always stored at 0x40 + let freemem := mload(0x40) + + mstore(freemem, 0x20) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + + mstore(add(freemem, 0x60), _base) + mstore(add(freemem, 0x80), _exp) + mstore(add(freemem, 0xa0), _mod) + + let success := staticcall(not(0), 0x5, freemem, 0xc0, freemem, 0x20) + switch success + case 0 { revert(0x0, 0x0) } + default { ret := mload(freemem) } + } + } +} diff --git a/src/libraries/WebAuthn.sol b/src/libraries/WebAuthn.sol index 7a5a113..6a59b01 100644 --- a/src/libraries/WebAuthn.sol +++ b/src/libraries/WebAuthn.sol @@ -121,8 +121,9 @@ library WebAuthn { // 12. Verify that the value of C.challenge equals the base64url encoding of options.challenge. bytes memory expectedChallenge = bytes(string.concat('"challenge":"', Base64.encodeURL(challenge), '"')); - string memory actualChallenge = - webAuthnAuth.clientDataJSON.slice(webAuthnAuth.challengeIndex, webAuthnAuth.challengeIndex + expectedChallenge.length); + string memory actualChallenge = webAuthnAuth.clientDataJSON.slice( + webAuthnAuth.challengeIndex, webAuthnAuth.challengeIndex + expectedChallenge.length + ); if (keccak256(bytes(actualChallenge)) != keccak256(expectedChallenge)) { return false; } diff --git a/src/modules/Passkey.sol b/src/modules/Passkey.sol index 866d4d3..ccb8237 100644 --- a/src/modules/Passkey.sol +++ b/src/modules/Passkey.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "../interfaces/IModule.sol"; import "../interfaces/IWallet.sol"; +import "../libraries/Base64Url.sol"; import {WebAuthn} from "../libraries/WebAuthn.sol"; @@ -13,8 +14,8 @@ contract PasskeyModuleFactory { } contract PasskeyModule is IModule { - uint256 immutable public x; - uint256 immutable public y; + uint256 public immutable x; + uint256 public immutable y; constructor(uint256 inputX, uint256 inputY) { x = inputX; @@ -41,14 +42,27 @@ contract PasskeyModule is IModule { function callback(UserOperation calldata, bytes32) external override {} - function isValidSignature(bytes32 digest, bytes memory signature) public view override returns (bytes4 magicValue) { - WebAuthn.WebAuthnAuth memory auth = abi.decode(signature, (WebAuthn.WebAuthnAuth)); + function isValidSignature(bytes32 digest, bytes memory signature) + public + view + override + returns (bytes4 magicValue) + { + (bytes memory a, string memory b, uint256 c, uint256 d, uint256 e, uint256 f) = abi.decode(signature, (bytes, string, uint256, uint256, uint256, uint256)); + + WebAuthn.WebAuthnAuth memory auth = WebAuthn.WebAuthnAuth({ + authenticatorData: a, + clientDataJSON: b, + challengeIndex: c, + typeIndex: d, + r: e, + s: f + }); if (WebAuthn.verify({challenge: abi.encode(digest), requireUV: false, webAuthnAuth: auth, x: x, y: y})) { return this.isValidSignature.selector; } else { return 0x0000; } - } -} \ No newline at end of file +} diff --git a/src/modules/Recovery.sol b/src/modules/Recovery.sol index 0a7fed1..15ebfec 100644 --- a/src/modules/Recovery.sol +++ b/src/modules/Recovery.sol @@ -127,7 +127,7 @@ contract RecoveryModule is IModule { return 0; } - function callback(UserOperation calldata userOp, bytes32) external onlyWallet override { + function callback(UserOperation calldata userOp, bytes32) external override onlyWallet { uint256 ticketNum = _decodeTicketNum(userOp.signature); delete _tickets[ticketNum]; } diff --git a/test/PasskeyModule.t.sol b/test/PasskeyModule.t.sol new file mode 100644 index 0000000..09fdad6 --- /dev/null +++ b/test/PasskeyModule.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.4; + +import "../src/modules/Passkey.sol"; +import "../src/libraries/WebAuthn.sol"; +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +contract PasskeyModuleTest is Test { + PasskeyModule passkeyModule; + PasskeyModuleFactory passkeyModuleFactory; + + function setUp() external { + passkeyModuleFactory = new PasskeyModuleFactory(); + } + + function testPassKey() external { + passkeyModule = passkeyModuleFactory.create( + 28203248099655634232680422976510411012986437076966613883671554831358983509938, + 79473938854726638551736530376995476499049493858003728502280535141260854783821, + bytes32(0) + ); + bytes memory signature = + hex"00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001eb6f6689e7b96f60dcae3542888f9d094a9abb8e04bd391104d6ee79a9f0967d3bc293dc51c51b23f9063ae81bb2e4a99b520f5f04cda804f0dd80b4c8d7f353000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000867b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22644c4836595a6f78345332616a6f4e4a384c374d47734657436c75584c6258514a62306e466c7377685930222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a33303030222c2263726f73734f726967696e223a66616c73657d0000000000000000000000000000000000000000000000000000"; + bytes4 returnValue = passkeyModule.isValidSignature( + 0x74b1fa619a31e12d9a8e8349f0becc1ac1560a5b972db5d025bd27165b30858d, signature + ); + + console.log(returnValue == passkeyModule.isValidSignature.selector); + } +}