签名最终结果而非中间步骤:为什么批量交易需要后置条件

本文指出当前区块链交易签名体验的痛点:用户几乎不读签名内容,尤其批量交易中,钱包显示不透明,硬件钱包更无法模拟执行。提出解决方案:通过后置条件(post-conditions)让用户只签名最终状态(如余额下限),而非中间步骤。EIP-8211(智能批量)标准支持此功能,允许在批量交易末尾添加谓词条目,由链上强制执行。这能防止前端欺骗、部分失败等安全问题,并简化硬件钱包的显示。文章还给出了开发者和用户的实践建议。

你在一个 DeFi 应用里点击了一个按钮。一个钱包弹了出来。它要求你签名。弹窗里显示:

  • 一个你从未听过的函数
  • 一个来历不明的合约地址
  • 一长串十六进制数据,可能与你期望的一致,也可能不是
  • 一个 gas 估算
  • 一个 确认 按钮

你确认了。你总是确认。每个人总是确认。

这是链上用户体验(UX)不为人知的秘密:几乎没有人真正阅读他们签署的内容。不是因为用户粗心,而是因为钱包展示的内容,从任何有实际意义的角度来看都是不可读的。那是一个哈希、一个选择器、一个原始调用。要正确解码它,需要知道调用图中每个合约的 ABI,模拟交易,追踪代币流动,并与你的意图进行交叉引用。

这可不是人们在咖啡还热的时候就能完成的事。

批量交易让问题严重恶化

单笔交易已经很难阅读。批量交易 —— 一次签名触发一系列调用 —— 更是一场可读性的灾难。

如今一个典型的 DeFi 批量交易可能看起来像:

  1. 批准 Router 使用 USDC
  2. 批准 Router 使用 WETH
  3. 在某个池中交换 USDC → WETH
  4. 封装更多 ETH
  5. 存入一个金库
  6. 质押金库份额
  7. 领取奖励
  8. 将奖励发送到另一个地址

钱包向你展示了八个不透明的步骤。每个步骤都有自己的目标、自己的参数、自己的潜在恶意。而意图 —— “我想最终让我的头寸被存入并质押,并且我愿意为此付出一些 USDC 和 WETH” —— 在屏幕上无处可寻。

而这里最危险的部分是:即使你仔细阅读了步骤 1,也没有任何东西将批次的诚实描述与其实际效果联系起来。一个恶意的前端可以要求你签署一个批次,其可读标签写的是“存入 Aave”,而底层调用却通过一个你从未见过的、刚刚注入资金的路由器掏空你的钱包。

而最重要的签名环境 —— 硬件钱包、气隙签名器、冷存储 —— 完全帮不上忙。Ledger、Keystone、二维码签名器、HSM:它们都没有 RPC、模拟器或代币流差异分析器。它们无法问“这笔交易到底会做什么?”。它们只能显示你交给它们的字节中的内容。

当这些字节是一个包含 30 次调用、带有动态解析 calldata 的批次时,设备就无法展示任何有意义的信息。它渲染选择器和十六进制数据,并希望你认得它们。正是在系统中最有价值的密钥被要求签名的关键时刻,用户被迫在黑暗中签名。

这就是核心的不对称性:最需要可读性的签名设备,恰恰是拥有最少上下文来呈现可读性的设备。热钱包无论进行多少更好的模拟,都无法弥补冷钱包的不足。不变条件必须存在于离线设备实际能看到的地方 —— 这意味着它必须存在于已签名载荷内部

验证每一步是错误的问题

对这个问题的常见回应是:让步骤可读。更好的解码器。更好的模拟器。更好的工具提示。所有这些都有帮助,但它们都有一个共同的局限性:它们试图逐步骤解释交易内部发生了什么

这是向用户提出了错误的问题。

用户不关心中间过程。他们关心结果。他们关心什么进去了、什么出来了:

“我愿意花费最多 1,000 USDC,并且我期望最终获得至少 0.35 WETH,同时我的 Aave 头寸保持不变。”

这句话是用户实际上能够阅读、理解和批准的。其他的一切 —— 哪个路由器、哪个池子、哪一跳、哪个滑点 —— 都是管道工程。

所以真正的问题是:我们如何让用户签署结果,而不是签署管道工程?

后置条件:签署结果,而非中间过程

后置条件 是关于交易完成后你账户状态的断言。这相当于在链上说:

“我不在乎这笔交易做了什么,只要它完成后: 我的 USDC 余额至少是 1,000 我的 WETH 余额至少是 10,000 我的 Aave 头寸没有缩水”

如果交易结算时这些不变条件中的任何一个被违反,整个交易就会回滚。原子性地。没有部分执行,没有“哦,最后一步失败了,但我们已经花掉了你的 USDC。”

后置条件反转了签名模型:

没有后置条件 有后置条件
用户审核每一步(并且失败) 用户审核结果(并且成功)
Dapp 被信任行为正确 链强制执行结果
Dapp 中的错误 → 用户损失资金 Dapp 中的错误 → 交易回滚
可读性随复杂度扩展 可读性是恒定的

