钱包签名验证的是字节,不是页面文案。安全 UX 的职责,是让用户在可信边界内理解这些字节将授权谁、转移什么、作用在哪条链、何时失效,以及模拟结果仍有哪些不确定性。更漂亮的确认框不能修复被隐藏的无限授权、错误网络或恶意合约。
一、本文解决什么问题
Web3 用户常面对两类确认:
交易签名:签名后广播,可直接改变链上状态
消息签名:不一定立即上链,但可能用于登录、Permit、订单或资产授权用户看到的却可能只是:
Confirm transaction;- 一段十六进制 Calldata;
- “Sign to continue”;
- 一个被截断的地址;
- 一个未经验证的 ENS 名称;
- 一个写着“免费”的 Permit;
- 一个没有说明授权额度和 Spender 的 Approve;
- 一个来自 DApp 的“模拟成功”绿色提示。
这会产生真实资产风险:
- 用户以为是登录,实际签署了可执行订单或 Permit;
- 用户只想交换 100 Token,却授权无限额度;
- 用户在错误 Chain 上向同地址合约发送交易;
- 剪贴板中的收款地址被恶意软件替换;
- 钓鱼站复制品牌和 ENS 名称,诱导签署资产转移;
- 模拟基于旧区块或错误 RPC,实际执行结果不同;
- 钱包无法解析新合约,退化为 Blind Signing;
- Approval 成功后 Swap 失败,长期权限残留;
- 用户断开钱包,却以为链上 Allowance 已撤销。
本文覆盖大纲中的:
- Human-readable Transaction
- Approval Scope
- Unlimited Approval
- Permit
- Network Mismatch
- Address Checksum
- ENS / Name Resolution
- Phishing Warning
- Signature Preview
- Clipboard Risk
- Transaction Simulation
核心结论
- 可读交易必须由最终待签字段和可信 ABI/资产元数据生成,不能直接相信 DApp 提供的自然语言说明。
- Approval 的风险由 Spender、Token、额度、有效期、可调用路径和目标合约升级能力共同决定,不只是“是否无限”。
- Unlimited Approval 降低重复授权成本,却把未来 Token 余额暴露给长期 Spender 权限;默认策略应基于使用频率与风险,而不是一刀切。
- Permit 是签名授权,不等于无风险登录。签名可能被任何持有者提交,必须展示 Owner、Spender、Value、Nonce、Deadline、Chain 和 Verifying Contract。
- Network Mismatch 必须阻止签名和发送;同一个地址在不同链上可以代表完全不同代码与资产。
- Address Checksum 只能发现一部分输入错误,不能证明地址属于正确的人、正确合约或正确网络。
- ENS/Name Resolution 是名称到地址的解析层,不是身份认证。Forward 与 Reverse 记录、Chain Context 和解析时点都要校验。
- Phishing Warning 应基于可解释证据和阻断策略,不能只依赖容易误报的域名黑名单。
- Signature Preview 必须区分交易、EIP-191 消息、EIP-712 Typed Data 和合约账户签名语义。
- Transaction Simulation 是风险信号,不是执行保证;模拟与签名字段必须完全绑定,并显示状态时点与不确定性。
二、安全 UX 的信任边界
2.1 页面、钱包与链分别知道什么
- DApp 最了解业务意图,但页面可能被 XSS 或供应链攻击控制;
- 钱包掌握最终签名 Payload,但可能缺少 ABI 和业务上下文;
- RPC/模拟器能解释执行路径,但可能落后、错误或被恶意控制;
- 用户拥有最终授权权,却无法直接阅读字节。
安全 UX 必须组合多个来源,并清楚标记哪些信息已验证、哪些只是声明。
2.2 三层展示
- 原始事实:Chain ID、From、To、Value、Data、Fee、签名域;
- 解析结果:函数名、参数、Token、Approval、资产变化;
- 风险解释:未知合约、无限授权、代理升级、模拟失败、钓鱼信号。
任何自然语言都必须能回溯到原始事实。
2.3 不要把安全责任推给用户
“请自行核对所有信息”不是完整安全设计。产品应:
- 默认最小权限;
- 阻止错链;
- 对高风险签名增加摩擦;
- 自动解析常见权限;
- 提供撤销入口;
- 保存可审计的交易意图;
- 在无法解释时明确降级,而不是伪装成安全。
三、Human-readable Transaction
3.1 可读交易包含什么
type HumanReadableTransaction = {
network: {
chainId: number;
name: string;
};
sender: AddressLabel;
target: AddressLabel;
action: string;
value: AssetAmount;
parameters: readonly DisplayField[];
approvals: readonly ApprovalChange[];
assetChanges: readonly AssetChange[];
estimatedFee: FeeDisplay;
warnings: readonly RiskWarning[];
sourceBlock?: BlockRef;
};3.2 解析流程
3.3 ABI 的信任
Function Selector 只有 4 byte,不同函数签名可能碰撞。仅从 Selector 数据库猜函数名不足以证明真实 ABI。
ABI 来源优先级可以是:
- 产品维护的可信部署清单和 Code Hash;
- 已验证源码/可信 Explorer;
- 经过审计的协议 Registry;
- Selector 猜测,仅标为“可能”。
代理合约还要解析当前 Implementation,且升级后 ABI 可能变化。
3.4 Batch 与嵌套调用
multicall、Smart Account Batch、Router 和 Universal Executor 可能在一笔交易中包含多个动作。UI 不应只显示最外层 execute(bytes):
1. Approve TOKEN to Router: Unlimited
2. Swap 100 TOKEN for at least 0.5 ETH
3. Transfer remaining TOKEN to 0x...无法完全解析时,应展示未知子调用数量、Target、Value 和 Calldata Hash,并提高风险级别。
3.5 不能隐藏的字段
- Chain;
- From;
- To;
- 原生 Value;
- Token/NFT 资产变化;
- Approval/Operator;
- Deadline/Expiry;
- 最大费用;
- Delegatecall/升级/权限变更;
- 未知调用。
3.6 DApp 文案与签名字段绑定
DApp 可传递业务 Intent ID,但钱包/确认层必须计算:
intentPayloadHash = hash(normalized chainId, from, to, value, data)模拟、预览和签名都绑定同一 Hash,防止预览后字段被替换。
四、Approval Scope
4.1 Approval 是能力授权
ERC-20 approve(spender, amount) 允许 Spender 通过 transferFrom 在额度内移动 Owner Token。ERC-721/1155 还存在 Token ID 级 Approval 和 Operator 全局授权。
风险维度:
Who: Spender / Operator
What: Token Contract / NFT Collection
How much: Amount / Token ID / All Assets
Where: Chain ID
When: 是否有 Expiry
How used: transferFrom / protocol execution path
Can change: Spender 是否 Proxy/Upgradeable4.2 Spender 不一定是用户正在交互的页面合约
Swap UI 可能让用户授权 Router、Permit2 或 Vault,而交易 Target 是另一个 Executor。确认页必须展示真正被授权地址,而不是只显示 DApp 品牌。
4.3 Exact Approval
只授权本次所需额度:
需要支出:100 TOKEN
建议授权:100 TOKEN
当前授权:0 TOKEN优点:最小化未来风险;缺点:重复操作需要再次授权,增加 Gas 和交互步骤。
4.4 Bounded Approval
可提供预设:
- 本次额度;
- 自定义额度;
- 有上限的周期额度;
- Unlimited,作为高级选项并明确风险。
不要使用暗色模式按钮、预选无限额度或模糊文案诱导扩大权限。
4.5 Approval 状态机
Approval 交易 Confirmed 后再发业务交易,或使用协议支持的原子 Permit/Batch,避免在本地乐观假设额度已生效。
4.6 修改非零 Allowance 的边界
历史上部分 ERC-20 和集成会建议先将 Allowance 设为 0,再设置新值,以降低某些竞态/兼容问题。Token 行为并不完全统一,应使用成熟库和目标 Token 实测,不能假设所有 Token 都支持相同增减接口或返回值。
五、Unlimited Approval
5.1 为什么常见
授权最大 uint256 可减少后续 Approve 交易和 Gas,适合频繁使用同一可信 Spender 的场景。
但风险不是“本次余额”,而是 Spender 在授权有效期间可能移动的未来余额。
5.2 风险来源
- Spender 合约漏洞;
- Proxy Upgrade 被劫持;
- Admin/Signer Key 泄露;
- 任意调用或 Router 漏洞;
- 用户误授权钓鱼 Spender;
- Token 回调/非标准行为;
- 长期不用却未撤销;
- 跨链同地址造成误认。
5.3 UI 要求
授权对象:Router 0x...
授权资产:USDC(Ethereum)
授权额度:无限
当前余额:1,000 USDC
风险范围:包括未来转入该地址的 USDC
撤销方式:链上 revoke,需支付 Gas不要只写“Recommended”。如果确实基于频繁交易推荐,应说明原因和替代方案。
5.4 默认策略
合理策略取决于:
- 使用频率;
- Spender 审计和升级治理;
- Token 价值;
- 用户 Gas 成本;
- 是否支持 Permit;
- 是否能原子授权与执行;
- 用户风险偏好。
高价值、低频操作优先 Exact Approval;高频小额可以提供 Bounded/Unlimited 选择。
5.5 Allowance Inventory
DApp 可以提供授权管理页:
- 按 Chain/Token/Spender 展示;
- 区分 Exact 与 Unlimited;
- 显示最后使用和合约风险;
- 支持批量选择,但每条撤销仍是链上操作;
- Receipt/Reorg 后更新状态;
- 不声称断开钱包会撤销授权。
六、Permit
6.1 Permit 解决什么
Permit 允许 Owner 通过离线签名表达 Token Allowance,由 Relayer、Spender 或后续交易提交链上,从而减少单独 Approve 交互。
常见但不同的方案包括:
- EIP-2612 风格 Token Permit;
- DAI 等历史变体;
- NFT Permit 相关标准/实现;
- Permit2 等独立授权合约方案;
- 协议自定义订单或授权签名。
不能因为函数名叫 permit 就假设字段和安全语义相同。
6.2 EIP-2612 核心上下文
常见 Typed Data 语义包含:
owner
spender
value
nonce
deadline
verifyingContract
chainId via EIP-712 domain具体 Domain 字段、Token Name/Version 和 Nonce 方法需要从合约实现和规范确认。
6.3 Permit 不是“只是登录签名”
签名持有者可以在 Deadline 前提交 Permit,创建链上 Allowance。即使用户未支付 Gas,资产权限仍可能被使用。
确认页必须明确写:
- 授权 Token;
- Spender;
- 额度;
- 有效期限;
- 网络与 Verifying Contract;
- 签名是否可由任何持有者提交;
- 后续是否会立即执行 Transfer/Swap。
6.4 Nonce 与 Deadline
Permit 防重放通常依赖 Nonce,时间限制依赖 Deadline。风险:
- Deadline 太长;
- Nonce 查询来自错误 Chain;
- 用户并发签署多个 Permit;
- 签名未提交但已泄露;
- Token 合约升级改变 Domain;
- 前端错误使用本地时间解释链上截止。
6.5 Permit2 的两层权限
独立授权合约方案可能先要求用户对该合约做一次 ERC-20 Approval,再通过签名细分协议 Spender、额度和期限。
UI 必须区分:
Token -> Permit Contract 的链上长期 Allowance
Permit Contract -> 应用 Spender 的签名权限只展示第二层会隐藏第一层长期授权风险。
6.6 签名失败与提交失败
用户完成 Permit 签名后,后续链上交易仍可能失败。产品应跟踪:
- 签名是否已上传给服务端;
- 是否已广播;
- Nonce 是否被消费;
- Deadline 是否过期;
- 业务交易是否成功;
- 未使用签名是否需要在服务端删除或链上失效。
删除服务端副本不能保证其他持有者没有复制签名。
七、Network Mismatch
7.1 同地址不等于同对象
地址 0xabc... 在 Ethereum、L2 和测试网可以部署不同代码,甚至一条链有合约、另一条链是 EOA。
因此所有显示都绑定:
chainId + address7.2 Read Chain、Wallet Chain 与 Intent Chain
- Read Chain:当前页面数据来源;
- Wallet Chain:钱包当前网络;
- Intent Chain:模拟和待签交易绑定网络。
写操作要求 Wallet Chain 与 Intent Chain 一致。Read Chain 可以不同,但 UI 必须清晰显示。
7.3 错链时阻断什么
[x] 禁止发起签名交易
[x] 使旧 Simulation 失效
[x] 清除旧 Gas/Nonce/Fee
[x] 重新选择该链合约地址
[x] 重新解析 ENS/Token 元数据
[ ] 不必阻止用户浏览只读页面7.4 切链不是强制命令
wallet_switchEthereumChain 可能被拒绝或不支持。DApp 应保留原状态,提供手动指引,不自动反复弹钱包。
切链成功后重新读取 Chain ID,不能只依赖 Promise Resolve。
7.5 RPC 错链
即使钱包 Chain 正确,Public RPC 也可能配置错误。客户端启动时调用 eth_chainId,高价值场景核对关键合约 Code Hash 和已知 Checkpoint。
八、Address Checksum
8.1 EIP-55 解决什么
EIP-55 使用地址十六进制字符的大小写携带校验信息,可发现一部分输入错误。它不改变底层 20-byte 地址。
8.2 Checksum 不能证明什么
- 地址属于预期收款人;
- 地址来自正确网络;
- 地址是安全合约;
- 地址不是钓鱼地址;
- 用户没有复制错但格式合法的另一个地址;
- 合约当前代码与过去相同。
8.3 输入策略
function parseRecipient(input: string): Address {
const trimmed = input.trim();
const address = parseEthereumAddress(trimmed);
if (hasMixedCase(trimmed) && !isValidChecksumAddress(trimmed)) {
throw new InvalidChecksumError(trimmed);
}
return address;
}全小写地址在一些库中可被视为非校验格式但仍是合法地址。产品应明确策略,不应自行实现 Hash 规则。
8.4 地址展示
只显示 0x1234…abcd 便于扫描,却容易被 Address Poisoning 利用。高价值操作应:
- 显示 ENS/标签和完整地址入口;
- 突出更多非连续字符;
- 与可信地址簿比对;
- 首次地址增加确认;
- 硬件钱包屏幕独立核对;
- 显示最近使用来源而非只按相似度推荐。
8.5 零地址与特殊地址
根据业务校验零地址、Precompile、系统合约和 Burn Address。不要把所有特殊地址简单判为非法,也不要允许资产转账表单默认为零地址。
九、ENS 与 Name Resolution
9.1 名称解析是动态查询
ENS 等名称系统把人类可读名称映射为地址或其他记录。解析结果可能随 Owner、Resolver 和 Record 更新而变化。
UI 应展示:
输入名称
解析网络/Registry
解析地址
解析区块或时间
是否通过额外验证9.2 Forward 与 Reverse
- Forward Resolution:
name -> address; - Reverse Resolution:
address -> name。
Reverse Name 不是天然可信标签。常见安全展示会验证 Reverse 返回的 Name 再 Forward 解析回同一地址,才显示为一致名称。
9.3 Chain Context
不同网络可能使用不同 Registry、Resolver 或跨链解析方案。不能在 Chain A 解析名称后直接用于 Chain B 交易。
还要处理多 Coin Record:名称可能对不同 Coin Type 配置不同地址。
9.4 Homograph 与 Unicode
Unicode 相似字符可制造视觉相同的钓鱼名称。UI 应使用成熟规范化和安全显示策略:
- 对可疑混合脚本告警;
- 提供 Punycode/规范表示;
- 不自行拼写 Unicode 规则;
- 高价值转账仍展示最终地址;
- 品牌名称使用可信 Allowlist/已验证资料。
9.5 解析与签名绑定
用户确认的是最终地址,不是名称字符串。解析后应冻结:
name + resolvedAddress + chainId + resolutionBlock如果签名前名称重新解析到不同地址,必须重新确认。
9.6 Resolver 故障
解析失败时不要静默使用历史地址。可以展示缓存结果及其时间,但要求用户明确接受或直接输入地址。
十、Phishing Warning
10.1 钓鱼信号
- 域名与官方域名相似;
- 新注册或信誉未知域名;
- TLS/证书异常;
- 钱包连接后立即要求高风险签名;
- 要求输入助记词/私钥;
- Spender/Target 与官方部署不符;
- Unlimited Approval 与页面功能不匹配;
- 签名 Domain 指向另一站点;
- 模拟显示资产流向未知地址;
- 前端资源或 DNS 被篡改。
10.2 风险分级
type RiskLevel = 'info' | 'warning' | 'high' | 'block';
type RiskFinding = {
code: string;
level: RiskLevel;
evidence: readonly string[];
userAction: string;
};高风险警告必须说明证据,例如“将授予未知地址无限 USDC 额度”,而不是泛泛写“此交易可能有风险”。
10.3 何时阻断
建议阻断:
- Chain 与 Intent 不一致;
- 签名字段与预览不一致;
- 已知恶意 Target/Spender;
- 助记词/私钥输入流程;
- Simulation 显示与用户意图相反的资产转移;
- 无法解析的通用资产转移且风险极高;
- 安全策略明确禁止的合约或函数。
警告疲劳会让用户习惯点击确认。低价值提示不要与真正资产风险使用同等级视觉。
10.4 黑名单边界
域名、地址和合约黑名单:
- 有更新延迟;
- 容易漏掉新攻击;
- 可能误报;
- 同地址在不同链语义不同;
- Proxy Implementation 可变化。
黑名单只是一个信号,还要结合行为分析、部署清单和 Simulation。
10.5 品牌与官方链接
官方域名、合约地址和社交渠道应通过多个可信来源发布。发生前端安全事故时,团队需要能快速:
- 下线受影响前端;
- 标记恶意域名;
- 通知钱包和安全服务;
- 发布链上/签名公告;
- 指导用户撤销 Approval/Permit;
- 监控受影响地址。
十一、Signature Preview
11.1 先区分签名类型
| 类型 | 典型用途 | 主要风险 |
|---|---|---|
| Transaction | 链上状态变更 | 直接转移资产/调用合约 |
| EIP-191 / Personal Sign | 登录、声明、挑战 | 文本欺骗、跨域重放、原始 Hash 误导 |
| EIP-712 Typed Data | Permit、订单、授权 | 可读但可能直接产生资产权限 |
| Raw/Unknown Bytes | 专有协议 | 用户难以理解,Blind Signing |
| Smart Account/UserOp | 账户自定义执行 | Batch、Session、Paymaster 等复杂语义 |
11.2 EIP-712 预览
至少展示:
- Domain Name、Version;
- Chain ID;
- Verifying Contract;
- Primary Type;
- 关键字段;
- Nonce;
- Deadline/Expiry;
- 可能产生的资产或权限结果。
“结构化”不代表“安全”。攻击者同样可以构造结构清晰的恶意 Permit。
11.3 登录签名
安全登录消息应绑定:
- Domain;
- URI;
- Address;
- Chain ID;
- 一次性 Nonce;
- Issued At;
- Expiration;
- Statement/Resources。
固定可重复消息不能抵抗重放。
11.4 Hash 和十六进制签名
如果只能显示 Hash/Bytes,钱包不能知道其真实语义。应:
- 明确标记“无法解析”;
- 不伪造自然语言;
- 展示调用来源和 Domain;
- 对高价值账户默认拒绝或要求高级确认;
- 使用独立 Simulation/Policy Engine。
11.5 Signature 与提交生命周期
消息签名可能在未来由第三方提交。预览要说明:
- 谁可以提交;
- 是否一次性;
- 何时失效;
- 如何撤销;
- 签名是否已上传服务端;
- 断开钱包是否影响其有效性。
十二、Clipboard Risk
12.1 地址替换
恶意软件可监听剪贴板,将 Ethereum 地址替换为攻击者地址。由于首尾字符可预先碰撞,简单核对 4+4 字符不够。
12.2 DApp 输入防护
粘贴地址后:
- 去除不可见空白;
- 解析并规范化地址;
- 验证 Mixed-case Checksum;
- 展示完整地址或分组;
- 查询地址标签、代码和历史关系;
- 首次收款地址二次确认;
- 检测与近期地址高度相似但不同的 Address Poisoning;
- 签名前再次核对最终
to。
12.3 复制操作
复制地址时:
- 使用明确按钮和成功反馈;
- 不自动把私钥、助记词、签名写入剪贴板;
- 移动端提示系统剪贴板可能被其他应用读取;
- 敏感内容必要时提供短时清除,但不能保证所有系统历史都被删除;
- 不在 Analytics 记录被复制内容。
12.4 QR Code
QR 减少手工输入但并非绝对安全:
- 页面 QR 可被 XSS 替换;
- 扫码结果仍需展示地址和 Chain;
- Payment URI 参数可能包含 Value/Token/Calldata;
- 相机解析库可能误处理;
- 线下二维码贴纸可被覆盖。
12.5 硬件钱包核对
最终以可信设备屏幕展示的地址和金额为重要独立信号。若设备只显示 Blind Signing Hash,剪贴板替换和页面篡改风险仍无法由硬件隔离解决。
十三、Transaction Simulation
13.1 Simulation 要回答什么
- 交易是否 Revert?
- 资产如何变化?
- 创建/修改了哪些 Approval?
- 调用了哪些合约?
- 是否 Delegatecall、升级、安装模块?
- 是否转移 NFT/原生资产?
- 实际接收者是谁?
- Gas 与 Fee 估计多少?
- 是否命中已知恶意模式?
13.2 模拟与签名字段一致
type SimulationBinding = {
chainId: number;
from: `0x${string}`;
to?: `0x${string}`;
value: bigint;
data: `0x${string}`;
nonce?: bigint;
transactionType?: number;
blockNumber: bigint;
payloadHash: `0x${string}`;
};签名前重新计算 Payload Hash。任何字段变化都使模拟失效。
13.3 模拟来源
- 本地/公共 RPC
eth_call; - Debug Trace;
- 第三方 Simulation Service;
- Fork 节点;
- 钱包内置模拟。
多个来源的能力、隐私和客户端版本不同。第三方服务会看到用户地址和交易意图,应纳入隐私评估。
13.4 Simulation 的不确定性
模拟成功后,以下因素可变化:
- Balance/Allowance;
- Oracle;
- AMM 储备;
- Deadline;
- Nonce;
- 合约升级和暂停;
- Base Fee;
- 区块时间和 Coinbase;
- 前置交易、MEV 和 Reorg。
因此仍需链上 Min Output、Deadline、额度和权限限制。
13.5 资产变化展示
发送:100 USDC
预计收到:0.038 ETH
最少收到:0.037 ETH
新增授权:Router 可使用最多 100 USDC
Gas:预计 0.002 ETH,最大 0.004 ETH
模拟区块:#12345678
风险:输出受池状态变化影响区分“预计”“最少”“最大”和“最终”。
13.6 未知 Token 与欺骗性元数据
Token Name/Symbol/Decimals 可由恶意合约伪造或与知名资产相同。显示时绑定 Chain + Contract Address,并优先使用可信 Token List 和 Code/Deployment 信息。
13.7 Simulation 失败
区分:
- 确定性 Revert;
- RPC/Trace 不支持;
- Archive 状态缺失;
- Simulation Service 超时;
- 无法解析但执行可能成功;
- 状态过快变化。
高风险且无法模拟时可以阻断;低风险可以明确降级并要求用户核对原始字段。策略需要产品和风险模型共同定义。
十四、把 UX 组合成签名前决策流
14.1 决策结果
type SigningDecision =
| { action: 'allow'; preview: TransactionPreview }
| { action: 'warn'; preview: TransactionPreview; findings: RiskFinding[] }
| { action: 'block'; findings: RiskFinding[] };用户覆盖高风险警告时,记录的是风险代码和 Intent Hash,不记录签名/私钥。
14.2 安全摩擦要与风险匹配
低风险重复确认会造成警告疲劳;高风险操作可以:
- 要求展开完整 Spender;
- 手动输入部分地址;
- 等待短暂冷静期;
- 使用硬件钱包;
- 多方审批;
- 限额和 Timelock;
- 禁止 Blind Signing。
十五、移动端 UX
15.1 小屏信息层级
首屏优先展示:
- 资产变化;
- Approval/Permit;
- 目标和网络;
- 最大费用;
- 风险告警。
原始 Calldata、完整 ABI 和 Trace 放在可展开详情,但不能隐藏高风险权限。
15.2 App 切换
DApp 跳转钱包后,页面可能后台冻结。恢复时:
- 检查签名 Request ID;
- 校验 Chain/Account;
- 不重复创建签名;
- 重新确认 Simulation 是否过期;
- 不信任 Deep Link 的
success参数; - 以钱包响应和链上 Receipt 为准。
15.3 截图与屏幕共享
高风险页面避免展示助记词、私钥和完整恢复材料。签名确认可以显示地址和资产事实,不应为了“隐私”隐藏到无法核对。
十六、常见错误案例
16.1 DApp 说“登录”就按登录展示
实际 EIP-712 可能是 Permit 或订单。钱包必须按签名字段独立解析。
16.2 Approval 弹窗只显示 Token Symbol
同名 Token 可伪造。必须展示 Chain、Token Contract、Spender 和 Amount。
16.3 默认无限授权并隐藏 Exact 选项
这将未来余额暴露给 Spender。应提供清晰范围选择和撤销说明。
16.4 Permit 没有 Gas 就当作无风险
签名可被第三方提交并产生 Allowance。必须按资产授权展示。
16.5 Checksum 通过就认为地址正确
Checksum 只能检测部分输入错误,不能验证身份和网络。
16.6 只显示 ENS 不显示解析地址
名称记录可能变化或被劫持。签名前展示并冻结最终地址。
16.7 钓鱼警告只有“请注意风险”
没有证据和行动建议,用户无法判断。应说明未知 Spender、无限额度或 Domain 不一致等具体原因。
16.8 复制地址后只核对首尾 4 位
Address Poisoning 可以制造相似地址。高价值转账使用地址簿、更多字符和硬件屏幕核对。
16.9 Simulation 绿色就承诺一定成功
状态和排序会变化。必须说明模拟区块、边界,并用链上约束保护结果。
16.10 钱包断开后显示“所有授权已撤销”
连接 Session 与链上 Approval/Permit 独立。撤销需要链上交易或协议 Nonce/状态更新。
十七、安全实现建议
17.1 可信部署 Registry
type ContractIdentity = {
chainId: number;
address: `0x${string}`;
label: string;
protocol: string;
codeHash?: `0x${string}`;
proxy?: {
implementation?: `0x${string}`;
upgradeAdmin?: `0x${string}`;
};
abiVersion: string;
};Registry 版本化并通过可信发布渠道更新。
17.2 Risk Rule
type RiskRule = {
id: string;
evaluate(context: SigningContext): RiskFinding | null;
};
const unlimitedApprovalRule: RiskRule = {
id: 'unlimited-approval',
evaluate(context) {
const approval = context.approvals.find((item) => item.isUnlimited);
if (!approval) return null;
return {
code: 'UNLIMITED_APPROVAL',
level: 'high',
evidence: [approval.spender, approval.token],
userAction: '改为本次所需额度,或确认你信任该 Spender。',
};
},
};规则输出可解释证据,不直接依赖模糊分数。
17.3 元数据降级
Token/ENS/ABI 元数据失败时仍展示原始 Chain + Address,不应空白或用未经验证的缓存名称替代。
17.4 安全日志
记录:
- Intent ID;
- Payload Hash;
- Chain;
- Target/Spender 地址;
- 风险代码;
- 用户选择;
- Simulation Block;
- Transaction Hash。
不记录助记词、私钥、完整签名、WalletConnect URI 和敏感 Raw Payload。
十八、测试与验证方法
18.1 Human-readable Transaction
[ ] 已知 ABI 正确解码
[ ] Selector Collision 不被当成确定 ABI
[ ] Proxy Implementation 变化后缓存失效
[ ] Batch 子调用完整展示
[ ] 未知调用明确标记
[ ] 预览 Payload Hash 与最终签名字段一致18.2 Approval/Permit
- Exact、Bounded、Unlimited;
- ERC-20 非标准返回;
- ERC-721 Token Approval;
setApprovalForAll;- EIP-2612 与非标准 Permit;
- Permit Deadline 过期;
- Nonce 重放;
- Permit2 两层权限;
- Approval Receipt Reorg;
- Spender Proxy Upgrade。
18.3 Network/Address/ENS
- 钱包与 Intent Chain 不一致;
- RPC 返回错误 Chain ID;
- 同地址不同链不同 Code;
- Mixed-case Checksum 错误;
- 全小写地址产品策略;
- ENS Forward/Reverse 不一致;
- Unicode Homograph;
- 签名前 ENS 记录变化;
- Resolver 暂时不可用。
18.4 Phishing
- 品牌相似域名;
- 未知 Spender 无限授权;
- DApp 文案与 EIP-712 Primary Type 不一致;
- 已知恶意地址;
- 新 Proxy Implementation;
- 模拟资产流向第三方;
- 黑名单误报后的安全降级;
- 高风险 Block 规则不可被普通 UI 参数绕过。
18.5 Clipboard
- 复制合法地址;
- 测试工具替换为相似地址;
- 粘贴后触发差异和地址簿警告;
- 最终交易
to再次核对; - 硬件屏幕显示真实地址;
- 剪贴板内容不进入 Analytics。
18.6 Simulation
- 模拟成功后余额变化;
- Oracle/AMM 变化;
- Deadline 过期;
- 合约升级;
eth_call成功但实际被 MEV 影响;- Trace Service 与本地 RPC 结果不同;
- Simulation 结果迟到且 Payload 已改变;
- Reorg 使 Simulation Block 不再规范。
18.7 可用性测试
让目标用户在不看教程的情况下回答:
- 这笔操作在哪条链?
- 谁可以动用哪种资产?
- 额度和有效期是多少?
- 最坏费用是多少?
- 签名后是否立即上链?
- 如何撤销?
- 哪些结果只是模拟?
无法回答说明信息架构需要改进,而不是增加更多技术术语。
十九、监控与事故响应
19.1 指标
signature_warning_shown{code,chain}
signature_warning_override{code,chain}
unlimited_approval_selected{protocol,chain}
permit_signature_requested{type,chain}
simulation_failure{reason,chain}
network_mismatch_blocked{wallet,chain}
phishing_block{rule,chain}
address_book_mismatch{chain}控制标签基数,不把地址和域名直接作为无限 Metrics Label。
19.2 前端供应链事故
若 DApp 被注入恶意交易:
- 下线或隔离受影响构建;
- 保存恶意 Bundle、域名和 Payload 证据;
- 通知钱包、安全服务和用户;
- 发布可信官方公告;
- 识别恶意 Spender/Permit/Transaction;
- 提供链上撤销步骤;
- 监控资金流和受影响地址;
- 发布 Postmortem 和长期修复。
不要要求用户把助记词提交给“恢复工具”。
二十、方案选择
| 场景 | UX 安全重点 | 主要代价 |
|---|---|---|
| 简单转账 | 地址、网络、金额、Checksum | 核对摩擦 |
| DEX | Approval、Slippage、Simulation、MEV | 信息量较大 |
| NFT 市场 | Operator Approval、Collection/Token ID | 全局授权风险 |
| Permit 登录/交易 | Domain、Spender、Value、Deadline | 签名语义复杂 |
| Smart Account | Batch、Module、Session Key、Paymaster | 多层执行解析 |
| 跨链桥 | 源/目标链、Token 映射、Finality | 流程长且不确定 |
| Treasury | 地址簿、硬件屏幕、多方审批 | 操作效率降低 |
安全 UX 不应追求最少点击,而应追求每次高风险点击都对应清晰、准确、可验证的决定。
二十一、上线检查清单
[ ] 可读预览从最终待签字段生成
[ ] ABI 来源和置信度可追踪
[ ] 未知/Batch/Delegatecall 调用不会被隐藏
[ ] Chain、From、To、Value、Fee 始终可查看
[ ] Approval 展示 Token、Spender、额度和作用范围
[ ] Unlimited Approval 非隐藏默认且说明未来余额风险
[ ] 提供 Exact/Bounded Approval 和撤销入口
[ ] Permit 展示 Owner、Spender、Value、Nonce、Deadline 和 Domain
[ ] Permit2 等方案展示底层长期 Allowance
[ ] Wallet Chain、Read Chain、Intent Chain 明确区分
[ ] Network Mismatch 阻止签名并使旧 Simulation 失效
[ ] 地址解析使用成熟 Checksum 实现
[ ] 高价值地址不只显示首尾 4 位
[ ] ENS 展示最终地址并验证 Forward/Reverse 一致性
[ ] Unicode 可疑名称有安全显示策略
[ ] Phishing Warning 包含具体证据和行动建议
[ ] 已知恶意/字段不一致场景可以硬阻断
[ ] 签名类型和链上后果明确区分
[ ] 剪贴板内容不进入日志与 Analytics
[ ] Simulation 与签名 Payload Hash 完全绑定
[ ] 模拟结果标注区块、来源和不确定性
[ ] 已完成 Approval、Permit、错链、钓鱼、剪贴板和 Reorg 测试二十二、总结
Web3 UX 与安全的共同目标,是让用户理解并控制真实授权,而不是让确认流程看起来更顺滑。
真正需要记住的是:
- Human-readable Transaction 必须从最终待签字段、可信 ABI 和模拟结果生成,并保留原始事实入口。
- Approval 是长期能力授权,Scope 由资产、Spender、额度、期限、网络和升级能力共同决定。
- Unlimited Approval 的风险覆盖未来余额,是否使用应基于频率、合约治理和用户选择。
- Permit 虽然只是消息签名,却可能由第三方上链创建权限,必须按资产授权级别展示。
- Chain ID 与地址共同定义对象;Network Mismatch 必须阻断写操作并重新模拟。
- Checksum、ENS 和品牌标签都是辅助信号,不能证明地址身份或合约安全。
- Phishing Warning 应可解释、分级并在确定高风险时阻断,避免警告疲劳。
- Signature Preview 必须说明签名类型、Domain、Nonce、Deadline 和可提交者。
- Clipboard 与二维码只改变输入通道,不消除地址替换风险,最终仍需独立核对。
- Transaction Simulation 降低未知性,但只有链上约束、最终签名字段和实际 Receipt 共同决定结果。
真正安全的确认体验,会让用户在点击之前知道自己授予了什么,也会在系统无法确定时诚实地说“不确定”。
问答复盘
Q1:DApp 页面显示“兑换 100 USDC”是否足以证明签名内容?
答: 不足。必须从最终 chainId/from/to/value/data 解码并模拟,确认预览 Payload Hash 与实际签名字段一致。
Q2:Unlimited Approval 的风险是否只等于用户当前 Token 余额?
答: 不是。只要授权仍有效,Spender 可能使用未来转入的 Token,风险还取决于 Spender 合约、升级权限和漏洞。
Q3:Permit 不消耗 Gas,为什么仍是高风险签名?
答: Permit 可以由持有签名的人提交链上并创建 Allowance。用户必须核对 Spender、额度、Nonce、Deadline、Chain 和 Verifying Contract。
Q4:Address Checksum 通过是否意味着收款地址正确?
答: 不意味着。Checksum 只能发现部分输入错误,无法证明地址身份、网络、合约代码或是否为钓鱼地址。
Q5:ENS Reverse Record 显示知名名称,可以直接信任吗?
答: 不可以。应把 Reverse Name 再做 Forward Resolution,确认返回同一地址,并展示最终地址和解析网络。
Q6:Wallet Chain 与 DApp Read Chain 不一致时,是否必须阻止页面使用?
答: 不必阻止只读浏览,但必须阻止签名和发送,直到 Wallet Chain 与 Transaction Intent Chain 一致并重新模拟。
Q7:EIP-712 Typed Data 可读,是否天然比普通消息安全?
答: 不天然安全。结构化字段提高可解释性,但恶意 Permit、订单和授权同样可以使用 EIP-712,仍需验证 Domain 和业务后果。
Q8:复制地址后核对首尾字符为什么不够?
答: 攻击者可以生成首尾相似地址并通过 Address Poisoning 混入历史记录。高价值操作应使用地址簿、更多字符和硬件屏幕核对。
Q9:Transaction Simulation 成功是否保证交易不会 Revert?
答: 不保证。模拟与执行之间状态、时间、Nonce、Oracle、AMM 和交易排序可能变化,应通过 Deadline、Slippage 和额度等链上约束保护。
Q10:用户断开钱包后,Approval 和 Permit 会自动失效吗?
答: 不会。钱包连接是通信状态,链上 Allowance 或仍有效的签名授权需要通过链上撤销、Nonce 或协议状态显式失效。
延伸知识
- 交易状态机:Simulation、Signature、Pending、Replacement、Receipt 与 Reorg。
- Wallet Connection:Provider、Session、Chain Switching 与 Disconnect 边界。
- 签名安全:EIP-191、EIP-712、Nonce、Deadline 和跨域重放。
- 智能合约权限模型:Owner、Role、Multisig、Timelock 与 Guardian。
- Oracle 与 MEV:价格、Slippage、抢跑、夹击和私有交易。
- 前端供应链安全:CSP、依赖锁定、可信构建、DNS 与事故响应。