Skip to content

Commit 5ac5999

Browse files
committed
feat(tee-proof-verifier): add support for Solidity-compatible pubkey in report_data
This PR is part of the effort to implement on-chain TEE proof verification. This PR goes hand in hand with: - matter-labs/zksync-era#3414 - #228
1 parent a9b89ef commit 5ac5999

File tree

4 files changed

+50
-19
lines changed

4 files changed

+50
-19
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/tee-key-preexec/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
use anyhow::{Context, Result};
1010
use clap::Parser;
11-
use secp256k1::{rand, PublicKey, Secp256k1, SecretKey};
11+
use secp256k1::{rand, PublicKey, Secp256k1};
1212
use sha3::{Digest, Keccak256};
1313
use std::{ffi::OsString, os::unix::process::CommandExt, process::Command};
1414
use teepot::quote::get_quote;
@@ -99,6 +99,8 @@ fn main() -> Result<()> {
9999

100100
#[cfg(test)]
101101
mod tests {
102+
use secp256k1::SecretKey;
103+
102104
use super::*;
103105

104106
#[test]

bin/verify-era-proof-attestation/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ ctrlc.workspace = true
1414
hex.workspace = true
1515
jsonrpsee-types.workspace = true
1616
reqwest.workspace = true
17-
secp256k1.workspace = true
17+
secp256k1 = { workspace = true, features = ["recovery"] }
1818
serde.workspace = true
1919
serde_with = { workspace = true, features = ["hex"] }
20+
sha3.workspace = true
2021
teepot.workspace = true
2122
tokio.workspace = true
2223
tracing.workspace = true

bin/verify-era-proof-attestation/src/verification.rs

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
use crate::{args::AttestationPolicyArgs, client::JsonRpcClient};
55
use anyhow::{Context, Result};
66
use hex::encode;
7-
use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey};
7+
use secp256k1::{
8+
ecdsa::{RecoverableSignature, RecoveryId},
9+
Message, PublicKey, SECP256K1,
10+
};
11+
use sha3::{Digest, Keccak256};
812
use teepot::{
913
client::TcbLevel,
1014
quote::{
@@ -27,22 +31,25 @@ pub async fn verify_batch_proof(
2731
}
2832

2933
let batch_no = batch_number.0;
30-
31-
let public_key = PublicKey::from_slice(
32-
&quote_verification_result.quote.get_report_data()[..PUBLIC_KEY_SIZE],
33-
)?;
34-
debug!(batch_no, "public key: {}", public_key);
35-
3634
let root_hash = node_client.get_root_hash(batch_number).await?;
37-
debug!(batch_no, "root hash: {}", root_hash);
35+
let ethereum_address_from_quote = &quote_verification_result.quote.get_report_data()[..20];
36+
let signature_array: &[u8; 65] = signature.try_into()?;
37+
let ethereum_address_from_signature = recover_signer(signature_array, root_hash)?;
38+
let verification_successful = &ethereum_address_from_signature == ethereum_address_from_quote;
39+
debug!(
40+
batch_no,
41+
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
42+
root_hash,
43+
encode(ethereum_address_from_quote),
44+
encode(ethereum_address_from_signature),
45+
);
3846

39-
let is_verified = verify_signature(signature, public_key, root_hash)?;
40-
if is_verified {
47+
if verification_successful {
4148
info!(batch_no, signature = %encode(signature), "Signature verified successfully.");
4249
} else {
4350
warn!(batch_no, signature = %encode(signature), "Failed to verify signature!");
4451
}
45-
Ok(is_verified)
52+
Ok(verification_successful)
4653
}
4754

4855
pub fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerificationResult> {
@@ -85,12 +92,6 @@ pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificat
8592
);
8693
}
8794

88-
fn verify_signature(signature: &[u8], public_key: PublicKey, root_hash: H256) -> Result<bool> {
89-
let signature = Signature::from_compact(signature)?;
90-
let root_hash_msg = Message::from_digest_slice(&root_hash.0)?;
91-
Ok(signature.verify(&root_hash_msg, &public_key).is_ok())
92-
}
93-
9495
fn is_quote_matching_policy(
9596
attestation_policy: &AttestationPolicyArgs,
9697
quote_verification_result: &QuoteVerificationResult,
@@ -136,3 +137,29 @@ fn check_policy(policy: Option<&str>, actual_value: &[u8], field_name: &str) ->
136137
}
137138
true
138139
}
140+
141+
/// Equivalent to the ecrecover precompile, ensuring that the signatures we produce off-chain
142+
/// can be recovered on-chain.
143+
pub fn recover_signer(sig: &[u8; 65], root_hash: H256) -> Result<[u8; 20]> {
144+
let root_hash_bytes = root_hash.as_bytes();
145+
let msg = Message::from_digest_slice(root_hash_bytes)?;
146+
let sig = RecoverableSignature::from_compact(
147+
&sig[0..64],
148+
RecoveryId::from_i32(sig[64] as i32 - 27)?,
149+
)?;
150+
let public = SECP256K1.recover_ecdsa(&msg, &sig)?;
151+
Ok(public_key_to_ethereum_address(&public))
152+
}
153+
154+
/// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256.
155+
fn public_key_to_ethereum_address(public: &PublicKey) -> [u8; 20] {
156+
let public_key_bytes = public.serialize_uncompressed();
157+
158+
// Skip the first byte (0x04) which indicates uncompressed key
159+
let hash: [u8; 32] = Keccak256::digest(&public_key_bytes[1..]).into();
160+
161+
// Take the last 20 bytes of the hash to get the Ethereum address
162+
let mut address = [0u8; 20];
163+
address.copy_from_slice(&hash[12..]);
164+
address
165+
}

0 commit comments

Comments
 (0)