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(verify-era-proof-attestation): added continuous mode with attestation policies #198

Merged
merged 1 commit into from
Sep 16, 2024
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
33 changes: 33 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ actix-http = "3"
actix-tls = "3"
actix-web = { version = "4.5", features = ["rustls-0_22"] }
anyhow = "1.0.82"
ctrlc = "3.4"
awc = { version = "3.4", features = ["rustls-0_22-webpki-roots"] }
base64 = "0.22.0"
bitflags = "2.5"
Expand All @@ -34,6 +35,7 @@ getrandom = "0.2.14"
hex = { version = "0.4.3", features = ["std"], default-features = false }
intel-tee-quote-verification-rs = { package = "teepot-tee-quote-verification-rs", path = "crates/teepot-tee-quote-verification-rs", version = "0.2.3-alpha.1" }
intel-tee-quote-verification-sys = { version = "0.2.1" }
jsonrpsee-types = { version = "0.23", default-features = false }
secp256k1 = { version = "0.29", features = ["rand-std", "global-context"] }
log = "0.4"
num-integer = "0.1.46"
Expand Down
11 changes: 8 additions & 3 deletions bin/verify-era-proof-attestation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
[package]
name = "verify-era-proof-attestation"
version.workspace = true
edition.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
name = "verify-era-proof-attestation"
repository.workspace = true
version.workspace = true

[dependencies]
anyhow.workspace = true
clap.workspace = true
ctrlc.workspace = true
hex.workspace = true
jsonrpsee-types.workspace = true
reqwest.workspace = true
secp256k1.workspace = true
serde.workspace = true
teepot.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-log.workspace = true
tracing-subscriber.workspace = true
url.workspace = true
zksync_basic_types.workspace = true
zksync_types.workspace = true
Expand Down
123 changes: 123 additions & 0 deletions bin/verify-era-proof-attestation/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2024 Matter Labs

use anyhow::{anyhow, Result};
use clap::{ArgGroup, Args, Parser};
use std::time::Duration;
use teepot::sgx::{parse_tcb_levels, EnumSet, TcbLevel};
use tracing_subscriber::filter::LevelFilter;
use url::Url;
use zksync_basic_types::L1BatchNumber;
use zksync_types::L2ChainId;

#[derive(Parser, Debug, Clone)]
#[command(author = "Matter Labs", version, about = "SGX attestation and batch signature verifier", long_about = None)]
#[clap(group(
ArgGroup::new("mode")
.required(true)
.args(&["batch_range", "continuous"]),
))]
pub struct Arguments {
#[clap(long, default_value_t = LevelFilter::WARN, value_parser = LogLevelParser)]
pub log_level: LevelFilter,
/// The batch number or range of batch numbers to verify the attestation and signature (e.g.,
/// "42" or "42-45"). This option is mutually exclusive with the `--continuous` mode.
#[clap(short = 'n', long = "batch", value_parser = parse_batch_range)]
pub batch_range: Option<(L1BatchNumber, L1BatchNumber)>,
/// Continuous mode: keep verifying new batches until interrupted. This option is mutually
/// exclusive with the `--batch` option.
#[clap(long, value_name = "FIRST_BATCH")]
pub continuous: Option<L1BatchNumber>,
/// URL of the RPC server to query for the batch attestation and signature.
#[clap(long = "rpc")]
pub rpc_url: Url,
/// Chain ID of the network to query.
#[clap(long = "chain", default_value_t = L2ChainId::default().as_u64())]
pub chain_id: u64,
/// Rate limit between requests in milliseconds.
#[clap(long, default_value = "0", value_parser = parse_duration)]
pub rate_limit: Duration,
/// Criteria for valid attestation policy. Invalid proofs will be rejected.
#[clap(flatten)]
pub attestation_policy: AttestationPolicyArgs,
}

/// Attestation policy implemented as a set of criteria that must be met by SGX attestation.
#[derive(Args, Debug, Clone)]
pub struct AttestationPolicyArgs {
/// Comma-separated list of allowed hex-encoded SGX mrsigners. Batch attestation must consist of
/// one of these mrsigners. If the list is empty, the mrsigner check is skipped.
#[arg(long = "mrsigners")]
pub sgx_mrsigners: Option<String>,
/// Comma-separated list of allowed hex-encoded SGX mrenclaves. Batch attestation must consist
/// of one of these mrenclaves. If the list is empty, the mrenclave check is skipped.
#[arg(long = "mrenclaves")]
pub sgx_mrenclaves: Option<String>,
/// Comma-separated list of allowed TCB levels. If the list is empty, the TCB level check is
/// skipped. Allowed values: Ok, ConfigNeeded, ConfigAndSwHardeningNeeded, SwHardeningNeeded,
/// OutOfDate, OutOfDateConfigNeeded.
#[arg(long, value_parser = parse_tcb_levels, default_value = "Ok")]
pub sgx_allowed_tcb_levels: EnumSet<TcbLevel>,
}

fn parse_batch_range(s: &str) -> Result<(L1BatchNumber, L1BatchNumber)> {
let parse = |s: &str| {
s.parse::<u32>()
.map(L1BatchNumber::from)
.map_err(|e| anyhow!(e))
};
match s.split_once('-') {
Some((start, end)) => {
let (start, end) = (parse(start)?, parse(end)?);
if start > end {
Err(anyhow!(
"Start batch number ({}) must be less than or equal to end batch number ({})",
start,
end
))
} else {
Ok((start, end))
}
}
None => {
let batch_number = parse(s)?;
Ok((batch_number, batch_number))
}
}
}

fn parse_duration(s: &str) -> Result<Duration> {
let millis = s.parse()?;
Ok(Duration::from_millis(millis))
}

#[derive(Clone)]
struct LogLevelParser;

impl clap::builder::TypedValueParser for LogLevelParser {
type Value = LevelFilter;

fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
clap::builder::TypedValueParser::parse(self, cmd, arg, value.to_owned())
}

fn parse(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: std::ffi::OsString,
) -> std::result::Result<Self::Value, clap::Error> {
use std::str::FromStr;
let p = clap::builder::PossibleValuesParser::new([
"off", "error", "warn", "info", "debug", "trace",
]);
let v = p.parse(cmd, arg, value)?;

Ok(LevelFilter::from_str(&v).unwrap())
}
}
45 changes: 45 additions & 0 deletions bin/verify-era-proof-attestation/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2024 Matter Labs

use anyhow::{anyhow, Context, Result};
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,
};

pub 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
}

pub struct MainNodeClient(NodeClient<L2>);

impl MainNodeClient {
pub fn new(rpc_url: Url, chain_id: u64) -> Result<Self> {
let node_client = NodeClient::http(rpc_url.into())
.context("failed creating JSON-RPC client for main node")?
.for_network(
L2ChainId::try_from(chain_id)
.map_err(anyhow::Error::msg)?
.into(),
)
.build();

Ok(MainNodeClient(node_client))
}
}

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))
}
}
Loading