-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fdb6baa
commit 2ca2a60
Showing
7 changed files
with
1,278 additions
and
298 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,262 +1,27 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.23; | ||
|
||
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; | ||
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; | ||
import {SIG_VALIDATION_FAILED} from "account-abstraction/core/Helpers.sol"; | ||
import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; | ||
import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; | ||
import {BLSOpen} from "./BLSOpen.sol"; | ||
|
||
import {BaseLightAccount} from "light-account/src/common/BaseLightAccount.sol"; | ||
import {CustomSlotInitializable} from "light-account/src/common/CustomSlotInitializable.sol"; | ||
contract BLSLightAccount { | ||
|
||
/// @title A simple ERC-4337 compatible smart contract account with a designated owner account. | ||
/// @dev Like eth-infinitism's SimpleAccount, but with the following changes: | ||
/// | ||
/// 1. Instead of the default storage slots, uses namespaced storage to avoid clashes when switching implementations. | ||
/// | ||
/// 2. Ownership can be transferred via `transferOwnership`, similar to the behavior of an `Ownable` contract. This is | ||
/// a simple single-step operation, so care must be taken to ensure that the ownership is being transferred to the | ||
/// correct address. | ||
/// | ||
/// 3. Supports [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) signature validation for both validating the | ||
/// signature on user operations and in exposing its own `isValidSignature` method. This only works when the owner of | ||
/// LightAccount also support ERC-1271. | ||
/// | ||
/// ERC-4337's bundler validation rules limit the types of contracts that can be used as owners to validate user | ||
/// operation signatures. For example, the contract's `isValidSignature` function may not use any forbidden opcodes | ||
/// such as `TIMESTAMP` or `NUMBER`, and the contract may not be an ERC-1967 proxy as it accesses a constant | ||
/// implementation slot not associated with the account, violating storage access rules. This also means that the | ||
/// owner of a LightAccount may not be another LightAccount if you want to send user operations through a bundler. | ||
/// | ||
/// 4. Event `SimpleAccountInitialized` renamed to `LightAccountInitialized`. | ||
/// | ||
/// 5. Uses custom errors. | ||
contract BLSLightAccount is BaseLightAccount, CustomSlotInitializable { | ||
using ECDSA for bytes32; | ||
using MessageHashUtils for bytes32; | ||
|
||
bytes32 private blsPublicKey; | ||
|
||
enum ExtendSignatureType { | ||
EOA, | ||
CONTRACT, | ||
CONTRACT_WITH_ADDR, | ||
reserve_3, | ||
reserve_4, | ||
reserve_5, | ||
reserve_6, | ||
reserve_7, | ||
reserve_8, | ||
reserve_9, | ||
reserve_10, | ||
reserve_11, | ||
reserve_12, | ||
reserve_13, | ||
reserve_14, | ||
reserve_15, | ||
BLS | ||
} | ||
|
||
/// @dev The version used for namespaced storage is not linked to the release version of the contract. Storage | ||
/// versions will be updated only when storage layout changes are made. | ||
/// keccak256(abi.encode(uint256(keccak256("blslight_account_v1.storage")) - 1)) & ~bytes32(uint256(0xff)); | ||
bytes32 internal constant _STORAGE_POSITION = 0x99f82d893aaaf0bfbe72b23177e6b97cf253cbd01abd0d2f793f75ec7cfc2e00; | ||
/// @dev keccak256(abi.encode(uint256(keccak256("blslight_account_v1.initializable")) - 1)) & ~bytes32(uint256(0xff)); | ||
bytes32 internal constant _INITIALIZABLE_STORAGE_POSITION = | ||
0x6c0a8bfcf5680463be0c279c880b798fd37cd3a6f41eba9aa29d36c2fe945100; | ||
|
||
struct BLSLightAccountStorage { | ||
address owner; | ||
} | ||
|
||
/// @notice Emitted when this account is first initialized. | ||
/// @param entryPoint The entry point. | ||
/// @param owner The initial owner. | ||
event BLSLightAccountInitialized(IEntryPoint indexed entryPoint, address indexed owner); | ||
|
||
/// @notice Emitted when this account's owner changes. Also emitted once at initialization, with a | ||
/// `previousOwner` of 0. | ||
/// @param previousOwner The previous owner. | ||
/// @param newOwner The new owner. | ||
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | ||
|
||
/// @dev The new owner is not a valid owner (e.g., `address(0)`, the account itself, or the current owner). | ||
error InvalidOwner(address owner); | ||
|
||
/// @notice Emitted when the BLS public key is updated. | ||
/// @param oldPublicKey The old BLS public key. | ||
/// @param newPublicKey The new BLS public key. | ||
event BlsPublicKeyUpdated(bytes32 indexed oldPublicKey, bytes32 indexed newPublicKey); | ||
|
||
constructor(IEntryPoint entryPoint_) CustomSlotInitializable(_INITIALIZABLE_STORAGE_POSITION) { | ||
_ENTRY_POINT = entryPoint_; | ||
// _disableInitializers(); | ||
} | ||
|
||
/// @notice Called once as part of initialization, either during initial deployment or when first upgrading to | ||
/// this contract. | ||
/// @dev The `_ENTRY_POINT` member is immutable, to reduce gas consumption. To update the entry point address, a new | ||
/// implementation of LightAccount must be deployed with the new entry point address, and then `upgradeToAndCall` | ||
/// must be called to upgrade the implementation. | ||
/// @param owner_ The initial owner of the account. | ||
function initialize(address owner_, bytes32 blsPublicKey_) external initializer { | ||
_initialize(owner_); | ||
blsPublicKey = blsPublicKey_; | ||
} | ||
|
||
function setBlsPublicKey(bytes32 newBlsPublicKey) external { | ||
require(msg.sender == owner(), "Only authorized accounts can perform this action"); | ||
bytes32 oldPublicKey = blsPublicKey; | ||
blsPublicKey = newBlsPublicKey; | ||
emit BlsPublicKeyUpdated(oldPublicKey, newBlsPublicKey); | ||
} | ||
|
||
function getBlsPublicKey() public view returns (bytes32) { | ||
return blsPublicKey; | ||
} | ||
|
||
/// @notice Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current | ||
/// owner or from the entry point via a user operation signed by the current owner. | ||
/// @param newOwner The new owner. | ||
function transferOwnership(address newOwner) external virtual onlyAuthorized { | ||
if (newOwner == address(0) || newOwner == address(this)) { | ||
revert InvalidOwner(newOwner); | ||
} | ||
_transferOwnership(newOwner); | ||
} | ||
|
||
/// @notice Return the current owner of this account. | ||
/// @return The current owner. | ||
function owner() public view returns (address) { | ||
return _getStorage().owner; | ||
uint256[4] private publicKey; | ||
address private owner; | ||
|
||
function validateUserOpSignature(uint256[] message, uint256[2] signature) | ||
external view returns (bool) { | ||
return BLSOpen.verifySingle(signature, publicKey, message); | ||
} | ||
|
||
function _initialize(address owner_) internal virtual { | ||
function initialize(address owner_, uint256[4] memory aPublicKey) external { | ||
if (owner_ == address(0)) { | ||
revert InvalidOwner(address(0)); | ||
} | ||
_getStorage().owner = owner_; | ||
emit BLSLightAccountInitialized(_ENTRY_POINT, owner_); | ||
emit OwnershipTransferred(address(0), owner_); | ||
owner = owner_; | ||
publicKey = aPublicKey; | ||
} | ||
|
||
function _transferOwnership(address newOwner) internal virtual { | ||
BLSLightAccountStorage storage _storage = _getStorage(); | ||
address oldOwner = _storage.owner; | ||
if (newOwner == oldOwner) { | ||
revert InvalidOwner(newOwner); | ||
} | ||
_storage.owner = newOwner; | ||
emit OwnershipTransferred(oldOwner, newOwner); | ||
} | ||
|
||
/// @dev Implement template method of BaseAccount. | ||
/// Uses a modified version of `SignatureChecker.isValidSignatureNow` in which the digest is wrapped with an | ||
/// "Ethereum Signed Message" envelope for the EOA-owner case but not in the ERC-1271 contract-owner case. | ||
function _validateSignature(PackedUserOperation calldata userOp, bytes32 userOpHash) | ||
internal | ||
virtual | ||
override | ||
returns (uint256 validationData) | ||
{ | ||
if (userOp.signature.length < 1) { | ||
revert InvalidSignatureType(); | ||
} | ||
uint8 signatureType = uint8(userOp.signature[0]); | ||
if (signatureType == uint8(ExtendSignatureType.EOA)) { | ||
// EOA signature | ||
bytes32 signedHash = userOpHash.toEthSignedMessageHash(); | ||
bytes memory signature = userOp.signature[1:]; | ||
return _successToValidationData(_isValidEOAOwnerSignature(signedHash, signature)); | ||
} else if (signatureType == uint8(ExtendSignatureType.CONTRACT)) { | ||
// Contract signature without address | ||
bytes memory signature = userOp.signature[1:]; | ||
return _successToValidationData(_isValidContractOwnerSignatureNow(userOpHash, signature)); | ||
} else if (signatureType == uint8(ExtendSignatureType.BLS)) { | ||
// BLS signature | ||
bytes memory signature = userOp.signature[1:]; | ||
return _successToValidationData(_isValidBlsSignature(userOp, signature)); | ||
} | ||
revert InvalidSignatureType(); | ||
} | ||
|
||
/// @dev Validates a BLS signature | ||
function _isValidBlsSignature(PackedUserOperation calldata userOp, bytes memory signature) | ||
internal | ||
view | ||
returns (bool) | ||
{ | ||
// TODO: Implement BLS signature validation | ||
// This will require an external library or precompile for BLS operations | ||
// For now, we'll just revert to indicate it's not implemented | ||
revert("BLS signature validation not implemented"); | ||
} | ||
|
||
/// @notice Check if the signature is a valid by the EOA owner for the given digest. | ||
/// @dev Only supports 65-byte signatures, and uses the digest directly. Reverts if the signature is malformed. | ||
/// @param digest The digest to be checked. | ||
/// @param signature The signature to be checked. | ||
/// @return True if the signature is valid and by the owner, false otherwise. | ||
function _isValidEOAOwnerSignature(bytes32 digest, bytes memory signature) internal view returns (bool) { | ||
address recovered = digest.recover(signature); | ||
return recovered == owner(); | ||
} | ||
|
||
/// @notice Check if the signature is a valid ERC-1271 signature by a contract owner for the given digest. | ||
/// @param digest The digest to be checked. | ||
/// @param signature The signature to be checked. | ||
/// @return True if the signature is valid and by an owner, false otherwise. | ||
function _isValidContractOwnerSignatureNow(bytes32 digest, bytes memory signature) internal view returns (bool) { | ||
return SignatureChecker.isValidERC1271SignatureNow(owner(), digest, signature); | ||
} | ||
|
||
/// @dev The signature is valid if it is signed by the owner's private key (if the owner is an EOA) or if it is a | ||
/// valid ERC-1271 signature from the owner (if the owner is a contract). Reverts if the signature is malformed. | ||
/// Note that unlike the signature validation used in `validateUserOp`, this does **not** wrap the hash in an | ||
/// "Ethereum Signed Message" envelope before checking the signature in the EOA-owner case. | ||
function _isValidSignature(bytes32 replaySafeHash, bytes calldata signature) | ||
internal | ||
view | ||
virtual | ||
override | ||
returns (bool) | ||
{ | ||
if (signature.length < 1) { | ||
revert InvalidSignatureType(); | ||
} | ||
uint8 signatureType = uint8(signature[0]); | ||
if (signatureType == uint8(SignatureType.EOA)) { | ||
// EOA signature | ||
return _isValidEOAOwnerSignature(replaySafeHash, signature[1:]); | ||
} else if (signatureType == uint8(SignatureType.CONTRACT)) { | ||
// Contract signature without address | ||
return _isValidContractOwnerSignatureNow(replaySafeHash, signature[1:]); | ||
} | ||
revert InvalidSignatureType(); | ||
} | ||
|
||
function _domainNameAndVersion() | ||
internal | ||
view | ||
virtual | ||
override | ||
returns (string memory name, string memory version) | ||
{ | ||
name = "BLSLightAccount"; | ||
// Set to the major version of the GitHub release at which the contract was last updated. | ||
version = "1"; | ||
} | ||
|
||
function _isFromOwner() internal view virtual override returns (bool) { | ||
return msg.sender == owner(); | ||
} | ||
|
||
function _getStorage() internal pure returns (BLSLightAccountStorage storage storageStruct) { | ||
bytes32 position = _STORAGE_POSITION; | ||
assembly ("memory-safe") { | ||
storageStruct.slot := position | ||
} | ||
function setBlsPublicKey(uint256[4] memory newPublicKey) public { | ||
publicKey = newPublicKey; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.23; | ||
|
||
import { BLS } from "./hubble-contracts/libs/BLS.sol"; | ||
|
||
library BLSOpen { | ||
function verifySingle( | ||
uint256[2] memory signature, | ||
uint256[4] memory pubkey, | ||
uint256[2] memory message | ||
) external view returns (bool) { | ||
uint256[4][] memory pubkeys = new uint256[4][](1); | ||
uint256[2][] memory messages = new uint256[2][](1); | ||
pubkeys[0] = pubkey; | ||
messages[0] = message; | ||
|
||
(bool verified, bool callSuccess) = BLS.verifyMultiple( | ||
signature, | ||
pubkeys, | ||
messages | ||
); | ||
return callSuccess && verified; | ||
|
||
// // NB: (result, success) opposite of `call` convention (success, result). | ||
// (bool verified, bool callSuccess) = BLS.verifySingle( | ||
// signature, | ||
// pubkey, | ||
// message | ||
// ); | ||
// return callSuccess && verified; | ||
} | ||
|
||
function verifyMultiple( | ||
uint256[2] memory signature, | ||
uint256[4][] memory pubkeys, | ||
uint256[2][] memory messages | ||
) external view returns (bool) { | ||
(bool verified, bool callSuccess) = BLS.verifyMultiple( | ||
signature, | ||
pubkeys, | ||
messages | ||
); | ||
return callSuccess && verified; | ||
} | ||
|
||
function hashToPoint( | ||
bytes32 domain, | ||
bytes memory message | ||
) external view returns (uint256[2] memory) { | ||
return BLS.hashToPoint( | ||
domain, | ||
message | ||
); | ||
} | ||
|
||
function isZeroBLSKey(uint256[4] memory blsKey) public pure returns (bool) { | ||
bool isZero = true; | ||
for (uint256 i=0; isZero && i<4; i++) { | ||
isZero = (blsKey[i] == 0); | ||
} | ||
return isZero; | ||
} | ||
|
||
} |
Oops, something went wrong.