diff --git a/Cargo.lock b/Cargo.lock index 0a98fb5ccd23..195e6a7ae707 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12903,9 +12903,11 @@ dependencies = [ "anyhow", "async-trait", "envy", + "hex", "reqwest 0.12.9", "secp256k1", "serde", + "sha3 0.10.8", "thiserror 1.0.69", "tokio", "tracing", diff --git a/core/bin/zksync_tee_prover/Cargo.toml b/core/bin/zksync_tee_prover/Cargo.toml index b853da348ee0..2628673845c9 100644 --- a/core/bin/zksync_tee_prover/Cargo.toml +++ b/core/bin/zksync_tee_prover/Cargo.toml @@ -16,7 +16,11 @@ anyhow.workspace = true async-trait.workspace = true envy.workspace = true reqwest = { workspace = true, features = ["zstd"] } -secp256k1 = { workspace = true, features = ["serde"] } +secp256k1 = { workspace = true, features = [ + "global-context", + "recovery", + "serde", +] } serde = { workspace = true, features = ["derive"] } thiserror.workspace = true tokio = { workspace = true, features = ["full"] } @@ -31,3 +35,7 @@ zksync_prover_interface.workspace = true zksync_tee_verifier.workspace = true zksync_types.workspace = true zksync_vlog.workspace = true + +[dev-dependencies] +hex.workspace = true +sha3.workspace = true diff --git a/core/bin/zksync_tee_prover/src/api_client.rs b/core/bin/zksync_tee_prover/src/api_client.rs index ffc2839b8d3b..186acc9f952a 100644 --- a/core/bin/zksync_tee_prover/src/api_client.rs +++ b/core/bin/zksync_tee_prover/src/api_client.rs @@ -1,5 +1,5 @@ use reqwest::{Client, Response, StatusCode}; -use secp256k1::{ecdsa::Signature, PublicKey}; +use secp256k1::PublicKey; use serde::Serialize; use url::Url; use zksync_basic_types::H256; @@ -87,13 +87,13 @@ impl TeeApiClient { pub async fn submit_proof( &self, batch_number: L1BatchNumber, - signature: Signature, + signature: [u8; 65], pubkey: &PublicKey, root_hash: H256, tee_type: TeeType, ) -> Result<(), TeeProverError> { let request = SubmitTeeProofRequest(Box::new(L1BatchTeeProofForL1 { - signature: signature.serialize_compact().into(), + signature: signature.into(), pubkey: pubkey.serialize().into(), proof: root_hash.as_bytes().into(), tee_type, diff --git a/core/bin/zksync_tee_prover/src/tee_prover.rs b/core/bin/zksync_tee_prover/src/tee_prover.rs index 58f3d45969ca..7ec604ae2373 100644 --- a/core/bin/zksync_tee_prover/src/tee_prover.rs +++ b/core/bin/zksync_tee_prover/src/tee_prover.rs @@ -1,6 +1,9 @@ use std::fmt; -use secp256k1::{ecdsa::Signature, Message, PublicKey, Secp256k1}; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, PublicKey, Secp256k1, SecretKey, SECP256K1, +}; use zksync_basic_types::H256; use zksync_node_framework::{ service::StopReceiver, @@ -67,10 +70,25 @@ impl fmt::Debug for TeeProver { } impl TeeProver { + // TODO TODO TODO add tests for this function + /// Signs the message in Ethereum-compatible format for on-chain verification. + fn sign_message(&self, sec: &SecretKey, message: Message) -> Result<[u8; 65], TeeProverError> { + let s = SECP256K1.sign_ecdsa_recoverable(&message, sec); + let (rec_id, data) = s.serialize_compact(); + + let mut signature = [0u8; 65]; + signature[..64].copy_from_slice(&data); + // as defined in the Ethereum Yellow Paper (Appendix F) + // https://ethereum.github.io/yellowpaper/paper.pdf + signature[64] = 27 + rec_id.to_i32() as u8; + + Ok(signature) + } + fn verify( &self, tvi: TeeVerifierInput, - ) -> Result<(Signature, L1BatchNumber, H256), TeeProverError> { + ) -> Result<([u8; 65], L1BatchNumber, H256), TeeProverError> { match tvi { TeeVerifierInput::V1(tvi) => { let observer = METRICS.proof_generation_time.start(); @@ -79,7 +97,7 @@ impl TeeProver { let batch_number = verification_result.batch_number; let msg_to_sign = Message::from_slice(root_hash_bytes) .map_err(|e| TeeProverError::Verification(e.into()))?; - let signature = self.config.signing_key.sign_ecdsa(msg_to_sign); + let signature = self.sign_message(&self.config.signing_key, msg_to_sign)?; let duration = observer.observe(); tracing::info!( proof_generation_time = duration.as_secs_f64(), @@ -182,3 +200,63 @@ impl Task for TeeProver { } } } + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + use sha3::{Digest, Keccak256}; + + /// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256. + pub fn public_key_to_address(public: &PublicKey) -> [u8; 20] { + let public_key_bytes = public.serialize_uncompressed(); + + // Skip the first byte (0x04) which indicates uncompressed key + let hash: [u8; 32] = Keccak256::digest(&public_key_bytes[1..]).into(); + + // Take the last 20 bytes of the hash to get the Ethereum address + let mut address = [0u8; 20]; + address.copy_from_slice(&hash[12..]); + address + } + + /// Recovers the address of the sender using secp256k1 pubkey recovery. + /// + /// Converts the public key into an ethereum address by hashing the public key with + /// keccak256. + /// + /// This does not ensure that the `s` value in the signature is low, and _just_ wraps the + /// underlying secp256k1 library. + /// &Message::from_digest_slice(&msg[..32]) + // #[allow(dead_code)] + pub fn recover_signer_unchecked(sig: &[u8; 65], msg: &Message) -> Result<[u8; 20]> { + let sig = RecoverableSignature::from_compact( + &sig[0..64], + RecoveryId::from_i32(sig[64] as i32 - 27)?, + )?; + + let public = SECP256K1.recover_ecdsa(msg, &sig)?; + Ok(public_key_to_address(&public)) + } + + #[test] + fn recover() { + let proof = "01000000c13bd882edb37ffbabc9f9e34a0d9789633b850fe55e625b768cc8e5feed7d9f7ab536cbc210c2fcc1385aaf88d8a91d8adc2740245f9deee5fd3d61dd2a71662fb6639515f1e2f3354361a82d86c1952352c1a81b"; + let proof_bytes = hex::decode(proof).unwrap(); + let msg = "216ac5cd5a5e13b0c9a81efb1ad04526b9f4ddd2fe6ebc02819c5097dfb0958c"; + let msg_bytes = hex::decode(msg).unwrap(); + let proof_addr = recover_signer_unchecked( + &proof_bytes[24..].try_into().unwrap(), + &Message::from_slice(&msg_bytes).unwrap(), + ) + .unwrap(); + let priv_key = "324b5d1744ec27d6ac458350ce6a6248680bb0209521b2c730c1fe82a433eb54"; + let priv_key_bytes = hex::decode(priv_key).unwrap(); + let priv_key = SecretKey::from_slice(&priv_key_bytes).unwrap(); + let pubkey = PublicKey::from_secret_key_global(&priv_key); + let pub_addr = public_key_to_address(&pubkey); + assert_eq!(pub_addr, proof_addr); + println!("Public address: {:?}", pub_addr); + println!("Proof public address: {:?}", proof_addr); + } +}