From 0575785378fab65353195620c148c414f7e9209a Mon Sep 17 00:00:00 2001 From: juan518munoz <62400508+juan518munoz@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:39:33 -0300 Subject: [PATCH] feat(eigen-cllient-extra-features): Address more comments (#386) * use `H160` for `DEFAULT_EIGENDA_SVC_MANAGER_ADDRESS` * `parse` instead of `from_str` * replace val with shif operation * replace `.map_err` with `.context` * remove needless parentheses * use `div_ceil` * remove needles `Clone` derives * remove redundant `to_vec()` * use `anyhow::bail!` * remove needless `Option` and obscuring `anyhow::Result` * move verifier test to separate file * replace u64 constant with Duration * use `ethabi::encode()` * refactor `decode_bytes` * wrap `G1Affine` inside of `Box` / fix clippy * use `SensitiveUrl` for `eigenda_eth_rpc` * refactor `save_point` fn * remove retriable error wrapping * replace `PKSigningClient` for simpler L1 Client * remove chain id from `EigenConfig` * use temp dir for kzg points * refactor `VerifierClient` trait * remove `VerifierConfig` * fix eigen config not initializing correctly * fix/refactor mock tests * remove unwraps * address comments/suggestions * improve Error handling * create temp file inside closure * Remove unnecessary clone * address comments * address comments * change `path()` fn to return `&std::path::Path` * replace `Path(String)` for `Path(PathBuf)` --------- Co-authored-by: Gianbelinche <39842759+gianbelinche@users.noreply.github.com> --- Cargo.lock | 1 + core/bin/zksync_server/src/node_builder.rs | 2 +- .../lib/config/src/configs/da_client/eigen.rs | 19 +- core/lib/env_config/src/da_client.rs | 15 +- core/lib/protobuf_config/src/da_client.rs | 16 +- .../src/proto/config/da_client.proto | 2 +- core/node/da_clients/Cargo.toml | 1 + core/node/da_clients/src/eigen/client.rs | 28 +- core/node/da_clients/src/eigen/errors.rs | 111 ++ core/node/da_clients/src/eigen/mod.rs | 1 + core/node/da_clients/src/eigen/sdk.rs | 103 +- core/node/da_clients/src/eigen/verifier.rs | 1347 ----------------- .../node/da_clients/src/eigen/verifier/mod.rs | 545 +++++++ .../da_clients/src/eigen/verifier/tests.rs | 795 ++++++++++ 14 files changed, 1532 insertions(+), 1454 deletions(-) create mode 100644 core/node/da_clients/src/eigen/errors.rs delete mode 100644 core/node/da_clients/src/eigen/verifier.rs create mode 100644 core/node/da_clients/src/eigen/verifier/mod.rs create mode 100644 core/node/da_clients/src/eigen/verifier/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 5bc26bf5e260..16d0bc9a007c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11822,6 +11822,7 @@ dependencies = [ "sha3 0.10.8", "subxt-metadata", "subxt-signer", + "tempfile", "thiserror", "tiny-keccak 2.0.2", "tokio", diff --git a/core/bin/zksync_server/src/node_builder.rs b/core/bin/zksync_server/src/node_builder.rs index 8350d2f0b422..cfafbbcd3ed4 100644 --- a/core/bin/zksync_server/src/node_builder.rs +++ b/core/bin/zksync_server/src/node_builder.rs @@ -550,7 +550,7 @@ impl MainNodeBuilder { (DAClientConfig::Eigen(mut config), DataAvailabilitySecrets::Eigen(secret)) => { if config.eigenda_eth_rpc.is_none() { - config.eigenda_eth_rpc = Some(l1_secrets.l1_rpc_url.expose_str().to_string()); + config.eigenda_eth_rpc = Some(l1_secrets.l1_rpc_url); } self.node.add_layer(EigenWiringLayer::new(config, secret)); } diff --git a/core/lib/config/src/configs/da_client/eigen.rs b/core/lib/config/src/configs/da_client/eigen.rs index 886b67768f4a..3525eef22ea2 100644 --- a/core/lib/config/src/configs/da_client/eigen.rs +++ b/core/lib/config/src/configs/da_client/eigen.rs @@ -1,10 +1,13 @@ use std::str::FromStr; use serde::Deserialize; -use zksync_basic_types::{secrets::PrivateKey, Address, H160}; +use zksync_basic_types::{secrets::PrivateKey, url::SensitiveUrl, Address, H160}; /// Default address of the EigenDA service manager contract deployed on Holesky. -const DEFAULT_EIGENDA_SVC_MANAGER_ADDRESS: &str = "0xD4A7E1Bd8015057293f0D0A557088c286942e84b"; +const DEFAULT_EIGENDA_SVC_MANAGER_ADDRESS: H160 = H160([ + 0xd4, 0xa7, 0xe1, 0xbd, 0x80, 0x15, 0x05, 0x72, 0x93, 0xf0, 0xd0, 0xa5, 0x57, 0x08, 0x8c, 0x28, + 0x69, 0x42, 0xe8, 0x4b, +]); /// Configuration for the EigenDA remote disperser client. #[derive(Clone, Debug, PartialEq, Deserialize)] @@ -15,19 +18,19 @@ pub struct EigenConfig { /// a value less or equal to 0 means that the disperser will not wait for finalization pub settlement_layer_confirmation_depth: u32, /// URL of the Ethereum RPC server - pub eigenda_eth_rpc: Option, + pub eigenda_eth_rpc: Option, /// Address of the service manager contract pub eigenda_svc_manager_address: Address, /// Wait for the blob to be finalized before returning the response pub wait_for_finalization: bool, /// Authenticated dispersal pub authenticated: bool, + /// Optional path to downloaded points directory + pub points_dir: Option, /// Url to the file containing the G1 point used for KZG pub g1_url: String, /// Url to the file containing the G2 point used for KZG pub g2_url: String, - /// Chain ID of the Ethereum network - pub chain_id: u64, } impl Default for EigenConfig { @@ -35,13 +38,13 @@ impl Default for EigenConfig { Self { disperser_rpc: "https://disperser-holesky.eigenda.xyz:443".to_string(), settlement_layer_confirmation_depth: 0, - eigenda_eth_rpc: Some("https://ethereum-holesky-rpc.publicnode.com".to_string()), - eigenda_svc_manager_address: H160::from_str(DEFAULT_EIGENDA_SVC_MANAGER_ADDRESS).unwrap_or_default(), + eigenda_eth_rpc: Some(SensitiveUrl::from_str("https://ethereum-holesky-rpc.publicnode.com").unwrap()), // Safe to unwrap, never fails + eigenda_svc_manager_address: DEFAULT_EIGENDA_SVC_MANAGER_ADDRESS, wait_for_finalization: false, authenticated: false, + points_dir: None, g1_url: "https://github.com/Layr-Labs/eigenda-proxy/raw/2fd70b99ef5bf137d7bbca3461cf9e1f2c899451/resources/g1.point".to_string(), g2_url: "https://github.com/Layr-Labs/eigenda-proxy/raw/2fd70b99ef5bf137d7bbca3461cf9e1f2c899451/resources/g2.point.powerOf2".to_string(), - chain_id: 19000, } } } diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index 2c40a810b05c..4469dd1afe4f 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -92,7 +92,7 @@ impl FromEnv for DataAvailabilitySecrets { mod tests { use std::str::FromStr; - use zksync_basic_types::H160; + use zksync_basic_types::url::SensitiveUrl; use zksync_config::{ configs::{ da_client::{ @@ -257,9 +257,9 @@ mod tests { DA_EIGENDA_SVC_MANAGER_ADDRESS="0x0000000000000000000000000000000000000123" DA_WAIT_FOR_FINALIZATION=true DA_AUTHENTICATED=false + DA_POINTS_DIR="resources/" DA_G1_URL="resources1" DA_G2_URL="resources2" - DA_CHAIN_ID=1 "#; lock.set_env(config); @@ -269,16 +269,15 @@ mod tests { DAClientConfig::Eigen(EigenConfig { disperser_rpc: "http://localhost:8080".to_string(), settlement_layer_confirmation_depth: 0, - eigenda_eth_rpc: Some("http://localhost:8545".to_string()), - eigenda_svc_manager_address: H160::from_str( - "0x0000000000000000000000000000000000000123" - ) - .unwrap(), + eigenda_eth_rpc: Some(SensitiveUrl::from_str("http://localhost:8545").unwrap()), + eigenda_svc_manager_address: "0x0000000000000000000000000000000000000123" + .parse() + .unwrap(), wait_for_finalization: true, authenticated: false, + points_dir: Some("resources/".to_string()), g1_url: "resources1".to_string(), g2_url: "resources2".to_string(), - chain_id: 1 }) ); } diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index a236718d3138..9ba38336da52 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use anyhow::Context; use zksync_config::configs::{ self, @@ -9,6 +11,7 @@ use zksync_config::configs::{ }, }; use zksync_protobuf::{required, ProtoRepr}; +use zksync_types::url::SensitiveUrl; use crate::{ parse_h160, @@ -66,16 +69,18 @@ impl ProtoRepr for proto::DataAvailabilityClient { &conf.settlement_layer_confirmation_depth, ) .context("settlement_layer_confirmation_depth")?, - eigenda_eth_rpc: required(&conf.eigenda_eth_rpc).ok().cloned(), + eigenda_eth_rpc: Some(SensitiveUrl::from_str( + required(&conf.eigenda_eth_rpc).context("eigenda_eth_rpc")?, + )?), eigenda_svc_manager_address: required(&conf.eigenda_svc_manager_address) .and_then(|x| parse_h160(x)) .context("eigenda_svc_manager_address")?, wait_for_finalization: *required(&conf.wait_for_finalization) .context("wait_for_finalization")?, authenticated: *required(&conf.authenticated).context("authenticated")?, + points_dir: conf.points_dir.clone(), g1_url: required(&conf.g1_url).context("g1_url")?.clone(), g2_url: required(&conf.g2_url).context("g2_url")?.clone(), - chain_id: *required(&conf.chain_id).context("chain_id")?, }), proto::data_availability_client::Config::ObjectStore(conf) => { ObjectStore(object_store_proto::ObjectStore::read(conf)?) @@ -118,7 +123,10 @@ impl ProtoRepr for proto::DataAvailabilityClient { settlement_layer_confirmation_depth: Some( config.settlement_layer_confirmation_depth, ), - eigenda_eth_rpc: config.eigenda_eth_rpc.clone(), + eigenda_eth_rpc: config + .eigenda_eth_rpc + .as_ref() + .map(|a| a.expose_str().to_string()), eigenda_svc_manager_address: Some(format!( "{:?}", config.eigenda_svc_manager_address @@ -127,7 +135,7 @@ impl ProtoRepr for proto::DataAvailabilityClient { authenticated: Some(config.authenticated), g1_url: Some(config.g1_url.clone()), g2_url: Some(config.g2_url.clone()), - chain_id: Some(config.chain_id), + points_dir: config.points_dir.as_ref().map(|a| a.to_string()), }), ObjectStore(config) => proto::data_availability_client::Config::ObjectStore( object_store_proto::ObjectStore::build(config), diff --git a/core/lib/protobuf_config/src/proto/config/da_client.proto b/core/lib/protobuf_config/src/proto/config/da_client.proto index cf4318c520d7..21ac2fb5e116 100644 --- a/core/lib/protobuf_config/src/proto/config/da_client.proto +++ b/core/lib/protobuf_config/src/proto/config/da_client.proto @@ -45,7 +45,7 @@ message EigenConfig { optional bool authenticated = 8; optional string g1_url = 9; optional string g2_url = 10; - optional uint64 chain_id = 11; + optional string points_dir = 11; reserved 1,2; reserved "rpc_node_url","inclusion_polling_interval_ms"; } diff --git a/core/node/da_clients/Cargo.toml b/core/node/da_clients/Cargo.toml index a0bc18567196..5a7930e433e8 100644 --- a/core/node/da_clients/Cargo.toml +++ b/core/node/da_clients/Cargo.toml @@ -66,6 +66,7 @@ zksync_web3_decl.workspace = true zksync_eth_client.workspace = true url.workspace = true thiserror.workspace = true +tempfile.workspace = true [dev-dependencies] serial_test.workspace = true diff --git a/core/node/da_clients/src/eigen/client.rs b/core/node/da_clients/src/eigen/client.rs index 823dcc746504..e9e49ee49a77 100644 --- a/core/node/da_clients/src/eigen/client.rs +++ b/core/node/da_clients/src/eigen/client.rs @@ -90,19 +90,13 @@ mod tests { use backon::{ConstantBuilder, Retryable}; use serial_test::file_serial; use zksync_config::{configs::da_client::eigen::EigenSecrets, EigenConfig}; - use zksync_da_client::{ - types::{DAError, DispatchResponse}, - DataAvailabilityClient, - }; + use zksync_da_client::{types::DispatchResponse, DataAvailabilityClient}; use zksync_types::secrets::PrivateKey; use crate::eigen::{blob_info::BlobInfo, EigenClient, GetBlobData}; impl EigenClient { - async fn get_blob_data( - &self, - blob_id: BlobInfo, - ) -> anyhow::Result>, DAError> { + async fn get_blob_data(&self, blob_id: BlobInfo) -> anyhow::Result> { self.client.get_blob_data(blob_id).await } @@ -111,8 +105,8 @@ mod tests { } } - const STATUS_QUERY_TIMEOUT: u64 = 1800000; // 30 minutes - const STATUS_QUERY_INTERVAL: u64 = 5; // 5 ms + const STATUS_QUERY_INTERVAL: Duration = Duration::from_millis(5); + const MAX_RETRY_ATTEMPTS: usize = 1800000; // With this value we retry for a duration of 30 minutes async fn get_blob_info( client: &EigenClient, @@ -127,8 +121,8 @@ mod tests { }) .retry( &ConstantBuilder::default() - .with_delay(Duration::from_millis(STATUS_QUERY_INTERVAL)) - .with_max_times((STATUS_QUERY_TIMEOUT / STATUS_QUERY_INTERVAL) as usize), + .with_delay(STATUS_QUERY_INTERVAL) + .with_max_times(MAX_RETRY_ATTEMPTS), ) .when(|e| e.to_string().contains("Blob not found")) .await?; @@ -177,7 +171,7 @@ mod tests { .data; assert_eq!(expected_inclusion_data, actual_inclusion_data); let retrieved_data = client.get_blob_data(blob_info).await.unwrap(); - assert_eq!(retrieved_data.unwrap(), data); + assert_eq!(retrieved_data, data); } #[ignore = "depends on external RPC"] @@ -205,7 +199,7 @@ mod tests { .data; assert_eq!(expected_inclusion_data, actual_inclusion_data); let retrieved_data = client.get_blob_data(blob_info).await.unwrap(); - assert_eq!(retrieved_data.unwrap(), data); + assert_eq!(retrieved_data, data); } #[ignore = "depends on external RPC"] @@ -235,7 +229,7 @@ mod tests { .data; assert_eq!(expected_inclusion_data, actual_inclusion_data); let retrieved_data = client.get_blob_data(blob_info).await.unwrap(); - assert_eq!(retrieved_data.unwrap(), data); + assert_eq!(retrieved_data, data); } #[ignore = "depends on external RPC"] @@ -263,7 +257,7 @@ mod tests { .data; assert_eq!(expected_inclusion_data, actual_inclusion_data); let retrieved_data = client.get_blob_data(blob_info).await.unwrap(); - assert_eq!(retrieved_data.unwrap(), data); + assert_eq!(retrieved_data, data); } #[ignore = "depends on external RPC"] @@ -292,6 +286,6 @@ mod tests { .data; assert_eq!(expected_inclusion_data, actual_inclusion_data); let retrieved_data = client.get_blob_data(blob_info).await.unwrap(); - assert_eq!(retrieved_data.unwrap(), data); + assert_eq!(retrieved_data, data); } } diff --git a/core/node/da_clients/src/eigen/errors.rs b/core/node/da_clients/src/eigen/errors.rs new file mode 100644 index 000000000000..b2eba8ee843e --- /dev/null +++ b/core/node/da_clients/src/eigen/errors.rs @@ -0,0 +1,111 @@ +use ark_bn254::G1Affine; +use tonic::{transport::Error as TonicError, Status}; +use zksync_eth_client::EnrichedClientError; + +use super::blob_info::BlobQuorumParam; + +/// Errors returned by this crate +#[derive(Debug, thiserror::Error)] +pub enum EigenClientError { + #[error(transparent)] + EthClient(#[from] EthClientError), + #[error(transparent)] + Verification(#[from] VerificationError), + #[error(transparent)] + Communication(#[from] CommunicationError), + #[error(transparent)] + BlobStatus(#[from] BlobStatusError), + #[error(transparent)] + Conversion(#[from] ConversionError), + #[error(transparent)] + Config(#[from] ConfigError), +} + +#[derive(Debug, thiserror::Error)] +pub enum ConfigError { + #[error(transparent)] + Secp(#[from] secp256k1::Error), + #[error(transparent)] + Tonic(#[from] TonicError), +} + +#[derive(Debug, thiserror::Error)] +pub enum CommunicationError { + #[error(transparent)] + Secp(#[from] secp256k1::Error), + #[error(transparent)] + Hex(#[from] hex::FromHexError), + #[error(transparent)] + GetBlobData(#[from] Box), +} + +#[derive(Debug, thiserror::Error)] +pub enum BlobStatusError { + #[error(transparent)] + Prost(#[from] prost::DecodeError), + #[error(transparent)] + Status(#[from] Status), +} + +/// Errors specific to conversion +#[derive(Debug, thiserror::Error)] +pub enum ConversionError {} + +/// Errors for the EthClient +#[derive(Debug, thiserror::Error)] +pub enum EthClientError { + #[error(transparent)] + HTTPClient(#[from] reqwest::Error), + #[error(transparent)] + SerdeJSON(#[from] serde_json::Error), + #[error("RPC: {0}")] + Rpc(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum KzgError { + #[error("Kzg setup error: {0}")] + Setup(String), + #[error(transparent)] + Internal(#[from] rust_kzg_bn254::errors::KzgError), +} + +#[derive(Debug, thiserror::Error)] +pub enum ServiceManagerError { + #[error(transparent)] + EnrichedClient(#[from] EnrichedClientError), + #[error("Decoding error: {0}")] + Decoding(String), +} + +/// Errors for the Verifier +#[derive(Debug, thiserror::Error)] +pub enum VerificationError { + #[error(transparent)] + ServiceManager(#[from] ServiceManagerError), + #[error(transparent)] + Kzg(#[from] KzgError), + #[error("Wrong proof")] + WrongProof, + #[error("Different commitments: expected {expected:?}, got {actual:?}")] + DifferentCommitments { + expected: Box, + actual: Box, + }, + #[error("Different roots: expected {expected:?}, got {actual:?}")] + DifferentRoots { expected: String, actual: String }, + #[error("Empty hashes")] + EmptyHash, + #[error("Different hashes: expected {expected:?}, got {actual:?}")] + DifferentHashes { expected: String, actual: String }, + #[error("Wrong quorum params: {blob_quorum_params:?}")] + WrongQuorumParams { blob_quorum_params: BlobQuorumParam }, + #[error("Quorum not confirmed")] + QuorumNotConfirmed, + #[error("Commitment not on curve: {0}")] + CommitmentNotOnCurve(G1Affine), + #[error("Commitment not on correct subgroup: {0}")] + CommitmentNotOnCorrectSubgroup(G1Affine), + #[error("Point download error: {0}")] + PointDownloadError(String), +} diff --git a/core/node/da_clients/src/eigen/mod.rs b/core/node/da_clients/src/eigen/mod.rs index 98cefc67b799..ff1d732604b6 100644 --- a/core/node/da_clients/src/eigen/mod.rs +++ b/core/node/da_clients/src/eigen/mod.rs @@ -1,5 +1,6 @@ mod blob_info; mod client; +mod errors; mod sdk; mod verifier; diff --git a/core/node/da_clients/src/eigen/sdk.rs b/core/node/da_clients/src/eigen/sdk.rs index a3cad7052c13..c22f86de6a57 100644 --- a/core/node/da_clients/src/eigen/sdk.rs +++ b/core/node/da_clients/src/eigen/sdk.rs @@ -8,17 +8,14 @@ use tonic::{ transport::{Channel, ClientTlsConfig, Endpoint}, Streaming, }; -use url::Url; use zksync_config::EigenConfig; -use zksync_da_client::types::DAError; -use zksync_eth_client::clients::PKSigningClient; -use zksync_types::{url::SensitiveUrl, K256PrivateKey, SLChainId}; use zksync_web3_decl::client::{Client, DynClient, L1}; use super::{ blob_info::BlobInfo, disperser::BlobInfo as DisperserBlobInfo, - verifier::{Verifier, VerifierConfig}, + errors::{ConfigError, EigenClientError, EthClientError, VerificationError}, + verifier::Verifier, GetBlobData, }; use crate::eigen::{ @@ -29,10 +26,9 @@ use crate::eigen::{ disperser_client::DisperserClient, AuthenticatedReply, BlobAuthHeader, }, - verifier::VerificationError, }; -#[derive(Debug, Clone)] +#[derive(Debug)] pub(crate) struct RawEigenClient { client: DisperserClient, private_key: SecretKey, @@ -48,47 +44,31 @@ impl RawEigenClient { pub async fn new( private_key: SecretKey, - config: EigenConfig, + cfg: EigenConfig, get_blob_data: Arc, - ) -> anyhow::Result { - let endpoint = - Endpoint::from_str(config.disperser_rpc.as_str())?.tls_config(ClientTlsConfig::new())?; - let client = DisperserClient::connect(endpoint).await?; - - let verifier_config = VerifierConfig { - rpc_url: config - .eigenda_eth_rpc - .clone() - .ok_or(anyhow::anyhow!("EigenDA ETH RPC not set"))?, - svc_manager_addr: config.eigenda_svc_manager_address, - max_blob_size: Self::BLOB_SIZE_LIMIT as u32, - g1_url: Url::parse(&config.g1_url)?, - g2_url: Url::parse(&config.g2_url)?, - settlement_layer_confirmation_depth: config.settlement_layer_confirmation_depth, - private_key: hex::encode(private_key.secret_bytes()), - chain_id: config.chain_id, - }; + ) -> Result { + let endpoint = Endpoint::from_str(cfg.disperser_rpc.as_str()) + .map_err(ConfigError::Tonic)? + .tls_config(ClientTlsConfig::new()) + .map_err(ConfigError::Tonic)?; + let client = DisperserClient::connect(endpoint) + .await + .map_err(ConfigError::Tonic)?; - let url = SensitiveUrl::from_str(&verifier_config.rpc_url)?; - let query_client: Client = Client::http(url)?.build(); + let rpc_url = cfg + .eigenda_eth_rpc + .clone() + .ok_or(EthClientError::Rpc("EigenDA ETH RPC not set".to_string()))?; + let query_client: Client = Client::http(rpc_url) + .map_err(|e| EthClientError::Rpc(e.to_string()))? + .build(); let query_client = Box::new(query_client) as Box>; - let signing_client = PKSigningClient::new_raw( - K256PrivateKey::from_bytes(zksync_types::H256::from_str( - &verifier_config.private_key, - )?)?, - verifier_config.svc_manager_addr, - Verifier::DEFAULT_PRIORITY_FEE_PER_GAS, - SLChainId(verifier_config.chain_id), - query_client, - ); - let verifier = Verifier::new(verifier_config, Arc::new(signing_client)) - .await - .context("Failed to create verifier")?; + let verifier = Verifier::new(cfg.clone(), Arc::new(query_client)).await?; Ok(RawEigenClient { client, private_key, - config, + config: cfg, verifier, get_blob_data, }) @@ -180,9 +160,7 @@ impl RawEigenClient { let blob_info = blob_info::BlobInfo::try_from(blob_info) .map_err(|e| anyhow::anyhow!("Failed to convert blob info: {}", e))?; - let Some(data) = self.get_blob_data(blob_info.clone()).await? else { - return Err(anyhow::anyhow!("Failed to get blob data")); - }; + let data = self.get_blob_data(blob_info.clone()).await?; let data_db = self.get_blob_data.get_blob_data(request_id).await?; if let Some(data_db) = data_db { if data_db != data { @@ -193,7 +171,7 @@ impl RawEigenClient { } self.verifier .verify_commitment(blob_info.blob_header.commitment.clone(), &data) - .map_err(|_| anyhow::anyhow!("Failed to verify commitment"))?; + .context("Failed to verify commitment")?; let result = self .verifier @@ -310,15 +288,15 @@ impl RawEigenClient { let resp = self .client .clone() - .get_blob_status(polling_request.clone()) + .get_blob_status(polling_request) .await? .into_inner(); match disperser::BlobStatus::try_from(resp.status)? { disperser::BlobStatus::Processing | disperser::BlobStatus::Dispersing => Ok(None), - disperser::BlobStatus::Failed => Err(anyhow::anyhow!("Blob dispatch failed")), + disperser::BlobStatus::Failed => anyhow::bail!("Blob dispatch failed"), disperser::BlobStatus::InsufficientSignatures => { - Err(anyhow::anyhow!("Insufficient signatures")) + anyhow::bail!("Insufficient signatures") } disperser::BlobStatus::Confirmed => { if !self.config.wait_for_finalization { @@ -331,17 +309,12 @@ impl RawEigenClient { let blob_info = resp.info.context("No blob header in response")?; Ok(Some(blob_info)) } - - _ => Err(anyhow::anyhow!("Received unknown blob status")), + _ => anyhow::bail!("Received unknown blob status"), } } - pub async fn get_blob_data( - &self, - blob_info: BlobInfo, - ) -> anyhow::Result>, DAError> { + pub async fn get_blob_data(&self, blob_info: BlobInfo) -> anyhow::Result> { use anyhow::anyhow; - use zksync_da_client::types::DAError; let blob_index = blob_info.blob_verification_proof.blob_index; let batch_header_hash = blob_info @@ -355,22 +328,15 @@ impl RawEigenClient { batch_header_hash, blob_index, }) - .await - .map_err(|e| DAError { - error: anyhow!(e), - is_retriable: true, - })? + .await? .into_inner(); if get_response.data.is_empty() { - return Err(DAError { - error: anyhow!("Failed to get blob data"), - is_retriable: false, - }); + return Err(anyhow!("Failed to get blob data")); } let data = remove_empty_byte_from_padded_bytes(&get_response.data); - Ok(Some(data)) + Ok(data) } } @@ -385,7 +351,7 @@ fn get_account_id(secret_key: &SecretKey) -> String { fn convert_by_padding_empty_byte(data: &[u8]) -> Vec { let parse_size = DATA_CHUNK_SIZE - 1; - let chunk_count = (data.len()).div_ceil(parse_size); + let chunk_count = data.len().div_ceil(parse_size); let mut valid_data = Vec::with_capacity(data.len() + chunk_count); for chunk in data.chunks(parse_size) { @@ -399,8 +365,9 @@ fn convert_by_padding_empty_byte(data: &[u8]) -> Vec { fn remove_empty_byte_from_padded_bytes(data: &[u8]) -> Vec { let parse_size = DATA_CHUNK_SIZE; - let data_len = (data.len() + parse_size - 1) / parse_size; - let mut valid_data = Vec::with_capacity(data.len() - data_len); + let chunk_count = data.len().div_ceil(parse_size); + // Safe subtraction, as we know chunk_count is always less than the length of the data + let mut valid_data = Vec::with_capacity(data.len() - chunk_count); for chunk in data.chunks(parse_size) { valid_data.extend_from_slice(&chunk[1..]); diff --git a/core/node/da_clients/src/eigen/verifier.rs b/core/node/da_clients/src/eigen/verifier.rs deleted file mode 100644 index 9f60c0ad3912..000000000000 --- a/core/node/da_clients/src/eigen/verifier.rs +++ /dev/null @@ -1,1347 +0,0 @@ -use std::{collections::HashMap, path::Path, sync::Arc}; - -use ark_bn254::{Fq, G1Affine}; -use ethabi::{encode, ParamType, Token}; -use rust_kzg_bn254::{blob::Blob, kzg::Kzg, polynomial::PolynomialFormat}; -use tokio::{fs::File, io::AsyncWriteExt}; -use url::Url; -use zksync_basic_types::web3::CallRequest; -use zksync_eth_client::{clients::PKSigningClient, EnrichedClientError, EnrichedClientResult}; -use zksync_types::{ - web3::{self, BlockId, BlockNumber}, - Address, U256, U64, -}; - -use super::blob_info::{BatchHeader, BlobHeader, BlobInfo, BlobQuorumParam, G1Commitment}; - -#[async_trait::async_trait] -pub trait VerifierClient: Sync + Send + std::fmt::Debug { - /// Returns the current block number. - async fn block_number(&self) -> EnrichedClientResult; - - /// Invokes a function on a contract specified by `contract_address` / `contract_abi` using `eth_call`. - async fn call_contract_function( - &self, - request: web3::CallRequest, - block: Option, - ) -> EnrichedClientResult; -} - -#[async_trait::async_trait] -impl VerifierClient for PKSigningClient { - async fn block_number(&self) -> EnrichedClientResult { - self.as_ref().block_number().await - } - - async fn call_contract_function( - &self, - request: web3::CallRequest, - block: Option, - ) -> EnrichedClientResult { - self.as_ref().call_contract_function(request, block).await - } -} - -#[derive(Debug, thiserror::Error)] -pub enum KzgError { - #[error("Kzg setup error: {0}")] - Setup(String), - #[error(transparent)] - Internal(#[from] rust_kzg_bn254::errors::KzgError), -} - -#[derive(Debug, thiserror::Error)] -pub enum ServiceManagerError { - #[error(transparent)] - EnrichedClient(#[from] EnrichedClientError), - #[error("Decoding error: {0}")] - Decoding(String), - #[cfg(test)] - #[error("Parsing error: {0}")] - Parsing(String), -} - -#[derive(Debug, thiserror::Error)] -pub enum VerificationError { - #[error(transparent)] - ServiceManager(#[from] ServiceManagerError), - #[error(transparent)] - Kzg(#[from] KzgError), - #[error("Wrong proof")] - WrongProof, - #[error("Different commitments: expected {expected:?}, got {actual:?}")] - DifferentCommitments { - expected: G1Affine, - actual: G1Affine, - }, - #[error("Different roots: expected {expected:?}, got {actual:?}")] - DifferentRoots { expected: String, actual: String }, - #[error("Empty hash")] - EmptyHash, - #[error("Different hashes: expected {expected:?}, got {actual:?}")] - DifferentHashes { expected: String, actual: String }, - #[error("Wrong quorum params: {blob_quorum_params:?}")] - WrongQuorumParams { blob_quorum_params: BlobQuorumParam }, - #[error("Quorum not confirmed")] - QuorumNotConfirmed, - #[error("Commitment not on curve: {0}")] - CommitmentNotOnCurve(G1Affine), - #[error("Commitment not on correct subgroup: {0}")] - CommitmentNotOnCorrectSubgroup(G1Affine), - #[error("Link Error: {0}")] - LinkError(String), -} - -/// Configuration for the verifier used for authenticated dispersals -#[derive(Debug, Clone)] -pub struct VerifierConfig { - pub rpc_url: String, - pub svc_manager_addr: Address, - pub max_blob_size: u32, - pub g1_url: Url, - pub g2_url: Url, - pub settlement_layer_confirmation_depth: u32, - pub private_key: String, - pub chain_id: u64, -} - -/// Verifier used to verify the integrity of the blob info -/// Kzg is used for commitment verification -/// EigenDA service manager is used to connect to the service manager contract -#[derive(Debug, Clone)] -pub struct Verifier { - kzg: Kzg, - cfg: VerifierConfig, - signing_client: Arc, -} - -impl Verifier { - pub const DEFAULT_PRIORITY_FEE_PER_GAS: u64 = 100; - pub const SRSORDER: u32 = 268435456; // 2 ^ 28 - pub const G1POINT: &'static str = "g1.point"; - pub const G2POINT: &'static str = "g2.point.powerOf2"; - pub const POINT_SIZE: u32 = 32; - - async fn save_point(url: Url, point: String) -> Result<(), VerificationError> { - let response = reqwest::get(url) - .await - .map_err(|e| VerificationError::LinkError(e.to_string()))?; - if !response.status().is_success() { - return Err(VerificationError::LinkError( - "Failed to get point".to_string(), - )); - } - let path = format!("./{}", point); - let path = Path::new(&path); - let mut file = File::create(path) - .await - .map_err(|e| VerificationError::LinkError(e.to_string()))?; - let content = response - .bytes() - .await - .map_err(|e| VerificationError::LinkError(e.to_string()))?; - file.write_all(&content) - .await - .map_err(|e| VerificationError::LinkError(e.to_string()))?; - Ok(()) - } - - async fn save_points(url_g1: Url, url_g2: Url) -> Result { - Self::save_point(url_g1, Self::G1POINT.to_string()).await?; - Self::save_point(url_g2, Self::G2POINT.to_string()).await?; - - Ok(".".to_string()) - } - - pub(crate) async fn new( - cfg: VerifierConfig, - signing_client: Arc, - ) -> Result { - let srs_points_to_load = cfg.max_blob_size / Self::POINT_SIZE; - let path = Self::save_points(cfg.clone().g1_url, cfg.clone().g2_url).await?; - let kzg_handle = tokio::task::spawn_blocking(move || { - Kzg::setup( - &format!("{}/{}", path, Self::G1POINT), - "", - &format!("{}/{}", path, Self::G2POINT), - Self::SRSORDER, - srs_points_to_load, - "".to_string(), - ) - }); - - let kzg = kzg_handle - .await - .map_err(|e| VerificationError::Kzg(KzgError::Setup(e.to_string())))? - .map_err(KzgError::Internal)?; - - Ok(Self { - kzg, - cfg, - signing_client, - }) - } - - /// Return the commitment from a blob - fn commit(&self, blob: &[u8]) -> Result { - let blob = Blob::from_bytes_and_pad(blob); - self.kzg - .blob_to_kzg_commitment(&blob, PolynomialFormat::InEvaluationForm) - .map_err(|e| VerificationError::Kzg(KzgError::Internal(e))) - } - - /// Compare the given commitment with the commitment generated with the blob - pub fn verify_commitment( - &self, - expected_commitment: G1Commitment, - blob: &[u8], - ) -> Result<(), VerificationError> { - let actual_commitment = self.commit(blob)?; - let expected_commitment = G1Affine::new_unchecked( - Fq::from(num_bigint::BigUint::from_bytes_be(&expected_commitment.x)), - Fq::from(num_bigint::BigUint::from_bytes_be(&expected_commitment.y)), - ); - if !expected_commitment.is_on_curve() { - return Err(VerificationError::CommitmentNotOnCurve(expected_commitment)); - } - if !expected_commitment.is_in_correct_subgroup_assuming_on_curve() { - return Err(VerificationError::CommitmentNotOnCorrectSubgroup( - expected_commitment, - )); - } - if actual_commitment != expected_commitment { - return Err(VerificationError::DifferentCommitments { - expected: expected_commitment, - actual: actual_commitment, - }); - } - Ok(()) - } - - pub(crate) fn hash_encode_blob_header(&self, blob_header: &BlobHeader) -> Vec { - let mut blob_quorums = vec![]; - for quorum in &blob_header.blob_quorum_params { - let quorum = Token::Tuple(vec![ - Token::Uint(ethabi::Uint::from(quorum.quorum_number)), - Token::Uint(ethabi::Uint::from(quorum.adversary_threshold_percentage)), - Token::Uint(ethabi::Uint::from(quorum.confirmation_threshold_percentage)), - Token::Uint(ethabi::Uint::from(quorum.chunk_length)), - ]); - blob_quorums.push(quorum); - } - let blob_header = Token::Tuple(vec![ - Token::Tuple(vec![ - Token::Uint(ethabi::Uint::from_big_endian(&blob_header.commitment.x)), - Token::Uint(ethabi::Uint::from_big_endian(&blob_header.commitment.y)), - ]), - Token::Uint(ethabi::Uint::from(blob_header.data_length)), - Token::Array(blob_quorums), - ]); - - let encoded = encode(&[blob_header]); - web3::keccak256(&encoded).to_vec() - } - - pub(crate) fn process_inclusion_proof( - &self, - proof: &[u8], - leaf: [u8; 32], - index: u32, - ) -> Result, VerificationError> { - let mut index = index; - if proof.is_empty() || proof.len() % 32 != 0 { - return Err(VerificationError::WrongProof); - } - let mut computed_hash = leaf.to_vec(); - for chunk in proof.chunks(32) { - let mut buffer = [0u8; 64]; - if index % 2 == 0 { - buffer[..32].copy_from_slice(&computed_hash); - buffer[32..].copy_from_slice(chunk); - } else { - buffer[..32].copy_from_slice(chunk); - buffer[32..].copy_from_slice(&computed_hash); - } - computed_hash = web3::keccak256(&buffer).to_vec(); - index /= 2; - } - - Ok(computed_hash) - } - - /// Verifies the certificate's batch root - pub(crate) fn verify_merkle_proof(&self, cert: &BlobInfo) -> Result<(), VerificationError> { - let inclusion_proof = &cert.blob_verification_proof.inclusion_proof; - let root = &cert - .blob_verification_proof - .batch_medatada - .batch_header - .batch_root; - let blob_index = cert.blob_verification_proof.blob_index; - let blob_header = &cert.blob_header; - - let blob_header_hash = self.hash_encode_blob_header(blob_header); - let leaf_hash = web3::keccak256(&blob_header_hash); - - let generated_root = - self.process_inclusion_proof(inclusion_proof, leaf_hash, blob_index)?; - - if generated_root != *root { - return Err(VerificationError::DifferentRoots { - expected: hex::encode(root), - actual: hex::encode(&generated_root), - }); - } - Ok(()) - } - - fn hash_batch_metadata( - &self, - batch_header: &BatchHeader, - signatory_record_hash: &[u8], - confirmation_block_number: u32, - ) -> Vec { - let batch_header_token = Token::Tuple(vec![ - Token::FixedBytes(batch_header.batch_root.clone()), // Clone only where necessary - Token::Bytes(batch_header.quorum_numbers.clone()), - Token::Bytes(batch_header.quorum_signed_percentages.clone()), - Token::Uint(ethabi::Uint::from(batch_header.reference_block_number)), - ]); - - let encoded = encode(&[batch_header_token]); - let header_hash = web3::keccak256(&encoded).to_vec(); - - let hash_token = Token::Tuple(vec![ - Token::FixedBytes(header_hash.to_vec()), - Token::FixedBytes(signatory_record_hash.to_owned()), // Clone only if required - ]); - - let mut hash_encoded = encode(&[hash_token]); - - hash_encoded.append(&mut confirmation_block_number.to_be_bytes().to_vec()); - web3::keccak256(&hash_encoded).to_vec() - } - - /// Retrieves the block to make the request to the service manager - async fn get_context_block(&self) -> Result { - let latest = self - .signing_client - .as_ref() - .block_number() - .await - .map_err(ServiceManagerError::EnrichedClient)? - .as_u64(); - - let depth = self - .cfg - .settlement_layer_confirmation_depth - .saturating_sub(1); - let block_to_return = latest.saturating_sub(depth as u64); - Ok(block_to_return) - } - - async fn call_batch_id_to_metadata_hash( - &self, - blob_info: &BlobInfo, - ) -> Result, VerificationError> { - let context_block = self.get_context_block().await?; - - let func_selector = - ethabi::short_signature("batchIdToBatchMetadataHash", &[ParamType::Uint(32)]); - let mut data = func_selector.to_vec(); - let mut batch_id_vec = [0u8; 32]; - U256::from(blob_info.blob_verification_proof.batch_id).to_big_endian(&mut batch_id_vec); - data.append(batch_id_vec.to_vec().as_mut()); - - let call_request = CallRequest { - to: Some(self.cfg.svc_manager_addr), - data: Some(zksync_basic_types::web3::Bytes(data)), - ..Default::default() - }; - - let res = self - .signing_client - .as_ref() - .call_contract_function( - call_request, - Some(BlockId::Number(BlockNumber::Number(context_block.into()))), - ) - .await - .map_err(ServiceManagerError::EnrichedClient)?; - - Ok(res.0.to_vec()) - } - - /// Verifies the certificate batch hash - pub(crate) async fn verify_batch(&self, blob_info: &BlobInfo) -> Result<(), VerificationError> { - let expected_hash = self.call_batch_id_to_metadata_hash(blob_info).await?; - - if expected_hash == vec![0u8; 32] { - return Err(VerificationError::EmptyHash); - } - - let actual_hash = self.hash_batch_metadata( - &blob_info - .blob_verification_proof - .batch_medatada - .batch_header, - &blob_info - .blob_verification_proof - .batch_medatada - .signatory_record_hash, - blob_info - .blob_verification_proof - .batch_medatada - .confirmation_block_number, - ); - - if expected_hash != actual_hash { - return Err(VerificationError::DifferentHashes { - expected: hex::encode(&expected_hash), - actual: hex::encode(&actual_hash), - }); - } - Ok(()) - } - - fn decode_bytes(&self, encoded: Vec) -> Result, VerificationError> { - let output_type = [ParamType::Bytes]; - let tokens: Vec = ethabi::decode(&output_type, &encoded) - .map_err(|e| ServiceManagerError::Decoding(e.to_string()))?; - let token = tokens - .first() - .ok_or(ServiceManagerError::Decoding("No tokens found".to_string()))?; - match token { - Token::Bytes(data) => Ok(data.to_vec()), - _ => Err(VerificationError::from(ServiceManagerError::Decoding( - "Token type mismatch".to_string(), - ))), - } - } - - async fn get_quorum_adversary_threshold( - &self, - quorum_number: u32, - ) -> Result { - let func_selector = ethabi::short_signature("quorumAdversaryThresholdPercentages", &[]); - let data = func_selector.to_vec(); - - let call_request = CallRequest { - to: Some(self.cfg.svc_manager_addr), - data: Some(zksync_basic_types::web3::Bytes(data)), - ..Default::default() - }; - - let res = self - .signing_client - .as_ref() - .call_contract_function(call_request, None) - .await - .map_err(ServiceManagerError::EnrichedClient)?; - - let percentages = self.decode_bytes(res.0.to_vec())?; - - if percentages.len() > quorum_number as usize { - return Ok(percentages[quorum_number as usize]); - } - Ok(0) - } - - async fn call_quorum_numbers_required(&self) -> Result, VerificationError> { - let func_selector = ethabi::short_signature("quorumNumbersRequired", &[]); - let data = func_selector.to_vec(); - let call_request = CallRequest { - to: Some(self.cfg.svc_manager_addr), - data: Some(zksync_basic_types::web3::Bytes(data)), - ..Default::default() - }; - - let res = self - .signing_client - .as_ref() - .call_contract_function(call_request, None) - .await - .map_err(ServiceManagerError::EnrichedClient)?; - - self.decode_bytes(res.0.to_vec()) - } - - /// Verifies that the certificate's blob quorum params are correct - pub async fn verify_security_params(&self, cert: &BlobInfo) -> Result<(), VerificationError> { - let blob_header = &cert.blob_header; - let batch_header = &cert.blob_verification_proof.batch_medatada.batch_header; - - let mut confirmed_quorums: HashMap = HashMap::new(); - for i in 0..blob_header.blob_quorum_params.len() { - if batch_header.quorum_numbers[i] as u32 - != blob_header.blob_quorum_params[i].quorum_number - { - return Err(VerificationError::WrongQuorumParams { - blob_quorum_params: blob_header.blob_quorum_params[i].clone(), - }); - } - if blob_header.blob_quorum_params[i].adversary_threshold_percentage - > blob_header.blob_quorum_params[i].confirmation_threshold_percentage - { - return Err(VerificationError::WrongQuorumParams { - blob_quorum_params: blob_header.blob_quorum_params[i].clone(), - }); - } - let quorum_adversary_threshold = self - .get_quorum_adversary_threshold(blob_header.blob_quorum_params[i].quorum_number) - .await?; - - if quorum_adversary_threshold > 0 - && blob_header.blob_quorum_params[i].adversary_threshold_percentage - < quorum_adversary_threshold as u32 - { - return Err(VerificationError::WrongQuorumParams { - blob_quorum_params: blob_header.blob_quorum_params[i].clone(), - }); - } - - if (batch_header.quorum_signed_percentages[i] as u32) - < blob_header.blob_quorum_params[i].confirmation_threshold_percentage - { - return Err(VerificationError::WrongQuorumParams { - blob_quorum_params: blob_header.blob_quorum_params[i].clone(), - }); - } - - confirmed_quorums.insert(blob_header.blob_quorum_params[i].quorum_number, true); - } - - let required_quorums = self.call_quorum_numbers_required().await?; - - for quorum in required_quorums { - if !confirmed_quorums.contains_key(&(quorum as u32)) { - return Err(VerificationError::QuorumNotConfirmed); - } - } - Ok(()) - } - - /// Verifies that the certificate is valid - pub async fn verify_inclusion_data_against_settlement_layer( - &self, - cert: &BlobInfo, - ) -> Result<(), VerificationError> { - self.verify_batch(cert).await?; - self.verify_merkle_proof(cert)?; - self.verify_security_params(cert).await?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use std::{collections::HashMap, str::FromStr, sync::Arc}; - - use url::Url; - use zksync_eth_client::{clients::PKSigningClient, EnrichedClientResult}; - use zksync_types::{ - url::SensitiveUrl, - web3::{BlockId, Bytes, CallRequest}, - Address, K256PrivateKey, SLChainId, H160, U64, - }; - use zksync_web3_decl::client::{Client, DynClient, L1}; - - use super::ServiceManagerError; - use crate::eigen::{ - blob_info::{ - BatchHeader, BatchMetadata, BlobHeader, BlobInfo, BlobQuorumParam, - BlobVerificationProof, G1Commitment, - }, - verifier::{Verifier, VerifierClient, VerifierConfig}, - }; - - fn get_verifier_config() -> VerifierConfig { - VerifierConfig { - rpc_url: "https://ethereum-holesky-rpc.publicnode.com".to_string(), - svc_manager_addr: Address::from_str("0xD4A7E1Bd8015057293f0D0A557088c286942e84b").unwrap(), - max_blob_size: 2 * 1024 * 1024, - g1_url: Url::parse("https://github.com/Layr-Labs/eigenda-proxy/raw/2fd70b99ef5bf137d7bbca3461cf9e1f2c899451/resources/g1.point").unwrap(), - g2_url: Url::parse("https://github.com/Layr-Labs/eigenda-proxy/raw/2fd70b99ef5bf137d7bbca3461cf9e1f2c899451/resources/g2.point.powerOf2").unwrap(), - settlement_layer_confirmation_depth: 0, - private_key: "0xd08aa7ae1bb5ddd46c3c2d8cdb5894ab9f54dec467233686ca42629e826ac4c6" - .to_string(), - chain_id: 17000, - } - } - - /// Mock struct for the Verifier - /// Used to avoid making actual calls to a remote disperser - /// and possible making the CI fail due to network issues. - /// To run tests with the actual verifier run: - /// `cargo test -p zksync_da_clients -- --ignored` - #[derive(Debug)] - pub struct MockVerifierClient { - replies: HashMap, - } - - impl MockVerifierClient { - pub fn new(replies: HashMap) -> Self { - Self { replies } - } - } - - #[async_trait::async_trait] - impl VerifierClient for MockVerifierClient { - async fn block_number(&self) -> EnrichedClientResult { - Ok(U64::from(42)) - } - - async fn call_contract_function( - &self, - request: CallRequest, - _block: Option, - ) -> EnrichedClientResult { - let req = serde_json::to_string(&request).unwrap(); - Ok(self.replies.get(&req).unwrap().clone()) - } - } - - fn create_remote_signing_client(cfg: VerifierConfig) -> PKSigningClient { - let url = SensitiveUrl::from_str(&cfg.rpc_url).unwrap(); - let query_client: Client = Client::http(url).unwrap().build(); - let query_client = Box::new(query_client) as Box>; - PKSigningClient::new_raw( - K256PrivateKey::from_bytes( - zksync_types::H256::from_str(&cfg.private_key) - .map_err(|e| ServiceManagerError::Parsing(e.to_string())) - .unwrap(), - ) - .map_err(|e| ServiceManagerError::Parsing(e.to_string())) - .unwrap(), - cfg.svc_manager_addr, - Verifier::DEFAULT_PRIORITY_FEE_PER_GAS, - SLChainId(cfg.chain_id), - query_client, - ) - } - - #[ignore = "depends on external RPC"] - #[tokio::test] - async fn test_verify_commitment() { - let cfg = get_verifier_config(); - let signing_client = create_remote_signing_client(cfg.clone()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let commitment = G1Commitment { - x: vec![ - 22, 11, 176, 29, 82, 48, 62, 49, 51, 119, 94, 17, 156, 142, 248, 96, 240, 183, 134, - 85, 152, 5, 74, 27, 175, 83, 162, 148, 17, 110, 201, 74, - ], - y: vec![ - 12, 132, 236, 56, 147, 6, 176, 135, 244, 166, 21, 18, 87, 76, 122, 3, 23, 22, 254, - 236, 148, 129, 110, 207, 131, 116, 58, 170, 4, 130, 191, 157, - ], - }; - let blob = vec![1u8; 100]; // Actual blob sent was this blob but kzg-padded, but Blob::from_bytes_and_pad padds it inside, so we don't need to pad it here. - let result = verifier.verify_commitment(commitment, &blob); - assert!(result.is_ok()); - } - - /// Test the verification of the commitment with a mocked verifier. - /// To test actual behaviour of the verifier, run the test above - #[tokio::test] - async fn test_verify_commitment_mocked() { - let cfg = get_verifier_config(); - let signing_client = MockVerifierClient::new(HashMap::new()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let commitment = G1Commitment { - x: vec![ - 22, 11, 176, 29, 82, 48, 62, 49, 51, 119, 94, 17, 156, 142, 248, 96, 240, 183, 134, - 85, 152, 5, 74, 27, 175, 83, 162, 148, 17, 110, 201, 74, - ], - y: vec![ - 12, 132, 236, 56, 147, 6, 176, 135, 244, 166, 21, 18, 87, 76, 122, 3, 23, 22, 254, - 236, 148, 129, 110, 207, 131, 116, 58, 170, 4, 130, 191, 157, - ], - }; - let blob = vec![1u8; 100]; // Actual blob sent was this blob but kzg-padded, but Blob::from_bytes_and_pad padds it inside, so we don't need to pad it here. - let result = verifier.verify_commitment(commitment, &blob); - assert!(result.is_ok()); - } - - #[ignore = "depends on external RPC"] - #[tokio::test] - async fn test_verify_merkle_proof() { - let cfg = get_verifier_config(); - let signing_client = create_remote_signing_client(cfg.clone()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let cert = BlobInfo { - blob_header: BlobHeader { - commitment: G1Commitment { - x: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - y: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - }, - data_length: 4, - blob_quorum_params: vec![ - BlobQuorumParam { - quorum_number: 0, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - BlobQuorumParam { - quorum_number: 1, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - ], - }, - blob_verification_proof: BlobVerificationProof { - batch_id: 66507, - blob_index: 92, - batch_medatada: BatchMetadata { - batch_header: BatchHeader { - batch_root: vec![ - 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, - 8, 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, - 211, 43, - ], - quorum_numbers: vec![0, 1], - quorum_signed_percentages: vec![100, 100], - reference_block_number: 2624794, - }, - signatory_record_hash: vec![ - 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, - 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, - ], - fee: vec![0], - confirmation_block_number: 2624876, - batch_header_hash: vec![ - 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, - 146, 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, - ], - }, - inclusion_proof: vec![ - 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, - 140, 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, - 125, 123, 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, - 157, 203, 22, 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, - 128, 112, 84, 34, 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, - 5, 120, 18, 187, 51, 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, - 166, 66, 157, 255, 237, 69, 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, - 156, 220, 159, 4, 99, 48, 191, 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, - 182, 109, 207, 197, 239, 161, 132, 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, - 253, 23, 169, 75, 236, 211, 126, 121, 132, 191, 68, 167, 200, 16, 154, 149, - 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, 153, 30, 209, 238, 53, 233, 148, - 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, 255, 219, 170, 98, 17, 160, 179, - 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, 73, 78, 79, 72, 253, 105, - 245, 84, 244, 196, - ], - quorum_indexes: vec![0, 1], - }, - }; - let result = verifier.verify_merkle_proof(&cert); - assert!(result.is_ok()); - } - - /// Test the verificarion of a merkle proof with a mocked verifier. - /// To test actual behaviour of the verifier, run the test above - #[tokio::test] - async fn test_verify_merkle_proof_mocked() { - let cfg = get_verifier_config(); - let signing_client = MockVerifierClient::new(HashMap::new()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let cert = BlobInfo { - blob_header: BlobHeader { - commitment: G1Commitment { - x: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - y: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - }, - data_length: 4, - blob_quorum_params: vec![ - BlobQuorumParam { - quorum_number: 0, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - BlobQuorumParam { - quorum_number: 1, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - ], - }, - blob_verification_proof: BlobVerificationProof { - batch_id: 66507, - blob_index: 92, - batch_medatada: BatchMetadata { - batch_header: BatchHeader { - batch_root: vec![ - 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, - 8, 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, - 211, 43, - ], - quorum_numbers: vec![0, 1], - quorum_signed_percentages: vec![100, 100], - reference_block_number: 2624794, - }, - signatory_record_hash: vec![ - 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, - 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, - ], - fee: vec![0], - confirmation_block_number: 2624876, - batch_header_hash: vec![ - 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, - 146, 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, - ], - }, - inclusion_proof: vec![ - 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, - 140, 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, - 125, 123, 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, - 157, 203, 22, 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, - 128, 112, 84, 34, 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, - 5, 120, 18, 187, 51, 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, - 166, 66, 157, 255, 237, 69, 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, - 156, 220, 159, 4, 99, 48, 191, 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, - 182, 109, 207, 197, 239, 161, 132, 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, - 253, 23, 169, 75, 236, 211, 126, 121, 132, 191, 68, 167, 200, 16, 154, 149, - 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, 153, 30, 209, 238, 53, 233, 148, - 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, 255, 219, 170, 98, 17, 160, 179, - 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, 73, 78, 79, 72, 253, 105, - 245, 84, 244, 196, - ], - quorum_indexes: vec![0, 1], - }, - }; - let result = verifier.verify_merkle_proof(&cert); - assert!(result.is_ok()); - } - - #[ignore = "depends on external RPC"] - #[tokio::test] - async fn test_hash_blob_header() { - let cfg = get_verifier_config(); - let signing_client = create_remote_signing_client(cfg.clone()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let blob_header = BlobHeader { - commitment: G1Commitment { - x: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, - ], - y: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, - ], - }, - data_length: 2, - blob_quorum_params: vec![ - BlobQuorumParam { - quorum_number: 2, - adversary_threshold_percentage: 4, - confirmation_threshold_percentage: 5, - chunk_length: 6, - }, - BlobQuorumParam { - quorum_number: 2, - adversary_threshold_percentage: 4, - confirmation_threshold_percentage: 5, - chunk_length: 6, - }, - ], - }; - let result = verifier.hash_encode_blob_header(&blob_header); - let expected = "ba4675a31c9bf6b2f7abfdcedd34b74645cb7332b35db39bff00ae8516a67393"; - assert_eq!(result, hex::decode(expected).unwrap()); - } - - /// Test hashing of a blob header with a mocked verifier. - /// To test actual behaviour of the verifier, run the test above - #[tokio::test] - async fn test_hash_blob_header_mocked() { - let cfg = get_verifier_config(); - let signing_client = MockVerifierClient::new(HashMap::new()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let blob_header = BlobHeader { - commitment: G1Commitment { - x: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, - ], - y: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, - ], - }, - data_length: 2, - blob_quorum_params: vec![ - BlobQuorumParam { - quorum_number: 2, - adversary_threshold_percentage: 4, - confirmation_threshold_percentage: 5, - chunk_length: 6, - }, - BlobQuorumParam { - quorum_number: 2, - adversary_threshold_percentage: 4, - confirmation_threshold_percentage: 5, - chunk_length: 6, - }, - ], - }; - let result = verifier.hash_encode_blob_header(&blob_header); - let expected = "ba4675a31c9bf6b2f7abfdcedd34b74645cb7332b35db39bff00ae8516a67393"; - assert_eq!(result, hex::decode(expected).unwrap()); - } - - #[ignore = "depends on external RPC"] - #[tokio::test] - async fn test_inclusion_proof() { - let cfg = get_verifier_config(); - let signing_client = create_remote_signing_client(cfg.clone()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let proof = hex::decode("c455c1ea0e725d7ea3e5f29e9f48be8fc2787bb0a914d5a86710ba302c166ac4f626d76f67f1055bb960a514fb8923af2078fd84085d712655b58a19612e8cd15c3e4ac1cef57acde3438dbcf63f47c9fefe1221344c4d5c1a4943dd0d1803091ca81a270909dc0e146841441c9bd0e08e69ce6168181a3e4060ffacf3627480bec6abdd8d7bb92b49d33f180c42f49e041752aaded9c403db3a17b85e48a11e9ea9a08763f7f383dab6d25236f1b77c12b4c49c5cdbcbea32554a604e3f1d2f466851cb43fe73617b3d01e665e4c019bf930f92dea7394c25ed6a1e200d051fb0c30a2193c459f1cfef00bf1ba6656510d16725a4d1dc031cb759dbc90bab427b0f60ddc6764681924dda848824605a4f08b7f526fe6bd4572458c94e83fbf2150f2eeb28d3011ec921996dc3e69efa52d5fcf3182b20b56b5857a926aa66605808079b4d52c0c0cfe06923fa92e65eeca2c3e6126108e8c1babf5ac522f4d7").unwrap(); - let leaf: [u8; 32] = - hex::decode("f6106e6ae4631e68abe0fa898cedbe97dbae6c7efb1b088c5aa2e8b91190ff96") - .unwrap() - .try_into() - .unwrap(); - let expected_root = - hex::decode("7390b8023db8248123dcaeca57fa6c9340bef639e204f2278fc7ec3d46ad071b") - .unwrap(); - - let actual_root = verifier.process_inclusion_proof(&proof, leaf, 580).unwrap(); - - assert_eq!(actual_root, expected_root); - } - - /// Test proof inclusion with a mocked verifier. - /// To test actual behaviour of the verifier, run the test above - #[tokio::test] - async fn test_inclusion_proof_mocked() { - let cfg = get_verifier_config(); - let signing_client = MockVerifierClient::new(HashMap::new()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let proof = hex::decode("c455c1ea0e725d7ea3e5f29e9f48be8fc2787bb0a914d5a86710ba302c166ac4f626d76f67f1055bb960a514fb8923af2078fd84085d712655b58a19612e8cd15c3e4ac1cef57acde3438dbcf63f47c9fefe1221344c4d5c1a4943dd0d1803091ca81a270909dc0e146841441c9bd0e08e69ce6168181a3e4060ffacf3627480bec6abdd8d7bb92b49d33f180c42f49e041752aaded9c403db3a17b85e48a11e9ea9a08763f7f383dab6d25236f1b77c12b4c49c5cdbcbea32554a604e3f1d2f466851cb43fe73617b3d01e665e4c019bf930f92dea7394c25ed6a1e200d051fb0c30a2193c459f1cfef00bf1ba6656510d16725a4d1dc031cb759dbc90bab427b0f60ddc6764681924dda848824605a4f08b7f526fe6bd4572458c94e83fbf2150f2eeb28d3011ec921996dc3e69efa52d5fcf3182b20b56b5857a926aa66605808079b4d52c0c0cfe06923fa92e65eeca2c3e6126108e8c1babf5ac522f4d7").unwrap(); - let leaf: [u8; 32] = - hex::decode("f6106e6ae4631e68abe0fa898cedbe97dbae6c7efb1b088c5aa2e8b91190ff96") - .unwrap() - .try_into() - .unwrap(); - let expected_root = - hex::decode("7390b8023db8248123dcaeca57fa6c9340bef639e204f2278fc7ec3d46ad071b") - .unwrap(); - - let actual_root = verifier.process_inclusion_proof(&proof, leaf, 580).unwrap(); - - assert_eq!(actual_root, expected_root); - } - - #[ignore = "depends on external RPC"] - #[tokio::test] - async fn test_verify_batch() { - let cfg = get_verifier_config(); - let signing_client = create_remote_signing_client(cfg.clone()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let cert = BlobInfo { - blob_header: BlobHeader { - commitment: G1Commitment { - x: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - y: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - }, - data_length: 4, - blob_quorum_params: vec![ - BlobQuorumParam { - quorum_number: 0, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - BlobQuorumParam { - quorum_number: 1, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - ], - }, - blob_verification_proof: BlobVerificationProof { - batch_id: 66507, - blob_index: 92, - batch_medatada: BatchMetadata { - batch_header: BatchHeader { - batch_root: vec![ - 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, - 8, 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, - 211, 43, - ], - quorum_numbers: vec![0, 1], - quorum_signed_percentages: vec![100, 100], - reference_block_number: 2624794, - }, - signatory_record_hash: vec![ - 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, - 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, - ], - fee: vec![0], - confirmation_block_number: 2624876, - batch_header_hash: vec![ - 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, - 146, 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, - ], - }, - inclusion_proof: vec![ - 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, - 140, 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, - 125, 123, 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, - 157, 203, 22, 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, - 128, 112, 84, 34, 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, - 5, 120, 18, 187, 51, 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, - 166, 66, 157, 255, 237, 69, 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, - 156, 220, 159, 4, 99, 48, 191, 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, - 182, 109, 207, 197, 239, 161, 132, 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, - 253, 23, 169, 75, 236, 211, 126, 121, 132, 191, 68, 167, 200, 16, 154, 149, - 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, 153, 30, 209, 238, 53, 233, 148, - 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, 255, 219, 170, 98, 17, 160, 179, - 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, 73, 78, 79, 72, 253, 105, - 245, 84, 244, 196, - ], - quorum_indexes: vec![0, 1], - }, - }; - let result = verifier.verify_batch(&cert).await; - assert!(result.is_ok()); - } - - /// Test batch verification with a mocked verifier. - /// To test actual behaviour of the verifier, run the test above - #[tokio::test] - async fn test_verify_batch_mocked() { - let mut mock_replies = HashMap::new(); - let mock_req = CallRequest { - from: None, - to: Some(H160::from_str("0xd4a7e1bd8015057293f0d0a557088c286942e84b").unwrap()), - gas: None, - gas_price: None, - value: None, - data: Some(Bytes::from( - hex::decode( - "eccbbfc900000000000000000000000000000000000000000000000000000000000103cb", - ) - .unwrap(), - )), - transaction_type: None, - access_list: None, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - }; - let mock_req = serde_json::to_string(&mock_req).unwrap(); - let mock_res = Bytes::from( - hex::decode("60933e76989e57d6fd210ae2fc3086958d708660ee6927f91963047ab1a91ba8") - .unwrap(), - ); - mock_replies.insert(mock_req, mock_res); - - let cfg = get_verifier_config(); - let signing_client = MockVerifierClient::new(mock_replies); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let cert = BlobInfo { - blob_header: BlobHeader { - commitment: G1Commitment { - x: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - y: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - }, - data_length: 4, - blob_quorum_params: vec![ - BlobQuorumParam { - quorum_number: 0, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - BlobQuorumParam { - quorum_number: 1, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - ], - }, - blob_verification_proof: BlobVerificationProof { - batch_id: 66507, - blob_index: 92, - batch_medatada: BatchMetadata { - batch_header: BatchHeader { - batch_root: vec![ - 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, - 8, 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, - 211, 43, - ], - quorum_numbers: vec![0, 1], - quorum_signed_percentages: vec![100, 100], - reference_block_number: 2624794, - }, - signatory_record_hash: vec![ - 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, - 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, - ], - fee: vec![0], - confirmation_block_number: 2624876, - batch_header_hash: vec![ - 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, - 146, 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, - ], - }, - inclusion_proof: vec![ - 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, - 140, 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, - 125, 123, 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, - 157, 203, 22, 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, - 128, 112, 84, 34, 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, - 5, 120, 18, 187, 51, 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, - 166, 66, 157, 255, 237, 69, 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, - 156, 220, 159, 4, 99, 48, 191, 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, - 182, 109, 207, 197, 239, 161, 132, 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, - 253, 23, 169, 75, 236, 211, 126, 121, 132, 191, 68, 167, 200, 16, 154, 149, - 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, 153, 30, 209, 238, 53, 233, 148, - 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, 255, 219, 170, 98, 17, 160, 179, - 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, 73, 78, 79, 72, 253, 105, - 245, 84, 244, 196, - ], - quorum_indexes: vec![0, 1], - }, - }; - let result = verifier.verify_batch(&cert).await; - assert!(result.is_ok()); - } - - // #[ignore = "depends on external RPC"] - #[tokio::test] - async fn test_verify_security_params() { - let cfg = get_verifier_config(); - let signing_client = create_remote_signing_client(cfg.clone()); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let cert = BlobInfo { - blob_header: BlobHeader { - commitment: G1Commitment { - x: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - y: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - }, - data_length: 4, - blob_quorum_params: vec![ - BlobQuorumParam { - quorum_number: 0, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - BlobQuorumParam { - quorum_number: 1, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - ], - }, - blob_verification_proof: BlobVerificationProof { - batch_id: 66507, - blob_index: 92, - batch_medatada: BatchMetadata { - batch_header: BatchHeader { - batch_root: vec![ - 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, - 8, 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, - 211, 43, - ], - quorum_numbers: vec![0, 1], - quorum_signed_percentages: vec![100, 100], - reference_block_number: 2624794, - }, - signatory_record_hash: vec![ - 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, - 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, - ], - fee: vec![0], - confirmation_block_number: 2624876, - batch_header_hash: vec![ - 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, - 146, 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, - ], - }, - inclusion_proof: vec![ - 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, - 140, 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, - 125, 123, 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, - 157, 203, 22, 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, - 128, 112, 84, 34, 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, - 5, 120, 18, 187, 51, 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, - 166, 66, 157, 255, 237, 69, 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, - 156, 220, 159, 4, 99, 48, 191, 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, - 182, 109, 207, 197, 239, 161, 132, 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, - 253, 23, 169, 75, 236, 211, 126, 121, 132, 191, 68, 167, 200, 16, 154, 149, - 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, 153, 30, 209, 238, 53, 233, 148, - 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, 255, 219, 170, 98, 17, 160, 179, - 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, 73, 78, 79, 72, 253, 105, - 245, 84, 244, 196, - ], - quorum_indexes: vec![0, 1], - }, - }; - let result = verifier.verify_security_params(&cert).await; - assert!(result.is_ok()); - } - - /// Test security params verification with a mocked verifier. - /// To test actual behaviour of the verifier, run the test above - #[tokio::test] - async fn test_verify_security_params_mocked() { - let mut mock_replies = HashMap::new(); - - // First request - let mock_req = CallRequest { - from: None, - to: Some(H160::from_str("0xd4a7e1bd8015057293f0d0a557088c286942e84b").unwrap()), - gas: None, - gas_price: None, - value: None, - data: Some(Bytes::from(hex::decode("8687feae").unwrap())), - transaction_type: None, - access_list: None, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - }; - let mock_req = serde_json::to_string(&mock_req).unwrap(); - let mock_res = Bytes::from( - hex::decode("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000") - .unwrap(), - ); - mock_replies.insert(mock_req, mock_res); - - // Second request - let mock_req = CallRequest { - from: None, - to: Some(H160::from_str("0xd4a7e1bd8015057293f0d0a557088c286942e84b").unwrap()), - gas: None, - gas_price: None, - value: None, - data: Some(Bytes::from(hex::decode("e15234ff").unwrap())), - transaction_type: None, - access_list: None, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - }; - let mock_req = serde_json::to_string(&mock_req).unwrap(); - let mock_res = Bytes::from( - hex::decode("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000") - .unwrap(), - ); - mock_replies.insert(mock_req, mock_res); - - let cfg = get_verifier_config(); - let signing_client = MockVerifierClient::new(mock_replies); - let verifier = Verifier::new(cfg, Arc::new(signing_client)).await.unwrap(); - let cert = BlobInfo { - blob_header: BlobHeader { - commitment: G1Commitment { - x: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - y: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - }, - data_length: 4, - blob_quorum_params: vec![ - BlobQuorumParam { - quorum_number: 0, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - BlobQuorumParam { - quorum_number: 1, - adversary_threshold_percentage: 33, - confirmation_threshold_percentage: 55, - chunk_length: 1, - }, - ], - }, - blob_verification_proof: BlobVerificationProof { - batch_id: 66507, - blob_index: 92, - batch_medatada: BatchMetadata { - batch_header: BatchHeader { - batch_root: vec![ - 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, - 8, 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, - 211, 43, - ], - quorum_numbers: vec![0, 1], - quorum_signed_percentages: vec![100, 100], - reference_block_number: 2624794, - }, - signatory_record_hash: vec![ - 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, - 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, - ], - fee: vec![0], - confirmation_block_number: 2624876, - batch_header_hash: vec![ - 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, - 146, 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, - ], - }, - inclusion_proof: vec![ - 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, - 140, 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, - 125, 123, 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, - 157, 203, 22, 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, - 128, 112, 84, 34, 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, - 5, 120, 18, 187, 51, 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, - 166, 66, 157, 255, 237, 69, 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, - 156, 220, 159, 4, 99, 48, 191, 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, - 182, 109, 207, 197, 239, 161, 132, 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, - 253, 23, 169, 75, 236, 211, 126, 121, 132, 191, 68, 167, 200, 16, 154, 149, - 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, 153, 30, 209, 238, 53, 233, 148, - 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, 255, 219, 170, 98, 17, 160, 179, - 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, 73, 78, 79, 72, 253, 105, - 245, 84, 244, 196, - ], - quorum_indexes: vec![0, 1], - }, - }; - let result = verifier.verify_security_params(&cert).await; - assert!(result.is_ok()); - } -} diff --git a/core/node/da_clients/src/eigen/verifier/mod.rs b/core/node/da_clients/src/eigen/verifier/mod.rs new file mode 100644 index 000000000000..295b474bda3d --- /dev/null +++ b/core/node/da_clients/src/eigen/verifier/mod.rs @@ -0,0 +1,545 @@ +use std::{ + collections::HashMap, + io::Write, + path::{Path, PathBuf}, + sync::Arc, +}; + +use ark_bn254::{Fq, G1Affine}; +use ethabi::{encode, ParamType, Token}; +use rust_kzg_bn254::{blob::Blob, kzg::Kzg, polynomial::PolynomialFormat}; +use tempfile::NamedTempFile; +use tokio::task::JoinHandle; +use zksync_basic_types::web3::CallRequest; +use zksync_config::EigenConfig; +use zksync_eth_client::EthInterface; +use zksync_types::{web3, Address, U256, U64}; +use zksync_web3_decl::client::{DynClient, L1}; + +use super::{ + blob_info::{BatchHeader, BlobHeader, BlobInfo, G1Commitment}, + errors::{KzgError, ServiceManagerError, VerificationError}, + sdk::RawEigenClient, +}; + +#[cfg(test)] +mod tests; + +fn decode_bytes(encoded: Vec) -> Result, VerificationError> { + let output_type = [ParamType::Bytes]; + let tokens = ethabi::decode(&output_type, &encoded) + .map_err(|e| ServiceManagerError::Decoding(e.to_string()))?; + + // Safe unwrap because decode guarantees type correctness and non-empty output + let token = tokens.into_iter().next().unwrap(); + + // Safe unwrap, as type is guaranteed + Ok(token.into_bytes().unwrap()) +} + +#[async_trait::async_trait] +pub trait VerifierClient: Sync + Send + std::fmt::Debug { + /// Request to the EigenDA service manager contract + /// the batch metadata hash for a given batch id + async fn batch_id_to_batch_metadata_hash( + &self, + batch_id: u32, + svc_manager_addr: Address, + settlement_layer_confirmation_depth: Option, + ) -> Result, VerificationError>; + + /// Request to the EigenDA service manager contract + /// the quorum adversary threshold percentages for a given quorum number + async fn quorum_adversary_threshold_percentages( + &self, + quorum_number: u32, + svc_manager_addr: Address, + ) -> Result; + + /// Request to the EigenDA service manager contract + /// the set of quorum numbers that are required + async fn required_quorum_numbers( + &self, + svc_manager_addr: Address, + ) -> Result, VerificationError>; +} + +#[async_trait::async_trait] +impl VerifierClient for Box> { + async fn batch_id_to_batch_metadata_hash( + &self, + batch_id: u32, + svc_manager_addr: Address, + settlement_layer_confirmation_depth: Option, + ) -> Result, VerificationError> { + let mut data = vec![]; + let func_selector = + ethabi::short_signature("batchIdToBatchMetadataHash", &[ParamType::Uint(32)]); + data.extend_from_slice(&func_selector); + let batch_id_data = encode(&[Token::Uint(U256::from(batch_id))]); + data.extend_from_slice(&batch_id_data); + + let call_request = CallRequest { + to: Some(svc_manager_addr), + data: Some(zksync_basic_types::web3::Bytes(data)), + ..Default::default() + }; + + let block_id = match settlement_layer_confirmation_depth { + Some(depth) => { + let depth = depth.saturating_sub(U64::one()); + let mut current_block = self + .block_number() + .await + .map_err(ServiceManagerError::EnrichedClient)?; + current_block = current_block.saturating_sub(depth); + Some(current_block.into()) + } + None => None, + }; + + let res = self + .as_ref() + .call_contract_function(call_request, block_id) + .await + .map_err(ServiceManagerError::EnrichedClient)?; + + Ok(res.0.to_vec()) + } + + async fn quorum_adversary_threshold_percentages( + &self, + quorum_number: u32, + svc_manager_addr: Address, + ) -> Result { + let func_selector = ethabi::short_signature("quorumAdversaryThresholdPercentages", &[]); + let data = func_selector.to_vec(); + + let call_request = CallRequest { + to: Some(svc_manager_addr), + data: Some(zksync_basic_types::web3::Bytes(data)), + ..Default::default() + }; + + let res = self + .as_ref() + .call_contract_function(call_request, None) + .await + .map_err(ServiceManagerError::EnrichedClient)?; + + let percentages = decode_bytes(res.0)?; + + if percentages.len() > quorum_number as usize { + return Ok(percentages[quorum_number as usize]); + } + Ok(0) + } + + async fn required_quorum_numbers( + &self, + svc_manager_addr: Address, + ) -> Result, VerificationError> { + let func_selector = ethabi::short_signature("quorumNumbersRequired", &[]); + let data = func_selector.to_vec(); + let call_request = CallRequest { + to: Some(svc_manager_addr), + data: Some(zksync_basic_types::web3::Bytes(data)), + ..Default::default() + }; + let res = self + .as_ref() + .call_contract_function(call_request, None) + .await + .map_err(ServiceManagerError::EnrichedClient)?; + + decode_bytes(res.0.to_vec()) + } +} + +#[derive(Debug)] +enum PointFile { + Temp(NamedTempFile), + Path(PathBuf), +} + +impl PointFile { + fn path(&self) -> &Path { + match self { + PointFile::Temp(file) => file.path(), + PointFile::Path(path) => path.as_path(), + } + } +} + +/// Verifier used to verify the integrity of the blob info +/// Kzg is used for commitment verification +/// EigenDA service manager is used to connect to the service manager contract +#[derive(Debug)] +pub struct Verifier { + kzg: Kzg, + cfg: EigenConfig, + client: Arc, +} + +impl Verifier { + pub const DEFAULT_PRIORITY_FEE_PER_GAS: u64 = 100; + pub const SRSORDER: u32 = 1 << 28; // 2 ^ 28 + pub const G1POINT: &'static str = "g1.point"; + pub const G2POINT: &'static str = "g2.point.powerOf2"; + pub const POINT_SIZE: u32 = 32; + + async fn download_temp_point(url: &String) -> Result { + let response = reqwest::get(url) + .await + .map_err(|e| VerificationError::PointDownloadError(e.to_string()))?; + + if !response.status().is_success() { + return Err(VerificationError::PointDownloadError(format!( + "Failed to download point from source {}", + url + ))); + } + + let content = response + .bytes() + .await + .map_err(|e| VerificationError::PointDownloadError(e.to_string()))?; + + // Tempfile writting uses `std::fs`, so we need to spawn a blocking task + let temp_file = tokio::task::spawn_blocking(move || { + let mut file = NamedTempFile::new() + .map_err(|e| VerificationError::PointDownloadError(e.to_string()))?; + + file.write_all(&content) + .map_err(|e| VerificationError::PointDownloadError(e.to_string()))?; + + file.flush() + .map_err(|e| VerificationError::PointDownloadError(e.to_string()))?; + + Ok::(file) + }) + .await + .map_err(|e| VerificationError::PointDownloadError(e.to_string()))??; + + Ok::(temp_file) + } + + async fn get_points(cfg: &EigenConfig) -> Result<(PointFile, PointFile), VerificationError> { + match &cfg.points_dir { + Some(path) => Ok(( + PointFile::Path(PathBuf::from(format!("{}/{}", path, Self::G1POINT))), + PointFile::Path(PathBuf::from(format!("{}/{}", path, Self::G2POINT))), + )), + None => { + tracing::info!("Points for KZG setup not found, downloading points to a temp file"); + Ok(( + PointFile::Temp(Self::download_temp_point(&cfg.g1_url).await?), + PointFile::Temp(Self::download_temp_point(&cfg.g2_url).await?), + )) + } + } + } + + pub(crate) async fn new( + cfg: EigenConfig, + client: Arc, + ) -> Result { + let srs_points_to_load = RawEigenClient::blob_size_limit() as u32 / Self::POINT_SIZE; + let (g1_point_file, g2_point_file) = Self::get_points(&cfg).await?; + let kzg_handle: JoinHandle> = + tokio::task::spawn_blocking(move || { + let g1_point_file_path = g1_point_file.path().to_str().ok_or(KzgError::Setup( + "Could not format point path into a valid string".to_string(), + ))?; + let g2_point_file_path = g2_point_file.path().to_str().ok_or(KzgError::Setup( + "Could not format point path into a valid string".to_string(), + ))?; + Kzg::setup( + g1_point_file_path, + "", + g2_point_file_path, + Self::SRSORDER, + srs_points_to_load, + "".to_string(), + ) + .map_err(KzgError::Internal) + }); + let kzg = kzg_handle + .await + .map_err(|e| VerificationError::Kzg(KzgError::Setup(e.to_string())))??; + + Ok(Self { kzg, cfg, client }) + } + + /// Return the commitment from a blob + fn commit(&self, blob: &[u8]) -> Result { + let blob = Blob::from_bytes_and_pad(blob); + self.kzg + .blob_to_kzg_commitment(&blob, PolynomialFormat::InEvaluationForm) + .map_err(|e| VerificationError::Kzg(KzgError::Internal(e))) + } + + /// Compare the given commitment with the commitment generated with the blob + pub fn verify_commitment( + &self, + expected_commitment: G1Commitment, + blob: &[u8], + ) -> Result<(), VerificationError> { + let actual_commitment = self.commit(blob)?; + let expected_commitment = G1Affine::new_unchecked( + Fq::from(num_bigint::BigUint::from_bytes_be(&expected_commitment.x)), + Fq::from(num_bigint::BigUint::from_bytes_be(&expected_commitment.y)), + ); + if !expected_commitment.is_on_curve() { + return Err(VerificationError::CommitmentNotOnCurve(expected_commitment)); + } + if !expected_commitment.is_in_correct_subgroup_assuming_on_curve() { + return Err(VerificationError::CommitmentNotOnCorrectSubgroup( + expected_commitment, + )); + } + if actual_commitment != expected_commitment { + return Err(VerificationError::DifferentCommitments { + expected: Box::new(expected_commitment), + actual: Box::new(actual_commitment), + }); + } + Ok(()) + } + + pub(crate) fn hash_encode_blob_header(&self, blob_header: &BlobHeader) -> Vec { + let mut blob_quorums = vec![]; + for quorum in &blob_header.blob_quorum_params { + let quorum = Token::Tuple(vec![ + Token::Uint(ethabi::Uint::from(quorum.quorum_number)), + Token::Uint(ethabi::Uint::from(quorum.adversary_threshold_percentage)), + Token::Uint(ethabi::Uint::from(quorum.confirmation_threshold_percentage)), + Token::Uint(ethabi::Uint::from(quorum.chunk_length)), + ]); + blob_quorums.push(quorum); + } + let blob_header = Token::Tuple(vec![ + Token::Tuple(vec![ + Token::Uint(ethabi::Uint::from_big_endian(&blob_header.commitment.x)), + Token::Uint(ethabi::Uint::from_big_endian(&blob_header.commitment.y)), + ]), + Token::Uint(ethabi::Uint::from(blob_header.data_length)), + Token::Array(blob_quorums), + ]); + + let encoded = encode(&[blob_header]); + web3::keccak256(&encoded).to_vec() + } + + pub(crate) fn process_inclusion_proof( + &self, + proof: &[u8], + leaf: [u8; 32], + index: u32, + ) -> Result, VerificationError> { + let mut index = index; + if proof.is_empty() || proof.len() % 32 != 0 { + return Err(VerificationError::WrongProof); + } + let mut computed_hash = leaf.to_vec(); + for chunk in proof.chunks(32) { + let mut buffer = [0u8; 64]; + if index % 2 == 0 { + buffer[..32].copy_from_slice(&computed_hash); + buffer[32..].copy_from_slice(chunk); + } else { + buffer[..32].copy_from_slice(chunk); + buffer[32..].copy_from_slice(&computed_hash); + } + computed_hash = web3::keccak256(&buffer).to_vec(); + index /= 2; + } + + Ok(computed_hash) + } + + /// Verifies the certificate's batch root + pub(crate) fn verify_merkle_proof(&self, cert: &BlobInfo) -> Result<(), VerificationError> { + let inclusion_proof = &cert.blob_verification_proof.inclusion_proof; + let root = &cert + .blob_verification_proof + .batch_medatada + .batch_header + .batch_root; + let blob_index = cert.blob_verification_proof.blob_index; + let blob_header = &cert.blob_header; + + let blob_header_hash = self.hash_encode_blob_header(blob_header); + let leaf_hash = web3::keccak256(&blob_header_hash); + + let generated_root = + self.process_inclusion_proof(inclusion_proof, leaf_hash, blob_index)?; + + if generated_root != *root { + return Err(VerificationError::DifferentRoots { + expected: hex::encode(root), + actual: hex::encode(&generated_root), + }); + } + Ok(()) + } + + fn hash_batch_metadata( + &self, + batch_header: &BatchHeader, + signatory_record_hash: &[u8], + confirmation_block_number: u32, + ) -> Vec { + let batch_header_token = Token::Tuple(vec![ + Token::FixedBytes(batch_header.batch_root.clone()), // Clone only where necessary + Token::Bytes(batch_header.quorum_numbers.clone()), + Token::Bytes(batch_header.quorum_signed_percentages.clone()), + Token::Uint(ethabi::Uint::from(batch_header.reference_block_number)), + ]); + + let encoded = encode(&[batch_header_token]); + let header_hash = web3::keccak256(&encoded).to_vec(); + + let hash_token = Token::Tuple(vec![ + Token::FixedBytes(header_hash.to_vec()), + Token::FixedBytes(signatory_record_hash.to_owned()), // Clone only if required + ]); + + let mut hash_encoded = encode(&[hash_token]); + + hash_encoded.append(&mut confirmation_block_number.to_be_bytes().to_vec()); + web3::keccak256(&hash_encoded).to_vec() + } + + async fn call_batch_id_to_metadata_hash( + &self, + blob_info: &BlobInfo, + ) -> Result, VerificationError> { + self.client + .as_ref() + .batch_id_to_batch_metadata_hash( + blob_info.blob_verification_proof.batch_id, + self.cfg.eigenda_svc_manager_address, + Some(U64::from(self.cfg.settlement_layer_confirmation_depth)), + ) + .await + } + + /// Verifies the certificate batch hash + pub(crate) async fn verify_batch(&self, blob_info: &BlobInfo) -> Result<(), VerificationError> { + let expected_hash = self.call_batch_id_to_metadata_hash(blob_info).await?; + + if expected_hash == vec![0u8; 32] { + return Err(VerificationError::EmptyHash); + } + + let actual_hash = self.hash_batch_metadata( + &blob_info + .blob_verification_proof + .batch_medatada + .batch_header, + &blob_info + .blob_verification_proof + .batch_medatada + .signatory_record_hash, + blob_info + .blob_verification_proof + .batch_medatada + .confirmation_block_number, + ); + + if expected_hash != actual_hash { + return Err(VerificationError::DifferentHashes { + expected: hex::encode(&expected_hash), + actual: hex::encode(&actual_hash), + }); + } + Ok(()) + } + + async fn get_quorum_adversary_threshold( + &self, + quorum_number: u32, + ) -> Result { + self.client + .as_ref() + .quorum_adversary_threshold_percentages( + quorum_number, + self.cfg.eigenda_svc_manager_address, + ) + .await + } + + async fn call_quorum_numbers_required(&self) -> Result, VerificationError> { + self.client + .as_ref() + .required_quorum_numbers(self.cfg.eigenda_svc_manager_address) + .await + } + + /// Verifies that the certificate's blob quorum params are correct + pub async fn verify_security_params(&self, cert: &BlobInfo) -> Result<(), VerificationError> { + let blob_header = &cert.blob_header; + let batch_header = &cert.blob_verification_proof.batch_medatada.batch_header; + + let mut confirmed_quorums: HashMap = HashMap::new(); + for i in 0..blob_header.blob_quorum_params.len() { + if batch_header.quorum_numbers[i] as u32 + != blob_header.blob_quorum_params[i].quorum_number + { + return Err(VerificationError::WrongQuorumParams { + blob_quorum_params: blob_header.blob_quorum_params[i].clone(), + }); + } + if blob_header.blob_quorum_params[i].adversary_threshold_percentage + > blob_header.blob_quorum_params[i].confirmation_threshold_percentage + { + return Err(VerificationError::WrongQuorumParams { + blob_quorum_params: blob_header.blob_quorum_params[i].clone(), + }); + } + let quorum_adversary_threshold = self + .get_quorum_adversary_threshold(blob_header.blob_quorum_params[i].quorum_number) + .await?; + + if quorum_adversary_threshold > 0 + && blob_header.blob_quorum_params[i].adversary_threshold_percentage + < quorum_adversary_threshold as u32 + { + return Err(VerificationError::WrongQuorumParams { + blob_quorum_params: blob_header.blob_quorum_params[i].clone(), + }); + } + + if (batch_header.quorum_signed_percentages[i] as u32) + < blob_header.blob_quorum_params[i].confirmation_threshold_percentage + { + return Err(VerificationError::WrongQuorumParams { + blob_quorum_params: blob_header.blob_quorum_params[i].clone(), + }); + } + + confirmed_quorums.insert(blob_header.blob_quorum_params[i].quorum_number, true); + } + + let required_quorums = self.call_quorum_numbers_required().await?; + + for quorum in required_quorums { + if !confirmed_quorums.contains_key(&(quorum as u32)) { + return Err(VerificationError::QuorumNotConfirmed); + } + } + Ok(()) + } + + /// Verifies that the certificate is valid + pub async fn verify_inclusion_data_against_settlement_layer( + &self, + cert: &BlobInfo, + ) -> Result<(), VerificationError> { + self.verify_batch(cert).await?; + self.verify_merkle_proof(cert)?; + self.verify_security_params(cert).await?; + Ok(()) + } +} diff --git a/core/node/da_clients/src/eigen/verifier/tests.rs b/core/node/da_clients/src/eigen/verifier/tests.rs new file mode 100644 index 000000000000..034723fcc37b --- /dev/null +++ b/core/node/da_clients/src/eigen/verifier/tests.rs @@ -0,0 +1,795 @@ +use std::{collections::HashMap, str::FromStr, sync::Arc}; + +use ethabi::{ParamType, Token}; +use zksync_config::EigenConfig; +use zksync_types::{ + url::SensitiveUrl, + web3::{Bytes, CallRequest}, + Address, H160, U256, U64, +}; +use zksync_web3_decl::client::{Client, DynClient, L1}; + +use super::VerificationError; +use crate::eigen::{ + blob_info::{ + BatchHeader, BatchMetadata, BlobHeader, BlobInfo, BlobQuorumParam, BlobVerificationProof, + G1Commitment, + }, + verifier::{decode_bytes, Verifier, VerifierClient}, +}; + +/// Mock struct for the Verifier +/// Used to avoid making actual calls to a remote disperser +/// and possible making the CI fail due to network issues. +/// To run tests with the actual verifier run: +/// `cargo test -p zksync_da_clients -- --ignored` +#[derive(Debug)] +pub struct MockVerifierClient { + replies: HashMap, +} + +impl MockVerifierClient { + pub fn new(replies: HashMap) -> Self { + Self { replies } + } +} + +#[async_trait::async_trait] +impl VerifierClient for MockVerifierClient { + async fn batch_id_to_batch_metadata_hash( + &self, + batch_id: u32, + svc_manager_addr: Address, + _settlement_layer_confirmation_depth: Option, + ) -> Result, VerificationError> { + let mut data = vec![]; + let func_selector = + ethabi::short_signature("batchIdToBatchMetadataHash", &[ParamType::Uint(32)]); + data.extend_from_slice(&func_selector); + let batch_id_data = ethabi::encode(&[Token::Uint(U256::from(batch_id))]); + data.extend_from_slice(&batch_id_data); + + let call_request = CallRequest { + to: Some(svc_manager_addr), + data: Some(zksync_basic_types::web3::Bytes(data)), + ..Default::default() + }; + + let req = serde_json::to_string(&call_request).unwrap(); + Ok(self.replies.get(&req).unwrap().clone().0) + } + + async fn quorum_adversary_threshold_percentages( + &self, + quorum_number: u32, + svc_manager_addr: Address, + ) -> Result { + let func_selector = ethabi::short_signature("quorumAdversaryThresholdPercentages", &[]); + let data = func_selector.to_vec(); + + let call_request = CallRequest { + to: Some(svc_manager_addr), + data: Some(zksync_basic_types::web3::Bytes(data)), + ..Default::default() + }; + + let req = serde_json::to_string(&call_request).unwrap(); + let res = self.replies.get(&req).unwrap().clone(); + let percentages = decode_bytes(res.0)?; + + if percentages.len() > quorum_number as usize { + return Ok(percentages[quorum_number as usize]); + } + Ok(0) + } + + async fn required_quorum_numbers( + &self, + svc_manager_addr: Address, + ) -> Result, VerificationError> { + let func_selector = ethabi::short_signature("quorumNumbersRequired", &[]); + let data = func_selector.to_vec(); + let call_request = CallRequest { + to: Some(svc_manager_addr), + data: Some(zksync_basic_types::web3::Bytes(data)), + ..Default::default() + }; + + let req = serde_json::to_string(&call_request).unwrap(); + let res = self.replies.get(&req).unwrap().clone(); + decode_bytes(res.0.to_vec()) + } +} + +fn create_remote_query_client() -> Box> { + let url = SensitiveUrl::from_str("https://ethereum-holesky-rpc.publicnode.com").unwrap(); + let query_client: Client = Client::http(url).unwrap().build(); + Box::new(query_client) as Box> +} + +#[ignore = "depends on external RPC"] +#[tokio::test] +async fn test_verify_commitment() { + let cfg = EigenConfig::default(); + let query_client = create_remote_query_client(); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let commitment = G1Commitment { + x: vec![ + 22, 11, 176, 29, 82, 48, 62, 49, 51, 119, 94, 17, 156, 142, 248, 96, 240, 183, 134, 85, + 152, 5, 74, 27, 175, 83, 162, 148, 17, 110, 201, 74, + ], + y: vec![ + 12, 132, 236, 56, 147, 6, 176, 135, 244, 166, 21, 18, 87, 76, 122, 3, 23, 22, 254, 236, + 148, 129, 110, 207, 131, 116, 58, 170, 4, 130, 191, 157, + ], + }; + let blob = vec![1u8; 100]; // Actual blob sent was this blob but kzg-padded, but Blob::from_bytes_and_pad padds it inside, so we don't need to pad it here. + let result = verifier.verify_commitment(commitment, &blob); + assert!(result.is_ok()); +} + +/// Test the verification of the commitment with a mocked verifier. +/// To test actual behaviour of the verifier, run the test above +#[tokio::test] +async fn test_verify_commitment_mocked() { + let cfg = EigenConfig::default(); + let query_client = MockVerifierClient::new(HashMap::new()); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let commitment = G1Commitment { + x: vec![ + 22, 11, 176, 29, 82, 48, 62, 49, 51, 119, 94, 17, 156, 142, 248, 96, 240, 183, 134, 85, + 152, 5, 74, 27, 175, 83, 162, 148, 17, 110, 201, 74, + ], + y: vec![ + 12, 132, 236, 56, 147, 6, 176, 135, 244, 166, 21, 18, 87, 76, 122, 3, 23, 22, 254, 236, + 148, 129, 110, 207, 131, 116, 58, 170, 4, 130, 191, 157, + ], + }; + let blob = vec![1u8; 100]; // Actual blob sent was this blob but kzg-padded, but Blob::from_bytes_and_pad padds it inside, so we don't need to pad it here. + let result = verifier.verify_commitment(commitment, &blob); + assert!(result.is_ok()); +} + +#[ignore = "depends on external RPC"] +#[tokio::test] +async fn test_verify_merkle_proof() { + let cfg = EigenConfig::default(); + let query_client = create_remote_query_client(); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let cert = BlobInfo { + blob_header: BlobHeader { + commitment: G1Commitment { + x: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + y: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + }, + data_length: 4, + blob_quorum_params: vec![ + BlobQuorumParam { + quorum_number: 0, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + BlobQuorumParam { + quorum_number: 1, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + ], + }, + blob_verification_proof: BlobVerificationProof { + batch_id: 66507, + blob_index: 92, + batch_medatada: BatchMetadata { + batch_header: BatchHeader { + batch_root: vec![ + 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, 8, + 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, 211, 43, + ], + quorum_numbers: vec![0, 1], + quorum_signed_percentages: vec![100, 100], + reference_block_number: 2624794, + }, + signatory_record_hash: vec![ + 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, + 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, + ], + fee: vec![0], + confirmation_block_number: 2624876, + batch_header_hash: vec![ + 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, 146, + 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, + ], + }, + inclusion_proof: vec![ + 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, 140, + 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, 125, 123, + 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, 157, 203, 22, + 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, 128, 112, 84, 34, + 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, 5, 120, 18, 187, 51, + 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, 166, 66, 157, 255, 237, 69, + 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, 156, 220, 159, 4, 99, 48, 191, + 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, 182, 109, 207, 197, 239, 161, 132, + 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, 253, 23, 169, 75, 236, 211, 126, 121, + 132, 191, 68, 167, 200, 16, 154, 149, 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, + 153, 30, 209, 238, 53, 233, 148, 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, + 255, 219, 170, 98, 17, 160, 179, 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, + 73, 78, 79, 72, 253, 105, 245, 84, 244, 196, + ], + quorum_indexes: vec![0, 1], + }, + }; + let result = verifier.verify_merkle_proof(&cert); + assert!(result.is_ok()); +} + +/// Test the verificarion of a merkle proof with a mocked verifier. +/// To test actual behaviour of the verifier, run the test above +#[tokio::test] +async fn test_verify_merkle_proof_mocked() { + let cfg = EigenConfig::default(); + let query_client = MockVerifierClient::new(HashMap::new()); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let cert = BlobInfo { + blob_header: BlobHeader { + commitment: G1Commitment { + x: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + y: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + }, + data_length: 4, + blob_quorum_params: vec![ + BlobQuorumParam { + quorum_number: 0, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + BlobQuorumParam { + quorum_number: 1, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + ], + }, + blob_verification_proof: BlobVerificationProof { + batch_id: 66507, + blob_index: 92, + batch_medatada: BatchMetadata { + batch_header: BatchHeader { + batch_root: vec![ + 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, 8, + 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, 211, 43, + ], + quorum_numbers: vec![0, 1], + quorum_signed_percentages: vec![100, 100], + reference_block_number: 2624794, + }, + signatory_record_hash: vec![ + 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, + 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, + ], + fee: vec![0], + confirmation_block_number: 2624876, + batch_header_hash: vec![ + 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, 146, + 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, + ], + }, + inclusion_proof: vec![ + 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, 140, + 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, 125, 123, + 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, 157, 203, 22, + 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, 128, 112, 84, 34, + 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, 5, 120, 18, 187, 51, + 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, 166, 66, 157, 255, 237, 69, + 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, 156, 220, 159, 4, 99, 48, 191, + 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, 182, 109, 207, 197, 239, 161, 132, + 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, 253, 23, 169, 75, 236, 211, 126, 121, + 132, 191, 68, 167, 200, 16, 154, 149, 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, + 153, 30, 209, 238, 53, 233, 148, 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, + 255, 219, 170, 98, 17, 160, 179, 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, + 73, 78, 79, 72, 253, 105, 245, 84, 244, 196, + ], + quorum_indexes: vec![0, 1], + }, + }; + let result = verifier.verify_merkle_proof(&cert); + assert!(result.is_ok()); +} + +#[ignore = "depends on external RPC"] +#[tokio::test] +async fn test_hash_blob_header() { + let cfg = EigenConfig::default(); + let query_client = create_remote_query_client(); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let blob_header = BlobHeader { + commitment: G1Commitment { + x: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ], + y: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ], + }, + data_length: 2, + blob_quorum_params: vec![ + BlobQuorumParam { + quorum_number: 2, + adversary_threshold_percentage: 4, + confirmation_threshold_percentage: 5, + chunk_length: 6, + }, + BlobQuorumParam { + quorum_number: 2, + adversary_threshold_percentage: 4, + confirmation_threshold_percentage: 5, + chunk_length: 6, + }, + ], + }; + let result = verifier.hash_encode_blob_header(&blob_header); + let expected = "ba4675a31c9bf6b2f7abfdcedd34b74645cb7332b35db39bff00ae8516a67393"; + assert_eq!(result, hex::decode(expected).unwrap()); +} + +/// Test hashing of a blob header with a mocked verifier. +/// To test actual behaviour of the verifier, run the test above +#[tokio::test] +async fn test_hash_blob_header_mocked() { + let cfg = EigenConfig::default(); + let query_client = MockVerifierClient::new(HashMap::new()); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let blob_header = BlobHeader { + commitment: G1Commitment { + x: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ], + y: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ], + }, + data_length: 2, + blob_quorum_params: vec![ + BlobQuorumParam { + quorum_number: 2, + adversary_threshold_percentage: 4, + confirmation_threshold_percentage: 5, + chunk_length: 6, + }, + BlobQuorumParam { + quorum_number: 2, + adversary_threshold_percentage: 4, + confirmation_threshold_percentage: 5, + chunk_length: 6, + }, + ], + }; + let result = verifier.hash_encode_blob_header(&blob_header); + let expected = "ba4675a31c9bf6b2f7abfdcedd34b74645cb7332b35db39bff00ae8516a67393"; + assert_eq!(result, hex::decode(expected).unwrap()); +} + +#[ignore = "depends on external RPC"] +#[tokio::test] +async fn test_inclusion_proof() { + let cfg = EigenConfig::default(); + let query_client = create_remote_query_client(); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let proof = hex::decode("c455c1ea0e725d7ea3e5f29e9f48be8fc2787bb0a914d5a86710ba302c166ac4f626d76f67f1055bb960a514fb8923af2078fd84085d712655b58a19612e8cd15c3e4ac1cef57acde3438dbcf63f47c9fefe1221344c4d5c1a4943dd0d1803091ca81a270909dc0e146841441c9bd0e08e69ce6168181a3e4060ffacf3627480bec6abdd8d7bb92b49d33f180c42f49e041752aaded9c403db3a17b85e48a11e9ea9a08763f7f383dab6d25236f1b77c12b4c49c5cdbcbea32554a604e3f1d2f466851cb43fe73617b3d01e665e4c019bf930f92dea7394c25ed6a1e200d051fb0c30a2193c459f1cfef00bf1ba6656510d16725a4d1dc031cb759dbc90bab427b0f60ddc6764681924dda848824605a4f08b7f526fe6bd4572458c94e83fbf2150f2eeb28d3011ec921996dc3e69efa52d5fcf3182b20b56b5857a926aa66605808079b4d52c0c0cfe06923fa92e65eeca2c3e6126108e8c1babf5ac522f4d7").unwrap(); + let leaf: [u8; 32] = + hex::decode("f6106e6ae4631e68abe0fa898cedbe97dbae6c7efb1b088c5aa2e8b91190ff96") + .unwrap() + .try_into() + .unwrap(); + let expected_root = + hex::decode("7390b8023db8248123dcaeca57fa6c9340bef639e204f2278fc7ec3d46ad071b").unwrap(); + + let actual_root = verifier.process_inclusion_proof(&proof, leaf, 580).unwrap(); + + assert_eq!(actual_root, expected_root); +} + +/// Test proof inclusion with a mocked verifier. +/// To test actual behaviour of the verifier, run the test above +#[tokio::test] +async fn test_inclusion_proof_mocked() { + let cfg = EigenConfig::default(); + let query_client = MockVerifierClient::new(HashMap::new()); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let proof = hex::decode("c455c1ea0e725d7ea3e5f29e9f48be8fc2787bb0a914d5a86710ba302c166ac4f626d76f67f1055bb960a514fb8923af2078fd84085d712655b58a19612e8cd15c3e4ac1cef57acde3438dbcf63f47c9fefe1221344c4d5c1a4943dd0d1803091ca81a270909dc0e146841441c9bd0e08e69ce6168181a3e4060ffacf3627480bec6abdd8d7bb92b49d33f180c42f49e041752aaded9c403db3a17b85e48a11e9ea9a08763f7f383dab6d25236f1b77c12b4c49c5cdbcbea32554a604e3f1d2f466851cb43fe73617b3d01e665e4c019bf930f92dea7394c25ed6a1e200d051fb0c30a2193c459f1cfef00bf1ba6656510d16725a4d1dc031cb759dbc90bab427b0f60ddc6764681924dda848824605a4f08b7f526fe6bd4572458c94e83fbf2150f2eeb28d3011ec921996dc3e69efa52d5fcf3182b20b56b5857a926aa66605808079b4d52c0c0cfe06923fa92e65eeca2c3e6126108e8c1babf5ac522f4d7").unwrap(); + let leaf: [u8; 32] = + hex::decode("f6106e6ae4631e68abe0fa898cedbe97dbae6c7efb1b088c5aa2e8b91190ff96") + .unwrap() + .try_into() + .unwrap(); + let expected_root = + hex::decode("7390b8023db8248123dcaeca57fa6c9340bef639e204f2278fc7ec3d46ad071b").unwrap(); + + let actual_root = verifier.process_inclusion_proof(&proof, leaf, 580).unwrap(); + + assert_eq!(actual_root, expected_root); +} + +#[ignore = "depends on external RPC"] +#[tokio::test] +async fn test_verify_batch() { + let cfg = EigenConfig::default(); + let query_client = create_remote_query_client(); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let cert = BlobInfo { + blob_header: BlobHeader { + commitment: G1Commitment { + x: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + y: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + }, + data_length: 4, + blob_quorum_params: vec![ + BlobQuorumParam { + quorum_number: 0, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + BlobQuorumParam { + quorum_number: 1, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + ], + }, + blob_verification_proof: BlobVerificationProof { + batch_id: 66507, + blob_index: 92, + batch_medatada: BatchMetadata { + batch_header: BatchHeader { + batch_root: vec![ + 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, 8, + 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, 211, 43, + ], + quorum_numbers: vec![0, 1], + quorum_signed_percentages: vec![100, 100], + reference_block_number: 2624794, + }, + signatory_record_hash: vec![ + 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, + 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, + ], + fee: vec![0], + confirmation_block_number: 2624876, + batch_header_hash: vec![ + 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, 146, + 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, + ], + }, + inclusion_proof: vec![ + 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, 140, + 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, 125, 123, + 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, 157, 203, 22, + 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, 128, 112, 84, 34, + 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, 5, 120, 18, 187, 51, + 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, 166, 66, 157, 255, 237, 69, + 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, 156, 220, 159, 4, 99, 48, 191, + 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, 182, 109, 207, 197, 239, 161, 132, + 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, 253, 23, 169, 75, 236, 211, 126, 121, + 132, 191, 68, 167, 200, 16, 154, 149, 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, + 153, 30, 209, 238, 53, 233, 148, 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, + 255, 219, 170, 98, 17, 160, 179, 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, + 73, 78, 79, 72, 253, 105, 245, 84, 244, 196, + ], + quorum_indexes: vec![0, 1], + }, + }; + let result = verifier.verify_batch(&cert).await; + assert!(result.is_ok()); +} + +/// Test batch verification with a mocked verifier. +/// To test actual behaviour of the verifier, run the test above +#[tokio::test] +async fn test_verify_batch_mocked() { + let mut mock_replies = HashMap::new(); + let mock_req = CallRequest { + to: Some(H160::from_str("0xd4a7e1bd8015057293f0d0a557088c286942e84b").unwrap()), + data: Some(Bytes::from( + hex::decode("eccbbfc900000000000000000000000000000000000000000000000000000000000103cb") + .unwrap(), + )), + ..Default::default() + }; + let mock_req = serde_json::to_string(&mock_req).unwrap(); + let mock_res = Bytes::from( + hex::decode("60933e76989e57d6fd210ae2fc3086958d708660ee6927f91963047ab1a91ba8").unwrap(), + ); + mock_replies.insert(mock_req, mock_res); + + let cfg = EigenConfig::default(); + let query_client = MockVerifierClient::new(mock_replies); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let cert = BlobInfo { + blob_header: BlobHeader { + commitment: G1Commitment { + x: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + y: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + }, + data_length: 4, + blob_quorum_params: vec![ + BlobQuorumParam { + quorum_number: 0, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + BlobQuorumParam { + quorum_number: 1, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + ], + }, + blob_verification_proof: BlobVerificationProof { + batch_id: 66507, + blob_index: 92, + batch_medatada: BatchMetadata { + batch_header: BatchHeader { + batch_root: vec![ + 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, 8, + 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, 211, 43, + ], + quorum_numbers: vec![0, 1], + quorum_signed_percentages: vec![100, 100], + reference_block_number: 2624794, + }, + signatory_record_hash: vec![ + 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, + 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, + ], + fee: vec![0], + confirmation_block_number: 2624876, + batch_header_hash: vec![ + 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, 146, + 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, + ], + }, + inclusion_proof: vec![ + 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, 140, + 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, 125, 123, + 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, 157, 203, 22, + 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, 128, 112, 84, 34, + 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, 5, 120, 18, 187, 51, + 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, 166, 66, 157, 255, 237, 69, + 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, 156, 220, 159, 4, 99, 48, 191, + 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, 182, 109, 207, 197, 239, 161, 132, + 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, 253, 23, 169, 75, 236, 211, 126, 121, + 132, 191, 68, 167, 200, 16, 154, 149, 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, + 153, 30, 209, 238, 53, 233, 148, 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, + 255, 219, 170, 98, 17, 160, 179, 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, + 73, 78, 79, 72, 253, 105, 245, 84, 244, 196, + ], + quorum_indexes: vec![0, 1], + }, + }; + let result = verifier.verify_batch(&cert).await; + assert!(result.is_ok()); +} + +#[ignore = "depends on external RPC"] +#[tokio::test] +async fn test_verify_security_params() { + let cfg = EigenConfig::default(); + let query_client = create_remote_query_client(); + let verifier = Verifier::new(cfg, Arc::new(query_client)).await.unwrap(); + let cert = BlobInfo { + blob_header: BlobHeader { + commitment: G1Commitment { + x: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + y: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + }, + data_length: 4, + blob_quorum_params: vec![ + BlobQuorumParam { + quorum_number: 0, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + BlobQuorumParam { + quorum_number: 1, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + ], + }, + blob_verification_proof: BlobVerificationProof { + batch_id: 66507, + blob_index: 92, + batch_medatada: BatchMetadata { + batch_header: BatchHeader { + batch_root: vec![ + 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, 8, + 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, 211, 43, + ], + quorum_numbers: vec![0, 1], + quorum_signed_percentages: vec![100, 100], + reference_block_number: 2624794, + }, + signatory_record_hash: vec![ + 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, + 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, + ], + fee: vec![0], + confirmation_block_number: 2624876, + batch_header_hash: vec![ + 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, 146, + 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, + ], + }, + inclusion_proof: vec![ + 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, 140, + 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, 125, 123, + 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, 157, 203, 22, + 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, 128, 112, 84, 34, + 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, 5, 120, 18, 187, 51, + 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, 166, 66, 157, 255, 237, 69, + 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, 156, 220, 159, 4, 99, 48, 191, + 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, 182, 109, 207, 197, 239, 161, 132, + 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, 253, 23, 169, 75, 236, 211, 126, 121, + 132, 191, 68, 167, 200, 16, 154, 149, 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, + 153, 30, 209, 238, 53, 233, 148, 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, + 255, 219, 170, 98, 17, 160, 179, 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, + 73, 78, 79, 72, 253, 105, 245, 84, 244, 196, + ], + quorum_indexes: vec![0, 1], + }, + }; + let result = verifier.verify_security_params(&cert).await; + assert!(result.is_ok()); +} + +/// Test security params verification with a mocked verifier. +/// To test actual behaviour of the verifier, run the test above +#[tokio::test] +async fn test_verify_securityt_params_mocked() { + let mut mock_replies = HashMap::new(); + + // First request + let mock_req = CallRequest { + to: Some(H160::from_str("0xd4a7e1bd8015057293f0d0a557088c286942e84b").unwrap()), + data: Some(Bytes::from(hex::decode("8687feae").unwrap())), + ..Default::default() + }; + let mock_req = serde_json::to_string(&mock_req).unwrap(); + let mock_res = Bytes::from( + hex::decode("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000032121210000000000000000000000000000000000000000000000000000000000") + .unwrap(), + ); + mock_replies.insert(mock_req, mock_res); + + // Second request + let mock_req = CallRequest { + to: Some(H160::from_str("0xd4a7e1bd8015057293f0d0a557088c286942e84b").unwrap()), + data: Some(Bytes::from(hex::decode("e15234ff").unwrap())), + ..Default::default() + }; + let mock_req = serde_json::to_string(&mock_req).unwrap(); + let mock_res = Bytes::from( + hex::decode("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000") + .unwrap(), + ); + mock_replies.insert(mock_req, mock_res); + + let cfg = EigenConfig::default(); + let client = MockVerifierClient::new(mock_replies); + let verifier = Verifier::new(cfg, Arc::new(client)).await.unwrap(); + let cert = BlobInfo { + blob_header: BlobHeader { + commitment: G1Commitment { + x: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + y: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + }, + data_length: 4, + blob_quorum_params: vec![ + BlobQuorumParam { + quorum_number: 0, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + BlobQuorumParam { + quorum_number: 1, + adversary_threshold_percentage: 33, + confirmation_threshold_percentage: 55, + chunk_length: 1, + }, + ], + }, + blob_verification_proof: BlobVerificationProof { + batch_id: 66507, + blob_index: 92, + batch_medatada: BatchMetadata { + batch_header: BatchHeader { + batch_root: vec![ + 179, 187, 53, 98, 192, 80, 151, 28, 125, 192, 115, 29, 129, 238, 216, 8, + 213, 210, 203, 143, 181, 19, 146, 113, 98, 131, 39, 238, 149, 248, 211, 43, + ], + quorum_numbers: vec![0, 1], + quorum_signed_percentages: vec![100, 100], + reference_block_number: 2624794, + }, + signatory_record_hash: vec![ + 172, 32, 172, 142, 197, 52, 84, 143, 120, 26, 190, 9, 143, 217, 62, 19, 17, + 107, 105, 67, 203, 5, 172, 249, 6, 60, 105, 240, 134, 34, 66, 133, + ], + fee: vec![0], + confirmation_block_number: 2624876, + batch_header_hash: vec![ + 122, 115, 2, 85, 233, 75, 121, 85, 51, 81, 248, 170, 198, 252, 42, 16, 1, 146, + 96, 218, 159, 44, 41, 40, 94, 247, 147, 11, 255, 68, 40, 177, + ], + }, + inclusion_proof: vec![ + 203, 160, 237, 48, 117, 255, 75, 254, 117, 144, 164, 77, 29, 146, 36, 48, 190, 140, + 50, 100, 144, 237, 125, 125, 75, 54, 210, 247, 147, 23, 48, 189, 120, 4, 125, 123, + 195, 244, 207, 239, 145, 109, 0, 21, 11, 162, 109, 79, 192, 100, 138, 157, 203, 22, + 17, 114, 234, 72, 174, 231, 209, 133, 99, 118, 201, 160, 137, 128, 112, 84, 34, + 136, 174, 139, 96, 26, 246, 148, 134, 52, 200, 229, 160, 145, 5, 120, 18, 187, 51, + 11, 109, 91, 237, 171, 215, 207, 90, 95, 146, 54, 135, 166, 66, 157, 255, 237, 69, + 183, 141, 45, 162, 145, 71, 16, 87, 184, 120, 84, 156, 220, 159, 4, 99, 48, 191, + 203, 136, 112, 127, 226, 192, 184, 110, 6, 177, 182, 109, 207, 197, 239, 161, 132, + 17, 89, 56, 137, 205, 202, 101, 97, 60, 162, 253, 23, 169, 75, 236, 211, 126, 121, + 132, 191, 68, 167, 200, 16, 154, 149, 202, 197, 7, 191, 26, 8, 67, 3, 37, 137, 16, + 153, 30, 209, 238, 53, 233, 148, 198, 253, 94, 216, 73, 25, 190, 205, 132, 208, + 255, 219, 170, 98, 17, 160, 179, 183, 200, 17, 99, 36, 130, 216, 223, 72, 222, 250, + 73, 78, 79, 72, 253, 105, 245, 84, 244, 196, + ], + quorum_indexes: vec![0, 1], + }, + }; + let result = verifier.verify_security_params(&cert).await; + assert!(result.is_ok()); +}