diff --git a/Cargo.lock b/Cargo.lock index 1df4e16e82..f5b507e713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2676,6 +2676,7 @@ dependencies = [ "hex", "num-bigint", "rand 0.8.5", + "thiserror 1.0.69", "zkfhe-greco", ] @@ -3150,6 +3151,7 @@ dependencies = [ "e3-utils", "fhe", "fhe-traits", + "hex", "rand 0.8.5", "rand_chacha 0.3.1", "tokio", @@ -3237,6 +3239,8 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "serde", + "tokio", + "tracing", ] [[package]] diff --git a/crates/aggregator/src/threshold_plaintext_aggregator.rs b/crates/aggregator/src/threshold_plaintext_aggregator.rs index 9beb053e77..092b438b84 100644 --- a/crates/aggregator/src/threshold_plaintext_aggregator.rs +++ b/crates/aggregator/src/threshold_plaintext_aggregator.rs @@ -22,7 +22,7 @@ use e3_trbfv::{ TrBFVConfig, TrBFVRequest, }; use e3_utils::utility_types::ArcBytes; -use tracing::{error, info, trace}; +use tracing::{debug, error, info, trace}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Collecting { @@ -247,6 +247,12 @@ impl Handler for ThresholdPlaintextAggregator { type Result = ResponseActFuture>; fn handle(&mut self, event: DecryptionshareCreated, _: &mut Self::Context) -> Self::Result { + let Some(ThresholdPlaintextAggregatorState::Collecting(Collecting { .. })) = + self.state.get() + else { + debug!(state=?self.state, "Aggregator has been closed for collecting so ignoring this event."); + return Box::pin(fut::ready(Ok(()))); + }; info!(event=?event, "Processing DecryptionShareCreated..."); let address = event.node.clone(); let party_id = event.party_id; diff --git a/crates/bfv-helpers/Cargo.toml b/crates/bfv-helpers/Cargo.toml index ff8c5db738..ca0e40a0df 100644 --- a/crates/bfv-helpers/Cargo.toml +++ b/crates/bfv-helpers/Cargo.toml @@ -15,8 +15,9 @@ rand.workspace = true anyhow.workspace = true fhe-util = { git = "https://github.com/gnosisguild/fhe.rs" } fhe-math = { git = "https://github.com/gnosisguild/fhe.rs" } -greco = { package = "zkfhe-greco", git = "https://github.com/gnosisguild/zkfhe-generator"} +greco = { package = "zkfhe-greco", git = "https://github.com/gnosisguild/zkfhe-generator" } num-bigint = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] hex.workspace = true diff --git a/crates/bfv-helpers/src/client.rs b/crates/bfv-helpers/src/client.rs index 26701211d5..1aede0675b 100644 --- a/crates/bfv-helpers/src/client.rs +++ b/crates/bfv-helpers/src/client.rs @@ -35,12 +35,12 @@ pub fn bfv_encrypt( public_key: Vec, degree: usize, plaintext_modulus: u64, - moduli: [u64; 1], + moduli: &[u64], ) -> Result> where Plaintext: for<'a> FheEncoder<&'a T, Error = FheError>, { - let params = build_bfv_params_arc(degree, plaintext_modulus, &moduli, None); + let params = build_bfv_params_arc(degree, plaintext_modulus, moduli, None); let pk = PublicKey::from_bytes(&public_key, ¶ms) .map_err(|e| anyhow!("Error deserializing public key:{e}"))?; @@ -86,7 +86,7 @@ pub fn bfv_verifiable_encrypt( public_key: Vec, degree: usize, plaintext_modulus: u64, - moduli: [u64; 1], + moduli: Vec, ) -> Result where Plaintext: for<'a> FheEncoder<&'a T, Error = FheError>, @@ -146,7 +146,7 @@ mod tests { let num = [1u64]; let encrypted_data = - bfv_encrypt(num, pk.to_bytes(), degree, plaintext_modulus, moduli).unwrap(); + bfv_encrypt(num, pk.to_bytes(), degree, plaintext_modulus, &moduli).unwrap(); let ct = Ciphertext::from_bytes(&encrypted_data, ¶ms).unwrap(); let pt = sk.try_decrypt(&ct).unwrap(); @@ -175,7 +175,7 @@ mod tests { pk.to_bytes(), degree, plaintext_modulus, - moduli, + &moduli, ) .unwrap(); @@ -202,8 +202,14 @@ mod tests { let pk = PublicKey::new(&sk, &mut rng); let num = [1u64]; - let encrypted_data = - bfv_verifiable_encrypt(num, pk.to_bytes(), degree, plaintext_modulus, moduli).unwrap(); + let encrypted_data = bfv_verifiable_encrypt( + num, + pk.to_bytes(), + degree, + plaintext_modulus, + moduli.to_vec(), + ) + .unwrap(); let ct = Ciphertext::from_bytes(&encrypted_data.encrypted_data, ¶ms).unwrap(); let pt = sk.try_decrypt(&ct).unwrap(); @@ -232,7 +238,7 @@ mod tests { pk.to_bytes(), degree, plaintext_modulus, - moduli, + moduli.to_vec(), ) .unwrap(); diff --git a/crates/bfv-helpers/src/lib.rs b/crates/bfv-helpers/src/lib.rs index ff85e72f8a..2727135442 100644 --- a/crates/bfv-helpers/src/lib.rs +++ b/crates/bfv-helpers/src/lib.rs @@ -9,8 +9,22 @@ mod util; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::U256; -use fhe::bfv::{BfvParameters, BfvParametersBuilder}; +use fhe::bfv::{BfvParameters, BfvParametersBuilder, Encoding, Plaintext}; +use fhe_traits::FheDecoder; use std::sync::Arc; +use thiserror::Error as ThisError; + +#[derive(ThisError, Debug)] +pub enum Error { + #[error("Plaintext decoding failed")] + PlaintextDecodeFailed, + // TODO: add errors from client.rs + #[error("Input was not encoded correctly")] + BadEncoding, +} + +/// Result that returns a type T or a BfvHelpersError +type Result = std::result::Result; /// Predefined BFV parameters for common use cases pub mod params { @@ -337,6 +351,35 @@ pub fn decode_bfv_params_arc(bytes: &[u8]) -> Arc { Arc::new(decode_bfv_params(bytes)) } +/// Decode Plaintext to a Vec +pub fn decode_plaintext_to_vec_u64(value: &Plaintext) -> Result> { + let decoded = Vec::::try_decode(&value, Encoding::poly()) + .map_err(|_| Error::PlaintextDecodeFailed)?; + + Ok(decoded) +} + +/// Convert from a Vec to Vec +pub fn encode_vec_u64_to_bytes(value: &[u64]) -> Vec { + let mut bytes = Vec::new(); + for num in &value.to_vec() { + bytes.extend_from_slice(&num.to_le_bytes()); + } + bytes +} + +/// Decode bytes to Vec +pub fn decode_bytes_to_vec_u64(bytes: &[u8]) -> Result> { + if bytes.len() % 8 != 0 { + return Err(Error::BadEncoding); + } + + Ok(bytes + .chunks_exact(8) + .map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap())) + .collect()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index c37fadebd7..f0ce77d13d 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -61,9 +61,9 @@ pub struct Cli { #[arg(long = "otel", global = true)] pub otel: Option, - // TODO: expose this as a feature once we have this hooked up to the bin integration test - #[arg(long, hide = true)] - pub experimental_trbfv: Option, + /// Enable the experimental TrBFV threshold feature. + #[arg(long, global = true)] + pub experimental_trbfv: bool, } impl Cli { @@ -158,7 +158,9 @@ impl Cli { } match self.command { - Commands::Start { peers } => start::execute(config, peers).await?, + Commands::Start { peers } => { + start::execute(config, peers, self.experimental_trbfv).await? + } Commands::Init { .. } => { bail!("Cannot run `enclave init` when a configuration exists."); } @@ -180,6 +182,7 @@ impl Cli { self.verbose, self.config, self.otel.clone().map(Into::into), + self.experimental_trbfv, ) .await? } diff --git a/crates/cli/src/nodes.rs b/crates/cli/src/nodes.rs index bcd4f0866b..15a2075dc1 100644 --- a/crates/cli/src/nodes.rs +++ b/crates/cli/src/nodes.rs @@ -78,15 +78,33 @@ pub async fn execute( verbose: u8, config_string: Option, otel: Option, + experimental_trbfv: bool, ) -> Result<()> { match command { NodeCommands::Up { detach, exclude } => { - nodes_up::execute(config, detach, exclude, verbose, config_string, otel).await? + nodes_up::execute( + config, + detach, + exclude, + verbose, + config_string, + otel, + experimental_trbfv, + ) + .await? } NodeCommands::Down => nodes_down::execute().await?, NodeCommands::Ps => nodes_ps::execute().await?, NodeCommands::Daemon { exclude } => { - nodes_daemon::execute(config, exclude, verbose, config_string, otel).await? + nodes_daemon::execute( + config, + exclude, + verbose, + config_string, + otel, + experimental_trbfv, + ) + .await? } NodeCommands::Start { id } => nodes_start::execute(&id).await?, NodeCommands::Status { id } => nodes_status::execute(&id).await?, diff --git a/crates/cli/src/nodes_daemon.rs b/crates/cli/src/nodes_daemon.rs index 2909c46456..cbdb44c107 100644 --- a/crates/cli/src/nodes_daemon.rs +++ b/crates/cli/src/nodes_daemon.rs @@ -14,6 +14,15 @@ pub async fn execute( verbose: u8, config_string: Option, otel: Option, + experimental_trbfv: bool, ) -> Result<()> { - daemon::execute(config, exclude, verbose, config_string, otel).await + daemon::execute( + config, + exclude, + verbose, + config_string, + otel, + experimental_trbfv, + ) + .await } diff --git a/crates/cli/src/nodes_up.rs b/crates/cli/src/nodes_up.rs index e894371e9c..198381f1bc 100644 --- a/crates/cli/src/nodes_up.rs +++ b/crates/cli/src/nodes_up.rs @@ -15,6 +15,16 @@ pub async fn execute( verbose: u8, config_string: Option, otel: Option, + experimental_trbfv: bool, ) -> Result<()> { - up::execute(config, detach, exclude, verbose, config_string, otel).await + up::execute( + config, + detach, + exclude, + verbose, + config_string, + otel, + experimental_trbfv, + ) + .await } diff --git a/crates/cli/src/start.rs b/crates/cli/src/start.rs index 91ca8c614e..df3897e6f3 100644 --- a/crates/cli/src/start.rs +++ b/crates/cli/src/start.rs @@ -11,7 +11,11 @@ use e3_entrypoint::helpers::listen_for_shutdown; use tracing::{info, instrument}; #[instrument(skip_all)] -pub async fn execute(mut config: AppConfig, peers: Vec) -> Result<()> { +pub async fn execute( + mut config: AppConfig, + peers: Vec, + experimental_trbfv: bool, +) -> Result<()> { owo(); let Some(address) = config.address() else { @@ -31,12 +35,15 @@ pub async fn execute(mut config: AppConfig, peers: Vec) -> Result<()> { &config, pubkey_write_path, plaintext_write_path, + experimental_trbfv, ) .await? } // Launch in ciphernode configuration - NodeRole::Ciphernode => e3_entrypoint::start::start::execute(&config, address).await?, + NodeRole::Ciphernode => { + e3_entrypoint::start::start::execute(&config, address, experimental_trbfv).await? + } }; info!( diff --git a/crates/entrypoint/src/nodes/daemon.rs b/crates/entrypoint/src/nodes/daemon.rs index 7359b1f511..7d528ae7e4 100644 --- a/crates/entrypoint/src/nodes/daemon.rs +++ b/crates/entrypoint/src/nodes/daemon.rs @@ -53,6 +53,7 @@ impl LaunchCommand { verbose: u8, maybe_config_string: &Option, maybe_otel: &Option, + experimental_trbfv: bool, ) -> Result { let enclave_bin = env::current_exe()?.display().to_string(); let mut args = vec![]; @@ -80,6 +81,10 @@ impl LaunchCommand { args.push(peer.to_string()); } + if experimental_trbfv { + args.push("--experimental-trbfv".to_string()); + } + Ok((enclave_bin, args)) } } @@ -91,6 +96,7 @@ fn extract_commands( verbose: u8, maybe_config_string: Option, maybe_otel: Option, + experimental_trbfv: bool, ) -> Result { let mut exclude_list = exclude.clone(); @@ -111,7 +117,12 @@ fn extract_commands( let mut cmds = HashMap::new(); for item in filtered.iter() { - let params = item.to_params(verbose, &maybe_config_string, &maybe_otel)?; + let params = item.to_params( + verbose, + &maybe_config_string, + &maybe_otel, + experimental_trbfv, + )?; cmds.insert(item.name.clone(), params); } @@ -125,6 +136,7 @@ pub async fn execute( verbose: u8, maybe_config_string: Option, maybe_otel: Option, + experimental_trbfv: bool, ) -> Result<()> { let command_map = extract_commands( config.nodes(), @@ -133,6 +145,7 @@ pub async fn execute( verbose, maybe_config_string, maybe_otel, + experimental_trbfv, )?; let process_manager = Arc::new(Mutex::new(ProcessManager::from(command_map))); diff --git a/crates/entrypoint/src/nodes/up.rs b/crates/entrypoint/src/nodes/up.rs index 8344afce91..062e46d371 100644 --- a/crates/entrypoint/src/nodes/up.rs +++ b/crates/entrypoint/src/nodes/up.rs @@ -19,6 +19,7 @@ pub async fn execute( verbose: u8, maybe_config_string: Option, maybe_otel: Option, + experimental_trbfv: bool, ) -> Result<()> { if client::is_ready().await? { bail!("Swarm is already running!"); @@ -30,7 +31,15 @@ pub async fn execute( } // run the swarm_daemon process locally forwarding args - daemon::execute(config, exclude, verbose, maybe_config_string, maybe_otel).await?; + daemon::execute( + config, + exclude, + verbose, + maybe_config_string, + maybe_otel, + experimental_trbfv, + ) + .await?; Ok(()) } diff --git a/crates/entrypoint/src/start/aggregator_start.rs b/crates/entrypoint/src/start/aggregator_start.rs index d0cfd37d1e..8260ddf199 100644 --- a/crates/entrypoint/src/start/aggregator_start.rs +++ b/crates/entrypoint/src/start/aggregator_start.rs @@ -27,6 +27,7 @@ pub async fn execute( config: &AppConfig, pubkey_write_path: Option, plaintext_write_path: Option, + experimental_trbfv: bool, ) -> Result<(Addr>, JoinHandle>, String)> { let bus = get_enclave_event_bus(); let rng = Arc::new(Mutex::new(ChaCha20Rng::from_rng(OsRng)?)); @@ -34,7 +35,7 @@ pub async fn execute( let repositories = store.repositories(); let cipher = Arc::new(Cipher::from_file(config.key_file()).await?); - CiphernodeBuilder::new(rng.clone(), cipher.clone()) + let mut builder = CiphernodeBuilder::new(rng.clone(), cipher.clone()) .with_source_bus(&bus) .with_datastore(store) .with_chains(&config.chains()) @@ -42,17 +43,21 @@ pub async fn execute( .with_contract_enclave_full() .with_contract_bonding_registry() .with_contract_ciphernode_registry() - .with_plaintext_aggregation() - .with_pubkey_aggregation() - .build() - .await?; + .with_pubkey_aggregation(); - let (_, join_handle, peer_id) = NetEventTranslator::setup_with_interface( + if experimental_trbfv { + builder = builder.with_threshold_plaintext_aggregation(); + } else { + builder = builder.with_plaintext_aggregation() + } + builder.build().await?; + let (_, _, join_handle, peer_id) = NetEventTranslator::setup_with_interface( bus.clone(), config.peers(), &cipher, config.quic_port(), repositories.libp2p_keypair(), + experimental_trbfv, ) .await?; diff --git a/crates/entrypoint/src/start/start.rs b/crates/entrypoint/src/start/start.rs index 1fa874a5d6..6ad6930cb6 100644 --- a/crates/entrypoint/src/start/start.rs +++ b/crates/entrypoint/src/start/start.rs @@ -26,6 +26,7 @@ use crate::helpers::datastore::setup_datastore; pub async fn execute( config: &AppConfig, address: Address, + experimental_trbfv: bool, ) -> Result<(Addr>, JoinHandle>, String)> { let rng = Arc::new(Mutex::new(rand_chacha::ChaCha20Rng::from_rng(OsRng)?)); @@ -34,25 +35,29 @@ pub async fn execute( let store = setup_datastore(&config, &bus)?; let repositories = store.repositories(); - CiphernodeBuilder::new(rng.clone(), cipher.clone()) + let mut builder = CiphernodeBuilder::new(rng.clone(), cipher.clone()) .with_address(&address.to_string()) - .with_keyshare() .with_source_bus(&bus) .with_datastore(store) .with_sortition_score() .with_chains(&config.chains()) .with_contract_enclave_reader() .with_contract_bonding_registry() - .with_contract_ciphernode_registry() - .build() - .await?; + .with_contract_ciphernode_registry(); - let (_, join_handle, peer_id) = NetEventTranslator::setup_with_interface( + if experimental_trbfv { + builder = builder.with_trbfv(); + } else { + builder = builder.with_keyshare(); + } + builder.build().await?; + let (_, _, join_handle, peer_id) = NetEventTranslator::setup_with_interface( bus.clone(), config.peers(), &cipher, config.quic_port(), repositories.libp2p_keypair(), + experimental_trbfv, ) .await?; diff --git a/crates/events/src/enclave_event/publish_document/mod.rs b/crates/events/src/enclave_event/publish_document/mod.rs index 336fdcdce9..91b476a885 100644 --- a/crates/events/src/enclave_event/publish_document/mod.rs +++ b/crates/events/src/enclave_event/publish_document/mod.rs @@ -9,14 +9,16 @@ mod filter; use std::fmt::{self, Display}; use actix::Message; -use chrono::{serde::ts_seconds, DateTime, Utc}; +use chrono::{serde::ts_seconds, DateTime, Duration, Utc}; use e3_utils::ArcBytes; use filter::Filter; use serde::{Deserialize, Serialize}; +use tracing::warn; use crate::E3id; pub type PartyId = u64; +const DEFAULT_KADEMLIA_EXPIRY_DAYS: i64 = 30; /// Diambiguates the kind of document we are looking for #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -44,8 +46,10 @@ impl DocumentMeta { e3_id: E3id, kind: DocumentKind, filter: Vec>, - expires_at: DateTime, + expires_at: Option>, ) -> DocumentMeta { + let expires_at = + expires_at.unwrap_or_else(|| Utc::now() + Duration::days(DEFAULT_KADEMLIA_EXPIRY_DAYS)); Self { e3_id, expires_at, @@ -76,9 +80,16 @@ pub struct PublishDocumentRequested { pub value: ArcBytes, } +impl PublishDocumentRequested { + pub fn new(meta: DocumentMeta, value: ArcBytes) -> Self { + warn!("Publishing document that is {}", value.size()); + Self { meta, value } + } +} + impl Display for PublishDocumentRequested { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.meta) // XXX:: apply ArcBytes and rely on debug once trbfv is merged + write!(f, "{:?}", self) } } @@ -101,7 +112,7 @@ mod tests { E3id::new("1", 1), DocumentKind::TrBFV, vec![Filter::Range(Some(100), Some(200)), Filter::Item(77)], - Utc::now(), + Some(Utc::now()), ); assert_eq!(meta.matches(&21), false); assert_eq!(meta.matches(&77), true); @@ -111,7 +122,12 @@ mod tests { } #[test] fn test_meta_no_filters() { - let meta = DocumentMeta::new(E3id::new("1", 1), DocumentKind::TrBFV, vec![], Utc::now()); + let meta = DocumentMeta::new( + E3id::new("1", 1), + DocumentKind::TrBFV, + vec![], + Some(Utc::now()), + ); assert_eq!(meta.matches(&21), true); assert_eq!(meta.matches(&77), true); assert_eq!(meta.matches(&90), true); diff --git a/crates/events/src/enclave_event/threshold_share_created.rs b/crates/events/src/enclave_event/threshold_share_created.rs index 4514ae2f1c..d07fbd84b1 100644 --- a/crates/events/src/enclave_event/threshold_share_created.rs +++ b/crates/events/src/enclave_event/threshold_share_created.rs @@ -38,6 +38,7 @@ pub struct ThresholdShare { pub struct ThresholdShareCreated { pub e3_id: E3id, pub share: Arc, + pub external: bool, } impl Display for ThresholdShareCreated { diff --git a/crates/evm-helpers/src/contracts.rs b/crates/evm-helpers/src/contracts.rs index db56937b86..db4b576879 100644 --- a/crates/evm-helpers/src/contracts.rs +++ b/crates/evm-helpers/src/contracts.rs @@ -136,7 +136,7 @@ pub trait EnclaveWrite { e3_params: Bytes, compute_provider_params: Bytes, custom_params: Bytes, - ) -> Result; + ) -> Result<(TransactionReceipt, U256)>; /// Activate an E3 with a public key async fn activate(&self, e3_id: U256, pub_key: Bytes) -> Result; @@ -382,13 +382,16 @@ impl EnclaveWrite for EnclaveContract { e3_params: Bytes, compute_provider_params: Bytes, custom_params: Bytes, - ) -> Result { + ) -> Result<(TransactionReceipt, U256)> { let _guard = NONCE_LOCK.lock().await; let wallet_addr = self .wallet_address .ok_or_else(|| eyre::eyre!("No wallet address configured"))?; let nonce = get_next_nonce(&*self.provider, wallet_addr).await?; + let contract = Enclave::new(self.contract_address, &self.provider); + let e3_id = contract.nexte3Id().call().await?; + let e3_request = E3RequestParams { threshold, startWindow: start_window, @@ -399,11 +402,10 @@ impl EnclaveWrite for EnclaveContract { customParams: custom_params.clone(), }; - let contract = Enclave::new(self.contract_address, &self.provider); let builder = contract.request(e3_request).nonce(nonce); let receipt = builder.send().await?.get_receipt().await?; - Ok(receipt) + Ok((receipt, e3_id)) } async fn activate(&self, e3_id: U256, pub_key: Bytes) -> Result { diff --git a/crates/evm/src/enclave_sol_reader.rs b/crates/evm/src/enclave_sol_reader.rs index 8cbf627e87..9811a939bc 100644 --- a/crates/evm/src/enclave_sol_reader.rs +++ b/crates/evm/src/enclave_sol_reader.rs @@ -33,10 +33,12 @@ impl From for e3_events::E3Requested { threshold_m: value.0.e3.threshold[0] as usize, threshold_n: value.0.e3.threshold[1] as usize, seed: value.0.e3.seed.into(), + // TODO: this should be delivered from the e3_program. Here we provide a sensible + // default that passes our tests error_size: ArcBytes::from_bytes( - &BigUint::from(10000000000000000000000000u128).to_bytes_be(), - ), // XXX: what should be here? - esi_per_ct: 3, // XXX: HARD CODING FOR NOW + &BigUint::from(36128399948547143872891754381312u128).to_bytes_be(), + ), + esi_per_ct: 3, // TODO: this should be delivered from the e3_program e3_id: E3id::new(value.0.e3Id.to_string(), value.1), } } diff --git a/crates/fhe/src/fhe.rs b/crates/fhe/src/fhe.rs index 94e25ddb64..8e6171d8a1 100644 --- a/crates/fhe/src/fhe.rs +++ b/crates/fhe/src/fhe.rs @@ -7,7 +7,10 @@ use super::create_crp; use anyhow::*; use async_trait::async_trait; -use e3_bfv_helpers::{build_bfv_params_arc, decode_bfv_params_arc}; +use e3_bfv_helpers::{ + build_bfv_params_arc, decode_bfv_params_arc, decode_plaintext_to_vec_u64, + encode_vec_u64_to_bytes, +}; use e3_data::{FromSnapshotWithParams, Snapshot}; use e3_events::{OrderedSet, Seed}; use e3_utils::{ArcBytes, SharedRng}; @@ -121,12 +124,9 @@ impl Fhe { .iter() .map(|k| DecryptionShare::deserialize(k, &self.params, arc_ct.clone())) .aggregate()?; - let decoded = Vec::::try_decode(&plaintext, Encoding::poly())?; - let mut bytes = Vec::with_capacity(decoded.len() * 8); - for value in decoded { - bytes.extend_from_slice(&value.to_le_bytes()); - } - + let decoded = + decode_plaintext_to_vec_u64(&plaintext).context("Could not decode plaintext")?; + let bytes = encode_vec_u64_to_bytes(&decoded); Ok(bytes) } } diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 709b76b619..d141672973 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -534,6 +534,7 @@ impl ThresholdKeyshare { pk_share, sk_sss, }), + external: false, })); Ok(()) diff --git a/crates/net/src/dialer.rs b/crates/net/src/dialer.rs index dee49daa78..8681bad81d 100644 --- a/crates/net/src/dialer.rs +++ b/crates/net/src/dialer.rs @@ -21,10 +21,8 @@ use tracing::info; use tracing::trace; use tracing::warn; -use crate::{ - events::{NetCommand, NetEvent}, - retry::{retry_with_backoff, to_retry, RetryError, BACKOFF_DELAY, BACKOFF_MAX_RETRIES}, -}; +use crate::events::{NetCommand, NetEvent}; +use e3_utils::{retry_with_backoff, to_retry, RetryError, BACKOFF_DELAY, BACKOFF_MAX_RETRIES}; /// Dial a single Multiaddr with retries and return an error should those retries not work async fn dial_multiaddr( diff --git a/crates/net/src/document_publisher.rs b/crates/net/src/document_publisher.rs index 1bd6f720d8..9f03994b72 100644 --- a/crates/net/src/document_publisher.rs +++ b/crates/net/src/document_publisher.rs @@ -8,26 +8,31 @@ use crate::{ events::{ call_and_await_response, DocumentPublishedNotification, GossipData, NetCommand, NetEvent, }, - retry::{retry_with_backoff, to_retry}, Cid, }; use actix::prelude::*; use anyhow::Result; use chrono::{DateTime, Utc}; use e3_events::{ - BusError, CiphernodeSelected, CorrelationId, DocumentReceived, E3RequestComplete, E3id, - EnclaveErrorType, EnclaveEvent, EventBus, PartyId, PublishDocumentRequested, Subscribe, + BusError, CiphernodeSelected, CorrelationId, DocumentKind, DocumentMeta, DocumentReceived, + E3RequestComplete, E3id, EnclaveErrorType, EnclaveEvent, EventBus, PartyId, + PublishDocumentRequested, Subscribe, ThresholdShareCreated, }; +use e3_utils::retry::{retry_with_backoff, to_retry}; use e3_utils::ArcBytes; use futures::TryFutureExt; +use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, sync::Arc, time::{Duration, Instant}, }; use tokio::sync::{broadcast, mpsc}; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; +const KADEMLIA_PUT_TIMEOUT: Duration = Duration::from_secs(30); +const KADEMLIA_GET_TIMEOUT: Duration = Duration::from_secs(30); +const KADEMLIA_BROADCAST_TIMEOUT: Duration = Duration::from_secs(30); /// DocumentPublisher is an actor that monitors events from both the NetInterface and the Enclave /// EventBus in order to manage document publishing interactions. In particular this involves the /// interactions of publishing a document and listening for notifications, determining if the node @@ -69,6 +74,7 @@ impl DocumentPublisher { // Add a list of events with paylods for the DHT match event { EnclaveEvent::PublishDocumentRequested { .. } => true, + EnclaveEvent::ThresholdShareCreated { .. } => true, _ => false, } } @@ -82,6 +88,7 @@ impl DocumentPublisher { ) -> Addr { let mut events = rx.resubscribe(); let addr = Self::new(bus, tx, rx, topic).start(); + EventConverter::setup(bus); // Listen on all events bus.do_send(Subscribe::new("*", addr.clone().recipient())); @@ -237,18 +244,8 @@ pub async fn handle_publish_document_requested( 1000, ) .await?; - - broadcast_document_published_notification( - tx, - rx, - DocumentPublishedNotification { - meta: event.meta, - key, - }, - topic, - ) - .await?; - + let notification = DocumentPublishedNotification::new(event.meta, key); + broadcast_document_published_notification(tx, rx, notification, topic).await?; Ok(()) } @@ -316,7 +313,7 @@ async fn put_record( } _ => None, }, - Duration::from_secs(3), + KADEMLIA_PUT_TIMEOUT, ) .await } @@ -342,7 +339,7 @@ async fn get_record( } _ => None, }, - Duration::from_secs(3), + KADEMLIA_GET_TIMEOUT, ) .await } @@ -370,11 +367,119 @@ async fn broadcast_document_published_notification( } _ => None, }, - Duration::from_secs(3), + KADEMLIA_BROADCAST_TIMEOUT, ) .await } +/// Convert between ThresholdShareCreated and DocumentPublished events +pub struct EventConverter { + bus: Addr>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +enum ReceivableDocument { + ThresholdShareCreated(ThresholdShareCreated), +} + +impl ReceivableDocument { + pub fn get_e3_id(&self) -> &E3id { + match self { + ReceivableDocument::ThresholdShareCreated(d) => &d.e3_id, + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + bincode::serialize(self) + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +impl EventConverter { + pub fn new(bus: &Addr>) -> Self { + Self { bus: bus.clone() } + } + pub fn setup(bus: &Addr>) -> Addr { + let addr = Self::new(bus).start(); + bus.do_send(Subscribe::new("ThresholdShareCreated", addr.clone().into())); + bus.do_send(Subscribe::new("DocumentReceived", addr.clone().into())); + addr + } + /// Local node created a threshold share. Send it as a published document + pub fn handle_threshold_share_created(&self, msg: ThresholdShareCreated) -> Result<()> { + // If this is received from elsewhere + if msg.external { + return Ok(()); + } + let receivable = ReceivableDocument::ThresholdShareCreated(msg); + let value = ArcBytes::from_bytes(&receivable.to_bytes()?); + let meta = DocumentMeta::new( + receivable.get_e3_id().clone(), + DocumentKind::TrBFV, + vec![], + None, + ); + self.bus + .do_send(EnclaveEvent::from(PublishDocumentRequested::new( + meta, value, + ))); + Ok(()) + } + /// Received document externally + pub fn handle_document_received(&self, msg: DocumentReceived) -> Result<()> { + warn!("Converting DocumentReceived..."); + let receivable = ReceivableDocument::from_bytes(&msg.value.extract_bytes())?; + let event = EnclaveEvent::from(match receivable { + ReceivableDocument::ThresholdShareCreated(evt) => ThresholdShareCreated { + external: true, + e3_id: evt.e3_id, + share: evt.share, + }, + }); + + self.bus.do_send(event); + Ok(()) + } +} + +impl Actor for EventConverter { + type Context = actix::Context; +} + +impl Handler for EventConverter { + type Result = (); + fn handle(&mut self, msg: EnclaveEvent, ctx: &mut Self::Context) -> Self::Result { + match msg { + EnclaveEvent::ThresholdShareCreated { data, .. } => ctx.notify(data), + EnclaveEvent::DocumentReceived { data, .. } => ctx.notify(data), + _ => (), + } + } +} + +impl Handler for EventConverter { + type Result = (); + fn handle(&mut self, msg: ThresholdShareCreated, ctx: &mut Self::Context) -> Self::Result { + match self.handle_threshold_share_created(msg) { + Ok(_) => (), + Err(err) => error!("{err}"), + } + } +} + +impl Handler for EventConverter { + type Result = (); + fn handle(&mut self, msg: DocumentReceived, ctx: &mut Self::Context) -> Self::Result { + match self.handle_document_received(msg) { + Ok(_) => (), + Err(err) => error!("{err}"), + } + } +} + #[cfg(test)] mod tests { use std::{collections::HashMap, num::NonZero, time::Duration}; @@ -435,7 +540,7 @@ mod tests { let (_guard, bus, _net_cmd_tx, mut net_cmd_rx, net_evt_tx, _net_evt_rx, _, _, _) = setup_test(); let value = ArcBytes::from_bytes(b"I am a special document"); - let expires_at = Utc::now() + chrono::Duration::days(1); + let expires_at = Some(Utc::now() + chrono::Duration::days(1)); let e3_id = E3id::new("1243", 1); // 1. Send a request to publish @@ -508,7 +613,7 @@ mod tests { setup_test(); let value = b"I am a special document".to_vec(); - let expires_at = Utc::now() + chrono::Duration::days(1); + let expires_at = Some(Utc::now() + chrono::Duration::days(1)); let e3_id = E3id::new("1243", 1); let cid = Cid::from_content(&value); @@ -571,7 +676,7 @@ mod tests { _, ) = setup_test(); let value = ArcBytes::from_bytes(b"I am a special document"); - let expires_at = Utc::now() + chrono::Duration::days(1); + let expires_at = Some(Utc::now() + chrono::Duration::days(1)); let e3_id = E3id::new("1243", 1); // Send a request to publish @@ -640,7 +745,7 @@ mod tests { E3id::new("1111", 1), DocumentKind::TrBFV, vec![], - expires_at, + Some(expires_at), ), }), ))?; @@ -654,7 +759,7 @@ mod tests { net_evt_tx.send(NetEvent::GossipData( GossipData::DocumentPublishedNotification(DocumentPublishedNotification { key: cid.clone(), - meta: DocumentMeta::new(e3_id, DocumentKind::TrBFV, vec![], expires_at), + meta: DocumentMeta::new(e3_id, DocumentKind::TrBFV, vec![], Some(expires_at)), }), ))?; diff --git a/crates/net/src/events.rs b/crates/net/src/events.rs index 00307cb822..0dedd10769 100644 --- a/crates/net/src/events.rs +++ b/crates/net/src/events.rs @@ -21,6 +21,7 @@ use std::{ time::{Duration, Instant}, }; use tokio::sync::{broadcast, mpsc}; +use tracing::{info, warn}; /// Incoming/Outgoing GossipData. We disambiguate on concerns relative to the net package. #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] @@ -138,6 +139,8 @@ impl NetEvent { pub fn correlation_id(&self) -> Option { use NetEvent as N; match self { + N::GossipPublished { correlation_id, .. } => Some(*correlation_id), + N::GossipPublishError { correlation_id, .. } => Some(*correlation_id), N::DhtGetRecordError { correlation_id, .. } => Some(*correlation_id), N::DhtGetRecordSucceeded { correlation_id, .. } => Some(*correlation_id), N::DhtPutRecordError { correlation_id, .. } => Some(*correlation_id), @@ -157,6 +160,10 @@ pub struct DocumentPublishedNotification { } impl DocumentPublishedNotification { + pub fn new(meta: DocumentMeta, key: Cid) -> Self { + Self { meta, key } + } + pub fn to_bytes(&self) -> Result> { bincode::serialize(self).context("Could not serialize DocumentPublishedNotification") } @@ -212,6 +219,5 @@ where }) .await .map_err(|_| anyhow::anyhow!(format!("Timed out waiting for response from {}", debug_cmd)))?; - result } diff --git a/crates/net/src/lib.rs b/crates/net/src/lib.rs index 51b3af5e4a..0b9868cd08 100644 --- a/crates/net/src/lib.rs +++ b/crates/net/src/lib.rs @@ -11,7 +11,6 @@ pub mod events; mod net_event_translator; mod net_interface; mod repo; -mod retry; pub use cid::Cid; pub use document_publisher::*; diff --git a/crates/net/src/net_event_translator.rs b/crates/net/src/net_event_translator.rs index df16a44116..18aa0cbb4a 100644 --- a/crates/net/src/net_event_translator.rs +++ b/crates/net/src/net_event_translator.rs @@ -7,6 +7,7 @@ use crate::events::GossipData; use crate::events::NetCommand; use crate::events::NetEvent; +use crate::DocumentPublisher; use crate::NetInterface; /// Actor for connecting to an libp2p client via it's mpsc channel interface /// This Actor should be responsible for @@ -20,6 +21,7 @@ use std::collections::HashSet; use std::sync::Arc; use tokio::sync::broadcast; use tokio::sync::mpsc; +use tracing::warn; use tracing::{error, info, instrument, trace}; // TODO: store event filtering here on this actor instead of is_local_only() on the event. We @@ -59,11 +61,12 @@ impl NetEventTranslator { } pub fn setup( - bus: Addr>, + bus: &Addr>, tx: &mpsc::Sender, - mut rx: broadcast::Receiver, + rx: &Arc>, topic: &str, ) -> Addr { + let mut rx = rx.resubscribe(); let addr = NetEventTranslator::new(bus.clone(), tx, topic).start(); // Listen on all events @@ -101,7 +104,6 @@ impl NetEventTranslator { EnclaveEvent::KeyshareCreated { .. } => true, EnclaveEvent::PlaintextAggregated { .. } => true, EnclaveEvent::PublicKeyAggregated { .. } => true, - EnclaveEvent::ThresholdShareCreated { .. } => true, _ => false, } } @@ -114,7 +116,14 @@ impl NetEventTranslator { cipher: &Arc, quic_port: u16, repository: Repository>, - ) -> Result<(Addr, tokio::task::JoinHandle>, String)> { + experimental_trbfv: bool, + ) -> Result<( + Addr, + Option>, + tokio::task::JoinHandle>, + String, + )> { + // TODO: We should separate NetInterface from NetEventTranslator let topic = "tmp-enclave-gossip-topic"; // Get existing keypair or generate a new one let mut bytes = match repository.read().await? { @@ -131,11 +140,22 @@ impl NetEventTranslator { let mut interface = NetInterface::new(&keypair, peers, Some(quic_port), topic)?; // Setup and start net event translator - let rx = interface.rx(); - let addr = NetEventTranslator::setup(bus, &interface.tx(), rx, topic); + let rx = &Arc::new(interface.rx()); + let addr = NetEventTranslator::setup(&bus, &interface.tx(), rx, topic); + let maybe_publisher = if experimental_trbfv { + Some(DocumentPublisher::setup(&bus, &interface.tx(), rx, topic)) + } else { + None + }; + let handle = tokio::spawn(async move { Ok(interface.start().await?) }); - Ok((addr, handle, keypair.public().to_peer_id().to_string())) + Ok(( + addr, + maybe_publisher, + handle, + keypair.public().to_peer_id().to_string(), + )) } } @@ -175,7 +195,7 @@ impl Handler for NetEventTranslator { trace!(evt_id=%id,"Have seen event before not rebroadcasting!"); return; } - + warn!("GossipPublish event: {}", event.event_type()); match evt.to_bytes() { Ok(data) => { if let Err(e) = tx diff --git a/crates/net/src/net_interface.rs b/crates/net/src/net_interface.rs index 9d1a96c43d..5620887feb 100644 --- a/crates/net/src/net_interface.rs +++ b/crates/net/src/net_interface.rs @@ -14,11 +14,13 @@ use libp2p::{ identify::{self, Behaviour as IdentifyBehaviour}, identity::Keypair, kad::{ - self, store::MemoryStore, Behaviour as KademliaBehaviour, GetRecordOk, QueryId, + self, + store::{MemoryStore, MemoryStoreConfig}, + Behaviour as KademliaBehaviour, Config as KademliaConfig, GetRecordOk, QueryId, QueryResult, Quorum, Record, RecordKey, }, swarm::{dial_opts::DialOpts, NetworkBehaviour, SwarmEvent}, - Swarm, + StreamProtocol, Swarm, }; use std::sync::atomic::AtomicBool; use std::{ @@ -30,6 +32,10 @@ use std::{io::Error, time::Duration}; use tokio::{select, sync::broadcast, sync::mpsc}; use tracing::{debug, error, info, trace, warn}; +const PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/kad/1.0.0"); +const MAX_KADEMLIA_PAYLOAD_MB: usize = 10; +const MAX_GOSSIP_MSG_SIZE_KB: usize = 700; + use crate::events::{GossipData, NetCommand}; use crate::events::{NetEvent, PutOrStoreError}; use crate::{dialer::dial_peers, Cid}; @@ -166,6 +172,7 @@ fn create_behaviour( let gossipsub_config = gossipsub::ConfigBuilder::default() .heartbeat_interval(Duration::from_secs(10)) + .max_transmit_size(MAX_GOSSIP_MSG_SIZE_KB * 1024) .validation_mode(gossipsub::ValidationMode::Strict) .build() .map_err(|msg| Error::new(std::io::ErrorKind::Other, msg))?; @@ -175,8 +182,19 @@ fn create_behaviour( gossipsub_config, )?; + let mut config = KademliaConfig::new(PROTOCOL_NAME); + config + .set_max_packet_size(MAX_KADEMLIA_PAYLOAD_MB * 1024 * 1024) + .set_query_timeout(Duration::from_secs(30)); + let store_config = MemoryStoreConfig { + max_records: 1024, + max_value_bytes: MAX_KADEMLIA_PAYLOAD_MB * 1024 * 1024, + max_providers_per_key: usize::MAX, + max_provided_keys: 1024, + }; + let store = MemoryStore::with_config(peer_id, store_config); // Setup Kademlia as server so that it responds to events correctly - let mut kademlia = KademliaBehaviour::new(peer_id, MemoryStore::new(peer_id)); + let mut kademlia = KademliaBehaviour::with_config(peer_id, store, config); kademlia.set_mode(Some(kad::Mode::Server)); Ok(NodeBehaviour { @@ -323,7 +341,9 @@ async fn process_swarm_event( let count = swarm.behaviour().gossipsub.mesh_peers(&topic).count(); event_tx.send(NetEvent::GossipSubscribed { count, topic })?; } - _ => {} + unknown => { + trace!("Unknown event: {:?}", unknown); + } }; Ok(()) } @@ -376,8 +396,10 @@ fn handle_gossip_publish( topic: String, correlation_id: CorrelationId, ) -> Result<()> { + let bytes = data.to_bytes()?; + warn!("About to try to Gossip {} bytes", bytes.len()); let gossipsub_behaviour = &mut swarm.behaviour_mut().gossipsub; - match gossipsub_behaviour.publish(gossipsub::IdentTopic::new(topic), data.to_bytes()?) { + match gossipsub_behaviour.publish(gossipsub::IdentTopic::new(topic), bytes) { Ok(message_id) => { event_tx.send(NetEvent::GossipPublished { correlation_id, @@ -385,7 +407,7 @@ fn handle_gossip_publish( })?; } Err(e) => { - warn!(error=?e, "Could not publish to swarm. Retrying..."); + error!(error=?e, "Could not GossipPublish."); event_tx.send(NetEvent::GossipPublishError { correlation_id, error: Arc::new(e), diff --git a/crates/test-helpers/Cargo.toml b/crates/test-helpers/Cargo.toml index ce77f5d76c..518da04118 100644 --- a/crates/test-helpers/Cargo.toml +++ b/crates/test-helpers/Cargo.toml @@ -28,6 +28,7 @@ e3-utils = { workspace = true } e3-sortition = { workspace = true } fhe = { workspace = true } fhe-traits = { workspace = true } +hex = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } tokio = { workspace = true } diff --git a/crates/test-helpers/src/bin/fake_encrypt.rs b/crates/test-helpers/src/bin/fake_encrypt.rs index 8d1d7de653..8f59f72c5d 100644 --- a/crates/test-helpers/src/bin/fake_encrypt.rs +++ b/crates/test-helpers/src/bin/fake_encrypt.rs @@ -6,12 +6,28 @@ // This is a test script designed to encrypt some fixed data to a fhe public key use clap::Parser; +use e3_sdk::bfv_helpers::decode_bfv_params; use e3_sdk::bfv_helpers::{build_bfv_params_from_set_arc, params::SET_2048_1032193_1}; use fhe::bfv::{Encoding, Plaintext, PublicKey}; use fhe_traits::{DeserializeParametrized, FheEncoder, FheEncrypter, Serialize}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; -use std::fs; +use std::{fs, sync::Arc}; + +#[derive(Debug, Clone)] +struct HexBytes(pub Vec); + +fn parse_hex(s: &str) -> Result { + // Remove "0x" or "0X" prefix if present + let s = s + .strip_prefix("0x") + .or_else(|| s.strip_prefix("0X")) + .unwrap_or(s); + // Decode hex string to bytes + hex::decode(s) + .map(HexBytes) + .map_err(|e| format!("Invalid hex string: {}", e)) +} #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -24,6 +40,9 @@ struct Args { #[arg(short, long, value_delimiter = ',')] plaintext: Vec, + + #[arg(long, value_parser = parse_hex)] + params: Option, } fn main() -> Result<(), Box> { @@ -32,13 +51,14 @@ fn main() -> Result<(), Box> { // Read the base64 encoded string from the input file println!("Loading public key from {}", args.input); let bytes = fs::read(&args.input)?; - - // Decode the base64 string - let param_set = SET_2048_1032193_1; - let params = build_bfv_params_from_set_arc(param_set); + let params = if let Some(params_bytes) = args.params { + Arc::new(decode_bfv_params(¶ms_bytes.0)) + } else { + build_bfv_params_from_set_arc(SET_2048_1032193_1) + }; let pubkey = PublicKey::from_bytes(&bytes, ¶ms)?; - let raw_plaintext = args.plaintext; + println!("Encrypting plaintext: {:?}", raw_plaintext); let pt = Plaintext::try_encode(&raw_plaintext, Encoding::poly(), ¶ms)?; diff --git a/crates/test-helpers/src/lib.rs b/crates/test-helpers/src/lib.rs index 6b65b72a52..c539e825d5 100644 --- a/crates/test-helpers/src/lib.rs +++ b/crates/test-helpers/src/lib.rs @@ -18,7 +18,7 @@ use e3_events::{ CiphernodeAdded, EnclaveEvent, EventBus, EventBusConfig, HistoryCollector, Seed, Subscribe, }; use e3_fhe::{create_crp, setup_crp_params, ParamsWithCrp}; -use e3_net::NetEventTranslator; +use e3_net::{DocumentPublisher, NetEventTranslator}; use e3_sdk::bfv_helpers::{params::SET_2048_1032193_1, BfvParamSet}; use e3_utils::SharedRng; use fhe::bfv::{BfvParameters, Ciphertext, Encoding, Plaintext, PublicKey}; @@ -136,6 +136,7 @@ pub fn simulate_libp2p_net(nodes: &[CiphernodeHandle]) { // converted to DocumentReceived events NetEventTranslator::is_forwardable_event(e) + || DocumentPublisher::is_document_publisher_event(e) }, dest, ) @@ -189,17 +190,23 @@ impl AddToCommittee { pub fn encrypt_ciphertext( params: &Arc, pubkey: PublicKey, - raw_plaintext: Vec, -) -> Result<(Arc, Vec)> { - let padded = &pad_end(&raw_plaintext, 0, 2048); - let mut bytes = Vec::with_capacity(padded.len() * 8); - for value in padded { - bytes.extend_from_slice(&value.to_le_bytes()); - } - let expected = bytes; - let pt = Plaintext::try_encode(&raw_plaintext, Encoding::poly(), ¶ms)?; - let ciphertext = pubkey.try_encrypt(&pt, &mut ChaCha20Rng::seed_from_u64(42))?; - Ok((Arc::new(ciphertext), expected)) + raw_plaintext: Vec>, +) -> Result<(Vec, Vec)> { + let mut rng = ChaCha20Rng::seed_from_u64(42); + let plaintext: Vec<_> = raw_plaintext + .into_iter() + .map(|raw| Ok(Plaintext::try_encode(&raw, Encoding::poly(), &params)?)) + .collect::<Result<_>>()?; + + let ciphertext = plaintext + .iter() + .map(|pt| { + pubkey + .try_encrypt(&pt, &mut rng) + .map_err(|e| anyhow!("{e}")) + }) + .collect::<Result<Vec<Ciphertext>>>()?; + Ok((ciphertext, plaintext)) } fn pad_end(input: &[u64], pad: u64, total: usize) -> Vec<u64> { diff --git a/crates/test-helpers/src/plaintext_writer.rs b/crates/test-helpers/src/plaintext_writer.rs index a0267d0aaa..b2a5911bbb 100644 --- a/crates/test-helpers/src/plaintext_writer.rs +++ b/crates/test-helpers/src/plaintext_writer.rs @@ -9,6 +9,7 @@ use std::path::PathBuf; use super::write_file_with_dirs; use actix::{Actor, Addr, Context, Handler}; use e3_events::{EnclaveEvent, EventBus, Subscribe}; +use e3_sdk::bfv_helpers::decode_bytes_to_vec_u64; use tracing::{error, info}; pub struct PlaintextWriter { @@ -37,16 +38,12 @@ impl Handler<EnclaveEvent> for PlaintextWriter { type Result = (); fn handle(&mut self, msg: EnclaveEvent, _: &mut Self::Context) -> Self::Result { if let EnclaveEvent::PlaintextAggregated { data, .. } = msg.clone() { - // HACK: decrypted output will be an array of ArcBytes and we will use this moving forward. For now - // only having the plaintext writer compatible with legacy tests and extracting the first value let Some(decrypted) = data.decrypted_output.first() else { error!("Decrypted output must not be empty!"); return; }; - let output: Vec<u64> = decrypted - .chunks_exact(8) - .map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap())) - .collect(); + + let output = decode_bytes_to_vec_u64(&decrypted).unwrap(); info!(path = ?&self.path, "Writing Plaintext To Path"); let contents: Vec<String> = output.iter().map(|&num| num.to_string()).collect(); diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index 13b71844c3..2035e5abd7 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -6,7 +6,7 @@ use actix::Actor; use alloy::primitives::{FixedBytes, I256, U256}; -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Result}; use e3_ciphernode_builder::CiphernodeBuilder; use e3_crypto::Cipher; use e3_events::{ @@ -15,7 +15,7 @@ use e3_events::{ TicketBalanceUpdated, }; use e3_multithread::Multithread; -use e3_sdk::bfv_helpers::{build_bfv_params_arc, encode_bfv_params}; +use e3_sdk::bfv_helpers::{build_bfv_params_arc, decode_bytes_to_vec_u64, encode_bfv_params}; use e3_test_helpers::ciphernode_system::CiphernodeSystemBuilder; use e3_test_helpers::{create_seed_from_u64, create_shared_rng_from_u64, AddToCommittee}; use e3_trbfv::helpers::calculate_error_size; @@ -340,10 +340,8 @@ async fn test_trbfv_actor() -> Result<()> { let results = plaintext .into_iter() - .map(|a| { - bincode::deserialize(&a.extract_bytes()).context("Could not deserialize plaintext") - }) - .collect::<Result<Vec<Vec<u64>>>>()?; + .map(|a| decode_bytes_to_vec_u64(&a.extract_bytes()).expect("error decoding bytes")) + .collect::<Vec<Vec<u64>>>(); let results: Vec<u64> = results .into_iter() diff --git a/crates/tests/tests/integration_legacy.rs b/crates/tests/tests/integration_legacy.rs index 5ff6e354a0..e07dcb2de2 100644 --- a/crates/tests/tests/integration_legacy.rs +++ b/crates/tests/tests/integration_legacy.rs @@ -22,6 +22,8 @@ use e3_events::{ }; use e3_net::events::GossipData; use e3_net::{events::NetEvent, NetEventTranslator}; +use e3_sdk::bfv_helpers::decode_bytes_to_vec_u64; +use e3_sdk::bfv_helpers::decode_plaintext_to_vec_u64; use e3_sdk::bfv_helpers::encode_bfv_params; use e3_test_helpers::encrypt_ciphertext; use e3_test_helpers::{ @@ -30,11 +32,12 @@ use e3_test_helpers::{ }; use e3_utils::utility_types::ArcBytes; use e3_utils::SharedRng; +use fhe::bfv::Encoding; use fhe::{ bfv::{BfvParameters, PublicKey, SecretKey}, mbfv::{AggregateIter, CommonRandomPoly, PublicKeyShare}, }; -use fhe_traits::Serialize; +use fhe_traits::{FheDecoder, Serialize}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; use std::{sync::Arc, time::Duration}; @@ -165,6 +168,15 @@ fn aggregate_public_key(shares: &Vec<PkSkShareTuple>) -> Result<PublicKey> { #[actix::test] async fn test_public_key_aggregation_and_decryption() -> Result<()> { + use tracing_subscriber::{fmt, EnvFilter}; + + let subscriber = fmt() + .with_env_filter(EnvFilter::new("info")) + .with_test_writer() + .finish(); + + let _guard = tracing::subscriber::set_default(subscriber); + // Setup let (bus, rng, seed, params, crpoly, _, _) = get_common_setup(None)?; let e3_id = E3id::new("1234", 1); @@ -237,37 +249,46 @@ async fn test_public_key_aggregation_and_decryption() -> Result<()> { println!("Aggregating decryption..."); // Aggregate decryption - // TODO: - // Making these values large (especially the yes value) requires changing - // the params we use here - as we tune the FHE we need to take care - let raw_plaintext = vec![1234u64, 873827u64]; + let raw_plaintext = vec![vec![1234, 567890]]; let (ciphertext, expected) = encrypt_ciphertext(&params, test_pubkey, raw_plaintext)?; // Setup Ciphertext Published Event let ciphertext_published_event = EnclaveEvent::from(CiphertextOutputPublished { - ciphertext_output: vec![ArcBytes::from_bytes(&ciphertext.to_bytes())], + ciphertext_output: ciphertext + .iter() + .map(|ct| ArcBytes::from_bytes(&ct.to_bytes())) + .collect(), e3_id: e3_id.clone(), }); bus.send(ciphertext_published_event.clone()).await?; - let expected_plaintext_agg_event = PlaintextAggregated { - e3_id: e3_id.clone(), - decrypted_output: vec![ArcBytes::from_bytes(&expected)], - }; let history = history_collector .send(TakeEvents::<EnclaveEvent>::new(6)) .await?; - let aggregated_event = history + let actual = history .into_iter() .filter_map(|e| match e { EnclaveEvent::PlaintextAggregated { data, .. } => Some(data), _ => None, }) - .collect::<Vec<_>>(); + .collect::<Vec<_>>() + .first() + .unwrap() + .clone(); - assert_eq!(aggregated_event, vec![expected_plaintext_agg_event]); + assert_eq!( + actual + .decrypted_output + .iter() + .map(|b| decode_bytes_to_vec_u64(b).unwrap()) + .collect::<Vec<Vec<u64>>>(), + expected + .iter() + .map(|p| decode_plaintext_to_vec_u64(p).unwrap()) + .collect::<Vec<Vec<u64>>>() + ); Ok(()) } @@ -383,11 +404,14 @@ async fn test_stopped_keyshares_retain_state() -> Result<()> { .aggregate()?; // Publish the ciphertext - let raw_plaintext = vec![1234u64, 873827u64]; + let raw_plaintext = vec![vec![1234, 567890]]; let (ciphertext, expected) = encrypt_ciphertext(&params, pubkey, raw_plaintext)?; bus.send( EnclaveEvent::from(CiphertextOutputPublished { - ciphertext_output: vec![ArcBytes::from_bytes(&ciphertext.to_bytes())], + ciphertext_output: ciphertext + .iter() + .map(|ct| ArcBytes::from_bytes(&ct.to_bytes())) + .collect(), e3_id: e3_id.clone(), }) .clone(), @@ -398,11 +422,28 @@ async fn test_stopped_keyshares_retain_state() -> Result<()> { .send(TakeEvents::<EnclaveEvent>::new(5)) .await?; - let actual = history.iter().find_map(|evt| match evt { - EnclaveEvent::PlaintextAggregated { data, .. } => Some(data.decrypted_output.clone()), - _ => None, - }); - assert_eq!(actual, Some(vec![ArcBytes::from_bytes(&expected)])); + let actual = history + .into_iter() + .filter_map(|e| match e { + EnclaveEvent::PlaintextAggregated { data, .. } => Some(data), + _ => None, + }) + .collect::<Vec<_>>() + .first() + .unwrap() + .clone(); + + assert_eq!( + actual + .decrypted_output + .iter() + .map(|b| decode_bytes_to_vec_u64(b).unwrap()) + .collect::<Vec<Vec<u64>>>(), + expected + .iter() + .map(|p| decode_plaintext_to_vec_u64(p).unwrap()) + .collect::<Vec<Vec<u64>>>() + ); Ok(()) } @@ -415,9 +456,9 @@ async fn test_p2p_actor_forwards_events_to_network() -> Result<()> { let bus = EventBus::<EnclaveEvent>::new(EventBusConfig { deduplicate: true }).start(); let history_collector = HistoryCollector::<EnclaveEvent>::new().start(); bus.do_send(Subscribe::new("*", history_collector.clone().recipient())); - let event_rx = event_tx.subscribe(); + let event_rx = Arc::new(event_tx.subscribe()); // Pas cmd and event channels to NetEventTranslator - NetEventTranslator::setup(bus.clone(), &cmd_tx, event_rx, "my-topic"); + NetEventTranslator::setup(&bus, &cmd_tx, &event_rx, "my-topic"); // Capture messages from output on msgs vec let msgs: Arc<Mutex<Vec<GossipData>>> = Arc::new(Mutex::new(Vec::new())); @@ -588,7 +629,7 @@ async fn test_p2p_actor_forwards_events_to_bus() -> Result<()> { let history_collector = HistoryCollector::<EnclaveEvent>::new().start(); bus.do_send(Subscribe::new("*", history_collector.clone().recipient())); - NetEventTranslator::setup(bus.clone(), &cmd_tx, event_rx, "mytopic"); + NetEventTranslator::setup(&bus, &cmd_tx, &Arc::new(event_rx), "mytopic"); // Capture messages from output on msgs vec let event = EnclaveEvent::from(E3Requested { diff --git a/crates/trbfv/src/calculate_threshold_decryption.rs b/crates/trbfv/src/calculate_threshold_decryption.rs index 0138158576..31ed8355ad 100644 --- a/crates/trbfv/src/calculate_threshold_decryption.rs +++ b/crates/trbfv/src/calculate_threshold_decryption.rs @@ -9,6 +9,7 @@ use std::sync::Arc; /// This module defines event payloads that will dcrypt a ciphertext with a threshold quorum of decryption shares use crate::{helpers::try_poly_from_bytes, PartyId, TrBFVConfig}; use anyhow::*; +use e3_bfv_helpers::{decode_plaintext_to_vec_u64, encode_vec_u64_to_bytes}; use e3_utils::utility_types::ArcBytes; use fhe::bfv::{Encoding, Plaintext}; use fhe::{bfv::Ciphertext, trbfv::ShareManager}; @@ -114,9 +115,8 @@ impl TryFrom<InnerResponse> for CalculateThresholdDecryptionResponse { .plaintext .into_iter() .map(|open_result| -> Result<_> { - let vec_64 = Vec::<u64>::try_decode(&open_result, Encoding::poly()) - .context("could not decode plaintext")?; - let bytes = bincode::serialize(&vec_64)?; + let decoded = decode_plaintext_to_vec_u64(&open_result)?; + let bytes = encode_vec_u64_to_bytes(&decoded); Ok(ArcBytes::from_bytes(&bytes)) }) .collect::<Result<_>>()?, diff --git a/crates/trbfv/tests/integration.rs b/crates/trbfv/tests/integration.rs index 10c51386a3..66b42dfe6b 100644 --- a/crates/trbfv/tests/integration.rs +++ b/crates/trbfv/tests/integration.rs @@ -8,8 +8,8 @@ use std::{ sync::{Arc, Mutex}, }; -use anyhow::{Context, Result}; -use e3_bfv_helpers::{build_bfv_params_arc, encode_bfv_params}; +use anyhow::Result; +use e3_bfv_helpers::{build_bfv_params_arc, decode_bytes_to_vec_u64, encode_bfv_params}; use e3_crypto::Cipher; use e3_fhe::create_crp; use e3_test_helpers::{create_seed_from_u64, create_shared_rng_from_u64, usecase_helpers}; @@ -142,10 +142,8 @@ async fn test_trbfv_isolation() -> Result<()> { let results = plaintext .into_iter() - .map(|a| { - bincode::deserialize(&a.extract_bytes()).context("Could not deserialize plaintext") - }) - .collect::<Result<Vec<Vec<u64>>>>()?; + .map(|a| decode_bytes_to_vec_u64(&a.extract_bytes()).unwrap()) + .collect::<Vec<Vec<u64>>>(); let results: Vec<u64> = results .into_iter() diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 458fd315ee..bc2563d2eb 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -7,10 +7,12 @@ description.workspace = true repository.workspace = true [dependencies] -alloy.workspace = true -derivative.workspace = true -serde.workspace = true actix.workspace = true +alloy.workspace = true anyhow.workspace = true +derivative.workspace = true rand.workspace = true rand_chacha.workspace = true +serde.workspace = true +tokio.workspace = true +tracing.workspace = true diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 19c2ebd0dc..761c59c4f4 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -8,9 +8,11 @@ pub mod actix; pub mod alloy; pub mod formatters; pub mod helpers; +pub mod retry; pub mod utility_types; pub use actix::*; pub use alloy::*; pub use formatters::*; pub use helpers::*; +pub use retry::*; pub use utility_types::*; diff --git a/crates/net/src/retry.rs b/crates/utils/src/retry.rs similarity index 100% rename from crates/net/src/retry.rs rename to crates/utils/src/retry.rs diff --git a/crates/utils/src/utility_types.rs b/crates/utils/src/utility_types.rs index a205aa9128..bb71734dd6 100644 --- a/crates/utils/src/utility_types.rs +++ b/crates/utils/src/utility_types.rs @@ -29,6 +29,10 @@ impl ArcBytes { pub fn extract_bytes(&self) -> Vec<u8> { (*self.0).clone() } + + pub fn size(&self) -> usize { + self.0.len() + } } impl Deref for ArcBytes { diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index b235b01406..9111aca25a 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -30,9 +30,9 @@ pub fn bfv_encrypt_number( public_key: Vec<u8>, degree: usize, plaintext_modulus: u64, - moduli: u64, + moduli: Vec<u64>, ) -> Result<Vec<u8>, JsValue> { - let encrypted_data = bfv_encrypt([data], public_key, degree, plaintext_modulus, [moduli]) + let encrypted_data = bfv_encrypt([data], public_key, degree, plaintext_modulus, &moduli) .map_err(|e| JsValue::from_str(&format!("{}", e)))?; Ok(encrypted_data) } @@ -60,9 +60,9 @@ pub fn bfv_encrypt_vector( public_key: Vec<u8>, degree: usize, plaintext_modulus: u64, - moduli: u64, + moduli: Vec<u64>, ) -> Result<Vec<u8>, JsValue> { - let encrypted_data = bfv_encrypt(data, public_key, degree, plaintext_modulus, [moduli]) + let encrypted_data = bfv_encrypt(data, public_key, degree, plaintext_modulus, &moduli) .map_err(|e| JsValue::from_str(&format!("{}", e)))?; Ok(encrypted_data) } @@ -91,9 +91,9 @@ pub fn bfv_verifiable_encrypt_number( public_key: Vec<u8>, degree: usize, plaintext_modulus: u64, - moduli: u64, + moduli: Vec<u64>, ) -> Result<Vec<JsValue>, JsValue> { - let result = bfv_verifiable_encrypt([data], public_key, degree, plaintext_modulus, [moduli]) + let result = bfv_verifiable_encrypt([data], public_key, degree, plaintext_modulus, moduli) .map_err(|e| JsValue::from_str(&format!("{}", e)))?; // Return as a vector of JsValues @@ -127,9 +127,9 @@ pub fn bfv_verifiable_encrypt_vector( public_key: Vec<u8>, degree: usize, plaintext_modulus: u64, - moduli: u64, + moduli: Vec<u64>, ) -> Result<Vec<JsValue>, JsValue> { - let result = bfv_verifiable_encrypt(data, public_key, degree, plaintext_modulus, [moduli]) + let result = bfv_verifiable_encrypt(data, public_key, degree, plaintext_modulus, moduli) .map_err(|e| JsValue::from_str(&format!("{}", e)))?; // Return as a vector of JsValues diff --git a/examples/CRISP/.gitignore b/examples/CRISP/.gitignore index 99c0e6ea24..384bd03867 100644 --- a/examples/CRISP/.gitignore +++ b/examples/CRISP/.gitignore @@ -47,4 +47,5 @@ playwright-report/ .enclave/data/ .enclave/config/ .enclave/caches/ +.enclave/ready cache_hardhat/ diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index 7abae24b74..63b33914ad 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2257,6 +2257,7 @@ dependencies = [ "fhe-util", "num-bigint", "rand 0.8.5", + "thiserror 1.0.69", "zkfhe-greco", ] diff --git a/examples/CRISP/enclave.config.yaml b/examples/CRISP/enclave.config.yaml index 6119452800..45bd0d7664 100644 --- a/examples/CRISP/enclave.config.yaml +++ b/examples/CRISP/enclave.config.yaml @@ -41,10 +41,20 @@ nodes: quic_port: 9203 autonetkey: true autopassword: true - ag: + cn4: address: "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" quic_port: 9204 autonetkey: true autopassword: true + cn5: + address: "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" + quic_port: 9205 + autonetkey: true + autopassword: true + ag: + address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + quic_port: 9206 + autonetkey: true + autopassword: true role: type: aggregator diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index 22c64fe348..87609307eb 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -165,7 +165,7 @@ "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "enclaveAddress": "0x0000000000000000000000000000000000000001", - "submissionWindow": "3" + "submissionWindow": "10" }, "blockNumber": 10, "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" @@ -203,7 +203,7 @@ "blockNumber": 23, "address": "0x59b670e9fA9D0A427751Af201D676719a970857b" }, - "RiscZeroGroth16Verifier": { + "MockRISC0Verifier": { "address": "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f" }, "CRISPInputValidator": { diff --git a/examples/CRISP/playwright.config.ts b/examples/CRISP/playwright.config.ts index ae724033bc..20788b4627 100644 --- a/examples/CRISP/playwright.config.ts +++ b/examples/CRISP/playwright.config.ts @@ -13,11 +13,13 @@ export default defineConfig({ baseURL: "http://localhost:3000", actionTimeout: 75 * 1000, }, - retries: process.env.CI ? 2 : 0, + retries: 0, fullyParallel: true, forbidOnly: !!process.env.CI, workers: process.env.CI ? 1 : undefined, - reporter: "html", + // reporter: "html", + reporter: [["html"], ["list"]], // Add list reporter + // Add support for ES modules projects: [ { diff --git a/examples/CRISP/scripts/dev_cipher.sh b/examples/CRISP/scripts/dev_cipher.sh index 2ca9dbb6c2..d9971a1f75 100755 --- a/examples/CRISP/scripts/dev_cipher.sh +++ b/examples/CRISP/scripts/dev_cipher.sh @@ -1,22 +1,29 @@ #!/usr/bin/env bash set -euo pipefail +READYFILE=$1 # nuke past installations as we are adding these nodes to the contract -rm -rf ./enclave/data -rm -rf ./enclave/config +rm -rf ./.enclave/data +rm -rf ./.enclave/config +rm -rf $READYFILE PRIVATE_KEY_AG="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" PRIVATE_KEY_CN1="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" PRIVATE_KEY_CN2="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" PRIVATE_KEY_CN3="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" +PRIVATE_KEY_CN4="0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a" +PRIVATE_KEY_CN5="0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba" enclave wallet set --name ag --private-key "$PRIVATE_KEY_AG" enclave wallet set --name cn1 --private-key "$PRIVATE_KEY_CN1" enclave wallet set --name cn2 --private-key "$PRIVATE_KEY_CN2" enclave wallet set --name cn3 --private-key "$PRIVATE_KEY_CN3" +enclave wallet set --name cn4 --private-key "$PRIVATE_KEY_CN4" +enclave wallet set --name cn5 --private-key "$PRIVATE_KEY_CN5" # using & instead of -d so that wait works below +# TODO: add --experimental-trbfv after testing enclave nodes up -v & sleep 2 @@ -24,10 +31,18 @@ sleep 2 CN1=$(cat ./enclave.config.yaml | yq -r '.nodes.cn1.address') CN2=$(cat ./enclave.config.yaml | yq -r '.nodes.cn2.address') CN3=$(cat ./enclave.config.yaml | yq -r '.nodes.cn3.address') +CN4=$(cat ./enclave.config.yaml | yq -r '.nodes.cn4.address') +CN5=$(cat ./enclave.config.yaml | yq -r '.nodes.cn5.address') # Add ciphernodes using variables from config.sh pnpm ciphernode:add --ciphernode-address "$CN1" --network "localhost" pnpm ciphernode:add --ciphernode-address "$CN2" --network "localhost" pnpm ciphernode:add --ciphernode-address "$CN3" --network "localhost" +pnpm ciphernode:add --ciphernode-address "$CN4" --network "localhost" +pnpm ciphernode:add --ciphernode-address "$CN5" --network "localhost" + +echo 1 > $READYFILE + +echo "CIPHERNODES HAVE BEEN ADDED." wait diff --git a/examples/CRISP/scripts/dev_services.sh b/examples/CRISP/scripts/dev_services.sh index fb876a98df..f95d198c7c 100755 --- a/examples/CRISP/scripts/dev_services.sh +++ b/examples/CRISP/scripts/dev_services.sh @@ -3,7 +3,7 @@ set -euo pipefail concurrently -kr \ - "./scripts/dev_cipher.sh" \ + "./scripts/dev_cipher.sh ./.enclave/ready" \ "./scripts/dev_program.sh" \ "wait-on tcp:13151 && ./scripts/dev_server.sh" \ - "wait-on tcp:4000 && ./scripts/dev_client.sh" + "wait-on tcp:4000 && wait-on file:./.enclave/ready && ./scripts/dev_client.sh" diff --git a/examples/CRISP/server/.env.example b/examples/CRISP/server/.env.example index 8599e48c51..cf83c576ad 100644 --- a/examples/CRISP/server/.env.example +++ b/examples/CRISP/server/.env.example @@ -20,8 +20,8 @@ FEE_TOKEN_ADDRESS="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" # E3 Config E3_WINDOW_SIZE=40 -E3_THRESHOLD_MIN=1 -E3_THRESHOLD_MAX=2 +E3_THRESHOLD_MIN=2 +E3_THRESHOLD_MAX=5 E3_DURATION=160 # E3 Compute Provider Config diff --git a/examples/CRISP/server/src/cli/commands.rs b/examples/CRISP/server/src/cli/commands.rs index fcfa271eb4..a1767899f6 100644 --- a/examples/CRISP/server/src/cli/commands.rs +++ b/examples/CRISP/server/src/cli/commands.rs @@ -4,7 +4,6 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use chrono::Utc; use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; use log::info; use num_bigint::BigUint; @@ -19,7 +18,7 @@ use crisp::config::CONFIG; use e3_sdk::bfv_helpers::{ build_bfv_params_from_set_arc, encode_bfv_params, params::SET_2048_1032193_1, }; -use e3_sdk::evm_helpers::contracts::{EnclaveContract, EnclaveRead, EnclaveWrite}; +use e3_sdk::evm_helpers::contracts::{EnclaveContract, EnclaveRead, EnclaveWrite, E3}; use fhe::bfv::{BfvParameters, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}; use fhe_traits::{ DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, @@ -74,7 +73,7 @@ pub async fn get_current_timestamp() -> Result<u64, Box<dyn std::error::Error + pub async fn initialize_crisp_round( token_address: &str, balance_threshold: &str, -) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { +) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> { info!( "Starting new CRISP round with token address: {} and balance threshold: {}", token_address, balance_threshold @@ -178,7 +177,7 @@ pub async fn initialize_crisp_round( CONFIG.ciphernode_registry_address ); - let res = contract + let (res, e3_id) = contract .request_e3( threshold, start_window, @@ -190,8 +189,19 @@ pub async fn initialize_crisp_round( ) .await?; info!("E3 request sent. TxHash: {:?}", res.transaction_hash); + let e3_id_u64 = u64::try_from(e3_id)?; + info!("E3 ID: {}", e3_id_u64); - Ok(()) + Ok(e3_id_u64) +} + +pub async fn check_e3_activated( + e3_id: u64, +) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> { + let contract = + EnclaveContract::read_only(&CONFIG.http_rpc_url, &CONFIG.enclave_address).await?; + let e3: E3 = contract.get_e3(U256::from(e3_id)).await?; + Ok(u64::try_from(e3.expiration)? > 0) } pub async fn activate_e3_round() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { diff --git a/examples/CRISP/server/src/cli/main.rs b/examples/CRISP/server/src/cli/main.rs index 7a10be5e15..2f68e62293 100644 --- a/examples/CRISP/server/src/cli/main.rs +++ b/examples/CRISP/server/src/cli/main.rs @@ -10,7 +10,7 @@ mod commands; use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; use reqwest::Client; -use commands::initialize_crisp_round; +use commands::{check_e3_activated, initialize_crisp_round}; use crisp::logger::init_logger; use log::info; @@ -49,6 +49,10 @@ enum Commands { #[arg(short, long, default_value = "1000000000000000000")] balance_threshold: String, }, + CheckActivate { + #[arg(short, long)] + e3id: u64, + }, } #[tokio::main] @@ -68,7 +72,12 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { token_address, balance_threshold, }) => { - initialize_crisp_round(&token_address, &balance_threshold).await?; + let e3_id = initialize_crisp_round(&token_address, &balance_threshold).await?; + println!("{}", e3_id); + } + Some(Commands::CheckActivate { e3id }) => { + let is_activated = check_e3_activated(e3id).await?; + println!("{}", is_activated); } None => { // Fall back to interactive mode if no command was specified @@ -77,7 +86,8 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 0 => { let token_address = get_token_address()?; let balance_threshold = get_balance_threshold()?; - initialize_crisp_round(&token_address, &balance_threshold).await?; + let e3_id = initialize_crisp_round(&token_address, &balance_threshold).await?; + println!("E3 ID: {}", e3_id); } _ => unreachable!(), } diff --git a/examples/CRISP/server/src/server/indexer.rs b/examples/CRISP/server/src/server/indexer.rs index dd29635c6b..1a30097aab 100644 --- a/examples/CRISP/server/src/server/indexer.rs +++ b/examples/CRISP/server/src/server/indexer.rs @@ -16,6 +16,7 @@ use alloy::providers::{Provider, ProviderBuilder}; use alloy::sol_types::{sol_data, SolType}; use alloy_primitives::{Address, U256}; use e3_sdk::{ + bfv_helpers::decode_bytes_to_vec_u64, evm_helpers::{ contracts::{ EnclaveContract, EnclaveContractFactory, EnclaveRead, EnclaveWrite, ReadWrite, @@ -46,7 +47,7 @@ pub async fn register_e3_requested( let e3_id = event.e3Id.to::<u64>(); let mut repo = CrispE3Repository::new(store.clone(), e3_id); - info!("E3Requested: {:?}", event); + info!("[e3_id={}] E3Requested: {:?}", e3_id, event); async move { // Convert custom params bytes back to token address and balance threshold. @@ -77,15 +78,15 @@ pub async fn register_e3_requested( // Get token holders from Etherscan API or mocked data. let token_holders = if matches!(CONFIG.chain_id, 31337 | 1337) { info!( - "Using mocked token holders for local network (chain_id: {})", - CONFIG.chain_id + "[e3_id={}] Using mocked token holders for local network (chain_id: {})", + e3_id, CONFIG.chain_id ); get_mock_token_holders() } else { info!( - "Using Etherscan API for network (chain_id: {})", - CONFIG.chain_id + "[e3_id={}] Using Etherscan API for network (chain_id: {})", + e3_id, CONFIG.chain_id ); let etherscan_client = @@ -98,7 +99,8 @@ pub async fn register_e3_requested( U256::from_str_radix(&balance_threshold.to_string(), 10).map_err( |e| { eyre::eyre!( - "Failed to convert balance threshold to U256: {}", + "[e3_id={}] Failed to convert balance threshold to U256: {}", + e3_id, e ) }, @@ -110,7 +112,8 @@ pub async fn register_e3_requested( if token_holders.is_empty() { return Err(eyre::eyre!( - "No eligible token holders found for token address {}.", + "[e3_id={}] No eligible token holders found for token address {}.", + e3_id, token_address ) .into()); @@ -129,7 +132,7 @@ pub async fn register_e3_requested( .root() .ok_or_else(|| eyre::eyre!("Failed to get merkle root from tree"))?; - info!("Merkle root: {}", merkle_root); + info!("[e3_id={}] Merkle root: {}", e3_id, merkle_root); // TODO: Publish merkle root on-chain (inputValidator contract). @@ -151,7 +154,7 @@ pub async fn register_e3_activated( let mut current_round_repo = CurrentRoundRepository::new(store); let expiration = event.expiration.to::<u64>(); - info!("Handling E3 request with id {}", e3_id); + info!("[e3_id={}] Handling E3 request", e3_id); async move { repo.start_round().await?; @@ -161,12 +164,16 @@ pub async fn register_e3_activated( // Calculate expiration time to sleep until let now = get_current_timestamp_rpc().await?; + info!("[e3_id={}] Current time before sleep: {}", e3_id, now); let wait_duration = if expiration > now { let secs = expiration - now; - info!("Need to wait {} seconds until expiration", secs); + info!( + "[e3_id={}] Need to wait {} seconds until expiration", + e3_id, secs + ); Duration::from_secs(secs) } else { - info!("Expired E3"); + info!("[e3_id={}] Expired E3", e3_id); Duration::ZERO }; if !wait_duration.is_zero() { @@ -176,7 +183,7 @@ pub async fn register_e3_activated( repo.update_status("Expired").await?; if repo.get_vote_count().await? > 0 { - info!("Starting computation for E3: {}", e3_id); + info!("[e3_id={}] Starting computation for E3", e3_id); repo.update_status("Computing").await?; let (id, status) = run_compute( @@ -205,14 +212,17 @@ pub async fn register_e3_activated( .into()); } - info!("Request Computation for E3: {}", e3_id); + info!("[e3_id={}] Request Computation for E3", e3_id); repo.update_status("PublishingCiphertext").await?; } else { - info!("E3 has no votes to decrypt. Setting status to Finished."); + info!( + "[e3_id={}] E3 has no votes to decrypt. Setting status to Finished.", + e3_id + ); repo.update_status("Finished").await?; } - info!("E3 request handled successfully."); + info!("[e3_id={}] E3 request handled successfully.", e3_id); Ok(()) } @@ -230,6 +240,7 @@ pub async fn register_ciphertext_output_published( let e3_id = event.e3Id.to::<u64>(); let mut repo = CrispE3Repository::new(store, e3_id); async move { + info!("[e3_id={}] Handling CiphertextOutputPublished", e3_id); repo.update_status("CiphertextPublished").await?; Ok(()) } @@ -247,17 +258,13 @@ pub async fn register_plaintext_output_published( let e3_id = event.e3Id.to::<u64>(); let mut repo = CrispE3Repository::new(store, e3_id); async move { - info!("CRISP: handling 'PlaintextOutputPublished'"); + info!("[e3_id={}] Handling PlaintextOutputPublished", e3_id); // The plaintextOutput from the event contains the result of the FHE computation. // The computation sums the encrypted votes: '0' for Option 1, '1' for Option 2. // Thus, the decrypted sum directly represents the number of votes for Option 2. // The output is expected to be a Vec<u8> in little endian format of u64s. - let decoded: Vec<u64> = event - .plaintextOutput - .chunks_exact(8) - .map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap())) - .collect(); + let decoded = decode_bytes_to_vec_u64(&event.plaintextOutput)?; // decoded[0] is the sum of all encrypted votes (0s and 1s). // Since Option 1 votes are encrypted as '0' and Option 2 votes as '1', @@ -271,9 +278,9 @@ pub async fn register_plaintext_output_published( // the Option 2 votes (the sum from the FHE output) from the total votes. let option_1 = total_votes - option_2; - info!("Vote Count: {:?}", total_votes); - info!("Votes Option 1: {:?}", option_1); - info!("Votes Option 2: {:?}", option_2); + info!("[e3_id={}] Vote Count: {:?}", e3_id, total_votes); + info!("[e3_id={}] Votes Option 1: {:?}", e3_id, option_1); + info!("[e3_id={}] Votes Option 2: {:?}", e3_id, option_2); repo.set_votes(option_1, option_2).await?; repo.update_status("Finished").await?; @@ -298,28 +305,31 @@ pub async fn register_committee_published( // making two calls to contract let e3 = contract.get_e3(event.e3Id).await?; if u64::try_from(e3.expiration)? > 0 { - info!("E3 already activated '{}'", event.e3Id); + info!("[e3_id={}] E3 already activated", event.e3Id); return Ok(()); } // Read Start time in Seconds let start_time = e3.startWindow[0].to::<u64>(); - info!("Start time: {}", start_time); + info!("[e3_id={}] Start time: {}", event.e3Id, start_time); // Get current time let now = get_current_timestamp_rpc().await?; - info!("Current time: {}", now); + info!("[e3_id={}] Current time: {}", event.e3Id, now); // Calculate wait duration let wait_duration = if start_time > now { let secs = start_time - now; - info!("Need to wait {} seconds until activation", secs); + info!( + "[e3_id={}] Need to wait {} seconds until activation", + event.e3Id, secs + ); Duration::from_secs(secs) } else { - info!("Activating E3"); + info!("[e3_id={}] Activating E3", event.e3Id); Duration::ZERO }; - info!("Wait duration: {:?}", wait_duration); + info!("[e3_id={}] Wait duration: {:?}", event.e3Id, wait_duration); // Sleep until start time if !wait_duration.is_zero() { @@ -328,7 +338,10 @@ pub async fn register_committee_published( // If not activated activate let tx = contract.activate(event.e3Id, event.publicKey).await?; - info!("E3 activated with tx: {:?}", tx.transaction_hash); + info!( + "[e3_id={}] E3 activated with tx: {:?}", + event.e3Id, tx.transaction_hash + ); Ok(()) } }) diff --git a/examples/CRISP/server/src/server/routes/rounds.rs b/examples/CRISP/server/src/server/routes/rounds.rs index 1ac3d49873..83a7746940 100644 --- a/examples/CRISP/server/src/server/routes/rounds.rs +++ b/examples/CRISP/server/src/server/routes/rounds.rs @@ -209,7 +209,7 @@ pub async fn initialize_crisp_round( batch_size: CONFIG.e3_compute_provider_batch_size, }; let compute_provider_params = Bytes::from(bincode::serialize(&compute_provider_params)?); - let res = contract + let (receipt, e3_id) = contract .request_e3( threshold, start_window, @@ -220,7 +220,10 @@ pub async fn initialize_crisp_round( custom_params_bytes, ) .await?; - info!("E3 request sent. TxHash: {:?}", res.transaction_hash); + info!( + "E3 request sent. TxHash: {:?}, E3 ID: {}", + receipt.transaction_hash, e3_id + ); Ok(()) } diff --git a/examples/CRISP/server/src/server/routes/voting.rs b/examples/CRISP/server/src/server/routes/voting.rs index 0f571b0241..59b448b7d2 100644 --- a/examples/CRISP/server/src/server/routes/voting.rs +++ b/examples/CRISP/server/src/server/routes/voting.rs @@ -40,7 +40,7 @@ async fn broadcast_encrypted_vote( store: web::Data<AppData>, ) -> impl Responder { let vote = data.into_inner(); - + info!("[e3_id={}] Broadcasting encrypted vote", vote.round_id); // Validate and update vote status let has_voted = match store .e3(vote.round_id) @@ -49,12 +49,16 @@ async fn broadcast_encrypted_vote( { Ok(voted) => voted, Err(e) => { - log::error!("Database error checking vote status: {:?}", e); + error!( + "[e3_id={}] Database error checking vote status: {:?}", + vote.round_id, e + ); return HttpResponse::InternalServerError().json("Internal server error"); } }; if has_voted { + info!("[e3_id={}] User has already voted", vote.round_id); return HttpResponse::Ok().json(VoteResponse { status: VoteResponseStatus::UserAlreadyVoted, tx_hash: None, @@ -65,7 +69,10 @@ async fn broadcast_encrypted_vote( let mut repo = store.e3(vote.round_id); if let Err(e) = repo.insert_voter_address(vote.address.clone()).await { - log::error!("Database error inserting voter: {:?}", e); + error!( + "[e3_id={}] Database error inserting voter: {:?}", + vote.round_id, e + ); return HttpResponse::InternalServerError().json("Internal server error"); } @@ -103,17 +110,23 @@ async fn broadcast_encrypted_vote( { Ok(c) => c, Err(e) => { - log::error!("Database error checking vote status: {:?}", e); + error!( + "[e3_id={}] Database error checking vote status: {:?}", + vote.round_id, e + ); return HttpResponse::InternalServerError().json("Internal server error"); } }; match contract.publish_input(e3_id, encoded_params).await { - Ok(hash) => HttpResponse::Ok().json(VoteResponse { - status: VoteResponseStatus::Success, - tx_hash: Some(hash.transaction_hash.to_string()), - message: Some("Vote Successful".to_string()), - }), + Ok(hash) => { + info!("[e3_id={}] Vote broadcasted successfully", vote.round_id); + HttpResponse::Ok().json(VoteResponse { + status: VoteResponseStatus::Success, + tx_hash: Some(hash.transaction_hash.to_string()), + message: Some("Vote Successful".to_string()), + }) + } Err(e) => handle_vote_error(e, repo, &vote.address).await, } } diff --git a/examples/CRISP/test/crisp.spec.ts b/examples/CRISP/test/crisp.spec.ts index 0854877ebf..7848adb66a 100644 --- a/examples/CRISP/test/crisp.spec.ts +++ b/examples/CRISP/test/crisp.spec.ts @@ -4,69 +4,144 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { Page } from "@playwright/test"; +import { ConsoleMessage, Page } from "@playwright/test"; import { testWithSynpress } from "@synthetixio/synpress"; import { MetaMask, metaMaskFixtures } from "@synthetixio/synpress/playwright"; import basicSetup from "./wallet-setup/basic.setup"; import { execSync } from "child_process"; -async function runCliInit() { +async function runCliInit(): Promise<number> { try { // Execute the command and wait for it to complete const output = execSync( "pnpm cli init --token-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --balance-threshold 1000", - { encoding: "utf-8" } + { encoding: "utf-8" }, ); console.log("Command output:", output); - return output; + const lines = output.trim().split("\n"); + const lastLine = lines[lines.length - 1].trim(); + const e3Id = parseInt(lastLine, 10); + if (isNaN(e3Id)) { + throw new Error(`Failed to parse e3Id from CLI output: ${lastLine}`); + } + return e3Id; } catch (error) { console.error("Error executing command:", error); throw error; } } +async function checkE3Activated(e3id: number): Promise<boolean> { + try { + const output = execSync(`pnpm cli check-activate --e3id ${e3id}`, { + encoding: "utf-8", + }); + const lines = output.trim().split("\n"); + const lastLine = lines[lines.length - 1].trim(); + return lastLine === "true"; + } catch (error) { + console.error("Error checking e3 activation:", error); + return false; + } +} + +async function waitForE3Activation( + e3id: number, + maxWaitMs: number = 300000, +): Promise<void> { + const startTime = Date.now(); + while (Date.now() - startTime < maxWaitMs) { + const isActivated = await checkE3Activated(e3id); + if (isActivated) { + console.log(`E3 ${e3id} is activated`); + return; + } + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + throw new Error(`E3 ${e3id} was not activated within ${maxWaitMs}ms`); +} + const test = testWithSynpress(metaMaskFixtures(basicSetup)); const { expect } = test; async function ensureHomePageLoaded(page: Page) { return await expect(page.locator("h4")).toHaveText( - "Coercion-Resistant Impartial Selection Protocol" + "Coercion-Resistant Impartial Selection Protocol", ); } +function log(msg: string) { + console.log(`[playwright] ${msg}`); +} + test("CRISP smoke test", async ({ context, page, metamaskPage, extensionId, }) => { + page.on("console", (msg: ConsoleMessage) => { + console.log(msg.text()); + }); + + log("============================================"); + log(" STARTING YOUR PLAYWRIGHT TEST! "); + log("============================================"); + + log("Creating new Metamask..."); const metamask = new MetaMask( context, metamaskPage, basicSetup.walletPassword, - extensionId + extensionId, ); - await runCliInit(); - // Wait 6 seconds for committee to be published - await page.waitForTimeout(6_000); + log("runCliInit()..."); + const e3id = await runCliInit(); + log(`Got e3 id: ${e3id}`); + await page.goto("/"); + + log(`ensureHomePageLoaded...`); await ensureHomePageLoaded(page); + + log(`searching for connect button...`); await page.locator('button:has-text("Connect Wallet")').click(); + log(`searching for MetaMask button...`); await page.locator('button:has-text("MetaMask")').click(); + log(`connecting to dapp...`); await metamask.connectToDapp(); + log(`clicking try demo...`); await page.locator('button:has-text("Try Demo")').click(); + + log(`waiting for E3 activation...`); + await waitForE3Activation(e3id); + log(`forcing page reload...`); + await page.reload(); + + log(`clicking first vote card...`); await page .locator("[data-test-id='poll-button-0'] > [data-test-id='card']") .click(); + log(`clicking Cast Vote...`); await page.locator('button:has-text("Cast Vote")').click(); - await page.waitForTimeout(220_000); + const WAIT = 300_000; + log(`waiting for ${WAIT}ms...`); + await page.waitForTimeout(WAIT); + log(`clicking historic polls button...`); await page.locator('a:has-text("Historic polls")').click(); + log(`asserting that Historic polls exists...`); await expect(page.locator("h1")).toHaveText("Historic polls"); + log(`asserting that result has 100% on the vote we clicked on...`); await expect( - page.locator("[data-test-id='poll-0-0'] [data-test-id='poll-result-0'] h3") + page.locator("[data-test-id='poll-0-0'] [data-test-id='poll-result-0'] h3"), ).toHaveText("100%"); + log(`asserting that result has 0% on the vote we did not click on...`); await expect( - page.locator("[data-test-id='poll-0-0'] [data-test-id='poll-result-1'] h3") + page.locator("[data-test-id='poll-0-0'] [data-test-id='poll-result-1'] h3"), ).toHaveText("0%"); + + log("============================================"); + log(" PLAYWRIGHT TEST IS COMPLETE "); + log("============================================"); }); diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index da0ed3208f..2fd0b2b467 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -851,5 +851,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-a2f64967aeae699bd499cc90bbcf76e2314a0651" + "buildInfoId": "solc-0_8_28-c084532c59ac53ee00b443bea013584d95d4e870" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index b4749a16db..ffdaa8d9d4 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -535,5 +535,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-a2f64967aeae699bd499cc90bbcf76e2314a0651" + "buildInfoId": "solc-0_8_28-c084532c59ac53ee00b443bea013584d95d4e870" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json index b7d1d76a68..adf68d7704 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json @@ -977,5 +977,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IEnclave.sol", - "buildInfoId": "solc-0_8_28-c5db5579375d04ced938f4f4a02b4414441687bc" + "buildInfoId": "solc-0_8_28-c084532c59ac53ee00b443bea013584d95d4e870" } \ No newline at end of file diff --git a/packages/enclave-contracts/deployed_contracts.json b/packages/enclave-contracts/deployed_contracts.json index cf2ffe77d8..d7b83debd8 100644 --- a/packages/enclave-contracts/deployed_contracts.json +++ b/packages/enclave-contracts/deployed_contracts.json @@ -116,45 +116,45 @@ }, "localhost": { "PoseidonT3": { - "blockNumber": 3, + "blockNumber": 1, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 4, - "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" + "blockNumber": 2, + "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 5, - "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" + "blockNumber": 3, + "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "EnclaveTicketToken": { "constructorArgs": { - "baseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "baseToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 7, - "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "blockNumber": 5, + "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { "constructorArgs": { "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "bondingRegistry": "0x0000000000000000000000000000000000000001" }, - "blockNumber": 8, - "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + "blockNumber": 6, + "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "BondingRegistry": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketToken": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", - "licenseToken": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", + "ticketToken": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", + "licenseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", "registry": "0x0000000000000000000000000000000000000001", "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "ticketPrice": "10000000", @@ -162,8 +162,50 @@ "minTicketBalance": "1", "exitDelay": "604800" }, - "blockNumber": 9, + "blockNumber": 7, + "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + }, + "CiphernodeRegistryOwnable": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "enclaveAddress": "0x0000000000000000000000000000000000000001", + "submissionWindow": "10" + }, + "blockNumber": 8, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + }, + "Enclave": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "registry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", + "bondingRegistry": "0x0165878A594ca255338adfa4d48449f69242Eb8F", + "feeToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", + "maxDuration": "2592000", + "params": [ + "0x000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000fc0010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000800000022a000100000000000000000000000000000000000000000000000000800000021a000100000000000000000000000000000000000000000000000000800000021200010000000000000000000000000000000000000000000000000080000001f60001" + ] + }, + "blockNumber": 9, + "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + }, + "MockComputeProvider": { + "blockNumber": 18, + "address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" + }, + "MockDecryptionVerifier": { + "blockNumber": 19, + "address": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "MockInputValidator": { + "blockNumber": 20, + "address": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" + }, + "MockE3Program": { + "constructorArgs": { + "mockInputValidator": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" + }, + "blockNumber": 21, + "address": "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d" } } } \ No newline at end of file diff --git a/packages/enclave-contracts/scripts/deployEnclave.ts b/packages/enclave-contracts/scripts/deployEnclave.ts index 9951fb8744..e6779b5f83 100644 --- a/packages/enclave-contracts/scripts/deployEnclave.ts +++ b/packages/enclave-contracts/scripts/deployEnclave.ts @@ -40,7 +40,7 @@ export const deployEnclave = async (withMocks?: boolean) => { ); const THIRTY_DAYS_IN_SECONDS = 60 * 60 * 24 * 30; - const SORTITION_SUBMISSION_WINDOW = 5; + const SORTITION_SUBMISSION_WINDOW = 10; const addressOne = "0x0000000000000000000000000000000000000001"; const poseidonT3 = await deployAndSavePoseidonT3({ hre }); diff --git a/packages/enclave-contracts/tasks/enclave.ts b/packages/enclave-contracts/tasks/enclave.ts index 6cd029d075..6bb3d8ae78 100644 --- a/packages/enclave-contracts/tasks/enclave.ts +++ b/packages/enclave-contracts/tasks/enclave.ts @@ -321,8 +321,21 @@ export const activateE3 = task("e3:activate", "Activate an E3 program") defaultValue: "", type: ArgumentType.STRING, }) + .addOption({ + name: "publicKeyFile", + description: "path to file containing the public key", + defaultValue: "", + type: ArgumentType.STRING, + }) .setAction(async () => ({ - default: async ({ e3Id, publicKey }, hre) => { + default: async ({ e3Id, publicKey: publicKeyArg, publicKeyFile }, hre) => { + const publicKey = + publicKeyArg || + (publicKeyFile ? fs.readFileSync(publicKeyFile, "utf8").trim() : "") || + process.env.PUBLIC_KEY; + + if (!publicKey) throw new Error("No public key provided!"); + const { deployAndSaveEnclave } = await import( "../scripts/deployAndSave/enclave" ); diff --git a/packages/enclave-sdk/src/enclave-sdk.ts b/packages/enclave-sdk/src/enclave-sdk.ts index 9401a8ebb3..98f36bebf5 100644 --- a/packages/enclave-sdk/src/enclave-sdk.ts +++ b/packages/enclave-sdk/src/enclave-sdk.ts @@ -42,7 +42,7 @@ import { bfv_verifiable_encrypt_number, bfv_verifiable_encrypt_vector, } from "@enclave-e3/wasm"; -import { CircuitInputs, generateProof } from "./greco"; +import { generateProof } from "./greco"; import { CompiledCircuit } from "@noir-lang/noir_js"; export class EnclaveSDK { @@ -71,14 +71,14 @@ export class EnclaveSDK { if (!isValidAddress(config.contracts.ciphernodeRegistry)) { throw new SDKError( "Invalid CiphernodeRegistry contract address", - "INVALID_ADDRESS" + "INVALID_ADDRESS", ); } if (!isValidAddress(config.contracts.feeToken)) { throw new SDKError( "Invalid FeeToken contract address", - "INVALID_ADDRESS" + "INVALID_ADDRESS", ); } @@ -86,7 +86,7 @@ export class EnclaveSDK { this.contractClient = new ContractClient( config.publicClient, config.walletClient, - config.contracts + config.contracts, ); this.protocol = config.protocol; @@ -98,6 +98,9 @@ export class EnclaveSDK { case FheProtocol.BFV: this.protocolParams = BfvProtocolParams.BFV_NORMAL; break; + case FheProtocol.TRBFV: + this.protocolParams = BfvProtocolParams.BFV_THRESHOLD; + break; default: throw new Error("Protocol not supported"); } @@ -117,7 +120,7 @@ export class EnclaveSDK { } catch (error) { throw new SDKError( `Failed to initialize SDK: ${error}`, - "SDK_INITIALIZATION_FAILED" + "SDK_INITIALIZATION_FAILED", ); } } @@ -130,7 +133,7 @@ export class EnclaveSDK { */ public async encryptNumber( data: bigint, - publicKey: Uint8Array + publicKey: Uint8Array, ): Promise<Uint8Array> { await initializeWasm(); switch (this.protocol) { @@ -140,7 +143,7 @@ export class EnclaveSDK { publicKey, this.protocolParams.degree, this.protocolParams.plaintextModulus, - this.protocolParams.moduli + BigUint64Array.from(this.protocolParams.moduli), ); default: throw new Error("Protocol not supported"); @@ -155,7 +158,7 @@ export class EnclaveSDK { */ public async encryptVector( data: BigUint64Array, - publicKey: Uint8Array + publicKey: Uint8Array, ): Promise<Uint8Array> { await initializeWasm(); switch (this.protocol) { @@ -165,7 +168,7 @@ export class EnclaveSDK { publicKey, this.protocolParams.degree, this.protocolParams.plaintextModulus, - this.protocolParams.moduli + BigUint64Array.from(this.protocolParams.moduli), ); default: throw new Error("Protocol not supported"); @@ -181,7 +184,7 @@ export class EnclaveSDK { */ public async encryptNumberAndGenInputs( data: bigint, - publicKey: Uint8Array + publicKey: Uint8Array, ): Promise<EncryptedValueAndPublicInputs> { await initializeWasm(); switch (this.protocol) { @@ -191,7 +194,7 @@ export class EnclaveSDK { publicKey, this.protocolParams.degree, this.protocolParams.plaintextModulus, - this.protocolParams.moduli + BigUint64Array.from(this.protocolParams.moduli), ); const publicInputs = JSON.parse(circuitInputs); @@ -214,7 +217,7 @@ export class EnclaveSDK { public async encryptNumberAndGenProof( data: bigint, publicKey: Uint8Array, - circuit: CompiledCircuit + circuit: CompiledCircuit, ): Promise<VerifiableEncryptionResult> { const { publicInputs, encryptedData } = await this.encryptNumberAndGenInputs(data, publicKey); @@ -234,7 +237,7 @@ export class EnclaveSDK { */ public async encryptVectorAndGenInputs( data: BigUint64Array, - publicKey: Uint8Array + publicKey: Uint8Array, ): Promise<EncryptedValueAndPublicInputs> { await initializeWasm(); switch (this.protocol) { @@ -244,7 +247,7 @@ export class EnclaveSDK { publicKey, this.protocolParams.degree, this.protocolParams.plaintextModulus, - this.protocolParams.moduli + BigUint64Array.from(this.protocolParams.moduli), ); const publicInputs = JSON.parse(circuitInputs); @@ -267,7 +270,7 @@ export class EnclaveSDK { public async encryptVectorAndGenProof( data: BigUint64Array, publicKey: Uint8Array, - circuit: CompiledCircuit + circuit: CompiledCircuit, ): Promise<VerifiableEncryptionResult> { const { publicInputs, encryptedData } = await this.encryptVectorAndGenInputs(data, publicKey); @@ -322,7 +325,7 @@ export class EnclaveSDK { params.e3ProgramParams, params.computeProviderParams, params.customParams, - params.gasLimit + params.gasLimit, ); } @@ -345,7 +348,7 @@ export class EnclaveSDK { public async activateE3( e3Id: bigint, publicKey: `0x${string}`, - gasLimit?: bigint + gasLimit?: bigint, ): Promise<Hash> { if (!this.initialized) { await this.initialize(); @@ -360,7 +363,7 @@ export class EnclaveSDK { public async publishInput( e3Id: bigint, data: `0x${string}`, - gasLimit?: bigint + gasLimit?: bigint, ): Promise<Hash> { if (!this.initialized) { await this.initialize(); @@ -376,7 +379,7 @@ export class EnclaveSDK { e3Id: bigint, ciphertextOutput: `0x${string}`, proof: `0x${string}`, - gasLimit?: bigint + gasLimit?: bigint, ): Promise<Hash> { if (!this.initialized) { await this.initialize(); @@ -386,7 +389,7 @@ export class EnclaveSDK { e3Id, ciphertextOutput, proof, - gasLimit + gasLimit, ); } @@ -406,11 +409,11 @@ export class EnclaveSDK { */ public onEnclaveEvent<T extends AllEventTypes>( eventType: T, - callback: EventCallback<T> + callback: EventCallback<T>, ): void { // Determine which contract to listen to based on event type const isEnclaveEvent = Object.values(EnclaveEventType).includes( - eventType as EnclaveEventType + eventType as EnclaveEventType, ); const contractAddress = isEnclaveEvent ? this.config.contracts.enclave @@ -423,7 +426,7 @@ export class EnclaveSDK { contractAddress, eventType, abi, - callback + callback, ); } @@ -432,7 +435,7 @@ export class EnclaveSDK { */ public off<T extends AllEventTypes>( eventType: T, - callback: EventCallback<T> + callback: EventCallback<T>, ): void { this.eventListener.off(eventType, callback); } @@ -442,7 +445,7 @@ export class EnclaveSDK { */ public once<T extends AllEventTypes>( type: T, - callback: EventCallback<T> + callback: EventCallback<T>, ): void { const handler: EventCallback<T> = (event) => { this.off(type, handler); @@ -460,10 +463,10 @@ export class EnclaveSDK { public async getHistoricalEvents( eventType: AllEventTypes, fromBlock?: bigint, - toBlock?: bigint + toBlock?: bigint, ): Promise<Log[]> { const isEnclaveEvent = Object.values(EnclaveEventType).includes( - eventType as EnclaveEventType + eventType as EnclaveEventType, ); const contractAddress = isEnclaveEvent ? this.config.contracts.enclave @@ -477,7 +480,7 @@ export class EnclaveSDK { eventType, abi, fromBlock, - toBlock + toBlock, ); } @@ -507,14 +510,14 @@ export class EnclaveSDK { args: readonly unknown[], contractAddress: `0x${string}`, abi: Abi, - value?: bigint + value?: bigint, ): Promise<bigint> { return this.contractClient.estimateGas( functionName, args, contractAddress, abi, - value + value, ); } @@ -560,7 +563,7 @@ export class EnclaveSDK { this.contractClient = new ContractClient( this.config.publicClient, this.config.walletClient, - this.config.contracts + this.config.contracts, ); this.initialized = false; diff --git a/packages/enclave-sdk/src/types.ts b/packages/enclave-sdk/src/types.ts index 0f21717cef..a07901a4f7 100644 --- a/packages/enclave-sdk/src/types.ts +++ b/packages/enclave-sdk/src/types.ts @@ -291,6 +291,10 @@ export enum FheProtocol { * The BFV protocol */ BFV = "BFV", + /** + * The TrBFV protocol + */ + TRBFV = "TRBFV", } /** @@ -312,7 +316,7 @@ export interface ProtocolParams { /** * The moduli */ - moduli: bigint; + moduli: bigint[]; } /** @@ -328,7 +332,24 @@ export const BfvProtocolParams = { BFV_NORMAL: { degree: 2048, plaintextModulus: 1032193n, - moduli: 0x3fffffff000001n, + moduli: [0x3fffffff000001n], + } as const satisfies ProtocolParams, + + /** + * Recommended parameters for TrBFV protocol + * - Degree: 8192 + * - Plaintext modulus: 1000 + * - Moduli: [0x00800000022a0001, 0x00800000021a0001, 0x0080000002120001, 0x0080000001f60001] + */ + BFV_THRESHOLD: { + degree: 8192, + plaintextModulus: 1000n, + moduli: [ + 0x00800000022a0001n, + 0x00800000021a0001n, + 0x0080000002120001n, + 0x0080000001f60001n, + ], } as const satisfies ProtocolParams, }; diff --git a/packages/enclave-sdk/src/utils.ts b/packages/enclave-sdk/src/utils.ts index ece09846f0..ead9dce144 100644 --- a/packages/enclave-sdk/src/utils.ts +++ b/packages/enclave-sdk/src/utils.ts @@ -7,7 +7,10 @@ import { type Address, type Hash, type Log, encodeAbiParameters } from "viem"; export class SDKError extends Error { - constructor(message: string, public readonly code?: string) { + constructor( + message: string, + public readonly code?: string, + ) { super(message); this.name = "SDKError"; } @@ -23,7 +26,7 @@ export function isValidHash(hash: string): hash is Hash { export function formatEventName( contractName: string, - eventName: string + eventName: string, ): string { return `${contractName}.${eventName}`; } @@ -59,13 +62,30 @@ export function getCurrentTimestamp(): number { } // BFV parameter set matching the Rust SET_2048_1032193_1 configuration -export const BFV_PARAMS_SET = { +export const SET_2048_1032193_1 = { degree: 2048, plaintext_modulus: 1032193, moduli: [0x3fffffff000001n], // BigInt for the modulus error2_variance: "10", } as const; +// BFV parameter set matching the Rust SET_8192_1000_4 configuration +export const SET_8192_1000_4 = { + degree: 8192, + plaintext_modulus: 1000, + moduli: [ + 0x00800000022a0001n, + 0x00800000021a0001n, + 0x0080000002120001n, + 0x0080000001f60001n, + ], + error2_variance: + "52309181128222339698631578526730685514457152477762943514050560000", +}; + +// Set default parameter set +export const BFV_PARAMS_SET = SET_2048_1032193_1; + // Compute provider parameters structure export interface ComputeProviderParams { name: string; @@ -82,8 +102,8 @@ export const DEFAULT_COMPUTE_PROVIDER_PARAMS: ComputeProviderParams = { // Default E3 configuration export const DEFAULT_E3_CONFIG = { - threshold_min: 1, - threshold_max: 2, + threshold_min: 2, + threshold_max: 5, window_size: 120, // 2 minutes in seconds duration: 1800, // 30 minutes in seconds payment_amount: "0", // 0 ETH in wei @@ -97,7 +117,7 @@ export function encodeBfvParams( degree: number = BFV_PARAMS_SET.degree, plaintext_modulus: number = BFV_PARAMS_SET.plaintext_modulus, moduli: readonly bigint[] = BFV_PARAMS_SET.moduli, - error2_variance: string = BFV_PARAMS_SET.error2_variance + error2_variance: string = BFV_PARAMS_SET.error2_variance, ): `0x${string}` { return encodeAbiParameters( [ @@ -119,7 +139,7 @@ export function encodeBfvParams( moduli: [...moduli], error2_variance, }, - ] + ], ); } @@ -129,7 +149,7 @@ export function encodeBfvParams( */ export function encodeComputeProviderParams( params: ComputeProviderParams, - mock: boolean = false + mock: boolean = false, ): `0x${string}` { if (mock) { return `0x${"0".repeat(32)}` as `0x${string}`; @@ -140,7 +160,7 @@ export function encodeComputeProviderParams( const bytes = encoder.encode(jsonString); return `0x${Array.from(bytes, (byte) => - byte.toString(16).padStart(2, "0") + byte.toString(16).padStart(2, "0"), ).join("")}`; } @@ -148,14 +168,14 @@ export function encodeComputeProviderParams( * Encode custom parameters for the smart contract. */ export function encodeCustomParams( - params: Record<string, unknown> + params: Record<string, unknown>, ): `0x${string}` { const jsonString = JSON.stringify(params); const encoder = new TextEncoder(); const bytes = encoder.encode(jsonString); return `0x${Array.from(bytes, (byte) => - byte.toString(16).padStart(2, "0") + byte.toString(16).padStart(2, "0"), ).join("")}`; } @@ -163,7 +183,7 @@ export function encodeCustomParams( * Calculate start window for E3 request */ export function calculateStartWindow( - windowSize: number = DEFAULT_E3_CONFIG.window_size + windowSize: number = DEFAULT_E3_CONFIG.window_size, ): [bigint, bigint] { const now = getCurrentTimestamp(); return [BigInt(now), BigInt(now + windowSize)]; @@ -181,7 +201,7 @@ export function decodePlaintextOutput(plaintextOutput: string): number | null { // Convert hex to bytes const bytes = new Uint8Array( - hex.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || [] + hex.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || [], ); if (bytes.length < 8) { diff --git a/scripts/compile-circuits.sh b/scripts/compile-circuits.sh index 70355883d1..7c14d6e781 100755 --- a/scripts/compile-circuits.sh +++ b/scripts/compile-circuits.sh @@ -5,6 +5,12 @@ set -euo pipefail # Ensure we're in the right directory cd circuits +if ! command -v nargo >/dev/null 2>&1 +then + echo "nargo could not be found" + exit 0 # exiting 0 so that other scripts are not affected +fi + # Checking circuit format echo "Checking circuit format..." if ! (nargo fmt --check); then @@ -19,4 +25,4 @@ if ! (nargo compile --workspace); then exit 1 fi -echo "Noir circuits compiled successfully" \ No newline at end of file +echo "Noir circuits compiled successfully" diff --git a/scripts/run-crisp-test.sh b/scripts/run-crisp-test.sh index f13c1cccc1..402e542c85 100755 --- a/scripts/run-crisp-test.sh +++ b/scripts/run-crisp-test.sh @@ -6,4 +6,4 @@ echo "Press any key to continue or Ctrl+C to cancel..." read -rm -rf * && git reset --hard HEAD && git submodule update --init --recursive && pnpm install && cd examples/CRISP && pnpm test:e2e "$@" +rm -rf * && git reset --hard HEAD && git submodule update --init --recursive && pnpm install && pnpm build && cd examples/CRISP && pnpm test:e2e "$@" diff --git a/templates/default/enclave.config.yaml b/templates/default/enclave.config.yaml index 8df8049404..24274110bf 100644 --- a/templates/default/enclave.config.yaml +++ b/templates/default/enclave.config.yaml @@ -41,10 +41,20 @@ nodes: quic_port: 9203 autonetkey: true autopassword: true - ag: + cn4: address: "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" quic_port: 9204 autonetkey: true autopassword: true + cn5: + address: "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" + quic_port: 9205 + autonetkey: true + autopassword: true + ag: + address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + quic_port: 9206 + autonetkey: true + autopassword: true role: type: aggregator diff --git a/templates/default/program/src/lib.rs b/templates/default/program/src/lib.rs index 7ebf0c9bfd..58a2cdbd1c 100644 --- a/templates/default/program/src/lib.rs +++ b/templates/default/program/src/lib.rs @@ -26,9 +26,7 @@ pub fn fhe_processor(fhe_inputs: &FHEInputs) -> Vec<u8> { mod tests { use super::*; use anyhow::Result; - use e3_bfv_helpers::{ - build_bfv_params_from_set_arc, encode_bfv_params, params::SET_2048_1032193_1, - }; + use e3_bfv_helpers::{build_bfv_params_arc, encode_bfv_params, params::SET_2048_1032193_1}; use fhe::bfv::{Encoding, Plaintext, PublicKey, SecretKey}; use fhe_traits::FheEncoder; use fhe_traits::FheEncrypter; @@ -39,7 +37,8 @@ mod tests { fn test() -> Result<()> { let mut rng = thread_rng(); - let params = build_bfv_params_from_set_arc(SET_2048_1032193_1); + let (degree, plaintext, moduli) = SET_2048_1032193_1; + let params = build_bfv_params_arc(degree, plaintext, &moduli); let secret_key = SecretKey::random(&params, &mut OsRng); let public_key = PublicKey::new(&secret_key, &mut rng); diff --git a/templates/default/scripts/dev_ciphernodes.sh b/templates/default/scripts/dev_ciphernodes.sh index e6f982b859..b5db725889 100755 --- a/templates/default/scripts/dev_ciphernodes.sh +++ b/templates/default/scripts/dev_ciphernodes.sh @@ -28,13 +28,18 @@ PRIVATE_KEY_AG="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff PRIVATE_KEY_CN1="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" PRIVATE_KEY_CN2="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" PRIVATE_KEY_CN3="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" +PRIVATE_KEY_CN4="0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a" +PRIVATE_KEY_CN5="0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba" -enclave wallet set --name ag --private-key "$PRIVATE_KEY_AG" -enclave wallet set --name cn1 --private-key "$PRIVATE_KEY_CN1" -enclave wallet set --name cn2 --private-key "$PRIVATE_KEY_CN2" -enclave wallet set --name cn3 --private-key "$PRIVATE_KEY_CN3" +enclave wallet set --name ag --private-key "$PRIVATE_KEY_AG" +enclave wallet set --name cn1 --private-key "$PRIVATE_KEY_CN1" +enclave wallet set --name cn2 --private-key "$PRIVATE_KEY_CN2" +enclave wallet set --name cn3 --private-key "$PRIVATE_KEY_CN3" +enclave wallet set --name cn4 --private-key "$PRIVATE_KEY_CN4" +enclave wallet set --name cn5 --private-key "$PRIVATE_KEY_CN5" # using & instead of -d so that wait works below +# TODO: add --experimental-trbfv after testing enclave nodes up -v & sleep 2 @@ -42,12 +47,16 @@ sleep 2 CN1=$(grep -A 1 'cn1:' enclave.config.yaml | grep 'address:' | sed 's/.*address: *"\([^"]*\)".*/\1/') CN2=$(grep -A 1 'cn2:' enclave.config.yaml | grep 'address:' | sed 's/.*address: *"\([^"]*\)".*/\1/') CN3=$(grep -A 1 'cn3:' enclave.config.yaml | grep 'address:' | sed 's/.*address: *"\([^"]*\)".*/\1/') +CN4=$(grep -A 1 'cn4:' enclave.config.yaml | grep 'address:' | sed 's/.*address: *"\([^"]*\)".*/\1/') +CN5=$(grep -A 1 'cn5:' enclave.config.yaml | grep 'address:' | sed 's/.*address: *"\([^"]*\)".*/\1/') # Add ciphernodes using variables from config.sh pnpm run deploy && sleep 2 pnpm hardhat ciphernode:admin-add --ciphernode-address $CN1 --network localhost pnpm hardhat ciphernode:admin-add --ciphernode-address $CN2 --network localhost pnpm hardhat ciphernode:admin-add --ciphernode-address $CN3 --network localhost +pnpm hardhat ciphernode:admin-add --ciphernode-address $CN4 --network localhost +pnpm hardhat ciphernode:admin-add --ciphernode-address $CN5 --network localhost # Function to send RPC request. send_rpc() { diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts index 6d18d74be1..5524302378 100644 --- a/templates/default/tests/integration.spec.ts +++ b/templates/default/tests/integration.spec.ts @@ -73,11 +73,11 @@ type E3State = async function setupEventListeners( sdk: EnclaveSDK, - store: Map<bigint, E3State> + store: Map<bigint, E3State>, ) { async function waitForEvent<T extends AllEventTypes>( type: T, - trigger?: () => Promise<void> + trigger?: () => Promise<void>, ): Promise<EnclaveEvent<T>> { return new Promise((resolve) => { sdk.once(type, resolve); @@ -191,7 +191,7 @@ describe("Integration", () => { const e3ProgramParams = encodeBfvParams(); const computeProviderParams = encodeComputeProviderParams( DEFAULT_COMPUTE_PROVIDER_PARAMS, - true // Mock the compute provider parameters, return 32 bytes of 0x00 + true, // Mock the compute provider parameters, return 32 bytes of 0x00 ); let state; @@ -252,21 +252,21 @@ describe("Integration", () => { await sdk.publishInput( e3Id, `0x${Array.from(enc1, (b) => b.toString(16).padStart(2, "0")).join( - "" - )}` as `0x${string}` + "", + )}` as `0x${string}`, ); }); await waitForEvent(EnclaveEventType.INPUT_PUBLISHED, async () => { await sdk.publishInput( e3Id, `0x${Array.from(enc2, (b) => b.toString(16).padStart(2, "0")).join( - "" - )}` as `0x${string}` + "", + )}` as `0x${string}`, ); }); const plaintextEvent = await waitForEvent( - EnclaveEventType.PLAINTEXT_OUTPUT_PUBLISHED + EnclaveEventType.PLAINTEXT_OUTPUT_PUBLISHED, ); const parsed = hexToUint8Array(plaintextEvent.data.plaintextOutput); diff --git a/tests/integration/base.sh b/tests/integration/base.sh index 7bc1e979c8..1a9fb9bcd9 100755 --- a/tests/integration/base.sh +++ b/tests/integration/base.sh @@ -24,6 +24,8 @@ enclave_wallet_set ag "$PRIVATE_KEY_AG" enclave_wallet_set cn1 "$PRIVATE_KEY_CN1" enclave_wallet_set cn2 "$PRIVATE_KEY_CN2" enclave_wallet_set cn3 "$PRIVATE_KEY_CN3" +enclave_wallet_set cn4 "$PRIVATE_KEY_CN4" +enclave_wallet_set cn5 "$PRIVATE_KEY_CN5" # start swarm enclave_nodes_up @@ -43,23 +45,42 @@ pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_2 --network localho heading "Add ciphernode $CIPHERNODE_ADDRESS_3" pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_3 --network localhost +heading "Add ciphernode $CIPHERNODE_ADDRESS_4" +pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_4 --network localhost + +heading "Add ciphernode $CIPHERNODE_ADDRESS_5" +pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_5 --network localhost + heading "Request Committee" -ENCODED_PARAMS=0x$($SCRIPT_DIR/lib/pack_e3_params.sh --moduli 0x3FFFFFFF000001 --degree 2048 --plaintext-modulus 1032193) +ENCODED_PARAMS=0x$($SCRIPT_DIR/lib/pack_e3_params.sh \ + --moduli 0x800000022a0001 \ + --moduli 0x800000021a0001 \ + --moduli 0x80000002120001 \ + --moduli 0x80000001f60001 \ + --degree 8192 \ + --plaintext-modulus 1032193) sleep 4 -pnpm committee:new --network localhost --duration 4 --e3-params "$ENCODED_PARAMS" +pnpm committee:new \ + --network localhost \ + --duration 4 \ + --e3-params "$ENCODED_PARAMS" \ + --threshold-quorum 2 \ + --threshold-total 5 waiton "$SCRIPT_DIR/output/pubkey.bin" + PUBLIC_KEY=$(xxd -p -c 10000000 "$SCRIPT_DIR/output/pubkey.bin") heading "Mock encrypted plaintext" -$SCRIPT_DIR/lib/fake_encrypt.sh --input "$SCRIPT_DIR/output/pubkey.bin" --output "$SCRIPT_DIR/output/output.bin" --plaintext $PLAINTEXT +$SCRIPT_DIR/lib/fake_encrypt.sh --input "$SCRIPT_DIR/output/pubkey.bin" --output "$SCRIPT_DIR/output/output.bin" --plaintext $PLAINTEXT --params "$ENCODED_PARAMS" heading "Mock activate e3-id" -# NOTE: using -s to avoid key spamming output -pnpm -s e3:activate --e3-id 0 --public-key "0x$PUBLIC_KEY" --network localhost +PUBLIC_KEY_FILE=/tmp/enclave-public-key.txt +echo "0x${PUBLIC_KEY}" > $PUBLIC_KEY_FILE +pnpm -s e3:activate --e3-id 0 --network localhost --public-key-file $PUBLIC_KEY_FILE heading "Mock publish input e3-id" pnpm e3:publishInput --network localhost --e3-id 0 --data 0x12345678 @@ -73,10 +94,11 @@ pnpm e3:publishCiphertext --e3-id 0 --network localhost --data-file "$SCRIPT_DIR waiton "$SCRIPT_DIR/output/plaintext.txt" -ACTUAL=$(cat $SCRIPT_DIR/output/plaintext.txt) - +ACTUAL=$(cut -d',' -f1,2 $SCRIPT_DIR/output/plaintext.txt) # Assume plaintext is shorter +echo "ACTUAL:" +echo $ACTUAL if [[ "$ACTUAL" != "$PLAINTEXT"* ]]; then echo "Invalid plaintext decrypted: actual='$ACTUAL' expected='$PLAINTEXT'" @@ -103,4 +125,3 @@ echo -e "\033[32m \033[0m" gracefull_shutdown - diff --git a/tests/integration/enclave.config.yaml b/tests/integration/enclave.config.yaml index fcb16eae42..81daff0ca0 100644 --- a/tests/integration/enclave.config.yaml +++ b/tests/integration/enclave.config.yaml @@ -41,11 +41,21 @@ nodes: quic_port: 9203 autonetkey: true autopassword: true - ag: + cn4: address: "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" quic_port: 9204 autonetkey: true autopassword: true + cn5: + address: "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" + quic_port: 9205 + autonetkey: true + autopassword: true + ag: + address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + quic_port: 9206 + autonetkey: true + autopassword: true role: type: aggregator pubkey_write_path: "./output/pubkey.bin" diff --git a/tests/integration/fns.sh b/tests/integration/fns.sh index 2251c9f200..a34448772d 100644 --- a/tests/integration/fns.sh +++ b/tests/integration/fns.sh @@ -19,18 +19,17 @@ PRIVATE_KEY_AG="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff PRIVATE_KEY_CN1="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" PRIVATE_KEY_CN2="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" PRIVATE_KEY_CN3="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" -NETWORK_PRIVATE_KEY_AG="0x51a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" +PRIVATE_KEY_CN4="0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a" +PRIVATE_KEY_CN5="0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba" CIPHERNODE_SECRET="We are the music makers and we are the dreamers of the dreams." # These are random addresses for now -CIPHERNODE_ADDRESS_1="0x90F79bf6EB2c4f870365E785982E1f101E93b906" -CIPHERNODE_ADDRESS_2="0x70997970C51812dc3A010C7d01b50e0d17dc79C8" -CIPHERNODE_ADDRESS_3="0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" +CIPHERNODE_ADDRESS_1="0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +CIPHERNODE_ADDRESS_2="0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" +CIPHERNODE_ADDRESS_3="0x90F79bf6EB2c4f870365E785982E1f101E93b906" +CIPHERNODE_ADDRESS_4="0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" +CIPHERNODE_ADDRESS_5="0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" -# These are the network private keys for the ciphernodes -NETWORK_PRIVATE_KEY_1="0x11a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" -NETWORK_PRIVATE_KEY_2="0x21a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" -NETWORK_PRIVATE_KEY_3="0x31a1e500a548b70d88184a1e042900c0ed6c57f8710bcc35dc8c85fa33d3f580" if command -v enclave >/dev/null 2>&1; then ENCLAVE_BIN="enclave" @@ -119,9 +118,14 @@ enclave_start() { enclave_nodes_up() { $ENCLAVE_BIN nodes up -v \ - --config "$SCRIPT_DIR/enclave.config.yaml" & + --config "$SCRIPT_DIR/enclave.config.yaml" --experimental-trbfv & } +# enclave_nodes_up() { +# $ENCLAVE_BIN nodes up -v \ +# --config "$SCRIPT_DIR/enclave.config.yaml" & +# } + enclave_nodes_down() { $ENCLAVE_BIN nodes down } @@ -205,5 +209,3 @@ kill_em_all trap 'cleanup $?' ERR INT TERM $SCRIPT_DIR/lib/clean_folders.sh "$SCRIPT_DIR" - - diff --git a/tests/integration/persist.sh b/tests/integration/persist.sh index 480805f3f8..16187d3fc6 100755 --- a/tests/integration/persist.sh +++ b/tests/integration/persist.sh @@ -24,6 +24,8 @@ enclave_wallet_set ag "$PRIVATE_KEY_AG" enclave_wallet_set cn1 "$PRIVATE_KEY_CN1" enclave_wallet_set cn2 "$PRIVATE_KEY_CN2" enclave_wallet_set cn3 "$PRIVATE_KEY_CN3" +enclave_wallet_set cn4 "$PRIVATE_KEY_CN4" +enclave_wallet_set cn5 "$PRIVATE_KEY_CN5" # start swarm enclave_nodes_up @@ -39,11 +41,28 @@ pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_2 --network localho heading "Add ciphernode $CIPHERNODE_ADDRESS_3" pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_3 --network localhost -heading "Request Committee" +heading "Add ciphernode $CIPHERNODE_ADDRESS_4" +pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_4 --network localhost + +heading "Add ciphernode $CIPHERNODE_ADDRESS_5" +pnpm ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_5 --network localhost -ENCODED_PARAMS=0x$($SCRIPT_DIR/lib/pack_e3_params.sh --moduli 0x3FFFFFFF000001 --degree 2048 --plaintext-modulus 1032193) +heading "Request Committee" -pnpm committee:new --network localhost --duration 4 --e3-params "$ENCODED_PARAMS" +ENCODED_PARAMS=0x$($SCRIPT_DIR/lib/pack_e3_params.sh \ + --moduli 0x800000022a0001 \ + --moduli 0x800000021a0001 \ + --moduli 0x80000002120001 \ + --moduli 0x80000001f60001 \ + --degree 8192 \ + --plaintext-modulus 1032193) + +pnpm committee:new \ + --network localhost \ + --duration 4 \ + --e3-params "$ENCODED_PARAMS" \ + --threshold-quorum 2 \ + --threshold-total 5 waiton "$SCRIPT_DIR/output/pubkey.bin" PUBLIC_KEY=$(xxd -p -c 10000000 "$SCRIPT_DIR/output/pubkey.bin") @@ -60,11 +79,13 @@ enclave_nodes_start ag sleep 2 heading "Mock encrypted plaintext" -$SCRIPT_DIR/lib/fake_encrypt.sh --input "$SCRIPT_DIR/output/pubkey.bin" --output "$SCRIPT_DIR/output/output.bin" --plaintext $PLAINTEXT +$SCRIPT_DIR/lib/fake_encrypt.sh --input "$SCRIPT_DIR/output/pubkey.bin" --output "$SCRIPT_DIR/output/output.bin" --plaintext $PLAINTEXT --params "$ENCODED_PARAMS" heading "Mock activate e3-id" -# NOTE using -s to avoid key spaming the output -pnpm -s e3:activate --e3-id 0 --public-key "0x$PUBLIC_KEY" --network localhost + +PUBLIC_KEY_FILE=/tmp/enclave-public-key.txt +echo "0x${PUBLIC_KEY}" > $PUBLIC_KEY_FILE +pnpm -s e3:activate --e3-id 0 --network localhost --public-key-file $PUBLIC_KEY_FILE heading "Mock publish input e3-id" pnpm e3:publishInput --network localhost --e3-id 0 --data 0x12345678 @@ -78,8 +99,7 @@ pnpm e3:publishCiphertext --e3-id 0 --network localhost --data-file "$SCRIPT_DIR waiton "$SCRIPT_DIR/output/plaintext.txt" -ACTUAL=$(cat $SCRIPT_DIR/output/plaintext.txt) - +ACTUAL=$(cut -d',' -f1,2 $SCRIPT_DIR/output/plaintext.txt) # Assume plaintext is shorter @@ -109,4 +129,3 @@ echo -e "\033[32m gracefull_shutdown -