本文档详细分析了 DIAP ERC-4337 集成的安全性,包括已实施的安全措施、潜在漏洞和缓解策略。
| 组件 | 风险等级 | 状态 |
|---|---|---|
| DIAPAccount | 🟡 中等 | 需要审计 |
| DIAPAccountFactory | 🟢 低 | 相对安全 |
| DIAPPaymaster | 🟡 中等 | 需要审计 |
| DIAPAgentNetwork 集成 | 🟢 低 | 最小改动 |
实施: ✅ 所有关键函数使用 ReentrancyGuard
contract DIAPAccount is BaseAccount, Initializable {
// 继承自 BaseAccount,使用 Checks-Effects-Interactions 模式
function transferToken(address token, address to, uint256 amount)
external onlyEntryPointOrOwner notFrozen
{
// 先检查权限
// 再执行转账(外部调用)
IERC20(token).transfer(to, amount);
}
}防护级别: 🟢 高
实施: ✅ 多层权限控制
// Level 1: Owner(完全控制)
modifier onlyOwner() {
if (msg.sender != _owner) revert OnlyOwner();
_;
}
// Level 2: EntryPoint 或 Owner
modifier onlyEntryPointOrOwner() {
if (msg.sender != address(ENTRY_POINT) && msg.sender != owner()) {
revert NotFromEntryPointOrOwner();
}
_;
}
// Level 3: 冻结检查
modifier notFrozen() {
if (_frozen) revert AccountFrozen();
_;
}防护级别: 🟢 高
实施: ✅ Solidity 0.8+ 自动检查
// 自动溢出检查
sessionKey.spentToday += amount;
// 关键操作额外验证
if (sessionKey.spentToday + amount > sessionKey.dailyLimit) {
revert DailyLimitExceeded();
}防护级别: 🟢 高
实施: ✅ ECDSA + EIP-1271
function _validateSignature(
IEntryPoint.UserOperation calldata userOp,
bytes32 userOpHash
) internal view override returns (uint256 validationData) {
bytes32 hash = userOpHash.toEthSignedMessageHash();
address signer = hash.recover(userOp.signature);
// 验证 owner 或 Session Key
if (signer == _owner) {
return 0;
}
SessionKeyData storage sessionKey = sessionKeys[signer];
if (!sessionKey.isActive || block.timestamp > sessionKey.validUntil) {
return 1;
}
return 0;
}防护级别: 🟢 高
实施: ✅ 多重限制
struct SessionKeyData {
uint256 validUntil; // 时间限制
uint256 dailyLimit; // 每日限额
uint256 perTxLimit; // 单笔限额
uint256 spentToday; // 已花费
uint256 lastResetTime; // 重置时间
bool isActive; // 激活状态
}防护级别: 🟢 高
描述: Session Key 签名可能被重放
当前状态:
漏洞代码:
// DIAPAccount.sol
function _validateSignature(...) internal view override returns (uint256) {
// 只验证签名者,没有 nonce 检查
address signer = hash.recover(userOp.signature);
if (signer == _owner) return 0;
// ...
}攻击场景:
- 攻击者截获一个有效的 UserOperation
- 在 Session Key 过期前重放该操作
- 可能导致重复转账或操作
缓解方案:
// 添加 nonce 管理
mapping(address => uint256) public sessionKeyNonces;
function _validateSignature(...) internal returns (uint256) {
address signer = hash.recover(userOp.signature);
if (sessionKeys[signer].isActive) {
// 验证并增加 nonce
uint256 expectedNonce = sessionKeyNonces[signer];
require(userOp.nonce == expectedNonce, "Invalid nonce");
sessionKeyNonces[signer]++;
}
return 0;
}优先级: 🔴 高 - 需要立即修复
描述: 攻击者可以抢先执行交易
当前状态:
攻击场景:
- 用户提交
addSessionKey交易 - 攻击者看到交易,抢先提交更高 Gas 价格的交易
- 攻击者的 Session Key 被添加
缓解方案:
// 添加提交-揭示模式
mapping(bytes32 => uint256) public commitments;
function commitSessionKey(bytes32 commitment) external onlyOwner {
commitments[commitment] = block.timestamp;
}
function revealSessionKey(
address key,
uint256 validUntil,
uint256 dailyLimit,
uint256 perTxLimit,
bytes32 salt
) external onlyOwner {
bytes32 commitment = keccak256(abi.encodePacked(
key, validUntil, dailyLimit, perTxLimit, salt
));
require(commitments[commitment] > 0, "Not committed");
require(block.timestamp >= commitments[commitment] + 1 minutes, "Too early");
// 添加 Session Key
_addSessionKey(key, validUntil, dailyLimit, perTxLimit);
delete commitments[commitment];
}优先级: 🟡 中 - 建议实施
描述: 限额重置逻辑可能被利用
当前代码:
// 重置每日限额(如果需要)
if (block.timestamp >= sessionKey.lastResetTime + 1 days) {
sessionKey.spentToday = 0;
sessionKey.lastResetTime = block.timestamp;
}攻击场景:
- 攻击者在 23:59 花费接近限额
- 在 00:01 限额重置
- 再次花费接近限额
- 在短时间内花费 2 倍限额
缓解方案:
// 使用固定的 UTC 时间重置
function _getDayStart(uint256 timestamp) internal pure returns (uint256) {
return (timestamp / 1 days) * 1 days;
}
function _checkAndUpdateSessionKeyLimit(address key, uint256 amount) internal {
SessionKeyData storage sessionKey = sessionKeys[key];
uint256 currentDayStart = _getDayStart(block.timestamp);
uint256 lastDayStart = _getDayStart(sessionKey.lastResetTime);
if (currentDayStart > lastDayStart) {
sessionKey.spentToday = 0;
sessionKey.lastResetTime = currentDayStart;
}
// 检查限额...
}优先级: 🟡 中 - 建议修复
描述: 恶意用户可能耗尽 Paymaster 资金
当前代码:
function validatePaymasterUserOp(...) external onlyEntryPoint {
// 检查配额
if (quota.usedToday + maxCost > quota.dailyQuota) {
revert QuotaExceeded();
}
// 检查存款
uint256 deposit = ENTRY_POINT.balanceOf(address(this));
if (deposit < maxCost) {
revert InsufficientDeposit();
}
// 批准赞助
return (context, 0);
}攻击场景:
- 攻击者创建多个账户
- 每个账户消耗最大配额
- Paymaster 资金快速耗尽
缓解方案:
// 添加全局限制
uint256 public globalDailyLimit;
uint256 public globalUsedToday;
uint256 public globalLastResetTime;
function validatePaymasterUserOp(...) external onlyEntryPoint {
// 检查全局限额
if (block.timestamp >= globalLastResetTime + 1 days) {
globalUsedToday = 0;
globalLastResetTime = block.timestamp;
}
if (globalUsedToday + maxCost > globalDailyLimit) {
revert GlobalQuotaExceeded();
}
// 其他检查...
globalUsedToday += maxCost;
}优先级: 🟡 中 - 建议实施
描述: 白名单检查可能被绕过
当前代码:
function executeWithSessionKey(
address dest,
uint256 value,
bytes calldata func
) external notFrozen {
if (msg.sender != address(ENTRY_POINT) && msg.sender != _owner) {
revert NotFromEntryPointOrOwner();
}
if (msg.sender != _owner) {
if (!whitelist[dest]) revert NotWhitelisted();
}
_call(dest, value, func);
}攻击场景:
- 攻击者调用白名单合约
- 白名单合约再调用非白名单合约
- 绕过白名单限制
缓解方案:
// 添加调用深度检查
uint256 private callDepth;
function executeWithSessionKey(...) external notFrozen {
require(callDepth == 0, "No nested calls");
callDepth++;
// 执行调用
_call(dest, value, func);
callDepth--;
}
// 或者:禁止 delegatecall
function _call(address target, uint256 value, bytes memory data) internal {
(bool success, bytes memory result) = target.call{value: value}(data);
// 不允许 delegatecall
require(success, "Call failed");
}优先级: 🟡 中 - 建议修复
描述: 批量操作可能因 Gas 限制失败
当前代码:
function batchTransferToken(
address token,
address[] calldata recipients,
uint256[] calldata amounts
) external onlyEntryPointOrOwner notFrozen {
require(recipients.length == amounts.length, "Length mismatch");
for (uint256 i = 0; i < recipients.length; i++) {
IERC20(token).transfer(recipients[i], amounts[i]);
}
}问题: 数组过大可能导致 Gas 不足
缓解方案:
uint256 public constant MAX_BATCH_SIZE = 50;
function batchTransferToken(...) external {
require(recipients.length <= MAX_BATCH_SIZE, "Batch too large");
// ...
}优先级: 🟢 低 - 可选优化
描述: 某些关键操作缺少事件
建议添加:
event SessionKeyLimitUpdated(address indexed key, uint256 newDailyLimit, uint256 newPerTxLimit);
event SessionKeyUsageRecorded(address indexed key, uint256 amount, uint256 remaining);
event EmergencyAction(address indexed actor, string action, uint256 timestamp);优先级: 🟢 低 - 建议添加
- 所有合约通过 Slither 静态分析
- 所有合约通过 Mythril 符号执行
- 进行完整的单元测试(覆盖率 > 90%)
- 进行集成测试
- 进行模糊测试(Echidna)
- 第三方安全审计
- 测试网部署和验证
- Bug 赏金计划
- 监控异常交易模式
- 监控 Paymaster 余额
- 监控 Session Key 使用情况
- 设置告警阈值
- 定期安全审查
- 准备暂停机制
- 准备升级方案
- 准备资金恢复流程
- 建立事件响应团队
- 准备公告模板
# Slither
pip install slither-analyzer
slither contracts/aa/
# Mythril
pip install mythril
myth analyze contracts/aa/DIAPAccount.sol# Echidna
echidna-test contracts/aa/DIAPAccount.sol --contract DIAPAccount# Certora Prover
certoraRun contracts/aa/DIAPAccount.sol --verify DIAPAccount:specs/DIAPAccount.spec- 🔴 Session Key 重放攻击防护
- 🔴 添加 nonce 管理
- 🟡 每日限额重置逻辑
- 🟡 Paymaster 全局限制
- 🟡 白名单绕过防护
- 🟡 前端运行防护(提交-揭示)
- 🟢 批量操作限制
- 🟢 添加完整事件日志
- 第三方安全审计
- Bug 赏金计划
- 形式化验证
- 持续监控和改进
// ✅ 好:Session Key 只有有限权限
sessionKey.dailyLimit = 1000 * 10**18; // 限制每日花费
// ❌ 坏:Session Key 无限权限
sessionKey.dailyLimit = type(uint256).max;// 多层检查
function transferToken(...) external {
// 1. 权限检查
require(msg.sender == owner || isValidSessionKey(msg.sender));
// 2. 限额检查
require(amount <= dailyLimit);
// 3. 白名单检查
require(whitelist[token]);
// 4. 冻结检查
require(!frozen);
// 执行转账
}// ✅ 好:默认拒绝
function isWhitelisted(address target) public view returns (bool) {
return whitelist[target]; // 默认 false
}
// ❌ 坏:默认允许
function isBlacklisted(address target) public view returns (bool) {
return blacklist[target]; // 默认 false,允许所有
}// 记录所有关键操作
event SessionKeyUsed(address indexed key, uint256 amount, uint256 timestamp);
event LimitExceeded(address indexed key, uint256 attempted, uint256 limit);
event EmergencyFreeze(address indexed actor, uint256 timestamp);- ✅ 基础安全措施完善
⚠️ 存在中等风险漏洞- 🔴 需要修复高风险问题
- 立即: 修复 Session Key 重放攻击
- 短期: 实施所有中风险缓解措施
- 中期: 完成第三方审计
- 长期: 建立持续安全监控
| 风险类别 | 当前状态 | 目标状态 |
|---|---|---|
| 重入攻击 | 🟢 低 | 🟢 低 |
| 权限控制 | 🟢 低 | 🟢 低 |
| 签名验证 | 🟡 中 | 🟢 低 |
| DoS 攻击 | 🟡 中 | 🟢 低 |
| 前端运行 | 🟡 中 | 🟢 低 |
| 总体 | 🟡 中 | 🟢 低 |
完成所有建议修复后,系统安全性将达到生产级别。