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

stacks-network / stacks-core / 23943270448

03 Apr 2026 10:32AM UTC coverage: 77.559% (-8.2%) from 85.712%
23943270448

Pull #7077

github

52f01d
web-flow
Merge fa3f939ed into c529ad924
Pull Request #7077: feat: add burnchain DB copy and validation

3654 of 4220 new or added lines in 18 files covered. (86.59%)

19324 existing lines in 182 files now uncovered.

171991 of 221755 relevant lines covered (77.56%)

7658447.9 hits per line

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

93.18
/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 {
50✔
28
        BurnchainOpSigner {
50✔
29
            secret_key,
50✔
30
            is_disposed: false,
50✔
31
        }
50✔
32
    }
50✔
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 {
1✔
42
        let hex_encoded = self.secret_key.to_hex();
1✔
43
        let mut as_bytes = hex_bytes(&hex_encoded).unwrap();
1✔
44
        as_bytes.insert(0, 0x80);
1✔
45
        stacks_common::address::b58::check_encode_slice(&as_bytes)
1✔
46
    }
1✔
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 {
1✔
54
        self.secret_key.to_hex()
1✔
55
    }
1✔
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 {
5✔
63
        Secp256k1PublicKey::from_private(&self.secret_key)
5✔
64
    }
5✔
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> {
9✔
79
        if self.is_disposed {
9✔
80
            debug!("Signer is disposed");
1✔
81
            return None;
1✔
82
        }
8✔
83

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

92
        Some(signature)
7✔
93
    }
9✔
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) {
2✔
99
        self.is_disposed = true;
2✔
100
    }
2✔
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.
UNCOV
109
    pub fn is_disposed(&self) -> bool {
×
UNCOV
110
        self.is_disposed
×
UNCOV
111
    }
×
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.
UNCOV
118
    pub fn undisposed(&self) -> Self {
×
UNCOV
119
        Self::new(self.secret_key.clone())
×
UNCOV
120
    }
×
121
}
122

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

127
    #[test]
128
    fn test_get_secret_key_as_wif() {
1✔
129
        let priv_key_hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
1✔
130
        let expected_wif = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
1✔
131

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

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

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

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

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

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

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

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

170
        assert_eq!(expected_msg_sig, msg_sig.to_hex());
1✔
171
    }
1✔
172

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

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

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

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

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

193
        op_signer.dispose();
1✔
194

195
        let result = op_signer.sign_message(message);
1✔
196
        assert!(result.is_none());
1✔
197
    }
1✔
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