@@ -2,38 +2,79 @@ use stacks::burnchains::PrivateKey;
2
2
use stacks_common:: util:: hash:: hex_bytes;
3
3
use stacks_common:: util:: secp256k1:: { MessageSignature , Secp256k1PrivateKey , Secp256k1PublicKey } ;
4
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).
5
10
pub struct BurnchainOpSigner {
11
+ /// The Secp256k1 private key used for signing operations.
6
12
secret_key : Secp256k1PrivateKey ,
7
- is_one_off : bool ,
13
+ /// Indicates whether the signer has been disposed and can no longer be used for signing.
8
14
is_disposed : bool ,
9
- usages : u8 ,
10
15
}
11
16
12
17
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 {
14
28
BurnchainOpSigner {
15
29
secret_key,
16
- usages : 0 ,
17
- is_one_off,
18
30
is_disposed : false ,
19
31
}
20
32
}
21
33
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 {
23
42
let hex_encoded = self . secret_key . to_hex ( ) ;
24
43
let mut as_bytes = hex_bytes ( & hex_encoded) . unwrap ( ) ;
25
44
as_bytes. insert ( 0 , 0x80 ) ;
26
45
stacks_common:: address:: b58:: check_encode_slice ( & as_bytes)
27
46
}
28
47
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 {
30
54
self . secret_key . to_hex ( )
31
55
}
32
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.
33
62
pub fn get_public_key ( & mut self ) -> Secp256k1PublicKey {
34
63
Secp256k1PublicKey :: from_private ( & self . secret_key )
35
64
}
36
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.
37
78
pub fn sign_message ( & mut self , hash : & [ u8 ] ) -> Option < MessageSignature > {
38
79
if self . is_disposed {
39
80
debug ! ( "Signer is disposed" ) ;
@@ -47,15 +88,13 @@ impl BurnchainOpSigner {
47
88
return None ;
48
89
}
49
90
} ;
50
- self . usages += 1 ;
51
-
52
- if self . is_one_off && self . usages == 1 {
53
- self . is_disposed = true ;
54
- }
55
91
56
92
Some ( signature)
57
93
}
58
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.
59
98
pub fn dispose ( & mut self ) {
60
99
self . is_disposed = true ;
61
100
}
@@ -77,26 +116,83 @@ impl BurnchainOpSigner {
77
116
/// This is useful in testing scenarios where you need a fresh, undisposed copy
78
117
/// of a signer without recreating the private key.
79
118
pub fn undisposed ( & self ) -> Self {
80
- Self :: new ( self . secret_key , false )
119
+ Self :: new ( self . secret_key )
81
120
}
82
121
}
83
122
84
123
#[ cfg( test) ]
85
- mod test {
86
- use stacks_common :: util :: secp256k1 :: Secp256k1PrivateKey ;
124
+ mod tests {
125
+ use super :: * ;
87
126
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
+ }
89
136
90
137
#[ 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( ) ) ;
101
197
}
102
198
}
0 commit comments