@@ -34,16 +34,27 @@ use blockstack_lib::chainstate::nakamoto::signer_set::NakamotoSigners;
34
34
use blockstack_lib:: chainstate:: nakamoto:: NakamotoBlock ;
35
35
use blockstack_lib:: chainstate:: stacks:: events:: StackerDBChunksEvent ;
36
36
use blockstack_lib:: chainstate:: stacks:: StacksTransaction ;
37
+ use blockstack_lib:: net:: api:: getinfo:: RPCPeerInfoData ;
37
38
use blockstack_lib:: net:: api:: postblock_proposal:: {
38
39
BlockValidateReject , BlockValidateResponse , ValidateRejectCode ,
39
40
} ;
40
41
use blockstack_lib:: util_lib:: boot:: boot_code_id;
42
+ use blockstack_lib:: util_lib:: signed_structured_data:: {
43
+ make_structured_data_domain, structured_data_message_hash,
44
+ } ;
45
+ use clarity:: types:: chainstate:: {
46
+ BlockHeaderHash , ConsensusHash , StacksPrivateKey , StacksPublicKey ,
47
+ } ;
48
+ use clarity:: types:: PrivateKey ;
49
+ use clarity:: util:: hash:: Sha256Sum ;
41
50
use clarity:: util:: retry:: BoundReader ;
42
51
use clarity:: util:: secp256k1:: MessageSignature ;
43
52
use clarity:: vm:: types:: serialization:: SerializationError ;
44
- use clarity:: vm:: types:: QualifiedContractIdentifier ;
53
+ use clarity:: vm:: types:: { QualifiedContractIdentifier , TupleData } ;
54
+ use clarity:: vm:: Value ;
45
55
use hashbrown:: { HashMap , HashSet } ;
46
56
use serde:: { Deserialize , Serialize } ;
57
+ use sha2:: { Digest , Sha512_256 } ;
47
58
use stacks_common:: codec:: {
48
59
read_next, read_next_at_most, read_next_exact, write_next, Error as CodecError ,
49
60
StacksMessageCodec ,
@@ -55,6 +66,7 @@ use tiny_http::{
55
66
} ;
56
67
57
68
use crate :: http:: { decode_http_body, decode_http_request} ;
69
+ use crate :: stacks_common:: types:: PublicKey ;
58
70
use crate :: {
59
71
BlockProposal , EventError , MessageSlotID as MessageSlotIDTrait ,
60
72
SignerMessage as SignerMessageTrait ,
@@ -65,7 +77,9 @@ define_u8_enum!(
65
77
/// the contract index in the signers contracts (i.e., X in signers-0-X)
66
78
MessageSlotID {
67
79
/// Block Response message from signers
68
- BlockResponse = 1
80
+ BlockResponse = 1 ,
81
+ /// Mock Signature message from Epoch 2.5 signers
82
+ MockSignature = 2
69
83
} ) ;
70
84
71
85
define_u8_enum ! (
@@ -100,7 +114,9 @@ SignerMessageTypePrefix {
100
114
/// Block Response message from signers
101
115
BlockResponse = 1 ,
102
116
/// Block Pushed message from miners
103
- BlockPushed = 2
117
+ BlockPushed = 2 ,
118
+ /// Mock Signature message from Epoch 2.5 signers
119
+ MockSignature = 3
104
120
} ) ;
105
121
106
122
#[ cfg_attr( test, mutants:: skip) ]
@@ -143,6 +159,7 @@ impl From<&SignerMessage> for SignerMessageTypePrefix {
143
159
SignerMessage :: BlockProposal ( _) => SignerMessageTypePrefix :: BlockProposal ,
144
160
SignerMessage :: BlockResponse ( _) => SignerMessageTypePrefix :: BlockResponse ,
145
161
SignerMessage :: BlockPushed ( _) => SignerMessageTypePrefix :: BlockPushed ,
162
+ SignerMessage :: MockSignature ( _) => SignerMessageTypePrefix :: MockSignature ,
146
163
}
147
164
}
148
165
}
@@ -156,6 +173,8 @@ pub enum SignerMessage {
156
173
BlockResponse ( BlockResponse ) ,
157
174
/// A block pushed from miners to the signers set
158
175
BlockPushed ( NakamotoBlock ) ,
176
+ /// A mock signature from the epoch 2.5 signers
177
+ MockSignature ( MockSignature ) ,
159
178
}
160
179
161
180
impl SignerMessage {
@@ -167,6 +186,7 @@ impl SignerMessage {
167
186
match self {
168
187
Self :: BlockProposal ( _) | Self :: BlockPushed ( _) => None ,
169
188
Self :: BlockResponse ( _) => Some ( MessageSlotID :: BlockResponse ) ,
189
+ Self :: MockSignature ( _) => Some ( MessageSlotID :: MockSignature ) ,
170
190
}
171
191
}
172
192
}
@@ -180,6 +200,7 @@ impl StacksMessageCodec for SignerMessage {
180
200
SignerMessage :: BlockProposal ( block_proposal) => block_proposal. consensus_serialize ( fd) ,
181
201
SignerMessage :: BlockResponse ( block_response) => block_response. consensus_serialize ( fd) ,
182
202
SignerMessage :: BlockPushed ( block) => block. consensus_serialize ( fd) ,
203
+ SignerMessage :: MockSignature ( signature) => signature. consensus_serialize ( fd) ,
183
204
} ?;
184
205
Ok ( ( ) )
185
206
}
@@ -201,6 +222,10 @@ impl StacksMessageCodec for SignerMessage {
201
222
let block = StacksMessageCodec :: consensus_deserialize ( fd) ?;
202
223
SignerMessage :: BlockPushed ( block)
203
224
}
225
+ SignerMessageTypePrefix :: MockSignature => {
226
+ let signature = StacksMessageCodec :: consensus_deserialize ( fd) ?;
227
+ SignerMessage :: MockSignature ( signature)
228
+ }
204
229
} ;
205
230
Ok ( message)
206
231
}
@@ -214,6 +239,178 @@ pub trait StacksMessageCodecExtensions: Sized {
214
239
fn inner_consensus_deserialize < R : Read > ( fd : & mut R ) -> Result < Self , CodecError > ;
215
240
}
216
241
242
+ /// A snapshot of the signer view of the stacks node to be used for mock signing.
243
+ #[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
244
+ pub struct MockSignData {
245
+ /// The stacks tip consensus hash at the time of the mock signature
246
+ pub stacks_tip_consensus_hash : ConsensusHash ,
247
+ /// The stacks tip header hash at the time of the mock signature
248
+ pub stacks_tip : BlockHeaderHash ,
249
+ /// The server version
250
+ pub server_version : String ,
251
+ /// The burn block height that triggered the mock signature
252
+ pub burn_block_height : u64 ,
253
+ /// The burn block height of the peer view at the time of the mock signature. Note
254
+ /// that this may be different from the burn_block_height if the peer view is stale.
255
+ pub peer_burn_block_height : u64 ,
256
+ /// The POX consensus hash at the time of the mock signature
257
+ pub pox_consensus : ConsensusHash ,
258
+ /// The chain id for the mock signature
259
+ pub chain_id : u32 ,
260
+ }
261
+
262
+ impl MockSignData {
263
+ fn new ( peer_view : RPCPeerInfoData , burn_block_height : u64 , chain_id : u32 ) -> Self {
264
+ Self {
265
+ stacks_tip_consensus_hash : peer_view. stacks_tip_consensus_hash ,
266
+ stacks_tip : peer_view. stacks_tip ,
267
+ server_version : peer_view. server_version ,
268
+ burn_block_height,
269
+ peer_burn_block_height : peer_view. burn_block_height ,
270
+ pox_consensus : peer_view. pox_consensus ,
271
+ chain_id,
272
+ }
273
+ }
274
+ }
275
+
276
+ impl StacksMessageCodec for MockSignData {
277
+ fn consensus_serialize < W : Write > ( & self , fd : & mut W ) -> Result < ( ) , CodecError > {
278
+ write_next ( fd, self . stacks_tip_consensus_hash . as_bytes ( ) ) ?;
279
+ write_next ( fd, & self . stacks_tip ) ?;
280
+ write_next ( fd, & ( self . server_version . as_bytes ( ) . len ( ) as u8 ) ) ?;
281
+ fd. write_all ( self . server_version . as_bytes ( ) )
282
+ . map_err ( CodecError :: WriteError ) ?;
283
+ write_next ( fd, & self . burn_block_height ) ?;
284
+ write_next ( fd, & self . peer_burn_block_height ) ?;
285
+ write_next ( fd, & self . pox_consensus ) ?;
286
+ write_next ( fd, & self . chain_id ) ?;
287
+ Ok ( ( ) )
288
+ }
289
+
290
+ fn consensus_deserialize < R : Read > ( fd : & mut R ) -> Result < Self , CodecError > {
291
+ let stacks_tip_consensus_hash = read_next :: < ConsensusHash , _ > ( fd) ?;
292
+ let stacks_tip = read_next :: < BlockHeaderHash , _ > ( fd) ?;
293
+ let len_byte: u8 = read_next ( fd) ?;
294
+ let mut bytes = vec ! [ 0u8 ; len_byte as usize ] ;
295
+ fd. read_exact ( & mut bytes) . map_err ( CodecError :: ReadError ) ?;
296
+ // must encode a valid string
297
+ let server_version = String :: from_utf8 ( bytes) . map_err ( |_e| {
298
+ CodecError :: DeserializeError (
299
+ "Failed to parse server version name: could not contruct from utf8" . to_string ( ) ,
300
+ )
301
+ } ) ?;
302
+ let burn_block_height = read_next :: < u64 , _ > ( fd) ?;
303
+ let peer_burn_block_height = read_next :: < u64 , _ > ( fd) ?;
304
+ let pox_consensus = read_next :: < ConsensusHash , _ > ( fd) ?;
305
+ let chain_id = read_next :: < u32 , _ > ( fd) ?;
306
+ Ok ( Self {
307
+ stacks_tip_consensus_hash,
308
+ stacks_tip,
309
+ server_version,
310
+ burn_block_height,
311
+ peer_burn_block_height,
312
+ pox_consensus,
313
+ chain_id,
314
+ } )
315
+ }
316
+ }
317
+
318
+ /// A mock signature for the stacks node to be used for mock signing.
319
+ /// This is only used by Epoch 2.5 signers to simulate the signing of a block for every sortition.
320
+ #[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
321
+ pub struct MockSignature {
322
+ /// The signature of the mock signature
323
+ signature : MessageSignature ,
324
+ /// The data that was signed across
325
+ pub sign_data : MockSignData ,
326
+ }
327
+
328
+ impl MockSignature {
329
+ /// Create a new mock sign data struct from the provided peer info, burn block height, chain id, and private key.
330
+ pub fn new (
331
+ peer_view : RPCPeerInfoData ,
332
+ burn_block_height : u64 ,
333
+ chain_id : u32 ,
334
+ stacks_private_key : & StacksPrivateKey ,
335
+ ) -> Self {
336
+ let mut sig = Self {
337
+ signature : MessageSignature :: empty ( ) ,
338
+ sign_data : MockSignData :: new ( peer_view, burn_block_height, chain_id) ,
339
+ } ;
340
+ sig. sign ( stacks_private_key)
341
+ . expect ( "Failed to sign MockSignature" ) ;
342
+ sig
343
+ }
344
+
345
+ /// The signature hash for the mock signature
346
+ pub fn signature_hash ( & self ) -> Sha256Sum {
347
+ let domain_tuple =
348
+ make_structured_data_domain ( "mock-signer" , "1.0.0" , self . sign_data . chain_id ) ;
349
+ let data_tuple = Value :: Tuple (
350
+ TupleData :: from_data ( vec ! [
351
+ (
352
+ "stacks-tip-consensus-hash" . into( ) ,
353
+ Value :: buff_from( self . sign_data. stacks_tip_consensus_hash. as_bytes( ) . into( ) )
354
+ . unwrap( ) ,
355
+ ) ,
356
+ (
357
+ "stacks-tip" . into( ) ,
358
+ Value :: buff_from( self . sign_data. stacks_tip. as_bytes( ) . into( ) ) . unwrap( ) ,
359
+ ) ,
360
+ (
361
+ "server-version" . into( ) ,
362
+ Value :: string_ascii_from_bytes( self . sign_data. server_version. clone( ) . into( ) )
363
+ . unwrap( ) ,
364
+ ) ,
365
+ (
366
+ "burn-block-height" . into( ) ,
367
+ Value :: UInt ( self . sign_data. burn_block_height. into( ) ) ,
368
+ ) ,
369
+ (
370
+ "pox-consensus" . into( ) ,
371
+ Value :: buff_from( self . sign_data. pox_consensus. as_bytes( ) . into( ) ) . unwrap( ) ,
372
+ ) ,
373
+ ] )
374
+ . expect ( "Error creating signature hash" ) ,
375
+ ) ;
376
+ structured_data_message_hash ( data_tuple, domain_tuple)
377
+ }
378
+
379
+ /// Sign the mock signature and set the internal signature field
380
+ fn sign ( & mut self , private_key : & StacksPrivateKey ) -> Result < ( ) , String > {
381
+ let signature_hash = self . signature_hash ( ) ;
382
+ self . signature = private_key. sign ( signature_hash. as_bytes ( ) ) ?;
383
+ Ok ( ( ) )
384
+ }
385
+ /// Verify the mock signature against the provided public key
386
+ pub fn verify ( & self , public_key : & StacksPublicKey ) -> Result < bool , String > {
387
+ if self . signature == MessageSignature :: empty ( ) {
388
+ return Ok ( false ) ;
389
+ }
390
+ let signature_hash = self . signature_hash ( ) ;
391
+ public_key
392
+ . verify ( & signature_hash. 0 , & self . signature )
393
+ . map_err ( |e| e. to_string ( ) )
394
+ }
395
+ }
396
+
397
+ impl StacksMessageCodec for MockSignature {
398
+ fn consensus_serialize < W : Write > ( & self , fd : & mut W ) -> Result < ( ) , CodecError > {
399
+ write_next ( fd, & self . signature ) ?;
400
+ self . sign_data . consensus_serialize ( fd) ?;
401
+ Ok ( ( ) )
402
+ }
403
+
404
+ fn consensus_deserialize < R : Read > ( fd : & mut R ) -> Result < Self , CodecError > {
405
+ let signature = read_next :: < MessageSignature , _ > ( fd) ?;
406
+ let sign_data = read_next :: < MockSignData , _ > ( fd) ?;
407
+ Ok ( Self {
408
+ signature,
409
+ sign_data,
410
+ } )
411
+ }
412
+ }
413
+
217
414
define_u8_enum ! (
218
415
/// Enum representing the reject code type prefix
219
416
RejectCodeTypePrefix {
@@ -507,7 +704,9 @@ mod test {
507
704
TransactionPostConditionMode , TransactionSmartContract , TransactionVersion ,
508
705
} ;
509
706
use blockstack_lib:: util_lib:: strings:: StacksString ;
707
+ use clarity:: consts:: CHAIN_ID_MAINNET ;
510
708
use clarity:: types:: chainstate:: { ConsensusHash , StacksBlockId , TrieHash } ;
709
+ use clarity:: types:: PrivateKey ;
511
710
use clarity:: util:: hash:: MerkleTree ;
512
711
use clarity:: util:: secp256k1:: MessageSignature ;
513
712
use rand:: { thread_rng, Rng , RngCore } ;
@@ -622,4 +821,74 @@ mod test {
622
821
. expect ( "Failed to deserialize SignerMessage" ) ;
623
822
assert_eq ! ( signer_message, deserialized_signer_message) ;
624
823
}
824
+
825
+ fn random_mock_sign_data ( ) -> MockSignData {
826
+ let stacks_tip_consensus_byte: u8 = thread_rng ( ) . gen ( ) ;
827
+ let stacks_tip_byte: u8 = thread_rng ( ) . gen ( ) ;
828
+ let pox_consensus_byte: u8 = thread_rng ( ) . gen ( ) ;
829
+ let chain_byte: u8 = thread_rng ( ) . gen_range ( 0 ..=1 ) ;
830
+ let chain_id = if chain_byte == 1 {
831
+ CHAIN_ID_TESTNET
832
+ } else {
833
+ CHAIN_ID_MAINNET
834
+ } ;
835
+ MockSignData {
836
+ stacks_tip_consensus_hash : ConsensusHash ( [ stacks_tip_consensus_byte; 20 ] ) ,
837
+ stacks_tip : BlockHeaderHash ( [ stacks_tip_byte; 32 ] ) ,
838
+ server_version : "0.0.0" . to_string ( ) ,
839
+ burn_block_height : thread_rng ( ) . next_u64 ( ) ,
840
+ peer_burn_block_height : thread_rng ( ) . next_u64 ( ) ,
841
+ pox_consensus : ConsensusHash ( [ pox_consensus_byte; 20 ] ) ,
842
+ chain_id,
843
+ }
844
+ }
845
+
846
+ #[ test]
847
+ fn verify_sign_mock_signature ( ) {
848
+ let private_key = StacksPrivateKey :: new ( ) ;
849
+ let public_key = StacksPublicKey :: from_private ( & private_key) ;
850
+
851
+ let bad_private_key = StacksPrivateKey :: new ( ) ;
852
+ let bad_public_key = StacksPublicKey :: from_private ( & bad_private_key) ;
853
+
854
+ let mut mock_signature = MockSignature {
855
+ signature : MessageSignature :: empty ( ) ,
856
+ sign_data : random_mock_sign_data ( ) ,
857
+ } ;
858
+ assert ! ( !mock_signature
859
+ . verify( & public_key)
860
+ . expect( "Failed to verify MockSignature" ) ) ;
861
+
862
+ mock_signature
863
+ . sign ( & private_key)
864
+ . expect ( "Failed to sign MockSignature" ) ;
865
+
866
+ assert ! ( mock_signature
867
+ . verify( & public_key)
868
+ . expect( "Failed to verify MockSignature" ) ) ;
869
+ assert ! ( !mock_signature
870
+ . verify( & bad_public_key)
871
+ . expect( "Failed to verify MockSignature" ) ) ;
872
+ }
873
+
874
+ #[ test]
875
+ fn serde_mock_signature ( ) {
876
+ let mock_signature = MockSignature {
877
+ signature : MessageSignature :: empty ( ) ,
878
+ sign_data : random_mock_sign_data ( ) ,
879
+ } ;
880
+ let serialized_signature = mock_signature. serialize_to_vec ( ) ;
881
+ let deserialized_signature = read_next :: < MockSignature , _ > ( & mut & serialized_signature[ ..] )
882
+ . expect ( "Failed to deserialize MockSignature" ) ;
883
+ assert_eq ! ( mock_signature, deserialized_signature) ;
884
+ }
885
+
886
+ #[ test]
887
+ fn serde_sign_data ( ) {
888
+ let sign_data = random_mock_sign_data ( ) ;
889
+ let serialized_data = sign_data. serialize_to_vec ( ) ;
890
+ let deserialized_data = read_next :: < MockSignData , _ > ( & mut & serialized_data[ ..] )
891
+ . expect ( "Failed to deserialize MockSignData" ) ;
892
+ assert_eq ! ( sign_data, deserialized_data) ;
893
+ }
625
894
}
0 commit comments