From ecaf8e5fd8a68af2ce9fa0c41bacd40af2c28b1e Mon Sep 17 00:00:00 2001 From: imduchuyyy Date: Thu, 20 Jun 2024 21:42:23 +0700 Subject: [PATCH 1/5] base passkey --- src/modules/Passkey.sol | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/modules/Passkey.sol diff --git a/src/modules/Passkey.sol b/src/modules/Passkey.sol new file mode 100644 index 0000000..0250c7e --- /dev/null +++ b/src/modules/Passkey.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache +pragma solidity ^0.8.0; + +import "../interfaces/IModule.sol"; +import "../interfaces/IWallet.sol"; + +contract PasskeyModuleFactory { + function create(bytes32 salt) external returns (PasskeyModule) { + return new PasskeyModule{salt: salt}(); + } +} + +contract PasskeyModule is IModule { + constructor() { + } + + function validateUserOp(UserOperation calldata userOp, bytes32) + external + view + override + returns (uint256 validationData) + { + return 0; + } + + function callback(UserOperation calldata userOp, bytes32) external override { + + } + + function isValidSignature(bytes32, bytes calldata) public view override returns (bytes4 magicValue) { + return 0x0000; + } +} \ No newline at end of file From 6d0c20fc6db994fa652c804010d2f89b2af344c1 Mon Sep 17 00:00:00 2001 From: imduchuyyy Date: Thu, 20 Jun 2024 21:46:36 +0700 Subject: [PATCH 2/5] base passkey --- src/modules/Passkey.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/Passkey.sol b/src/modules/Passkey.sol index 0250c7e..92a97b8 100644 --- a/src/modules/Passkey.sol +++ b/src/modules/Passkey.sol @@ -20,6 +20,8 @@ contract PasskeyModule is IModule { override returns (uint256 validationData) { + address module = address(bytes20(userOp.signature[:20])); + require(module == address(this), "invalid module"); return 0; } @@ -27,7 +29,7 @@ contract PasskeyModule is IModule { } - function isValidSignature(bytes32, bytes calldata) public view override returns (bytes4 magicValue) { + function isValidSignature(bytes32 digest, bytes calldata signature) public view override returns (bytes4 magicValue) { return 0x0000; } } \ No newline at end of file From c22ea89861b093b662145c4855dcbe430c328b54 Mon Sep 17 00:00:00 2001 From: imduchuyyy Date: Thu, 20 Jun 2024 22:03:48 +0700 Subject: [PATCH 3/5] add webauthn --- .gitmodules | 3 +++ lib/webauthn-sol | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/webauthn-sol diff --git a/.gitmodules b/.gitmodules index d3e0dcf..c941db5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/webauthn-sol"] + path = lib/webauthn-sol + url = https://github.com/base-org/webauthn-sol diff --git a/lib/webauthn-sol b/lib/webauthn-sol new file mode 160000 index 0000000..619f20a --- /dev/null +++ b/lib/webauthn-sol @@ -0,0 +1 @@ +Subproject commit 619f20ab0f074fef41066ee4ab24849a913263b2 From 2c28e2eb2749b29feae35600b38706ed56c64ace Mon Sep 17 00:00:00 2001 From: imduchuyyy Date: Thu, 20 Jun 2024 22:09:58 +0700 Subject: [PATCH 4/5] add webauthn --- remappings.txt | 1 + src/modules/Passkey.sol | 2 ++ 2 files changed, 3 insertions(+) diff --git a/remappings.txt b/remappings.txt index 19652a8..b09e896 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,3 +5,4 @@ forge-std/=lib/forge-std/src/ openzeppelin/=lib/openzeppelin-contracts/contracts/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +webauthn-sol/=lib/webauthn-sol/src/ diff --git a/src/modules/Passkey.sol b/src/modules/Passkey.sol index 92a97b8..d7063f0 100644 --- a/src/modules/Passkey.sol +++ b/src/modules/Passkey.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import "../interfaces/IModule.sol"; import "../interfaces/IWallet.sol"; +import {WebAuthn} from "webauthn-sol/WebAuthn.sol"; + contract PasskeyModuleFactory { function create(bytes32 salt) external returns (PasskeyModule) { return new PasskeyModule{salt: salt}(); From 4581eb5b1e451ab2ed9bc8ebe9c54c74f96e97e2 Mon Sep 17 00:00:00 2001 From: imduchuyyy Date: Thu, 20 Jun 2024 22:43:21 +0700 Subject: [PATCH 5/5] passkey module --- src/libraries/Base64.sol | 120 +++ src/libraries/FCL_ecdsa.sol | 131 ++++ src/libraries/FCL_elliptic.sol | 949 ++++++++++++++++++++++++ src/libraries/LibString.sol | 1269 ++++++++++++++++++++++++++++++++ src/libraries/WebAuthn.sol | 164 +++++ src/modules/Passkey.sol | 37 +- 6 files changed, 2660 insertions(+), 10 deletions(-) create mode 100644 src/libraries/Base64.sol create mode 100644 src/libraries/FCL_ecdsa.sol create mode 100644 src/libraries/FCL_elliptic.sol create mode 100644 src/libraries/LibString.sol create mode 100644 src/libraries/WebAuthn.sol diff --git a/src/libraries/Base64.sol b/src/libraries/Base64.sol new file mode 100644 index 0000000..0552d04 --- /dev/null +++ b/src/libraries/Base64.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.2) (utils/Base64.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides a set of functions to operate with Base64 strings. + */ +library Base64 { + /** + * @dev Base64 Encoding/Decoding Table + * See sections 4 and 5 of https://datatracker.ietf.org/doc/html/rfc4648 + */ + string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + string internal constant _TABLE_URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + /** + * @dev Converts a `bytes` to its Bytes64 `string` representation. + */ + function encode(bytes memory data) internal pure returns (string memory) { + return _encode(data, _TABLE, true); + } + + /** + * @dev Converts a `bytes` to its Bytes64Url `string` representation. + */ + function encodeURL(bytes memory data) internal pure returns (string memory) { + return _encode(data, _TABLE_URL, false); + } + + /** + * @dev Internal table-agnostic conversion + */ + function _encode(bytes memory data, string memory table, bool withPadding) private pure returns (string memory) { + /** + * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence + * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol + */ + if (data.length == 0) return ""; + + // If padding is enabled, the final length should be `bytes` data length divided by 3 rounded up and then + // multiplied by 4 so that it leaves room for padding the last chunk + // - `data.length + 2` -> Round up + // - `/ 3` -> Number of 3-bytes chunks + // - `4 *` -> 4 characters for each chunk + // If padding is disabled, the final length should be `bytes` data length multiplied by 4/3 rounded up as + // opposed to when padding is required to fill the last chunk. + // - `4 *` -> 4 characters for each chunk + // - `data.length + 2` -> Round up + // - `/ 3` -> Number of 3-bytes chunks + uint256 resultLength = withPadding ? 4 * ((data.length + 2) / 3) : (4 * data.length + 2) / 3; + + string memory result = new string(resultLength); + + /// @solidity memory-safe-assembly + assembly { + // Prepare the lookup table (skip the first "length" byte) + let tablePtr := add(table, 1) + + // Prepare result pointer, jump over length + let resultPtr := add(result, 0x20) + let dataPtr := data + let endPtr := add(data, mload(data)) + + // In some cases, the last iteration will read bytes after the end of the data. We cache the value, and + // set it to zero to make sure no dirty bytes are read in that section. + let afterPtr := add(endPtr, 0x20) + let afterCache := mload(afterPtr) + mstore(afterPtr, 0x00) + + // Run over the input, 3 bytes at a time + for { + + } lt(dataPtr, endPtr) { + + } { + // Advance 3 bytes + dataPtr := add(dataPtr, 3) + let input := mload(dataPtr) + + // To write each character, shift the 3 byte (24 bits) chunk + // 4 times in blocks of 6 bits for each character (18, 12, 6, 0) + // and apply logical AND with 0x3F to bitmask the least significant 6 bits. + // Use this as an index into the lookup table, mload an entire word + // so the desired character is in the least significant byte, and + // mstore8 this least significant byte into the result and continue. + + mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + } + + // Reset the value that was cached + mstore(afterPtr, afterCache) + + if withPadding { + // When data `bytes` is not exactly 3 bytes long + // it is padded with `=` characters at the end + switch mod(mload(data), 3) + case 1 { + mstore8(sub(resultPtr, 1), 0x3d) + mstore8(sub(resultPtr, 2), 0x3d) + } + case 2 { + mstore8(sub(resultPtr, 1), 0x3d) + } + } + } + + return result; + } +} \ No newline at end of file diff --git a/src/libraries/FCL_ecdsa.sol b/src/libraries/FCL_ecdsa.sol new file mode 100644 index 0000000..a993539 --- /dev/null +++ b/src/libraries/FCL_ecdsa.sol @@ -0,0 +1,131 @@ +//********************************************************************************************/ +// ___ _ ___ _ _ _ _ +// | __| _ ___ __| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__ +// | _| '_/ -_|_-< ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \ +// |_||_| \___/__/_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/ +// |__/|_| +///* 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_ecdsa.sol +///* +///* +///* DESCRIPTION: ecdsa verification implementation +///* +//**************************************************************************************/ +//* 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 <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) + uint256 constant n = FCL_Elliptic_ZZ.n; + + /** + * @dev ECDSA verification, given , signature, and public key. + */ + + /** + * @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){ + + 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; + } + + uint256 sInv = FCL_Elliptic_ZZ.FCL_nModInv(s); + + uint256 scalar_u = mulmod(uint256(message), sInv, FCL_Elliptic_ZZ.n); + uint256 scalar_v = mulmod(r, sInv, FCL_Elliptic_ZZ.n); + uint256 x1; + + x1 = FCL_Elliptic_ZZ.ecZZ_mulmuladd_S_asm(Qx, Qy, scalar_u, scalar_v); + + 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) { + 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 Qx; + uint256 Qy; + (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 + returns (bool) + { + + if (r == 0 || r >= n || s == 0 || s >= n) { + return false; + } + /* Q is pushed via the contract at address Shamir8 assumed to be correct + if (!isOnCurve(Q[0], Q[1])) { + return false; + }*/ + + uint256 sInv = FCL_Elliptic_ZZ.FCL_nModInv(s); + + uint256 X; + + //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 ); + + return X == 0; + } //end ecdsa_precomputed_verify() + + function ecdsa_precomputed_verify(bytes32 message, uint256[2] calldata rs, address Shamir8) + internal view + returns (bool) + { + uint256 r = rs[0]; + uint256 s = rs[1]; + if (r == 0 || r >= n || s == 0 || s >= n) { + return false; + } + /* Q is pushed via the contract at address Shamir8 assumed to be correct + if (!isOnCurve(Q[0], Q[1])) { + return false; + }*/ + + uint256 sInv = FCL_Elliptic_ZZ.FCL_nModInv(s); + + uint256 X; + + //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 ); + + 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 new file mode 100644 index 0000000..737d68b --- /dev/null +++ b/src/libraries/FCL_elliptic.sol @@ -0,0 +1,949 @@ +//********************************************************************************************/ +// ___ _ ___ _ _ _ _ +// | __| _ ___ __| |_ / __|_ _ _ _ _ __| |_ ___ | | (_) |__ +// | _| '_/ -_|_-< ' \ | (__| '_| || | '_ \ _/ _ \ | |__| | '_ \ +// |_||_| \___/__/_||_| \___|_| \_, | .__/\__\___/ |____|_|_.__/ +// |__/|_| +///* 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 <0.9.0; + +library FCL_Elliptic_ZZ { + // Set parameters for curve sec256r1. + + // address of the ModExp precompiled contract (Arbitrary-precision exponentiation under modulo) + address constant MODEXP_PRECOMPILE = 0x0000000000000000000000000000000000000005; + //curve prime field modulus + uint256 constant p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + //short weierstrass first coefficient + uint256 constant a = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; + //short weierstrass second coefficient + uint256 constant b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; + //generating point affine coordinates + uint256 constant gx = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; + uint256 constant gy = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; + //curve order (number of points) + uint256 constant n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + /* -2 mod p constant, used to speed up inversion and doubling (avoid negation)*/ + uint256 constant minus_2 = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFD; + /* -2 mod n constant, used to speed up inversion*/ + uint256 constant minus_2modn = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F; + + uint256 constant minus_1 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + //P+1 div 4 + uint256 constant pp1div4=0x3fffffffc0000000400000000000000000000000400000000000000000000000; + //arbitrary constant to express no quadratic residuosity + uint256 constant _NOTSQUARE=0xFFFFFFFF00000002000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + uint256 constant _NOTONCURVE=0xFFFFFFFF00000003000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + + /** + * /* inversion mod n via a^(n-2), use of precompiled using little Fermat theorem + */ + function FCL_nModInv(uint256 u) internal view returns (uint256 result) { + assembly { + let pointer := mload(0x40) + // 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(staticcall(not(0), 0x05, 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) internal view returns (uint256 result) { + assembly { + let pointer := mload(0x40) + // 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(staticcall(not(0), 0x05, pointer, 0xc0, pointer, 0x20)) { revert(0, 0) } + result := mload(pointer) + } + } + + //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 + + 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; +} + /** + * /* @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 + P[3] = 1; //ZZZ + P[0] = x0; + P[1] = y0; + } + } + + 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 + + y=SqrtMod(y2); + if(y==_NOTSQUARE){ + return _NOTONCURVE; + } + if((y&1)!=(parity&1)){ + y=p-y; + } + } + + /** + * /* @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) { + uint256 zzzInv = FCL_pModInv(zzz); //1/zzz + y1 = mulmod(y, zzzInv, p); //Y/zzz + uint256 _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(uint256 x, uint256 y, uint256 zz, uint256 zzz) + internal + pure + returns (uint256 P0, uint256 P1, uint256 P2, uint256 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) + */ + + function ecZZ_AddN(uint256 x1, uint256 y1, uint256 zz1, uint256 zzz1, uint256 x2, uint256 y2) + internal + pure + returns (uint256 P0, uint256 P1, uint256 P2, uint256 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 (uint256 x, uint256 y, uint256 zz, uint256 zzz) { + return (0, 0, 0, 0); + } + /** + * @dev Check if point is the neutral of the curve + */ + + // uint256 x0, uint256 y0, uint256 zz0, uint256 zzz0 + function ecZZ_IsZero(uint256, uint256 y0, uint256, uint256) internal pure returns (bool) { + return y0 == 0; + } + /** + * @dev Return the zero curve in affine coordinates. Compatible with the double formulae (no special case) + */ + + function ecAff_SetZero() internal pure returns (uint256 x, uint256 y) { + return (0, 0); + } + + /** + * @dev Check if the curve is the zero curve in affine rep. + */ + // uint256 x, uint256 y) + function ecAff_IsZero(uint256, uint256 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(uint256 x, uint256 y) internal pure returns (bool) { + if (x >= p || y >= p || ((x == 0) && (y == 0))) { + return false; + } + unchecked { + uint256 LHS = mulmod(y, y, p); // y^2 + uint256 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. Deal with P=Q + */ + + function ecAff_add(uint256 x0, uint256 y0, uint256 x1, uint256 y1) internal view returns (uint256, uint256) { + uint256 zz0; + uint256 zzz0; + + 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{ + (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 + * Returns only x for ECDSA use + * */ + function ecZZ_mulmuladd_S_asm( + uint256 Q0, + uint256 Q1, //affine rep for input point Q + uint256 scalar_u, + uint256 scalar_v + ) internal view returns (uint256 X) { + uint256 zz; + uint256 zzz; + uint256 Y; + uint256 index = 255; + uint256 H0; + uint256 H1; + + 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; + if (scalar_u == 0 && scalar_v == 0) return 0; + } + assembly { + for { let T4 := add(shl(1, and(shr(index, scalar_v), 1)), and(shr(index, scalar_u), 1)) } eq(T4, 0) { + index := sub(index, 1) + T4 := add(shl(1, and(shr(index, scalar_v), 1)), and(shr(index, scalar_u), 1)) + } {} + 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 {} 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(X, sub(p, T3), p), p) //-M(S-X3)=M(X3-S) + 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 + } + if iszero(zz) { + X := T1 + Y := T2 + zz := 1 + zzz := 1 + continue + } + // inlined EcZZ_AddN + + //T3:=sub(p, Y) + //T3:=Y + let y2 := addmod(mulmod(T2, zzz, p), Y, p) //R + T2 := addmod(mulmod(T1, zz, p), sub(p, X), p) //P + + //special extremely rare case accumulator where EcAdd is replaced by EcDbl, no need to optimize this + //todo : construct edge vector case + if iszero(y2) { + if iszero(T2) { + T1 := mulmod(minus_2, Y, p) //U = 2*Y1, y free + T2 := mulmod(T1, T1, p) // V=U^2 + T3 := mulmod(X, T2, p) // S = X1*V + + T1 := mulmod(T1, T2, p) // W=UV + y2 := mulmod(addmod(X, zz, p), addmod(X, sub(p, zz), p), p) //(X-ZZ)(X+ZZ) + T4 := mulmod(3, y2, p) //M=3*(X-ZZ)(X+ZZ) + + 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) + + Y := addmod(T2, mulmod(T1, Y, p), p) //Y3= M(S-X3)-W*Y1 + + continue + } + } + + 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 + let TT2 := mulmod(X, T4, p) + T4 := addmod(addmod(mulmod(y2, y2, p), sub(p, TT1), p), mulmod(minus_2, TT2, p), p) + Y := addmod(mulmod(addmod(TT2, sub(p, T4), p), y2, p), mulmod(Y, TT1, p), p) + + X := T4 + } + } //end loop + let T := mload(0x40) + 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(staticcall(not(0), 0x05, 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; + } + + + /** + * @dev Computation of uG+vQ using Strauss-Shamir's trick, G basepoint, Q public key + * Returns affine representation of point (normalized) + * */ + function ecZZ_mulmuladd( + uint256 Q0, + uint256 Q1, //affine rep for input point Q + uint256 scalar_u, + uint256 scalar_v + ) internal view returns (uint256 X, uint256 Y) { + uint256 zz; + uint256 zzz; + uint256 index = 255; + uint256[6] memory T; + uint256[2] memory H; + + unchecked { + 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 + + assembly { + for { let T4 := add(shl(1, and(shr(index, scalar_v), 1)), and(shr(index, scalar_u), 1)) } eq(T4, 0) { + index := sub(index, 1) + T4 := add(shl(1, and(shr(index, scalar_v), 1)), and(shr(index, scalar_u), 1)) + } {} + 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) { + Y := mload(add(H,32)) + X := mload(H) + } + + index := sub(index, 1) + zz := 1 + zzz := 1 + + for {} 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(X, sub(p, T3), p), p) //-M(S-X3)=M(X3-S) + 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 := mload(H) + T2 := mload(add(H,32)) + } + if iszero(zz) { + X := T1 + Y := T2 + zz := 1 + zzz := 1 + continue + } + // inlined EcZZ_AddN + + //T3:=sub(p, Y) + //T3:=Y + let y2 := addmod(mulmod(T2, zzz, p), Y, p) //R + T2 := addmod(mulmod(T1, zz, p), sub(p, X), p) //P + + //special extremely rare case accumulator where EcAdd is replaced by EcDbl, no need to optimize this + //todo : construct edge vector case + if iszero(y2) { + if iszero(T2) { + T1 := mulmod(minus_2, Y, p) //U = 2*Y1, y free + T2 := mulmod(T1, T1, p) // V=U^2 + T3 := mulmod(X, T2, p) // S = X1*V + + T1 := mulmod(T1, T2, p) // W=UV + y2 := mulmod(addmod(X, zz, p), addmod(X, sub(p, zz), p), p) //(X-ZZ)(X+ZZ) + T4 := mulmod(3, y2, p) //M=3*(X-ZZ)(X+ZZ) + + 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) + + Y := addmod(T2, mulmod(T1, Y, p), p) //Y3= M(S-X3)-W*Y1 + + continue + } + } + + 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 + let TT2 := mulmod(X, T4, p) + T4 := addmod(addmod(mulmod(y2, y2, p), sub(p, TT1), p), mulmod(minus_2, TT2, p), p) + Y := addmod(mulmod(addmod(TT2, sub(p, T4), p), y2, p), mulmod(Y, TT1, p), p) + + X := T4 + } + } //end loop + mstore(add(T, 0x60), zzz) + //(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(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 + X := mulmod(X, zz, p) //X/zz + } //end assembly + } //end unchecked + + return (X,Y); + } + + //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(uint256 scalar_u, uint256 scalar_v, address dataPointer) + internal view + returns (uint256 X /*, uint Y*/ ) + { + unchecked { + uint256 zz; // third and coordinates of the point + + uint256[6] memory T; + zz = 256; //start index + + 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) + let index := sub(zz, 1) + 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 {} gt(index, 191) { index := add(index, 191) } { + //inline Double + { + let TT1 := mulmod(2, Y, p) //U = 2*Y1, y free + let T2 := mulmod(TT1, TT1, p) // V=U^2 + let T3 := mulmod(X, T2, p) // S = X1*V + let T1 := mulmod(TT1, 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) + let T5 := 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), T5, p) //-Y3= W*Y1-M(S-X3), we replace Y by -Y to avoid a sub in ecAdd + + /* compute element to access in precomputed table */ + } + { + let T4 := add(shl(13, and(shr(index, scalar_v), 1)), shl(9, and(shr(index, scalar_u), 1))) + let index2 := sub(index, 64) + let T3 := + add(T4, add(shl(12, and(shr(index2, scalar_v), 1)), shl(8, and(shr(index2, scalar_u), 1)))) + let index3 := sub(index2, 64) + let T2 := + add(T3, add(shl(11, and(shr(index3, scalar_v), 1)), shl(7, and(shr(index3, scalar_u), 1)))) + index := sub(index3, 64) + let T1 := + add(T2, add(shl(10, and(shr(index, scalar_v), 1)), shl(6, and(shr(index, scalar_u), 1)))) + + //tbd: check validity of formulae with (0,1) to remove conditional jump + if iszero(T1) { + Y := sub(p, Y) + + continue + } + extcodecopy(dataPointer, T, T1, 64) + } + + { + /* Access to precomputed table using extcodecopy hack */ + + // inlined EcZZ_AddN + if iszero(zz) { + X := mload(T) + Y := mload(add(T, 32)) + zz := 1 + zzz := 1 + + continue + } + + let y2 := addmod(mulmod(mload(add(T, 32)), zzz, p), Y, p) + let T2 := addmod(mulmod(mload(T), zz, p), sub(p, X), p) + + //special case ecAdd(P,P)=EcDbl + if iszero(y2) { + if iszero(T2) { + let T1 := mulmod(minus_2, Y, p) //U = 2*Y1, y free + T2 := mulmod(T1, T1, p) // V=U^2 + let T3 := mulmod(X, T2, p) // S = X1*V + + T1 := mulmod(T1, T2, p) // W=UV + y2 := mulmod(addmod(X, zz, p), addmod(X, sub(p, zz), p), p) //(X-ZZ)(X+ZZ) + let T4 := mulmod(3, y2, p) //M=3*(X-ZZ)(X+ZZ) + + 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) + + Y := addmod(T2, mulmod(T1, Y, p), p) //Y3= M(S-X3)-W*Y1 + + continue + } + } + + let T4 := mulmod(T2, T2, p) + let T1 := mulmod(T4, T2, p) // + zz := mulmod(zz, T4, p) + //zzz3=V*ZZ1 + zzz := mulmod(zzz, T1, p) // W=UV/ + let zz1 := mulmod(X, T4, p) + X := addmod(addmod(mulmod(y2, y2, p), sub(p, T1), p), mulmod(minus_2, zz1, p), p) + Y := addmod(mulmod(addmod(zz1, sub(p, X), p), y2, p), mulmod(Y, T1, p), p) + } + } //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(staticcall(not(0), 0x05, T, 0xc0, T, 0x20)) { revert(0, 0) } + + zz := mload(T) + X := mulmod(X, zz, p) //X/zz + } + } //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 + returns (uint256 X /*, uint Y*/ ) + { + uint256 zz; // third and coordinates of the point + + uint256[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 { + codecopy(T, add(mload(T), dataPointer), 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 := add(index, 191) } { + 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 + + /* compute element to access in precomputed table */ + T4 := add(shl(13, and(shr(index, scalar_v), 1)), shl(9, and(shr(index, scalar_u), 1))) + index := sub(index, 64) + T4 := add(T4, add(shl(12, and(shr(index, scalar_v), 1)), shl(8, and(shr(index, scalar_u), 1)))) + index := sub(index, 64) + T4 := add(T4, add(shl(11, and(shr(index, scalar_v), 1)), shl(7, and(shr(index, scalar_u), 1)))) + index := sub(index, 64) + T4 := add(T4, add(shl(10, and(shr(index, scalar_v), 1)), shl(6, and(shr(index, scalar_u), 1)))) + //index:=add(index,192), restore index, interleaved with loop + + //tbd: check validity of formulae with (0,1) to remove conditional jump + if iszero(T4) { + Y := sub(p, Y) + + continue + } + { + /* Access to precomputed table using extcodecopy hack */ + codecopy(T, add(T4, dataPointer), 64) + + // inlined EcZZ_AddN + + 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(staticcall(not(0), 0x05, T, 0xc0, T, 0x20)) { revert(0, 0) } + + zz := mload(T) + X := mulmod(X, zz, p) //X/zz + } + } //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 + * (see sage directory, WebAuthn_precompute.sage) + */ + + /** + * @dev ECDSA verification using a precomputed table of multiples of P and Q appended at end of contract at address endcontract + * generation of contract bytecode for precomputations is done using sagemath code + * (see sage directory, WebAuthn_precompute.sage) + */ + + function ecdsa_precomputed_hackmem(bytes32 message, uint256[2] calldata rs, uint256 endcontract) + internal view + returns (bool) + { + uint256 r = rs[0]; + uint256 s = rs[1]; + if (r == 0 || r >= n || s == 0 || s >= n) { + return false; + } + /* Q is pushed via bytecode assumed to be correct + if (!isOnCurve(Q[0], Q[1])) { + return false; + }*/ + + uint256 sInv = FCL_nModInv(s); + uint256 X; + + //Shamir 8 dimensions + X = ecZZ_mulmuladd_S8_hackmem(mulmod(uint256(message), sInv, n), mulmod(r, sInv, n), endcontract); + + assembly { + X := addmod(X, sub(n, r), n) + } + 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 new file mode 100644 index 0000000..6805a12 --- /dev/null +++ b/src/libraries/LibString.sol @@ -0,0 +1,1269 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice Library for converting numbers into strings and other string operations. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol) +/// +/// @dev Note: +/// For performance and bytecode compactness, most of the string operations are restricted to +/// byte strings (7-bit ASCII), except where otherwise specified. +/// Usage of byte string operations on charsets with runes spanning two or more bytes +/// can lead to undefined behavior. +library LibString { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The length of the output is too small to contain all the hex digits. + error HexLengthInsufficient(); + + /// @dev The length of the string is more than 32 bytes. + error TooBigForSmallString(); + + /// @dev The input string must be a 7-bit ASCII. + error StringNot7BitASCII(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The constant returned when the `search` is not found in the string. + uint256 internal constant NOT_FOUND = type(uint256).max; + + /// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'. + uint128 internal constant ALPHANUMERIC_7_BIT_ASCII = 0x7fffffe07fffffe03ff000000000000; + + /// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'. + uint128 internal constant LETTERS_7_BIT_ASCII = 0x7fffffe07fffffe0000000000000000; + + /// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'. + uint128 internal constant LOWERCASE_7_BIT_ASCII = 0x7fffffe000000000000000000000000; + + /// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + uint128 internal constant UPPERCASE_7_BIT_ASCII = 0x7fffffe0000000000000000; + + /// @dev Lookup for '0123456789'. + uint128 internal constant DIGITS_7_BIT_ASCII = 0x3ff000000000000; + + /// @dev Lookup for '0123456789abcdefABCDEF'. + uint128 internal constant HEXDIGITS_7_BIT_ASCII = 0x7e0000007e03ff000000000000; + + /// @dev Lookup for '01234567'. + uint128 internal constant OCTDIGITS_7_BIT_ASCII = 0xff000000000000; + + /// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'. + uint128 internal constant PRINTABLE_7_BIT_ASCII = 0x7fffffffffffffffffffffff00003e00; + + /// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'. + uint128 internal constant PUNCTUATION_7_BIT_ASCII = 0x78000001f8000001fc00fffe00000000; + + /// @dev Lookup for ' \t\n\r\x0b\x0c'. + uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* DECIMAL OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the base 10 decimal representation of `value`. + function toString(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but + // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. + // We will need 1 word for the trailing zeros padding, 1 word for the length, + // and 3 words for a maximum of 78 digits. + str := add(mload(0x40), 0x80) + // Update the free memory pointer to allocate. + mstore(0x40, add(str, 0x20)) + // Zeroize the slot after the string. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + let w := not(0) // Tsk. + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let temp := value } 1 {} { + str := add(str, w) // `sub(str, 1)`. + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + // Keep dividing `temp` until zero. + temp := div(temp, 10) + if iszero(temp) { break } + } + + let length := sub(end, str) + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 0x20) + // Store the length. + mstore(str, length) + } + } + + /// @dev Returns the base 10 decimal representation of `value`. + function toString(int256 value) internal pure returns (string memory str) { + if (value >= 0) { + return toString(uint256(value)); + } + unchecked { + str = toString(~uint256(value) + 1); + } + /// @solidity memory-safe-assembly + assembly { + // We still have some spare memory space on the left, + // as we have allocated 3 words (96 bytes) for up to 78 digits. + let length := mload(str) // Load the string length. + mstore(str, 0x2d) // Store the '-' character. + str := sub(str, 1) // Move back the string pointer by a byte. + mstore(str, add(length, 1)) // Update the string length. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HEXADECIMAL OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the hexadecimal representation of `value`, + /// left-padded to an input length of `length` bytes. + /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, + /// giving a total length of `length * 2 + 2` bytes. + /// Reverts if `length` is too small for the output to contain all the digits. + function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value, length); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`, + /// left-padded to an input length of `length` bytes. + /// 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) + { + /// @solidity memory-safe-assembly + assembly { + // We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes + // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length. + // We add 0x20 to the total and round down to a multiple of 0x20. + // (0x20 + 0x20 + 0x02 + 0x20) = 0x62. + str := add(mload(0x40), and(add(shl(1, length), 0x42), not(0x1f))) + // Allocate the memory. + mstore(0x40, add(str, 0x20)) + // Zeroize the slot after the string. + mstore(str, 0) + + // Cache the end to calculate the length later. + let end := str + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + let start := sub(str, add(length, length)) + let w := not(1) // Tsk. + let temp := value + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for {} 1 {} { + str := add(str, w) // `sub(str, 2)`. + mstore8(add(str, 1), mload(and(temp, 15))) + mstore8(str, mload(and(shr(4, temp), 15))) + temp := shr(8, temp) + if iszero(xor(str, start)) { break } + } + + if temp { + mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`. + revert(0x1c, 0x04) + } + + // Compute the string's length. + let strLength := sub(end, str) + // Move the pointer and write the length. + str := sub(str, 0x20) + mstore(str, strLength) + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. + /// As address are 20 bytes long, the output will left-padded to have + /// a length of `20 * 2 + 2` bytes. + function toHexString(uint256 value) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x". + /// The output excludes leading "0" from the `toHexString` output. + /// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`. + function toMinimalHexString(uint256 value) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. + let strLength := add(mload(str), 2) // Compute the length. + mstore(add(str, o), 0x3078) // Write the "0x" prefix, accounting for leading zero. + str := sub(add(str, o), 2) // Move the pointer, accounting for leading zero. + mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output excludes leading "0" from the `toHexStringNoPrefix` output. + /// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`. + function toMinimalHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. + let strLength := mload(str) // Get the length. + str := add(str, o) // Move the pointer, accounting for leading zero. + mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is encoded using 2 hexadecimal digits per byte. + /// As address are 20 bytes long, the output will left-padded to have + /// a length of `20 * 2` bytes. + function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, + // 0x02 bytes for the prefix, and 0x40 bytes for the digits. + // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0. + str := add(mload(0x40), 0x80) + // Allocate the memory. + mstore(0x40, add(str, 0x20)) + // Zeroize the slot after the string. + mstore(str, 0) + + // Cache the end to calculate the length later. + let end := str + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + let w := not(1) // Tsk. + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let temp := value } 1 {} { + str := add(str, w) // `sub(str, 2)`. + mstore8(add(str, 1), mload(and(temp, 15))) + mstore8(str, mload(and(shr(4, temp), 15))) + temp := shr(8, temp) + if iszero(temp) { break } + } + + // Compute the string's length. + let strLength := sub(end, str) + // Move the pointer and write the length. + str := sub(str, 0x20) + mstore(str, strLength) + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, + /// and the alphabets are capitalized conditionally according to + /// https://eips.ethereum.org/EIPS/eip-55 + function toHexStringChecksummed(address value) internal pure returns (string memory str) { + str = toHexString(value); + /// @solidity memory-safe-assembly + assembly { + let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` + let o := add(str, 0x22) + let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` + let t := shl(240, 136) // `0b10001000 << 240` + for { let i := 0 } 1 {} { + mstore(add(i, i), mul(t, byte(i, hashed))) + i := add(i, 1) + if eq(i, 20) { break } + } + mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask))))) + o := add(o, 0x20) + mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask))))) + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. + function toHexString(address value) internal pure returns (string memory str) { + str = toHexStringNoPrefix(value); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } + } + + /// @dev Returns the hexadecimal representation of `value`. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexStringNoPrefix(address value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + str := mload(0x40) + + // Allocate the memory. + // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, + // 0x02 bytes for the prefix, and 0x28 bytes for the digits. + // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. + mstore(0x40, add(str, 0x80)) + + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + str := add(str, 2) + mstore(str, 40) + + let o := add(str, 0x20) + mstore(add(o, 40), 0) + + value := shl(96, value) + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + for { let i := 0 } 1 {} { + let p := add(o, add(i, i)) + let temp := byte(i, value) + mstore8(add(p, 1), mload(and(temp, 15))) + mstore8(p, mload(shr(4, temp))) + i := add(i, 1) + if eq(i, 20) { break } + } + } + } + + /// @dev Returns the hex encoded string from the raw bytes. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexString(bytes memory raw) internal pure returns (string memory str) { + str = toHexStringNoPrefix(raw); + /// @solidity memory-safe-assembly + assembly { + let strLength := add(mload(str), 2) // Compute the length. + mstore(str, 0x3078) // Write the "0x" prefix. + str := sub(str, 2) // Move the pointer. + mstore(str, strLength) // Write the length. + } + } + + /// @dev Returns the hex encoded string from the raw bytes. + /// The output is encoded using 2 hexadecimal digits per byte. + function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + let length := mload(raw) + str := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix. + mstore(str, add(length, length)) // Store the length of the output. + + // Store "0123456789abcdef" in scratch space. + mstore(0x0f, 0x30313233343536373839616263646566) + + let o := add(str, 0x20) + let end := add(raw, length) + + for {} iszero(eq(raw, end)) {} { + raw := add(raw, 1) + mstore8(add(o, 1), mload(and(mload(raw), 15))) + mstore8(o, mload(and(shr(4, mload(raw)), 15))) + o := add(o, 2) + } + mstore(o, 0) // Zeroize the slot after the string. + mstore(0x40, add(o, 0x20)) // Allocate the memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RUNE STRING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the number of UTF characters in the string. + function runeCount(string memory s) internal pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + if mload(s) { + mstore(0x00, div(not(0), 255)) + mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506) + let o := add(s, 0x20) + let end := add(o, mload(s)) + for { result := 1 } 1 { result := add(result, 1) } { + o := add(o, byte(0, mload(shr(250, mload(o))))) + if iszero(lt(o, end)) { break } + } + } + } + } + + /// @dev Returns if this string is a 7-bit ASCII string. + /// (i.e. all characters codes are in [0..127]) + function is7BitASCII(string memory s) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + let mask := shl(7, div(not(0), 255)) + result := 1 + let n := mload(s) + if n { + let o := add(s, 0x20) + let end := add(o, n) + let last := mload(end) + mstore(end, 0) + for {} 1 {} { + if and(mask, mload(o)) { + result := 0 + break + } + o := add(o, 0x20) + if iszero(lt(o, end)) { break } + } + mstore(end, last) + } + } + } + + /// @dev Returns if this string is a 7-bit ASCII string, + /// AND all characters are in the `allowed` lookup. + /// Note: If `s` is empty, returns true regardless of `allowed`. + function is7BitASCII(string memory s, uint128 allowed) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if mload(s) { + let allowed_ := shr(128, shl(128, allowed)) + let o := add(s, 0x20) + let end := add(o, mload(s)) + for {} 1 {} { + result := and(result, shr(byte(0, mload(o)), allowed_)) + o := add(o, 1) + if iszero(and(result, lt(o, end))) { break } + } + } + } + } + + /// @dev Converts the bytes in the 7-bit ASCII string `s` to + /// an allowed lookup for use in `is7BitASCII(s, allowed)`. + /// To save runtime gas, you can cache the result in an immutable variable. + function to7BitASCIIAllowedLookup(string memory s) internal pure returns (uint128 result) { + /// @solidity memory-safe-assembly + assembly { + if mload(s) { + let o := add(s, 0x20) + let end := add(o, mload(s)) + for {} 1 {} { + result := or(result, shl(byte(0, mload(o)), 1)) + o := add(o, 1) + if iszero(lt(o, end)) { break } + } + if shr(128, result) { + mstore(0x00, 0xc9807e0d) // `StringNot7BitASCII()`. + revert(0x1c, 0x04) + } + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTE STRING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // For performance and bytecode compactness, byte string operations are restricted + // to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets. + // Usage of byte string operations on charsets with runes spanning two or more bytes + // can lead to undefined behavior. + + /// @dev Returns `subject` all occurrences of `search` replaced with `replacement`. + function replace(string memory subject, string memory search, string memory replacement) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let subjectLength := mload(subject) + let searchLength := mload(search) + let replacementLength := mload(replacement) + + subject := add(subject, 0x20) + search := add(search, 0x20) + replacement := add(replacement, 0x20) + result := add(mload(0x40), 0x20) + + let subjectEnd := add(subject, subjectLength) + if iszero(gt(searchLength, subjectLength)) { + let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1) + let h := 0 + if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } + let m := shl(3, sub(0x20, and(searchLength, 0x1f))) + let s := mload(search) + for {} 1 {} { + let t := mload(subject) + // Whether the first `searchLength % 32` bytes of + // `subject` and `search` matches. + if iszero(shr(m, xor(t, s))) { + if h { + if iszero(eq(keccak256(subject, searchLength), h)) { + mstore(result, t) + result := add(result, 1) + subject := add(subject, 1) + if iszero(lt(subject, subjectSearchEnd)) { break } + continue + } + } + // Copy the `replacement` one word at a time. + for { let o := 0 } 1 {} { + mstore(add(result, o), mload(add(replacement, o))) + o := add(o, 0x20) + if iszero(lt(o, replacementLength)) { break } + } + result := add(result, replacementLength) + subject := add(subject, searchLength) + if searchLength { + if iszero(lt(subject, subjectSearchEnd)) { break } + continue + } + } + mstore(result, t) + result := add(result, 1) + subject := add(subject, 1) + if iszero(lt(subject, subjectSearchEnd)) { break } + } + } + + let resultRemainder := result + result := add(mload(0x40), 0x20) + let k := add(sub(resultRemainder, result), sub(subjectEnd, subject)) + // Copy the rest of the string one word at a time. + for {} lt(subject, subjectEnd) {} { + mstore(resultRemainder, mload(subject)) + resultRemainder := add(resultRemainder, 0x20) + subject := add(subject, 0x20) + } + result := sub(result, 0x20) + let last := add(add(result, 0x20), k) // Zeroize the slot after the string. + mstore(last, 0) + mstore(0x40, add(last, 0x20)) // Allocate the memory. + mstore(result, k) // Store the length. + } + } + + /// @dev Returns the byte index of the first location of `search` in `subject`, + /// searching from left to right, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. + function indexOf(string memory subject, string memory search, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + for { let subjectLength := mload(subject) } 1 {} { + if iszero(mload(search)) { + if iszero(gt(from, subjectLength)) { + result := from + break + } + result := subjectLength + break + } + let searchLength := mload(search) + let subjectStart := add(subject, 0x20) + + result := not(0) // Initialize to `NOT_FOUND`. + + subject := add(subjectStart, from) + let end := add(sub(add(subjectStart, subjectLength), searchLength), 1) + + let m := shl(3, sub(0x20, and(searchLength, 0x1f))) + let s := mload(add(search, 0x20)) + + if iszero(and(lt(subject, end), lt(from, subjectLength))) { break } + + if iszero(lt(searchLength, 0x20)) { + for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { + if iszero(shr(m, xor(mload(subject), s))) { + if eq(keccak256(subject, searchLength), h) { + result := sub(subject, subjectStart) + break + } + } + subject := add(subject, 1) + if iszero(lt(subject, end)) { break } + } + break + } + for {} 1 {} { + if iszero(shr(m, xor(mload(subject), s))) { + result := sub(subject, subjectStart) + break + } + subject := add(subject, 1) + if iszero(lt(subject, end)) { break } + } + break + } + } + } + + /// @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) + { + result = indexOf(subject, search, 0); + } + + /// @dev Returns the byte index of the first location of `search` in `subject`, + /// searching from right to left, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. + function lastIndexOf(string memory subject, string memory search, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := not(0) // Initialize to `NOT_FOUND`. + let searchLength := mload(search) + if gt(searchLength, mload(subject)) { break } + let w := result + + let fromMax := sub(mload(subject), searchLength) + if iszero(gt(fromMax, from)) { from := fromMax } + + let end := add(add(subject, 0x20), w) + subject := add(add(subject, 0x20), from) + if iszero(gt(subject, end)) { break } + // As this function is not too often used, + // we shall simply use keccak256 for smaller bytecode size. + for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { + if eq(keccak256(subject, searchLength), h) { + result := sub(subject, add(end, 1)) + break + } + subject := add(subject, w) // `sub(subject, 1)`. + if iszero(gt(subject, end)) { break } + } + break + } + } + } + + /// @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) + { + result = lastIndexOf(subject, search, uint256(int256(-1))); + } + + /// @dev Returns true if `search` is found in `subject`, false otherwise. + function contains(string memory subject, string memory search) internal pure returns (bool) { + return indexOf(subject, search) != NOT_FOUND; + } + + /// @dev Returns whether `subject` starts with `search`. + function startsWith(string memory subject, string memory search) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let searchLength := mload(search) + // Just using keccak256 directly is actually cheaper. + // forgefmt: disable-next-item + result := and( + iszero(gt(searchLength, mload(subject))), + eq( + keccak256(add(subject, 0x20), searchLength), + keccak256(add(search, 0x20), searchLength) + ) + ) + } + } + + /// @dev Returns whether `subject` ends with `search`. + function endsWith(string memory subject, string memory search) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let searchLength := mload(search) + let subjectLength := mload(subject) + // Whether `search` is not longer than `subject`. + let withinRange := iszero(gt(searchLength, subjectLength)) + // Just using keccak256 directly is actually cheaper. + // forgefmt: disable-next-item + result := and( + withinRange, + eq( + keccak256( + // `subject + 0x20 + max(subjectLength - searchLength, 0)`. + add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))), + searchLength + ), + keccak256(add(search, 0x20), searchLength) + ) + ) + } + } + + /// @dev Returns `subject` repeated `times`. + function repeat(string memory subject, uint256 times) + internal + pure + returns (string memory result) + { + /// @solidity memory-safe-assembly + assembly { + let subjectLength := mload(subject) + if iszero(or(iszero(times), iszero(subjectLength))) { + subject := add(subject, 0x20) + result := mload(0x40) + let output := add(result, 0x20) + for {} 1 {} { + // Copy the `subject` one word at a time. + for { let o := 0 } 1 {} { + mstore(add(output, o), mload(add(subject, o))) + o := add(o, 0x20) + if iszero(lt(o, subjectLength)) { break } + } + output := add(output, subjectLength) + times := sub(times, 1) + if iszero(times) { break } + } + mstore(output, 0) // Zeroize the slot after the string. + let resultLength := sub(output, add(result, 0x20)) + mstore(result, resultLength) // Store the length. + // Allocate the memory. + mstore(0x40, add(result, add(resultLength, 0x20))) + } + } + } + + /// @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) + { + /// @solidity memory-safe-assembly + assembly { + let subjectLength := mload(subject) + if iszero(gt(subjectLength, end)) { end := subjectLength } + if iszero(gt(subjectLength, start)) { start := subjectLength } + if lt(start, end) { + result := mload(0x40) + let resultLength := sub(end, start) + mstore(result, resultLength) + subject := add(subject, start) + let w := not(0x1f) + // Copy the `subject` one word at a time, backwards. + for { let o := and(add(resultLength, 0x1f), w) } 1 {} { + mstore(add(result, o), mload(add(subject, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + // Zeroize the slot after the string. + mstore(add(add(result, 0x20), resultLength), 0) + // Allocate memory for the length and the bytes, + // rounded up to a multiple of 32. + mstore(0x40, add(result, and(add(resultLength, 0x3f), w))) + } + } + } + + /// @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) + { + 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) + { + /// @solidity memory-safe-assembly + assembly { + let subjectLength := mload(subject) + let searchLength := mload(search) + + if iszero(gt(searchLength, subjectLength)) { + subject := add(subject, 0x20) + search := add(search, 0x20) + result := add(mload(0x40), 0x20) + + let subjectStart := subject + let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1) + let h := 0 + if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } + let m := shl(3, sub(0x20, and(searchLength, 0x1f))) + let s := mload(search) + for {} 1 {} { + let t := mload(subject) + // Whether the first `searchLength % 32` bytes of + // `subject` and `search` matches. + if iszero(shr(m, xor(t, s))) { + if h { + if iszero(eq(keccak256(subject, searchLength), h)) { + subject := add(subject, 1) + if iszero(lt(subject, subjectSearchEnd)) { break } + continue + } + } + // Append to `result`. + mstore(result, sub(subject, subjectStart)) + result := add(result, 0x20) + // Advance `subject` by `searchLength`. + subject := add(subject, searchLength) + if searchLength { + if iszero(lt(subject, subjectSearchEnd)) { break } + continue + } + } + subject := add(subject, 1) + if iszero(lt(subject, subjectSearchEnd)) { break } + } + let resultEnd := result + // Assign `result` to the free memory pointer. + result := mload(0x40) + // Store the length of `result`. + mstore(result, shr(5, sub(resultEnd, add(result, 0x20)))) + // Allocate memory for result. + // We allocate one more word, so this array can be recycled for {split}. + mstore(0x40, add(resultEnd, 0x20)) + } + } + } + + /// @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) + { + uint256[] memory indices = indicesOf(subject, delimiter); + /// @solidity memory-safe-assembly + assembly { + let w := not(0x1f) + let indexPtr := add(indices, 0x20) + let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1))) + mstore(add(indicesEnd, w), mload(subject)) + mstore(indices, add(mload(indices), 1)) + let prevIndex := 0 + for {} 1 {} { + let index := mload(indexPtr) + mstore(indexPtr, 0x60) + if iszero(eq(index, prevIndex)) { + let element := mload(0x40) + let elementLength := sub(index, prevIndex) + mstore(element, elementLength) + // Copy the `subject` one word at a time, backwards. + for { let o := and(add(elementLength, 0x1f), w) } 1 {} { + mstore(add(element, o), mload(add(add(subject, prevIndex), o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + // Zeroize the slot after the string. + mstore(add(add(element, 0x20), elementLength), 0) + // Allocate memory for the length and the bytes, + // rounded up to a multiple of 32. + mstore(0x40, add(element, and(add(elementLength, 0x3f), w))) + // Store the `element` into the array. + mstore(indexPtr, element) + } + prevIndex := add(index, mload(delimiter)) + indexPtr := add(indexPtr, 0x20) + if iszero(lt(indexPtr, indicesEnd)) { break } + } + result := indices + if iszero(mload(delimiter)) { + result := add(indices, 0x20) + mstore(result, sub(mload(indices), 2)) + } + } + } + + /// @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) + { + /// @solidity memory-safe-assembly + assembly { + let w := not(0x1f) + result := mload(0x40) + let aLength := mload(a) + // Copy `a` one word at a time, backwards. + for { let o := and(add(aLength, 0x20), w) } 1 {} { + mstore(add(result, o), mload(add(a, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + let bLength := mload(b) + let output := add(result, aLength) + // Copy `b` one word at a time, backwards. + for { let o := and(add(bLength, 0x20), w) } 1 {} { + mstore(add(output, o), mload(add(b, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + let totalLength := add(aLength, bLength) + let last := add(add(result, 0x20), totalLength) + // Zeroize the slot after the string. + mstore(last, 0) + // Stores the length. + mstore(result, totalLength) + // Allocate memory for the length and the bytes, + // rounded up to a multiple of 32. + mstore(0x40, and(add(last, 0x1f), w)) + } + } + + /// @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) + { + /// @solidity memory-safe-assembly + assembly { + let length := mload(subject) + if length { + result := add(mload(0x40), 0x20) + subject := add(subject, 1) + let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff) + let w := not(0) + for { let o := length } 1 {} { + o := add(o, w) + let b := and(0xff, mload(add(subject, o))) + mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20))) + if iszero(o) { break } + } + result := mload(0x40) + mstore(result, length) // Store the length. + let last := add(add(result, 0x20), length) + mstore(last, 0) // Zeroize the slot after the string. + mstore(0x40, add(last, 0x20)) // Allocate the memory. + } + } + } + + /// @dev Returns a string from a small bytes32 string. + /// `s` must be null-terminated, or behavior will be undefined. + function fromSmallString(bytes32 s) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let n := 0 + for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'. + mstore(result, n) + let o := add(result, 0x20) + mstore(o, s) + mstore(add(o, n), 0) + mstore(0x40, add(result, 0x40)) + } + } + + /// @dev Returns the small string, with all bytes after the first null byte zeroized. + function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'. + mstore(0x00, s) + mstore(result, 0x00) + result := mload(0x00) + } + } + + /// @dev Returns the string as a normalized null-terminated small string. + function toSmallString(string memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(s) + if iszero(lt(result, 33)) { + mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`. + revert(0x1c, 0x04) + } + result := shl(shl(3, sub(32, result)), mload(add(s, result))) + } + } + + /// @dev Returns a lowercased copy of the string. + /// WARNING! This function is only compatible with 7-bit ASCII strings. + function lower(string memory subject) internal pure returns (string memory result) { + result = toCase(subject, false); + } + + /// @dev Returns an UPPERCASED copy of the string. + /// WARNING! This function is only compatible with 7-bit ASCII strings. + function upper(string memory subject) internal pure returns (string memory result) { + result = toCase(subject, true); + } + + /// @dev Escapes the string to be used within HTML tags. + function escapeHTML(string memory s) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + let end := add(s, mload(s)) + result := add(mload(0x40), 0x20) + // Store the bytes of the packed offsets and strides into the scratch space. + // `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6. + mstore(0x1f, 0x900094) + mstore(0x08, 0xc0000000a6ab) + // Store ""&'<>" into the scratch space. + mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b)) + for {} iszero(eq(s, end)) {} { + s := add(s, 1) + let c := and(mload(s), 0xff) + // Not in `["\"","'","&","<",">"]`. + if iszero(and(shl(c, 1), 0x500000c400000000)) { + mstore8(result, c) + result := add(result, 1) + continue + } + let t := shr(248, mload(c)) + mstore(result, mload(and(t, 0x1f))) + result := add(result, shr(5, t)) + } + let last := result + mstore(last, 0) // Zeroize the slot after the string. + result := mload(0x40) + mstore(result, sub(last, add(result, 0x20))) // Store the length. + mstore(0x40, add(last, 0x20)) // Allocate the memory. + } + } + + /// @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) + { + /// @solidity memory-safe-assembly + assembly { + let end := add(s, mload(s)) + result := add(mload(0x40), 0x20) + if addDoubleQuotes { + mstore8(result, 34) + result := add(1, result) + } + // Store "\\u0000" in scratch space. + // Store "0123456789abcdef" in scratch space. + // Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`. + // into the scratch space. + mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672) + // Bitmask for detecting `["\"","\\"]`. + let e := or(shl(0x22, 1), shl(0x5c, 1)) + for {} iszero(eq(s, end)) {} { + s := add(s, 1) + let c := and(mload(s), 0xff) + if iszero(lt(c, 0x20)) { + if iszero(and(shl(c, 1), e)) { + // Not in `["\"","\\"]`. + mstore8(result, c) + result := add(result, 1) + continue + } + mstore8(result, 0x5c) // "\\". + mstore8(add(result, 1), c) + result := add(result, 2) + continue + } + if iszero(and(shl(c, 1), 0x3700)) { + // Not in `["\b","\t","\n","\f","\d"]`. + mstore8(0x1d, mload(shr(4, c))) // Hex value. + mstore8(0x1e, mload(and(c, 15))) // Hex value. + mstore(result, mload(0x19)) // "\\u00XX". + result := add(result, 6) + continue + } + mstore8(result, 0x5c) // "\\". + mstore8(add(result, 1), mload(add(c, 8))) + result := add(result, 2) + } + if addDoubleQuotes { + mstore8(result, 34) + result := add(1, result) + } + let last := result + mstore(last, 0) // Zeroize the slot after the string. + result := mload(0x40) + mstore(result, sub(last, add(result, 0x20))) // Store the length. + mstore(0x40, add(last, 0x20)) // Allocate the memory. + } + } + + /// @dev Escapes the string to be used within double-quotes in a JSON. + function escapeJSON(string memory s) internal pure returns (string memory result) { + result = escapeJSON(s, false); + } + + /// @dev Returns whether `a` equals `b`. + function eq(string memory a, string memory b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) + } + } + + /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small string. + function eqs(string memory a, bytes32 b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + // These should be evaluated on compile time, as far as possible. + let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`. + let x := not(or(m, or(b, add(m, and(b, m))))) + let r := shl(7, iszero(iszero(shr(128, x)))) + r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x)))))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + // forgefmt: disable-next-item + result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))), + xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20))))) + } + } + + /// @dev Packs a single string with its length into a single word. + /// Returns `bytes32(0)` if the length is zero or greater than 31. + function packOne(string memory a) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + // We don't need to zero right pad the string, + // since this is our own custom non-standard packing scheme. + result := + mul( + // Load the length and the bytes. + mload(add(a, 0x1f)), + // `length != 0 && length < 32`. Abuses underflow. + // Assumes that the length is valid and within the block gas limit. + lt(sub(mload(a), 1), 0x1f) + ) + } + } + + /// @dev Unpacks a string packed using {packOne}. + /// Returns the empty string if `packed` is `bytes32(0)`. + /// If `packed` is not an output of {packOne}, the output behavior is undefined. + function unpackOne(bytes32 packed) internal pure returns (string memory result) { + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + result := mload(0x40) + // Allocate 2 words (1 for the length, 1 for the bytes). + mstore(0x40, add(result, 0x40)) + // Zeroize the length slot. + mstore(result, 0) + // Store the length and bytes. + mstore(add(result, 0x1f), packed) + // Right pad with zeroes. + mstore(add(add(result, 0x20), mload(result)), 0) + } + } + + /// @dev Packs two strings with their lengths into a single word. + /// Returns `bytes32(0)` if combined length is zero or greater than 30. + function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let aLength := mload(a) + // We don't need to zero right pad the strings, + // since this is our own custom non-standard packing scheme. + 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)) + ), + // `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) + ) + } + } + + /// @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) + { + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + resultA := mload(0x40) + resultB := add(resultA, 0x40) + // Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words. + mstore(0x40, add(resultB, 0x40)) + // Zeroize the length slots. + mstore(resultA, 0) + mstore(resultB, 0) + // Store the lengths and bytes. + mstore(add(resultA, 0x1f), packed) + mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA)))) + // Right pad with zeroes. + mstore(add(add(resultA, 0x20), mload(resultA)), 0) + mstore(add(add(resultB, 0x20), mload(resultB)), 0) + } + } + + /// @dev Directly returns `a` without copying. + function directReturn(string memory a) internal pure { + assembly { + // Assumes that the string does not start from the scratch space. + let retStart := sub(a, 0x20) + let retUnpaddedSize := add(mload(a), 0x40) + // Right pad with zeroes. Just in case the string is produced + // by a method that doesn't zero right pad. + mstore(add(retStart, retUnpaddedSize), 0) + // Store the return offset. + mstore(retStart, 0x20) + // End the transaction, returning the string. + return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize))) + } + } +} \ No newline at end of file diff --git a/src/libraries/WebAuthn.sol b/src/libraries/WebAuthn.sol new file mode 100644 index 0000000..7a5a113 --- /dev/null +++ b/src/libraries/WebAuthn.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {FCL_ecdsa} from "./FCL_ecdsa.sol"; +import {FCL_Elliptic_ZZ} from "./FCL_elliptic.sol"; +import {Base64} from "./Base64.sol"; +import {LibString} from "./LibString.sol"; + +/// @title WebAuthn +/// +/// @notice A library for verifying WebAuthn Authentication Assertions, built off the work +/// of Daimo. +/// +/// @dev Attempts to use the RIP-7212 precompile for signature verification. +/// If precompile verification fails, it falls back to FreshCryptoLib. +/// +/// @author Coinbase (https://github.com/base-org/webauthn-sol) +/// @author Daimo (https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol) +library WebAuthn { + using LibString for string; + + struct WebAuthnAuth { + /// @dev The WebAuthn authenticator data. + /// See https://www.w3.org/TR/webauthn-2/#dom-authenticatorassertionresponse-authenticatordata. + bytes authenticatorData; + /// @dev The WebAuthn client data JSON. + /// See https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson. + string clientDataJSON; + /// @dev The index at which "challenge":"..." occurs in `clientDataJSON`. + uint256 challengeIndex; + /// @dev The index at which "type":"..." occurs in `clientDataJSON`. + uint256 typeIndex; + /// @dev The r value of secp256r1 signature + uint256 r; + /// @dev The s value of secp256r1 signature + uint256 s; + } + + /// @dev Bit 0 of the authenticator data struct, corresponding to the "User Present" bit. + /// See https://www.w3.org/TR/webauthn-2/#flags. + bytes1 private constant _AUTH_DATA_FLAGS_UP = 0x01; + + /// @dev Bit 2 of the authenticator data struct, corresponding to the "User Verified" bit. + /// See https://www.w3.org/TR/webauthn-2/#flags. + bytes1 private constant _AUTH_DATA_FLAGS_UV = 0x04; + + /// @dev Secp256r1 curve order / 2 used as guard to prevent signature malleability issue. + uint256 private constant _P256_N_DIV_2 = FCL_Elliptic_ZZ.n / 2; + + /// @dev The precompiled contract address to use for signature verification in the “secp256r1” elliptic curve. + /// See https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md. + address private constant _VERIFIER = address(0x100); + + /// @dev The expected type (hash) in the client data JSON when verifying assertion signatures. + /// See https://www.w3.org/TR/webauthn-2/#dom-collectedclientdata-type + bytes32 private constant _EXPECTED_TYPE_HASH = keccak256('"type":"webauthn.get"'); + + /// + /// @notice Verifies a Webauthn Authentication Assertion as described + /// in https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. + /// + /// @dev We do not verify all the steps as described in the specification, only ones relevant to our context. + /// Please carefully read through this list before usage. + /// + /// Specifically, we do verify the following: + /// - Verify that authenticatorData (which comes from the authenticator, such as iCloud Keychain) indicates + /// a well-formed assertion with the user present bit set. If `requireUV` is set, checks that the authenticator + /// enforced user verification. User verification should be required if, and only if, options.userVerification + /// is set to required in the request. + /// - Verifies that the client JSON is of type "webauthn.get", i.e. the client was responding to a request to + /// assert authentication. + /// - Verifies that the client JSON contains the requested challenge. + /// - Verifies that (r, s) constitute a valid signature over both the authenicatorData and client JSON, for public + /// key (x, y). + /// + /// We make some assumptions about the particular use case of this verifier, so we do NOT verify the following: + /// - Does NOT verify that the origin in the `clientDataJSON` matches the Relying Party's origin: tt is considered + /// the authenticator's responsibility to ensure that the user is interacting with the correct RP. This is + /// enforced by most high quality authenticators properly, particularly the iCloud Keychain and Google Password + /// Manager were tested. + /// - Does NOT verify That `topOrigin` in `clientDataJSON` is well-formed: We assume it would never be present, i.e. + /// the credentials are never used in a cross-origin/iframe context. The website/app set up should disallow + /// cross-origin usage of the credentials. This is the default behaviour for created credentials in common settings. + /// - Does NOT verify that the `rpIdHash` in `authenticatorData` is the SHA-256 hash of the RP ID expected by the Relying + /// Party: this means that we rely on the authenticator to properly enforce credentials to be used only by the correct RP. + /// This is generally enforced with features like Apple App Site Association and Google Asset Links. To protect from + /// edge cases in which a previously-linked RP ID is removed from the authorised RP IDs, we recommend that messages + /// signed by the authenticator include some expiry mechanism. + /// - Does NOT verify the credential backup state: this assumes the credential backup state is NOT used as part of Relying + /// Party business logic or policy. + /// - Does NOT verify the values of the client extension outputs: this assumes that the Relying Party does not use client + /// extension outputs. + /// - Does NOT verify the signature counter: signature counters are intended to enable risk scoring for the Relying Party. + /// This assumes risk scoring is not used as part of Relying Party business logic or policy. + /// - Does NOT verify the attestation object: this assumes that response.attestationObject is NOT present in the response, + /// i.e. the RP does not intend to verify an attestation. + /// + /// @param challenge The challenge that was provided by the relying party. + /// @param requireUV A boolean indicating whether user verification is required. + /// @param webAuthnAuth The `WebAuthnAuth` struct. + /// @param x The x coordinate of the public key. + /// @param y The y coordinate of the public key. + /// + /// @return `true` if the authentication assertion passed validation, else `false`. + function verify(bytes memory challenge, bool requireUV, WebAuthnAuth memory webAuthnAuth, uint256 x, uint256 y) + internal + view + returns (bool) + { + if (webAuthnAuth.s > _P256_N_DIV_2) { + // guard against signature malleability + return false; + } + + // 11. Verify that the value of C.type is the string webauthn.get. + // bytes("type":"webauthn.get").length = 21 + string memory _type = webAuthnAuth.clientDataJSON.slice(webAuthnAuth.typeIndex, webAuthnAuth.typeIndex + 21); + if (keccak256(bytes(_type)) != _EXPECTED_TYPE_HASH) { + return false; + } + + // 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); + if (keccak256(bytes(actualChallenge)) != keccak256(expectedChallenge)) { + return false; + } + + // Skip 13., 14., 15. + + // 16. Verify that the UP bit of the flags in authData is set. + if (webAuthnAuth.authenticatorData[32] & _AUTH_DATA_FLAGS_UP != _AUTH_DATA_FLAGS_UP) { + return false; + } + + // 17. If user verification is required for this assertion, verify that the User Verified bit of the flags in + // authData is set. + if (requireUV && (webAuthnAuth.authenticatorData[32] & _AUTH_DATA_FLAGS_UV) != _AUTH_DATA_FLAGS_UV) { + return false; + } + + // skip 18. + + // 19. Let hash be the result of computing a hash over the cData using SHA-256. + bytes32 clientDataJSONHash = sha256(bytes(webAuthnAuth.clientDataJSON)); + + // 20. Using credentialPublicKey, verify that sig is a valid signature over the binary concatenation of authData + // and hash. + bytes32 messageHash = sha256(abi.encodePacked(webAuthnAuth.authenticatorData, clientDataJSONHash)); + bytes memory args = abi.encode(messageHash, webAuthnAuth.r, webAuthnAuth.s, x, y); + // try the RIP-7212 precompile address + (bool success, bytes memory ret) = _VERIFIER.staticcall(args); + // staticcall will not revert if address has no code + // check return length + // note that even if precompile exists, ret.length is 0 when verification returns false + // so an invalid signature will be checked twice: once by the precompile and once by FCL. + // Ideally this signature failure is simulated offchain and no one actually pay this gas. + bool valid = ret.length > 0; + if (success && valid) return abi.decode(ret, (uint256)) == 1; + + return FCL_ecdsa.ecdsa_verify(messageHash, webAuthnAuth.r, webAuthnAuth.s, x, y); + } +} diff --git a/src/modules/Passkey.sol b/src/modules/Passkey.sol index d7063f0..866d4d3 100644 --- a/src/modules/Passkey.sol +++ b/src/modules/Passkey.sol @@ -4,19 +4,24 @@ pragma solidity ^0.8.0; import "../interfaces/IModule.sol"; import "../interfaces/IWallet.sol"; -import {WebAuthn} from "webauthn-sol/WebAuthn.sol"; +import {WebAuthn} from "../libraries/WebAuthn.sol"; contract PasskeyModuleFactory { - function create(bytes32 salt) external returns (PasskeyModule) { - return new PasskeyModule{salt: salt}(); + function create(uint256 x, uint256 y, bytes32 salt) external returns (PasskeyModule) { + return new PasskeyModule{salt: salt}(x, y); } } contract PasskeyModule is IModule { - constructor() { + uint256 immutable public x; + uint256 immutable public y; + + constructor(uint256 inputX, uint256 inputY) { + x = inputX; + y = inputY; } - function validateUserOp(UserOperation calldata userOp, bytes32) + function validateUserOp(UserOperation calldata userOp, bytes32 digest) external view override @@ -24,14 +29,26 @@ contract PasskeyModule is IModule { { address module = address(bytes20(userOp.signature[:20])); require(module == address(this), "invalid module"); - return 0; - } - function callback(UserOperation calldata userOp, bytes32) external override { + bytes memory signature = userOp.signature[20:]; + + if (this.isValidSignature.selector == isValidSignature(digest, signature)) { + return 0; + } + return 1; } - function isValidSignature(bytes32 digest, bytes calldata signature) public view override returns (bytes4 magicValue) { - return 0x0000; + 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)); + + 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