Skip to content

Commit bf860f4

Browse files
committed
test(signer): Add check that duplicate signers produce identical signatures and recovered pubkeys
1 parent c301621 commit bf860f4

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

stacks-common/src/util/secp256k1.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use crate::util::hash::{hex_bytes, to_hex};
3838
// per-thread Secp256k1 context
3939
thread_local!(static _secp256k1: Secp256k1<secp256k1::All> = Secp256k1::new());
4040

41-
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
41+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
4242
pub struct Secp256k1PublicKey {
4343
// serde is broken for secp256k1, so do it ourselves
4444
#[serde(

testnet/stacks-node/src/tests/signer/v0.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2982,6 +2982,7 @@ fn duplicate_signers() {
29822982

29832983
// First two signers have same private key
29842984
signer_stacks_private_keys[1] = signer_stacks_private_keys[0];
2985+
let unique_signers = num_signers - 1;
29852986
let duplicate_pubkey = Secp256k1PublicKey::from_private(&signer_stacks_private_keys[0]);
29862987
let duplicate_pubkey_from_copy =
29872988
Secp256k1PublicKey::from_private(&signer_stacks_private_keys[1]);
@@ -3007,5 +3008,53 @@ fn duplicate_signers() {
30073008

30083009
signer_test.mine_and_verify_confirmed_naka_block(timeout, num_signers);
30093010

3011+
info!("------------------------- Read all `BlockResponse::Accepted` messages -------------------------");
3012+
3013+
let mut signer_accepted_responses = vec![];
3014+
let start_polling = Instant::now();
3015+
while start_polling.elapsed() <= timeout {
3016+
std::thread::sleep(Duration::from_secs(1));
3017+
let messages = test_observer::get_stackerdb_chunks()
3018+
.into_iter()
3019+
.flat_map(|chunk| chunk.modified_slots)
3020+
.filter_map(|chunk| {
3021+
SignerMessage::consensus_deserialize(&mut chunk.data.as_slice()).ok()
3022+
})
3023+
.filter_map(|message| match message {
3024+
SignerMessage::BlockResponse(BlockResponse::Accepted(m)) => {
3025+
info!("Message(accepted): {message:?}");
3026+
Some(m)
3027+
}
3028+
_ => {
3029+
debug!("Message(ignored): {message:?}");
3030+
None
3031+
}
3032+
});
3033+
signer_accepted_responses.extend(messages);
3034+
}
3035+
3036+
info!("------------------------- Assert there are {unique_signers} unique signatures and recovered pubkeys -------------------------");
3037+
3038+
// Pick a message hash
3039+
let (selected_sighash, _) = signer_accepted_responses
3040+
.iter()
3041+
.min_by_key(|(sighash, _)| *sighash)
3042+
.copied()
3043+
.expect("No `BlockResponse::Accepted` messages recieved");
3044+
3045+
// Filter only resonses for selected block and collect unique pubkeys and signatures
3046+
let (pubkeys, signatures): (HashSet<_>, HashSet<_>) = signer_accepted_responses
3047+
.into_iter()
3048+
.filter(|(hash, _)| *hash == selected_sighash)
3049+
.map(|(msg, sig)| {
3050+
let pubkey = Secp256k1PublicKey::recover_to_pubkey(msg.bits(), &sig)
3051+
.expect("Failed to recover pubkey");
3052+
(pubkey, sig)
3053+
})
3054+
.unzip();
3055+
3056+
assert_eq!(pubkeys.len(), unique_signers);
3057+
assert_eq!(signatures.len(), unique_signers);
3058+
30103059
signer_test.shutdown();
30113060
}

0 commit comments

Comments
 (0)