2323)
2424from bitcoinutils .script import Script
2525from bitcoinutils .hdwallet import HDWallet
26+ from base64 import b64decode
2627
2728
2829class TestPrivateKeys (unittest .TestCase ):
@@ -72,6 +73,13 @@ def setUp(self):
7273 b"\x08 \xa8 \xfd \x17 \xb4 H\xa6 \x85 T\x19 \x9c G\xd0 \x8f \xfb \x10 \xd4 \xb8 "
7374 )
7475 self .address = "1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm"
76+
77+ # Message public key recovery tests
78+ self .valid_message = "Hello, Bitcoin!"
79+ # 65-byte Bitcoin signature (1-byte recovery ID + 64-byte ECDSA signature)
80+ self .valid_signature = b'\x1f \x0c \xfc \xd8 V\xec 27)\xa7 \xfc \x02 :\xda \xcf T\xb2 *\x02 \x16 .\xe2 s\x7f \x18 [&^\xb3 e\xee 3"KN\xfc t\x01 1Z[\x05 \xb5 \xea \n !\xe8 \xce \x9e m\x89 /\xf2 \xa0 \x15 \x83 {\x7f \x9e \xba +\xb4 \xf8 &\x15 '
81+ # Known valid public key corresponding to the message + signature
82+ self .expected_public_key = '02649abc7094d2783670255073ccfd132677555ca84045c5a005611f25ef51fdbf'
7583
7684 def test_pubkey_creation (self ):
7785 pub1 = PublicKey (self .public_key_hex )
@@ -98,6 +106,38 @@ def test_pubkey_to_hash160(self):
98106 def test_pubkey_x_only (self ):
99107 pub = PublicKey (self .public_key_hex )
100108 self .assertEqual (pub .to_x_only_hex (), self .public_key_hex [2 :66 ])
109+
110+ #Tests for PublicKey recovery from message and signature
111+ def test_public_key_recovery_valid (self ):
112+ """Test successful public key recovery from a valid message and signature"""
113+ pubkey = PublicKey (message = self .valid_message , signature = self .valid_signature )
114+ self .assertEqual (pubkey .key .to_string ("compressed" ).hex (), self .expected_public_key )
115+
116+ def test_invalid_signature_length (self ):
117+ """Test handling of invalid signature length (not 65 bytes)"""
118+ short_signature = self .valid_signature [:60 ] # Truncate signature to 60 bytes
119+ with self .assertRaises (ValueError ) as context :
120+ PublicKey (message = self .valid_message , signature = short_signature )
121+ self .assertEqual (str (context .exception ), "Invalid signature length, must be exactly 65 bytes" )
122+
123+ def test_invalid_recovery_id (self ):
124+ """Test handling of an invalid recovery ID"""
125+ invalid_signature = bytes ([50 ]) + self .valid_signature [1 :] # Modify recovery ID to 50
126+ with self .assertRaises (ValueError ) as context :
127+ PublicKey (message = self .valid_message , signature = invalid_signature )
128+ self .assertIn ("Invalid recovery ID" , str (context .exception ))
129+
130+ def test_missing_parameters (self ):
131+ """Test that missing both hex_str and (message, signature) raises an error"""
132+ with self .assertRaises (TypeError ) as context :
133+ PublicKey ()
134+ self .assertEqual (str (context .exception ), "Either 'hex_str' or ('message', 'signature') must be provided." )
135+
136+ def test_empty_message (self ):
137+ """Test handling of an empty message for public key recovery"""
138+ with self .assertRaises (ValueError ) as context :
139+ PublicKey (message = "" , signature = self .valid_signature )
140+ self .assertEqual (str (context .exception ), "Empty message provided for public key recovery." )
101141
102142
103143class TestP2pkhAddresses (unittest .TestCase ):
@@ -311,7 +351,6 @@ def test_legacy_address_from_mnemonic(self):
311351 hdw .from_path ("m/44'/1'/0'/0/3" )
312352 address = hdw .get_private_key ().get_public_key ().get_address ()
313353 self .assertTrue (address .to_string (), self .legacy_address_m_44_1h_0h_0_3 )
314-
315-
354+
316355if __name__ == "__main__" :
317356 unittest .main ()
0 commit comments