Skip to content

Commit 2153540

Browse files
authored
Merge pull request #6249 from fdefelici/refactor/clean-op-signer
refactor: clean op signer
2 parents 5cea7f9 + ea2c239 commit 2153540

File tree

8 files changed

+142
-47
lines changed

8 files changed

+142
-47
lines changed

stacks-node/src/burnchains/bitcoin_regtest_controller.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3141,7 +3141,6 @@ mod tests {
31413141
"9e446f6b0c6a96cf2190e54bcd5a8569c3e386f091605499464389b8d4e0bfc201",
31423142
)
31433143
.unwrap(),
3144-
false,
31453144
);
31463145
assert!(btc_controller.serialize_tx(
31473146
StacksEpochId::Epoch25,

stacks-node/src/keychain.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ impl Keychain {
228228

229229
/// Create a BurnchainOpSigner representation of this keychain
230230
pub fn generate_op_signer(&self) -> BurnchainOpSigner {
231-
BurnchainOpSigner::new(self.get_secret_key(), false)
231+
BurnchainOpSigner::new(self.get_secret_key())
232232
}
233233
}
234234

@@ -451,7 +451,7 @@ mod tests {
451451
}
452452

453453
pub fn generate_op_signer(&self) -> BurnchainOpSigner {
454-
BurnchainOpSigner::new(self.secret_keys[0], false)
454+
BurnchainOpSigner::new(self.secret_keys[0])
455455
}
456456
}
457457

stacks-node/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,11 +366,11 @@ fn main() {
366366
let keychain = Keychain::default(seed);
367367
println!(
368368
"Hex formatted secret key: {}",
369-
keychain.generate_op_signer().get_sk_as_hex()
369+
keychain.generate_op_signer().get_secret_key_as_hex()
370370
);
371371
println!(
372372
"WIF formatted secret key: {}",
373-
keychain.generate_op_signer().get_sk_as_wif()
373+
keychain.generate_op_signer().get_secret_key_as_wif()
374374
);
375375
return;
376376
}

stacks-node/src/operations.rs

Lines changed: 122 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,79 @@ use stacks::burnchains::PrivateKey;
22
use stacks_common::util::hash::hex_bytes;
33
use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey};
44

5+
/// A signer used for burnchain operations, which manages a private key and provides
6+
/// functionality to derive public keys, sign messages, and export keys in different formats.
7+
///
8+
/// The signer can be "disposed" to prevent further use of the private key (e.g., for security
9+
/// or lifecycle management).
510
pub struct BurnchainOpSigner {
11+
/// The Secp256k1 private key used for signing operations.
612
secret_key: Secp256k1PrivateKey,
7-
is_one_off: bool,
13+
/// Indicates whether the signer has been disposed and can no longer be used for signing.
814
is_disposed: bool,
9-
usages: u8,
1015
}
1116

