Skip to content

Commit d64976e

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 d64976e

File tree

8 files changed

+99
-31
lines changed

8 files changed

+99
-31
lines changed

Cargo.lock

Lines changed: 3 additions & 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-attestation/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ anyhow.workspace = true
1212
clap.workspace = true
1313
hex.workspace = true
1414
secp256k1.workspace = true
15+
sha3.workspace = true
1516
teepot.workspace = true
1617
zksync_basic_types.workspace = true

bin/verify-attestation/src/main.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
66
use anyhow::{Context, Result};
77
use clap::{Args, Parser, Subcommand};
8-
use secp256k1::{ecdsa::Signature, Message, PublicKey};
8+
use core::convert::TryInto;
9+
use hex::encode;
10+
use secp256k1::{Message, PublicKey};
911
use std::{fs, io::Read, path::PathBuf, str::FromStr, time::UNIX_EPOCH};
1012
use teepot::{
1113
client::TcbLevel,
14+
ethereum::recover_signer,
1215
quote::{error, tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
1316
};
1417
use zksync_basic_types::H256;
@@ -87,14 +90,25 @@ fn verify_signature(
8790
let reportdata = &quote_verification_result.quote.get_report_data();
8891
let public_key = PublicKey::from_slice(reportdata)?;
8992
println!("Public key from attestation quote: {}", public_key);
90-
let signature_bytes = fs::read(&signature_args.signature_file)?;
91-
let signature = Signature::from_compact(&signature_bytes)?;
92-
let root_hash_msg = Message::from_digest_slice(&signature_args.root_hash.0)?;
93-
if signature.verify(&root_hash_msg, &public_key).is_ok() {
94-
println!("Signature verified successfully");
95-
} else {
96-
println!("Failed to verify signature");
97-
}
93+
let signature_bytes: &[u8] = &fs::read(&signature_args.signature_file)?;
94+
let ethereum_address_from_quote = &quote_verification_result.quote.get_report_data()[..20];
95+
let root_hash_bytes = signature_args.root_hash.as_bytes();
96+
let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
97+
let ethereum_address_from_signature =
98+
recover_signer(&signature_bytes.try_into()?, root_hash_msg)?;
99+
let verification_successful = &ethereum_address_from_signature == ethereum_address_from_quote;
100+
101+
println!(
102+
"Signature '{}' {}. Ethereum address from attestation quote: {}. Ethereum address from signature: {}.",
103+
encode(signature_bytes),
104+
if verification_successful {
105+
"verified successfully"
106+
} else {
107+
"verification failed"
108+
},
109+
encode(ethereum_address_from_quote),
110+
encode(ethereum_address_from_signature)
111+
);
98112
Ok(())
99113
}
100114

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

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
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::Message;
88
use teepot::{
99
client::TcbLevel,
10+
ethereum::recover_signer,
1011
quote::{
1112
error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
1213
QuoteVerificationResult, Report,
1314
},
1415
};
1516
use tracing::{debug, info, warn};
16-
use zksync_basic_types::{L1BatchNumber, H256};
17+
use zksync_basic_types::L1BatchNumber;
1718

1819
pub async fn verify_batch_proof(
1920
quote_verification_result: &QuoteVerificationResult,
@@ -27,22 +28,38 @@ pub async fn verify_batch_proof(
2728
}
2829

2930
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-
3631
let root_hash = node_client.get_root_hash(batch_number).await?;
37-
debug!(batch_no, "root hash: {}", root_hash);
32+
let ethereum_address_from_quote = &quote_verification_result.quote.get_report_data()[..20];
33+
let signature_bytes: &[u8; 65] = signature.try_into()?;
34+
let root_hash_bytes = root_hash.as_bytes();
35+
let root_hash_msg = Message::from_digest_slice(root_hash_bytes)?;
36+
let ethereum_address_from_signature = recover_signer(signature_bytes, root_hash_msg)?;
37+
let verification_successful = &ethereum_address_from_signature == ethereum_address_from_quote;
38+
debug!(
39+
batch_no,
40+
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
41+
root_hash,
42+
encode(ethereum_address_from_quote),
43+
encode(ethereum_address_from_signature),
44+
);
3845

39-
let is_verified = verify_signature(signature, public_key, root_hash)?;
40-
if is_verified {
41-
info!(batch_no, signature = %encode(signature), "Signature verified successfully.");
46+
if verification_successful {
47+
info!(
48+
batch_no,
49+
signature = encode(signature),
50+
ethereum_address = encode(ethereum_address_from_quote),
51+
"Signature verified successfully."
52+
);
4253
} else {
43-
warn!(batch_no, signature = %encode(signature), "Failed to verify signature!");
54+
warn!(
55+
batch_no,
56+
signature = encode(signature),
57+
ethereum_address_from_signature = encode(ethereum_address_from_signature),
58+
ethereum_address_from_quote = encode(ethereum_address_from_quote),
59+
"Failed to verify signature!"
60+
);
4461
}
45-
Ok(is_verified)
62+
Ok(verification_successful)
4663
}
4764

4865
pub fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerificationResult> {
@@ -85,12 +102,6 @@ pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificat
85102
);
86103
}
87104

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-
94105
fn is_quote_matching_policy(
95106
attestation_policy: &AttestationPolicyArgs,
96107
quote_verification_result: &QuoteVerificationResult,

crates/teepot/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ pkcs8.workspace = true
3232
rand.workspace = true
3333
rsa.workspace = true
3434
rustls.workspace = true
35+
secp256k1 = { workspace = true, features = ["recovery"] }
3536
serde.workspace = true
3637
serde_json.workspace = true
3738
serde_with.workspace = true
3839
sha2.workspace = true
40+
sha3.workspace = true
3941
signature.workspace = true
4042
tdx-attest-rs.workspace = true
4143
thiserror.workspace = true
@@ -47,7 +49,6 @@ x509-cert.workspace = true
4749
zeroize.workspace = true
4850

4951
[dev-dependencies]
50-
anyhow.workspace = true
5152
base64.workspace = true
5253
testaso.workspace = true
5354
tokio.workspace = true

crates/teepot/src/ethereum/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright (c) 2023-2024 Matter Labs
3+
4+
//! Ethereum-specific helper functions for on-chain verification of Intel SGX attestation.
5+
6+
use anyhow::Result;
7+
use secp256k1::{
8+
ecdsa::{RecoverableSignature, RecoveryId},
9+
Message, PublicKey, SECP256K1,
10+
};
11+
use sha3::{Digest, Keccak256};
12+
13+
/// Equivalent to the ecrecover precompile, ensuring that the signatures we produce off-chain
14+
/// can be recovered on-chain.
15+
pub fn recover_signer(sig: &[u8; 65], root_hash: Message) -> Result<[u8; 20]> {
16+
let sig = RecoverableSignature::from_compact(
17+
&sig[0..64],
18+
RecoveryId::from_i32(sig[64] as i32 - 27)?,
19+
)?;
20+
let public = SECP256K1.recover_ecdsa(&root_hash, &sig)?;
21+
Ok(public_key_to_ethereum_address(&public))
22+
}
23+
24+
/// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256.
25+
fn public_key_to_ethereum_address(public: &PublicKey) -> [u8; 20] {
26+
let public_key_bytes = public.serialize_uncompressed();
27+
28+
// Skip the first byte (0x04) which indicates uncompressed key
29+
let hash: [u8; 32] = Keccak256::digest(&public_key_bytes[1..]).into();
30+
31+
// Take the last 20 bytes of the hash to get the Ethereum address
32+
let mut address = [0u8; 20];
33+
address.copy_from_slice(&hash[12..]);
34+
address
35+
}

crates/teepot/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#![deny(clippy::all)]
88

99
pub mod client;
10+
pub mod ethereum;
1011
pub mod json;
1112
pub mod log;
1213
pub mod quote;

0 commit comments

Comments
 (0)