diff --git a/Cargo.lock b/Cargo.lock index 4ceefab..8338c06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6050,6 +6050,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-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..94d1be5 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,27 @@ 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_array: &[u8; 65] = signature + .try_into() + .map_err(|_| anyhow::anyhow!("Signature must be exactly 65 bytes long"))?; + let ethereum_address_from_signature = recover_signer(signature_array, 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 { + if verification_successful { info!(batch_no, signature = %encode(signature), "Signature verified successfully."); } else { warn!(batch_no, signature = %encode(signature), "Failed to verify signature!"); } - Ok(is_verified) + Ok(verification_successful) } pub fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result { @@ -85,12 +94,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 +139,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. +pub 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 +}