Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/signature-order.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed signature ordering check that only compared against first signer's index.
3 changes: 1 addition & 2 deletions stackslib/src/chainstate/nakamoto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,9 +904,8 @@ impl NakamotoBlockHeader {
"Signatures are out of order".to_string(),
));
}
} else {
last_index = Some(signer_index);
}
last_index = Some(signer_index);

total_weight_signed = total_weight_signed
.checked_add(signer.weight)
Expand Down
45 changes: 45 additions & 0 deletions stackslib/src/chainstate/nakamoto/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3523,6 +3523,51 @@ pub mod nakamoto_block_signatures {
}
}

#[test]
/// Test that out-of-order signatures are detected even after the first signature.
/// This is a regression test for a bug where `last_index` was only set on the
/// first iteration (inside an `else` branch), so subsequent out-of-order
/// signatures were compared against the first signer's index instead of the
/// previous signer's index. For example, with signers at indices [0, 2, 1],
/// the bug would accept the sequence because index 1 > index 0 (the stale
/// last_index), even though index 1 < index 2 (the actual previous signer).
fn test_out_of_order_signer_signatures_after_first() {
// We need 3 signers so we can construct a sequence where the first
// pair is in order but a later pair is not: [0, 2, 1] or similar.
// We'll use [0, 2, 1] — indices go 0 -> 2 -> 1, which is out of order
// at the third signature.
let signers = [
(Secp256k1PrivateKey::random(), 100),
(Secp256k1PrivateKey::random(), 100),
(Secp256k1PrivateKey::random(), 100),
];
let reward_set = make_reward_set(&signers);

let mut header = NakamotoBlockHeader::empty();
let message = header.signer_signature_hash().0;

// Sign in order [signer_0, signer_2, signer_1] — out of order at position 3
let signer_signature = [0, 2, 1]
.iter()
.map(|&i| {
signers[i]
.0
.sign(&message)
.expect("Failed to sign block sighash")
})
.collect::<Vec<_>>();

header.signer_signature = signer_signature;

match header.verify_signer_signatures(&reward_set) {
Ok(_) => panic!("Expected out of order signatures to fail"),
Err(ChainstateError::InvalidStacksBlock(msg)) => {
assert!(msg.contains("out of order"));
}
_ => panic!("Expected InvalidStacksBlock error"),
}
}

#[test]
pub fn test_compute_voting_weight_threshold() {
assert_eq!(
Expand Down
Loading