Aptos Move 标准库capability模块完全指南:基于 signer 的防伪授权令牌与委派机制
【免费下载链接】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
本文深入讲解 Move 标准库(nursery 目录)中的capability模块——一套基于"能力安全"(capability-based security)思想的访问控制原语。在 Aptos 智能合约开发中,capability用于实现"只有通过 signer 授权才能执行敏感操作"的编程模式,例如模块初始化、特权函数调用、管理员操作授权等。读完本文,你将掌握该模块的全部 API(create/acquire/acquire_linear/delegate/revoke)、两种令牌(Cap与LinearCap)的设计差异、委派与撤销机制,以及如何通过 Move 规范语言(specification language)为委派目标附加额外约束。该模块在仓库中的完整文档位于 capability.md,源码位于 capability.move。
一、概述:什么是 capability
capability模块定义的"能力"(capability)是一种不可伪造的令牌(unforgable token),它证明某个 signer 已经授权了一个特定操作。该模块被明确标记为EXPERIMENTAL(实验性),意味着 API 可能在未来版本中变化。
其核心安全保证来自两条关键设计:
- 令牌只在获取它的那笔交易(transaction)内有效;
- 由于
capability::Cap类型没有key能力、无法被存储到全局内存(global storage),能力令牌不可能"泄露"到交易之外。
由此可以推导出一个重要结论:在一笔交易内,凡是把 capability 作为参数调用的函数,都能保证该 capability 一定是在此交易执行过程中、通过一次正确的 signer 授权步骤获取的,不存在"凭空捏造"或"从链上读取"的路径。这正是 capability 模式在 Move 中被称为安全访问控制基石的原因——函数只要检查调用者是否持有Cap<Feature>令牌,即可确信授权已经完成。
二、核心数据结构
模块定义了四个类型,分为"对外令牌"与"内部存储状态"两类(源码见 capability.move):
1.Cap<Feature>—— 可复制、可丢弃的能力令牌
struct Cap<phantom Feature> has copy, drop { root: address }- 拥有
copy与drop能力,不能存储在全局内存中; - 字段
root记录能力所有者的地址; - 类型参数
Feature使用phantom修饰,仅作为"类型标签"参与类型检查,不占用运行时存储; - 由于可
copy,同一能力可以在交易内被多次使用。
2.LinearCap<Feature>—— 线性能力令牌
struct LinearCap<phantom Feature> has drop { root: address }- 只有
drop能力,没有copy; - 适用于"一次授权只能使用一次"的场景:由于无法复制,使用(move)一次后令牌即被消耗,天然强制了单次使用的语义;
- 是否暴露线性还是非线性能力,由拥有
Feature类型的模块自行决定(见acquire_linear)。
3.CapState<Feature>—— 能力配置状态(链上资源)
struct CapState<phantom Feature> has key { delegates: vector<address> }- 拥有
key能力,存储于所有者的账户下; delegates记录当前已被授权的委派人(delegate)地址列表;- 由
create函数创建,是判断"某地址是否拥有某能力"的链上事实依据。
4.CapDelegateState<Feature>—— 委派关系状态(链上资源)
struct CapDelegateState<phantom Feature> has key { root: address }- 存储于**被委派人(delegate)**的账户下;
root字段指向能力所有者(root)的地址,用于在委派者申请能力时定位其对应的能力根。
这四种类型构成了完整闭环:链上CapState/CapDelegateState记录授权事实,链下Cap/LinearCap作为交易内流转的授权凭证。
三、实战用法:如何在业务模块中封装能力
文档给出的标准用法是:将能力的创建与获取封装在一个模块内,该模块拥有一个只有自己能构造的"类型标签"(type tag)结构体,从而完全控制能力的发放。
以下示例来自文档(源码注释版见 capability.move):
module Pkg::Feature { use std::capability::Cap; /// A type tag used in Cap<Feature>. Only this module can create an instance, /// and there is no public function other than Self::acquire which returns a value of this type. /// This way, this module has full control how Cap<Feature> is given out. struct Feature has drop {} /// Initializes this module. public fun initialize(s: &signer) { // Create capability. This happens once at module initialization time. // One needs to provide a witness for being the owner of Feature // in the 2nd parameter. <<additional conditions allowing to initialize this capability>> capability::create<Feature>(s, &Feature{}); } /// Acquires the capability to work with this feature. public fun acquire(s: &signer): Cap<Feature> { <<additional conditions allowing to acquire this capability>> capability::acquire<Feature>(s, &Feature{}); } /// Does something related to the feature. The caller must pass a Cap<Feature>. public fun do_something(_cap: Cap<Feature>) { ... } }模式解读:witness(见证者)机制
该模式的关键是类型标签 + witness:
Feature结构体没有任何字段、只有drop能力,且Feature {}只能在本模块内构造(initialize与acquire中的&Feature{});create、acquire等capability函数都要求调用者传入&Feature作为witness(见证者),以证明调用者确实"拥有"该类型参数;- 由于外界无法构造
Feature{},外界就无法绕过Pkg::Feature::acquire直接调用capability::acquire<Feature>; <<additional conditions>>占位符表示:模块可以在发放能力前加入自己的额外授权条件(如白名单、时间锁、治理投票等),从而把能力发放完全置于模块控制之下。
这种"函数参数要求&Featurewitness"的设计,是 Move 中典型的phantom type + witness权限模式,与aptos_std::type_info等基于类型标签的模式同源。
四、API 详解:从创建到获取
create—— 创建能力类
public fun create<Feature>(owner: &signer, _feature_witness: &Feature)创建一个新的能力类,所有者(owner)为传入的 signer 地址。调用者必须传入自己拥有Feature类型参数的 witness。其实现为:
public fun create<Feature>(owner: &signer, _feature_witness: &Feature) { let addr = signer::address_of(owner); assert!(!exists<CapState<Feature>>(addr), error::already_exists(ECAP)); move_to<CapState<Feature>>(owner, CapState{ delegates: vector::empty() }); }- 若该地址下已存在
CapState<Feature>,则以error::already_exists(ECAP)中止(abort); - 否则将
CapState { delegates: vector::empty() }发布到 owner 账户下——初始时委派列表为空。
acquire—— 获取能力令牌
public fun acquire<Feature>(requester: &signer, _feature_witness: &Feature): Cap<Feature>只有能力所有者本人、或经授权的委派人才能成功调用,实现为:
public fun acquire<Feature>(requester: &signer, _feature_witness: &Feature): Cap<Feature> acquires CapState, CapDelegateState { Cap<Feature>{root: validate_acquire<Feature>(requester)} }它把鉴权逻辑委托给内部函数validate_acquire,并将返回的 root 地址封装进Cap<Feature>令牌。
acquire_linear—— 获取线性能力令牌
public fun acquire_linear<Feature>(requester: &signer, _feature_witness: &Feature): LinearCap<Feature>与acquire逻辑完全一致,但返回LinearCap<Feature>(不可复制、单次使用)。是否向用户暴露线性或非线性能力,由拥有Feature的模块决定。
validate_acquire—— 核心鉴权逻辑
fun validate_acquire<Feature>(requester: &signer): address acquires CapState, CapDelegateState { let addr = signer::address_of(requester); if (exists<CapDelegateState<Feature>>(addr)) { let root_addr = borrow_global<CapDelegateState<Feature>>(addr).root; // double check that requester is actually registered as a delegate assert!(exists<CapState<Feature>>(root_addr), error::invalid_state(EDELEGATE)); assert!(vector::contains(&borrow_global<CapState<Feature>>(root_addr).delegates, &addr), error::invalid_state(EDELEGATE)); root_addr } else { assert!(exists<CapState<Feature>>(addr), error::not_found(ECAP)); addr } }鉴权路径分两条:
- 委派人路径:若
addr下存在CapDelegateState<Feature>(即该地址曾被委派过),则读取其root地址,并做双重校验——root 地址下确实存在CapState<Feature>、且addr确实出现在该CapState的delegates列表中;任一校验失败以error::invalid_state(EDELEGATE)中止; - 所有者路径:否则要求
addr下存在CapState<Feature>(即该地址是能力所有者),否则以error::not_found(ECAP)中止。
两条路径都返回能力根(root)地址。值得注意的是,委派路径的"双重校验"(先查CapDelegateState再核对delegates列表)是防止链上状态不一致的关键防御手段。
root_addr/linear_root_addr—— 读取能力根地址
public fun root_addr<Feature>(cap: Cap<Feature>, _feature_witness: &Feature): address public fun linear_root_addr<Feature>(cap: LinearCap<Feature>, _feature_witness: &Feature): address两者实现均为直接返回cap.root字段,用于从令牌反查能力所有者地址。注意文档描述"Only the owner of the feature can do this",但实现上并未做权限检查——实际的权限约束由"持有令牌本身即已授权"这一前提保证(从源码结构看,_feature_witness参数的存在更多是延续 witness 惯例)。
五、委派(Delegation)与撤销
能力附带一个可选的委派特性:能力所有者可以通过delegate指定另一个 signer 也具备获取该能力的能力;委派可以被revoke撤销。
delegate—— 注册委派关系
public fun delegate<Feature>(cap: Cap<Feature>, _feature_witness: &Feature, to: &signer) acquires CapState { let addr = signer::address_of(to); if (exists<CapDelegateState<Feature>>(addr)) return; move_to(to, CapDelegateState<Feature>{root: cap.root}); add_element(&mut borrow_global_mut<CapState<Feature>>(cap.root).delegates, addr); }- 若目标地址已存在
CapDelegateState<Feature>(委派关系已存在),函数直接返回、不做任何事——即delegate是幂等的; - 否则在
to账户下发布CapDelegateState { root: cap.root },并把to的地址加入所有者CapState.delegates列表(通过add_element去重后插入)。
注意:delegate同样需要持有Cap<Feature>令牌,即只有当前已授权者(所有者或委派人)才能再委派给他人——这构成了一种可传递的授权链。
revoke—— 撤销委派关系
public fun revoke<Feature>(cap: Cap<Feature>, _feature_witness: &Feature, from: address) acquires CapState, CapDelegateState { if (!exists<CapDelegateState<Feature>>(from)) return; let CapDelegateState{root: _root} = move_from<CapDelegateState<Feature>>(from); remove_element(&mut borrow_global_mut<CapState<Feature>>(cap.root).delegates, &from); }- 若
from地址下不存在CapDelegateState<Feature>,同样直接返回(幂等); - 否则从
from账户移除该资源(move_from),并将from从所有者CapState.delegates列表中移除(通过remove_element)。
撤销后,from地址上不再有CapDelegateState<Feature>,因此validate_acquire将走"所有者路径",因from下没有CapState<Feature>而中止——被撤销的委派人随即失去获取能力的能力。
辅助函数add_element/remove_element
fun add_element<E: drop>(v: &mut vector<E>, x: E) { if (!vector::contains(v, &x)) { vector::push_back(v, x) } } fun remove_element<E: drop>(v: &mut vector<E>, x: &E) { let (found, index) = vector::index_of(v, x); if (found) { vector::remove(v, index); } }两个私有工具函数分别实现"去重后追加"与"按值查找并移除",保证delegates列表中不出现重复地址。源码中保留了 TODO 注释,探讨"重复委派/撤销应当幂等返回还是中止"的设计取舍(见 capability.move),说明该 API 仍处实验演进中。
六、错误常量与错误处理
| 常量 | 值 | 含义 | 触发条件 |
|---|---|---|---|
ECAP | 0 | 能力类已存在 / 不存在 | create时目标地址已存在CapState(报already_exists);validate_acquire所有者路径未找到CapState(报not_found) |
EDELEGATE | 1 | 委派状态非法 | validate_acquire双重校验失败:root 无CapState或地址不在delegates列表中(报invalid_state) |
源码见 capability.move。错误码通过std::error的分类函数(already_exists/not_found/invalid_state)包装,Move 调用方可利用assert的abort_code精确区分失败原因。
七、模块规范与形式化验证
capability模块自带 Move Prover 规范,支持开发者用规范语言为委派目标附加额外约束。
内置规范函数
spec fun spec_has_cap<Feature>(addr: address): bool { exists<CapState<Feature>>(addr) } spec fun spec_delegates<Feature>(addr: address): vector<address> { global<CapState<Feature>>(addr).delegates }spec_has_cap<Feature>(a):地址a是否拥有该能力;spec_delegates<Feature>(a):地址a名下能力的委派列表。
用全局不变量约束委派
文档给出了两个典型的全局不变量(global invariant)示例:
示例一:完全禁止委派——要求凡是拥有能力Feature的地址,其委派列表长度必须为 0:
invariant forall a: address where capability::spec_has_cap<Feature>(a): len(capability::spec_delegates<Feature>(a)) == 0;示例二:约束委派目标——若存在委派,则每个委派人都必须满足特定谓词(如白名单校验):
invariant forall a: address where capability::spec_has_cap<Feature>(a): forall d in capability::spec_delegates<Feature>(a): is_valid_delegate_for_feature(d);这类不变量由 Move Prover 在编译/验证阶段检查,可以在不改动运行时逻辑的前提下,把"委派策略"提升为可证明的形式化约束。
aptos-stdlib 版本的增强规范
在 Aptos 框架的正式标准库版本 aptos-stdlib/sources/capability.move 及其 capability.spec.move 中,规范被进一步强化:
- 新增
spec_has_delegate_cap<Feature>(addr)判断地址是否存在委派能力资源; create规范声明aborts_if spec_has_cap<Feature>(addr)与ensures spec_has_cap<Feature>(addr);acquire/acquire_linear通过共享的AcquireSchema精确刻画三条中止条件(委派路径的 root 无CapState、地址不在委派列表、非委派路径无CapState),并保证返回令牌的root字段与鉴权结果一致;delegate/revoke也有对应的aborts_if与ensures声明(revoke的移除性质因证明器限制被 TODO 注释,见源码中的 issue #7422 引用)。
此外 aptos-stdlib 版本将错误码重新编号为ECAPABILITY_ALREADY_EXISTS = 1、ECAPABILITY_NOT_FOUND = 2、EDELEGATE = 3,并把root_addr等函数改为self接收者风格、使用vector的方法链语法(.contains/.index_of/.remove/.push_back)——两版本 API 语义一致、风格略有差异,生产环境请以 aptos-stdlib 版本为准。
八、测试用例验证
仓库在 nursery/tests/capability_tests.move 中提供了完整的单元测试,覆盖四条核心路径:
#[test] fun test_success() { let owner = create_signer(); capability::create(&owner, &Feature{}); let _cap = capability::acquire(&owner, &Feature{}); } #[test] #[expected_failure(abort_code = 0x60000, location = std::capability)] fun test_failure() { let (owner, other) = create_two_signers(); capability::create(&owner, &Feature{}); let _cap = capability::acquire(&other, &Feature{}); } #[test] fun test_delegate_success() { let (owner, delegate) = create_two_signers(); capability::create(&owner, &Feature{}); let cap = capability::acquire(&owner, &Feature{}); capability::delegate(cap, &Feature{}, &delegate); let _delegate_cap = capability::acquire(&delegate, &Feature{}); } #[test] #[expected_failure(abort_code = 0x60000, location = std::capability)] fun test_delegate_failure_after_revoke() { let (owner, delegate) = create_two_signers(); capability::create(&owner, &Feature{}); let cap = capability::acquire(&owner, &Feature{}); capability::delegate(copy cap, &Feature{}, &delegate); // the copy should NOT be needed capability::revoke(cap, &Feature{}, signer::address_of(&delegate)); let _delegate_cap = capability::acquire(&delegate, &Feature{}); }四个用例分别验证:
test_success:所有者创建并获取能力成功;test_failure:非所有者尝试获取能力,按预期以abort_code = 0x60000(not_found(0)经error分类后编码)在std::capability处中止;test_delegate_success:所有者委派给 delegate 后,delegate 可成功获取能力;test_delegate_failure_after_revoke:撤销委派后,delegate 再获取能力即失败;测试中还以注释提示copy cap其实并非必需(因为Cap可复制,但delegate按值传参后revoke仍需要cap)。
这些测试直接印证了文档描述的所有权、委派与撤销三条核心语义,可作为理解该模块行为的最小可运行范例。nursery 包的工程配置见 nursery/Move.toml:包名MoveNursery、依赖本仓库MoveStdlib,开发地址std = "0x1"。
九、总结与适用场景
capability模块为 Move 智能合约提供了一套"不可伪造、交易内有效、可委派、可形式化约束"的授权机制。其设计精髓在于:
- 类型系统即安全边界:
Cap<Feature>无key能力、不能上链,配合模块私有的 witness 类型,从编译期杜绝了能力伪造与泄露; - 链上状态 + 链下令牌分离:
CapState/CapDelegateState记录授权事实,Cap/LinearCap作为交易内凭证,二者由validate_acquire在获取时统一校验; - 委派机制解耦授权与执行:所有者可将操作权委托给其他账户,并能随时撤销,配合 Move Prover 不变量可实现可证明的委派策略。
典型适用场景包括:模块管理员的特权操作授权、多签/代理执行的权限流转、需要"单次授权单次使用"的一次性操作(LinearCap)、以及需要形式化验证授权策略的高安全模块。需要注意的是,该模块仍标记为EXPERIMENTAL(delegate/revoke的幂等语义仍有待最终定夺),在 Aptos 上线的正式标准库版本为 aptos-stdlib 的 capability 模块,两者 API 兼容,生产代码建议以 aptos-stdlib 版本为基准并同步查阅其 规范文件。
【免费下载链接】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),仅供参考