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

stacks-network / stacks-core / 25904007932-1

15 May 2026 06:31AM UTC coverage: 47.459% (-38.5%) from 85.959%
25904007932-1

Pull #7210

github

869a54
web-flow
Merge 27877974d into 1c7b8e6ac
Pull Request #7210: [wip] epoch 4 release branch

36 of 53 new or added lines in 1 file covered. (67.92%)

88645 existing lines in 346 files now uncovered.

104136 of 219422 relevant lines covered (47.46%)

12897381.15 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,676✔
28
        BurnchainOpSigner {
25,676✔
29
            secret_key,
25,676✔
30
            is_disposed: false,
25,676✔
31
        }
25,676✔
32
    }
25,676✔
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.
UNCOV
41
    pub fn get_secret_key_as_wif(&self) -> String {
×
UNCOV
42
        let hex_encoded = self.secret_key.to_hex();
×
UNCOV
43
        let mut as_bytes = hex_bytes(&hex_encoded).unwrap();
×
UNCOV
44
        as_bytes.insert(0, 0x80);
×
UNCOV
45
        stacks_common::address::b58::check_encode_slice(&as_bytes)
×
UNCOV
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.
UNCOV
53
    pub fn get_secret_key_as_hex(&self) -> String {
×
UNCOV
54
        self.secret_key.to_hex()
×
UNCOV
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 {
29,104✔
63
        Secp256k1PublicKey::from_private(&self.secret_key)
29,104✔
64
    }
29,104✔
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,927✔
79
        if self.is_disposed {
17,927✔
UNCOV
80
            debug!("Signer is disposed");
×
UNCOV
81
            return None;
×
82
        }
17,927✔
83

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

92
        Some(signature)
17,927✔
93
    }
17,927✔
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,961✔
99
        self.is_disposed = true;
8,961✔
100
    }
8,961✔
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]
UNCOV
128
    fn test_get_secret_key_as_wif() {
×
UNCOV
129
        let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
×
UNCOV
130
        let expected_wif = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
×
131

UNCOV
132
        let secret = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
×
UNCOV
133
        let op_signer = BurnchainOpSigner::new(secret);
×
UNCOV
134
        assert_eq!(expected_wif, &op_signer.get_secret_key_as_wif());
×
UNCOV
135
    }
×
136

137
    #[test]
UNCOV
138
    fn test_get_secret_key_as_hex() {
×
UNCOV
139
        let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
×
UNCOV
140
        let expected_hex = priv_key_hex;
×
141

UNCOV
142
        let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
×
UNCOV
143
        let op_signer = BurnchainOpSigner::new(secp_k);
×
UNCOV
144
        assert_eq!(expected_hex, op_signer.get_secret_key_as_hex());
×
UNCOV
145
    }
×
146

147
    #[test]
UNCOV
148
    fn test_get_public_key() {
×
UNCOV
149
        let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
×
UNCOV
150
        let expected_hex = "04d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645cd85228a6fb29940e858e7e55842ae2bd115d1ed7cc0e82d934e929c97648cb0a";
×
151

UNCOV
152
        let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
×
UNCOV
153
        let mut op_signer = BurnchainOpSigner::new(secp_k);
×
UNCOV
154
        assert_eq!(expected_hex, op_signer.get_public_key().to_hex());
×
UNCOV
155
    }
×
156

157
    #[test]
UNCOV
158
    fn test_sign_message_ok() {
×
UNCOV
159
        let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
×
UNCOV
160
        let message = &[0u8; 32];
×
UNCOV
161
        let expected_msg_sig = "00b911e6cf9c49b738c4a0f5e33c003fa5b74a00ddc68e574e9f1c3504f6ba7e84275fd62773978cc8165f345cc3f691cf68be274213d552e79af39998df61273f";
×
162

UNCOV
163
        let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
×
UNCOV
164
        let mut op_signer = BurnchainOpSigner::new(secp_k);
×
165

UNCOV
166
        let msg_sig = op_signer
×
UNCOV
167
            .sign_message(message)
×
UNCOV
168
            .expect("Message should be signed!");
×
169

UNCOV
170
        assert_eq!(expected_msg_sig, msg_sig.to_hex());
×
UNCOV
171
    }
×
172

173
    #[test]
UNCOV
174
    fn test_sign_message_fails_due_to_hash_length() {
×
UNCOV
175
        let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
×
UNCOV
176
        let message = &[0u8; 20];
×
177

UNCOV
178
        let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
×
UNCOV
179
        let mut op_signer = BurnchainOpSigner::new(secp_k);
×
180

UNCOV
181
        let result = op_signer.sign_message(message);
×
UNCOV
182
        assert!(result.is_none());
×
UNCOV
183
    }
×
184

185
    #[test]
UNCOV
186
    fn test_sign_message_fails_due_to_disposal() {
×
UNCOV
187
        let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
×
UNCOV
188
        let message = &[0u8; 32];
×
189

UNCOV
190
        let secp_k = Secp256k1PrivateKey::from_hex(priv_key_hex).unwrap();
×
UNCOV
191
        let mut op_signer = BurnchainOpSigner::new(secp_k);
×
192

UNCOV
193
        op_signer.dispose();
×
194

UNCOV
195
        let result = op_signer.sign_message(message);
×
UNCOV
196
        assert!(result.is_none());
×
UNCOV
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