TON 智能合约中的 3 种授权类型

发布: (2025年12月5日 GMT+8 04:48)
5 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您翻译成简体中文并保持原有的格式、Markdown 语法以及技术术语不变。

介绍

在智能合约中,我们经常需要将特定操作限制在特定参与者之上。
一个常见的例子是钱包合约:我们必须对消息发送者进行授权,以确保只有有效用户才能发送资金。

下面列出了在 TON 智能合约中授权消息的三种常见方式。这些机制用于外部消息,除非另有说明。

Source: wallet‑v3 contract on GitHub

基于签名的授权(外部消息)

合约使用存储在其数据中的公钥来验证消息的签名。该方法是确定性的,并且能够工作,因为合约存储是公开的,私钥无法保存在合约内部。

// 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
}

为什么它能工作

  1. 确定性 – 加密验证是确定性的,不像随机数生成那样具有不确定性。
  2. 公开存储 – 公钥可以安全地存放在合约的状态中。

基于签名的授权是外部消息中使用最广泛的方法。

地址比较(内部消息)

对于内部消息,合约仅仅将发送者的地址与存储在其状态中的地址进行比较。

// 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

合约存储所有者地址,并在发送者不匹配时抛出错误。此方法可以扩展为存储多个授权地址,以用于不同的角色。

Source: Jetton Wallet contract on GitHub

多发送者授权(Jetton 钱包示例)

当合约需要授权任意数量的发送者时——例如在 Jetton 合约中——我们需要一种更灵活的方式。Jetton 钱包会验证发送者钱包是否属于同一 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
}

关键要点

  • calculate_user_jetton_wallet_address 根据发送者地址、Jetton Master 地址以及合约代码(my_code())推导出预期的钱包地址。
  • my_code() 通过 c7 寄存器返回当前合约代码(参见 TVM 寄存器文档)。
  • 合约还可以在其存储中保存另一个合约的代码,以计算外部合约地址。
  • 当需要初始化参数时,必须仔细验证;如果两个合约共享已知参数(例如管理员地址),最好从自己的存储中读取,而不是从消息中获取。

结论

选择正确的授权策略是设计 TON 智能合约架构的关键部分。对于外部消息使用签名验证,对于简单的内部检查使用地址比较,在需要授权多个可能的发送者时(如 Jetton Wallet 中的确定性地址计算)使用确定性地址计算。

祝编码愉快! 🚀

Back to Blog

相关文章

阅读更多 »

智能合约入门

介绍 学习区块链概念可能具有挑战性,但理解智能合约并不一定如此。本指南介绍了智能合约的基础…

加密支付网关详解

加密支付网关定义 加密支付网关是一种服务或内部组件,用于将链下业务事件——如订单或发票——连接……