Skip to content

Commit

Permalink
remove submodule
Browse files Browse the repository at this point in the history
  • Loading branch information
fanhousanbu committed Oct 4, 2024
1 parent fdb6baa commit 2ca2a60
Show file tree
Hide file tree
Showing 7 changed files with 1,278 additions and 298 deletions.
2 changes: 1 addition & 1 deletion cache/solidity-files-cache.json

Large diffs are not rendered by default.

261 changes: 13 additions & 248 deletions src/BLSLightAccount.sol
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;
}
}
64 changes: 64 additions & 0 deletions src/BLSOpen.sol
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;
}

}
Loading

0 comments on commit 2ca2a60

Please sign in to comment.