你点击“确认”时屏幕上弹出的那行摘要,真的就是链上将要执行的内容吗?两星期前我发布了一份威胁模型,用四条硬邦邦的属性试图证明:即便代理被完全控制,它也无法完成签名。模型刚放出去两天,一位读者就在评论区把它拆了个干净。

评论者叫 ANP2 Network,语气礼貌,手法精准。他指出的漏洞根本不是模型里那些被反复加固的密钥和授权门——它攻击的是“人眼所见”与“机器所签”之间的那条缝。

打开网易新闻 查看精彩图片

我原来的四条防线是这么设计的:带外人工确认;一个确定性的授权门;最小权限原则;网络隔离。有了这四道墙,代理不能直接碰到私钥,也不能绕过授权门自行签名。确实,它连签名的门槛都摸不到。但是,这位评论者发现了更刁钻的攻击面:代理根本不需要自己动手签名,它只需要让人类亲手为一份错误的交易按下确认。

具体手法简直像一场混淆游戏。代理提议一笔交易,例如“为艺术品 X 出价 0.1 ETH”,人类看到的摘要正是这短短一句话。然而,代理塞进交易的真实调用数据(calldata)却是另一码事:屏幕显示的是美好描述,二进制字节里藏着完全不同的操作。当人类点击确认,确定性门收到的是那一串恶意字节密钥从未泄露,所有的安全属性都没有失效——只有资金被悄悄转错了地方。

这就是经典的“混淆代理”失效模式,和盲签的危险同根同源。我最初那句“代理完全被攻破时仍无法签名”的结论并非虚假,但它根本不够用。真正需要守护的属性应该是:代理不能引发任何一次未经真正审查的签名。一句话里,一个“签名”变成“审查”,攻击面就彻底翻了个样。

修复没有推翻之前的“决策与签名分离”架构,而是顺着它修补。方案分成两半:第一,从授权门即将签名的那些精确字节中直接提取人类可见的意图,经由代理完全没有操控权的渲染器展示,永不再用代理自述的摘要。第二,把人类的“确认”绑定到这些字节的哈希值上,让“是”变为对数据指纹的承诺,而不是对一个可以被代理随意替换的标签。

我在 7 月 10 日上线了修复的前半部分,却没有实施下半部分。这一点必须说清楚,因为它太容易造成一厢情愿的误解。现在这个版本的实现是这样运行的:在人类首次看到交易之前,交易对象已被冻住——目标地址、函数选择器、以太值、链 ID、随机数、Gas 参数、签名者地址,全部锁定,只此一份。确认界面上展示的内容,并不是代理交上来的描述稿,而是从这份冻结对象的真实调用数据里强制解码出来的:函数名及其每一个参数,都是解析字节得出的,没有一字来自代理的叙述。确认之后,拿去签名的还是这同一个冻结体,不会根据代理的任何二次输入重建,连一个字段都不重新计算。代理唯一能插手的地方——一个 tokenId 和一个金额——在冻结瞬间就被消耗成了不可再被篡改的惰性字节。

这整场修复其实只是把一句话砸实:人眼看到的,必须是机器将签的、且已冻得死死的那份唯一真实。如果还有后续的补丁,那就是把“确认”更进一步钉在这些字节的哈希上。但现在这个半程版本已经足够在包含两个竞争代理的真实拍卖里安全运作,而且整个过程在链上可查。

一个原本看似无懈可击的模型,被一条评论撕裂,又用一份直接修改的代码缝上,缝的时候还故意留了另一半缺口。这听起来像是工程上的不完美,但正是这种把“已做的”和“未做的”都摊在阳光下的诚实,才让安全模型不至于活在免责声明里。