跳到主要内容

钱包应该自己查账

Robert Mao
BlockchainSecurityVerifiability

钱包保住了私钥,屏幕上的 balance 就一定可信吗?

上一篇谈了我们怎样把 Passkey 带到账户和授权设计里。这次我想接着讲另一件事:钱包应该有能力核对自己的账,而不是服务器报一个数字,它就照着显示。

这也是我们设计下一代钱包时的一条原则。钱包自己做轻量验证,关注与自己有关的交易;自己的本地记录,也用可验证的账本来管理。密钥保护之外,数据是否可信,同样应该由钱包承担一部分责任。

先交代进度:下一代钱包仍在内部工程验证中,尚未正式发布。本地可验证账本已有实现;面向外部转入交易的链上证明自动获取与多源核验,仍在集成验证。下面讲的是这个架构为什么值得做,以及验证怎样才算有根据。

去银行查账,不需要把整家银行搬回家

一个人去银行核对账单,关心的是自己的钱有没有到账、有没有多扣、某笔消费是不是自己做的。他不需要把全银行所有人的流水搬回家,重新算一遍。

钱包也可以按这个范围工作。它要检查自己的账户和交易,通常不需要在手机上重执行全网每一笔交易。链上数据可能公开,但账户范围的验证需求,并不要求钱包审计所有人的业务。银行的例子借用的是查账范围;钱包还要用密码学证据核对数据。

信用卡出现一笔陌生消费,我们会提出 dispute,要求查清楚。这里值得借鉴的是这种核对能力:账单上的一行字,也应当经得起追问。在银行体系里,调查和退款由银行、商户及卡组织的规则处理;链上交易最终确认后的处理由相应协议决定。钱包验出问题,能做的是拒绝不可信的数据、提示异常,不能凭此撤回一笔已经最终确认的转账。

我希望用户拿到的,是能查账的钱包。

一笔“到账”,要拿什么来证明?

很多钱包通过 RPC 向远程节点查询余额和交易。RPC 是一种调用方式,仍然可以用来取数据;关键在于,钱包如何判断取回来的东西。

假设服务器说:“你收到了一笔转账。”如果钱包只显示返回的金额,它依赖的是服务器对这笔交易的描述。轻量验证则要继续追问:原始交易是什么?它属于哪个已认证的账本状态?有什么证据把两者连接起来?

一种常见方法,是让服务器连同交易提供 Merkle 包含性证明。可以把它理解成一条短的核对路径:钱包用交易数据和这条路径,自己算出一个摘要,再与已经认证的账本摘要比较。改了收款人或金额,重算的结果就应当对不上。以太坊的轻客户端文档也解释了通过较少数据检查证明的思路,具体协议和安全假设则随链而不同。[1]

这里最关键的是“已经认证”。恶意服务器也能编一份交易、造一棵树,再给自己的树签名。钱包必须预先知道接受什么信任锚,并按目标链的规则检查签名、验证者集合或最终性证据,才能判断这个摘要是否值得接受。

证明要连接到可信的账本承诺,不能只是几段看起来像证明的数据。

按这个设计,钱包成为一个在本地核验证据的轻量节点。它仍需要网络提供数据,也仍依赖目标链的共识和信任假设。它承担的是与自己账户有关的核验工作,不参与全网交易排序。

还有一个容易漏掉的问题:证明某笔转账存在,只回答了“这笔有没有”。服务器若把另一笔转出藏起来,或者始终返回昨天的真实账本,单笔证明仍可能正确,余额却已经过时。

因此,交易包含性、账户记录的完整性和状态的新鲜度需要分别检查。保存已经验证的检查点,可以发现退回更早历史的情况;要知道网络是否已有更新,还需要能获得更新的认证状态,并结合适合该链的证明机制或独立数据来源。几个网址若实际共用同一个后端,也算不上几个独立来源。

这正是外部转入交易的验证仍需继续集成验证的部分。不能把某个证明验证器能运行,直接写成钱包已经能发现所有漏报。

手机里自己的账,也要经得起核对

远程节点是一层,本地记录是另一层。

钱包自己还要记住应用授权、签名操作和账户变更。这些记录会经过备份、恢复和跨设备同步。即使网络返回的数据可靠,本地历史损坏了、同步漏了一段,界面仍可能给出错误的状态。

我们在钱包里采用同构的 verifiable ledger。用中文说,就是让钱包本地也使用与链端相同类型的可验证账本结构。

普通数据库擅长保存和查询当前值,也可以配合审计机制使用。我们希望钱包自己的历史能直接参与验证:把变化追加为事件,让后一条记录连接前面的摘要,再用 Merkle 摘要与签名检查点对一段历史作承诺。检查点可以理解成一本账在某个位置留下的、可核对的签名封账记录。

恢复时,钱包重新检查这些连接和签名,按照相同规则重放事件,得到应有的状态,再与保存的结果比较。这样,授权状态有了可以追溯的来处;备份服务只负责保存数据,钱包自己检查取回来的记录。

链端和钱包端都用明确的记录、摘要与验证规则来处理历史,而不是链上讲可验证,回到用户设备里就只剩一张没人核对的表。

但检查有它的起点。若设备上的历史、检查点和“最新位置”一起被换成一份完整的旧备份,旧记录本身仍可能通过验签。从验证原理看,发现这种整体回滚,需要一个没有同时被回滚的可信参照,例如另一个设备保留的更新检查点,或外部可验证的承诺。哈希链能帮助发现历史不一致,无法凭空知道自己没见过的未来。[2]

同样,若有权签名的密钥已经被控制,攻击者写入的新记录可能具有有效签名。账本的一致性检查需要与密钥保护、授权限制和恢复设计共同工作。

从保住钥匙,到核对自己的钱

Passkey 关注谁在认证、怎样保护认证凭证;交易验证关注服务器提供的证据;本地账本关注钱包保存和恢复的历史。这几个问题发生在不同位置,做对其中一个,其他位置仍要认真处理。

对用户来说,最终应该是很具体的区别:一笔转账,钱包能说出自己核对了什么;一次恢复,它能检查记录是否接续;数据对不上,它会把异常说明白,而不是继续展示一个让人安心的数字。

我不希望用户为了检查自己的账户,先去学会维护全节点。去银行核对自己的账,本来就不需要管理整家银行。钱包也应该把这件事做好:把验证留在设备里,把能看懂的结果交给用户。

参考

[1] Ethereum.org:Light clients。用于解释轻客户端的通用思路,不代表本钱包采用以太坊协议。

[2] Laurie 等:RFC 9162:Certificate Transparency Version 2.0,§2.1.3、§2.1.4 区分包含性证明与一致性证明。该文介绍证书透明日志;本文引用其可验证日志概念。