重大的胜利不仅仅是安全性。而是认知压缩。一个包含 3 个步骤的批次和一个包含 30 个步骤的批次在签名屏幕上看起来一样:两行,一个人类可读的不变条件,一个你真正值得点击的 确认 按钮。

EIP-8211 使其成为一等特性

EIP-8211 —— 智能批处理 是一个提议的以太坊标准,用于在执行时、链上解析和验证其参数的批量交易。大部分提议旨在组合复杂的 DeFi 流程(将一个步骤的输出送入下一个步骤的输入,处理滑点,跨链协调),但同样机制中埋藏了我们一直在寻找的原语:谓词条目

谓词条目是批次中的一个步骤,但它不调用任何东西。它只读取链上状态并断言条件。如果任何断言失败,整个批次回滚。

相关的构建块是:

  • BALANCE 获取器 —— 读取给定账户的 ERC-20 或原生余额。正是你期望的 balanceOf(USDC, me) 查询。
  • STATIC_CALL 获取器 —— 通过 staticcall 读取任何可调用的内容:预言机价格、金库份额价值、债务余额、授权等。
  • 约束EQGTELTEIN)—— 对获取值的布尔检查。

将它们放在一起,后置条件就是批次末尾的一小段声明性数据:

ASSERT balanceOf(USDC, me)  >= 1000 * 10^6
ASSERT balanceOf(WETH, me)  >= 10000 * 10^18

没有新合约。没有额外操作码。没有链下谓词服务。批次已经用于实际步骤的相同编码被复用于不变条件检查。其余由链完成。

不变条件存在于签名字节中

这是使后置条件适用于硬件钱包和气隙签名器的特性,值得明确说明:

后置条件不是工具提示。它不是 UI 提示。它是批次本身的谓词条目,以签名者将签名的相同字节编码,采用固定、确定的格式。

这改变了最简签名器必须做的事情。硬件钱包不必理解 Uniswap、Aave 或任何 30 个中间调用。它只需要解析批次末尾的几个谓词条目 —— 每个都是 (token, account, comparator, threshold) 元组 —— 并以人类可理解的单位渲染它们:

此批次仅在完成后满足以下条件时成功:

  USDC 余额 of 0xAbc…  ≥  1,000.000000
  WETH 余额 of 0xAbc…  ≥  0.350000
  aUSDC 余额 of 0xAbc…  ≥  3,383.000000

[ 批准 ]   [ 拒绝 ]

无需 RPC。无需模拟。无需代币列表查找,除了设备已经拥有的常见 ERC-20 的符号/小数(对于未知代币,显示原始地址和原始单位,这仍然是诚实且可读的)。

批次中的其他一切 —— 交换跳转、路由器地址、calldata —— 可以在设备屏幕上保持不透明而不会使用户面临风险,因为它们都不能影响用户签署的结果。要么最终状态满足不变条件,批次提交;要么不满足,整个交易回滚。

谁产生不变条件

两种模式,相同的链上机制:

热钱包,模拟驱动。 当 dapp 请求发送批次时,它告诉钱包它想要做什么(“通过 Uniswap v4 将 1 ETH 交换为 USDC,然后供应给 Aave”)。钱包模拟意图,看到哪些余额预计会移动,导出一组最低预期值,并在将批次交给签名者之前自动附加后置条件

  • 之前:1 ETH, 0 USDC, 0 aUSDC
  • 之后:~0 ETH, 0 USDC, ~3,400 aUSDC(在 0.5% 滑点内)
  • 注入:aUSDC ≥ 3,383ETH ≥ 起始 − 1 − dust

冷钱包,载荷驱动。 宿主机(或 dapp 本身)组装包含后置条件的批次,并将其转发给硬件钱包。设备没有网络,但它不需要:不变条件已经在字节中。它显示它们,用户将其与意图进行比较,然后选择签名 —— 或不签名。

在两种情况下,用户最终签署了同一个问题:“在此交易结束时,我是否仍然至少拥有 X,最多拥有 Y?” 链强制执行该答案。dapp 不能对结果撒谎,因为结果就是用户实际签署的内容 —— 以字节形式,而不是 UI 上的描述。

为什么这改变了安全模型

今天的签名模型信任 dapp 来构建正确的交易,并信任钱包来解释该交易做了什么。两者都失败了,而且往往悄无声息。

后置条件改变了谁信任谁:

  • dapp 仍然构建交易。 它仍然是知道如何路由交换、选择池子、组合金库存款的实体。
  • 不变条件存在于已签名载荷中。 不在 dapp 的 UI 中,不在热钱包的工具提示中,不在中心化模拟器中 —— 而在到达签名设备的字节中。无论谁产生了它们(通过模拟的热钱包、通过声明意图的 SDK、通过用户手动覆盖),一旦它们进入载荷,每个下游方都看到相同的不变条件。
  • 签名者只需阅读不变条件。 冷或气隙设备可以解析一个小的固定格式 —— 代币、账户、比较器、阈值 —— 并显示它。它不必理解批次的其余部分就可以安全。
  • 链强制执行不变条件。 不是前端,不是 RPC,不是钱包 —— 而是 EVM 本身。

