Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ ORGS += pimlico
ORGS += daimo
ORGS += zerodev
ORGS += alchemy
ORGS += rhinestone
ORGS += biconomy

.PHONY: all $(ORGS)

Expand All @@ -19,3 +21,5 @@ $(ORGS):
zerodev: eth-infinitism daimo

alchemy: eth-infinitism

biconomy: eth-infinitism rhinestone
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ make
- [alchemy-light-account:1.1.0](https://usecannon.com/packages/alchemy-light-account/1.1.0/13370-main) ([source](https://github.com/alchemyplatform/light-account/releases/tag/v1.1.0))
- [alchemy-light-account:2.0.0](https://usecannon.com/packages/alchemy-light-account/2.0.0/13370-main) ([source](https://github.com/alchemyplatform/light-account/releases/tag/v2.0.0))
- [alchemy-modular-account:2.0.0](https://usecannon.com/packages/alchemy-modular-account/2.0.0/13370-main) ([source](https://github.com/alchemyplatform/modular-account/releases/tag/v2.0.0))
- [rhinestone-registry:1.0.0](https://usecannon.com/packages/rhinestone-registry/1.0.0/13370-main) ([source](https://github.com/rhinestonewtf/registry/tree/v1.0))
- [biconomy-kee:0.0.4](https://usecannon.com/packages/biconomy-mee/0.0.4/13370-main) ([source](https://github.com/bcnmy/mee-contracts/tree/v0.0.4))
- [biconomy-nexus:1.0.2](https://usecannon.com/packages/biconomy-nexux/1.0.2/13370-main) ([source](https://github.com/bcnmy/nexus/tree/v1.0.2))
- [biconomy-nexus:1.2.0](https://usecannon.com/packages/biconomy-nexux/1.2.0/13370-main) ([source](https://github.com/bcnmy/nexus/tree/v1.2.0))

## Useful tools

Expand Down
10 changes: 10 additions & 0 deletions biconomy/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
PROJECTS += mee
PROJECTS += nexus

.PHONY: all $(PROJECTS)
all: $(PROJECTS)

$(PROJECTS):
@$(MAKE) -C $@

nexus: mee
9 changes: 9 additions & 0 deletions biconomy/mee/0.0.4/cannonfile.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name = "biconomy-mee"
version = "0.0.4"
description = "Nexus ERC-4337 infrastructure"

[deploy.K1MeeValidator]
artifact = "K1MeeValidator"
create2 = true
salt = "0x000000000000000000000000000000000000000047819504fd5006001ee95238"
ifExists = "continue"
25 changes: 25 additions & 0 deletions biconomy/mee/0.0.4/contracts/interfaces/ISessionValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.23;

import {IModule} from "erc7579/interfaces/IERC7579Module.sol";

uint256 constant ERC7579_MODULE_TYPE_STATELESS_VALIDATOR = 7;

/**
* ISessionValidator is a contract that validates signatures for a given session.
* this interface expects to validate the signature in a stateless way.
* all parameters required to validate the signature are passed in the function call.
* Only one ISessionValidator is responsible to validate a userOp.
* if you want to use multiple validators, you can create a ISessionValidator that aggregates multiple signatures that
* are packed into userOp.signature
* It is used to validate the signature of a session.
* hash The userOp hash
* sig The signature of userOp
* data the config data that is used to validate the signature
*/
interface ISessionValidator is IModule {
function validateSignatureWithData(bytes32 hash, bytes calldata sig, bytes calldata data)
external
view
returns (bool validSig);
}
40 changes: 40 additions & 0 deletions biconomy/mee/0.0.4/contracts/lib/fusion/NoMeeFlowLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import "account-abstraction/interfaces/PackedUserOperation.sol";
import "account-abstraction/core/Helpers.sol";
import "../util/EcdsaLib.sol";

library NoMeeFlowLib {
/**
* Standard userOp validator - validates by simply checking if the userOpHash was signed by the account's EOA owner.
*
* @param userOpHash userOpHash being validated.
* @param parsedSignature Signature
* @param expectedSigner Signer expected to be recovered
*/
function validateUserOp(bytes32 userOpHash, bytes memory parsedSignature, address expectedSigner)
internal
view
returns (uint256)
{
if (!EcdsaLib.isValidSignature(expectedSigner, userOpHash, parsedSignature)) {
return SIG_VALIDATION_FAILED;
}
return SIG_VALIDATION_SUCCESS;
}

/**
* @notice Validates the signature against the expected signer (owner)
* @param expectedSigner Signer expected to be recovered
* @param hash Hash of the userOp
* @param parsedSignature Signature
*/
function validateSignatureForOwner(address expectedSigner, bytes32 hash, bytes memory parsedSignature)
internal
view
returns (bool)
{
return EcdsaLib.isValidSignature(expectedSigner, hash, parsedSignature);
}
}
213 changes: 213 additions & 0 deletions biconomy/mee/0.0.4/contracts/lib/fusion/PermitValidatorLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol";
import {EcdsaLib} from "../util/EcdsaLib.sol";
import {MEEUserOpHashLib} from "../util/MEEUserOpHashLib.sol";
import {IERC20Permit} from "openzeppelin/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import "account-abstraction/core/Helpers.sol";

/**
* @dev Library to validate the signature for MEE ERC-2612 Permit mode
* This is the mode where superTx hash is pasted into deadline field of the ERC-2612 Permit
* So the whole permit is signed along with the superTx hash
* For more details see Fusion docs:
* - https://ethresear.ch/t/fusion-module-7702-alternative-with-no-protocol-changes/20949
* - https://docs.biconomy.io/explained/eoa#fusion-module
*
* @dev Important: since ERC20 permit token knows nothing about the MEE, it will treat the superTx hash as a deadline:
* - if (very unlikely) the superTx hash being converted to uint256 is a timestamp in the past, the permit will fail
* - the deadline with most superTx hashes will be very far in the future
*
* @dev Since at this point bytes32 superTx hash is a blind hash, users and wallets should pay attention if
* the permit2 deadline field does not make sense as the timestamp. In this case, it can be a sign of a
* phishing attempt (injecting super txn hash as the deadline) and the user should not sign the permit.
* This is going to be mitigated in the future by making superTx hash a EIP-712 hash.
*/
bytes32 constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

struct DecodedErc20PermitSig {
IERC20Permit token;
address spender;
bytes32 domainSeparator;
uint256 amount;
uint256 nonce;
bool isPermitTx;
bytes32 superTxHash;
uint48 lowerBoundTimestamp;
uint48 upperBoundTimestamp;
uint8 v;
bytes32 r;
bytes32 s;
bytes32[] proof;
}

struct DecodedErc20PermitSigShort {
address spender;
bytes32 domainSeparator;
uint256 amount;
uint256 nonce;
bytes32 superTxHash;
uint8 v;
bytes32 r;
bytes32 s;
bytes32[] proof;
}

library PermitValidatorLib {
error PermitFailed();

uint8 constant EIP_155_MIN_V_VALUE = 37;

using MessageHashUtils for bytes32;

/**
* This function parses the given userOpSignature into a DecodedErc20PermitSig data structure.
*
* Once parsed, the function will check for two conditions:
* 1. is the userOp part of the merkle tree
* 2. is the recovered message signer equal to the expected signer?
*
* NOTES: This function will revert if either of following is met:
* 1. the userOpSignature couldn't be abi.decoded into a valid DecodedErc20PermitSig struct as defined in this contract
* 2. userOp is not part of the merkle tree
* 3. recovered Permit message signer wasn't equal to the expected signer
*
* The function will also perform the Permit approval on the given token in case the
* isPermitTx flag was set to true in the decoded signature struct.
*
* @param userOpHash UserOp hash being validated.
* @param parsedSignature Signature provided as the userOp.signature parameter (minus the prepended tx type byte).
* @param expectedSigner Signer expected to be recovered when decoding the ERC20OPermit signature.
*/
function validateUserOp(bytes32 userOpHash, bytes calldata parsedSignature, address expectedSigner)
internal
returns (uint256)
{
DecodedErc20PermitSig memory decodedSig = _decodeFullPermitSig(parsedSignature);

bytes32 meeUserOpHash = MEEUserOpHashLib.getMEEUserOpHash(
userOpHash, decodedSig.lowerBoundTimestamp, decodedSig.upperBoundTimestamp
);

if (
!EcdsaLib.isValidSignature(
expectedSigner,
_getSignedDataHash(expectedSigner, decodedSig),
abi.encodePacked(decodedSig.r, decodedSig.s, uint8(decodedSig.v))
)
) {
return SIG_VALIDATION_FAILED;
}

if (!MerkleProof.verify(decodedSig.proof, decodedSig.superTxHash, meeUserOpHash)) {
return SIG_VALIDATION_FAILED;
}

if (decodedSig.isPermitTx) {
try decodedSig.token.permit(
expectedSigner,
decodedSig.spender,
decodedSig.amount,
uint256(decodedSig.superTxHash),
uint8(decodedSig.v),
decodedSig.r,
decodedSig.s
) {
// all good
} catch {
// check if by some reason this permit was already successfully used (and not spent yet)
if (IERC20(address(decodedSig.token)).allowance(expectedSigner, decodedSig.spender) < decodedSig.amount)
{
// if the above expectationis not true, revert
revert PermitFailed();
}
}
}

return _packValidationData(false, decodedSig.upperBoundTimestamp, decodedSig.lowerBoundTimestamp);
}

function validateSignatureForOwner(address expectedSigner, bytes32 dataHash, bytes calldata parsedSignature)
internal
view
returns (bool)
{
DecodedErc20PermitSigShort calldata decodedSig = _decodeShortPermitSig(parsedSignature);

if (
!EcdsaLib.isValidSignature(
expectedSigner,
_getSignedDataHash(expectedSigner, decodedSig),
abi.encodePacked(decodedSig.r, decodedSig.s, uint8(decodedSig.v))
)
) {
return false;
}

if (!MerkleProof.verify(decodedSig.proof, decodedSig.superTxHash, dataHash)) {
return false;
}

return true;
}

function _decodeFullPermitSig(bytes calldata parsedSignature)
private
pure
returns (DecodedErc20PermitSig calldata decodedSig)
{
assembly {
decodedSig := add(parsedSignature.offset, 0x20)
}
}

function _decodeShortPermitSig(bytes calldata parsedSignature)
private
pure
returns (DecodedErc20PermitSigShort calldata)
{
DecodedErc20PermitSigShort calldata decodedSig;
assembly {
decodedSig := add(parsedSignature.offset, 0x20)
}
return decodedSig;
}

function _getSignedDataHash(address expectedSigner, DecodedErc20PermitSig memory decodedSig)
private
pure
returns (bytes32)
{
uint256 deadline = uint256(decodedSig.superTxHash);

bytes32 structHash = keccak256(
abi.encode(
PERMIT_TYPEHASH, expectedSigner, decodedSig.spender, decodedSig.amount, decodedSig.nonce, deadline
)
);
return _hashTypedData(structHash, decodedSig.domainSeparator);
}

function _getSignedDataHash(address expectedSigner, DecodedErc20PermitSigShort memory decodedSig)
private
pure
returns (bytes32)
{
uint256 deadline = uint256(decodedSig.superTxHash);

bytes32 structHash = keccak256(
abi.encode(
PERMIT_TYPEHASH, expectedSigner, decodedSig.spender, decodedSig.amount, decodedSig.nonce, deadline
)
);
return _hashTypedData(structHash, decodedSig.domainSeparator);
}

function _hashTypedData(bytes32 structHash, bytes32 domainSeparator) private pure returns (bytes32) {
return MessageHashUtils.toTypedDataHash(domainSeparator, structHash);
}
}
Loading
Loading