TON 智能合约的 3 种授权类型
Source: Dev.to
Introduction
在智能合约中,我们经常需要将特定操作限制在特定参与者上。
一个常见的例子是钱包合约:我们必须对消息发送者进行授权,以确保只有合法用户才能发送资金。
下面介绍了 TON 智能合约中三种常见的消息授权方式。这些机制默认用于外部消息,除非另有说明。
Signature‑based Authorization (External Messages)
合约使用存储在其数据中的公钥来验证消息的签名。这种方法是确定性的,并且能够工作,因为合约存储是公开的,私钥无法保存在合约内部。
// wallet3-code.fc (excerpt)
() recv_external(slice in_msg) impure {
var signature = in_msg~load_bits(512);
;; some code
var (stored_seqno, stored_subwallet, public_key) =
(ds~load_uint(32), ds~load_uint(32), ds~load_uint(256));
;;
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
accept_message();
;; other code
}
Source: wallet‑v3 contract on GitHub
Why it works
- 确定性 – 加密验证是确定性的,不像随机数生成那样不确定。
- 公开存储 – 公钥可以安全地存放在合约的状态中。
基于签名的授权是外部消息中最常用的方法。
Address Comparison (Internal Messages)
对于内部消息,合约仅仅将发送者的地址与存储在状态中的地址进行比较。
// nft-collection-editable.fc (excerpt)
() recv_internal(cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) {
;; ignore empty messages
return ();
}
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) {
;; ignore all bounced messages
return ();
}
slice sender_address = cs~load_msg_addr();
int op = in_msg_body~load_uint(32);
int query_id = in_msg_body~load_uint(64);
var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data();
;; some code
throw_unless(401, equal_slices(sender_address, owner_address));
;; some code
}
Source: NFT Collection contract on GitHub
合约存储所有者地址,如果发送者地址不匹配则抛出错误。这种做法可以扩展为存储多个授权地址,以对应不同的角色。
Authorization for Multiple Senders (Jetton Wallet Example)
当合约需要授权任意数量的发送者——例如在 Jetton 合约中——我们需要一种更灵活的方案。Jetton Wallet 会验证发送者钱包是否属于同一个 Jetton Master,或者是否是由发送者地址推导出的确定性钱包地址。
// jetton-wallet.fc (excerpt)
() receive_jettons(slice in_msg_body, slice sender_address, int my_ton_balance, int msg_value)
impure inline_ref {
(int status, int balance, slice owner_address, slice jetton_master_address) = load_data();
int query_id = in_msg_body~load_query_id();
int jetton_amount = in_msg_body~load_coins();
slice from_address = in_msg_body~load_msg_addr();
slice response_address = in_msg_body~load_msg_addr();
throw_unless(error::not_valid_wallet,
equal_slices_bits(jetton_master_address, sender_address)
|
equal_slices_bits(
calculate_user_jetton_wallet_address(
from_address,
jetton_master_address,
my_code()
),
sender_address
)
);
;; other code
}
Source: Jetton Wallet contract on GitHub
Key points
calculate_user_jetton_wallet_address根据发送者地址、Jetton Master 地址以及合约代码(my_code())推导出预期的钱包地址。my_code()通过c7寄存器返回当前合约代码(参见TVM 寄存器文档)。- 合约还可以在其存储中保存另一个合约的代码,以计算外部合约地址。
- 当需要初始化参数时,必须仔细验证;如果两个合约共享已知参数(例如管理员地址),最好从自己的存储中读取,而不是从消息中获取。
Conclusion
选择合适的授权策略是设计 TON 智能合约架构的关键环节。对外部消息使用签名验证,对内部简单检查使用地址比较,而在需要授权大量可能发送者的场景(如 Jetton Wallet)则使用确定性地址计算。
祝编码愉快! 🚀