mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-21 23:23:57 +02:00
feat(verify-attestation): RPC attestation and batch signature verification binary
This is another variant of the binary tool for verifying attestation and the signature of a given batch. Unlike the existing tool, this variant does not require you to provide two separate files—one for the attestation and one for the signature. Instead, it automatically fetches both from the RPC node. Unfortunately, after discussing with @popzxc, we found that there is no way to reuse the RPC client because our published crates on crates.io are outdated and do not include the recently merged TEE-specific code changes. To be fixed in the future.
This commit is contained in:
parent
27f35a7432
commit
5e4b8901b0
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