fuels-rs 如何锁定已解锁钱包的签名器并区分 Wallet 可用的操作边界
【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs
在 fuels-rs 中,一个持有签名器(signer)的Wallet实例既能查询链上状态,也能签名消息和交易。当签名任务执行完毕,你希望把私钥从内存中移出,只保留“查余额、列币、看交易”这类只读能力。fuels-rs 用两个类型把这个边界固化在编译期:Wallet<Unlocked<S>>表示附带签名器的钱包,Wallet<Locked>表示只知道自己地址、无法签名的钱包。本文的目标是:把一个已解锁的钱包通过lock方法转换(或直接用地址构造)为Wallet<Locked>,并弄清转换前后各自可用哪些操作。
两种钱包状态及设计意图
Wallet<Unlocked<S>>与Wallet<Locked>的区别(见 access.md):
Wallet<Unlocked<S>>代表附带签名器的钱包。凡是涉及签名消息或交易的操作,钱包必须是这个类型。Wallet<Locked>代表没有签名器的钱包,它只知道自己的公开地址(public address)。Wallet<Locked>不能用于签名交易,但仍可执行一系列有用的操作,包括列出交易、列出资产、查询余额等。
官方给出的设计准则是:API 开发者应尽量减少使用Wallet<Unlocked<S>>的范围,确保签名器在内存中停留的时间不超过必要,以缩小下游库和应用被攻击或出漏洞的面。也就是说,lock不是“临时禁用”,而是把签名能力从类型上拿掉。
从源码 wallet.rs 可以看到实现逻辑:Wallet内部由state(泛型参数S)与provider组成。lock的实现是:
pub fn lock(&self) -> Wallet<Locked> { Wallet::new_locked(self.state.signer.address(), self.provider.clone()) }签名器本身没有参与转换——只取出了它的地址,provider被克隆到新钱包。注意:lock不会销毁原Wallet<Unlocked<S>>,原变量仍是解锁状态;只有让原变量离开作用域(或显式丢弃),签名器才真正不再被持有。
从解锁钱包转到 Wallet
转换方法就是文档中给出的lock(来源 access.md 的 "Transitioning States" 一节):
let wallet_locked = wallet_unlocked.lock();约束是wallet_unlocked必须实现S: Signer,即类型为Wallet<Unlocked<S>>。转换后wallet_locked的静态类型是Wallet<Locked>,从这一刻起它不再有signer()访问器(signer()只在 wallet.rs 的Wallet<Unlocked<S>>实现中定义)。
如果场景是“我只有某个地址,从未在本进程持有过签名器”,可以跳过lock,直接用地址构造Wallet<Locked>。e2e 测试 test_wallet_get_coins 展示了这条路径:
let addr = Address::zeroed(); let coins = setup_single_asset_coins(addr, AssetId::zeroed(), NUM_COINS, AMOUNT); let provider = setup_test_provider(coins, vec![], None, None).await?; let wallet = Wallet::new_locked(addr, provider.clone());Wallet::new_locked(addr, provider)同样来自 wallet.rs。两条路径产出的对象类型一致,操作边界完全相同。
Wallet 可用的操作边界
边界由 trait 实现决定。在 wallet.rs 中:
Wallet<Locked>只实现了ViewOnlyAccount(见 locked 模块):提供address()、try_provider(),以及经由get_spendable_resources组装输入的能力。ViewOnlyAccount是查询余额的通用接口(见 accounts.md)。Wallet<Unlocked<S>>同时实现ViewOnlyAccount和Account,额外提供add_witnesses,这是交易构建时挂签名器的入口。转账类方法transfer、force_transfer_to_contract、withdraw_to_base_layer属于Accounttrait(见 accounts.md 的 "Transferring assets" 一节)。
对照 index.md 的钱包分类说明,可以归纳成下表:
| 操作 | Wallet<Unlocked | Wallet |
|---|---|---|
address()/try_provider() | 支持 | 支持 |
查询余额(get_asset_balance、get_balances,见 checking-balances-and-coins.md) | 支持 | 支持 |
列出币(get_coins) | 支持 | 支持 |
transfer、force_transfer_to_contract、withdraw_to_base_layer | 支持 | 不可用(类型上不存在) |
| 签名消息 / 为交易构建器添加签名器 | 支持 | 不可用(类型上不存在) |
对开发者来说,这意味着边界不是靠运行时报错,而是靠编译期拒绝:在Wallet<Locked>上调transfer或signer()会直接编译失败。这正是 access 文档中“minimise their usage ofWallet<Unlocked<S>>”建议能落地的原因。
完整路径与验证方式
下面是一段把三种状态串起来的代码骨架,示例值取自 examples/wallets/src/lib.rs 中create_wallet_from_mnemonic与 e2e 测试test_wallet_get_coins:
use fuels::prelude::*; // 1. 构造解锁钱包(含签名器) let provider = setup_test_provider(vec![], vec![], None, None).await?; let phrase = "oblige salon price punch saddle immune slogan rare snap desert retire surprise"; let key = SecretKey::new_from_mnemonic_phrase_with_path( phrase, fuels::accounts::signers::derivation::DEFAULT_DERIVATION_PATH, )?; let wallet_unlocked = Wallet::new(PrivateKeySigner::new(key), provider.clone()); // 2. 锁定:签名器从状态中移除,只保留地址与克隆的 provider let wallet_locked: Wallet<Locked> = wallet_unlocked.lock(); // 3. 只读操作在锁定钱包上可用 let asset_id = AssetId::zeroed(); let balance: u128 = wallet_locked.get_asset_balance(&asset_id).await?; let coins = wallet_locked.get_coins(asset_id).await?; // 4. 地址一致:锁定的钱包与解锁钱包指向同一账户 assert_eq!(wallet_locked.address(), wallet_unlocked.address());setup_test_provider是 fuels-test-helpers 提供的测试 provider 构造器,上面的代码放在#[tokio::test]中运行;在连接真实节点的场景下,用你自己的 provider 替换这一行即可。
验证方式有三层:
- 编译期验证:在
wallet_locked上尝试调用transfer或signer(),预期编译失败。能编译通过的代码才是合法的只读路径。 - 地址一致性:如上例,
lock只从签名器提取地址,wallet_locked.address()应等于原解锁钱包的地址。 - 只读查询可执行:e2e 测试 test_wallet_get_coins 用
Wallet::new_locked构造的钱包调用了get_coins并断言返回的 coin 数量与总额,证明Wallet<Locked>的查询路径可用。该测试中的具体数值(NUM_COINS = 3、AMOUNT = 1000)是测试自身的设定值,作为文档示例理解即可,不是通用预期值。
边界与限制
lock与new_locked都不涉及私钥的销毁或存储管理:Wallet<Locked>只持有地址。如果你还需要“把私钥加密存盘、之后凭主密码取回”,那是另一条路径——Keystore::save_key/Keystore::load_key(见 keystore.md 与 examples/wallets/src/lib.rs 的create_and_store_mnemonic_key),与本文的运行时锁定相互独立。- 锁定后的钱包无法再恢复为解锁状态:
Wallet<Locked>没有对应的unlock方法,重建Wallet<Unlocked<S>>需要重新提供签名器(私钥、助记词或 KMS)。 - 签名器种类(
PrivateKeySigner、AWS/Google KMS、Fake Signer,见 index.md)只影响解锁钱包的构造方式;lock之后两者行为一致,因为签名器信息已不可达。
【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考