diff --git a/README.md b/README.md
index e06d9f3d..b4d95dbf 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,6 @@ Documentation: (https://github.com/bcnmy/nexus/wiki)
## Getting Started
-To kickstart, follow these steps:
To kickstart, follow these steps:
### Prerequisites
@@ -111,11 +110,10 @@ Generate documentation from NatSpec comments.
### 🚀 Deploy Contracts
-```bash
-yarn run deploy:hardhat --network hardhat
-```
+Nexus contracts are pre-deployed on most EVM chains.
+Please see the addresses [here](https://docs.biconomy.io/contractsAndAudits).
-Deploys contracts onto the blockchain network.
+If you need to deploy Nexus on your own chain or you want to deploy the contracts with different addresses, please see [this](https://github.com/bcnmy/nexus/tree/deploy-v1.0.1/scripts/bash-deploy) script. Or the same script on differnet deploy branches.
### 🎨 Lint Code
@@ -148,20 +146,7 @@ To generate reports of the storage layout for potential upgrades safety using `h
| Auditor | Date | Final Report Link |
| ---------------- | ---------- | ----------------------- |
| CodeHawks-Cyfrin | 17-09-2024 | [View Report](./audits/CodeHawks-Cyfrin-17-09-2024.pdf) |
-| Firm Name | DD-MM-YYYY | [View Report](./audits) |
-
-## 🏆 Biconomy Champions League 🏆
-
-Welcome to the Champions League, a place where your contributions to Biconomy are celebrated and immortalized in our Hall of Fame. This elite group showcases individuals who have significantly advanced our mission, from enhancing code efficiency to strengthening security, and enriching our documentation.
-
-### Champions Roster
-
-| 🍊 Contributor | 🛡️ Domain |
-| -------------- | ----------------- |
-| @user1 | Code Optimization |
-| @user2 | Security |
-| @user3 | Documentation |
-| ... | ... |
+| Spearbit | 10/11-2024 | [View Report](./audits/report-cantinacode-biconomy-0708-final.pdf) / [View Add-on](./audits/report-cantinacode-biconomy-erc7739-addon-final.pdf) |
### Entering the League
diff --git a/audits/assets/DefiSafety.Process_Quality_Review.Biconomy_Nexus.pdf b/audits/assets/DefiSafety.Process_Quality_Review.Biconomy_Nexus.pdf
new file mode 100644
index 00000000..4b0111a1
Binary files /dev/null and b/audits/assets/DefiSafety.Process_Quality_Review.Biconomy_Nexus.pdf differ
diff --git a/audits/report-cantinacode-biconomy-erc7739-addon-final.pdf b/audits/report-cantinacode-biconomy-erc7739-addon-final.pdf
new file mode 100644
index 00000000..02cb4bd1
Binary files /dev/null and b/audits/report-cantinacode-biconomy-erc7739-addon-final.pdf differ
diff --git a/contracts/Nexus.sol b/contracts/Nexus.sol
index c6e83e75..265dc1de 100644
--- a/contracts/Nexus.sol
+++ b/contracts/Nexus.sol
@@ -18,11 +18,10 @@ import { ExecLib } from "./lib/ExecLib.sol";
import { INexus } from "./interfaces/INexus.sol";
import { BaseAccount } from "./base/BaseAccount.sol";
import { IERC7484 } from "./interfaces/IERC7484.sol";
-import { IERC7739 } from "./interfaces/IERC7739.sol";
import { ModuleManager } from "./base/ModuleManager.sol";
import { ExecutionHelper } from "./base/ExecutionHelper.sol";
import { IValidator } from "./interfaces/modules/IValidator.sol";
-import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_HOOK, MODULE_TYPE_MULTI, SUPPORTS_NESTED_TYPED_DATA_SIGN } from "./types/Constants.sol";
+import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_HOOK, MODULE_TYPE_MULTI, SUPPORTS_ERC7739 } from "./types/Constants.sol";
import { ModeLib, ExecutionMode, ExecType, CallType, CALLTYPE_BATCH, CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL, EXECTYPE_DEFAULT, EXECTYPE_TRY } from "./lib/ModeLib.sol";
import { NonceLib } from "./lib/NonceLib.sol";
import { SentinelListLib, SENTINEL, ZERO_ADDRESS } from "sentinellist/SentinelList.sol";
@@ -39,6 +38,7 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
using ModeLib for ExecutionMode;
using ExecLib for bytes;
using NonceLib for uint256;
+ using SentinelListLib for SentinelListLib.SentinelList;
/// @dev The timelock period for emergency hook uninstallation.
uint256 internal constant _EMERGENCY_TIMELOCK = 1 days;
@@ -226,6 +226,15 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
/// bytes4(keccak256("isValidSignature(bytes32,bytes)") = 0x1626ba7e
/// @dev Delegates the validation to a validator module specified within the signature data.
function isValidSignature(bytes32 hash, bytes calldata signature) external view virtual override returns (bytes4) {
+ // Handle potential ERC7739 support detection request
+ if (signature.length == 0) {
+ // Forces the compiler to optimize for smaller bytecode size.
+ if (uint256(hash) == (~signature.length / 0xffff) * 0x7739) {
+ return checkERC7739Support(hash, signature);
+ }
+ }
+ // else proceed with normal signature verification
+
// First 20 bytes of data will be validator address and rest of the bytes is complete signature.
address validator = address(bytes20(signature[0:20]));
require(_isValidatorInstalled(validator), ValidatorNotInstalled(validator));
@@ -320,19 +329,30 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
UUPSUpgradeable.upgradeToAndCall(newImplementation, data);
}
- /// @dev For automatic detection that the smart account supports the nested EIP-712 workflow
- /// Offchain usage only
- /// Iterates over all the validators
- function supportsNestedTypedDataSign() public view virtual returns (bytes32) {
- SentinelListLib.SentinelList storage validators = _getAccountStorage().validators;
- address next = validators.entries[SENTINEL];
- while (next != ZERO_ADDRESS && next != SENTINEL) {
- try IERC7739(next).supportsNestedTypedDataSign() returns (bytes32 res) {
- if (res == SUPPORTS_NESTED_TYPED_DATA_SIGN) return SUPPORTS_NESTED_TYPED_DATA_SIGN;
- } catch {}
- next = validators.entries[next];
+ /// @dev For automatic detection that the smart account supports the ERC7739 workflow
+ /// Iterates over all the validators but only if this is a detection request
+ /// ERC-7739 spec assumes that if the account doesn't support ERC-7739
+ /// it will try to handle the detection request as it was normal sig verification
+ /// request and will return 0xffffffff since it won't be able to verify the 0x signature
+ /// against 0x7739...7739 hash.
+ /// So this approach is consistent with the ERC-7739 spec.
+ /// If no validator supports ERC-7739, this function returns false
+ /// thus the account will proceed with normal signature verification
+ /// and return 0xffffffff as a result.
+ function checkERC7739Support(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
+ bytes4 result;
+ unchecked {
+ SentinelListLib.SentinelList storage validators = _getAccountStorage().validators;
+ address next = validators.entries[SENTINEL];
+ while (next != ZERO_ADDRESS && next != SENTINEL) {
+ bytes4 support = IValidator(next).isValidSignatureWithSender(msg.sender, hash, signature);
+ if (bytes2(support) == bytes2(SUPPORTS_ERC7739) && support > result) {
+ result = support;
+ }
+ next = validators.getNext(next);
+ }
}
- return bytes4(0xffffffff);
+ return result == bytes4(0) ? bytes4(0xffffffff) : result;
}
/// @dev Ensures that only authorized callers can upgrade the smart contract implementation.
@@ -343,6 +363,6 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
/// @dev EIP712 domain name and version.
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
name = "Nexus";
- version = "1.0.0";
+ version = "1.0.1";
}
}
diff --git a/contracts/base/ERC7739Validator.sol b/contracts/base/ERC7739Validator.sol
deleted file mode 100644
index 1c97dae6..00000000
--- a/contracts/base/ERC7739Validator.sol
+++ /dev/null
@@ -1,310 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.27;
-
-import { IERC7739 } from "../interfaces/IERC7739.sol";
-import { EIP712 } from "solady/utils/EIP712.sol";
-
-/// @title ERC-7739: Nested Typed Data Sign Support for ERC-7579 Validators
-
-abstract contract ERC7739Validator is IERC7739 {
- /// @dev `keccak256("PersonalSign(bytes prefixed)")`.
- bytes32 internal constant _PERSONAL_SIGN_TYPEHASH = 0x983e65e5148e570cd828ead231ee759a8d7958721a768f93bc4483ba005c32de;
- bytes32 internal constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
-
- /// @dev For automatic detection that the smart account supports the nested EIP-712 workflow.
- /// By default, it returns `bytes32(bytes4(keccak256("supportsNestedTypedDataSign()")))`,
- /// denoting support for the default behavior, as implemented in
- /// `_erc1271IsValidSignatureViaNestedEIP712`, which is called in `isValidSignature`.
- /// Future extensions should return a different non-zero `result` to denote different behavior.
- /// This method intentionally returns bytes32 to allow freedom for future extensions.
- function supportsNestedTypedDataSign() public view virtual returns (bytes32 result) {
- result = bytes4(0xd620c85a);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- INTERNAL
- //////////////////////////////////////////////////////////////////////////*/
-
- /// @dev Returns whether the `signature` is valid for the `hash.
- /// Use this in your validator's `isValidSignatureWithSender` implementation.
- function _erc1271IsValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature) internal view virtual returns (bool) {
- return
- _erc1271IsValidSignatureViaSafeCaller(sender, hash, signature) ||
- _erc1271IsValidSignatureViaNestedEIP712(hash, signature) ||
- _erc1271IsValidSignatureViaRPC(hash, signature);
- }
-
- /// @dev Returns whether the `msg.sender` is considered safe, such
- /// that we don't need to use the nested EIP-712 workflow.
- /// Override to return true for more callers.
- /// See: https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU
- function _erc1271CallerIsSafe(address sender) internal view virtual returns (bool) {
- // The canonical `MulticallerWithSigner` at 0x000000000000D9ECebf3C23529de49815Dac1c4c
- // is known to include the account in the hash to be signed.
- return sender == 0x000000000000D9ECebf3C23529de49815Dac1c4c;
- }
-
- /// @dev Returns whether the `hash` and `signature` are valid.
- /// Obtains the authorized signer's credentials and calls some
- /// module's specific internal function to validate the signature
- /// against credentials.
- /// Override for your module's custom logic.
- function _erc1271IsValidSignatureNowCalldata(bytes32 hash, bytes calldata signature) internal view virtual returns (bool);
-
- /// @dev Unwraps and returns the signature.
- function _erc1271UnwrapSignature(bytes calldata signature) internal view virtual returns (bytes calldata result) {
- result = signature;
- /// @solidity memory-safe-assembly
- assembly {
- // Unwraps the ERC6492 wrapper if it exists.
- // See: https://eips.ethereum.org/EIPS/eip-6492
- if eq(
- calldataload(add(result.offset, sub(result.length, 0x20))),
- mul(0x6492, div(not(mload(0x60)), 0xffff)) // `0x6492...6492`.
- ) {
- let o := add(result.offset, calldataload(add(result.offset, 0x40)))
- result.length := calldataload(o)
- result.offset := add(o, 0x20)
- }
- }
- }
-
- /// @dev Performs the signature validation without nested EIP-712 if the caller is
- /// a safe caller. A safe caller must include the address of this account in the hash.
- function _erc1271IsValidSignatureViaSafeCaller(
- address sender,
- bytes32 hash,
- bytes calldata signature
- ) internal view virtual returns (bool result) {
- if (_erc1271CallerIsSafe(sender)) result = _erc1271IsValidSignatureNowCalldata(hash, signature);
- }
-
- /// @dev ERC1271 signature validation (Nested EIP-712 workflow).
- ///
- /// This uses ECDSA recovery by default (see: `_erc1271IsValidSignatureNowCalldata`).
- /// It also uses a nested EIP-712 approach to prevent signature replays when a single EOA
- /// owns multiple smart contract accounts,
- /// while still enabling wallet UIs (e.g. Metamask) to show the EIP-712 values.
- ///
- /// Crafted for phishing resistance, efficiency, flexibility.
- /// __________________________________________________________________________________________
- ///
- /// Glossary:
- ///
- /// - `APP_DOMAIN_SEPARATOR`: The domain separator of the `hash` passed in by the application.
- /// Provided by the front end. Intended to be the domain separator of the contract
- /// that will call `isValidSignature` on this account.
- ///
- /// - `ACCOUNT_DOMAIN_SEPARATOR`: The domain separator of this account.
- /// See: `EIP712._domainSeparator()`.
- /// __________________________________________________________________________________________
- ///
- /// For the `TypedDataSign` workflow, the final hash will be:
- /// ```
- /// keccak256(\x19\x01 ‖ APP_DOMAIN_SEPARATOR ‖
- /// hashStruct(TypedDataSign({
- /// contents: hashStruct(originalStruct),
- /// name: keccak256(bytes(eip712Domain().name)),
- /// version: keccak256(bytes(eip712Domain().version)),
- /// chainId: eip712Domain().chainId,
- /// verifyingContract: eip712Domain().verifyingContract,
- /// salt: eip712Domain().salt,
- /// extensions: keccak256(abi.encodePacked(eip712Domain().extensions))
- /// }))
- /// )
- /// ```
- /// where `‖` denotes the concatenation operator for bytes.
- /// The order of the fields is important: `contents` comes before `name`.
- ///
- /// The signature will be `r ‖ s ‖ v ‖
- /// APP_DOMAIN_SEPARATOR ‖ contents ‖ contentsType ‖ uint16(contentsType.length)`,
- /// where `contents` is the bytes32 struct hash of the original struct.
- ///
- /// The `APP_DOMAIN_SEPARATOR` and `contents` will be used to verify if `hash` is indeed correct.
- /// __________________________________________________________________________________________
- ///
- /// For the `PersonalSign` workflow, the final hash will be:
- /// ```
- /// keccak256(\x19\x01 ‖ ACCOUNT_DOMAIN_SEPARATOR ‖
- /// hashStruct(PersonalSign({
- /// prefixed: keccak256(bytes(\x19Ethereum Signed Message:\n ‖
- /// base10(bytes(someString).length) ‖ someString))
- /// }))
- /// )
- /// ```
- /// where `‖` denotes the concatenation operator for bytes.
- ///
- /// The `PersonalSign` type hash will be `keccak256("PersonalSign(bytes prefixed)")`.
- /// The signature will be `r ‖ s ‖ v`.
- /// __________________________________________________________________________________________
- ///
- /// For demo and typescript code, see:
- /// - https://github.com/junomonster/nested-eip-712
- /// - https://github.com/frangio/eip712-wrapper-for-eip1271
- ///
- /// Their nomenclature may differ from ours, although the high-level idea is similar.
- ///
- /// Of course, if you have control over the codebase of the wallet client(s) too,
- /// you can choose a more minimalistic signature scheme like
- /// `keccak256(abi.encode(address(this), hash))` instead of all these acrobatics.
- /// All these are just for widespread out-of-the-box compatibility with other wallet clients.
- /// We want to create bazaars, not walled castles.
- /// And we'll use push the Turing Completeness of the EVM to the limits to do so.
- function _erc1271IsValidSignatureViaNestedEIP712(bytes32 hash, bytes calldata signature) internal view virtual returns (bool result) {
- bytes32 t = _typedDataSignFieldsForAccount(msg.sender);
- /// @solidity memory-safe-assembly
- assembly {
- let m := mload(0x40) // Cache the free memory pointer.
- // `c` is `contentsType.length`, which is stored in the last 2 bytes of the signature.
- let c := shr(240, calldataload(add(signature.offset, sub(signature.length, 2))))
- for {} 1 {} {
- let l := add(0x42, c) // Total length of appended data (32 + 32 + c + 2).
- let o := add(signature.offset, sub(signature.length, l)) // Offset of appended data.
- mstore(0x00, 0x1901) // Store the "\x19\x01" prefix.
- calldatacopy(0x20, o, 0x40) // Copy the `APP_DOMAIN_SEPARATOR` and `contents` struct hash.
- // Use the `PersonalSign` workflow if the reconstructed hash doesn't match,
- // or if the appended data is invalid, i.e.
- // `appendedData.length > signature.length || contentsType.length == 0`.
- if or(xor(keccak256(0x1e, 0x42), hash), or(lt(signature.length, l), iszero(c))) {
- t := 0 // Set `t` to 0, denoting that we need to `hash = _hashTypedData(hash)`.
- mstore(t, _PERSONAL_SIGN_TYPEHASH)
- mstore(0x20, hash) // Store the `prefixed`.
- hash := keccak256(t, 0x40) // Compute the `PersonalSign` struct hash.
- break
- }
- // Else, use the `TypedDataSign` workflow.
- // `TypedDataSign({ContentsName} contents,bytes1 fields,...){ContentsType}`.
- mstore(m, "TypedDataSign(") // Store the start of `TypedDataSign`'s type encoding.
- let p := add(m, 0x0e) // Advance 14 bytes to skip "TypedDataSign(".
- calldatacopy(p, add(o, 0x40), c) // Copy `contentsType` to extract `contentsName`.
- // `d & 1 == 1` means that `contentsName` is invalid.
- let d := shr(byte(0, mload(p)), 0x7fffffe000000000000010000000000) // Starts with `[a-z(]`.
- // Store the end sentinel '(', and advance `p` until we encounter a '(' byte.
- for {
- mstore(add(p, c), 40)
- } iszero(eq(byte(0, mload(p)), 40)) {
- p := add(p, 1)
- } {
- d := or(shr(byte(0, mload(p)), 0x120100000001), d) // Has a byte in ", )\x00".
- }
- mstore(p, " contents,bytes1 fields,string n") // Store the rest of the encoding.
- mstore(add(p, 0x20), "ame,string version,uint256 chain")
- mstore(add(p, 0x40), "Id,address verifyingContract,byt")
- mstore(add(p, 0x60), "es32 salt,uint256[] extensions)")
- p := add(p, 0x7f)
- calldatacopy(p, add(o, 0x40), c) // Copy `contentsType`.
- // Fill in the missing fields of the `TypedDataSign`.
- calldatacopy(t, o, 0x40) // Copy the `contents` struct hash to `add(t, 0x20)`.
- mstore(t, keccak256(m, sub(add(p, c), m))) // Store `typedDataSignTypehash`.
- // The "\x19\x01" prefix is already at 0x00.
- // `APP_DOMAIN_SEPARATOR` is already at 0x20.
- mstore(0x40, keccak256(t, 0x120)) // `hashStruct(typedDataSign)`.
- // Compute the final hash, corrupted if `contentsName` is invalid.
- hash := keccak256(0x1e, add(0x42, and(1, d)))
- signature.length := sub(signature.length, l) // Truncate the signature.
- break
- }
- mstore(0x40, m) // Restore the free memory pointer.
- }
- if (t == bytes32(0)) hash = _hashTypedDataForAccount(msg.sender, hash); // `PersonalSign` workflow.
- result = _erc1271IsValidSignatureNowCalldata(hash, signature);
- }
-
- /// @dev Performs the signature validation without nested EIP-712 to allow for easy sign ins.
- /// This function must always return false or revert if called on-chain.
- function _erc1271IsValidSignatureViaRPC(bytes32 hash, bytes calldata signature) internal view virtual returns (bool result) {
- // Non-zero gasprice is a heuristic to check if a call is on-chain,
- // but we can't fully depend on it because it can be manipulated.
- // See: https://x.com/NoahCitron/status/1580359718341484544
- if (tx.gasprice == uint256(0)) {
- /// @solidity memory-safe-assembly
- assembly {
- mstore(gasprice(), gasprice())
- // See: https://gist.github.com/Vectorized/3c9b63524d57492b265454f62d895f71
- let b := 0x000000000000378eDCD5B5B0A24f5342d8C10485 // Basefee contract,
- pop(staticcall(0xffff, b, codesize(), gasprice(), gasprice(), 0x20))
- // If `gasprice < basefee`, the call cannot be on-chain, and we can skip the gas burn.
- if iszero(mload(gasprice())) {
- let m := mload(0x40) // Cache the free memory pointer.
- mstore(gasprice(), 0x1626ba7e) // `isValidSignature(bytes32,bytes)`.
- mstore(0x20, b) // Recycle `b` to denote if we need to burn gas.
- mstore(0x40, 0x40)
- let gasToBurn := or(add(0xffff, gaslimit()), gaslimit())
- // Burns gas computationally efficiently. Also, requires that `gas > gasToBurn`.
- if or(eq(hash, b), lt(gas(), gasToBurn)) {
- invalid()
- }
- // Make a call to this with `b`, efficiently burning the gas provided.
- // No valid transaction can consume more than the gaslimit.
- // See: https://ethereum.github.io/yellowpaper/paper.pdf
- // Most RPCs perform calls with a gas budget greater than the gaslimit.
- pop(staticcall(gasToBurn, address(), 0x1c, 0x64, gasprice(), gasprice()))
- mstore(0x40, m) // Restore the free memory pointer.
- }
- }
- result = _erc1271IsValidSignatureNowCalldata(hash, signature);
- }
- }
-
- /// @dev For use in `_erc1271IsValidSignatureViaNestedEIP712`,
- function _typedDataSignFieldsForAccount(address account) private view returns (bytes32 m) {
- (
- bytes1 fields,
- string memory name,
- string memory version,
- uint256 chainId,
- address verifyingContract,
- bytes32 salt,
- uint256[] memory extensions
- ) = EIP712(account).eip712Domain();
- /// @solidity memory-safe-assembly
- assembly {
- m := mload(0x40) // Grab the free memory pointer.
- mstore(0x40, add(m, 0x120)) // Allocate the memory.
- // Skip 2 words for the `typedDataSignTypehash` and `contents` struct hash.
- mstore(add(m, 0x40), shl(248, byte(0, fields)))
- mstore(add(m, 0x60), keccak256(add(name, 0x20), mload(name)))
- mstore(add(m, 0x80), keccak256(add(version, 0x20), mload(version)))
- mstore(add(m, 0xa0), chainId)
- mstore(add(m, 0xc0), shr(96, shl(96, verifyingContract)))
- mstore(add(m, 0xe0), salt)
- mstore(add(m, 0x100), keccak256(add(extensions, 0x20), shl(5, mload(extensions))))
- }
- }
-
- /// @notice Hashes typed data according to eip-712
- /// Uses account's domain separator
- /// @param account the smart account, who's domain separator will be used
- /// @param structHash the typed data struct hash
- function _hashTypedDataForAccount(address account, bytes32 structHash) private view returns (bytes32 digest) {
- (
- ,
- /*bytes1 fields*/ string memory name,
- string memory version,
- uint256 chainId,
- address verifyingContract /*bytes32 salt*/ /*uint256[] memory extensions*/,
- ,
-
- ) = EIP712(account).eip712Domain();
-
- /// @solidity memory-safe-assembly
- assembly {
- //Rebuild domain separator out of 712 domain
- let m := mload(0x40) // Load the free memory pointer.
- mstore(m, _DOMAIN_TYPEHASH)
- mstore(add(m, 0x20), keccak256(add(name, 0x20), mload(name))) // Name hash.
- mstore(add(m, 0x40), keccak256(add(version, 0x20), mload(version))) // Version hash.
- mstore(add(m, 0x60), chainId)
- mstore(add(m, 0x80), verifyingContract)
- digest := keccak256(m, 0xa0) //domain separator
-
- // Hash typed data
- mstore(0x00, 0x1901000000000000) // Store "\x19\x01".
- mstore(0x1a, digest) // Store the domain separator.
- mstore(0x3a, structHash) // Store the struct hash.
- digest := keccak256(0x18, 0x42)
- // Restore the part of the free memory slot that was overwritten.
- mstore(0x3a, 0)
- }
- }
-}
diff --git a/contracts/interfaces/IERC7739.sol b/contracts/interfaces/IERC7739.sol
deleted file mode 100644
index 51e73547..00000000
--- a/contracts/interfaces/IERC7739.sol
+++ /dev/null
@@ -1,6 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.27;
-
-interface IERC7739 {
- function supportsNestedTypedDataSign() external view returns (bytes32);
-}
diff --git a/contracts/lib/BootstrapLib.sol b/contracts/lib/BootstrapLib.sol
index c9b45297..5c614ec9 100644
--- a/contracts/lib/BootstrapLib.sol
+++ b/contracts/lib/BootstrapLib.sol
@@ -15,7 +15,7 @@ library BootstrapLib {
/// @param module The address of the module.
/// @param data The initialization data for the module.
/// @return config A BootstrapConfig structure containing the module and its data.
- function createSingleConfig(address module, bytes memory data) public pure returns (BootstrapConfig memory config) {
+ function createSingleConfig(address module, bytes memory data) internal pure returns (BootstrapConfig memory config) {
config.module = module;
config.data = data;
}
@@ -24,7 +24,7 @@ library BootstrapLib {
/// @param module The address of the module.
/// @param data The initialization data for the module.
/// @return config An array containing a single BootstrapConfig structure.
- function createArrayConfig(address module, bytes memory data) public pure returns (BootstrapConfig[] memory config) {
+ function createArrayConfig(address module, bytes memory data) internal pure returns (BootstrapConfig[] memory config) {
config = new BootstrapConfig[](1);
config[0].module = module;
config[0].data = data;
@@ -34,7 +34,7 @@ library BootstrapLib {
/// @param modules An array of module addresses.
/// @param datas An array of initialization data for each module.
/// @return configs An array of BootstrapConfig structures.
- function createMultipleConfigs(address[] memory modules, bytes[] memory datas) public pure returns (BootstrapConfig[] memory configs) {
+ function createMultipleConfigs(address[] memory modules, bytes[] memory datas) internal pure returns (BootstrapConfig[] memory configs) {
require(modules.length == datas.length, "BootstrapLib: length mismatch");
configs = new BootstrapConfig[](modules.length);
diff --git a/contracts/mocks/MockValidator.sol b/contracts/mocks/MockValidator.sol
index e7fb8b66..5a216f4f 100644
--- a/contracts/mocks/MockValidator.sol
+++ b/contracts/mocks/MockValidator.sol
@@ -8,7 +8,7 @@ import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOp
import { ECDSA } from "solady/utils/ECDSA.sol";
import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
-import { ERC7739Validator } from "../base/ERC7739Validator.sol";
+import { ERC7739Validator } from "erc7739Validator/ERC7739Validator.sol";
contract MockValidator is ERC7739Validator {
mapping(address => address) public smartAccountOwners;
@@ -24,15 +24,7 @@ contract MockValidator is ERC7739Validator {
bytes calldata signature
) external view virtual returns (bytes4 sigValidationResult) {
// can put additional checks based on sender here
-
- // check if sig is valid
- bool success = _erc1271IsValidSignatureWithSender(sender, hash, _erc1271UnwrapSignature(signature));
- /// @solidity memory-safe-assembly
- assembly {
- // `success ? bytes4(keccak256("isValidSignature(bytes32,bytes)")) : 0xffffffff`.
- // We use `0xffffffff` for invalid, in convention with the reference implementation.
- sigValidationResult := shl(224, or(0x1626ba7e, sub(0, iszero(success))))
- }
+ return _erc1271IsValidSignatureWithSender(sender, hash, _erc1271UnwrapSignature(signature));
}
// ISessionValidator interface for smart session
diff --git a/contracts/mocks/MockValidator_7739v2.sol b/contracts/mocks/MockValidator_7739v2.sol
new file mode 100644
index 00000000..acfc86f5
--- /dev/null
+++ b/contracts/mocks/MockValidator_7739v2.sol
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.27;
+
+import { IModule } from "../interfaces/modules/IModule.sol";
+import { IModuleManager } from "../interfaces/base/IModuleManager.sol";
+import { VALIDATION_SUCCESS, VALIDATION_FAILED, MODULE_TYPE_VALIDATOR, ERC1271_MAGICVALUE, ERC1271_INVALID } from "../types/Constants.sol";
+import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol";
+import { ECDSA } from "solady/utils/ECDSA.sol";
+import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";
+import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
+import { ERC7739Validator } from "erc7739Validator/ERC7739Validator.sol";
+
+contract MockValidator_7739v2 is ERC7739Validator {
+ mapping(address => address) public smartAccountOwners;
+
+ function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external view returns (uint256 validation) {
+ address owner = smartAccountOwners[msg.sender];
+ return _validateSignatureForOwner(owner, userOpHash, userOp.signature) ? VALIDATION_SUCCESS : VALIDATION_FAILED;
+ }
+
+ function isValidSignatureWithSender(
+ address sender,
+ bytes32 hash,
+ bytes calldata signature
+ ) external view virtual returns (bytes4) {
+ return 0x77390002;
+ }
+
+ // ISessionValidator interface for smart session
+ function validateSignatureWithData(bytes32 hash, bytes calldata sig, bytes calldata data) external view returns (bool validSig) {
+ address owner = address(bytes20(data[0:20]));
+ return _validateSignatureForOwner(owner, hash, sig);
+ }
+
+ function _validateSignatureForOwner(address owner, bytes32 hash, bytes calldata signature) internal view returns (bool) {
+ if (SignatureCheckerLib.isValidSignatureNowCalldata(owner, hash, signature)) {
+ return true;
+ }
+ if (SignatureCheckerLib.isValidSignatureNowCalldata(owner, MessageHashUtils.toEthSignedMessageHash(hash), signature)) {
+ return true;
+ }
+ return false;
+ }
+
+ /// @dev Returns whether the `hash` and `signature` are valid.
+ /// Obtains the authorized signer's credentials and calls some
+ /// module's specific internal function to validate the signature
+ /// against credentials.
+ function _erc1271IsValidSignatureNowCalldata(bytes32 hash, bytes calldata signature) internal view override returns (bool) {
+ // obtain credentials
+ address owner = smartAccountOwners[msg.sender];
+
+ // call custom internal function to validate the signature against credentials
+ return _validateSignatureForOwner(owner, hash, signature);
+ }
+
+ /// @dev Returns whether the `sender` is considered safe, such
+ /// that we don't need to use the nested EIP-712 workflow.
+ /// See: https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU
+ // The canonical `MulticallerWithSigner` at 0x000000000000D9ECebf3C23529de49815Dac1c4c
+ // is known to include the account in the hash to be signed.
+ // msg.sender = Smart Account
+ // sender = 1271 og request sender
+ function _erc1271CallerIsSafe(address sender) internal view virtual override returns (bool) {
+ return (sender == 0x000000000000D9ECebf3C23529de49815Dac1c4c || // MulticallerWithSigner
+ sender == msg.sender);
+ }
+
+ function onInstall(bytes calldata data) external {
+ require(IModuleManager(msg.sender).isModuleInstalled(MODULE_TYPE_VALIDATOR, address(this), ""), "Validator is still installed");
+ smartAccountOwners[msg.sender] = address(bytes20(data));
+ }
+
+ function onUninstall(bytes calldata data) external {
+ data;
+ require(!IModuleManager(msg.sender).isModuleInstalled(MODULE_TYPE_VALIDATOR, address(this), ""), "Validator is still installed");
+ delete smartAccountOwners[msg.sender];
+ }
+
+ function isModuleType(uint256 moduleTypeId) external pure returns (bool) {
+ return moduleTypeId == MODULE_TYPE_VALIDATOR;
+ }
+
+ function isOwner(address account, address owner) external view returns (bool) {
+ return smartAccountOwners[account] == owner;
+ }
+
+ function isInitialized(address) external pure returns (bool) {
+ return false;
+ }
+
+ function getOwner(address account) external view returns (address) {
+ return smartAccountOwners[account];
+ }
+}
diff --git a/contracts/modules/validators/K1Validator.sol b/contracts/modules/validators/K1Validator.sol
index 2d5dbf76..110fcfc2 100644
--- a/contracts/modules/validators/K1Validator.sol
+++ b/contracts/modules/validators/K1Validator.sol
@@ -14,7 +14,7 @@ pragma solidity ^0.8.27;
import { ECDSA } from "solady/utils/ECDSA.sol";
import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol";
-import { ERC7739Validator } from "../../base/ERC7739Validator.sol";
+import { ERC7739Validator } from "erc7739Validator/ERC7739Validator.sol";
import { IValidator } from "../../interfaces/modules/IValidator.sol";
import { EnumerableSet } from "../../lib/EnumerableSet4337.sol";
import { MODULE_TYPE_VALIDATOR, VALIDATION_SUCCESS, VALIDATION_FAILED } from "../../types/Constants.sol";
@@ -109,6 +109,11 @@ contract K1Validator is IValidator, ERC7739Validator {
_safeSenders.remove(msg.sender, sender);
}
+ /// @notice Checks if a sender is in the _safeSenders list for the smart account
+ function isSafeSender(address sender, address smartAccount) external view returns (bool) {
+ return _safeSenders.contains(smartAccount, sender);
+ }
+
/**
* Check if the module is initialized
* @param smartAccount The smart account to check
@@ -141,6 +146,10 @@ contract K1Validator is IValidator, ERC7739Validator {
/**
* Validates an ERC-1271 signature
+ * @dev implements signature malleability prevention
+ * see: https://eips.ethereum.org/EIPS/eip-1271#reference-implementation
+ * Please note, that this prevention does not protect against replay attacks in general
+ * So the protocol using ERC-1271 should make sure hash is replay-safe.
*
* @param sender The sender of the ERC-1271 call to the account
* @param hash The hash of the message
@@ -149,20 +158,15 @@ contract K1Validator is IValidator, ERC7739Validator {
* @return sigValidationResult the result of the signature validation, which can be:
* - EIP1271_SUCCESS if the signature is valid
* - EIP1271_FAILED if the signature is invalid
+ * - 0x7739000X if this is the ERC-7739 support detection request.
+ * Where X is the version of the ERC-7739 support.
*/
function isValidSignatureWithSender(
address sender,
bytes32 hash,
bytes calldata signature
- ) external view virtual override returns (bytes4 sigValidationResult) {
- // check if sig is valid
- bool success = _erc1271IsValidSignatureWithSender(sender, hash, _erc1271UnwrapSignature(signature));
- /// @solidity memory-safe-assembly
- assembly {
- // `success ? bytes4(keccak256("isValidSignature(bytes32,bytes)")) : 0xffffffff`.
- // We use `0xffffffff` for invalid, in convention with the reference implementation.
- sigValidationResult := shl(224, or(0x1626ba7e, sub(0, iszero(success))))
- }
+ ) external view virtual override returns (bytes4) {
+ return _erc1271IsValidSignatureWithSender(sender, hash, _erc1271UnwrapSignature(signature));
}
/// @notice ISessionValidator interface for smart session
@@ -188,7 +192,7 @@ contract K1Validator is IValidator, ERC7739Validator {
/// @notice Returns the version of the module
/// @return The version of the module
function version() external pure returns (string memory) {
- return "1.0.0";
+ return "1.0.1";
}
/// @notice Checks if the module is of the specified type
@@ -238,16 +242,6 @@ contract K1Validator is IValidator, ERC7739Validator {
/// @param hash The hash of the data to validate
/// @param signature The signature data
function _validateSignatureForOwner(address owner, bytes32 hash, bytes calldata signature) internal view returns (bool) {
- // Check if the 's' value is valid
- bytes32 s;
- assembly {
- // same as `s := mload(add(signature, 0x40))` but for calldata
- s := calldataload(add(signature.offset, 0x20))
- }
- if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
- return false;
- }
-
// verify signer
// owner can not be zero address in this contract
if (_recoverSigner(hash, signature) == owner) return true;
diff --git a/contracts/types/Constants.sol b/contracts/types/Constants.sol
index 115193b0..22e00ba0 100644
--- a/contracts/types/Constants.sol
+++ b/contracts/types/Constants.sol
@@ -46,4 +46,5 @@ bytes32 constant MODULE_ENABLE_MODE_TYPE_HASH = keccak256(bytes(MODULE_ENABLE_MO
bytes1 constant MODE_VALIDATION = 0x00;
bytes1 constant MODE_MODULE_ENABLE = 0x01;
-bytes32 constant SUPPORTS_NESTED_TYPED_DATA_SIGN = bytes4(0xd620c85a);
+bytes4 constant SUPPORTS_ERC7739 = 0x77390000;
+bytes4 constant SUPPORTS_ERC7739_V1 = 0x77390001;
\ No newline at end of file
diff --git a/coverage-report.html b/coverage-report.html
new file mode 100644
index 00000000..5cb04eed
--- /dev/null
+++ b/coverage-report.html
@@ -0,0 +1,165 @@
+
+
+
+
+