11use std:: fmt;
22
3- use secp256k1:: { ecdsa :: Signature , Message , PublicKey , Secp256k1 } ;
3+ use secp256k1:: { Message , PublicKey , Secp256k1 , SecretKey , SECP256K1 } ;
44use zksync_basic_types:: H256 ;
55use zksync_node_framework:: {
66 service:: StopReceiver ,
@@ -67,10 +67,24 @@ impl fmt::Debug for TeeProver {
6767}
6868
6969impl TeeProver {
70+ /// Signs the message in Ethereum-compatible format for on-chain verification.
71+ pub fn sign_message ( sec : & SecretKey , message : Message ) -> Result < [ u8 ; 65 ] , TeeProverError > {
72+ let s = SECP256K1 . sign_ecdsa_recoverable ( & message, sec) ;
73+ let ( rec_id, data) = s. serialize_compact ( ) ;
74+
75+ let mut signature = [ 0u8 ; 65 ] ;
76+ signature[ ..64 ] . copy_from_slice ( & data) ;
77+ // as defined in the Ethereum Yellow Paper (Appendix F)
78+ // https://ethereum.github.io/yellowpaper/paper.pdf
79+ signature[ 64 ] = 27 + rec_id. to_i32 ( ) as u8 ;
80+
81+ Ok ( signature)
82+ }
83+
7084 fn verify (
7185 & self ,
7286 tvi : TeeVerifierInput ,
73- ) -> Result < ( Signature , L1BatchNumber , H256 ) , TeeProverError > {
87+ ) -> Result < ( [ u8 ; 65 ] , L1BatchNumber , H256 ) , TeeProverError > {
7488 match tvi {
7589 TeeVerifierInput :: V1 ( tvi) => {
7690 let observer = METRICS . proof_generation_time . start ( ) ;
@@ -79,7 +93,7 @@ impl TeeProver {
7993 let batch_number = verification_result. batch_number ;
8094 let msg_to_sign = Message :: from_slice ( root_hash_bytes)
8195 . map_err ( |e| TeeProverError :: Verification ( e. into ( ) ) ) ?;
82- let signature = self . config . signing_key . sign_ecdsa ( msg_to_sign) ;
96+ let signature = TeeProver :: sign_message ( & self . config . signing_key , msg_to_sign) ? ;
8397 let duration = observer. observe ( ) ;
8498 tracing:: info!(
8599 proof_generation_time = duration. as_secs_f64( ) ,
@@ -182,3 +196,65 @@ impl Task for TeeProver {
182196 }
183197 }
184198}
199+
200+ #[ cfg( test) ]
201+ mod tests {
202+ use anyhow:: Result ;
203+ use secp256k1:: ecdsa:: { RecoverableSignature , RecoveryId } ;
204+ use sha3:: { Digest , Keccak256 } ;
205+
206+ use super :: * ;
207+
208+ /// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256.
209+ pub fn public_key_to_ethereum_address ( public : & PublicKey ) -> [ u8 ; 20 ] {
210+ let public_key_bytes = public. serialize_uncompressed ( ) ;
211+
212+ // Skip the first byte (0x04) which indicates uncompressed key
213+ let hash: [ u8 ; 32 ] = Keccak256 :: digest ( & public_key_bytes[ 1 ..] ) . into ( ) ;
214+
215+ // Take the last 20 bytes of the hash to get the Ethereum address
216+ let mut address = [ 0u8 ; 20 ] ;
217+ address. copy_from_slice ( & hash[ 12 ..] ) ;
218+ address
219+ }
220+
221+ /// Equivalent to the ecrecover precompile, ensuring that the signatures we produce off-chain
222+ /// can be recovered on-chain.
223+ pub fn recover_signer ( sig : & [ u8 ; 65 ] , msg : & Message ) -> Result < [ u8 ; 20 ] > {
224+ let sig = RecoverableSignature :: from_compact (
225+ & sig[ 0 ..64 ] ,
226+ RecoveryId :: from_i32 ( sig[ 64 ] as i32 - 27 ) ?,
227+ ) ?;
228+ let public = SECP256K1 . recover_ecdsa ( msg, & sig) ?;
229+ Ok ( public_key_to_ethereum_address ( & public) )
230+ }
231+
232+ #[ test]
233+ fn recover ( ) {
234+ // Decode the sample secret key, generate the public key, and derive the Ethereum address
235+ // from the public key
236+ let secp = Secp256k1 :: new ( ) ;
237+ let secret_key_bytes =
238+ hex:: decode ( "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3" )
239+ . unwrap ( ) ;
240+ let secret_key = SecretKey :: from_slice ( & secret_key_bytes) . unwrap ( ) ;
241+ let public_key = PublicKey :: from_secret_key ( & secp, & secret_key) ;
242+ let expected_address = hex:: decode ( "627306090abaB3A6e1400e9345bC60c78a8BEf57" ) . unwrap ( ) ;
243+ let address = public_key_to_ethereum_address ( & public_key) ;
244+
245+ assert_eq ! ( address, expected_address. as_slice( ) ) ;
246+
247+ // Generate a random root hash, create a message from the hash, and sign the message using
248+ // the secret key
249+ let root_hash = H256 :: random ( ) ;
250+ let root_hash_bytes = root_hash. as_bytes ( ) ;
251+ let msg_to_sign = Message :: from_slice ( root_hash_bytes) . unwrap ( ) ;
252+ let signature = TeeProver :: sign_message ( & secret_key, msg_to_sign) . unwrap ( ) ;
253+
254+ // Recover the signer's Ethereum address from the signature and the message, and verify it
255+ // matches the expected address
256+ let proof_addr = recover_signer ( & signature, & msg_to_sign) . unwrap ( ) ;
257+
258+ assert_eq ! ( proof_addr, expected_address. as_slice( ) ) ;
259+ }
260+ }
0 commit comments