以太坊账户抽象(Account Abstraction, AA)是提升用户体验和智能合约账户功能的重要技术。以下是账户抽象的全面解析:
账户抽象的基本概念
以太坊有两种账户类型:
- 外部拥有账户(EOA):由私钥控制,无代码
- 合约账户(CA):由代码控制,有智能合约
账户抽象旨在让所有账户都像智能合约一样灵活,提供更丰富的功能和更好的用户体验。
EIP-4337:账户抽象标准
1. 核心概念
EIP-4337通过入口点合约和用户操作实现账户抽象,无需协议层变更。
2. 架构组件
solidity// 用户操作结构 struct UserOperation { address sender; // 发送者账户 uint256 nonce; // 账户nonce bytes initCode; // 初始化代码(如果是新账户) bytes callData; // 调用数据 uint256 callGasLimit; // 调用Gas限制 uint256 verificationGasLimit; // 验证Gas限制 uint256 preVerificationGas; // 预验证Gas uint256 maxFeePerGas; // 最大Gas费用 uint256 maxPriorityFeePerGas; // 最大优先费用 bytes paymasterAndData; // 支付者数据 bytes signature; // 签名 } // 入口点合约 interface IEntryPoint { function handleOps( UserOperation[] calldata ops, address payable beneficiary ) external; function handleAggregatedOps( UserOpsPerAggregator[] calldata opsPerAggregator, address payable beneficiary ) external; }
智能合约钱包实现
1. 基础智能合约钱包
solidity// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract SmartWallet { address public owner; uint256 public nonce; IEntryPoint public entryPoint; event OwnerChanged(address indexed oldOwner, address indexed newOwner); event TransactionExecuted(address indexed target, uint256 value, bytes data); modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } constructor(address _owner, address _entryPoint) { owner = _owner; entryPoint = IEntryPoint(_entryPoint); } function execute(address target, uint256 value, bytes memory data) external { require(msg.sender == address(entryPoint), "Not entry point"); (bool success, ) = target.call{value: value}(data); require(success, "Execution failed"); emit TransactionExecuted(target, value, data); } function changeOwner(address newOwner) external { require(msg.sender == address(entryPoint), "Not entry point"); emit OwnerChanged(owner, newOwner); owner = newOwner; } function validateUserOp( UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds ) external returns (uint256 validationData) { require(msg.sender == address(entryPoint), "Not entry point"); // 验证签名 bytes32 ethSignedMessageHash = keccak256( abi.encodePacked("\x19Ethereum Signed Message:\n32", userOpHash) ); address signer = recoverSigner(ethSignedMessageHash, userOp.signature); require(signer == owner, "Invalid signature"); // 如果需要,支付缺失的资金 if (missingAccountFunds > 0) { payable(address(entryPoint)).transfer(missingAccountFunds); } return 0; // 验证成功 } function recoverSigner(bytes32 messageHash, bytes memory signature) internal pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature); return ecrecover(messageHash, v, r, s); } function splitSignature(bytes memory sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) { require(sig.length == 65, "Invalid signature length"); assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } } receive() external payable {} }
2. 多签钱包
soliditycontract MultiSigWallet { address[] public owners; mapping(address => bool) public isOwner; uint256 public required; uint256 public nonce; IEntryPoint public entryPoint; struct Transaction { address to; uint256 value; bytes data; bool executed; } mapping(uint256 => Transaction) public transactions; mapping(uint256 => mapping(address => bool)) public confirmations; event OwnerAdded(address indexed owner); event OwnerRemoved(address indexed owner); event RequirementChanged(uint256 required); event TransactionSubmitted(uint256 indexed txId); event TransactionConfirmed(uint256 indexed txId, address indexed owner); event TransactionExecuted(uint256 indexed txId); constructor(address[] memory _owners, uint256 _required, address _entryPoint) { require(_owners.length >= _required, "Invalid owners"); require(_required > 0, "Invalid required"); for (uint256 i = 0; i < _owners.length; i++) { require(!isOwner[_owners[i]], "Duplicate owner"); owners.push(_owners[i]); isOwner[_owners[i]] = true; emit OwnerAdded(_owners[i]); } required = _required; entryPoint = IEntryPoint(_entryPoint); } function executeTransaction( uint256 txId, address to, uint256 value, bytes memory data, bytes[] memory signatures ) external { require(msg.sender == address(entryPoint), "Not entry point"); // 验证签名 uint256 validSignatures = 0; for (uint256 i = 0; i < signatures.length; i++) { bytes32 txHash = keccak256(abi.encodePacked(txId, to, value, data, nonce)); bytes32 ethSignedMessageHash = keccak256( abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash) ); address signer = recoverSigner(ethSignedMessageHash, signatures[i]); if (isOwner[signer] && !confirmations[txId][signer]) { confirmations[txId][signer] = true; validSignatures++; emit TransactionConfirmed(txId, signer); } } require(validSignatures >= required, "Insufficient signatures"); require(!transactions[txId].executed, "Already executed"); transactions[txId] = Transaction({ to: to, value: value, data: data, executed: true }); (bool success, ) = to.call{value: value}(data); require(success, "Execution failed"); nonce++; emit TransactionExecuted(txId); } function recoverSigner(bytes32 messageHash, bytes memory signature) internal pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature); return ecrecover(messageHash, v, r, s); } function splitSignature(bytes memory sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) { require(sig.length == 65, "Invalid signature length"); assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } } receive() external payable {} }
支付者(Paymaster)
1. Gas赞助
soliditycontract Paymaster { IEntryPoint public entryPoint; address public owner; mapping(address => uint256) public balances; event Deposit(address indexed account, uint256 amount); event Withdraw(address indexed account, uint256 amount); constructor(address _entryPoint) { entryPoint = IEntryPoint(_entryPoint); owner = msg.sender; } function deposit(address account) public payable { balances[account] += msg.value; emit Deposit(account, msg.value); } function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount, "Insufficient balance"); balances[msg.sender] -= amount; payable(msg.sender).transfer(amount); emit Withdraw(msg.sender, amount); } function validatePaymasterUserOp( UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) external returns (bytes memory context, uint256 validationData) { require(msg.sender == address(entryPoint), "Not entry point"); address account = userOp.sender; require(balances[account] >= maxCost, "Insufficient balance"); return ("", 0); // 验证成功 } function postOp( bytes calldata context, uint256 actualGasCost ) external { require(msg.sender == address(entryPoint), "Not entry point"); // 后处理逻辑 } receive() external payable {} }
账户抽象优势
1. 批量交易
soliditycontract BatchWallet { address public owner; IEntryPoint public entryPoint; struct BatchCall { address target; uint256 value; bytes data; } function executeBatch(BatchCall[] calldata calls) external { require(msg.sender == address(entryPoint), "Not entry point"); for (uint256 i = 0; i < calls.length; i++) { (bool success, ) = calls[i].target.call{value: calls[i].value}(calls[i].data); require(success, "Execution failed"); } } }
2. 社交恢复
soliditycontract SocialRecoveryWallet { address[] public guardians; mapping(address => bool) public isGuardian; address public owner; uint256 public recoveryThreshold; IEntryPoint public entryPoint; struct RecoveryRequest { address newOwner; uint256 timestamp; mapping(address => bool) approvals; uint256 approvalCount; } mapping(uint256 => RecoveryRequest) public recoveryRequests; uint256 public recoveryNonce; event GuardianAdded(address indexed guardian); event GuardianRemoved(address indexed guardian); event RecoveryRequested(uint256 indexed requestId, address indexed newOwner); event RecoveryApproved(uint256 indexed requestId, address indexed guardian); event RecoveryExecuted(uint256 indexed requestId, address indexed newOwner); constructor(address[] memory _guardians, uint256 _threshold, address _entryPoint) { for (uint256 i = 0; i < _guardians.length; i++) { guardians.push(_guardians[i]); isGuardian[_guardians[i]] = true; emit GuardianAdded(_guardians[i]); } recoveryThreshold = _threshold; entryPoint = IEntryPoint(_entryPoint); owner = msg.sender; } function requestRecovery(address newOwner) external { require(isGuardian[msg.sender], "Not guardian"); uint256 requestId = recoveryNonce; RecoveryRequest storage request = recoveryRequests[requestId]; request.newOwner = newOwner; request.timestamp = block.timestamp; emit RecoveryRequested(requestId, newOwner); recoveryNonce++; } function approveRecovery(uint256 requestId) external { require(isGuardian[msg.sender], "Not guardian"); RecoveryRequest storage request = recoveryRequests[requestId]; require(!request.approvals[msg.sender], "Already approved"); request.approvals[msg.sender] = true; request.approvalCount++; emit RecoveryApproved(requestId, msg.sender); if (request.approvalCount >= recoveryThreshold) { owner = request.newOwner; emit RecoveryExecuted(requestId, request.newOwner); } } }
3. 交易限额
soliditycontract LimitWallet { address public owner; IEntryPoint public entryPoint; uint256 public dailyLimit; uint256 public dailySpent; uint256 public lastResetTime; event DailyLimitChanged(uint256 newLimit); event TransactionExecuted(address indexed target, uint256 value); constructor(uint256 _dailyLimit, address _entryPoint) { dailyLimit = _dailyLimit; entryPoint = IEntryPoint(_entryPoint); owner = msg.sender; lastResetTime = block.timestamp; } function execute(address target, uint256 value, bytes memory data) external { require(msg.sender == address(entryPoint), "Not entry point"); // 重置每日限额 if (block.timestamp >= lastResetTime + 1 days) { dailySpent = 0; lastResetTime = block.timestamp; } // 检查限额 require(dailySpent + value <= dailyLimit, "Daily limit exceeded"); dailySpent += value; // 执行交易 (bool success, ) = target.call{value: value}(data); require(success, "Execution failed"); emit TransactionExecuted(target, value); } }
账户抽象最佳实践
- 安全第一:充分测试智能合约钱包
- 用户体验:提供清晰的界面和指引
- Gas优化:优化Gas使用
- 备份恢复:实现多种恢复方式
- 权限管理:细粒度权限控制
- 审计监控:定期审计和监控
著名项目
- ERC-4337:账户抽象标准
- Safe:多签钱包
- Argent:智能合约钱包
- Gnosis Safe:多签解决方案
- WalletConnect:钱包连接协议
账户抽象正在改变以太坊的用户体验,使区块链应用更加易用和普及。