tx.origin 和 msg.sender 听谁的:EIP-7645 想把"始作俑者"改写成直接来电 图 1
tx.origin 和 msg.sender 听谁的:EIP-7645 想把"始作俑者"改写成直接来电 · 图 1

一个数字的两个答案

以太坊虚拟机里记录”来电者”的操作码有两个:ORIGIN(编号 0x32)返回整笔交易最初的外部账户地址,CALLER(编号 0x33,也写作 msg.sender)返回当前调用帧的直接上级。用户直接转账时两者相同;一旦交易穿过两层以上合约,落差就出现了——始作俑者还是你,直接来电却已经是中间合约。从 2016 年年中起,合约安全社区就一致反对用 ORIGIN 做授权判断,EIP-7645 在 Motivation 里把这段历史写成了正式注脚。

tx.origin 和 msg.sender 听谁的:EIP-7645 想把"始作俑者"改写成直接来电 图 2
tx.origin 和 msg.sender 听谁的:EIP-7645 想把”始作俑者”改写成直接来电 · 图 2

落差如何被钓走授权

经典剧本是这样的:你被诱导对恶意合约 X 发起一笔调用。X 内部再调用一个用 ORIGIN 校验权限的合约 Y——比如”只有交易发起者本人才能领奖励”。站在 Y 里看,CALLER 是 X,本该拒绝;但 ORIGIN 仍然等于你,于是 Y 把你当成本人办理。整个链条里你只签了一次名,权限却被中间人穿过去了。7645 还举了一个当代版本:在 EIP-4337 的智能钱包交易中,如果打包服务在某个合约里持有靠 ORIGIN 判定的权限,那么被它打包的任意一笔子交易都能冒充它,因为整批调用里 ORIGIN 始终显示的是打包者地址。

提案的做法:把旧键位直接改成新键位

2024 年 3 月 3 日提交的 EIP-7645 方案干净得近乎粗暴:所有客户端必须让 0x32 在一切上下文中推送与 0x33 相同的值,交易结构与验证规则一概不动。提案给这份改写列了三条理由:为账户抽象扫清一道必然要处理的障碍、消灭 ORIGIN 与直接发送者可被利用的落差、简化 EVM 执行模型。兼容性上它坦承不是无损的——凡是靠两者差异写逻辑的合约都会受影响,只是这类合约本就违反多年共识。提案状态如今停在 Stagnant(停滞),没有进入任何升级议程。

为什么账户抽象绕不开它

账户抽象的本质,是让”代表用户执行”的角色从签名转移到合约。钱包合约、入口合约、打包中继都会成为调用链上的新中间层,只要 ORIGIN 还保留”最初外部账户”的语义,它就一直暴露着两个问题:一是上文的权限劫持面,二是智能账户与外部账户在合约眼里不再同权——批量交易的发起者地址永远占据 ORIGIN,账户抽象方案不得不为此打各种补丁。7645 的算盘是把补丁一次性拆掉。提案原文引用了 EIP-7377 迁移交易与 EIP-3074 授权调用等同期方案,说明它把自己定位成地基级清理,这个判断与它始终停滞的现状形成了对照。

用户视角还剩什么可做

对只签名不写合约的用户,ORIGIN 之争的落点很朴素:钓鱼者利用的不是签名本身,而是你签名之后合约拿哪个字段当身份。这决定了 签名钓鱼是什么?钱包怎么防 反复强调的核对方法——签名前看清授权对象与用途,比事后追问哪个操作码背锅有效得多。合约开发者侧的纪律则一以贯之:身份判断用 msg.sender,永远不用 tx.origin。截至本文撰写,7645 未生效,主网两个操作码依旧各报各的值,任何把它写成”以太坊已取消 tx.origin”的说法都是错误的。

钱包与授权界面在争论什么

用户端与这对操作码最直接的重逢在授权弹窗上。很多合约的 approve 与领取逻辑只用 msg.sender,钓鱼者便退而求其次:诱导你亲手对恶意合约签名。这里有个容易高估的边界:把 ORIGIN 并入 SENDER 能堵住”中间合约冒用发起者身份”这一类越权,但对”你直接调用恶意合约、它作为直接来电者合法行使你给它的权限”这种剧本毫无帮助——那种情况下两个字段本来就相等。换句话说,7645 修的是合约程序员少踩一个坑,不是普通人的防骗符。核对签名的基本功仍然在弹窗那三十秒里:看清调用对象、用途与额度,方法参见 授权钓鱼怎么识别?签名前四查

技术债清理的一种范式

7645 值得记住的另一点是它的方式:不加新机制、不给新权限,只是把一个早已被废弃的读法并入正确读法。与它同类的清理在以太坊里并不多见,因为每个操作码都有历史包袱。风险提示:本文为机制与提案分析,不构成投资建议;具体合约行为以其部署源码为准。