diff --git a/ethereum/Cargo.lock b/ethereum/Cargo.lock index c4397dd6..2e88eec1 100644 --- a/ethereum/Cargo.lock +++ b/ethereum/Cargo.lock @@ -561,7 +561,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -1435,6 +1435,7 @@ dependencies = [ "ethereum-types", "getset", "hex", + "serde", "sha2 0.9.9", "ssz_types", "thiserror", @@ -2637,7 +2638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3619,7 +3620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.70", diff --git a/ethereum/core/Cargo.toml b/ethereum/core/Cargo.toml index 9abb03dc..a16b67ed 100644 --- a/ethereum/core/Cargo.toml +++ b/ethereum/core/Cargo.toml @@ -11,6 +11,7 @@ anyhow = { workspace = true } bls12_381 = { workspace = true, features = ["experimental"] } hex = { workspace = true } getset = { workspace = true } +serde = { workspace = true, features = ["derive"] } sha2 = { workspace = true } thiserror = { workspace = true } tiny-keccak = { workspace = true, features = ["keccak"] } diff --git a/ethereum/core/src/crypto/sig.rs b/ethereum/core/src/crypto/sig.rs index ea075e9e..5b5c6f68 100644 --- a/ethereum/core/src/crypto/sig.rs +++ b/ethereum/core/src/crypto/sig.rs @@ -274,7 +274,7 @@ pub const SYNC_AGGREGATE_BYTES_LEN: usize = SYNC_COMMITTEE_SIZE / 8 + SIG_LEN; /// index who signed the message as bits and the actual signature. /// /// From [the Altaïr specifications](https://github.com/ethereum/consensus-specs/blob/81f3ea8322aff6b9fb15132d050f8f98b16bdba4/specs/altair/beacon-chain.md#syncaggregate). -#[derive(Debug, Clone, Getters)] +#[derive(Debug, Clone, Eq, PartialEq, Getters)] #[getset(get = "pub")] pub struct SyncAggregate { sync_committee_bits: [u8; SYNC_COMMITTEE_SIZE], diff --git a/ethereum/core/src/types/store.rs b/ethereum/core/src/types/store.rs index 417f547d..e4adc28c 100644 --- a/ethereum/core/src/types/store.rs +++ b/ethereum/core/src/types/store.rs @@ -180,7 +180,8 @@ impl LightClientStore { .sync_aggregate() .sync_committee_bits() .iter() - .sum::() + .map(|&bit| u64::from(bit)) + .sum::() < 1 { return Err(ConsensusError::InsufficientSigners); diff --git a/ethereum/core/src/types/update.rs b/ethereum/core/src/types/update.rs index e6d52a68..8f56c974 100644 --- a/ethereum/core/src/types/update.rs +++ b/ethereum/core/src/types/update.rs @@ -30,7 +30,7 @@ pub const UPDATE_BASE_BYTES_LEN: usize = LIGHT_CLIENT_HEADER_BASE_BYTES_LEN * 2 /// A data structure containing the necessary data for a light client to update its state from the Beacon chain. /// /// From [the Altaïr specifications](https://github.com/ethereum/consensus-specs/blob/81f3ea8322aff6b9fb15132d050f8f98b16bdba4/specs/altair/light-client/sync-protocol.md#lightclientupdate). -#[derive(Debug, Clone, Getters)] +#[derive(Debug, Clone, Eq, PartialEq, Getters)] #[getset(get = "pub")] pub struct Update { attested_header: LightClientHeader, diff --git a/ethereum/docs/src/run/setup_client.md b/ethereum/docs/src/run/setup_client.md index dd168524..e6d9ad91 100644 --- a/ethereum/docs/src/run/setup_client.md +++ b/ethereum/docs/src/run/setup_client.md @@ -10,5 +10,5 @@ With our deployment machine properly configured, we can run the client. ```bash git clone git@github.com:lurk-lab/zk-light-clients.git && \ cd zk-light-clients/ethereum && \ - RUST_LOG="debug" cargo +nightly-2024-05-31 run -p light-client --release --bin client -- -c -b + RUST_LOG="debug" cargo +nightly-2024-05-31 run -p light-client --release --bin client -- -c -b -p ``` \ No newline at end of file diff --git a/ethereum/docs/src/run/setup_proof_server.md b/ethereum/docs/src/run/setup_proof_server.md index 1e66b0de..99fbdb7e 100644 --- a/ethereum/docs/src/run/setup_proof_server.md +++ b/ethereum/docs/src/run/setup_proof_server.md @@ -1 +1,42 @@ -# Deploy the Proof Server \ No newline at end of file +# Deploy the Proof Server + +We have two components to deploy for the Proof Server to work as intended. The primary and the secondary server. +There is no particular order in which they should be deployed, but here we will deploy the secondary and then +the primary. + +For best results, the primary and secondary servers should be deployed to **different server instances**, so that +proof generation can happen in parallel if necessary. + +## Requirements + +Make sure to finish the [initial configuration](./configuration.md) first. + +## Environment variables + +- `RUSTFLAGS="-C target-cpu=native -C opt-level=3"`: + - `-C target-cpu=native`: This will ensure that the binary is optimized + for the CPU it is running on. This is very important + for [plonky3](https://github.com/plonky3/plonky3?tab=readme-ov-file#cpu-features) performance. + - `-C opt-level=3`: This turns on the maximum level of compiler optimizations. + - This can also be configured in `~/.cargo/config.toml` instead by adding: + ```toml + [target.'cfg(all())'] + rustflags = ["-C", "target-cpu=native", "-C", "opt-level=3"] + ``` + +Make sure to launch the proof servers with `cargo +nightly-2024-05-31`. + +> **Note** +> +> One can also set the `RUST_LOG` environment variable to `debug` to get more information +> about the execution of the server. + +## Deploy the primary server + +Finally, once the primary server is configured in the same fashion, run it: + +```bash +git clone git@github.com:lurk-lab/zk-light-clients.git && \ + cd zk-light-clients/ethereum/light-client && \ + SHARD_BATCH_SIZE=0 RUSTFLAGS="-C target-cpu=native -C opt-level=3" cargo +nightly-2024-05-31 run --release --bin server_primary -- -a --snd-addr +``` diff --git a/ethereum/ethereum-programs/artifacts/committee-change-program b/ethereum/ethereum-programs/artifacts/committee-change-program index 17b41625..af1ab096 100755 Binary files a/ethereum/ethereum-programs/artifacts/committee-change-program and b/ethereum/ethereum-programs/artifacts/committee-change-program differ diff --git a/ethereum/light-client/Cargo.toml b/ethereum/light-client/Cargo.toml index 4ec4c8f0..4d9ef727 100644 --- a/ethereum/light-client/Cargo.toml +++ b/ethereum/light-client/Cargo.toml @@ -14,6 +14,7 @@ hex = { workspace = true } log = { workspace = true } reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } # LC crates @@ -22,9 +23,6 @@ ethereum-programs = { path = "../ethereum-programs" } # Sphinx crates sphinx-sdk = { workspace = true } -[dev-dependencies] -serde_json = { workspace = true } - [[bin]] name = "client" path = "src/bin/client.rs" diff --git a/ethereum/light-client/src/bin/client.rs b/ethereum/light-client/src/bin/client.rs index 85577b03..f5935437 100644 --- a/ethereum/light-client/src/bin/client.rs +++ b/ethereum/light-client/src/bin/client.rs @@ -1,11 +1,14 @@ // Copyright (c) Yatima, Inc. // SPDX-License-Identifier: Apache-2.0 +use anyhow::Result; use clap::Parser; -use ethereum_lc_core::merkle::Merkleized; +use ethereum_lc::client::Client; +use ethereum_lc::proofs::ProvingMode; use ethereum_lc_core::types::store::LightClientStore; use ethereum_lc_core::types::utils::calc_sync_period; use log::info; +use std::sync::Arc; /// The maximum number of light client updates that can be requested. /// @@ -26,6 +29,15 @@ struct Cli { /// It is recommended to use https://www.lightclientdata.org #[arg(short, long)] beacon_node_address: String, + + /// The address of the proof server + #[arg(short, long)] + proof_server_address: String, +} + +pub struct ClientState { + client: Client, + store: LightClientStore, } #[tokio::main] @@ -33,15 +45,36 @@ async fn main() { let Cli { checkpoint_provider_address, beacon_node_address, + proof_server_address, .. } = Cli::parse(); // Initialize the logger. env_logger::init(); + let checkpoint_provider_address = Arc::new(checkpoint_provider_address); + let beacon_node_address = Arc::new(beacon_node_address); + let proof_server_address = Arc::new(proof_server_address); + + let _state = initialize_light_client( + checkpoint_provider_address, + beacon_node_address, + proof_server_address, + ) + .await; +} + +async fn initialize_light_client( + checkpoint_provider_address: Arc, + beacon_node_address: Arc, + proof_server_address: Arc, +) -> Result { // Instantiate client. - let client = - ethereum_lc::client::Client::new(&checkpoint_provider_address, &beacon_node_address); + let client = Client::new( + &checkpoint_provider_address, + &beacon_node_address, + &proof_server_address, + ); info!("Fetching latest state checkpoint and bootstrap data..."); @@ -79,15 +112,18 @@ async fn main() { .try_into() .expect("Failed to convert checkpoint bytes to Bytes32"); - let mut store = LightClientStore::initialize(trusted_block_root, &bootstrap) + let store = LightClientStore::initialize(trusted_block_root, &bootstrap) .expect("Could not initialize the store based on bootstrap data"); + let mut client_state = ClientState { client, store }; + info!("Fetching updates..."); // Fetch updates let sync_period = calc_sync_period(bootstrap.header().beacon().slot()); - let update_response = client + let update_response = client_state + .client .get_update_data(sync_period, MAX_REQUEST_LIGHT_CLIENT_UPDATES) .await .expect("Failed to fetch update data"); @@ -109,35 +145,24 @@ async fn main() { info!("Sync period changed, updating store..."); } - store + let proof = client_state + .client + .prove_committee_change(ProvingMode::STARK, &client_state.store, update.update()) + .await + .expect("Failed to prove committee change"); + + client_state + .client + .verify_committee_change(proof.clone()) + .await + .expect("Failed to prove committee change"); + + // TODO this is redundant, to simplify + client_state + .store .process_light_client_update(update.update()) - .expect("Failed to process update"); - - assert_eq!( - store - .next_sync_committee() - .clone() - .unwrap() - .hash_tree_root() - .unwrap(), - update - .update() - .next_sync_committee() - .hash_tree_root() - .unwrap() - ); - - if calc_sync_period(bootstrap.header().beacon().slot()) - != calc_sync_period(update.update().attested_header().beacon().slot()) - { - assert_eq!( - store.finalized_header().hash_tree_root().unwrap(), - update.update().finalized_header().hash_tree_root().unwrap() - ); - assert_eq!( - store.optimistic_header().hash_tree_root().unwrap(), - update.update().finalized_header().hash_tree_root().unwrap() - ) - } + .unwrap() } + + Ok(client_state) } diff --git a/ethereum/light-client/src/bin/server_primary.rs b/ethereum/light-client/src/bin/server_primary.rs index 331f974e..293619ce 100644 --- a/ethereum/light-client/src/bin/server_primary.rs +++ b/ethereum/light-client/src/bin/server_primary.rs @@ -1,5 +1,77 @@ // Copyright (c) Yatima, Inc. // SPDX-License-Identifier: Apache-2.0 -#[allow(clippy::missing_const_for_fn)] -fn main() {} +use anyhow::{Error, Result}; +use clap::Parser; +use ethereum_lc::proofs::committee_change::CommitteeChangeProver; +use ethereum_lc::proofs::Prover; +use ethereum_lc::types::network::Request; +use ethereum_lc::utils::{read_bytes, write_bytes}; +use log::{error, info}; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio::task::spawn_blocking; + +#[derive(Parser)] +struct Cli { + /// Address of this server. E.g. 127.0.0.1:1234 + #[arg(short, long)] + addr: String, + + /// Address of the secondary server. E.g. 127.0.0.1:4321 + #[arg(long)] + snd_addr: String, +} + +#[tokio::main] +async fn main() -> Result<()> { + let Cli { addr, .. } = Cli::parse(); + + env_logger::init(); + + let listener = TcpListener::bind(addr).await?; + info!("Server is running on {}", listener.local_addr()?); + + let committee_prover = Arc::new(CommitteeChangeProver::new()); + + loop { + let (mut client_stream, _) = listener.accept().await?; + info!("Received a connection"); + + let committee_prover = committee_prover.clone(); + + tokio::spawn(async move { + info!("Awaiting request"); + let request_bytes = read_bytes(&mut client_stream).await?; + info!("Request received"); + + info!("Deserializing request"); + match Request::from_bytes(&request_bytes) { + Ok(request) => match request { + Request::ProveCommitteeChange(boxed) => { + info!("Start proving"); + let proof_handle = spawn_blocking(move || { + let (proving_mode, inputs) = boxed.as_ref(); + committee_prover.prove(inputs.clone(), proving_mode.clone()) + }); + let proof = proof_handle.await??; + info!("Proof generated. Serializing"); + let proof_bytes = proof.to_bytes()?; + info!("Sending proof"); + write_bytes(&mut client_stream, &proof_bytes).await?; + info!("Proof sent"); + } + Request::VerifyCommitteeChange(proof) => { + write_bytes( + &mut client_stream, + &[u8::from(committee_prover.verify(&proof).is_ok())], + ) + .await?; + } + }, + Err(err) => error!("Failed to deserialize request object: {err}"), + } + Ok::<(), Error>(()) + }); + } +} diff --git a/ethereum/light-client/src/client/mod.rs b/ethereum/light-client/src/client/mod.rs index e310370f..4d27c282 100644 --- a/ethereum/light-client/src/client/mod.rs +++ b/ethereum/light-client/src/client/mod.rs @@ -15,19 +15,25 @@ use crate::client::beacon::BeaconClient; use crate::client::checkpoint::CheckpointClient; use crate::client::error::ClientError; +use crate::client::proof_server::ProofServerClient; +use crate::proofs::{ProofType, ProvingMode}; use crate::types::beacon::update::UpdateResponse; use crate::types::checkpoint::Checkpoint; use ethereum_lc_core::types::bootstrap::Bootstrap; +use ethereum_lc_core::types::store::LightClientStore; +use ethereum_lc_core::types::update::Update; pub(crate) mod beacon; pub(crate) mod checkpoint; pub mod error; +mod proof_server; /// The client for the light client. It is the entrypoint for any needed remote call. #[derive(Debug, Clone)] pub struct Client { beacon_client: BeaconClient, checkpoint_client: CheckpointClient, + proof_server_client: ProofServerClient, } impl Client { @@ -41,10 +47,15 @@ impl Client { /// # Returns /// /// A new `Client`. - pub fn new(checkpoint_provider_address: &str, beacon_node_address: &str) -> Self { + pub fn new( + checkpoint_provider_address: &str, + beacon_node_address: &str, + proof_server_address: &str, + ) -> Self { Self { beacon_client: BeaconClient::new(beacon_node_address), checkpoint_client: CheckpointClient::new(checkpoint_provider_address), + proof_server_client: ProofServerClient::new(proof_server_address), } } @@ -83,6 +94,20 @@ impl Client { self.checkpoint_client.get_checkpoint(slot).await } + /// `get_update_data` makes an HTTP request to the Beacon Node API to get the update data. + /// + /// # Arguments + /// + /// * `sync_period` - The sync committee period. + /// * `max` - The maximum number of updates to fetch. Maxed at 128. + /// + /// # Returns + /// + /// The update data. + /// + /// # Errors + /// + /// Returns an error if the request fails or the response is not successful or properly formatted. pub async fn get_update_data( &self, sync_period: u64, @@ -90,4 +115,49 @@ impl Client { ) -> Result { self.beacon_client.get_update_data(sync_period, max).await } + + /// `prove_committee_change` makes a request to the Proof Server API to generate the proof of a committee change. + /// + /// # Arguments + /// + /// * `proving_mode` - The proving mode, either STARK or SNARK. + /// * `store` - The light client store. + /// * `update` - The update data. + /// + /// # Returns + /// + /// The proof of the committee change. + /// + /// # Errors + /// + /// Returns an error if the request fails or the response is not successful or properly formatted. + pub async fn prove_committee_change( + &self, + proving_mode: ProvingMode, + store: &LightClientStore, + update: &Update, + ) -> Result { + self.proof_server_client + .prove_committee_change(proving_mode, store, update) + .await + } + + /// `verify_committee_change` makes a request to the Proof Server API to verify the proof of a committee change. + /// + /// # Arguments + /// + /// * `proof` - The proof of the committee change. + /// + /// # Returns + /// + /// A boolean indicating whether the proof is valid. + /// + /// # Errors + /// + /// Returns an error if the request fails or the response is not successful or properly formatted. + pub async fn verify_committee_change(&self, proof: ProofType) -> Result { + self.proof_server_client + .verify_committee_change(proof) + .await + } } diff --git a/ethereum/light-client/src/client/proof_server.rs b/ethereum/light-client/src/client/proof_server.rs new file mode 100644 index 00000000..7202b2a5 --- /dev/null +++ b/ethereum/light-client/src/client/proof_server.rs @@ -0,0 +1,147 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! # Proof Server client module +//! +//! This module contains the client to connect and query the Proof Server. It creates one-time TCP +//! connections to the Proof Server to generate and verify our proofs. + +use crate::client::error::ClientError; +use crate::proofs::committee_change::CommitteeChangeIn; +use crate::proofs::{ProofType, ProvingMode}; +use crate::types::network::Request; +use crate::utils::{read_bytes, write_bytes}; +use ethereum_lc_core::types::store::LightClientStore; +use ethereum_lc_core::types::update::Update; +use tokio::net::TcpStream; + +/// An internal client to handle communication with a Checkpoint Provider. +#[derive(Debug, Clone)] +pub(crate) struct ProofServerClient { + /// The address of the Proof Server. + address: String, +} + +impl ProofServerClient { + /// Create a new client with the given address. + /// + /// # Arguments + /// + /// * `proof_server_address` - The address of the Proof Server. + /// + /// # Returns + /// + /// A new `ProofServerClient`. + pub(crate) fn new(proof_server_address: &str) -> Self { + Self { + address: proof_server_address.to_string(), + } + } + + /// Prove a sync committee change by executing the [`LightClientStore::process_light_client_update`] + /// and proving its correct execution. + /// + /// # Arguments + /// + /// * `proving_mode` - The proving mode to use, either STARK or SNARK. + /// * `store` - The light client store. + /// * `update` - The update to process. + /// + /// # Returns + /// + /// A proof of the sync committee change. + pub(crate) async fn prove_committee_change( + &self, + proving_mode: ProvingMode, + store: &LightClientStore, + update: &Update, + ) -> Result { + let mut stream = + TcpStream::connect(&self.address) + .await + .map_err(|err| ClientError::Request { + endpoint: "ProofServer::ProveCommitteeChange".into(), + source: err.into(), + })?; + let inputs = CommitteeChangeIn::new(store.clone(), update.clone()); + let request = Request::ProveCommitteeChange(Box::new((proving_mode, inputs))); + + write_bytes( + &mut stream, + &request.to_bytes().map_err(|err| ClientError::Request { + endpoint: "ProofServer::ProveCommitteeChange".into(), + source: err.into(), + })?, + ) + .await + .map_err(|err| ClientError::Request { + endpoint: "prover".into(), + source: err.into(), + })?; + + let res = read_bytes(&mut stream) + .await + .map_err(|err| ClientError::Response { + endpoint: "ProofServer::ProveCommitteeChange".into(), + source: err.into(), + })?; + + ProofType::from_bytes(&res).map_err(|err| ClientError::Response { + endpoint: "ProofServer::ProveCommitteeChange".into(), + source: err.into(), + }) + } + + /// Verify a proof of a sync committee change. + /// + /// # Arguments + /// + /// * `proof` - The proof to verify. + /// + /// # Returns + /// + /// A boolean indicating whether the proof is valid. + pub(crate) async fn verify_committee_change( + &self, + proof: ProofType, + ) -> Result { + let mut stream = + TcpStream::connect(&self.address) + .await + .map_err(|err| ClientError::Request { + endpoint: "ProofServer::VerifyCommitteeChange".into(), + source: err.into(), + })?; + + let request = Request::VerifyCommitteeChange(proof); + + write_bytes( + &mut stream, + &request.to_bytes().map_err(|err| ClientError::Request { + endpoint: "ProofServer::VerifyCommitteeChange".into(), + source: err.into(), + })?, + ) + .await + .map_err(|err| ClientError::Request { + endpoint: "prover".into(), + source: err.into(), + })?; + + let res = read_bytes(&mut stream) + .await + .map_err(|err| ClientError::Response { + endpoint: "ProofServer::VerifyCommitteeChange".into(), + source: err.into(), + })?; + + if res.len() != 1 { + return Err(ClientError::Response { + endpoint: "ProofServer::VerifyCommitteeChange".into(), + source: "Invalid response length".into(), + }); + } + + Ok(res[0] == 1) + } +} diff --git a/ethereum/light-client/src/lib.rs b/ethereum/light-client/src/lib.rs index 41137c91..d19f8865 100644 --- a/ethereum/light-client/src/lib.rs +++ b/ethereum/light-client/src/lib.rs @@ -32,3 +32,4 @@ pub mod client; pub mod proofs; pub mod types; +pub mod utils; diff --git a/ethereum/light-client/src/proofs/committee_change.rs b/ethereum/light-client/src/proofs/committee_change.rs index bfabac83..3318c4e7 100644 --- a/ethereum/light-client/src/proofs/committee_change.rs +++ b/ethereum/light-client/src/proofs/committee_change.rs @@ -10,8 +10,11 @@ use crate::proofs::error::ProverError; use crate::proofs::{ProofType, Prover, ProvingMode}; use anyhow::Result; use ethereum_lc_core::crypto::hash::HashValue; +use ethereum_lc_core::deserialization_error; +use ethereum_lc_core::types::error::TypesError; use ethereum_lc_core::types::store::LightClientStore; use ethereum_lc_core::types::update::Update; +use ethereum_lc_core::types::utils::{extract_u32, OFFSET_BYTE_LENGTH}; use ethereum_programs::COMMITTEE_CHANGE_PROGRAM; use sphinx_sdk::{ProverClient, SphinxProvingKey, SphinxStdin, SphinxVerifyingKey}; @@ -42,7 +45,7 @@ impl CommitteeChangeProver { } /// The input for the sync committee change proof. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct CommitteeChangeIn { store: LightClientStore, update: Update, @@ -62,6 +65,57 @@ impl CommitteeChangeIn { pub const fn new(store: LightClientStore, update: Update) -> Self { Self { store, update } } + + /// Serialize the `CommitteeChangeIn` struct to SSZ bytes. + /// + /// # Returns + /// + /// A `Vec` containing the SSZ serialized `CommitteeChangeIn` struct. + pub fn to_ssz_bytes(&self) -> Result, TypesError> { + let mut bytes = vec![]; + + let store_offset: u32 = (OFFSET_BYTE_LENGTH * 2) as u32; + let store_bytes = self.store.to_ssz_bytes()?; + bytes.extend_from_slice(&store_offset.to_le_bytes()); + + let update_offset = store_offset + store_bytes.len() as u32; + let update_bytes = self.update.to_ssz_bytes()?; + bytes.extend_from_slice(&update_offset.to_le_bytes()); + + bytes.extend_from_slice(&store_bytes); + bytes.extend_from_slice(&update_bytes); + + Ok(bytes) + } + + /// Deserialize a `CommitteeChangeIn` struct from SSZ bytes. + /// + /// # Arguments + /// + /// * `bytes` - The SSZ encoded bytes. + /// + /// # Returns + /// + /// A `Result` containing either the deserialized `CommitteeChangeIn` struct or a `TypesError`. + pub fn from_ssz_bytes(bytes: &[u8]) -> Result { + let cursor = 0; + let (cursor, store_offset) = extract_u32("CommmitteeChangeIn", bytes, cursor)?; + let (cursor, update_offset) = extract_u32("CommmitteeChangeIn", bytes, cursor)?; + + // Deserialize the Light Client store + if cursor != store_offset as usize { + return Err(deserialization_error!( + "CommmitteeChangeIn", + "Invalid offset for store" + )); + } + let store = LightClientStore::from_ssz_bytes(&bytes[cursor..update_offset as usize])?; + + // Deserialize the Update + let update = Update::from_ssz_bytes(&bytes[update_offset as usize..])?; + + Ok(Self { store, update }) + } } /// The output for the sync committee change proof. @@ -115,8 +169,6 @@ impl Prover for CommitteeChangeProver { } fn prove(&self, inputs: Self::StdIn, mode: ProvingMode) -> Result { - sphinx_sdk::utils::setup_logger(); - let stdin = self.generate_sphinx_stdin(inputs)?; match mode { diff --git a/ethereum/light-client/src/proofs/mod.rs b/ethereum/light-client/src/proofs/mod.rs index a0f1bdbf..23b9025e 100644 --- a/ethereum/light-client/src/proofs/mod.rs +++ b/ethereum/light-client/src/proofs/mod.rs @@ -20,24 +20,135 @@ //! sub-module. use anyhow::{anyhow, Result}; -use sphinx_sdk::{SphinxPlonkBn254Proof, SphinxProof, SphinxStdin}; +use serde::{Deserialize, Serialize}; +use sphinx_sdk::{SphinxPlonkBn254Proof, SphinxProof, SphinxProofWithPublicValues, SphinxStdin}; pub mod committee_change; mod error; /// The proving mode for the prover. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum ProvingMode { STARK, SNARK, } +impl ProvingMode { + /// Returns a boolean indicating if the proving mode is STARK. + /// + /// # Returns + /// + /// A boolean indicating if the proving mode is STARK. + pub const fn is_stark(&self) -> bool { + matches!(self, ProvingMode::STARK) + } + + /// Returns a serialized representation of the enum. + /// + /// # Returns + /// + /// A u8 representing the enum. + pub const fn to_bytes(&self) -> u8 { + match self { + ProvingMode::STARK => 0, + ProvingMode::SNARK => 1, + } + } + + /// Returns a ProvingMode from a serialized representation. + /// + /// # Arguments + /// + /// * `bytes` - The serialized representation of the enum. + /// + /// # Returns + /// + /// The ProvingMode. + pub fn from_bytes(bytes: &[u8]) -> Result { + match bytes[0] { + 0 => Ok(ProvingMode::STARK), + 1 => Ok(ProvingMode::SNARK), + _ => Err(anyhow!("Invalid proving mode")), + } + } +} + /// The proof type generated by the prover. +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum ProofType { STARK(SphinxProof), SNARK(SphinxPlonkBn254Proof), } +impl ProofType { + /// Returns a boolean indicating if the proof type is STARK. + /// + /// # Returns + /// + /// A boolean indicating if the proof type is STARK. + pub const fn is_stark(&self) -> bool { + matches!(self, ProofType::STARK(_)) + } + + /// Serialize the proof type to bytes. + /// + /// # Returns + /// + /// The serialized proof type. + pub fn to_bytes(&self) -> Result> { + let mut bytes = vec![]; + + match self { + ProofType::STARK(proof) => { + bytes.extend_from_slice(&[0]); + + bytes.extend_from_slice( + &serde_json::to_vec(&(proof.clone() as SphinxProofWithPublicValues<_>)) + .map_err(|err| anyhow!(err))?, + ); + + Ok(bytes) + } + ProofType::SNARK(proof) => { + bytes.extend_from_slice(&[1]); + bytes.extend_from_slice( + &serde_json::to_vec(&(proof.clone() as SphinxProofWithPublicValues<_>)) + .map_err(|err| anyhow!(err))?, + ); + + Ok(bytes) + } + } + } + + /// Deserialize the proof type from bytes. + /// + /// # Arguments + /// + /// * `bytes` - The serialized proof type. + /// + /// # Returns + /// + /// The proof type. + pub fn from_bytes(bytes: &[u8]) -> Result { + match bytes[0] { + 0 => { + let proof = serde_json::from_slice::(&bytes[1..]) + .map_err(|err| anyhow!(err))?; + + Ok(ProofType::STARK(proof as SphinxProof)) + } + 1 => { + let proof = serde_json::from_slice::(&bytes[1..]) + .map_err(|err| anyhow!(err))?; + + Ok(ProofType::SNARK(proof)) + } + _ => Err(anyhow!("Invalid proof type")), + } + } +} + impl From for String { fn from(mode: ProvingMode) -> String { match mode { diff --git a/ethereum/light-client/src/types/mod.rs b/ethereum/light-client/src/types/mod.rs index 556843d7..c63ca171 100644 --- a/ethereum/light-client/src/types/mod.rs +++ b/ethereum/light-client/src/types/mod.rs @@ -14,8 +14,10 @@ //! //! - `beacon`: This sub-module contains the data structures used by the Beacon Node. //! - `checkpoint`: This sub-module contains the data structures used by the Checkpoint service. +//! - `network`: This sub-module contains the data structures that serves as payload for the Proof Server. //! //! For more detailed information, users should refer to the specific documentation for each sub-module. pub mod beacon; pub mod checkpoint; +pub mod network; diff --git a/ethereum/light-client/src/types/network.rs b/ethereum/light-client/src/types/network.rs new file mode 100644 index 00000000..984b10e6 --- /dev/null +++ b/ethereum/light-client/src/types/network.rs @@ -0,0 +1,68 @@ +use crate::proofs::committee_change::CommitteeChangeIn; +use crate::proofs::{ProofType, ProvingMode}; +use anyhow::{anyhow, Error}; + +#[derive(Debug, Clone)] +pub enum Request { + /// Request to prove the validity of a sync committee change. + ProveCommitteeChange(Box<(ProvingMode, CommitteeChangeIn)>), + /// Request to verify the validity of a proof for a sync committee change. + VerifyCommitteeChange(ProofType), +} + +impl Request { + /// Returns a serialized representation of the enum. + /// + /// # Returns + /// + /// A Vec representing the enum. + pub fn to_bytes(&self) -> Result, Error> { + match self { + Request::ProveCommitteeChange(boxed) => { + let mut bytes = vec![0]; + + let (proving_mode, committee_change_in) = boxed.as_ref(); + + bytes.push(proving_mode.to_bytes()); + bytes.extend_from_slice( + &committee_change_in.to_ssz_bytes().map_err(|e| anyhow!(e))?, + ); + Ok(bytes) + } + Request::VerifyCommitteeChange(proof_type) => { + let mut bytes = vec![1]; + bytes.extend_from_slice(&proof_type.to_bytes().map_err(|e| anyhow!(e))?); + Ok(bytes) + } + } + } + + /// Returns a Request from a serialized representation. + /// + /// # Arguments + /// + /// * `bytes` - The serialized representation of the enum. + /// + /// # Returns + /// + /// The Request. + pub fn from_bytes(bytes: &[u8]) -> Result { + match bytes[0] { + 0 => { + let proving_mode = ProvingMode::from_bytes(&bytes[1..2])?; + + let committee_change_in = CommitteeChangeIn::from_ssz_bytes(&bytes[2..])?; + + Ok(Request::ProveCommitteeChange(Box::new(( + proving_mode, + committee_change_in, + )))) + } + 1 => { + let proof_type = ProofType::from_bytes(&bytes[1..])?; + Ok(Request::VerifyCommitteeChange(proof_type)) + } + _ => Err(anyhow!("Invalid request")), + } + } +} diff --git a/ethereum/light-client/src/utils.rs b/ethereum/light-client/src/utils.rs new file mode 100644 index 00000000..dad05b51 --- /dev/null +++ b/ethereum/light-client/src/utils.rs @@ -0,0 +1,29 @@ +use anyhow::Result; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; + +/// Auxiliary function to write bytes on a stream. Before actually writing the +/// bytes, it writes the number of bytes to be written as a big-endian `u32`. +/// +/// # Errors +/// This function errors if the number of bytes can't fit in a `u32` +pub async fn write_bytes(stream: &mut TcpStream, bytes: &[u8]) -> Result<()> { + stream.write_u32(u32::try_from(bytes.len())?).await?; + stream.write_all(bytes).await?; + stream.flush().await?; + Ok(()) +} + +/// Auxiliary function to read bytes on a stream. Before actually reading the +/// bytes, it reads the number of bytes to be read as a big-endian `u32`. +/// +/// # Important +/// The number of bytes read must fit in a `u32` thus the amount of data read in +/// a single call to this function is 4 GB. +pub async fn read_bytes(stream: &mut TcpStream) -> Result> { + let size = stream.read_u32().await?; + let mut bytes = vec![0; size as usize]; + let num_read = stream.read_exact(&mut bytes).await?; + assert_eq!(num_read, bytes.len()); + Ok(bytes) +} diff --git a/ethereum/programs/committee-change/Cargo.lock b/ethereum/programs/committee-change/Cargo.lock index 5eff36aa..906088b0 100644 --- a/ethereum/programs/committee-change/Cargo.lock +++ b/ethereum/programs/committee-change/Cargo.lock @@ -203,6 +203,7 @@ dependencies = [ "bls12_381", "getset", "hex", + "serde", "sha2 0.9.9", "thiserror", "tiny-keccak", diff --git a/ethereum/programs/committee-change/src/main.rs b/ethereum/programs/committee-change/src/main.rs index 6c854074..5dc3e851 100644 --- a/ethereum/programs/committee-change/src/main.rs +++ b/ethereum/programs/committee-change/src/main.rs @@ -80,7 +80,13 @@ pub fn main() { sphinx_zkvm::precompiles::unconstrained! { println!("cycle-tracker-end: hash_new_sync_committee"); } + let next_sync_committee_hash = keccak256_hash(&store.next_sync_committee().as_ref().expect("Store should have a next sync committee after processing update").to_ssz_bytes()) + .expect("LightClientStore::current_sync_committee: could not hash committee after processing update"); + sphinx_zkvm::precompiles::unconstrained! { + println!("cycle-tracker-end: hash_new_sync_committee"); + } // Commit the two hashes sphinx_zkvm::io::commit(&old_sync_committee_hash.hash()); sphinx_zkvm::io::commit(&updated_sync_committee_hash.hash()); + sphinx_zkvm::io::commit(&next_sync_committee_hash.hash()); } diff --git a/ethereum/programs/inclusion/Cargo.lock b/ethereum/programs/inclusion/Cargo.lock index e3740bf0..3af8f118 100644 --- a/ethereum/programs/inclusion/Cargo.lock +++ b/ethereum/programs/inclusion/Cargo.lock @@ -195,6 +195,7 @@ dependencies = [ "bls12_381", "getset", "hex", + "serde", "sha2 0.9.9", "thiserror", "tiny-keccak",