怎样使用 Substrate 做一个能安全支持百万地址的热钱包?
用于 runtime 开发的 Substrate 和 FRAME 系统定义了一组强大的原语功能,用于构建区块链基础架构。将两者共同使用,为解决现有问题创造了新颖的方法。本文将描述一个实际应用程序,其中 Substrate 的函数用于实现多地址热钱包。
热钱包通常意味着将支出密钥保存在在线设备上,以便可以方便地创建和广播交易,但风险通常较高。本文将探讨一些 Substrate 的账户抽象 —— 多签名账户、代理账户和衍生账户 —— 如何让我们能构建一个可以安全支持数百万个地址的热钱包。
如果您需要为多个用户账户持有代币,但又想为每个客户提供自己的存款地址,那么这样的钱包将非常有用。简单的解决方案是通过生成新的密钥对为每个客户生成一个新的存款地址。但是快速处理所有这些密钥对并不容易。如果您有成千上万的用户怎么办?使用 Substrate 的账户抽象,我们可以构建一个可扩展性和安全性更高的解决方案。
来源和账户 ID
在开始构建热钱包之前,我们需要奠定它将要使用的基础。当用户与区块链进行交互时,他们正在调用某些函数。这些 “可调度(dispatchable)” 函数的集合构成了区块链的界面。
由于可调度函数是从外部调用的,因此区块链可能首先关心的是谁真正调用了该函数。首先,一个函数需要检查调用方是否有权执行该函数。其次,链可能需要确切地知道谁调用了该函数以更新有关调用者的一些信息。如果调用者是一个账户,则链可能需要更新该账户的余额,例如扣除交易费用。
您可能会想,“如果调用者是账户,那意味着什么?” Substrate 中的函数本身不是来自帐户,而是来自来源(origins)。例如,Polkadot 的治理系统具有一系列特殊来源,这些特殊来源具有特权,例如分配国库资金或撤销 Slash。如果您使用 Substrate 设计自己的区块链,则可以创建自己的自定义来源。但是,本文要记住的一点是,账户只是 Substrate 来源的一种变体。您可以想象成 Substrate 告诉可调度函数,“此调度的来源是一个账户。”
既然我们已经实现了抽象的第一个飞跃,我们需要一种方法来告诉函数来源是指哪个账户。如果您使用过任何区块链,则可能会习惯于使用一个账户 ID 作为与私钥相对应的公钥。这没问题,这一点也可以在 Substrate 中使用。从这个意义上说,一个账户由公钥标识,并由相应的私钥授权。
Substrate 支持更多抽象。账户 ID 可以是任何 32 个字节的数字。[1]可以是与私钥相对应的公钥,但不是必须的。它只需要某种授权方法。就像在其中一样,必须有某种独特的方法来生成此帐户标识号,以便 Substrate 可以完成上述开头的句子:“此调度的来源是由该编号标识的账户。” [2]
哈希函数
哈希函数一直在区块链中出现。区块实际上是通过其哈希值链接在一起的。但是我们将使用哈希函数的属性来达到另外两个目的:生成账户 ID 和识别函数调用。
哈希函数会接受一些任意大小的输入,并将其映射到固定大小的输出(例如 32 个字节)。但是,它不仅将数据映射到任何 32 字节的数字,而是应该确定性地将唯一数据映射到一个唯一的数字。碰巧的是,32 个字节可以捕获天文数字量级的 item。[3]
例如,我们可以获取有关链的一些信息,例如 “polkadot-treasury”,并使用哈希函数将其转换为账户 ID (32 字节)。或者,我们可以获取有关某些交易的信息,例如 “将 10 个单位转移到账户 123…”,并相信哈希是该信息的唯一图像。
多签账户
有了这些,我们就可以开始构建热钱包的第一部分:多重签名(multisig)账户。由于总体比较笨重,多重签名账户可能看起来不像是热门钱包的一部分,但是此账户将作为其余组件的安全基础,并且刚刚所说的笨重不会妨碍日常使用。
一些区块链使用加密多重签名,其中多个密钥持有者在链上提交交易之前,先在链外签署了单个交易。Substrate 的 FRAME 附带的多重签名系统以另一种方式工作:它根据构成多重签名的各个账户以及从生成的账户中分发所需的必要阈值来生成账户 ID。Substrate 向所有这些信息添加一个特殊的多重签名前缀,并对其进行哈希处理以获取一个 32 字节的输出,该输出将用作多重签名账户 ID。请注意,此账户 ID 没有与其关联的私钥。
为了从该新帐户 ID 授权交易,多签的成员均使用其希望多签帐户进行的函数调用在链上提交交易。但是,每个人都提交函数调用效率不高;它可能很大,并且区块空间是稀缺的(因此很昂贵)。哈希函数再次派上用场:只有一个账户需要提交实际的函数调用;其他人只提交哈希。他们只用说:“我们同意通过多签账户使用此哈希调用函数”,而无需重新提交函数。
这个多重签名本身太笨拙,无法用作热钱包,因为它需要多个密钥持有者提交交易才能使其正常工作。但是它是高度安全的,可以将它作为基本账户,而我们可以在不牺牲其安全性的情况下将其转变为热钱包。
代理账户
代理帐户允许多签地址将支出权限委派给另一个账户,该账户将用作热钱包,同时仍保持多签的安全。我们将设置一次性延时代理来管理支出,并设置另一个(或多个)即时代理来管理此多签账户的安全性。
代理账户将一个账户的特权授予另一个账户,以代表该账户进行函数调用。这些特权可以是特定的,例如 “仅限与抵押相关的交易”,或广泛的,例如 “不涉及转移资金的所有交易”,甚至是完整的特权,如 “任何交易”。
创建代理只需要从要代理的账户进行一次交易,并说明哪个其他账户是其代理及其特权。代理关系到位后,代理账户就可以为代理账户进行交易,本质上就是告诉链:“我是该账户的代理,我具有这些特权,并且我想代表代理账户。” 链的逻辑将验证代理确实具有正确的特权,并使用代理账户的来源派遣该功能。
添加时间延迟会增加一层安全性。想象一下 600 个区块的时间延迟(在 Polkadot 中为一小时)。代理账户仍会提交交易,说它是具有某些特权的代理,但只会宣布它要进行的函数调用的哈希值。代理账户的所有者可以请求实际的函数调用并进行审查。如果所有者不同意,他们可以通过在延迟时间到期之前提交另一个交易来拒绝函数调用。在时间延迟之后,代理可以提交与公告相对应的实际函数调用,然后由 Substrate 调度。[4]
对于我们的用例,多签密钥持有者将进行交易以将另一个账户设置为具有完全特权的延时代理,例如包括余额转移在内。也许此代理账户将存在于可自动进行交易的在线服务器上。每当进行交易时,它都必须先宣布哈希,然后将实际的函数调用发送给其他帐户持有人(为简单起见,我们将此其他帐户持有人视为多签的成员),他们可以验证该函数调用是否为非恶意的。如果是恶意的,多签可以及时进行交易以拒绝该调用,并且出于谨慎考虑,确定代理账户已被盗用并将其删除。
这样配置确实行得通,但是我们仍然可以让它变得更方便使用。仅使用一个代理,我们可能需要很长的时间延迟,因为协调足够多的多重签名密钥持有人以进行拒绝交易可能会在短时间内比较困难。但是一个帐户可以有多个具有不同特权的代理帐户。要解决此问题,请将每个多签密钥持有者设置为具有非转账特权的代理,尤其是具有拒绝来自延时代理的通知的特权。
让我们简要介绍一下此配置。在中心,我们有一个多签账户。该账户没有私钥,但是有两种方法可以控制它:使用延时代理账户或收集足够的成员账户签名者。多签的每个成员还具有拒绝来自完全特权代理的交易的能力,但是如果没有其他成员加入进行多签交易,则无法进行余额转账。
它自己就是一个功能齐全的热钱包,只需删除代理并设置一个新的热钱包即可更改热键(具有完全特权的代理帐户),而无需更改其地址(多重签名帐户)。但是我们最初的问题陈述要求成千上万的用户使用唯一的存款地址,到目前为止,我们只有一个用户。
衍生账户
到目前为止,我们已经使用多种方式访问一个多重签名账户。现在,我们将使用一个帐户访问许多账户。
Substrate 中的每个账户都有一个可以访问的衍生帐户树。为了获得账户 ID,Substrate 理所当然地使用来哈希算法。通过用所需的索引和派生前缀对发起调用的帐户的账户 ID 进行哈希处理,Substrate 创建了一个新的账户 ID。例如,发送方提供一个函数调用和一个索引,说:“我想从具有此索引的派生账户中调度此函数。”
你可能已经猜到后面会发生什么了。钱包所有者可以为其每个用户分配一个索引,并提供派生账户 ID 作为该用户的存款地址。为了访问资金,代理地址将发出交易以从多重签名帐户的派生地址转移资金。
具体而言,索引限制为 16 位或 65,536 个派生帐户,但是也可以嵌套。也就是说,每个衍生产帐户可以拥有自己的 65,536 个衍生产帐户集,依此类推。树的第二层将拥有超过 40 亿个账户。
全貌
最后让我们把这些知识用起来。想象一下,索引为 11 的用户向您付款,您有一些 “储蓄账户” 要存入资金。整个交易看起来像是:“我是多签账户的代理人,我想将资金从多签的衍生账户(索引为 11) 转移到储蓄账户。”
假设对监管者来说一切正常,则延迟时间将到期,并且代理可以广播完整的交易。如果多重签名成员认为热键需要更改,他们可以简单地生成一个新密钥并将旧密钥作为代理删除,而不会影响多重签名或其任何派生地址。
上图显示了我们已设置的钱包的示意图:多重签名(MS)由一组 n 个密钥(记为 k)控制,并将时间延迟代理(H)设置为热键。它几乎可以从多签中导出无限的地址(d 的集合)。
我们甚至可以进一步优化此工作流程。Substrate 还提供了发送一批函数调用的函数。如果用户定期在其衍生帐户中进行存款和提款,你可以在一批转账中将其全部发送。
Substrate 的链上帐户抽象提供了管理账户的强大方法。通过减少所需的实际密钥数量并根据正式规则而不是私钥访问账户,您可以操作成千上万个账户,而不必处理存储相等数量的签名密钥的限制。本文仅关注构建热钱包的一个示例,但是所有抽象都是孤立的,可以组成更高级的应用程序。
注释:
不必非得是 32 个字节。您可以随心所欲地构建 runtime,但我不希望本文超出了必要范围。↩︎
快速解释一下 “unique” 一词,从严格的意义上讲,我在这里的意思比普通字典中的定义更严格。一个账户是唯一的,并不是说它只能有一个表示,而是所有表示(或一系列表示)都可证明为相等的。可能存在无限数量的方法来生成一个特定的数字(账户),但是只要所有这些方法都确实生成相同的帐户,那么该账户就可以视为唯一的。详细解释其中的数学原理会复杂到让你头大,但是我们将生成账户 ID 并将它们在函数之间传递,这里的关键是无论我们有多少个函数串在一起(串联)以到达某个帐户 ID,它在充当调度源的方面表现为相同的账户 ID。↩︎
如果您有兴趣了解的话,32 个字节最多可容纳 1.15x10 ^ 77。到可观察宇宙边缘的距离为 457 亿光年,即 4.32x10 ^ 23 公里或 4.32x10 ^ 29 毫米。如果我们我们把它想作是一张平面的光盘,则其面积为 5.87x10 ^ 59 平方毫米。我们仍然相差 10 ^ 18 或十亿平方。因此,两个不同的哈希输入具有相同输出的机会就像两个项目都落在可观察的宇宙中的同一平方毫米上,然后将其分解为 10 亿乘以 10 亿的网格,然后又都落在了相同的正方形上。这些正方形是 1 皮米(picometer)宽。作为参考,氦原子的直径为 62 皮米。↩︎
实际上,只要代理发出通知,任何账户都可以提交调用,但是出于实用主义的考虑,假设我们的热钱包仅使用同一账户进行通知和提交。↩︎
微信掃描關注公眾號,及時掌握新動向
2.本文版權歸屬原作所有,僅代表作者本人觀點,不代表比特範的觀點或立場
2.本文版權歸屬原作所有,僅代表作者本人觀點,不代表比特範的觀點或立場