mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-22 23:44:48 +02:00
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:
commit
6b0b0b69ca
11 changed files with 2991 additions and 170 deletions
1
.github/workflows/nix.yml
vendored
1
.github/workflows/nix.yml
vendored
|
@ -76,6 +76,7 @@ jobs:
|
||||||
- { nixpackage: 'container-self-attestation-test-sgx-dcap' }
|
- { nixpackage: 'container-self-attestation-test-sgx-dcap' }
|
||||||
- { nixpackage: 'container-self-attestation-test-sgx-azure' }
|
- { nixpackage: 'container-self-attestation-test-sgx-azure' }
|
||||||
- { nixpackage: 'container-verify-attestation-sgx' }
|
- { nixpackage: 'container-verify-attestation-sgx' }
|
||||||
|
- { nixpackage: 'container-verify-era-proof-attestation-sgx' }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v27
|
- uses: cachix/install-nix-action@v27
|
||||||
|
|
2902
Cargo.lock
generated
2902
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -42,6 +42,7 @@ pgp = "0.13"
|
||||||
p256 = "0.13.2"
|
p256 = "0.13.2"
|
||||||
pkcs8 = { version = "0.10" }
|
pkcs8 = { version = "0.10" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
ring = { version = "0.17.8", features = ["std"], default-features = false }
|
ring = { version = "0.17.8", features = ["std"], default-features = false }
|
||||||
rsa = { version = "0.9.6", features = ["sha2", "pem"] }
|
rsa = { version = "0.9.6", features = ["sha2", "pem"] }
|
||||||
rustls = { version = "0.22" }
|
rustls = { version = "0.22" }
|
||||||
|
@ -60,7 +61,10 @@ tracing = "0.1"
|
||||||
tracing-actix-web = "0.7"
|
tracing-actix-web = "0.7"
|
||||||
tracing-log = "0.2"
|
tracing-log = "0.2"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
url = "2.5.2"
|
||||||
x509-cert = { version = "0.2", features = ["builder", "signature"] }
|
x509-cert = { version = "0.2", features = ["builder", "signature"] }
|
||||||
zeroize = { version = "1.7.0", features = ["serde"] }
|
zeroize = { version = "1.7.0", features = ["serde"] }
|
||||||
webpki-roots = "0.26.1"
|
webpki-roots = "0.26.1"
|
||||||
zksync_basic_types = "=0.1.0"
|
zksync_basic_types = "=0.1.0"
|
||||||
|
zksync_web3_decl = "=0.1.0"
|
||||||
|
zksync_types = "=0.1.0"
|
||||||
|
|
21
bin/verify-era-proof-attestation/Cargo.toml
Normal file
21
bin/verify-era-proof-attestation/Cargo.toml
Normal 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
|
176
bin/verify-era-proof-attestation/src/main.rs
Normal file
176
bin/verify-era-proof-attestation/src/main.rs
Normal 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("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(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));
|
||||||
|
}
|
|
@ -54,6 +54,9 @@
|
||||||
shells = {
|
shells = {
|
||||||
default = "teepot";
|
default = "teepot";
|
||||||
};
|
};
|
||||||
|
devShells = {
|
||||||
|
default = "teepot";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs-builder = channels: {
|
outputs-builder = channels: {
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
{ teepotCrate }: teepotCrate.craneLib.cargoClippy (
|
{ teepotCrate }: teepotCrate.craneLib.cargoClippy (
|
||||||
teepotCrate.commonArgs // {
|
teepotCrate.commonArgs // {
|
||||||
pname = "teepot";
|
pname = "teepot";
|
||||||
inherit (teepotCrate) cargoArtifacts NIX_OUTPATH_USED_AS_RANDOM_SEED;
|
inherit (teepotCrate) cargoArtifacts;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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" ];
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,9 +3,7 @@
|
||||||
{ teepotCrate }: teepotCrate.craneLib.buildPackage (
|
{ teepotCrate }: teepotCrate.craneLib.buildPackage (
|
||||||
teepotCrate.commonArgs // {
|
teepotCrate.commonArgs // {
|
||||||
pname = "teepot";
|
pname = "teepot";
|
||||||
inherit (teepotCrate) cargoArtifacts
|
inherit (teepotCrate) cargoArtifacts;
|
||||||
NIX_OUTPATH_USED_AS_RANDOM_SEED;
|
|
||||||
|
|
||||||
|
|
||||||
passthru = {
|
passthru = {
|
||||||
inherit (teepotCrate) rustPlatform
|
inherit (teepotCrate) rustPlatform
|
||||||
|
@ -13,7 +11,6 @@
|
||||||
commonArgs
|
commonArgs
|
||||||
craneLib
|
craneLib
|
||||||
cargoArtifacts;
|
cargoArtifacts;
|
||||||
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = [
|
outputs = [
|
||||||
|
@ -29,6 +26,7 @@
|
||||||
"vault_admin"
|
"vault_admin"
|
||||||
"vault_unseal"
|
"vault_unseal"
|
||||||
"verify_attestation"
|
"verify_attestation"
|
||||||
|
"verify_era_proof_attestation"
|
||||||
];
|
];
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
removeReferencesToVendoredSources "$out" "$cargoVendorDir"
|
removeReferencesToVendoredSources "$out" "$cargoVendorDir"
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
}:
|
}:
|
||||||
mkShell {
|
mkShell {
|
||||||
inputsFrom = [ teepot.teepot ];
|
inputsFrom = [ teepot.teepot ];
|
||||||
|
|
||||||
packages = [
|
packages = [
|
||||||
dive
|
dive
|
||||||
taplo
|
taplo
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
, rust-bin
|
, rust-bin
|
||||||
, pkgs
|
, pkgs
|
||||||
, src
|
, src
|
||||||
, ...
|
, openssl
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
rustVersion = rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
rustVersion = rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||||
|
@ -24,6 +24,7 @@ let
|
||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
|
openssl
|
||||||
nixsgx.sgx-sdk
|
nixsgx.sgx-sdk
|
||||||
nixsgx.sgx-dcap
|
nixsgx.sgx-dcap
|
||||||
nixsgx.sgx-dcap.quote_verify
|
nixsgx.sgx-dcap.quote_verify
|
||||||
|
@ -31,7 +32,6 @@ let
|
||||||
|
|
||||||
strictDeps = true;
|
strictDeps = true;
|
||||||
|
|
||||||
|
|
||||||
src = with lib.fileset; toSource {
|
src = with lib.fileset; toSource {
|
||||||
root = src;
|
root = src;
|
||||||
fileset = unions [
|
fileset = unions [
|
||||||
|
@ -46,12 +46,15 @@ let
|
||||||
};
|
};
|
||||||
|
|
||||||
checkType = "debug";
|
checkType = "debug";
|
||||||
|
env = {
|
||||||
|
OPENSSL_NO_VENDOR = "1";
|
||||||
|
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
|
cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
|
||||||
pname = "teepot-workspace";
|
pname = "teepot-workspace";
|
||||||
inherit NIX_OUTPATH_USED_AS_RANDOM_SEED;
|
|
||||||
});
|
});
|
||||||
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit rustPlatform
|
inherit rustPlatform
|
||||||
|
@ -59,5 +62,4 @@ in
|
||||||
commonArgs
|
commonArgs
|
||||||
craneLib
|
craneLib
|
||||||
cargoArtifacts;
|
cargoArtifacts;
|
||||||
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue