Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tee-key-preexec): support for onchain-compatible pubkey in report_data #251

Merged
merged 2 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
serde_with = { version = "3.8", features = ["base64", "hex"] }
sha2 = "0.10.8"
sha3 = "0.10.8"
signature = "2.2.0"
tdx-attest-rs = { version = "0.1.2", git = "https://github.com/intel/SGXDataCenterAttestationPrimitives.git", rev = "aa239d25a437a28f3f4de92c38f5b6809faac842" }
teepot = { path = "crates/teepot" }
Expand Down
1 change: 1 addition & 0 deletions bin/tee-key-preexec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ repository.workspace = true
[dependencies]
anyhow.workspace = true
clap.workspace = true
hex.workspace = true
rand.workspace = true
secp256k1.workspace = true
teepot.workspace = true
Expand Down
17 changes: 9 additions & 8 deletions bin/tee-key-preexec/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

use anyhow::{Context, Result};
use clap::Parser;
use secp256k1::{rand, Keypair, PublicKey, Secp256k1, SecretKey};
use secp256k1::{rand, Secp256k1};
use std::{ffi::OsString, os::unix::process::CommandExt, process::Command};
use teepot::quote::get_quote;
use teepot::{
ethereum::public_key_to_ethereum_address, prover::reportdata::ReportDataV1, quote::get_quote,
};
use tracing::error;
use tracing_log::LogTracer;
use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
Expand All @@ -37,14 +39,13 @@ fn main_with_error() -> Result<()> {
tracing::subscriber::set_global_default(subscriber).context("Failed to set logger")?;

let args = Args::parse();

let mut rng = rand::thread_rng();
let secp = Secp256k1::new();
let keypair = Keypair::new(&secp, &mut rng);
let signing_key = SecretKey::from_keypair(&keypair);
let verifying_key = PublicKey::from_keypair(&keypair);
let verifying_key_bytes = verifying_key.serialize();
let tee_type = match get_quote(verifying_key_bytes.as_ref()) {
let (signing_key, verifying_key) = secp.generate_keypair(&mut rng);
let ethereum_address = public_key_to_ethereum_address(&verifying_key);
let report_data = ReportDataV1 { ethereum_address };
let report_data_bytes: [u8; 64] = report_data.into();
let tee_type = match get_quote(&report_data_bytes) {
Ok((tee_type, quote)) => {
// save quote to file
std::fs::write(TEE_QUOTE_FILE, quote)?;
Expand Down
1 change: 1 addition & 0 deletions bin/verify-attestation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
40 changes: 27 additions & 13 deletions bin/verify-attestation/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@

//! Tool for SGX attestation and batch signature verification

use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use clap::{Args, Parser, Subcommand};
use secp256k1::{ecdsa::Signature, Message, PublicKey};
use core::convert::TryInto;
use hex::encode;
use secp256k1::Message;
use std::{fs, io::Read, path::PathBuf, str::FromStr, time::UNIX_EPOCH};
use teepot::{
client::TcbLevel,
ethereum::recover_signer,
prover::reportdata::ReportData,
quote::{error, tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
};
use zksync_basic_types::H256;
Expand Down Expand Up @@ -84,17 +88,27 @@ fn verify_signature(
quote_verification_result: &QuoteVerificationResult,
signature_args: &SignatureArgs,
) -> Result<()> {
let reportdata = &quote_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 report_data = ReportData::try_from(quote_verification_result.quote.get_report_data())?;
let ethereum_address_from_quote = match report_data {
ReportData::V1(report_data_v1) => report_data_v1.ethereum_address,
_ => return Err(anyhow!("Unsupported report data version")),
};
haraldh marked this conversation as resolved.
Show resolved Hide resolved
let signature_bytes: &[u8] = &fs::read(&signature_args.signature_file)?;
let root_hash = Message::from_digest_slice(signature_args.root_hash.as_bytes())?;
let ethereum_address_from_signature = recover_signer(&signature_bytes.try_into()?, &root_hash)?;
let verification_successful = ethereum_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(())
}

Expand Down
78 changes: 54 additions & 24 deletions bin/verify-era-proof-attestation/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// Copyright (c) 2023-2024 Matter Labs

use crate::{args::AttestationPolicyArgs, client::JsonRpcClient};
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use hex::encode;
use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey};
use secp256k1::{ecdsa::Signature, Message};
use teepot::{
client::TcbLevel,
ethereum::recover_signer,
prover::reportdata::ReportData,
quote::{
error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
QuoteVerificationResult, Report,
Expand All @@ -15,6 +17,51 @@ use teepot::{
use tracing::{debug, info, warn};
use zksync_basic_types::{L1BatchNumber, H256};

struct TeeProof {
report: ReportData,
root_hash: H256,
signature: Vec<u8>,
}

impl TeeProof {
pub fn new(report: ReportData, root_hash: H256, signature: Vec<u8>) -> Self {
Self {
report,
root_hash,
signature,
}
}

pub fn verify(&self) -> Result<bool> {
match &self.report {
ReportData::V0(report) => {
let signature = Signature::from_compact(&self.signature)?;
let root_hash_msg = Message::from_digest_slice(&self.root_hash.0)?;
Ok(signature.verify(&root_hash_msg, &report.pubkey).is_ok())
}
ReportData::V1(report) => {
let ethereum_address_from_report = report.ethereum_address;
let root_hash_msg = Message::from_digest_slice(self.root_hash.as_bytes())?;
let signature_bytes: [u8; 65] = self
.signature
.clone()
.try_into()
.map_err(|e| anyhow!("{:?}", e))?;
let ethereum_address_from_signature =
recover_signer(&signature_bytes, &root_hash_msg)?;
debug!(
"Root hash: {}. Ethereum address from the attestation quote: {}. Ethereum address from the signature: {}.",
self.root_hash,
encode(ethereum_address_from_report),
encode(ethereum_address_from_signature),
);
Ok(ethereum_address_from_signature == ethereum_address_from_report)
}
ReportData::Unknown(_) => Ok(false),
}
}
}

pub async fn verify_batch_proof(
quote_verification_result: &QuoteVerificationResult,
attestation_policy: &AttestationPolicyArgs,
Expand All @@ -26,23 +73,12 @@ pub async fn verify_batch_proof(
return Ok(false);
}

let batch_no = batch_number.0;

let public_key = PublicKey::from_slice(
&quote_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 is_verified = verify_signature(signature, public_key, root_hash)?;
if is_verified {
info!(batch_no, signature = %encode(signature), "Signature verified successfully.");
} else {
warn!(batch_no, signature = %encode(signature), "Failed to verify signature!");
}
Ok(is_verified)
let report_data_bytes = quote_verification_result.quote.get_report_data();
let report_data = ReportData::try_from(report_data_bytes)?;
let tee_proof = TeeProof::new(report_data, root_hash, signature.to_vec());
let verification_successful = tee_proof.verify().is_ok();
Ok(verification_successful)
}

pub fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerificationResult> {
Expand Down Expand Up @@ -85,12 +121,6 @@ pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificat
);
}

fn verify_signature(signature: &[u8], public_key: PublicKey, root_hash: H256) -> Result<bool> {
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,
Expand Down
4 changes: 3 additions & 1 deletion crates/teepot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ pkcs8.workspace = true
rand.workspace = true
rsa.workspace = true
rustls.workspace = true
secp256k1 = { workspace = true, features = ["recovery"] }
serde.workspace = true
serde_json.workspace = true
serde_with.workspace = true
sha2.workspace = true
sha3.workspace = true
signature.workspace = true
tdx-attest-rs.workspace = true
thiserror.workspace = true
Expand All @@ -47,8 +49,8 @@ x509-cert.workspace = true
zeroize.workspace = true

[dev-dependencies]
anyhow.workspace = true
base64.workspace = true
testaso.workspace = true
tokio.workspace = true
tracing-test.workspace = true
zksync_basic_types.workspace = true
86 changes: 86 additions & 0 deletions crates/teepot/src/ethereum/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2024 Matter Labs

//! Ethereum-specific helper functions for on-chain verification of Intel SGX attestation.

use anyhow::Result;
use secp256k1::{
ecdsa::{RecoverableSignature, RecoveryId},
Message, PublicKey, SECP256K1,
};
use sha3::{Digest, Keccak256};

/// 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: &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(root_hash, &sig)?;
Ok(public_key_to_ethereum_address(&public))
}

/// Converts a public key into an Ethereum address by hashing the encoded public key with Keccak256.
pub 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
}

#[cfg(test)]
mod tests {
use secp256k1::{Secp256k1, SecretKey};
use zksync_basic_types::H256;

use super::*;

/// Signs the message in Ethereum-compatible format for on-chain verification.
fn sign_message(sec: &SecretKey, message: Message) -> Result<[u8; 65]> {
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)
}

#[test]
fn recover() {
// Decode the sample secret key, generate the public key, and derive the Ethereum address
// from the public key
let secp = Secp256k1::new();
let secret_key_bytes =
hex::decode("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3")
.unwrap();
let secret_key = SecretKey::from_slice(&secret_key_bytes).unwrap();
let public_key = PublicKey::from_secret_key(&secp, &secret_key);
let expected_address = hex::decode("627306090abaB3A6e1400e9345bC60c78a8BEf57").unwrap();
let address = public_key_to_ethereum_address(&public_key);

assert_eq!(address, expected_address.as_slice());

// Generate a random root hash, create a message from the hash, and sign the message using
// the secret key
let root_hash = H256::random();
let root_hash_bytes = root_hash.as_bytes();
let msg_to_sign = Message::from_digest_slice(root_hash_bytes).unwrap();
let signature = sign_message(&secret_key, msg_to_sign).unwrap();

// Recover the signer's Ethereum address from the signature and the message, and verify it
// matches the expected address
let proof_addr = recover_signer(&signature, &msg_to_sign).unwrap();

assert_eq!(proof_addr, expected_address.as_slice());
}
}
2 changes: 2 additions & 0 deletions crates/teepot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
#![deny(clippy::all)]

pub mod client;
pub mod ethereum;
pub mod json;
pub mod log;
pub mod prover;
pub mod quote;
pub mod server;
pub mod sgx;
Expand Down
6 changes: 6 additions & 0 deletions crates/teepot/src/prover/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023 Matter Labs

//! Common functionality for TEE provers and verifiers.

pub mod reportdata;
Loading
Loading