From eb26298c90f59bf394bb6cebbf7146fdcdc90672 Mon Sep 17 00:00:00 2001 From: intelliDean Date: Wed, 29 Apr 2026 18:33:20 +0100 Subject: [PATCH 1/2] security: untrack config file containing secrets --- flashstat.toml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 flashstat.toml diff --git a/flashstat.toml b/flashstat.toml deleted file mode 100644 index e44fbe8..0000000 --- a/flashstat.toml +++ /dev/null @@ -1,25 +0,0 @@ -# FlashStat Configuration -# 🏮 The Transparency Layer for Unichain - -[rpc] -# ws_url = "wss://unichain-sepolia.api.onfinality.io/public-ws" -ws_url = "wss://boldest-dark-liquid.unichain-sepolia.quiknode.pro/c2eef6b727dc39db498c7b2e6659a1f5f2b8f4bd" - -# http_url = "https://sepolia.unichain.org" -http_url = "https://boldest-dark-liquid.unichain-sepolia.quiknode.pro/c2eef6b727dc39db498c7b2e6659a1f5f2b8f4bd" - -[storage] -db_path = "./data/flashstat_db" - -[tee] -# Placeholder for the Unichain sequencer TEE address -sequencer_address = "0x4ab3387810ef500bfe05a49dc53a44c222cbab3e" -attestation_enabled = false -expected_mrenclave = "0000000000000000000000000000000000000000000000000000000000000000" - -[guardian] -# Private key for the guardian wallet (use environment variables in production!) -# FLASHSTAT__GUARDIAN__PRIVATE_KEY -private_key = "5efc4b7414983a31e0cb2a45c265043a2a6bfd8b82808c5b8f59ddfff04db34c" -# Address of the Unichain SlashingManager contract -slashing_contract = "0x192136979A03A89f3A818a7a72E762E4A7D0D279" From e52d51f013f68178d9f8f23b79b192e078532ca3 Mon Sep 17 00:00:00 2001 From: intelliDean Date: Wed, 29 Apr 2026 22:51:52 +0100 Subject: [PATCH 2/2] feat: improve TEE attestation, add CI, and implement secure keystore support --- .github/workflows/ci.yml | 39 +++++++++++++++++++ README.md | 3 +- crates/flashstat-core/src/lib.rs | 59 +++++++++++++++++++++-------- crates/flashstat-core/src/wallet.rs | 13 +++---- 4 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ef4b20e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Check & Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + - name: Format Check + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + - name: Run Tests + run: cargo test --all-features diff --git a/README.md b/README.md index ecc30a6..1db6891 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,13 @@ FlashStat is built as a high-performance Rust monorepo: - **`bin/flashstat`**: The primary indexing engine. Subscribes to 200ms Flashblocks via WebSockets. - **`bin/flashstat-server`**: JSON-RPC server providing confidence metrics. - **`crates/flashstat-core`**: Core monitoring and reorg detection logic. -- **`crates/flashstat-db`**: Ultra-low latency persistence layer using RocksDB. +- **`crates/flashstat-db`**: Ultra-low latency persistence layer using redb (pure-Rust). - **`crates/flashstat-api`**: Type-safe JSON-RPC interface definitions. ## 🚀 Getting Started ### Prerequisites - Rust (Latest Stable) -- LLVM/Clang (for RocksDB) ### Configuration Edit `flashstat.toml` or set environment variables: diff --git a/crates/flashstat-core/src/lib.rs b/crates/flashstat-core/src/lib.rs index 0a62d25..abe4ae0 100644 --- a/crates/flashstat-core/src/lib.rs +++ b/crates/flashstat-core/src/lib.rs @@ -8,7 +8,7 @@ pub mod wallet; use chrono::Utc; use ethers::prelude::*; use eyre::Result; -use flashstat_db::{FlashStorage, RedbStorage}; +use flashstat_db::FlashStorage; use futures_util::StreamExt; use std::sync::Arc; use std::time::Duration; @@ -172,18 +172,27 @@ impl FlashMonitor { // Phase 5: Optional TDX Attestation Check if self.config.tee.attestation_enabled { - let quote = extract_quote_from_block(ð_block); - if let Ok(valid) = self.tee_verifier.verify_tdx_attestation( - "e.unwrap_or_default(), - self.config.tee.expected_mrenclave.as_deref(), - ) { - if valid { - confidence = 99.0; - info!("🛡️ TDX Attestation Verified for block #{}", number); - } else { - confidence = 45.0; - warn!("⚠️ TEE Signature valid but Attestation FAILED for block #{}", number); + if let Some(quote) = extract_quote_from_block(ð_block) { + match self.tee_verifier.verify_tdx_attestation( + "e, + self.config.tee.expected_mrenclave.as_deref(), + ) { + Ok(true) => { + confidence = 99.0; + info!("🛡️ TDX Attestation Verified for block #{}", number); + } + Ok(false) => { + confidence = 45.0; + warn!("⚠️ TEE Signature valid but Attestation Check FAILED for block #{}", number); + } + Err(e) => { + confidence = 70.0; + warn!("⚠️ TEE Signature valid but Attestation verification ERROR for block #{}: {:?}", number, e); + } } + } else { + confidence = 85.0; + warn!("⚠️ Attestation enabled but NO quote found in block #{}", number); } } } else { @@ -401,9 +410,29 @@ fn extract_signature_from_block(block: &Block) -> Option { } /// Helper to extract the TEE attestation quote from a block. -fn extract_quote_from_block(_block: &Block) -> Option { - // TODO: Implement actual extraction logic for Unichain (e.g. from RLP-encoded extra data) - None +/// In Unichain, the quote may be present in extra_data or a custom header. +fn extract_quote_from_block(block: &Block) -> Option { + let extra_data = &block.extra_data; + + // OP-Stack extra_data structure: [32-byte zero prefix] [65-byte signature] [optional quote] + // If the data is longer than 32 + 65, the remainder might be the quote. + if extra_data.len() > 97 { + let quote = &extra_data[97..]; + Some(Bytes::from(quote.to_vec())) + } else { + // Fallback: check if the extra_data itself is an RLP list containing the quote + let rlp = ethers::utils::rlp::Rlp::new(extra_data); + if rlp.is_list() && rlp.item_count().unwrap_or(0) >= 2 { + if let Ok(quote_item) = rlp.at(1) { + if let Ok(quote_bytes) = quote_item.as_val::>() { + if quote_bytes.len() > 128 { + return Some(Bytes::from(quote_bytes)); + } + } + } + } + None + } } async fn analyze_and_update_equivocation( diff --git a/crates/flashstat-core/src/wallet.rs b/crates/flashstat-core/src/wallet.rs index 3377794..f00d2bb 100644 --- a/crates/flashstat-core/src/wallet.rs +++ b/crates/flashstat-core/src/wallet.rs @@ -2,7 +2,6 @@ use ethers::prelude::*; use eyre::{Result, eyre}; use std::sync::Arc; use flashstat_common::GuardianConfig; -use std::str::FromStr; abigen!( SlashingManager, @@ -13,7 +12,6 @@ abigen!( ); pub struct GuardianWallet { - client: Arc, LocalWallet>>, contract: SlashingManager, LocalWallet>>, } @@ -24,17 +22,18 @@ impl GuardianWallet { let wallet = if let Some(pk) = &config.private_key { pk.parse::()?.with_chain_id(chain_id) - } else if let Some(_path) = &config.keystore_path { - // Placeholder for keystore logic - would require password prompt - return Err(eyre!("Keystore support requires interactive password. Use private_key for now.")); + } else if let Some(path) = &config.keystore_path { + let password = std::env::var("FLASHSTAT__GUARDIAN__PASSWORD") + .map_err(|_| eyre!("Keystore configured but FLASHSTAT__GUARDIAN__PASSWORD not set"))?; + LocalWallet::decrypt_keystore(path, password)?.with_chain_id(chain_id) } else { return Err(eyre!("No guardian wallet configured")); }; let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let contract = SlashingManager::new(config.slashing_contract, client.clone()); + let contract = SlashingManager::new(config.slashing_contract, client); - Ok(Self { client, contract }) + Ok(Self { contract }) } pub async fn submit_equivocation_proof(&self, proof_bytes: Vec) -> Result {