• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 23757460166

30 Mar 2026 05:08PM UTC coverage: 46.858% (-38.9%) from 85.712%
23757460166

Pull #7058

github

cb13d9
web-flow
Merge 5b10bfbb9 into 7a9774e50
Pull Request #7058: test: fix flakiness in `check_capitulate_miner_view`

101943 of 217556 relevant lines covered (46.86%)

12736976.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

28.41
/stacks-node/src/operations.rs
1
use stacks::burnchains::PrivateKey;
2
use stacks_common::util::hash::hex_bytes;
3
use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey};
4

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).
10
pub struct BurnchainOpSigner {
11
    /// The Secp256k1 private key used for signing operations.
12
    secret_key: Secp256k1PrivateKey,
13
    /// Indicates whether the signer has been disposed and can no longer be used for signing.
14
    is_disposed: bool,
15
}
16

17
impl 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 {
25,570✔
28
        BurnchainOpSigner {
25,570✔
29
            secret_key,
25,570✔
30
            is_disposed: false,
25,570✔
31
        }
25,570✔
32
    }
25,570✔
33

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 {
×
42
        let hex_encoded = self.secret_key.to_hex();
×
43
        let mut as_bytes = hex_bytes(&hex_encoded).unwrap();
×
44
        as_bytes.insert(0, 0x80);
×
45
        stacks_common::address::b58::check_encode_slice(&as_bytes)
×
46
    }
×
47

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 {
×
54
        self.secret_key.to_hex()
×
55
    }
×
56

57
    /// Derives and returns the public key associated with the private key.
58
    ///
59
    /// # Returns
60
    ///
61
    /// A `Secp256k1PublicKey` corresponding to the private key.
62
    pub fn get_public_key(&mut self) -> Secp256k1PublicKey {
27,908✔
63
        Secp256k1PublicKey::from_private(&self.secret_key)
27,908✔
64
    }
27,908✔
65

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.
78
    pub fn sign_message(&mut self, hash: &[u8]) -> Option<MessageSignature> {
17,145✔
79
        if self.is_disposed {
17,145✔
80
            debug!("Signer is disposed");
×
81
            return None;
×
82
        }
17,145✔
83

84
        let signature = match self.secret_key.sign(hash) {
17,145✔
85
            Ok(r) => r,
17,145✔
86
            Err(e) => {
×
87
                debug!("Secret key error: {e:?}");
×
88
                return None;
×
89
            }
90
        };
91

92
        Some(signature)
17,145✔
93
    }
17,145✔
94

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.
98
    pub fn dispose(&mut self) {
8,570✔
99
        self.is_disposed = true;
8,570✔
100
    }
8,570✔
101
}
102

103
/// Test-only utilities for `BurnchainOpSigner`
104
#[cfg(any(test, feature = "testing"))]
105
impl BurnchainOpSigner {
106
    /// Returns `true` if the signer has been disposed.
107
    ///
108
    /// This is useful in tests to assert that disposal behavior is working as expected.
109
    pub fn is_disposed(&self) -> bool {
13✔
110
        self.is_disposed
13✔
111
    }
13✔
112

113
    /// Returns a new `BurnchainOpSigner` instance using the same secret key,
114
    /// but with `is_disposed` set to `false` and `is_one_off` set to `false`.
115
    ///
116
    /// This is useful in testing scenarios where you need a fresh, undisposed copy
117
    /// of a signer without recreating the private key.
118
    pub fn undisposed(&self) -> Self {
5✔
119
        Self::new(self.secret_key.clone())
5✔
120
    }
5✔
121
}
122

123
#[cfg(test)]
124
mod tests {
125
    use super::*;
126

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
    }
×
136

137
    #[test]
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());
×
197
    }
×
198
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc