window.ethereum之前长什么样:EIP-2696与request方法 图 1
window.ethereum之前长什么样:EIP-2696与request方法 · 图 1

任何写过连接钱包代码的人都熟悉这个调用:provider.request,一个方法名,一个对象参数,返回一个Promise。你觉得理所当然的这个形状,在2020年6月之前并不是标准。那时网站和钱包之间流通着一堆散装接口,各家私有方法、回调风格、参数封装互不兼容。EIP-2696由Micah Zoltu和Erik Marks在2020年6月提出,只干一件事:定义一个带request方法的对象,作为网页与以太坊提供方之间的传输层。提案状态是Final,它后来成为EIP-1193的骨干。

一份极小的合同

按规范,提供方必须在暴露的EthereumProvider对象上实现request方法;调用时传一个参数对象,含method字符串和可选的params;如果这次调用对应某个JSON-RPC方法,method字段就写RPC方法名,params按JSON-RPC类型写成JavaScript对象——注意是对象,不是先序列化成JSON字符串。规范画下的另一条边界同样关键:这份标准只管传输,不管载荷——哪些method合法、双方如何就payload达成一致,全部留给别的标准去回答。一个方法承载所有请求,返回值统一走Promise,错误统一reject。对比它要替代的旧世界(send、sendAsync、各家的私有扩展),这份合同小到几乎没有讨价还价空间。

从散装接口到一个方法

回看动因:运行时注入对象是浏览器插件和桌面应用的主流做法,插件把一个以太坊对象挂到全局,网站代码来拿。在2696之前,每个钱包挂出的对象形状不同,网站要么写适配层,要么只兼容一两家。把差异收敛到一个request方法后,适配成本被压到一行代码。更重要的是权力结构的变化:method字段意味着任何RPC调用都可以从这扇门过,钱包因此成为所有请求的唯一闸口——弹窗、拒绝、改写都是闸口动作。后来的多钱包发现(6963)、事件系统(1193补上的events)都是在同一扇门上加门铃和门牌,2696本身守的是门轴。

一个方法过所有请求的代价

把审计注意力换成攻击面也发生在这里。钓鱼网站不需要发明新接口——它调用的是同一个request,method字段照样写eth_sendTransaction或eth_sign。协议层没做任何区分,正规与恶意共用一条管道,防线被推到两端:钱包的参数显示质量和用户的识读能力。这也是为什么后来的标准拼命在载荷上做文章(结构化签名EIP-712的普及、交易模拟插件),传输统一之后,语义透明成了新的战场。理解了这一点,就理解了为什么一份只有半页合同的Final提案,会经常被安全分析文章引为起点。

一次调用在协议栈里的旅程

把网页里一行provider.request代码拆开看它经过的层。最底层是JSON-RPC,规定请求长成一个带id、method、params的信封;往上一层是各种传输方式——HTTP轮询、WebSocket、进程内注入;再往上是浏览器里现实使用的形态:网站无法自己发起JSON-RPC到钱包进程,中间靠注入对象搭桥。2696站的正是这座桥的位置:它不管信封里装什么method,只管把JavaScript对象递到桥对面,对面回一个Promise。于是同一行request代码,底下可能是真HTTP也可能是进程内直连,网站代码毫不知情。这也是它比早期散装接口高明的地方:散装接口把传输细节泄漏进方法名,各家实现互译成本高;一个方法一个对象,把兼容性压缩到了函数签名的粒度。

快速问答

问:2696和1193什么区别? 答:2696只定义request传输;1193把它包成完整的Provider接口并补上事件与连接生命周期,并声明依赖2696。 问:普通用户会感觉到区别吗? 答:感受是历史上的一次:钱包弹窗从各家不同变成同一道闸口,安全习惯也得以标准化。 问:params写对象和写字符串会怎样? 答:规范要求对象形式;实现可能宽容,但按规范写才不会被严格的提供方拒绝。

风险提示:本文描述接口标准史,不构成任何投资建议。签名请求请在设备上逐项核对。