mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-21 15:13:56 +02:00
WIP
This commit is contained in:
parent
8dbfe47e2f
commit
85bba1d697
3 changed files with 219 additions and 121 deletions
|
@ -3,11 +3,15 @@
|
||||||
|
|
||||||
//! Tool for SGX attestation and batch signature verification
|
//! Tool for SGX attestation and batch signature verification
|
||||||
|
|
||||||
|
mod rpc_api;
|
||||||
|
mod verifier;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey};
|
use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::time::Duration;
|
||||||
use teepot::{
|
use teepot::{
|
||||||
client::TcbLevel,
|
client::TcbLevel,
|
||||||
sgx::{tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
|
sgx::{tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
|
||||||
|
@ -21,6 +25,9 @@ use zksync_web3_decl::{
|
||||||
namespaces::ZksNamespaceClient,
|
namespaces::ZksNamespaceClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use rpc_api::MainNodeClient;
|
||||||
|
use verifier::verify_proof;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author = "Matter Labs", version, about = "SGX attestation and batch signature verifier", long_about = None)]
|
#[command(author = "Matter Labs", version, about = "SGX attestation and batch signature verifier", long_about = None)]
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
|
@ -33,6 +40,9 @@ struct Arguments {
|
||||||
/// Chain ID of the network to query.
|
/// Chain ID of the network to query.
|
||||||
#[clap(short, long, default_value_t = L2ChainId::default().as_u64())]
|
#[clap(short, long, default_value_t = L2ChainId::default().as_u64())]
|
||||||
chain_id: u64,
|
chain_id: u64,
|
||||||
|
/// Run continuously, polling for new batch ranges from the RPC server.
|
||||||
|
#[clap(short, long)]
|
||||||
|
continuous: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_batch_range(s: &str) -> Result<(L1BatchNumber, L1BatchNumber)> {
|
fn parse_batch_range(s: &str) -> Result<(L1BatchNumber, L1BatchNumber)> {
|
||||||
|
@ -61,55 +71,6 @@ fn parse_batch_range(s: &str) -> Result<(L1BatchNumber, L1BatchNumber)> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait JsonRpcClient {
|
|
||||||
async fn get_root_hash(&self, batch_number: L1BatchNumber) -> Result<H256>;
|
|
||||||
// TODO implement get_tee_proofs(batch_number, tee_type) once https://crates.io/crates/zksync_web3_decl crate is updated
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MainNodeClient(NodeClient<L2>);
|
|
||||||
|
|
||||||
impl JsonRpcClient for MainNodeClient {
|
|
||||||
async fn get_root_hash(&self, batch_number: L1BatchNumber) -> Result<H256> {
|
|
||||||
self.0
|
|
||||||
.get_l1_batch_details(batch_number)
|
|
||||||
.rpc_context("get_l1_batch_details")
|
|
||||||
.await?
|
|
||||||
.and_then(|res| res.base.root_hash)
|
|
||||||
.ok_or_else(|| anyhow!("No root hash found for batch #{}", batch_number))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON-RPC request and response structures for fetching TEE proofs
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct GetProofsRequest {
|
|
||||||
jsonrpc: String,
|
|
||||||
id: u32,
|
|
||||||
method: String,
|
|
||||||
params: (L1BatchNumber, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct GetProofsResponse {
|
|
||||||
jsonrpc: String,
|
|
||||||
result: Vec<Proof>,
|
|
||||||
id: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct Proof {
|
|
||||||
#[serde(rename = "l1BatchNumber")]
|
|
||||||
l1_batch_number: u32,
|
|
||||||
#[serde(rename = "teeType")]
|
|
||||||
tee_type: String,
|
|
||||||
pubkey: Vec<u8>,
|
|
||||||
signature: Vec<u8>,
|
|
||||||
proof: Vec<u8>,
|
|
||||||
#[serde(rename = "provedAt")]
|
|
||||||
proved_at: String,
|
|
||||||
attestation: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let args = Arguments::parse();
|
let args = Arguments::parse();
|
||||||
|
@ -123,93 +84,81 @@ async fn main() -> Result<()> {
|
||||||
.build();
|
.build();
|
||||||
let node_client = MainNodeClient(node_client);
|
let node_client = MainNodeClient(node_client);
|
||||||
let http_client = Client::new();
|
let http_client = Client::new();
|
||||||
let (start_batch_number, end_batch_number) = args.batch_range;
|
|
||||||
|
if args.continuous {
|
||||||
|
run_continuous_mode(&http_client, &node_client, &args.rpc_url).await?;
|
||||||
|
} else {
|
||||||
|
run_once(&http_client, &node_client, &args.rpc_url, args.batch_range).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_continuous_mode(
|
||||||
|
http_client: &Client,
|
||||||
|
node_client: &MainNodeClient,
|
||||||
|
rpc_url: &Url,
|
||||||
|
) -> Result<()> {
|
||||||
|
loop {
|
||||||
|
let batch_range = poll_latest_batch_range(http_client, rpc_url).await?;
|
||||||
|
run_once(http_client, node_client, batch_range, rpc_url).await?;
|
||||||
|
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_once(
|
||||||
|
http_client: &Client,
|
||||||
|
node_client: &MainNodeClient,
|
||||||
|
rpc_url: &Url,
|
||||||
|
batch_range: (L1BatchNumber, L1BatchNumber),
|
||||||
|
) -> Result<()> {
|
||||||
|
let (start_batch_number, end_batch_number) = batch_range;
|
||||||
|
|
||||||
for batch_number in start_batch_number.0..=end_batch_number.0 {
|
for batch_number in start_batch_number.0..=end_batch_number.0 {
|
||||||
let proofs_request = GetProofsRequest {
|
println!("Verifying batch #{}", proof.l1_batch_number);
|
||||||
jsonrpc: "2.0".to_string(),
|
|
||||||
id: 1,
|
let proofs_response = get_tee_proofs(batch_number, http_client, rpc_url).await?;
|
||||||
method: "unstable_getTeeProofs".to_string(),
|
let mut batch_proof_instance = 1;
|
||||||
params: (L1BatchNumber(batch_number), "Sgx".to_string()),
|
|
||||||
};
|
|
||||||
let proofs_response = http_client
|
|
||||||
.post(args.rpc_url.clone())
|
|
||||||
.json(&proofs_request)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?
|
|
||||||
.json::<GetProofsResponse>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for proof in proofs_response
|
for proof in proofs_response
|
||||||
.result
|
.result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|proof| proof.tee_type.to_lowercase() == "sgx")
|
.filter(|proof| proof.tee_type.eq_ignore_ascii_case("Sgx"))
|
||||||
{
|
{
|
||||||
println!(
|
println!(
|
||||||
"Verifying batch #{} proved at {}",
|
" Verifying proof instance {} of the batch, proved at {}"
|
||||||
proof.l1_batch_number, proof.proved_at
|
proof.l1_batch_number, proof.proved_at
|
||||||
);
|
);
|
||||||
let quote_verification_result = verify_attestation_quote(&proof.attestation)?;
|
let verification_result = verify_proof(proof, node_client).await?;
|
||||||
print_quote_verification_summary("e_verification_result);
|
batch_proof_instance += 1;
|
||||||
let public_key = PublicKey::from_slice(
|
|
||||||
"e_verification_result.quote.report_body.reportdata[..PUBLIC_KEY_SIZE],
|
|
||||||
)?;
|
|
||||||
println!("Public key from attestation quote: {}", public_key);
|
|
||||||
let root_hash = node_client
|
|
||||||
.get_root_hash(L1BatchNumber(proof.l1_batch_number))
|
|
||||||
.await?;
|
|
||||||
println!("Root hash: {}", root_hash);
|
|
||||||
verify_signature(&proof.signature, public_key, root_hash)?;
|
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_signature(signature: &[u8], public_key: PublicKey, root_hash: H256) -> Result<()> {
|
async fn get_tee_proofs(
|
||||||
let signature = Signature::from_compact(signature)?;
|
batch_number: u32,
|
||||||
let root_hash_msg = Message::from_digest_slice(&root_hash.0)?;
|
http_client: &Client,
|
||||||
if signature.verify(&root_hash_msg, &public_key).is_ok() {
|
rpc_url: &Url,
|
||||||
println!("Signature verified successfully");
|
) -> Result<Vec<Proof>> {
|
||||||
} else {
|
let proofs_request = GetProofsRequest {
|
||||||
println!("Failed to verify signature");
|
jsonrpc: "2.0".to_string(),
|
||||||
}
|
id: 1,
|
||||||
Ok(())
|
method: "unstable_getTeeProofs".to_string(),
|
||||||
}
|
params: (L1BatchNumber(batch_number), "Sgx".to_string()),
|
||||||
|
};
|
||||||
|
let proofs_response = http_client
|
||||||
|
.post(rpc_url.clone())
|
||||||
|
.json(&proofs_request)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json::<GetProofsResponse>()
|
||||||
|
.await;
|
||||||
|
// TODO return 404 if no proofs found
|
||||||
|
|
||||||
fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerificationResult> {
|
Ok(proofs_response)
|
||||||
println!(
|
|
||||||
"Verifying quote ({} bytes)...",
|
|
||||||
attestation_quote_bytes.len()
|
|
||||||
);
|
|
||||||
let collateral =
|
|
||||||
tee_qv_get_collateral(attestation_quote_bytes).context("Failed to get collateral")?;
|
|
||||||
let unix_time: i64 = std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)?
|
|
||||||
.as_secs() as _;
|
|
||||||
verify_quote_with_collateral(attestation_quote_bytes, Some(&collateral), unix_time)
|
|
||||||
.context("Failed to verify quote with collateral")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_quote_verification_summary(quote_verification_result: &QuoteVerificationResult) {
|
|
||||||
let QuoteVerificationResult {
|
|
||||||
collateral_expired,
|
|
||||||
result,
|
|
||||||
quote,
|
|
||||||
advisories,
|
|
||||||
..
|
|
||||||
} = quote_verification_result;
|
|
||||||
if *collateral_expired {
|
|
||||||
println!("Freshly fetched collateral expired");
|
|
||||||
}
|
|
||||||
let tcblevel = TcbLevel::from(*result);
|
|
||||||
for advisory in advisories {
|
|
||||||
println!("\tInfo: Advisory ID: {advisory}");
|
|
||||||
}
|
|
||||||
println!("Quote verification result: {}", tcblevel);
|
|
||||||
println!("mrsigner: {}", hex::encode(quote.report_body.mrsigner));
|
|
||||||
println!("mrenclave: {}", hex::encode(quote.report_body.mrenclave));
|
|
||||||
println!("reportdata: {}", hex::encode(quote.report_body.reportdata));
|
|
||||||
}
|
}
|
||||||
|
|
67
bin/verify-era-proof-attestation/src/rpc_api.rs
Normal file
67
bin/verify-era-proof-attestation/src/rpc_api.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
use reqwest::Client;
|
||||||
|
use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::time::Duration;
|
||||||
|
use teepot::{
|
||||||
|
client::TcbLevel,
|
||||||
|
sgx::{tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
use zksync_basic_types::{L1BatchNumber, H256};
|
||||||
|
use zksync_types::L2ChainId;
|
||||||
|
use zksync_web3_decl::{
|
||||||
|
client::{Client as NodeClient, L2},
|
||||||
|
error::ClientRpcContext,
|
||||||
|
namespaces::ZksNamespaceClient,
|
||||||
|
};
|
||||||
|
|
||||||
|
trait JsonRpcClient {
|
||||||
|
async fn get_root_hash(&self, batch_number: L1BatchNumber) -> Result<H256>;
|
||||||
|
// TODO implement get_tee_proofs(batch_number, tee_type) once https://crates.io/crates/zksync_web3_decl crate is updated
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MainNodeClient(NodeClient<L2>);
|
||||||
|
|
||||||
|
impl JsonRpcClient for MainNodeClient {
|
||||||
|
async fn get_root_hash(&self, batch_number: L1BatchNumber) -> Result<H256> {
|
||||||
|
self.0
|
||||||
|
.get_l1_batch_details(batch_number)
|
||||||
|
.rpc_context("get_l1_batch_details")
|
||||||
|
.await?
|
||||||
|
.and_then(|res| res.base.root_hash)
|
||||||
|
.ok_or_else(|| anyhow!("No root hash found for batch #{}", batch_number))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON-RPC request and response structures for fetching TEE proofs
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct GetProofsRequest {
|
||||||
|
jsonrpc: String,
|
||||||
|
id: u32,
|
||||||
|
method: String,
|
||||||
|
params: (L1BatchNumber, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct GetProofsResponse {
|
||||||
|
jsonrpc: String,
|
||||||
|
result: Vec<Proof>,
|
||||||
|
id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct Proof {
|
||||||
|
#[serde(rename = "l1BatchNumber")]
|
||||||
|
l1_batch_number: u32,
|
||||||
|
#[serde(rename = "teeType")]
|
||||||
|
tee_type: String,
|
||||||
|
pubkey: Vec<u8>,
|
||||||
|
signature: Vec<u8>,
|
||||||
|
proof: Vec<u8>,
|
||||||
|
#[serde(rename = "provedAt")]
|
||||||
|
proved_at: String,
|
||||||
|
attestation: Vec<u8>,
|
||||||
|
}
|
82
bin/verify-era-proof-attestation/src/verifier.rs
Normal file
82
bin/verify-era-proof-attestation/src/verifier.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
use reqwest::Client;
|
||||||
|
use secp256k1::{constants::PUBLIC_KEY_SIZE, ecdsa::Signature, Message, PublicKey};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::time::Duration;
|
||||||
|
use teepot::{
|
||||||
|
client::TcbLevel,
|
||||||
|
sgx::{tee_qv_get_collateral, verify_quote_with_collateral, QuoteVerificationResult},
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
use zksync_basic_types::{L1BatchNumber, H256};
|
||||||
|
use zksync_types::L2ChainId;
|
||||||
|
use zksync_web3_decl::{
|
||||||
|
client::{Client as NodeClient, L2},
|
||||||
|
error::ClientRpcContext,
|
||||||
|
namespaces::ZksNamespaceClient,
|
||||||
|
};
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
if signature.verify(&root_hash_msg, &public_key).is_ok() {
|
||||||
|
println!(" Signature verified successfully");
|
||||||
|
} else {
|
||||||
|
println!(" Failed to verify signature");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_proof(proof: Proof, node_client: &MainNodeClient) -> Result<(), anyhow::Error> {
|
||||||
|
let quote_verification_result = verify_attestation_quote(&proof.attestation)?;
|
||||||
|
print_quote_verification_summary("e_verification_result);
|
||||||
|
let public_key = PublicKey::from_slice(
|
||||||
|
"e_verification_result.quote.report_body.reportdata[..PUBLIC_KEY_SIZE],
|
||||||
|
)?;
|
||||||
|
println!(" Public key from attestation quote: {}", public_key);
|
||||||
|
let root_hash = node_client
|
||||||
|
.get_root_hash(L1BatchNumber(proof.l1_batch_number))
|
||||||
|
.await?;
|
||||||
|
println!(" Root hash: {}", root_hash);
|
||||||
|
verify_signature(&proof.signature, public_key, root_hash)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_quote_verification_summary(quote_verification_result: &QuoteVerificationResult) {
|
||||||
|
let QuoteVerificationResult {
|
||||||
|
collateral_expired,
|
||||||
|
result,
|
||||||
|
quote,
|
||||||
|
advisories,
|
||||||
|
..
|
||||||
|
} = quote_verification_result;
|
||||||
|
if *collateral_expired {
|
||||||
|
println!(" Freshly fetched collateral expired");
|
||||||
|
}
|
||||||
|
let tcblevel = TcbLevel::from(*result);
|
||||||
|
for advisory in advisories {
|
||||||
|
println!(" \tInfo: Advisory ID: {advisory}");
|
||||||
|
}
|
||||||
|
println!(" Quote verification result: {}", tcblevel);
|
||||||
|
println!(" mrsigner: {}", hex::encode(quote.report_body.mrsigner));
|
||||||
|
println!(" mrenclave: {}", hex::encode(quote.report_body.mrenclave));
|
||||||
|
println!(
|
||||||
|
" reportdata: {}",
|
||||||
|
hex::encode(quote.report_body.reportdata)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerificationResult> {
|
||||||
|
println!(
|
||||||
|
"Verifying quote ({} bytes)...",
|
||||||
|
attestation_quote_bytes.len()
|
||||||
|
);
|
||||||
|
let collateral =
|
||||||
|
tee_qv_get_collateral(attestation_quote_bytes).context("Failed to get collateral")?;
|
||||||
|
let unix_time: i64 = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)?
|
||||||
|
.as_secs() as _;
|
||||||
|
verify_quote_with_collateral(attestation_quote_bytes, Some(&collateral), unix_time)
|
||||||
|
.context("Failed to verify quote with collateral")
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue