@@ -42,6 +42,7 @@ use blockstack_lib::util_lib::boot::boot_code_id;
42
42
use blockstack_lib:: util_lib:: signed_structured_data:: {
43
43
make_structured_data_domain, structured_data_message_hash,
44
44
} ;
45
+ use clarity:: consts:: { CHAIN_ID_MAINNET , CHAIN_ID_TESTNET } ;
45
46
use clarity:: types:: chainstate:: {
46
47
BlockHeaderHash , ConsensusHash , StacksPrivateKey , StacksPublicKey ,
47
48
} ;
@@ -525,7 +526,9 @@ RejectCodeTypePrefix {
525
526
/// The block was rejected due to no sortition view
526
527
NoSortitionView = 3 ,
527
528
/// The block was rejected due to a mismatch with expected sortition view
528
- SortitionViewMismatch = 4
529
+ SortitionViewMismatch = 4 ,
530
+ /// The block was rejected due to a testing directive
531
+ TestingDirective = 5
529
532
} ) ;
530
533
531
534
impl TryFrom < u8 > for RejectCodeTypePrefix {
@@ -545,6 +548,7 @@ impl From<&RejectCode> for RejectCodeTypePrefix {
545
548
RejectCode :: RejectedInPriorRound => RejectCodeTypePrefix :: RejectedInPriorRound ,
546
549
RejectCode :: NoSortitionView => RejectCodeTypePrefix :: NoSortitionView ,
547
550
RejectCode :: SortitionViewMismatch => RejectCodeTypePrefix :: SortitionViewMismatch ,
551
+ RejectCode :: TestingDirective => RejectCodeTypePrefix :: TestingDirective ,
548
552
}
549
553
}
550
554
}
@@ -562,6 +566,8 @@ pub enum RejectCode {
562
566
RejectedInPriorRound ,
563
567
/// The block was rejected due to a mismatch with expected sortition view
564
568
SortitionViewMismatch ,
569
+ /// The block was rejected due to a testing directive
570
+ TestingDirective ,
565
571
}
566
572
567
573
define_u8_enum ! (
@@ -615,8 +621,8 @@ impl std::fmt::Display for BlockResponse {
615
621
BlockResponse :: Rejected ( r) => {
616
622
write ! (
617
623
f,
618
- "BlockRejected: signer_sighash = {}, code = {}, reason = {}" ,
619
- r. reason_code, r. reason, r. signer_signature_hash
624
+ "BlockRejected: signer_sighash = {}, code = {}, reason = {}, signature = {} " ,
625
+ r. reason_code, r. reason, r. signer_signature_hash, r . signature
620
626
)
621
627
}
622
628
}
@@ -629,9 +635,14 @@ impl BlockResponse {
629
635
Self :: Accepted ( ( hash, sig) )
630
636
}
631
637
632
- /// Create a new rejected BlockResponse for the provided block signer signature hash and rejection code
633
- pub fn rejected ( hash : Sha512Trunc256Sum , reject_code : RejectCode ) -> Self {
634
- Self :: Rejected ( BlockRejection :: new ( hash, reject_code) )
638
+ /// Create a new rejected BlockResponse for the provided block signer signature hash and rejection code and sign it with the provided private key
639
+ pub fn rejected (
640
+ hash : Sha512Trunc256Sum ,
641
+ reject_code : RejectCode ,
642
+ private_key : & StacksPrivateKey ,
643
+ mainnet : bool ,
644
+ ) -> Self {
645
+ Self :: Rejected ( BlockRejection :: new ( hash, reject_code, private_key, mainnet) )
635
646
}
636
647
}
637
648
@@ -677,16 +688,94 @@ pub struct BlockRejection {
677
688
pub reason_code : RejectCode ,
678
689
/// The signer signature hash of the block that was rejected
679
690
pub signer_signature_hash : Sha512Trunc256Sum ,
691
+ /// The signer's signature across the rejection
692
+ pub signature : MessageSignature ,
693
+ /// The chain id
694
+ pub chain_id : u32 ,
680
695
}
681
696
682
697
impl BlockRejection {
683
698
/// Create a new BlockRejection for the provided block and reason code
684
- pub fn new ( signer_signature_hash : Sha512Trunc256Sum , reason_code : RejectCode ) -> Self {
685
- Self {
699
+ pub fn new (
700
+ signer_signature_hash : Sha512Trunc256Sum ,
701
+ reason_code : RejectCode ,
702
+ private_key : & StacksPrivateKey ,
703
+ mainnet : bool ,
704
+ ) -> Self {
705
+ let chain_id = if mainnet {
706
+ CHAIN_ID_MAINNET
707
+ } else {
708
+ CHAIN_ID_TESTNET
709
+ } ;
710
+ let mut rejection = Self {
686
711
reason : reason_code. to_string ( ) ,
687
712
reason_code,
688
713
signer_signature_hash,
714
+ signature : MessageSignature :: empty ( ) ,
715
+ chain_id,
716
+ } ;
717
+ rejection
718
+ . sign ( private_key)
719
+ . expect ( "Failed to sign BlockRejection" ) ;
720
+ rejection
721
+ }
722
+
723
+ /// Create a new BlockRejection from a BlockValidateRejection
724
+ pub fn from_validate_rejection (
725
+ reject : BlockValidateReject ,
726
+ private_key : & StacksPrivateKey ,
727
+ mainnet : bool ,
728
+ ) -> Self {
729
+ let chain_id = if mainnet {
730
+ CHAIN_ID_MAINNET
731
+ } else {
732
+ CHAIN_ID_TESTNET
733
+ } ;
734
+ let mut rejection = Self {
735
+ reason : reject. reason ,
736
+ reason_code : RejectCode :: ValidationFailed ( reject. reason_code ) ,
737
+ signer_signature_hash : reject. signer_signature_hash ,
738
+ chain_id,
739
+ signature : MessageSignature :: empty ( ) ,
740
+ } ;
741
+ rejection
742
+ . sign ( private_key)
743
+ . expect ( "Failed to sign BlockRejection" ) ;
744
+ rejection
745
+ }
746
+
747
+ /// The signature hash for the block rejection
748
+ pub fn hash ( & self ) -> Sha256Sum {
749
+ let domain_tuple = make_structured_data_domain ( "block-rejection" , "1.0.0" , self . chain_id ) ;
750
+ let data = Value :: buff_from ( self . signer_signature_hash . as_bytes ( ) . into ( ) ) . unwrap ( ) ;
751
+ structured_data_message_hash ( data, domain_tuple)
752
+ }
753
+
754
+ /// Sign the block rejection and set the internal signature field
755
+ fn sign ( & mut self , private_key : & StacksPrivateKey ) -> Result < ( ) , String > {
756
+ let signature_hash = self . hash ( ) ;
757
+ self . signature = private_key. sign ( signature_hash. as_bytes ( ) ) ?;
758
+ Ok ( ( ) )
759
+ }
760
+
761
+ /// Verify the rejection's signature against the provided signer public key
762
+ pub fn verify ( & self , public_key : & StacksPublicKey ) -> Result < bool , String > {
763
+ if self . signature == MessageSignature :: empty ( ) {
764
+ return Ok ( false ) ;
765
+ }
766
+ let signature_hash = self . hash ( ) ;
767
+ public_key
768
+ . verify ( & signature_hash. 0 , & self . signature )
769
+ . map_err ( |e| e. to_string ( ) )
770
+ }
771
+
772
+ /// Recover the public key from the rejection signature
773
+ pub fn recover_public_key ( & self ) -> Result < StacksPublicKey , & ' static str > {
774
+ if self . signature == MessageSignature :: empty ( ) {
775
+ return Err ( "No signature to recover public key from" ) ;
689
776
}
777
+ let signature_hash = self . hash ( ) ;
778
+ StacksPublicKey :: recover_to_pubkey ( signature_hash. as_bytes ( ) , & self . signature )
690
779
}
691
780
}
692
781
@@ -695,6 +784,8 @@ impl StacksMessageCodec for BlockRejection {
695
784
write_next ( fd, & self . reason . as_bytes ( ) . to_vec ( ) ) ?;
696
785
write_next ( fd, & self . reason_code ) ?;
697
786
write_next ( fd, & self . signer_signature_hash ) ?;
787
+ write_next ( fd, & self . chain_id ) ?;
788
+ write_next ( fd, & self . signature ) ?;
698
789
Ok ( ( ) )
699
790
}
700
791
@@ -705,24 +796,18 @@ impl StacksMessageCodec for BlockRejection {
705
796
} ) ?;
706
797
let reason_code = read_next :: < RejectCode , _ > ( fd) ?;
707
798
let signer_signature_hash = read_next :: < Sha512Trunc256Sum , _ > ( fd) ?;
799
+ let chain_id = read_next :: < u32 , _ > ( fd) ?;
800
+ let signature = read_next :: < MessageSignature , _ > ( fd) ?;
708
801
Ok ( Self {
709
802
reason,
710
803
reason_code,
711
804
signer_signature_hash,
805
+ chain_id,
806
+ signature,
712
807
} )
713
808
}
714
809
}
715
810
716
- impl From < BlockValidateReject > for BlockRejection {
717
- fn from ( reject : BlockValidateReject ) -> Self {
718
- Self {
719
- reason : reject. reason ,
720
- reason_code : RejectCode :: ValidationFailed ( reject. reason_code ) ,
721
- signer_signature_hash : reject. signer_signature_hash ,
722
- }
723
- }
724
- }
725
-
726
811
impl StacksMessageCodec for RejectCode {
727
812
fn consensus_serialize < W : Write > ( & self , fd : & mut W ) -> Result < ( ) , CodecError > {
728
813
write_next ( fd, & ( RejectCodeTypePrefix :: from ( self ) as u8 ) ) ?;
@@ -732,7 +817,8 @@ impl StacksMessageCodec for RejectCode {
732
817
RejectCode :: ConnectivityIssues
733
818
| RejectCode :: RejectedInPriorRound
734
819
| RejectCode :: NoSortitionView
735
- | RejectCode :: SortitionViewMismatch => {
820
+ | RejectCode :: SortitionViewMismatch
821
+ | RejectCode :: TestingDirective => {
736
822
// No additional data to serialize / deserialize
737
823
}
738
824
} ;
@@ -755,6 +841,7 @@ impl StacksMessageCodec for RejectCode {
755
841
RejectCodeTypePrefix :: RejectedInPriorRound => RejectCode :: RejectedInPriorRound ,
756
842
RejectCodeTypePrefix :: NoSortitionView => RejectCode :: NoSortitionView ,
757
843
RejectCodeTypePrefix :: SortitionViewMismatch => RejectCode :: SortitionViewMismatch ,
844
+ RejectCodeTypePrefix :: TestingDirective => RejectCode :: TestingDirective ,
758
845
} ;
759
846
Ok ( code)
760
847
}
@@ -782,6 +869,9 @@ impl std::fmt::Display for RejectCode {
782
869
"The block was rejected due to a mismatch with expected sortition view."
783
870
)
784
871
}
872
+ RejectCode :: TestingDirective => {
873
+ write ! ( f, "The block was rejected due to a testing directive." )
874
+ }
785
875
}
786
876
}
787
877
}
@@ -792,12 +882,6 @@ impl From<BlockResponse> for SignerMessage {
792
882
}
793
883
}
794
884
795
- impl From < BlockValidateReject > for BlockResponse {
796
- fn from ( rejection : BlockValidateReject ) -> Self {
797
- Self :: Rejected ( rejection. into ( ) )
798
- }
799
- }
800
-
801
885
#[ cfg( test) ]
802
886
mod test {
803
887
use blockstack_lib:: chainstate:: nakamoto:: NakamotoBlockHeader ;
@@ -851,14 +935,20 @@ mod test {
851
935
let rejection = BlockRejection :: new (
852
936
Sha512Trunc256Sum ( [ 0u8 ; 32 ] ) ,
853
937
RejectCode :: ValidationFailed ( ValidateRejectCode :: InvalidBlock ) ,
938
+ & StacksPrivateKey :: new ( ) ,
939
+ thread_rng ( ) . gen_bool ( 0.5 ) ,
854
940
) ;
855
941
let serialized_rejection = rejection. serialize_to_vec ( ) ;
856
942
let deserialized_rejection = read_next :: < BlockRejection , _ > ( & mut & serialized_rejection[ ..] )
857
943
. expect ( "Failed to deserialize BlockRejection" ) ;
858
944
assert_eq ! ( rejection, deserialized_rejection) ;
859
945
860
- let rejection =
861
- BlockRejection :: new ( Sha512Trunc256Sum ( [ 1u8 ; 32 ] ) , RejectCode :: ConnectivityIssues ) ;
946
+ let rejection = BlockRejection :: new (
947
+ Sha512Trunc256Sum ( [ 1u8 ; 32 ] ) ,
948
+ RejectCode :: ConnectivityIssues ,
949
+ & StacksPrivateKey :: new ( ) ,
950
+ thread_rng ( ) . gen_bool ( 0.5 ) ,
951
+ ) ;
862
952
let serialized_rejection = rejection. serialize_to_vec ( ) ;
863
953
let deserialized_rejection = read_next :: < BlockRejection , _ > ( & mut & serialized_rejection[ ..] )
864
954
. expect ( "Failed to deserialize BlockRejection" ) ;
@@ -877,6 +967,8 @@ mod test {
877
967
let response = BlockResponse :: Rejected ( BlockRejection :: new (
878
968
Sha512Trunc256Sum ( [ 1u8 ; 32 ] ) ,
879
969
RejectCode :: ValidationFailed ( ValidateRejectCode :: InvalidBlock ) ,
970
+ & StacksPrivateKey :: new ( ) ,
971
+ thread_rng ( ) . gen_bool ( 0.5 ) ,
880
972
) ) ;
881
973
let serialized_response = response. serialize_to_vec ( ) ;
882
974
let deserialized_response = read_next :: < BlockResponse , _ > ( & mut & serialized_response[ ..] )
0 commit comments