1217
impl BurnchainOpSigner {
13-
pub fn new(secret_key: Secp256k1PrivateKey, is_one_off: bool) -> BurnchainOpSigner {
18+
/// Creates a new `BurnchainOpSigner` from the given private key.
19+
///
20+
/// # Arguments
21+
///
22+
/// * `secret_key` - A Secp256k1 private key used for signing.
23+
///
24+
/// # Returns
25+
///
26+
/// A new instance of `BurnchainOpSigner`.
27+
pub fn new(secret_key: Secp256k1PrivateKey) -> Self {
1428
BurnchainOpSigner {
1529
secret_key,
16-
usages: 0,
17-
is_one_off,
1830
is_disposed: false,
1931
}
2032
}
2133

22-
pub fn get_sk_as_wif(&self) -> String {
34+
/// Returns the private key encoded as a Wallet Import Format (WIF) string.
35+
///
36+
/// This format is commonly used for exporting private keys in Bitcoin-related systems.
37+
///
38+
/// # Returns
39+
///
40+
/// A WIF-encoded string representation of the private key.
41+
pub fn get_secret_key_as_wif(&self) -> String {
2342
let hex_encoded = self.secret_key.to_hex();
2443
let mut as_bytes = hex_bytes(&hex_encoded).unwrap();
2544
as_bytes.insert(0, 0x80);
2645
stacks_common::address::b58::check_encode_slice(&as_bytes)
2746
}
2847

29-
pub fn get_sk_as_hex(&self) -> String {
48+
/// Returns the private key encoded as a hexadecimal string.
49+
///
50+
/// # Returns
51+
///
52+
/// A hex-encoded string representation of the private key.
53+
pub fn get_secret_key_as_hex(&self) -> String {
3054
self.secret_key.to_hex()
3155
}
3256

57+
/// Derives and returns the public key associated with the private key.
58+
///
59+
/// # Returns
60+
///
61+
/// A `Secp256k1PublicKey` corresponding to the private key.
3362
pub fn get_public_key(&mut self) -> Secp256k1PublicKey {
3463
Secp256k1PublicKey::from_private(&self.secret_key)
3564
}
3665

66+
/// Signs the given message hash using the private key.
67+
///
68+
/// If the signer has been disposed, no signature will be produced.
69+
///
70+
/// # Arguments
71+
///
72+
/// * `hash` - A byte slice representing the hash of the message to sign.
73+
/// This must be exactly **32 bytes** long, as required by the Secp256k1 signing algorithm.
74+
/// # Returns
75+
///
76+
/// `Some(MessageSignature)` if signing was successful, or `None` if the signer
77+
/// is disposed or signing failed.
3778
pub fn sign_message(&mut self, hash: &[u8]) -> Option<MessageSignature> {
3879
if self.is_disposed {
3980
debug!("Signer is disposed");
@@ -47,15 +88,13 @@ impl BurnchainOpSigner {
4788
return None;
4889
}
4990
};
50-
self.usages += 1;
51-
52-
if self.is_one_off && self.usages == 1 {
53-
self.is_disposed = true;
54-
}
5591

5692
Some(signature)
5793
}
5894

95+
/// Marks the signer as disposed, preventing any further signing operations.
96+
///
97+
/// Once disposed, the private key can no longer be used to sign messages.
5998
pub fn dispose(&mut self) {
6099
self.is_disposed = true;
61100
}
@@ -77,26 +116,83 @@ impl BurnchainOpSigner {
77116
/// This is useful in testing scenarios where you need a fresh, undisposed copy
78117
/// of a signer without recreating the private key.
79118
pub fn undisposed(&self) -> Self {
80-
Self::new(self.secret_key, false)
119+
Self::new(self.secret_key)
81120
}
82121
}
83122

84123
#[cfg(test)]
85-
mod test {
86-
use stacks_common::util::secp256k1::Secp256k1PrivateKey;
124+
mod tests {
125+
use super::*;
87126

88-
use super::BurnchainOpSigner;
127+
#[test]
128+
fn test_get_secret_key_as_wif() {
129+
let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
130+
let expected_wif = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
131+
132+
let secret = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
133+
let op_signer = BurnchainOpSigner::new(secret);
134+
assert_eq!(expected_wif, &op_signer.get_secret_key_as_wif());
135+
}
89136

90137
#[test]
91-
fn test_wif() {
92-
let examples = [(
93-
"0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D",
94-
"5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ",
95-
)];
96-
for (secret_key, expected_wif) in examples.iter() {
97-
let secp_k = Secp256k1PrivateKey::from_hex(secret_key).unwrap();
98-
let op_signer = BurnchainOpSigner::new(secp_k, false);
99-
assert_eq!(expected_wif, &op_signer.get_sk_as_wif());
100-
}
138+
fn test_get_secret_key_as_hex() {
139+
let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
140+
let expected_hex = priv_key_hex;
141+
142+
let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
143+
let op_signer = BurnchainOpSigner::new(secp_k);
144+
assert_eq!(expected_hex, op_signer.get_secret_key_as_hex());
145+
}
146+
147+
#[test]
148+
fn test_get_public_key() {
149+
let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
150+
let expected_hex = "04d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645cd85228a6fb29940e858e7e55842ae2bd115d1ed7cc0e82d934e929c97648cb0a";
151+
152+
let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
153+
let mut op_signer = BurnchainOpSigner::new(secp_k);
154+
assert_eq!(expected_hex, op_signer.get_public_key().to_hex());
155+
}
156+
157+
#[test]
158+
fn test_sign_message_ok() {
159+
let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
160+
let message = &[0u8; 32];
161+
let expected_msg_sig = "00b911e6cf9c49b738c4a0f5e33c003fa5b74a00ddc68e574e9f1c3504f6ba7e84275fd62773978cc8165f345cc3f691cf68be274213d552e79af39998df61273f";
162+
163+
let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
164+
let mut op_signer = BurnchainOpSigner::new(secp_k);
165+
166+
let msg_sig = op_signer
167+
.sign_message(message)
168+
.expect("Message should be signed!");
169+
170+
assert_eq!(expected_msg_sig, msg_sig.to_hex());
171+
}
172+
173+
#[test]
174+
fn test_sign_message_fails_due_to_hash_length() {
175+
let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
176+
let message = &[0u8; 20];
177+
178+
let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
179+
let mut op_signer = BurnchainOpSigner::new(secp_k);
180+
181+
let result = op_signer.sign_message(message);
182+
assert!(result.is_none());
183+
}
184+
185+
#[test]
186+
fn test_sign_message_fails_due_to_disposal() {
187+
let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
188+
let message = &[0u8; 32];
189+
190+
let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
191+
let mut op_signer = BurnchainOpSigner::new(secp_k);
192+
193+
op_signer.dispose();
194+
195+
let result = op_signer.sign_message(message);
196+
assert!(result.is_none());
101197
}
102198
}

stacks-node/src/tests/epoch_21.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ fn transition_fixes_bitcoin_rigidity() {
697697
burn_header_hash: BurnchainHeaderHash([0u8; 32]),
698698
};
699699

700-
let mut spender_signer = BurnchainOpSigner::new(spender_sk, false);
700+
let mut spender_signer = BurnchainOpSigner::new(spender_sk);
701701

702702
assert!(
703703
btc_regtest_controller
@@ -849,7 +849,7 @@ fn transition_fixes_bitcoin_rigidity() {
849849
burn_header_hash: BurnchainHeaderHash([0u8; 32]),
850850
};
851851

852-
let mut spender_signer = BurnchainOpSigner::new(spender_sk, false);
852+
let mut spender_signer = BurnchainOpSigner::new(spender_sk);
853853

854854
assert!(
855855
btc_regtest_controller
@@ -923,7 +923,7 @@ fn transition_fixes_bitcoin_rigidity() {
923923
burn_header_hash: BurnchainHeaderHash([0u8; 32]),
924924
};
925925

926-
let mut spender_signer = BurnchainOpSigner::new(spender_2_sk, false);
926+
let mut spender_signer = BurnchainOpSigner::new(spender_2_sk);
927927

928928
btc_regtest_controller
929929
.submit_manual(
@@ -989,7 +989,7 @@ fn transition_fixes_bitcoin_rigidity() {
989989
burn_header_hash: BurnchainHeaderHash([0u8; 32]),
990990
};
991991

992-
let mut spender_signer = BurnchainOpSigner::new(spender_2_sk, false);
992+
let mut spender_signer = BurnchainOpSigner::new(spender_2_sk);
993993

994994
btc_regtest_controller
995995
.submit_manual(

stacks-node/src/tests/nakamoto_integrations.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3553,7 +3553,7 @@ fn vote_for_aggregate_key_burn_op() {
35533553
burn_header_hash: BurnchainHeaderHash::zero(),
35543554
});
35553555

3556-
let mut signer_burnop_signer = BurnchainOpSigner::new(signer_sk, false);
3556+
let mut signer_burnop_signer = BurnchainOpSigner::new(signer_sk);
35573557
assert!(
35583558
btc_regtest_controller
35593559
.submit_operation(
@@ -4739,10 +4739,10 @@ fn burn_ops_integration_test() {
47394739
"reward_cycle" => reward_cycle,
47404740
);
47414741

4742-
let mut signer_burnop_signer_1 = BurnchainOpSigner::new(signer_sk_1, false);
4743-
let mut signer_burnop_signer_2 = BurnchainOpSigner::new(signer_sk_2, false);
4744-
let mut stacker_burnop_signer_1 = BurnchainOpSigner::new(stacker_sk_1, false);
4745-
let mut stacker_burnop_signer_2 = BurnchainOpSigner::new(stacker_sk_2, false);
4742+
let mut signer_burnop_signer_1 = BurnchainOpSigner::new(signer_sk_1);
4743+
let mut signer_burnop_signer_2 = BurnchainOpSigner::new(signer_sk_2);
4744+
let mut stacker_burnop_signer_1 = BurnchainOpSigner::new(stacker_sk_1);
4745+
let mut stacker_burnop_signer_2 = BurnchainOpSigner::new(stacker_sk_2);
47464746

47474747
info!(
47484748
"Before stack-stx op, signer 1 total: {}",

stacks-node/src/tests/neon_integrations.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,7 +2019,7 @@ fn stx_transfer_btc_integration_test() {
20192019
burn_header_hash: BurnchainHeaderHash([0u8; 32]),
20202020
};
20212021

2022-
let mut spender_signer = BurnchainOpSigner::new(spender_sk, false);
2022+
let mut spender_signer = BurnchainOpSigner::new(spender_sk);
20232023

20242024
assert!(
20252025
btc_regtest_controller
@@ -2090,7 +2090,7 @@ fn stx_transfer_btc_integration_test() {
20902090
burn_header_hash: BurnchainHeaderHash([0u8; 32]),
20912091
};
20922092

2093-
let mut spender_signer = BurnchainOpSigner::new(spender_2_sk, false);
2093+
let mut spender_signer = BurnchainOpSigner::new(spender_2_sk);
20942094

20952095
btc_regtest_controller
20962096
.submit_manual(
@@ -2288,7 +2288,7 @@ fn stx_delegate_btc_integration_test() {
22882288
until_burn_height: None,
22892289
};
22902290

2291-
let mut spender_signer = BurnchainOpSigner::new(spender_sk, false);
2291+
let mut spender_signer = BurnchainOpSigner::new(spender_sk);
22922292
assert!(
22932293
btc_regtest_controller
22942294
.submit_operation(
@@ -2656,7 +2656,7 @@ fn stack_stx_burn_op_test() {
26562656
burn_header_hash: BurnchainHeaderHash::zero(),
26572657
});
26582658

2659-
let mut spender_signer_1 = BurnchainOpSigner::new(signer_sk_1, false);
2659+
let mut spender_signer_1 = BurnchainOpSigner::new(signer_sk_1);
26602660
assert!(
26612661
btc_regtest_controller
26622662
.submit_operation(
@@ -2684,7 +2684,7 @@ fn stack_stx_burn_op_test() {
26842684
burn_header_hash: BurnchainHeaderHash::zero(),
26852685
});
26862686

2687-
let mut spender_signer_2 = BurnchainOpSigner::new(signer_sk_2, false);
2687+
let mut spender_signer_2 = BurnchainOpSigner::new(signer_sk_2);
26882688
assert!(
26892689
btc_regtest_controller
26902690
.submit_operation(
@@ -3041,7 +3041,7 @@ fn vote_for_aggregate_key_burn_op_test() {
30413041
burn_header_hash: BurnchainHeaderHash::zero(),
30423042
});
30433043

3044-
let mut spender_signer = BurnchainOpSigner::new(signer_sk, false);
3044+
let mut spender_signer = BurnchainOpSigner::new(signer_sk);
30453045
assert!(
30463046
btc_regtest_controller
30473047
.submit_operation(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3738,7 +3738,7 @@ fn tx_replay_btc_on_stx_invalidation() {
37383738
let num_signers = 5;
37393739
let sender_sk = Secp256k1PrivateKey::from_seed("sender_1".as_bytes());
37403740
let sender_addr = tests::to_addr(&sender_sk);
3741-
let mut sender_burnop_signer = BurnchainOpSigner::new(sender_sk, false);
3741+
let mut sender_burnop_signer = BurnchainOpSigner::new(sender_sk);
37423742
let send_amt = 100;
37433743
let send_fee = 180;
37443744
let recipient_sk = Secp256k1PrivateKey::from_seed("recipient_1".as_bytes());

0 commit comments

Comments
 (0)