Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

import "../samples/SimpleWallet.sol";
import "./IBLSWallet.sol";
import "../samples/SimpleAccount.sol";
import "./IBLSAccount.sol";

/**
* Minimal BLS-based wallet that uses an aggregated signature.
* The wallet must maintain its own BLS public-key, and expose its trusted signature aggregator.
* Note that unlike the "standard" SimpleWallet, this wallet can't be called directly
* (normal SimpleWallet uses its "signer" address as both the ecrecover signer, and as a legitimate
* Minimal BLS-based account that uses an aggregated signature.
* The account must maintain its own BLS public-key, and expose its trusted signature aggregator.
* Note that unlike the "standard" SimpleAccount, this account can't be called directly
* (normal SimpleAccount uses its "signer" address as both the ecrecover signer, and as a legitimate
* Ethereum sender address. Obviously, a BLS public is not a valid Ethereum sender address.)
*/
contract BLSWallet is SimpleWallet, IBLSWallet {
contract BLSAccount is SimpleAccount, IBLSAccount {
address public immutable aggregator;
uint256[4] private publicKey;

constructor(IEntryPoint anEntryPoint, address anAggregator, uint256[4] memory aPublicKey)
SimpleWallet(anEntryPoint, address(0)) {
SimpleAccount(anEntryPoint, address(0)) {
publicKey = aPublicKey;
aggregator = anAggregator;
}

function _validateSignature(UserOperation calldata userOp, bytes32 requestId, address userOpAggregator)
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash, address userOpAggregator)
internal override view returns (uint256 deadline) {

(userOp, requestId);
require(userOpAggregator == aggregator, "BLSWallet: wrong aggregator");
(userOp, userOpHash);
require(userOpAggregator == aggregator, "BLSAccount: wrong aggregator");
return 0;
}

Expand All @@ -46,9 +46,9 @@ contract BLSWallet is SimpleWallet, IBLSWallet {
}


contract BLSWalletDeployer {
contract BLSAccountDeployer {

function deployWallet(IEntryPoint anEntryPoint, address anAggregator, uint salt, uint256[4] memory aPublicKey) public returns (BLSWallet) {
return new BLSWallet{salt : bytes32(salt)}(anEntryPoint, anAggregator, aPublicKey);
function deployAccount(IEntryPoint anEntryPoint, address anAggregator, uint salt, uint256[4] memory aPublicKey) public returns (BLSAccount) {
return new BLSAccount{salt : bytes32(salt)}(anEntryPoint, anAggregator, aPublicKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ pragma abicoder v2;
import "../interfaces/IAggregator.sol";
import "../interfaces/IEntryPoint.sol";
import {BLSOpen} from "./lib/BLSOpen.sol";
import "./IBLSWallet.sol";
import "./IBLSAccount.sol";
import "./BLSHelper.sol";
import "hardhat/console.sol";

/**
* A BLS-based signature aggregator, to validate aggregated signature of multiple UserOps if BLSWallet
* A BLS-based signature aggregator, to validate aggregated signature of multiple UserOps if BLSAccount
*/
contract BLSSignatureAggregator is IAggregator {
using UserOperationLib for UserOperation;
Expand All @@ -22,7 +22,7 @@ contract BLSSignatureAggregator is IAggregator {
if (initCode.length > 0) {
publicKey = getTrailingPublicKey(initCode);
} else {
return IBLSWallet(userOp.sender).getBlsPublicKey();
return IBLSAccount(userOp.sender).getBlsPublicKey();
}
}

Expand Down Expand Up @@ -55,9 +55,9 @@ contract BLSSignatureAggregator is IAggregator {
for (uint256 i = 0; i < userOpsLen; i++) {

UserOperation memory userOp = userOps[i];
IBLSWallet blsWallet = IBLSWallet(userOp.sender);
IBLSAccount blsAccount = IBLSAccount(userOp.sender);

blsPublicKeys[i] = blsWallet.getBlsPublicKey{gas : 30000}();
blsPublicKeys[i] = blsAccount.getBlsPublicKey{gas : 30000}();

messages[i] = _userOpToMessage(userOp, keccak256(abi.encode(blsPublicKeys[i])));
}
Expand All @@ -69,7 +69,7 @@ contract BLSSignatureAggregator is IAggregator {
* NOTE: this hash is not the same as UserOperation.hash()
* (slightly less efficient, since it uses memory userOp)
*/
function getUserOpHash(UserOperation memory userOp) internal pure returns (bytes32) {
function internalUserOpHash(UserOperation memory userOp) internal pure returns (bytes32) {
return keccak256(abi.encode(
userOp.sender,
userOp.nonce,
Expand All @@ -86,30 +86,30 @@ contract BLSSignatureAggregator is IAggregator {

/**
* return the BLS "message" for the given UserOp.
* the wallet should sign this value using its public-key
* the account checks the signature over this value using its public-key
*/
function userOpToMessage(UserOperation memory userOp) public view returns (uint256[2] memory) {
bytes32 hashPublicKey = _getUserOpPubkeyHash(userOp);
return _userOpToMessage(userOp, hashPublicKey);
}

function _userOpToMessage(UserOperation memory userOp, bytes32 publicKeyHash) internal view returns (uint256[2] memory) {
bytes32 requestId = _getRequestId(userOp, publicKeyHash);
return BLSOpen.hashToPoint(BLS_DOMAIN, abi.encodePacked(requestId));
bytes32 userOpHash = _getUserOpHash(userOp, publicKeyHash);
return BLSOpen.hashToPoint(BLS_DOMAIN, abi.encodePacked(userOpHash));
}

//return the public-key hash of a userOp.
function _getUserOpPubkeyHash(UserOperation memory userOp) internal view returns (bytes32 hashPublicKey) {
return keccak256(abi.encode(getUserOpPublicKey(userOp)));
}

function getRequestId(UserOperation memory userOp) public view returns (bytes32) {
function getUserOpHash(UserOperation memory userOp) public view returns (bytes32) {
bytes32 hashPublicKey = _getUserOpPubkeyHash(userOp);
return _getRequestId(userOp, hashPublicKey);
return _getUserOpHash(userOp, hashPublicKey);
}

function _getRequestId(UserOperation memory userOp, bytes32 hashPublicKey) internal view returns (bytes32) {
return keccak256(abi.encode(getUserOpHash(userOp), hashPublicKey, address(this), block.chainid));
function _getUserOpHash(UserOperation memory userOp, bytes32 hashPublicKey) internal view returns (bytes32) {
return keccak256(abi.encode(internalUserOpHash(userOp), hashPublicKey, address(this), block.chainid));
}

/**
Expand All @@ -118,7 +118,7 @@ contract BLSSignatureAggregator is IAggregator {
* First it validates the signature over the userOp. then it return data to be used when creating the handleOps:
* @param userOp the userOperation received from the user.
* @return sigForUserOp the value to put into the signature field of the userOp when calling handleOps.
* (usually empty, unless wallet and aggregator support some kind of "multisig"
* (usually empty, unless account and aggregator support some kind of "multisig"
*/
function validateUserOpSignature(UserOperation calldata userOp)
external view returns (bytes memory sigForUserOp) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.7.6;

import "../interfaces/IAggregatedWallet.sol";
import "../interfaces/IAggregatedAccount.sol";

/**
* a BLS wallet should expose its own public key.
* a BLS account should expose its own public key.
*/
interface IBLSWallet is IAggregatedWallet {
interface IBLSAccount is IAggregatedAccount {
function getBlsPublicKey() external view returns (uint256[4] memory);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,66 @@ pragma solidity ^0.8.12;
/* solhint-disable no-inline-assembly */
/* solhint-disable reason-string */

import "../interfaces/IWallet.sol";
import "../interfaces/IAccount.sol";
import "../interfaces/IEntryPoint.sol";

/**
* Basic wallet implementation.
* this contract provides the basic logic for implementing the IWallet interface - validateUserOp
* specific wallet implementation should inherit it and provide the wallet-specific logic
* Basic account implementation.
* this contract provides the basic logic for implementing the IAccount interface - validateUserOp
* specific account implementation should inherit it and provide the account-specific logic
*/
abstract contract BaseWallet is IWallet {
abstract contract BaseAccount is IAccount {
using UserOperationLib for UserOperation;

/**
* return the wallet nonce.
* return the account nonce.
* subclass should return a nonce value that is used both by _validateAndUpdateNonce, and by the external provider (to read the current nonce)
*/
function nonce() public view virtual returns (uint256);

/**
* return the entryPoint used by this wallet.
* subclass should return the current entryPoint used by this wallet.
* return the entryPoint used by this account.
* subclass should return the current entryPoint used by this account.
*/
function entryPoint() public view virtual returns (IEntryPoint);

/**
* Validate user's signature and nonce.
* subclass doesn't need to override this method. Instead, it should override the specific internal validation methods.
*/
function validateUserOp(UserOperation calldata userOp, bytes32 requestId, address aggregator, uint256 missingWalletFunds)
function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, address aggregator, uint256 missingAccountFunds)
external override virtual returns (uint256 deadline) {
_requireFromEntryPoint();
deadline = _validateSignature(userOp, requestId, aggregator);
deadline = _validateSignature(userOp, userOpHash, aggregator);
if (userOp.initCode.length == 0) {
_validateAndUpdateNonce(userOp);
}
_payPrefund(missingWalletFunds);
_payPrefund(missingAccountFunds);
}

/**
* ensure the request comes from the known entrypoint.
*/
function _requireFromEntryPoint() internal virtual view {
require(msg.sender == address(entryPoint()), "wallet: not from EntryPoint");
require(msg.sender == address(entryPoint()), "account: not from EntryPoint");
}

/**
* validate the signature is valid for this message.
* @param userOp validate the userOp.signature field
* @param requestId convenient field: the hash of the request, to check the signature against
* @param userOpHash convenient field: the hash of the request, to check the signature against
* (also hashes the entrypoint and chain-id)
* @param aggregator the current aggregator. can be ignored by wallets that don't use aggregators
* @param aggregator the current aggregator. can be ignored by accounts that don't use aggregators
* @return deadline the last block timestamp this operation is valid, or zero if it is valid indefinitely.
* Note that the validation code cannot use block.timestamp (or block.number) directly.
*/
function _validateSignature(UserOperation calldata userOp, bytes32 requestId, address aggregator)
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash, address aggregator)
internal virtual returns (uint256 deadline);

/**
* validate the current nonce matches the UserOperation nonce.
* then it should update the wallet's state to prevent replay of this UserOperation.
* called only if initCode is empty (since "nonce" field is used as "salt" on wallet creation)
* then it should update the account's state to prevent replay of this UserOperation.
* called only if initCode is empty (since "nonce" field is used as "salt" on account creation)
* @param userOp the op to validate.
*/
function _validateAndUpdateNonce(UserOperation calldata userOp) internal virtual;
Expand All @@ -74,20 +74,20 @@ abstract contract BaseWallet is IWallet {
* subclass MAY override this method for better funds management
* (e.g. send to the entryPoint more than the minimum required, so that in future transactions
* it will not be required to send again)
* @param missingWalletFunds the minimum value this method should send the entrypoint.
* @param missingAccountFunds the minimum value this method should send the entrypoint.
* this value MAY be zero, in case there is enough deposit, or the userOp has a paymaster.
*/
function _payPrefund(uint256 missingWalletFunds) internal virtual {
if (missingWalletFunds != 0) {
(bool success,) = payable(msg.sender).call{value : missingWalletFunds, gas : type(uint256).max}("");
function _payPrefund(uint256 missingAccountFunds) internal virtual {
if (missingAccountFunds != 0) {
(bool success,) = payable(msg.sender).call{value : missingAccountFunds, gas : type(uint256).max}("");
(success);
//ignore failure (its EntryPoint's job to verify, not wallet.)
//ignore failure (its EntryPoint's job to verify, not account.)
}
}

/**
* expose an api to modify the entryPoint.
* must be called by current "admin" of the wallet.
* must be called by current "admin" of the account.
* @param newEntryPoint the new entrypoint to trust.
*/
function updateEntryPoint(address newEntryPoint) external {
Expand All @@ -97,7 +97,7 @@ abstract contract BaseWallet is IWallet {

/**
* ensure the caller is allowed "admin" operations (such as changing the entryPoint)
* default implementation trust the wallet itself (or any signer that passes "validateUserOp")
* default implementation trust the account itself (or any signer that passes "validateUserOp")
* to be the "admin"
*/
function _requireFromAdmin() internal view virtual {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ abstract contract BasePaymaster is IPaymaster, Ownable {
entryPoint = _entryPoint;
}

function validatePaymasterUserOp(UserOperation calldata userOp, bytes32 requestId, uint256 maxCost)
function validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
external virtual override returns (bytes memory context, uint256 deadline);

function postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) external override {
Expand Down
Loading