11use alloc:: vec:: Vec ;
22
33use ethereum_types:: { Address , H256 , U256 } ;
4+ use k256:: ecdsa:: { RecoveryId , Signature , VerifyingKey } ;
45use rlp:: { DecoderError , Rlp , RlpStream } ;
56use sha3:: { Digest , Keccak256 } ;
67
@@ -9,6 +10,20 @@ use crate::{
910 Bytes ,
1011} ;
1112
13+ /// Error type for EIP-7702 authorization signature recovery
14+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
15+ #[ cfg_attr( feature = "with-serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
16+ pub enum AuthorizationError {
17+ /// Invalid signature format
18+ InvalidSignature ,
19+ /// Invalid recovery ID
20+ InvalidRecoveryId ,
21+ /// Signature recovery failed
22+ RecoveryFailed ,
23+ /// Invalid public key format
24+ InvalidPublicKey ,
25+ }
26+
1227/// EIP-7702 transaction type as defined in the specification
1328pub const SET_CODE_TX_TYPE : u8 = 0x04 ;
1429
@@ -64,6 +79,72 @@ impl rlp::Decodable for AuthorizationListItem {
6479 }
6580}
6681
82+ impl AuthorizationListItem {
83+ /// Recover the authorizing address from the authorization signature according to EIP-7702
84+ pub fn authorizing_address ( & self ) -> Result < Address , AuthorizationError > {
85+ // Create the authorization message hash according to EIP-7702
86+ let message_hash = self . authorization_message_hash ( ) ;
87+
88+ // Create signature from r and s components
89+ let mut signature_bytes = [ 0u8 ; 64 ] ;
90+ signature_bytes[ 0 ..32 ] . copy_from_slice ( & self . r [ ..] ) ;
91+ signature_bytes[ 32 ..64 ] . copy_from_slice ( & self . s [ ..] ) ;
92+
93+ // Create the signature and recovery ID
94+ let signature = Signature :: from_bytes ( & signature_bytes. into ( ) )
95+ . map_err ( |_| AuthorizationError :: InvalidSignature ) ?;
96+
97+ let recovery_id = RecoveryId :: try_from ( if self . y_parity { 1u8 } else { 0u8 } )
98+ . map_err ( |_| AuthorizationError :: InvalidRecoveryId ) ?;
99+
100+ // Recover the verifying key using VerifyingKey::recover_from_prehash
101+ // message_hash is already a 32-byte Keccak256 hash, so we use recover_from_prehash
102+ let verifying_key =
103+ VerifyingKey :: recover_from_prehash ( message_hash. as_bytes ( ) , & signature, recovery_id)
104+ . map_err ( |_| AuthorizationError :: RecoveryFailed ) ?;
105+
106+ // Convert public key to Ethereum address
107+ Self :: verifying_key_to_address ( & verifying_key)
108+ }
109+
110+ /// Create the authorization message hash according to EIP-7702
111+ pub fn authorization_message_hash ( & self ) -> H256 {
112+ // EIP-7702 authorization message format:
113+ // MAGIC || rlp([chain_id, address, nonce])
114+ let mut message = alloc:: vec![ AUTHORIZATION_MAGIC ] ;
115+
116+ // RLP encode the authorization tuple
117+ let mut rlp_stream = RlpStream :: new_list ( 3 ) ;
118+ rlp_stream. append ( & self . chain_id ) ;
119+ rlp_stream. append ( & self . address ) ;
120+ rlp_stream. append ( & self . nonce ) ;
121+ message. extend_from_slice ( & rlp_stream. out ( ) ) ;
122+
123+ // Return keccak256 hash of the complete message
124+ H256 :: from_slice ( Keccak256 :: digest ( & message) . as_slice ( ) )
125+ }
126+
127+ /// Convert VerifyingKey to Ethereum address
128+ fn verifying_key_to_address (
129+ verifying_key : & VerifyingKey ,
130+ ) -> Result < Address , AuthorizationError > {
131+ // Convert public key to bytes (uncompressed format, skip the 0x04 prefix)
132+ let pubkey_point = verifying_key. to_encoded_point ( false ) ;
133+ let pubkey_bytes = pubkey_point. as_bytes ( ) ;
134+
135+ // pubkey_bytes is 65 bytes: [0x04, x_coord (32 bytes), y_coord (32 bytes)]
136+ // We want just the x and y coordinates (64 bytes total)
137+ if pubkey_bytes. len ( ) >= 65 && pubkey_bytes[ 0 ] == 0x04 {
138+ let pubkey_coords = & pubkey_bytes[ 1 ..65 ] ;
139+ // Ethereum address is the last 20 bytes of keccak256(pubkey)
140+ let hash = Keccak256 :: digest ( pubkey_coords) ;
141+ Ok ( Address :: from_slice ( & hash[ 12 ..] ) )
142+ } else {
143+ Err ( AuthorizationError :: InvalidPublicKey )
144+ }
145+ }
146+ }
147+
67148pub type AuthorizationList = Vec < AuthorizationListItem > ;
68149
69150#[ derive( Clone , Debug , PartialEq , Eq ) ]
@@ -206,3 +287,118 @@ impl From<EIP7702Transaction> for EIP7702TransactionMessage {
206287 t. to_message ( )
207288 }
208289}
290+
291+ #[ cfg( test) ]
292+ mod tests {
293+ use super :: * ;
294+ use ethereum_types:: { Address , H256 , U256 } ;
295+
296+ #[ test]
297+ fn test_authorizing_address_with_real_signature ( ) {
298+ use k256:: ecdsa:: SigningKey ;
299+ use k256:: elliptic_curve:: SecretKey ;
300+
301+ // Use a fixed test private key for deterministic testing
302+ let private_key_bytes = [
303+ 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 , 0x09 , 0x0a , 0x0b , 0x0c , 0x0d , 0x0e ,
304+ 0x0f , 0x10 , 0x11 , 0x12 , 0x13 , 0x14 , 0x15 , 0x16 , 0x17 , 0x18 , 0x19 , 0x1a , 0x1b , 0x1c ,
305+ 0x1d , 0x1e , 0x1f , 0x20 ,
306+ ] ;
307+
308+ let secret_key =
309+ SecretKey :: from_bytes ( & private_key_bytes. into ( ) ) . expect ( "Invalid private key" ) ;
310+ let signing_key = SigningKey :: from ( secret_key) ;
311+ let verifying_key = signing_key. verifying_key ( ) ;
312+
313+ // Create authorization data
314+ let chain_id = 1u64 ;
315+ let address = Address :: from_slice ( & [ 0x42u8 ; 20 ] ) ;
316+ let nonce = U256 :: zero ( ) ;
317+
318+ // Create the EIP-7702 authorization message hash
319+ let mut message = alloc:: vec![ AUTHORIZATION_MAGIC ] ;
320+ let mut rlp_stream = RlpStream :: new_list ( 3 ) ;
321+ rlp_stream. append ( & chain_id) ;
322+ rlp_stream. append ( & address) ;
323+ rlp_stream. append ( & nonce) ;
324+ message. extend_from_slice ( & rlp_stream. out ( ) ) ;
325+ let message_hash = H256 :: from_slice ( Keccak256 :: digest ( & message) . as_slice ( ) ) ;
326+
327+ // Sign the message hash
328+ let ( signature, recovery_id) = signing_key
329+ . sign_prehash_recoverable ( message_hash. as_bytes ( ) )
330+ . expect ( "Failed to sign message" ) ;
331+
332+ // Extract signature components
333+ let signature_bytes = signature. to_bytes ( ) ;
334+ let r = H256 :: from_slice ( & signature_bytes[ 0 ..32 ] ) ;
335+ let s = H256 :: from_slice ( & signature_bytes[ 32 ..64 ] ) ;
336+ let y_parity = recovery_id. is_y_odd ( ) ;
337+
338+ // Create AuthorizationListItem with real signature
339+ let auth_item = AuthorizationListItem {
340+ chain_id,
341+ address,
342+ nonce,
343+ y_parity,
344+ r,
345+ s,
346+ } ;
347+
348+ // Recover the authorizing address
349+ let recovered_address = auth_item
350+ . authorizing_address ( )
351+ . expect ( "Failed to recover authorizing address" ) ;
352+
353+ // Convert the original verifying key to an Ethereum address for comparison
354+ let expected_address = AuthorizationListItem :: verifying_key_to_address ( & verifying_key)
355+ . expect ( "Failed to convert verifying key to address" ) ;
356+
357+ // Verify that the recovered address matches the original signer
358+ assert_eq ! ( recovered_address, expected_address) ;
359+ assert_ne ! ( recovered_address, Address :: zero( ) ) ;
360+
361+ // For deterministic testing, verify specific expected values
362+ // This ensures the implementation is working correctly with known inputs
363+ assert_eq ! (
364+ expected_address,
365+ Address :: from_slice( & hex_literal:: hex!(
366+ "6370ef2f4db3611d657b90667de398a2cc2a370c"
367+ ) )
368+ ) ;
369+ }
370+
371+ #[ test]
372+ fn test_authorizing_address_error_handling ( ) {
373+ // Test with invalid signature components (zero values are invalid in ECDSA)
374+ let auth_item = AuthorizationListItem {
375+ chain_id : 1 ,
376+ address : Address :: from_slice ( & [ 0x42u8 ; 20 ] ) ,
377+ nonce : U256 :: zero ( ) ,
378+ y_parity : false ,
379+ r : H256 :: zero ( ) , // Invalid r value (r cannot be zero)
380+ s : H256 :: zero ( ) , // Invalid s value (s cannot be zero)
381+ } ;
382+
383+ // This should return an error due to invalid signature
384+ let result = auth_item. authorizing_address ( ) ;
385+ assert ! ( result. is_err( ) ) ;
386+ assert_eq ! ( result. unwrap_err( ) , AuthorizationError :: InvalidSignature ) ;
387+
388+ // Test with values that are too high (greater than secp256k1 curve order)
389+ // secp256k1 curve order is FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
390+ let auth_item_high_values = AuthorizationListItem {
391+ chain_id : 1 ,
392+ address : Address :: from_slice ( & [ 0x42u8 ; 20 ] ) ,
393+ nonce : U256 :: zero ( ) ,
394+ y_parity : false ,
395+ // Use maximum possible values which exceed the curve order
396+ r : H256 :: from_slice ( & [ 0xFF ; 32 ] ) ,
397+ s : H256 :: from_slice ( & [ 0xFF ; 32 ] ) ,
398+ } ;
399+
400+ let result = auth_item_high_values. authorizing_address ( ) ;
401+ assert ! ( result. is_err( ) ) ;
402+ assert_eq ! ( result. unwrap_err( ) , AuthorizationError :: InvalidSignature ) ;
403+ }
404+ }
0 commit comments