资讯动态

Aptos Move 中的 Friend Visibility(好友可见性):从 public/private 到细粒度模块信任

发布时间:2026/9/18 2:35:29 来源:尧图企业网站定制
Aptos Move 中的 Friend Visibility好友可见性从 public/private 到细粒度模块信任【免费下载链接】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-coreFriend visibility 是 Move 语言 1.2 版本引入的可见性机制为模块函数提供介于 private 与 public 之间的白名单式访问控制。本文以 friend visibility 变更设计文档 为核心结合 Aptos 仓库中字节码格式、字节码验证器、编译器 v2 及 Aptos 框架的源码与真实用例完整讲解public(friend)修饰符、friend 列表声明规则、字节码层面的实现以及设计权衡帮助你理解并正确使用这一细粒度模块信任机制。一、背景传统 public/private 可见性模型的局限在 Move 1.2 之前一个函数只有两种可见性public或 private不加修饰符。public函数可以在任何地方被调用private 函数只能在其定义模块内被调用。这种简单模型在实际开发中暴露出三个问题。1. 过于宽松的可见性模型对于仅限已知的特定模块集合访问的函数即白名单式访问开发者只能将其声明为public这违背了设计初衷。文档以 Diem 框架中的initialize函数为例理论上这些初始化函数只应被Genesis模块调用绝不应暴露给其他模块或脚本。但由于可见性模型的限制它们不得不被声明为public同时依靠运行时能力检查和静态验证来保证这些函数在非 genesis 状态下调用时会 abort——这是一种事后补救式的安全策略。2. 模块后续升级不灵活public函数是面向全世界的契约它不能被删除、重命名函数签名也不能被修改。原因在于要评估修改public函数 API 的影响必须全局扫描链上已发布的所有代码调用点——在开放发布模型的区块链网络中这既不可行也不可扩展。而 friend 函数只对模块的 friend 列表白名单构成契约且模块所有者完全控制 friend 列表的成员。因此更新 friend 函数要容易得多只需协调 friend 列表中的模块即可尤其当 friend 函数与所有 friend 模块都由同一所有者维护时。3. 规范编写与验证的简化机会Friend visibility 能简化 Move Prover 的规范编写与验证给定一个 friend 函数及其宿主模块的 friend 列表可以轻松且穷尽地找到该 friend 函数的所有调用点。基于此甚至可以完全跳过 friend 函数的规范将其实现内联到调用方从而简化验证技术并证明更强的性质。相比之下public函数必须编写完整且准确的规范。二、核心概念四种可见性级别与 friend 列表Friend visibility 将可见性级别从两种扩展为四种可见性源码写法字节码文件格式中的表示private无修饰符Privatefriendpublic(friend)Friendscriptpublic(script)ScriptpublicpublicPublic其中 Script 可见性解决的是 Diem 框架中一个正交的问题详见 Script Visibility 变更文档。从字节码层面看Visibility枚举定义在 move-binary-format 的 file_format.rs 中#[repr(u8)] pub enum Visibility { /// Accessible within its defining module only. #[default] Private 0x0, /// Accessible by any module or script outside of its declaring module. Public 0x1, // DEPRECATED for separate entry modifier // Script 0x2, /// Accessible by this module as well as modules declared in the friend list. Friend 0x3, }注意两点Friend 0x3是一个独立的字节码值0x2Script已标记为 DEPRECATED改为独立的 entry 修饰符工具方法is_public()对Friend返回false而is_public_or_friend()对Friend返回true说明在需要对外可见的判断语境中friend 函数被归类为受限可见。除新的public(friend)修饰符外每个模块允许拥有一个 friend 列表通过零个或多个friend address::name语句声明。friend 列表中的模块可以调用宿主模块中定义的public(friend)函数而非 friend 模块则被禁止访问public(friend)函数。在字节码文件格式中friend 列表是一个新的 section由friend_decls: VecModuleHandle字段承载见 file_format.rs。三、public(friend)新可见性修饰符public(friend)可以应用于模块中的任何函数定义。一个public(friend)函数可以被以下两种函数调用同一模块模块M中的任何其他函数模块M的 friend 列表中任一模块所定义的任何函数。除此之外public(friend)函数与其他模块内函数遵循相同规则它们可以调用同一模块中的其他函数public(script)函数除外、创建新的结构体实例、访问该模块声明的类型的全局存储等。Aptos 框架中大量使用该特性。例如 aptos_account.move 中的register_apt、burn_from_fungible_store_for_gas、mint_to_fungible_store_for_gas等均为public(friend)函数再如 aptos_coin.move 中的initialize函数public(friend) fun initialize(aptos_framework: signer): (BurnCapabilityAptosCoin, MintCapabilityAptosCoin) {这与变更文档中initialize这类函数应只被特定模块使用的动机完全对应——在 Aptos 框架中aptos_coin::initialize正是通过 friend 机制仅向genesis、transaction_fee等受信模块开放。四、friend 列表声明语法一个模块可以通过 friend 声明语句声明其他模块为好友支持两种形式friend address::name— 使用完全限定模块名的 friend 声明friend module-name-alias— 使用模块别名通过use语句引入的 friend 声明。一个模块可以有多个 friend 声明所有 friend 模块的并集被记录在 friend 列表中。为了可读性friend 声明通常应放在模块定义的开头附近。需要注意Move 脚本不能声明 friend 模块因为 friend 函数的概念在脚本中根本不存在。以 coin.move 的实际声明为例其模块定义开头即是三个 friend 声明module aptos_framework::coin { use std::error; use std::option::{Self, Option}; ... friend aptos_framework::aptos_coin; friend aptos_framework::genesis; friend aptos_framework::transaction_fee;同样的模式还出现在 aptos_account.move它声明了genesis、resource_account、transaction_fee、transaction_validation四个 friend。在 aptos-framework 的全部源码中friend声明的使用数量达上百处可见该机制已是框架内部模块协作的标准手段。五、friend 声明的规则约束Friend 声明受以下规则约束且这些规则并非仅停留在文档层面而是由字节码验证器强制实施模块不能将自己声明为 friend例如0x2::M不能声明0x2::M为 friendfriend 模块必须在同一账户地址下例如0x2::M不能声明0x3::N为 friendfriend 关系不能形成循环模块依赖例如0x2::Afriend0x2::Bfriend0x2::Cfriend0x2::A是不允许的更一般地声明 friend 会给 friend 模块增加对当前模块的依赖若该 friend 模块已被直接或间接使用就会产生依赖环friend 在模块发布时必须存在例如0x2::M不能声明一个加载器无法解析的0x2::X为 friendfriend 列表不能包含重复项。其中规则 1、2、5 的强制性可以在源码中找到直接证据move-bytecode-verifier/src/friends.rs 是专门的验证 pass若friend_decls包含模块自身返回INVALID_FRIEND_DECL_WITH_SELF若存在地址与自身不同的 friend 模块返回INVALID_FRIEND_DECL_WITH_MODULES_OUTSIDE_ACCOUNT_ADDRESS。源码注释明确说明同账户地址限制是策略决定而非技术要求——VM 和字节码验证器的其他 pass 并不依赖friend 模块必须同地址这一假设只是因为缺乏跨账户 friend 的明确用例、且为最小化发布流程改动才暂时强制此约束未来可能放开重复项检查由 check_duplication.rs 中的check_module_handles(module.friend_decls())完成对应的 VM 状态码定义在 move-core 的 vm_status.rsINVALID_FRIEND_DECL_WITH_SELF 1104、INVALID_FRIEND_DECL_WITH_MODULES_OUTSIDE_ACCOUNT_ADDRESS 1105、INVALID_FRIEND_DECL_WITH_MODULES_IN_DEPENDENCIES 1106。从编译器侧看module_generator.rs 在生成字节码时遍历module_env.get_friend_modules()为每个 friend 模块生成ModuleHandle并push进module.friend_decls从而把源码中的friend声明物化为字节码中的 friend 列表 section。六、完整示例模块 A 与其 friend 模块变更文档给出了一个典型示例展示public(friend)函数与 friend 模块的完整协作方式address 0x2 { module A { // friend declaration via fully qualified module name friend 0x2::B; // friend declaration via module alias use 0x2::C; friend C; public(friend) fun foo() { // a friend function can call other non-script functions in the same module i_am_private(); i_am_public(); bar(); } public(friend) fun bar() {} fun i_am_private() { // other functions in the same module can also call friend functions bar(); } public fun i_am_public() { // other functions in the same module can also call friend functions bar(); } } module B { use 0x2::A; public fun foo() { // as a friend of 0x2::A, functions in B can call friend functions in A A::foo(); } public fun bar() { 0x2::A::bar(); } } }该示例完整展示了 friend 可见性的三条访问路径模块A通过完全限定名friend 0x2::B;和模块别名use 0x2::C; friend C;两种方式声明 friendpublic(friend)函数foo可以调用同模块的 private、public 以及其他 friend 函数同模块的 private 函数i_am_private和 public 函数i_am_public也可以调用 friend 函数barfriend 模块B中的函数既可以通过use引入的别名A::foo()调用也可以通过完全限定路径0x2::A::bar()调用 friend 函数。七、设计权衡为何选择模块到模块粒度变更文档详细记录了设计过程中对 friend 粒度、声明位置和发布顺序的权衡理解这些有助于正确运用该特性。1. 粒度选择Module-to-Module已采纳四种候选方案中最终选择了模块到模块的粒度——模块B是模块A的 friend模块B中任何函数都能访问模块A中的任何 friend 函数。理由如下模块一直是 Move 语言的信任边界现有可见性模型以宿主模块定义 public/privateStruct/Resource 类型只有定义模块才能访问其内部结构。因此以模块作为 friend 访问的信任边界最自然与其他语言如 C的 friend 粒度呼应。未被采纳的方案及其原因Module-to-Function模块 B 是函数 foo 的 friend粒度过细破坏了模块是边界的心智模型且可能导致一个模块对每个 friend 函数都要重复写friend AFunction-to-Module函数 foo 是模块 A 的 friend表达上很奇怪——信任0x3::B::foo()却不信任同一模块中的0x3::B::bar()难以设想有效用例Function-to-Function函数 foo 是函数 bar 的 friend除了信任同一模块的一个函数而非另一个的怪异感外还会导致开发不灵活若B::foo是 private 函数重命名它居然需要同步更新模块 A 中的内容private 函数将不再真正私有。2. 声明位置Callee-side已采纳Friend 列表由被调用方模块所有者在源码中声明。优点是 friend 列表与模块源码同文件开发者查看同一文件即可知道谁能调用 friend 函数、friend 函数应如何加固后期增删 friend 只需更新列表并重新发布模块受可升级性与兼容性检查约束。备选的 Caller-side调用方申请友谊方案则存在明显缺陷代码所有者无法直观掌握 friend 关系全貌来源真相需要额外存储VM 可更新的字节码 section 或账户中的FriendList实体且查看模块源码时无法获知谁可访问 friend 函数。3. 发布顺序与跨模块引用Friend 机制引入了跨模块引用使发布流程复杂化。考虑如下模块address 0x2 { module M { friend 0x2::N; public(friend) fun foo() {} } module N { use 0x2::M; fun bar() { M::foo(); } } }模块N因use 0x2::M依赖M而M因friend 0x2::N反向引用N。在逐个模块发布的模型下显然必须先发布M先发布N会使N::bar()调用失败先发布M无副作用因为无人能调用M::foo()发布M时字节码验证器看到指向尚不存在的N::bar()的可见性约束前向声明不会尝试解析该函数句柄必须容忍这种前向引用风险在于竞态条件若 Alice 和 Eve 都能在0x2发布当M发布后Eve 可能抢在 Alice 之前发布一个恶意bar()的N滥用M开发者对 Alice 的信任。文档给出的缓解方案有两种仍用单模块发布模型的安全三步流程先发布一个空的占位模块N再发布模块M最后发布使用 friend 函数的更新版N未来的多签名 多模块原子发布模型允许一批模块即使位于不同账户在单笔交易中原子发布/更新从根本上消除竞态风险也无需三步流程。4. 其他共享可见性方案的对比Address visibility地址可见性类似 Java 的 package 概念用地址充当命名空间实现public(address)即internal。问题在于无法控制后续向该地址的发布可能违反 Move发布时所有绑定已知且不可更改的原则——若同地址发布即获得他人模块内部状态访问权就可能读写原本仅限一组模块私有的状态Package visibility包可见性即 .NET CLR 的internal模型一起编译在 Move 中即一起发布的模块可互访内部状态。问题在于发布后如加载时难以验证可见性/可访问性版本升级可能遗留保留权限但非本意的模块且 Move 尚无多模块包概念。相比而言针对选定模块的 friend visibility 提供比包可见性更细粒度的访问控制因此是更优的选择。八、总结Friend visibility 为 Move 提供了面向模块白名单的细粒度访问控制从语言层面解决了受限访问函数被迫 public的历史问题语言层面新增public(friend)修饰符与friend address::name声明语句四种可见性级别完整覆盖私有实现、好友协作、脚本入口与全局公开四种场景字节码层面Visibility::Friend 0x3与模块的friend_decls列表成为字节码格式的一部分见 file_format.rs验证层面字节码验证器以独立 pass 强制实施不能自我 friend、必须同账户地址、不能重复等规则见 friends.rs 与 check_duplication.rs实践层面Aptos 框架coin.move、aptos_account.move、aptos_coin.move 等已将 friend 机制作为模块间受信协作的标准工具尤其用于initialize、gas 结算、代币铸造/销毁等敏感操作。对开发者而言在设计模块 API 时面向全局的稳定契约用public仅供内部实现用 private只允许同地址下特定模块访问的敏感能力则应优先考虑public(friend) friend 列表它既提供了比public更严格的安全边界又保留了比 private 更灵活的跨模块协作能力同时显著降低了未来升级维护的成本。【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价