#38516 [BC-High] Signer can censor transactions and halt the network by providing an invalid nonce o

Submitted on Jan 5th 2025 at 15:55:56 UTC by @n4nika for Attackathon | Stacks

  • Report ID: #38516

  • Report Type: Blockchain/DLT

  • Report severity: High

  • Target: https://github.com/stacks-network/sbtc/tree/immunefi_attackaton_0.9/signer

  • Impacts:

    • Network not being able to confirm new transactions (total network shutdown)

Description

Summary

Whenever a coordinator gathers nonces, the first threshold of signers providing a nonce are taken as the signer set. The problem is that during nonce gathering, not enough integrity checks are done on the provided nonces, allowing a signer to send malformed nonces and with that, censor any transactions they want to, in the worst case censoring ALL transactions, shutting down the network.

Finding Description

In wsts::fire.rs::gather_nonces, we add a signer's provided nonce_response to our public_nonces without checking the integrity of the nonces vector.

let nonce_info = self
    .message_nonces
    .entry(nonce_response.message.clone())
    .or_default();
nonce_info
    .public_nonces
    .insert(nonce_response.signer_id, nonce_response.clone());

Since neither the validity of the nonce, nor the length of the nonces vector is checked, we can either provide an invalid nonce, making it impossible to create a valid signature, or provide multiple nonces.

If we provide multiple nonces, we later fail in sign_with_tweak when we try to create the aggregate signature:

If this errors, the signature generation will be unsuccessful.

Mitigation

Consider checking that the length of the provided nonces matches the length of valid unique key_ids provided. If not, the packet should be dismissed and the signer marked as malicious.

Regarding the validity of the nonce, I am unsure how to properly check that at this point in the signing process.

Proof of Concept

PoC

Please apply the following diff and execute the test with cargo test --package signer --test integration -- transaction_coordinator::sign_bitcoin_transaction_poc --exact --show-output --ignored --nocapture.

This will show that when we try to sign a message, sometimes we fail with Err(UnexpectedOperationResult(SignError(Coordinator(Aggregator(BadNonceLen(3, 2)))))), showing that the whole signing round fails. Now this only happens sometimes in our simulated scenario since we use a 2/3 signing set. In a realworld scenario with a set of 11/15, we are very likely to always provide a malicious nonce, causing the signing round to abort.

Last updated

Was this helpful?