这关闭了最具破坏性的钱包漏洞类别:诱饵切换交易,其中 UI 说一件事而 calldata 做另一件事。在后置条件模型下,诱饵切换交易只会回滚。用户损失一点 gas。没有人损失资金。

它也顺便修复了一系列琐碎的失败场景:

  • 一个被夹击超过其滑点容忍度的交换 —— 回滚。
  • 一个静默返回少于预期份额的金库 —— 回滚。
  • 一个交付错误代币的桥 —— 回滚。
  • 一个有 bug 的路由器忘记将代币发送回来 —— 回滚。

所有这些目前都会产生半损坏的状态,用户必须手动解开。通过注入的后置条件,它们变成干净、原子的无操作。

普通用户应该记住什么

你不需要理解 EIP-8211 如何工作。你需要知道从你使用的钱包和 dapp 中要求什么:

  1. “这件事完成后我将拥有什么?” —— 签名屏幕应该用具体的数字回答,而不是十六进制。如果不是,你的钱包落后了好几年。
  2. “如果错了会发生什么?” —— 唯一可接受的答案是*“交易回滚,你损失 gas,你保留资金。”* 如果答案是“我猜你得去浏览器查查”,那么钱包并没有强制执行你的意图。
  3. “谁来决定最小值?” —— 应该是你的钱包。不是 dapp。不是中继者。钱包是唯一与你利益一致的方。

一个代表你注入后置条件的钱包,会将每个签名提示变成你与链之间的契约:“我只有在最终仍然拥有 X 和 Y 时才会签署。” 这是一个你可以阅读的句子。这是一个你值得点击的按钮。

前端开发者应该构建什么

如果你正在构建钱包、dapp 或介于两者之间的 SDK,具体工作如下:

  • 将不变条件放在载荷中,而不是 UI 中。 后置条件必须是批次末尾的谓词条目 —— 到达签名者的相同字节。热钱包屏幕上的工具提示不是安全特性;签名数据中的谓词条目才是。这允许没有互联网接入的硬件钱包仍然向用户显示有意义的内容。
  • 从意图派生不变条件(当你拥有上下文时)。 在热钱包上,将用户声明的目标(“将 1 ETH 交换为 USDC”)映射到一组最小的后置条件(“最终拥有 ≥ X USDC,最终花费 ≤ Y ETH”)。模拟作为这些数字的来源是可以的;但它绝不能是它们的执行者。执行者是你附加到批次的谓词条目。
  • 设计载荷使其离线可读。 后置条件应使用最简单的获取器 —— BALANCE 用于代币余额,简短的 STATIC_CALL 用于 LP 头寸、金库份额、借贷头寸。谓词条目越短越规律,受限签名器渲染它所需的解析就越少。避免只有全节点才能解释的巧妙编码。
  • 高亮显示不变条件,隐藏调用。 在每个签名器上 —— 热、冷或介于两者之间 —— 后置条件应该是签名屏幕上最突出的内容,以人类可理解的单位(代币、符号、小数)显示。高级用户可以展开底层调用;其他人则获得结果摘要。
  • 允许用户编辑阈值。 滑点不是 dapp 选择的魔法数字。它是用户偏好。热钱包应直接暴露调节旋钮;冷钱包流程应允许用户在宿主机上收紧边界并重新将载荷交给设备。批次相应地回滚。
  • 为硬件设备提供最小的谓词解析器。 如果你维护一个硬件钱包应用,最高杠杆的添加是一个能解析 EIP-8211 谓词条目的解析器,以符号/小数形式渲染 (token, account, comparator, threshold)。这一个特性就能将每个智能批次交易从不可读的十六进制变成用户可以评估的句子。

做得好的话,这将把整个“这笔交易签署安全吗?”的问题 —— 目前每次都是一项研究项目 —— 压缩成用户可接受或拒绝的一行可读内容。

结束语

链上用户体验的长久笑话是,我们构建了有史以来最强大的金融系统,然后要求它的用户一次一个十六进制字符串地批准它。可读性不是一个外观问题;它是整个堆栈安全模型实际终止的点。如果用户不能理解他们签署的内容,那么底层的密码学有多严谨就无关紧要了。

后置条件并不会让交易更容易阅读。它们使可读性对于安全性变得不必要。你签署结果。链强制执行结果。中间的一切成为计算机的问题 —— 这正是它应该归属的地方。

EIP-8211 恰好是最早的具体提案之一,为钱包提供了一种标准、可组合的方式,将那些结果注入到它们看到的每个批次中。钱包越早采用它,签名屏幕就越早不再是一场信仰之跃。

进一步阅读

  • 原文链接: blog.biconomy.io/sign-th...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Biconomy
Biconomy
江湖只有他的大名,没有他的介绍。