Solana多重签名安全性

  • osecio
  • 发布于 2025-02-22 11:39
  • 阅读 11

本文探讨了在Solana多重签名环境中,当部分签名者被攻击时,如何安全地进行交易签名。文章分析了Solana的两种签名方式:基于最近区块哈希的签名和基于持久nonce的签名,并提出了一种在允许签名者观察交易手续费支付者的前提下,通过等待区块哈希过期并观察链上交易来确保安全签名的流程。总结了在签名者无法完全信任的环境下,确保交易安全性的关键在于可观察性。

Solana 多签安全

如果他们的多签签署者被入侵,团队该怎么办? 我们将探索 Solana 的交易签名模型,并提出在 Solana 上存在恶意签署者的情况下安全签名的程序。

Solana 多签安全标题图片

Bybit 黑客事件 提出了一个有趣的问题:如果他们的签名者受到威胁,团队该怎么办?

Solana 签名

我们首先需要了解 Solana 签名是如何工作的。 有两种方法可以签署 Solana 交易。

最近的区块哈希

最直接的方法是使用“最近的区块哈希”。 来自文档

在交易处理期间,Solana 验证器将检查每个交易的最近区块哈希是否记录在最近存储的 151 个哈希(即“最大处理期限”)中。 如果交易的最近区块哈希早于此最大处理期限,则该交易将不被处理。

实际的常量在此处定义

// 领导者将接受的区块哈希的最大期限
pub const MAX_PROCESSING_AGE: usize = MAX_RECENT_BLOCKHASHES / 2;

对于那些好奇的人,该逻辑从这里开始,并且很容易理解,最终在 is_hash_index_valid 检查中结束。

fn is_hash_index_valid(last_hash_index: u64, max_age: usize, hash_index: u64) -> bool {
    last_hash_index - hash_index <= max_age as u64
}

一个重要的结果是,任何签名的交易都有大约几分钟的自然过期时间。

由于插槽(即验证者可以生成区块的时间段)配置为持续约 400 毫秒,但可能在 400 毫秒到 600 毫秒之间波动,因此给定的区块哈希只能被交易使用约 60 到 90 秒,之后将被运行时视为已过期。

这意味着攻击者必须在很短的时间内使用恶意的签名交易。

持久 Nonce

第二种类型的签名是持久 nonce。 这些是为了解决上面提到的功能(或问题)而创建的:短过期时间。

持久交易 nonce 提供了一个机会来创建和签署可以在未来任何时间提交的交易,以及更多。 这开辟了范围广泛的用例,否则这些用例是不可能或太难实现的

如果我们检查一下最近的区块哈希验证代码,我们还可以看到对持久 nonce 的处理。

    let recent_blockhash = tx.message().recent_blockhash();
    if let Some(hash_info) = hash_queue.get_hash_info_if_valid(recent_blockhash, max_age) {
        Ok(CheckedTransactionDetails {
            nonce: None,
            lamports_per_signature: hash_info.lamports_per_signature(),
        })
    } else if let Some((nonce, previous_lamports_per_signature)) = self
        .check_load_and_advance_message_nonce_account(
            tx.message(),
            next_durable_nonce,
            next_lamports_per_signature,
        )
    {
        Ok(CheckedTransactionDetails {
            nonce: Some(nonce),
            lamports_per_signature: previous_lamports_per_signature,
        })
    } else {
        error_counters.blockhash_not_found += 1;
        Err(TransactionError::BlockhashNotFound)
    }

该文档很好地解释了它们的工作原理。

持久交易 Nonce 的长度为 32 字节(通常表示为 base58 编码的字符串),用于代替最近的区块哈希,以使每笔交易都是唯一的(以避免双重支付),同时消除了未执行交易的死亡率。

持久 nonce 由系统程序创建和管理。 它们没有固定的 PDA,因此每个帐户可以有多个关联的 nonce。

使用持久 nonce 后,它将被“推进”以防止重放攻击。 新的 nonce 是基于当前的区块哈希计算的,并且无法提前预测。

    let hash_queue = self.blockhash_queue.read().unwrap();
    let last_blockhash = hash_queue.last_hash();
    let next_durable_nonce = DurableNonce::from_blockhash(&last_blockhash);

这对我们的威胁模型产生了一个重要的影响。 与最近的区块哈希交易不同,持久 nonce 交易可以被保存和重复使用。

威胁模型

让我们考虑一下原始问题的简化形式。

  1. 我们有一个 N/M 多签
  2. 签名者无法看到他们正在签署的内容,包括内容和签名数量。 这大致相当于盲签交易。
  3. 我们可以准确地查询链的状态。

我们可以安全地签署交易吗?

一个观察结果是,这个问题很难用持久 nonce 解决。 通过签署持久 nonce 交易,攻击者可以收集签名并在未来某个不确定的时间点重放它们。

持久 nonce 需要一个链上账户,并且可以使用 getProgramAccounts 调用来验证你的签名者是否具有关联的持久 nonce

const connection = new Connection(clusterApiUrl('testnet'));
const nonceAccounts = await connection.getProgramAccounts(
  // 系统程序拥有所有 nonce 账户。
  SYSTEM_PROGRAM_ADDRESS,
  {
    filters: [\
      {\
        // Nonce 账户正好是 80 字节长\
        dataSize: 80,\
      },\
      {\
        // 授权者的 32 字节公钥被写入\
        // 到 nonce 账户数据的第 8-40 个字节中。\
        memcmp: {\
          bytes: AUTHORITY_PUBLIC_KEY.toBase58(),\
          offset: 8,\
        },\
      },\
    ],
  }
);

不幸的是,这还不够1。 一笔交易可能有多个签名者,攻击者可以使用他们自己的持久 nonce 支付者。 这意味着我们上面定义的问题很遗憾是无法解决的。

    let instruction = system_instruction::transfer(&from, &ledger_base_pubkey, 42);
    let message =
        Message::new_with_nonce(vec![instruction], Some(&evil_nonce_authority), &nonce_account, &evil_nonce_authority)
            .serialize();

幸运的是,通过一个小修改,这个问题是可以解决的。 如果允许签名者观察交易的费用支付者呢? 例如,Ledger 在此处记录费用支付者

bool print_config_show_authority(const PrintConfig* print_config, const Pubkey* authority) {
    return print_config->expert_mode || !pubkeys_equal(print_config->signer_pubkey, authority);
}

假设我们已经确定我们的签名者没有关联的 nonce 账户。 如果我们的公钥是新提议交易的费用支付者,我们可以肯定该交易不使用持久 nonce!

如果没有持久 nonce,问题就变得容易解决得多。 等待足够的时间后,所有先前签名的交易都将过期。 如果我们没有看到任何意外的交易,那就意味着我们是安全的。

然后,我们可以使用以下过程。

  1. 确保所有签名者都没有持久 nonce 账户。
  2. 第一个签名者签名并提交交易。
  3. 等待两分钟,让所有最近的区块哈希过期。
  4. 观察与签名者相关的最近交易,以确保没有提交任何意外内容。
  5. 对每个签名者重复步骤 2 到 4

进一步

Solana 的签名模型是独一无二的。 如果协议部署在没有这些独特属性的区块链上,该怎么办? 最重要的约束是可观察性。 必须有一种方法可以看到你正在签署的内容,无论是在签署时还是在事后隐式地看到。

例如,pcaversaccio 编写了一个工具来验证 Safe 交易哈希。 随着这个领域的成熟,我们希望更多的开源工具能够出现。

脚注

  1. 此博客文章的原始版本没有考虑恶意的费用支付者。 感谢 @PierreArowana 指出了这一点。
  • 原文链接: osec.io/blog/2025-02-22-...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
osecio
osecio
Audits that protect blockchain ideas.