Merge pull request #189 from matter-labs/patrick/sgx-rpc-attestation-verifier

feat(verify-attestation): RPC attestation and batch signature verification binary
This commit is contained in:
Harald Hoyer 2024-08-30 12:37:01 +02:00 committed by GitHub
commit 6b0b0b69ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 2991 additions and 170 deletions

View file

@ -76,6 +76,7 @@ jobs:
- { nixpackage: 'container-self-attestation-test-sgx-dcap' }
- { nixpackage: 'container-self-attestation-test-sgx-azure' }
- { nixpackage: 'container-verify-attestation-sgx' }
- { nixpackage: 'container-verify-era-proof-attestation-sgx' }
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27

2902
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -42,6 +42,7 @@ pgp = "0.13"
p256 = "0.13.2"
pkcs8 = { version = "0.10" }
rand = "0.8"
reqwest = { version = "0.12", features = ["json"] }
ring = { version = "0.17.8", features = ["std"], default-features = false }
rsa = { version = "0.9.6", features = ["sha2", "pem"] }
rustls = { version = "0.22" }
@ -60,7 +61,10 @@ tracing = "0.1"
tracing-actix-web = "0.7"
tracing-log = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
url = "2.5.2"
x509-cert = { version = "0.2", features = ["builder", "signature"] }
zeroize = { version = "1.7.0", features = ["serde"] }
webpki-roots = "0.26.1"
zksync_basic_types = "=0.1.0"
zksync_web3_decl = "=0.1.0"
zksync_types = "=0.1.0"

View file

@ -0,0 +1,21 @@
[package]
name = "verify-era-proof-attestation"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
anyhow.workspace = true
clap.workspace = true
hex.workspace = true
reqwest.workspace = true
secp256k1.workspace = true
serde.workspace = true
teepot.workspace = true
tokio.workspace = true
url.workspace = true
zksync_basic_types.workspace = true
zksync_types.workspace = true
zksync_web3_decl.workspace = true

View file

@ -0,0 +1,176 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2024 Matter Labs
//! Tool for SGX attestation and batch signature verification
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 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,
};
#[derive(Parser, Debug)]
#[command(author = "Matter Labs", version, about = "SGX attestation and batch signature verifier", long_about = None)]
struct Arguments {
/// The batch number for which we want to verify the attestation and signature.
#[clap(short = 'n', long)]
batch_number: L1BatchNumber,
/// URL of the RPC server to query for the batch attestation and signature.
#[clap(short, long)]
rpc_url: Url,
/// Chain ID of the network to query.
#[clap(short, long, default_value_t = L2ChainId::default().as_u64())]
chain_id: u64,
}
trait JsonRpcClient {
async fn get_root_hash(&self, batch_number: L1BatchNumber) -> Result<H256>;
// TODO implement get_tee_proofs(batch_number, tee_type) once 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]
async fn main() -> Result<()> {
let args = Arguments::parse();
let node_client: NodeClient<L2> = NodeClient::http(args.rpc_url.clone().into())
.context("failed creating JSON-RPC client for main node")?
.for_network(
L2ChainId::try_from(args.chain_id)
.map_err(anyhow::Error::msg)?
.into(),
)
.build();
let node_client = MainNodeClient(node_client);
let http_client = Client::new();
let request = GetProofsRequest {
jsonrpc: "2.0".to_string(),
id: 1,
method: "unstable_getTeeProofs".to_string(),
params: (args.batch_number, "Sgx".to_string()),
};
let response = http_client
.post(args.rpc_url)
.json(&request)
.send()
.await?
.error_for_status()?
.json::<GetProofsResponse>()
.await?;
for proof in response.result {
println!("Verifying batch #{}", proof.l1_batch_number);
let quote_verification_result = verify_attestation_quote(&proof.attestation)?;
print_quote_verification_summary(&quote_verification_result);
let public_key = PublicKey::from_slice(
&quote_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(args.batch_number).await?;
println!("Root hash: {}", root_hash);
verify_signature(&proof.signature, public_key, root_hash)?;
println!();
}
Ok(())
}
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(())
}
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")
}
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));
}

View file

@ -54,6 +54,9 @@
shells = {
default = "teepot";
};
devShells = {
default = "teepot";
};
};
outputs-builder = channels: {

View file

@ -3,6 +3,6 @@
{ teepotCrate }: teepotCrate.craneLib.cargoClippy (
teepotCrate.commonArgs // {
pname = "teepot";
inherit (teepotCrate) cargoArtifacts NIX_OUTPATH_USED_AS_RANDOM_SEED;
inherit (teepotCrate) cargoArtifacts;
}
)

View file

@ -0,0 +1,33 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs
{ dockerTools
, buildEnv
, teepot
, openssl
, curl
, nixsgx
, pkg-config
}:
dockerTools.buildLayeredImage {
name = "verify-era-proof-attestation";
config.Entrypoint = [ "${teepot.teepot.verify_era_proof_attestation}/bin/verify-era-proof-attestation" ];
config.Env = [ "LD_LIBRARY_PATH=/lib" ];
contents = buildEnv {
name = "image-root";
paths = with dockerTools; with nixsgx;[
pkg-config
openssl.out
curl.out
sgx-dcap.quote_verify
sgx-dcap.default_qpl
teepot.teepot.verify_era_proof_attestation
usrBinEnv
binSh
caCertificates
fakeNss
];
pathsToLink = [ "/bin" "/lib" "/etc" "/share" ];
};
}

View file

@ -3,9 +3,7 @@
{ teepotCrate }: teepotCrate.craneLib.buildPackage (
teepotCrate.commonArgs // {
pname = "teepot";
inherit (teepotCrate) cargoArtifacts
NIX_OUTPATH_USED_AS_RANDOM_SEED;
inherit (teepotCrate) cargoArtifacts;
passthru = {
inherit (teepotCrate) rustPlatform
@ -13,7 +11,6 @@
commonArgs
craneLib
cargoArtifacts;
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
};
outputs = [
@ -29,6 +26,7 @@
"vault_admin"
"vault_unseal"
"verify_attestation"
"verify_era_proof_attestation"
];
postInstall = ''
removeReferencesToVendoredSources "$out" "$cargoVendorDir"

View file

@ -9,6 +9,7 @@
}:
mkShell {
inputsFrom = [ teepot.teepot ];
packages = [
dive
taplo

View file

@ -8,7 +8,7 @@
, rust-bin
, pkgs
, src
, ...
, openssl
}:
let
rustVersion = rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
@ -24,6 +24,7 @@ let
];
buildInputs = [
openssl
nixsgx.sgx-sdk
nixsgx.sgx-dcap
nixsgx.sgx-dcap.quote_verify
@ -31,7 +32,6 @@ let
strictDeps = true;
src = with lib.fileset; toSource {
root = src;
fileset = unions [
@ -46,12 +46,15 @@ let
};
checkType = "debug";
env = {
OPENSSL_NO_VENDOR = "1";
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
};
};
cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
pname = "teepot-workspace";
inherit NIX_OUTPATH_USED_AS_RANDOM_SEED;
});
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
in
{
inherit rustPlatform
@ -59,5 +62,4 @@ in
commonArgs
craneLib
cargoArtifacts;
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
}