diff --git a/Cargo.lock b/Cargo.lock index 4ceefab..4654dc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6033,6 +6033,7 @@ dependencies = [ "clap 4.5.23", "hex", "secp256k1 0.29.1", + "sha3", "teepot", "zksync_basic_types", ] @@ -6050,6 +6051,7 @@ dependencies = [ "secp256k1 0.29.1", "serde", "serde_with 3.11.0", + "sha3", "teepot", "tokio", "tracing", diff --git a/bin/tee-key-preexec/src/main.rs b/bin/tee-key-preexec/src/main.rs index 2d61478..ec4396f 100644 --- a/bin/tee-key-preexec/src/main.rs +++ b/bin/tee-key-preexec/src/main.rs @@ -8,7 +8,7 @@ use anyhow::{Context, Result}; use clap::Parser; -use secp256k1::{rand, PublicKey, Secp256k1, SecretKey}; +use secp256k1::{rand, PublicKey, Secp256k1}; use sha3::{Digest, Keccak256}; use std::{ffi::OsString, os::unix::process::CommandExt, process::Command}; use teepot::quote::get_quote; @@ -99,6 +99,8 @@ fn main() -> Result<()> { #[cfg(test)] mod tests { + use secp256k1::SecretKey; + use super::*; #[test] diff --git a/bin/verify-attestation/Cargo.toml b/bin/verify-attestation/Cargo.toml index ff473d9..5a7c276 100644 --- a/bin/verify-attestation/Cargo.toml +++ b/bin/verify-attestation/Cargo.toml @@ -12,5 +12,6 @@ anyhow.workspace = true clap.workspace = true hex.workspace = true secp256k1.workspace = true +sha3.workspace = true teepot.workspace = true zksync_basic_types.workspace = true diff --git a/bin/verify-attestation/src/main.rs b/bin/verify-attestation/src/main.rs index d799462..e87d8ac 100644 --- a/bin/verify-attestation/src/main.rs +++ b/bin/verify-attestation/src/main.rs @@ -5,7 +5,13 @@ use anyhow::{Context, Result}; use clap::{Args, Parser, Subcommand}; -use secp256k1::{ecdsa::Signature, Message, PublicKey}; +use core::convert::TryInto; +use hex::encode; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, PublicKey, SECP256K1, +}; +use sha3::{Digest, Keccak256}; use std::{fs, io::Read, path::PathBuf, str::FromStr, time::UNIX_EPOCH}; use teepot::{ client::TcbLevel, @@ -87,14 +93,23 @@ fn verify_signature( let reportdata = "e_verification_result.quote.get_report_data(); let public_key = PublicKey::from_slice(reportdata)?; println!("Public key from attestation quote: {}", public_key); - let signature_bytes = fs::read(&signature_args.signature_file)?; - let signature = Signature::from_compact(&signature_bytes)?; - let root_hash_msg = Message::from_digest_slice(&signature_args.root_hash.0)?; - if signature.verify(&root_hash_msg, &public_key).is_ok() { - println!("Signature verified successfully"); - } else { - println!("Failed to verify signature"); - } + let signature_bytes: &[u8] = &fs::read(&signature_args.signature_file)?; + let ethereum_address_from_quote = "e_verification_result.quote.get_report_data()[..20]; + let ethereum_address_from_signature = + recover_signer(&signature_bytes.try_into()?, signature_args.root_hash)?; + let verification_successful = ðereum_address_from_signature == ethereum_address_from_quote; + + println!( + "Signature '{}' {}. Ethereum address from attestation quote: {}. Ethereum address from signature: {}.", + encode(signature_bytes), + if verification_successful { + "verified successfully" + } else { + "verification failed" + }, + encode(ethereum_address_from_quote), + encode(ethereum_address_from_signature) + ); Ok(()) } @@ -133,3 +148,31 @@ fn print_quote_verification_summary(quote_verification_result: &QuoteVerificatio println!("{:#}", "e.report); } + +// COPY-PASTE AREA + +/// Equivalent to the ecrecover precompile, ensuring that the signatures we produce off-chain +/// can be recovered on-chain. +fn recover_signer(sig: &[u8; 65], root_hash: H256) -> Result<[u8; 20]> { + let root_hash_bytes = root_hash.as_bytes(); + let msg = Message::from_digest_slice(root_hash_bytes)?; + 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_ethereum_address(&public)) +} + +/// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256. +fn public_key_to_ethereum_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 +} diff --git a/bin/verify-era-proof-attestation/Cargo.toml b/bin/verify-era-proof-attestation/Cargo.toml index acd88a9..d67ee8c 100644 --- a/bin/verify-era-proof-attestation/Cargo.toml +++ b/bin/verify-era-proof-attestation/Cargo.toml @@ -14,9 +14,10 @@ ctrlc.workspace = true hex.workspace = true jsonrpsee-types.workspace = true reqwest.workspace = true -secp256k1.workspace = true +secp256k1 = { workspace = true, features = ["recovery"] } serde.workspace = true serde_with = { workspace = true, features = ["hex"] } +sha3.workspace = true teepot.workspace = true tokio.workspace = true tracing.workspace = true diff --git a/bin/verify-era-proof-attestation/src/verification.rs b/bin/verify-era-proof-attestation/src/verification.rs index b389500..fb6bac1 100644 --- a/bin/verify-era-proof-attestation/src/verification.rs +++ b/bin/verify-era-proof-attestation/src/verification.rs @@ -4,7 +4,11 @@ use crate::{args::AttestationPolicyArgs, client::JsonRpcClient}; use anyhow::{Context, Result}; use hex::encode; -use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey}; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, PublicKey, SECP256K1, +}; +use sha3::{Digest, Keccak256}; use teepot::{ client::TcbLevel, quote::{ @@ -27,22 +31,36 @@ pub async fn verify_batch_proof( } let batch_no = batch_number.0; - - let public_key = PublicKey::from_slice( - "e_verification_result.quote.get_report_data()[..PUBLIC_KEY_SIZE], - )?; - debug!(batch_no, "public key: {}", public_key); - let root_hash = node_client.get_root_hash(batch_number).await?; - debug!(batch_no, "root hash: {}", root_hash); + let ethereum_address_from_quote = "e_verification_result.quote.get_report_data()[..20]; + let signature_bytes: &[u8; 65] = signature.try_into()?; + let ethereum_address_from_signature = recover_signer(signature_bytes, root_hash)?; + let verification_successful = ðereum_address_from_signature == ethereum_address_from_quote; + debug!( + batch_no, + "Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.", + root_hash, + encode(ethereum_address_from_quote), + encode(ethereum_address_from_signature), + ); - let is_verified = verify_signature(signature, public_key, root_hash)?; - if is_verified { - info!(batch_no, signature = %encode(signature), "Signature verified successfully."); + if verification_successful { + info!( + batch_no, + signature = encode(signature), + ethereum_address = encode(ethereum_address_from_quote), + "Signature verified successfully." + ); } else { - warn!(batch_no, signature = %encode(signature), "Failed to verify signature!"); + warn!( + batch_no, + signature = encode(signature), + ethereum_address_from_signature = encode(ethereum_address_from_signature), + ethereum_address_from_quote = encode(ethereum_address_from_quote), + "Failed to verify signature!" + ); } - Ok(is_verified) + Ok(verification_successful) } pub fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result { @@ -85,12 +103,6 @@ pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificat ); } -fn verify_signature(signature: &[u8], public_key: PublicKey, root_hash: H256) -> Result { - let signature = Signature::from_compact(signature)?; - let root_hash_msg = Message::from_digest_slice(&root_hash.0)?; - Ok(signature.verify(&root_hash_msg, &public_key).is_ok()) -} - fn is_quote_matching_policy( attestation_policy: &AttestationPolicyArgs, quote_verification_result: &QuoteVerificationResult, @@ -136,3 +148,29 @@ fn check_policy(policy: Option<&str>, actual_value: &[u8], field_name: &str) -> } true } + +/// Equivalent to the ecrecover precompile, ensuring that the signatures we produce off-chain +/// can be recovered on-chain. +fn recover_signer(sig: &[u8; 65], root_hash: H256) -> Result<[u8; 20]> { + let root_hash_bytes = root_hash.as_bytes(); + let msg = Message::from_digest_slice(root_hash_bytes)?; + 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_ethereum_address(&public)) +} + +/// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256. +fn public_key_to_ethereum_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 +}