Diem RecoveryAddress 模块深入解析:VASP 账户恢复机制的设计、实现与形式化验证
【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem
本篇文章基于 Diem 区块链核心框架(language/diem-framework/releases/artifacts/release-1.4.0-rc0/docs/modules/RecoveryAddress.md)及其源码 RecoveryAddress.move,系统剖析 Diem 为 VASP(虚拟资产服务提供商)设计的链上账户恢复机制:它如何通过密钥轮换能力(KeyRotationCapability)的委托,让一个"回收地址"集中托管同一 VASP 下多个账户的密钥恢复权限。读完本文,你将掌握RecoveryAddress资源的结构与生命周期、三个核心入口函数的实现细节与中止条件、MAX_REGISTERED_KEYS等关键常量的约束,以及 Move Prover 形式化规范如何保证该机制的安全属性。
一、为什么需要链上账户恢复机制
在 Diem 的账户模型中,每个账户的链上身份由其认证密钥(authentication key)决定。一旦账户持有者丢失了私钥,也就丢失了账户的控制权——除非存在一种机制,让账户所有者能够重新生成并轮换认证密钥。
Diem 对此给出的答案是DiemAccount模块中的KeyRotationCapability(密钥轮换能力):它是一种可被提取、转移、存储的"授权凭证",持有该凭证的一方即拥有为对应账户轮换认证密钥的权力。而RecoveryAddress模块在此基础上更进一步,提供了面向 VASP 的多账户集中恢复方案:
- 同一 VASP 名下的多个账户,可以将各自的
KeyRotationCapability委托给一个共同的恢复地址; - 恢复地址下的
RecoveryAddress资源集中保存所有这些能力; - 恢复地址自身的认证密钥可以被"埋在深山里"(buried in the mountain),只有在真正需要恢复某个账户时才被取出使用。
这种设计在隔离高风险密钥与日常操作密钥的同时,保留了紧急情况下的恢复通道,是 Diem 链上账户管理体系中 VASP 侧的关键基础设施。
二、核心资源:RecoveryAddress
2.1 资源定义
RecoveryAddress是一个具备key能力的结构体,意味着它可以作为全局存储资源发布在账户地址下(见 RecoveryAddress.move):
struct RecoveryAddress has key { rotation_caps: vector<KeyRotationCapability> }唯一字段rotation_caps是一个KeyRotationCapability的向量,存放同一 VASP 下多个账户委托过来的密钥轮换能力。该资源只能存储在 VASP 账户地址下,并且从发布那一刻起就永不删除(详见下文形式化规范中的"资源持久性"不变量)。
2.2 资源设计的两大保证
从源码注释与publish实现(RecoveryAddress.move)可以看出,该结构设计刻意保证了两个性质:
- 防止"恢复循环":
publish强制要求资源创建者把自己的KeyRotationCapability放在rotation_caps的第一个位置(Vector::singleton(rotation_cap)),并断言该能力确实属于创建者自己(EKEY_ROTATION_DEPENDENCY_CYCLE)。这从机制上杜绝了"A 是 B 的恢复地址、B 又是 A 的恢复地址"这类循环依赖。 rotation_caps恒非空:由于首个元素必然是恢复地址自身的能力,向量永远至少有一个元素,这简化了后续所有遍历逻辑与形式化推理。
三、错误码与关键常量
模块定义了一套语义明确的错误码,全部通过标准库 Errors 的错误构造器包装为对应类别的错误(invalid_argument、already_published、not_published、limit_exceeded)。下表汇总了全部错误常量及其触发场景:
| 常量 | 值 | 含义 | 对应错误类别 |
|---|---|---|---|
ENOT_A_VASP | 0 | 只有 VASP 才能创建恢复地址 | INVALID_ARGUMENT |
EKEY_ROTATION_DEPENDENCY_CYCLE | 1 | 将形成密钥轮换依赖循环 | INVALID_ARGUMENT |
ECANNOT_ROTATE_KEY | 2 | 调用者没有轮换该账户密钥的权限 | INVALID_ARGUMENT |
EINVALID_KEY_ROTATION_DELEGATION | 3 | 委托双方不属于同一 VASP | INVALID_ARGUMENT |
EACCOUNT_NOT_RECOVERABLE | 4 | 目标账户不在恢复资源中 | INVALID_ARGUMENT |
ERECOVERY_ADDRESS | 5 | RecoveryAddress资源状态异常 | NOT_PUBLISHED/ALREADY_PUBLISHED |
EMAX_KEYS_REGISTERED | 6 | 注册的密钥数量已达上限 | LIMIT_EXCEEDED |
此外还有一个关键上限常量:
MAX_REGISTERED_KEYS: u64 = 256:单个恢复地址最多可注册 256 个密钥轮换能力(RecoveryAddress.move)。add_rotation_capability在追加前会校验Vector::length(recovery_caps) < MAX_REGISTERED_KEYS,超出即抛出EMAX_KEYS_REGISTERED。
四、三个核心入口函数
4.1publish:初始化恢复地址
public fun publish(recovery_account: &signer, rotation_cap: KeyRotationCapability)publish提取recovery_account自己的KeyRotationCapability,并在其地址下发布RecoveryAddress资源。执行流程与中止条件(RecoveryAddress.move):
- 校验
recovery_account是 VASP 账户,否则以ENOT_A_VASP中止; - 校验传入的
rotation_cap确属recovery_account本人(防止循环依赖),否则以EKEY_ROTATION_DEPENDENCY_CYCLE中止; - 校验该地址下尚未存在
RecoveryAddress资源(不可重复发布),否则以ERECOVERY_ADDRESS(ALREADY_PUBLISHED类别)中止; - 通过
move_to发布资源,rotation_caps初始化为仅含自身能力的单元素向量。
在链上脚本层,这一操作对应Script::create_recovery_address/AccountAdministrationScripts::create_recovery_address交易脚本(见 transaction_script_builder.rs 与 AccountAdministrationScripts.move)。
4.2add_rotation_capability:登记新账户的恢复能力
public fun add_rotation_capability( to_recover: KeyRotationCapability, recovery_address: address, )将一个账户(to_recover)的密钥轮换能力加入恢复地址的rotation_caps向量。其安全约束(RecoveryAddress.move):
recovery_address下必须已存在RecoveryAddress资源,否则以ERECOVERY_ADDRESS(NOT_PUBLISHED)中止;- 通过
VASP::is_same_vasp校验to_recover与recovery_address属于同一 VASP,否则以EINVALID_KEY_ROTATION_DELEGATION中止——这是跨实体恢复权限的硬性隔离边界; - 校验注册数量未达
MAX_REGISTERED_KEYS,否则以EMAX_KEYS_REGISTERED中止; - 通过
Vector::push_back追加能力到向量尾部。
链上脚本层对应Script::add_recovery_rotation_capability(见 transaction_script_builder.rs),其错误码映射在 AccountAdministrationScripts.move 中有完整表格化文档。
4.3rotate_authentication_key:执行密钥恢复
public fun rotate_authentication_key( account: &signer, recovery_address: address, to_recover: address, new_key: vector<u8>, )这是恢复机制的最终落点:将to_recover账户的认证密钥轮换为new_key(32 字节)。执行逻辑(RecoveryAddress.move):
- 校验
recovery_address下存在恢复资源,否则以ERECOVERY_ADDRESS中止; - 权限校验:调用者(
account)必须是to_recover本人(能力原主可自行轮换)或recovery_address资源持有者(可代任何已登记账户轮换),否则以ECANNOT_ROTATE_KEY中止; - 线性遍历
rotation_caps,逐个比对DiemAccount::key_rotation_capability_address(cap) == &to_recover; - 命中后调用
DiemAccount::rotate_authentication_key(cap, new_key)完成轮换并返回; - 若遍历结束仍未命中,以
EACCOUNT_NOT_RECOVERABLE中止。
需要注意两个细节:
new_key必须是 32 字节:形式化规范RotateAuthenticationKeyAbortsIf显式声明aborts_if len(new_key) != 32 with Errors::INVALID_ARGUMENT,即长度不合法时中止;- 只有
to_recover与恢复地址持有者两个入口:规范中aborts_if !(Signer::spec_address_of(account) == recovery_address || Signer::spec_address_of(account) == to_recover)将可调用方精确限定为两者,杜绝了任意第三方借道恢复地址干扰他人账户。
链上脚本层对应Script::rotate_authentication_key_with_recovery_address(见 transaction_script_builder.rs)。
五、模块级形式化规范:用证明保证安全
该模块的最大亮点在于随源码内嵌的 Move Prover 规范(spec module段,见 RecoveryAddress.move),将安全属性写成可机械验证的不变量。主要包括五类:
5.1 初始化不变量
每个恢复地址必然持有自己的
KeyRotationCapability,且位于rotation_caps[0],向量长度恒大于 0。
invariant forall addr: address where spec_is_recovery_address(addr): ( len(spec_get_rotation_caps(addr)) > 0 && spec_get_rotation_caps(addr)[0].account_address == addr );这印证了 2.2 节的两个设计保证,并使其成为全模块恒成立的性质。
5.2 资源持久性不变量
一旦地址下发布了
RecoveryAddress资源,该资源在任何交易后都继续存在(模块未提供删除路径)。
invariant update forall addr: address: old(spec_is_recovery_address(addr)) ==> spec_is_recovery_address(addr);5.3 密钥轮换能力持久性不变量
若恢复地址在更新前持有某账户的能力,则更新后仍然持有;
RecoveryAddress资源本身也永不消失。
invariant update forall addr: address where old(exists<RecoveryAddress>(addr)): exists<RecoveryAddress>(addr);5.4 资源与角色一致性不变量
只有 VASP 账户才能持有
RecoveryAddress资源。
invariant forall addr: address where spec_is_recovery_address(addr): VASP::is_vasp(addr);该不变量与publish中的ENOT_A_VASP检查形成"运行时检查 + 静态证明"的双重保障。
5.5 辅助函数与函数级规范
模块定义了三个规范辅助函数:
spec_is_recovery_address(addr):判定某地址是否为恢复地址(即是否存在该资源);spec_get_rotation_caps(recovery_address):读取恢复地址下的全部能力向量;spec_holds_key_rotation_cap_for(recovery_address, addr):判定恢复地址是否持有某账户的能力。
每个公开函数均通过AbortsIf/Ensures模式声明了完备的中止条件与后置条件。例如rotate_authentication_key的后置条件(RecoveryAddress.move)为:
ensures DiemAccount::authentication_key(to_recover) == new_key;即"函数成功返回,则to_recover的认证密钥必然已变为new_key",把运行时行为固化为可验证的承诺。
六、模块依赖与在框架中的位置
RecoveryAddress的依赖关系(见文档头部use声明)展示了它与框架其他模块的协作边界:
| 依赖模块 | 用途 |
|---|---|
| DiemAccount | KeyRotationCapability类型定义、能力地址查询与认证密钥轮换执行 |
| VASP | is_vasp(角色校验)与is_same_vasp(同组校验) |
| Errors | 错误码包装与分类 |
| Signer | 获取签名者地址 |
| Vector | 能力向量的构造、遍历与追加 |
其上层调用方为 AccountAdministrationScripts.move,将三个函数封装为可直接提交到链上的交易脚本;同时每个版本发布的交易脚本构建器 transaction_script_builder.rs 提供了 Rust 侧的脚本枚举(CreateRecoveryAddress、AddRecoveryRotationCapability、RotateAuthenticationKeyWithRecoveryAddress),供客户端 SDK 生成对应交易。
从发布产物看,该模块自 release-1.2.0-rc0 起即已存在(见 release-1.2.0-rc0/docs/modules/RecoveryAddress.md),并在 1.4.0-rc0 中持续提供相同接口,属于 Diem 框架中长期稳定的账户管理组件。
七、典型使用流程与安全边界总结
结合上文,一个完整的 VASP 账户恢复场景可以归纳为四个步骤:
- 初始化:VASP 账户 A 调用
publish(链上脚本create_recovery_address),提取自身能力并在 A 下发布RecoveryAddress资源; - 登记:同一 VASP 下的账户 B、C 等各自调用
add_rotation_capability(链上脚本add_recovery_rotation_capability),把自身能力委托给 A(需满足同 VASP 约束); - 隔离保管:A 的认证密钥私钥被离线妥善保管("buried in the mountain"),日常不参与任何交易签名;
- 应急恢复:当 B 或 C 的私钥丢失时,由 A(或账户本人)调用
rotate_authentication_key(链上脚本rotate_authentication_key_with_recovery_address),将目标账户认证密钥轮换为新密钥。
该机制的安全边界可以凝练为三点:
- 同 VASP 隔离:恢复能力只能在 VASP 内部委托,跨 VASP 委托被
EINVALID_KEY_ROTATION_DELEGATION强制拒绝; - 双入口调用:只有能力原主与恢复地址持有者可以触发轮换,第三方无法干预;
- 能力集中、密钥分离:多个账户的恢复能力集中在单一地址,而该地址的高权限密钥独立保管,实现了"日常低风险操作"与"应急高权限操作"的物理隔离。
【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考