的链上安全存储、委托与所有权转移)
Aptos Core 的 Move 标准库 nursery 模块 vault基于能力Capability的链上安全存储、委托与所有权转移【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core本文深入解析 Aptos Core 仓库中 Move 语言标准库实验区nursery里的std::vault模块一个实现能力安全capability-based security的链上安全存储容器。读完本篇你将掌握 vault 的四种能力类型读、改、委托、转移的获取与使用方式、委托/撤销/所有权转移的完整操作流程、访问器accessor互斥机制的底层原理以及该模块在 Move 包体系中的构建与测试方式。模块定位标准库实验区中的安全存储容器在 Aptos Core 仓库中vault模块位于 Move 标准库的 nursery实验区目录下实现源码vault.move生成的 API 文档vault.md包配置Move.tomlnursery 包名为MoveNursery版本 1.5.0依赖同级目录的MoveStdlib开发地址映射为std 0x1[package] name MoveNursery version 1.5.0 [dependencies] MoveStdlib { local .. } [dev-addresses] std 0x1这意味着 vault 与其他 nursery 模块如acl、capability、offer、role、guid等同见 nursery/sources 目录一样属于孵化中的标准库构件尚未进入正式链上框架。它的文档由构建系统在编译标准库时自动生成——main.rs 中有一段专门的流程会先清空再重建 nursery 文档目录build_nursery_doc而 lib.rs 定义了NURSERY_DIR与NURSERY_DOCS_DIR常量。模块的目标用源码头部注释vault.move#L1-L4概括实现一段受保护的安全内存称为 vault其内容只能在持有者 sign er 授权下操作授权通过**能力capability**管理支持将能力委托给其他 signer含撤销也支持所有权转移。核心设计不可伪造的能力令牌文档 Capabilities 章节 给出了 vault 安全模型的三个关键性质能力是不可伪造的令牌每个能力实例代表对 vault 执行某项特定操作的权限。要获得能力实例必须通过 signer 认证——请求者要么是 vault 的所有者要么是被委托过该能力的地址。最小权限原则能力一旦获取就可以作为参数传给其他函数去执行相应操作。被调用的函数只需要能力本身不需要接触原始 signer。这正是能力安全的核心价值——不会给代码授予超出其需要的权限。事务内局部性保证不可伪造性能力实例只能由本模块的函数创建且由于它们不具备 Move 语言的store或key能力无法被存储到链上状态中因而泄漏不出当前交易。文档给出的典型使用示例如下struct Content has store { ssn: u64 } ... // 创建新 vault vault::new(signer, bMy Vault, Content{ ssn: 525659745 }); ... // 获取读能力 let read_cap vault::acquire_read_capContent(signer); process(read_cap) ... fun process(cap: vault::ReadCapContent) { let accessor vault::read_accessor(cap); let content vault::borrow(accessor); do something with content: Content vault::release_read_accessor(accessor); }这里体现的能力 → 访问器 → 内容引用三段式使用模式是 vault 模块最重要的编程范式能力负责鉴权访问器负责把内容从全局状态中借出borrow/borrow_mut才返回真正的数据引用。需要指出的是文档示例与当前源码签名存在细微出入详见文末文档与源码的差异一节实际调用请以源码为准。类型体系四种能力、CapType 与链上资源vault 模块定义了四个能力结构体全部has copy, drop、不含store/keyvault.move#L104-L129结构体权限类型参数要求ReadCapContent读取 vault 内容Content: store dropModifyCapContent修改 vault 内容Content: store dropDelegateCapContent将能力委托给他人Content: store dropTransferCapContent转移 vault 所有权Content: store drop四个能力结构体字段完全一致各含两个字段vault_address: addressvault 所属地址即 vault 的实际位置authority: address签发该能力所依据的权威地址所有者自身或被委托人这个双地址设计是理解整个鉴权链路的关键能力实例本身记录了我指向哪个 vault以及我是基于谁的授权产生的。CapType能力的类型标签能力在链上状态委托记录、事件中需要以值的形式出现但能力实例本身不可存储。模块因此引入了CapTypevault.move#L133-L145——一个has copy, drop, store的结构体仅含一个code: u8字段struct CapType has copy, drop, store { code: u8 } public fun read_cap_type(): CapType { CapType{ code : 0 } } public fun modify_cap_type(): CapType { CapType{ code : 1 } } public fun delegate_cap_type(): CapType { CapType{ code : 2 } } public fun transfer_cap_type(): CapType { CapType{ code : 3 } }四种能力类型分别对应 code 0、1、2、3。delegate、revoke等函数通过CapType参数指定操作的是哪种能力从而让一个通用的委托接口覆盖全部四种权限。链上资源Vault 及其三个伴随资源vault 的全部链上状态由四个私有private资源承载vault.move#L172-L200/// 私有。vault 的表示。 struct VaultContent: store has key { /// 内容。若 option 为空表示内容当前已被移入某个访问器中。 content: option::OptionContent, } /// 私有。若 vault 支持委托则存放委托者信息。 struct VaultDelegatesphantom Content: store has key { /// 当前已授权的委托者地址列表。 delegates: vectoraddress, } /// 私有。若启用了事件生成则包含事件生成器。 struct VaultEventsphantom Content: store has key { metadata: vectoru8, delegate_events: event::EventHandleVaultDelegateEvent, transfer_events: event::EventHandleVaultTransferEvent, } /// 私有。存放在委托者地址处指向 vault 所有者并描述其被授予的能力。 struct VaultDelegatephantom Content: store has key { vault_address: address, granted_caps: vectorCapType, }几个设计要点值得展开content是Option类型这是实现访问器互斥的核心技巧。content为None时表示内容正被某个访问器持有内容已被 move 进访问器结构体为Some时内容在 vault 中就位。VaultDelegates与VaultDelegate成对出现前者存在于 vault 所有者地址下维护有哪些委托者后者存在于每个委托者自己的地址下记录其指向哪个 vault、被授予了哪些CapType。委托关系的两侧都有状态撤销时可以双向清理。VaultEvents是可选的只有调用enable_events之后才创建metadata字段用于在事件中识别该 vault例如传入bMy Vault之类的标识。与之对应模块定义了两种可上链的事件结构体VaultDelegateEvent字段为metadata、vault_address、authority、delegate、cap: CapType、is_revoked: bool在委托授予或撤销且事件功能开启时产生VaultTransferEvent字段为metadata、vault_address、authority、new_vault_address在所有权转移且事件功能开启时产生。此外还有两个非资源访问器结构体ReadAccessorContent与ModifyAccessorContent均含content: Content与vault_address: address两个字段是内容离开全局状态后的容器。生命周期管理创建、启用功能与销毁创建newpublic fun newContent: store(owner: signer, initial_content: Content) { let addr signer::address_of(owner); assert!(!existsVaultContent(addr), error::already_exists(EVAULT)); move_toVaultContent(owner, Vault{ content: option::some(initial_content) }) }实现见 vault.move#L206-L215要点同一 signer 对同一Content类型只能拥有一个 vault重复创建以error::already_exists(EVAULT)中止初始内容被包装为option::some(initial_content)新创建的 vault默认不支持委托也不产生事件——两者都需要显式启用。这种按需开启的设计避免了为不打算共享的 vault 引入委托滥用面。启用委托enable_delegation与is_delegation_enabledpublic fun enable_delegationContent: store(owner: signer) { assert!(!is_delegation_enabledContent(owner), error::already_exists(EDELEGATE)); move_toVaultDelegatesContent(owner, VaultDelegates{delegates: vector::empty()}) }启用委托的本质只是在所有者地址下创建一个空的VaultDelegates资源is_delegation_enabled则通过检查该资源是否存在来回答是否已启用vault.move#L219-L229。注意enable_delegation只能由所有者执行因为资源 move 到owner名下重复启用会中止。启用事件enable_eventspublic fun enable_eventsContent: store(owner: signer, metadata: vectoru8) { let addr signer::address_of(owner); assert!(existsVaultContent(addr), error::not_found(EVAULT)); assert!(!existsVaultEventsContent(addr), error::already_exists(EEVENT)); move_toVaultEventsContent(owner, VaultEvents{ metadata, delegate_events: event::new_event_handleVaultDelegateEvent(owner), transfer_events: event::new_event_handleVaultTransferEvent(owner), }); }vault.move#L233-L245调用者传入一段metadata字节向量作为该 vault 在事件流中的标识随后创建两个EventHandle。此后delegate、revoke、transfer等操作才会实际发射事件未启用时相关代码路径直接跳过。销毁remove_vaultpublic fun remove_vaultContent: store drop(owner: signer): Content acquires Vault, VaultDelegates, VaultDelegate, VaultEvents { let addr signer::address_of(owner); assert!(existsVaultContent(addr), error::not_found(EVAULT)); let Vault{content} move_fromVaultContent(addr); assert!(option::is_some(content), error::invalid_state(EACCESSOR_IN_USE)); if (existsVaultDelegatesContent(addr)) { let delegate_cap DelegateCapContent{vault_address: addr, authority: addr}; revoke_all(delegate_cap); }; if (existsVaultEventsContent(addr)) { let VaultEvents{metadata: _metadata, delegate_events, transfer_events} move_fromVaultEventsContent(addr); event::destroy_handle(delegate_events); event::destroy_handle(transfer_events); }; option::extract(mut content) }vault.move#L249-L268销毁流程保证状态不残留若当前存在未释放的访问器content为None以EACCESSOR_IN_USE中止——不允许在内容借出期间拆毁 vault若启用了委托先构造一个指向自身的能力并调用revoke_all清理所有委托者侧的VaultDelegate资源若启用了事件销毁两个事件句柄event::destroy_handle最后用option::extract把内容返回给调用者。能力获取validate_cap统一鉴权四种能力的获取函数结构完全相同都以私有的validate_cap作为唯一鉴权入口public fun acquire_read_capContent: store drop(requester: signer): ReadCapContent acquires VaultDelegate { let (vault_address, authority) validate_capContent(requester, read_cap_type()); ReadCap{ vault_address, authority } } // acquire_modify_cap / acquire_delegate_cap / acquire_transfer_cap 同构vault.move#L275-L303而validate_cap的逻辑体现了委托者优先、所有者兜底的判定顺序fun validate_capContent: store drop(requester: signer, cap: CapType): (address, address) acquires VaultDelegate { let addr signer::address_of(requester); if (existsVaultDelegateContent(addr)) { // 请求者是委托者检查其被授予的能力集合 let delegate borrow_globalVaultDelegateContent(addr); assert!(vector::contains(delegate.granted_caps, cap), error::permission_denied(EDELEGATE)); (delegate.vault_address, addr) } else { // 不是委托者则必须是 vault 所有者才能成功 assert!(existsVaultContent(addr), error::not_found(EVAULT)); (addr, addr) } }vault.move#L307-L320注意两个细节委托者返回的vault_address来自其VaultDelegate记录而非请求者自身地址——委托者通过该记录间接指向自己那个 vaultauthority则是委托者自己的地址。能力实例的vault_address与authority字段就是在此处被一次性确定并固化进能力的后续使用能力时如创建访问器不再需要任何 signer 或再次鉴权——这正是文档强调的被调函数不需要接触原始 signer的实现基础。文档中委托章节的完整操作示例如下结合上文的delegate/revoke实际接口// 启用委托。只有所有者 signer 能执行。 vault::enable_delegationContent(signer); ... // 将读能力委托给另一个 signer。 let delegate_cap vault::acquire_delegate_capContent(signer); vault::delegate(delegate_cap, other_signer, vault::read_cap_type()); ... // 另一个 signer 现在可以自己获取读能力 let read_cap vault::acquire_read_capContent(other_signer); ... // 已授予的能力可以被撤销且无需对方 signer 在场 vault::revoke(delegate_cap, signer::address_of(other_signer), vault::read_cap_type());文档还特别指出委托可以是传递性的——如果所有者把获取 DelegateCap 的权限即delegate_cap_type()授予某个委托者该委托者就能进一步转授访问权限从而形成委托链。访问器内容读写的唯一通道与互斥保证能力解决谁有资格访问器解决如何安全地把内容拿出来用。以读访问器为例public fun read_accessorContent: store drop(cap: ReadCapContent): ReadAccessorContent acquires Vault { let content mut borrow_global_mutVaultContent(cap.vault_address).content; assert!(option::is_some(content), error::invalid_state(EACCESSOR_IN_USE)); ReadAccessor{ vault_address: cap.vault_address, content: option::extract(content) } } public fun borrowContent: store drop(accessor: ReadAccessorContent): Content { accessor.content } public fun release_read_accessorContent: store drop(accessor: ReadAccessorContent) acquires Vault { let ReadAccessor{ content: new_content, vault_address } accessor; let content mut borrow_global_mutVaultContent(vault_address).content; // 理论上可证明此处不会发生但保留断言作为双重保险。 assert!(option::is_none(content), error::internal(EACCESSOR_INCONSISTENCY)); option::fill(content, new_content); }vault.move#L337-L358其工作机制借出extractoption::extract把内容从Option中移出、置为None内容被 move 进ReadAccessor结构体。由于Option状态留在全局任何并发的第二个read_accessor/modify_accessor调用都会检测到None并以EACCESSOR_IN_USE中止——同一个 vault 在任一时刻至多存在一个访问器无论读或改互斥完全由链上状态保证无需额外锁。读取borrowborrow只是返回accessor.contentborrow_mut同理返回mut accessor.content本身不做校验。归还fillrelease_*_accessor把内容 move 回全局的Option源码注释说明我们应当能够证明下方断言不会触发但保留它作为双重保险即EACCESSOR_INCONSISTENCY分支在类型系统上应不可达但仍以error::internal防御。修改访问器modify_accessor/borrow_mut/release_modify_accessorvault.move#L372-L394与读访问器完全同构区别仅在于borrow_mut返回可变引用且release_modify_accessor的文档明确确保所有修改被写回 vault。文档对访问器的约束表述为Only one accessor (whether read or modify) for the same vault can exist at a time, and this function will abort if one is in use. An accessor must be explicitly released——访问器必须显式释放不存在隐式释放路径。委托与撤销的状态机授予delegatepublic fun delegateContent: store drop(cap: DelegateCapContent, to_signer: signer, cap_type: CapType) acquires VaultDelegates, VaultDelegate, VaultEvents { assert!( existsVaultDelegatesContent(cap.vault_address), error::invalid_state(EDELEGATION_NOT_ENABLED) ); let addr signer::address_of(to_signer); assert!(addr ! cap.vault_address, error::invalid_argument(EDELEGATE_TO_SELF)); if (!existsVaultDelegateContent(addr)) { // 若尚不存在则创建 VaultDelegate move_toVaultDelegateContent( to_signer, VaultDelegate{vault_address: cap.vault_address, granted_caps: vector::empty()} ); // 将该委托者加入 VaultDelegates let vault_delegates borrow_global_mutVaultDelegatesContent(cap.vault_address); add_element(mut vault_delegates.delegates, addr); }; // 授予能力 let delegate borrow_global_mutVaultDelegateContent(addr); add_element(mut delegate.granted_caps, *cap_type); // 生成事件 emit_delegate_event(cap, cap_type, addr, false); }vault.move#L402-L429防御要点未启用委托的 vault 拒绝一切委托EDELEGATION_NOT_ENABLED禁止自我委托to_signer地址等于 vault 所有者地址时以EDELEGATE_TO_SELF中止首次委托时同时初始化两侧状态委托者侧的VaultDelegate 所有者侧的VaultDelegates.delegates列表add_element是私有辅助函数vault.move#L483-L487先vector::contains再push_back保证同一能力类型不会在granted_caps中重复出现。撤销revoke与revoke_allrevokevault.move#L432-L453撤销指定委托者的某种能力类型从委托者的granted_caps中移除对应CapType经由remove_element辅助函数若该委托者剩余授权为零则清理整个委托关系move_from掉委托者地址下的VaultDelegate并从所有者侧VaultDelegates.delegates中移除其地址——两侧状态保持一致撤销无需对方 signer 参与这对紧急收回权限场景至关重要文档原话There is no need to have the other signer for this。revoke_allvault.move#L456-L472遍历delegates向量逐个pop_back清空每个委托者的全部授权并逐个发出is_revoked true的委托事件。remove_vault与transfer内部都复用了它保证任何终结性操作都不会留下悬挂的委托。事件发射emit_delegate_eventfun emit_delegate_eventContent: store drop( cap: DelegateCapContent, cap_type: CapType, delegate: address, is_revoked: bool ) acquires VaultEvents { if (existsVaultEventsContent(cap.vault_address)) { let event VaultDelegateEvent{ metadata: *borrow_globalVaultEventsContent(cap.vault_address).metadata, vault_address: cap.vault_address, authority: cap.authority, delegate, cap: cap_type, is_revoked }; event::emit_event(mut borrow_global_mutVaultEventsContent(cap.vault_address).delegate_events, event); } }vault.move#L490-L507注意authority字段直接取自能力实例——这意味着事件能区分所有者亲授与某委托者转授为传递性委托链提供了可审计性。所有权转移transferpublic fun transferContent: store drop(cap: TransferCapContent, to_owner: signer) acquires Vault, VaultEvents, VaultDelegate, VaultDelegates { let new_addr signer::address_of(to_owner); assert!(!existsVaultContent(new_addr), error::already_exists(EVAULT)); assert!( option::is_some(borrow_globalVaultContent(cap.vault_address).content), error::invalid_state(EACCESSOR_IN_USE) ); // 撤销所有委托者 if (existsVaultDelegatesContent(cap.vault_address)) { let delegate_cap DelegateCapContent{vault_address: cap.vault_address, authority: cap.authority}; revoke_all(delegate_cap); }; // 若启用事件则发射事件。事件在旧 vault 上发射而非新 vault。 if (existsVaultEventsContent(cap.vault_address)) { let event VaultTransferEvent { metadata: *borrow_globalVaultEventsContent(cap.vault_address).metadata, vault_address: cap.vault_address, authority: cap.authority, new_vault_address: new_addr }; event::emit_event(mut borrow_global_mutVaultEventsContent(cap.vault_address).transfer_events, event); }; // 移动 vault move_toVaultContent(to_owner, move_fromVaultContent(cap.vault_address)); }vault.move#L514-L542转移语义可以归纳为四条规则新所有者地址下不能已存在同类型的 vaultEVAULT内容不能正被访问器持有EACCESSOR_IN_USE转移前自动撤销全部委托——文档明确新所有者需要按需重新创建委托者即委托关系不随所有权迁移VaultTransferEvent在旧 vault的事件句柄上发射源码注释特别说明 We emit the event on the old vault not the new one随后才把Vault资源从旧地址move_from到新地址move_to。值得注意的一个细节transfer内部构造了一个DelegateCap{vault_address: cap.vault_address, authority: cap.authority}来复用revoke_all——说明能力实例在模块内部是可以按规则直接构造的纯值其可信性来自只有模块函数能创建并使用它们这一不变式。错误码常量一览模块定义在 vault.move#L92-L98共七个错误原因码常量值触发场景结合error函数EVAULT0创建已存在的 vaultalready_existsvault 不存在not_foundEDELEGATE1委托者未获授权请求能力permission_denied重复启用委托already_exists撤销不存在的委托者not_foundEACCESSOR_IN_USE2已有访问器未释放时再创建访问器、销毁或转移 vaultinvalid_stateEACCESSOR_INCONSISTENCY3释放访问器时发现全局内容非空理论上不可达的internal错误EDELEGATE_TO_SELF4向 vault 所有者自身执行委托invalid_argumentEDELEGATION_NOT_ENABLED5在未启用委托的 vault 上执行delegate/revoke/revoke_allinvalid_stateEEVENT6重复调用enable_eventsalready_exists排查 vault 交易失败时错误模块0x1::vault开发地址下 上述 reason code 是定位问题路径的直接依据。能力约束与 Move 语言演进drop要求与 phantom 类型参数文档 Abilities 章节 讨论了一个重要的语言层限制目前我们要求 vault 的Content类型具有drop能力才能实例化ReadCapContent这样的能力类型。否则能力本身将需要一个显式的释放函数而这对纯值类型来说没有意义。我们预期 Move 语言会加入phantom 类型参数或类似特性从而让ReadCapContent在不要求Content同样具备这些能力的情况下也能drop和copy。从仓库当前源码看vault.move#L108四项能力结构体已经声明为struct ReadCapphantom Content: store drop has copy, drop——即文档中预期未来才有的 phantom 类型参数在源码里已经落地Content不再以值的形式进入能力结构体文档中TODO: removedroponContent 的说明相对当前源码已略显滞后。这一差异提示读者nursery 模块处于活跃演进中以源码为准、以文档为纲是最稳妥的阅读策略。构建、测试与文档生成vault 模块在构建系统中的三个事实性证据包结构nursery 是独立的 Move 包MoveNurseryMove.toml依赖MoveStdlib本地路径..即 move-stdlib/Move.toml 定义的MoveStdlib包同样 1.5.0 版本、开发地址std 0x1。跨包引用时vault 模块即以MoveNursery包名暴露。文档生成move-stdlib/src/main.rs 在构建流程中包含Generating nursery documentation步骤先删除旧文档目录再调用 lib.rs 的build_nursery_doc重新生成——所以 nursery/docs 目录下的.md包括本文分析的 vault.md是构建产物与源码注释同步生成。单元测试tests/move_unit_test.rs 中的run_tests_for_pkg(nursery, true)会运行 nursery 包的全部测试文件且额外注入nursery_natives原生函数。需要注意的是当前 nursery/tests 目录 中只有acl_tests.move、capability_tests.move、compare_tests.move、errors_tests.move、guid_tests.move、role_tests.move尚无 vault 的专属测试文件——从这一事实可以推断 vault 模块的测试覆盖仍待补齐这也是 nursery 定位的体现。文档与源码的差异提示为保证读者按源码正确调用此处汇总文档示例与当前源码签名之间的几处出入均可在仓库中直接核对文档 Capabilities 示例写作vault::new(signer, bMy Vault, Content{...})三个参数而源码new实际签名为newContent: store(owner: signer, initial_content: Content)两个参数示例中的bMy Vault语义上对应enable_events的metadata参数两段操作被合并在了同一行示例里。文档 Delegation 示例引用了vault::delegate_read_cap(delegate_cap, other_signer)与vault::revoke_read_cap(...)源码中不存在这两个函数实际接口是泛化的delegate(cap, to_signer, cap_type)与revoke(cap, addr, cap_type)需显式传入vault::read_cap_type()等CapType值。文档中能力结构体的声明未标注phantom如ReadCapContent: drop, store而当前源码已使用ReadCapphantom Content: store drop能力实例不再因Content的存储语义而受限。小结std::vault用约 540 行 Move 源码实现了一个结构清晰的能力安全存储原语四个不可存储、可拷贝的能力令牌读/改/委托/转移由统一鉴权函数validate_cap签发内容访问通过Option状态实现全局唯一访问器互斥委托关系在所有者与委托者两侧地址各留一份状态支持撤销、全量撤销乃至传递性委托链所有权转移则强制先清空全部委托并在旧地址上留痕。配合错误码常量与可选的事件系统它展示了在 Move 的资源模型之上如何把最小权限不可伪造可审计这三个能力安全的核心性质逐一落实为链上可验证的状态约束。对于希望在 Aptos 链上构建带授权语义的私有数据容器密钥材料、受保护配置、SSN 等文档示例中的敏感内容的开发者该模块提供了一个可以直接借鉴的参考实现——其源码与文档均在 third_party/move/move-stdlib/nursery 下可随时查阅。【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考