diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 3e9d2f46..91b7f76a 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -19,7 +19,10 @@ jobs: with: toolchain: 1.81.0 override: true - + + - name: Checking code format + run: cd ./packages/ciphernode && cargo fmt -- --check + - name: Run tests run: | cd ./packages/ciphernode/ diff --git a/packages/ciphernode/Cargo.lock b/packages/ciphernode/Cargo.lock index 028fdc22..f4b8a1d7 100644 --- a/packages/ciphernode/Cargo.lock +++ b/packages/ciphernode/Cargo.lock @@ -109,6 +109,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "aggregator" +version = "0.1.0" +dependencies = [ + "actix", + "anyhow", + "bincode", + "enclave-core", + "fhe 0.1.0", + "sortition", +] + [[package]] name = "ahash" version = "0.8.11" @@ -1358,18 +1370,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "base91" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eb5fbae7b5ee422f239444a3dca9bdf5ecb3abf3af1bf87c8097db3f7bc025" - [[package]] name = "bfv" version = "0.1.0" dependencies = [ "async-std", - "fhe", + "fhe 0.1.0-beta.7", "fhe-traits", "fhe-util", "rand", @@ -1778,6 +1784,13 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data" +version = "0.1.0" +dependencies = [ + "actix", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -2006,10 +2019,11 @@ dependencies = [ name = "enclave" version = "0.1.0" dependencies = [ - "async-std", - "fhe", - "fhe-traits", - "fhe-util", + "actix-rt", + "alloy", + "clap", + "enclave_node", + "tokio", ] [[package]] @@ -2017,52 +2031,42 @@ name = "enclave-core" version = "0.1.0" dependencies = [ "actix", - "actix-rt", "alloy", "alloy-primitives 0.6.4", "alloy-sol-types 0.6.4", "anyhow", - "async-std", - "base64 0.22.1", - "base91", "bincode", "bs58", - "fhe", - "fhe-traits", - "fhe-util", "futures-util", - "libp2p", - "p2p", - "rand", - "rand_chacha", - "secp256k1", "serde", "sha2", - "sortition", - "tokio", ] [[package]] name = "enclave_node" version = "0.1.0" dependencies = [ + "actix", "actix-rt", + "aggregator", "alloy", "alloy-primitives 0.6.4", - "async-std", - "base64 0.22.1", - "base91", + "anyhow", "bfv", "bincode", "clap", + "data", "enclave-core", - "fhe", - "fhe-traits", - "fhe-util", + "evm", + "fhe 0.1.0", + "keyshare", + "logger", "p2p", "rand", "rand_chacha", + "router", "sortition", + "test-helpers", "tokio", ] @@ -2139,13 +2143,16 @@ dependencies = [ ] [[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +name = "evm" +version = "0.1.0" dependencies = [ - "indenter", - "once_cell", + "actix", + "alloy", + "alloy-primitives 0.6.4", + "anyhow", + "enclave-core", + "futures-util", + "sortition", ] [[package]] @@ -2207,6 +2214,21 @@ dependencies = [ "zeroize_derive", ] +[[package]] +name = "fhe" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode", + "enclave-core", + "fhe 0.1.0-beta.7", + "fhe-traits", + "fhe-util", + "rand", + "rand_chacha", + "serde", +] + [[package]] name = "fhe-math" version = "0.1.0-beta.7" @@ -2922,12 +2944,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" version = "2.3.0" @@ -3072,6 +3088,17 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "keyshare" +version = "0.1.0" +dependencies = [ + "actix", + "anyhow", + "data", + "enclave-core", + "fhe 0.1.0", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -3696,6 +3723,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "logger" +version = "0.1.0" +dependencies = [ + "actix", + "base64 0.22.1", + "enclave-core", +] + [[package]] name = "lru" version = "0.12.4" @@ -4183,12 +4219,11 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" name = "p2p" version = "0.1.0" dependencies = [ + "actix", + "anyhow", "async-std", "async-trait", - "bfv", - "fhe", - "fhe-traits", - "fhe-util", + "enclave-core", "futures", "libp2p", "tokio", @@ -4935,6 +4970,19 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "router" +version = "0.1.0" +dependencies = [ + "actix", + "aggregator", + "data", + "enclave-core", + "fhe 0.1.0", + "keyshare", + "sortition", +] + [[package]] name = "rtnetlink" version = "0.10.1" @@ -5168,24 +5216,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.11.1" @@ -5416,11 +5446,9 @@ dependencies = [ name = "sortition" version = "0.1.0" dependencies = [ + "actix", "alloy", - "alloy-primitives 0.6.4", - "alloy-sol-types 0.6.4", - "eyre", - "futures-util", + "enclave-core", "num", "rand", ] @@ -5599,6 +5627,54 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "test-helpers" +version = "0.1.0" +dependencies = [ + "actix", + "bincode", + "clap", + "enclave-core", + "fhe 0.1.0", + "fhe 0.1.0-beta.7", + "fhe-traits", + "rand", + "rand_chacha", +] + +[[package]] +name = "tests" +version = "0.1.0" +dependencies = [ + "actix", + "actix-rt", + "aggregator", + "alloy", + "alloy-primitives 0.6.4", + "anyhow", + "async-std", + "base64 0.22.1", + "bfv", + "bincode", + "clap", + "data", + "enclave-core", + "evm", + "fhe 0.1.0", + "fhe 0.1.0-beta.7", + "fhe-traits", + "fhe-util", + "keyshare", + "logger", + "p2p", + "rand", + "rand_chacha", + "router", + "sortition", + "test-helpers", + "tokio", +] + [[package]] name = "thiserror" version = "1.0.63" diff --git a/packages/ciphernode/Cargo.toml b/packages/ciphernode/Cargo.toml index 5a82643f..a2884404 100644 --- a/packages/ciphernode/Cargo.toml +++ b/packages/ciphernode/Cargo.toml @@ -1,2 +1,63 @@ [workspace] -members = ["core", "enclave", "enclave_node", "p2p", "bfv"] +members = [ + "core", + "enclave", + "enclave_node", + "p2p", + "bfv", + "data", + "evm", + "fhe", + "router", + "keyshare", + "aggregator", + "test_helpers", + "logger", + "tests", +] + +[workspace.dependencies] +actix = "0.13.5" +actix-rt = "2.10.0" +alloy = { version = "0.3.3", features = ["full"] } +alloy-primitives = { version = "0.6", default-features = false, features = [ + "rlp", + "serde", + "std", +] } +alloy-sol-types = { version = "0.6" } +anyhow = "1.0.86" +async-std = { version = "1.12", features = ["attributes"] } +async-trait = "0.1" +bincode = "1.3.3" +bs58 = "0.5.1" +base64 = "0.22.1" +clap = { version = "4.5.17", features = ["derive"] } +enclave_node = { path = "../enclave_node" } +fhe_rs = { package = "fhe", git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +futures = "0.3.30" +futures-util = "0.3" +num = "0.4.3" +rand_chacha = "0.3.1" +rand = "0.8.5" +serde = { version = "1.0.208", features = ["derive"] } +sha2 = "0.10.8" +tokio = { version = "1.38", features = ["full"] } +tracing = "0.1.37" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p = { version = "0.53.2", features = [ + "async-std", + "identify", + "macros", + "noise", + "ping", + "rendezvous", + "tcp", + "tokio", + "yamux", + "mdns", + "gossipsub", + "quic", +] } diff --git a/packages/ciphernode/README.md b/packages/ciphernode/README.md new file mode 100644 index 00000000..29e2007b --- /dev/null +++ b/packages/ciphernode/README.md @@ -0,0 +1,51 @@ +# E3 Requested + +```mermaid +sequenceDiagram + autonumber + participant EVM as EVM + participant CS as CiphernodeSelector + participant E3 as E3RequestRouter + participant KS as Keyshare + participant PKA as PublicKeyAggregator + participant S as Sortition + + EVM--)CS: E3Requested + CS->>+S: has node? + S--)-CS: yes + CS--)E3: CiphernodeSelected + E3->>PKA: Create new PublicKeyAggreator for this e3_id + E3->>KS: Create new Keyshare for this e3_id + loop + KS--)PKA: KeyshareCreated + PKA->>+S: has node? + S--)-PKA: yes + end + PKA--)EVM: PublicKeyAggregated + PKA--)PKA: Stop +``` + +# Ciphertext output published + +```mermaid +sequenceDiagram + autonumber + participant EVM as EVM + participant E3 as E3RequestRouter + participant KS as Keyshare + participant PTA as PlaintextAggregator + participant S as Sortition + + EVM--)E3: CiphertextOutputPublished + E3->>PTA: Create new PlaintextAggreator for this e3_id + loop + KS--)PTA: DecryptionShareCreated + PTA->>+S: has node? + S--)-PTA: yes + end + PTA--)EVM: PlaintextAggregated + PTA--)+KS: PlaintextAggregated + PTA--)PTA: Stop + KS--)-KS: Stop +``` + diff --git a/packages/ciphernode/aggregator/Cargo.toml b/packages/ciphernode/aggregator/Cargo.toml new file mode 100644 index 00000000..695e6bf6 --- /dev/null +++ b/packages/ciphernode/aggregator/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "aggregator" +version = "0.1.0" +edition = "2021" + +[dependencies] +actix = { workspace = true } +anyhow = { workspace = true } +bincode = { workspace = true } +enclave-core = { path = "../core" } +fhe = { path = "../fhe" } +sortition = { path = "../sortition" } diff --git a/packages/ciphernode/aggregator/src/lib.rs b/packages/ciphernode/aggregator/src/lib.rs new file mode 100644 index 00000000..db42567c --- /dev/null +++ b/packages/ciphernode/aggregator/src/lib.rs @@ -0,0 +1,5 @@ +mod plaintext_aggregator; +mod publickey_aggregator; + +pub use plaintext_aggregator::*; +pub use publickey_aggregator::*; diff --git a/packages/ciphernode/core/src/plaintext_aggregator.rs b/packages/ciphernode/aggregator/src/plaintext_aggregator.rs similarity index 82% rename from packages/ciphernode/core/src/plaintext_aggregator.rs rename to packages/ciphernode/aggregator/src/plaintext_aggregator.rs index 3d78fb0b..99231da3 100644 --- a/packages/ciphernode/core/src/plaintext_aggregator.rs +++ b/packages/ciphernode/aggregator/src/plaintext_aggregator.rs @@ -1,10 +1,11 @@ -use std::sync::Arc; - -use crate::{ - ordered_set::OrderedSet, ActorFactory, DecryptionshareCreated, E3id, EnclaveEvent, EventBus, Fhe, GetAggregatePlaintext, GetHasNode, PlaintextAggregated, Seed, Sortition -}; use actix::prelude::*; use anyhow::Result; +use enclave_core::{ + DecryptionshareCreated, E3id, EnclaveEvent, EventBus, OrderedSet, PlaintextAggregated, Seed, +}; +use fhe::{Fhe, GetAggregatePlaintext}; +use sortition::{GetHasNode, Sortition}; +use std::sync::Arc; #[derive(Debug, Clone)] pub enum PlaintextAggregatorState { @@ -172,7 +173,7 @@ impl Handler for PlaintextAggregator { fn handle(&mut self, msg: ComputeAggregate, _: &mut Self::Context) -> Self::Result { let decrypted_output = self.fhe.get_aggregate_plaintext(GetAggregatePlaintext { decryptions: msg.shares.clone(), - ciphertext_output: msg.ciphertext_output + ciphertext_output: msg.ciphertext_output, })?; // Update the local state @@ -189,34 +190,3 @@ impl Handler for PlaintextAggregator { Ok(()) } } - -pub struct PlaintextAggregatorFactory; -impl PlaintextAggregatorFactory { - pub fn create(bus: Addr, sortition: Addr) -> ActorFactory { - Box::new(move |ctx, evt| { - // Save plaintext aggregator - let EnclaveEvent::CiphertextOutputPublished { data, .. } = evt else { - return; - }; - let Some(ref fhe) = ctx.fhe else { - return; - }; - let Some(ref meta) = ctx.meta else { - return; - }; - - ctx.plaintext = Some( - PlaintextAggregator::new( - fhe.clone(), - bus.clone(), - sortition.clone(), - data.e3_id, - meta.threshold_m, - meta.seed, - data.ciphertext_output, - ) - .start(), - ); - }) - } -} diff --git a/packages/ciphernode/core/src/publickey_aggregator.rs b/packages/ciphernode/aggregator/src/publickey_aggregator.rs similarity index 82% rename from packages/ciphernode/core/src/publickey_aggregator.rs rename to packages/ciphernode/aggregator/src/publickey_aggregator.rs index ba17b34c..8f1d13b4 100644 --- a/packages/ciphernode/core/src/publickey_aggregator.rs +++ b/packages/ciphernode/aggregator/src/publickey_aggregator.rs @@ -1,14 +1,11 @@ -use std::sync::Arc; - -use crate::{ - eventbus::EventBus, - events::{E3id, EnclaveEvent, KeyshareCreated, PublicKeyAggregated}, - fhe::{Fhe, GetAggregatePublicKey}, - ordered_set::OrderedSet, - ActorFactory, GetHasNode, Seed, Sortition, -}; use actix::prelude::*; use anyhow::Result; +use enclave_core::{ + E3id, EnclaveEvent, EventBus, KeyshareCreated, OrderedSet, PublicKeyAggregated, Seed, +}; +use fhe::{Fhe, GetAggregatePublicKey}; +use sortition::{GetHasNode, Sortition}; +use std::sync::Arc; #[derive(Debug, Clone)] pub enum PublicKeyAggregatorState { @@ -193,36 +190,3 @@ impl Handler for PublicKeyAggregator { Ok(()) } } - -pub struct PublicKeyAggregatorFactory; -impl PublicKeyAggregatorFactory { - pub fn create(bus: Addr, sortition: Addr) -> ActorFactory { - Box::new(move |ctx, evt| { - // Saving the publickey aggregator with deps on E3Requested - let EnclaveEvent::E3Requested { data, .. } = evt else { - return; - }; - - let Some(ref fhe) = ctx.fhe else { - println!("fhe was not on ctx"); - return; - }; - let Some(ref meta) = ctx.meta else { - println!("meta was not on ctx"); - return; - }; - - ctx.publickey = Some( - PublicKeyAggregator::new( - fhe.clone(), - bus.clone(), - sortition.clone(), - data.e3_id, - meta.threshold_m, - meta.seed, - ) - .start(), - ); - }) - } -} diff --git a/packages/ciphernode/bfv/src/lib.rs b/packages/ciphernode/bfv/src/lib.rs index d8e55781..b3c76b19 100644 --- a/packages/ciphernode/bfv/src/lib.rs +++ b/packages/ciphernode/bfv/src/lib.rs @@ -4,14 +4,14 @@ mod util; -use std::{sync::Arc}; use fhe::{ bfv::{BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, SecretKey}, mbfv::{AggregateIter, CommonRandomPoly, DecryptionShare, PublicKeyShare}, }; -use fhe_traits::{FheDecoder, Serialize as FheSerialize, Deserialize, DeserializeParametrized}; -use rand::{Rng, rngs::OsRng, thread_rng}; -use util::timeit::{timeit}; +use fhe_traits::{Deserialize, DeserializeParametrized, FheDecoder, Serialize as FheSerialize}; +use rand::{rngs::OsRng, thread_rng, Rng}; +use std::sync::Arc; +use util::timeit::timeit; pub struct EnclaveBFV { pub pk_share: PublicKeyShare, @@ -22,52 +22,63 @@ pub struct EnclaveBFV { impl EnclaveBFV { pub fn new(degree: usize, plaintext_modulus: u64, moduli: Vec) -> Self { - // let degree = 4096; - // let plaintext_modulus: u64 = 4096; - // let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + // let degree = 4096; + // let plaintext_modulus: u64 = 4096; + // let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - // Generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc().unwrap() - ); + // Generate the BFV parameters structure. + let params = timeit!( + "Parameters generation", + BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc() + .unwrap() + ); - let crp = CommonRandomPoly::new(¶ms, &mut thread_rng()).unwrap(); - //TODO: save encrypted sk_share to disk? + let crp = CommonRandomPoly::new(¶ms, &mut thread_rng()).unwrap(); + //TODO: save encrypted sk_share to disk? let sk_share = SecretKey::random(¶ms, &mut OsRng); let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng()).unwrap(); - Self { pk_share, sk_share, params, crp } + Self { + pk_share, + sk_share, + params, + crp, + } } pub fn serialize_pk(&mut self) -> Vec { - self.pk_share.to_bytes() + self.pk_share.to_bytes() } - pub fn deserialize_pk(&mut self, bytes: Vec, par_bytes: Vec, crp_bytes: Vec) -> PublicKeyShare { - let params = Arc::new(BfvParameters::try_deserialize(&par_bytes).unwrap()); - let crp = CommonRandomPoly::deserialize(&crp_bytes, ¶ms).unwrap(); - PublicKeyShare::deserialize(&bytes, ¶ms, crp.clone()).unwrap() + pub fn deserialize_pk( + &mut self, + bytes: Vec, + par_bytes: Vec, + crp_bytes: Vec, + ) -> PublicKeyShare { + let params = Arc::new(BfvParameters::try_deserialize(&par_bytes).unwrap()); + let crp = CommonRandomPoly::deserialize(&crp_bytes, ¶ms).unwrap(); + PublicKeyShare::deserialize(&bytes, ¶ms, crp.clone()).unwrap() } pub fn serialize_crp(&mut self) -> Vec { - self.crp.to_bytes() + self.crp.to_bytes() } pub fn deserialize_crp(&mut self, bytes: Vec, par_bytes: Vec) -> CommonRandomPoly { - let params = Arc::new(BfvParameters::try_deserialize(&par_bytes).unwrap()); - CommonRandomPoly::deserialize(&bytes, ¶ms).unwrap() + let params = Arc::new(BfvParameters::try_deserialize(&par_bytes).unwrap()); + CommonRandomPoly::deserialize(&bytes, ¶ms).unwrap() } pub fn serialize_params(&mut self) -> Vec { - self.params.to_bytes() + self.params.to_bytes() } pub fn deserialize_params(&mut self, par_bytes: Vec) -> Arc { - Arc::new(BfvParameters::try_deserialize(&par_bytes).unwrap()) + Arc::new(BfvParameters::try_deserialize(&par_bytes).unwrap()) } -} \ No newline at end of file +} diff --git a/packages/ciphernode/bfv/src/util.rs b/packages/ciphernode/bfv/src/util.rs index 8e3d167f..2b5c2e48 100644 --- a/packages/ciphernode/bfv/src/util.rs +++ b/packages/ciphernode/bfv/src/util.rs @@ -126,4 +126,4 @@ pub fn encode_database( } #[allow(dead_code)] -fn main() {} \ No newline at end of file +fn main() {} diff --git a/packages/ciphernode/core/Cargo.toml b/packages/ciphernode/core/Cargo.toml index 8a8836f4..ede42cc4 100644 --- a/packages/ciphernode/core/Cargo.toml +++ b/packages/ciphernode/core/Cargo.toml @@ -9,27 +9,13 @@ repository = "https://github.com/gnosisguild/enclave/packages/ciphernode" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -p2p = { path = "../p2p" } -sortition = { path = "../sortition" } -async-std = "1.12.0" -libp2p = "0.53.2" -fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -actix = "0.13.5" -actix-rt = "2.10.0" -anyhow = "1.0.86" -rand = "0.8.5" -rand_chacha = "0.3.1" -secp256k1 = "0.29.0" -tokio = { version = "1.39.3", features = ["full"] } -sha2 = "0.10.8" -bs58 = "0.5.1" -serde = { version = "1.0.208", features = ["derive"] } -bincode = "1.3.3" -futures-util = "0.3" -alloy = { version = "0.3.3", features = ["full"] } -alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } -alloy-sol-types = { version = "0.6" } -base64 = "0.22.1" -base91 = "0.1.0" +actix = { workspace = true } +sha2 = { workspace = true } +bs58 = { workspace = true } +serde = { workspace = true } +bincode = { workspace = true } +futures-util = { workspace = true } +alloy = { workspace = true } +alloy-primitives = { workspace = true } +alloy-sol-types = { workspace = true } +anyhow = { workspace = true } diff --git a/packages/ciphernode/core/src/eventbus.rs b/packages/ciphernode/core/src/eventbus.rs index 6171fa07..6ee89ad5 100644 --- a/packages/ciphernode/core/src/eventbus.rs +++ b/packages/ciphernode/core/src/eventbus.rs @@ -1,7 +1,8 @@ -use crate::events::{EnclaveEvent, EventId}; use actix::prelude::*; use std::collections::{HashMap, HashSet}; +use super::events::{EnclaveEvent, EventId}; + #[derive(Message, Debug)] #[rtype(result = "()")] pub struct Subscribe { diff --git a/packages/ciphernode/core/src/events.rs b/packages/ciphernode/core/src/events.rs index 6bb33b9d..22b70648 100644 --- a/packages/ciphernode/core/src/events.rs +++ b/packages/ciphernode/core/src/events.rs @@ -427,56 +427,3 @@ impl EnclaveEvent { extract_enclave_event_name(&s).to_string() } } - -#[cfg(test)] -mod tests { - use super::EnclaveEvent; - use crate::{events::extract_enclave_event_name, E3id, KeyshareCreated}; - use alloy_primitives::address; - use fhe::{ - bfv::{BfvParametersBuilder, SecretKey}, - mbfv::{CommonRandomPoly, PublicKeyShare}, - }; - use fhe_traits::Serialize; - use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; - use std::error::Error; - - #[test] - fn test_extract_enum_name() { - assert_eq!( - extract_enclave_event_name("KeyshareCreated(KeyshareCreated { pubkey: [] })"), - "KeyshareCreated" - ); - assert_eq!( - extract_enclave_event_name("CommitteeSelected(SomeStruct { t: 8 })"), - "CommitteeSelected" - ); - } - - #[test] - fn test_deserialization() -> Result<(), Box> { - let moduli = &vec![0x3FFFFFFF000001]; - let degree = 2048usize; - let plaintext_modulus = 1032193u64; - let mut rng = ChaCha20Rng::from_entropy(); - let params = BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc()?; - let crp = CommonRandomPoly::new(¶ms, &mut rng)?; - let sk_share = { SecretKey::random(¶ms, &mut rng) }; - let pk_share = { PublicKeyShare::new(&sk_share, crp.clone(), &mut rng)? }; - let pubkey = pk_share.to_bytes(); - let kse = EnclaveEvent::from(KeyshareCreated { - e3_id: E3id::from(1001), - pubkey, - node: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").to_string(), - }); - let kse_bytes = kse.to_bytes()?; - let _ = EnclaveEvent::from_bytes(&kse_bytes.clone()); - // deserialization occurred without panic! - Ok(()) - } -} diff --git a/packages/ciphernode/core/src/lib.rs b/packages/ciphernode/core/src/lib.rs index 89feb3d8..154d1461 100644 --- a/packages/ciphernode/core/src/lib.rs +++ b/packages/ciphernode/core/src/lib.rs @@ -2,401 +2,10 @@ #![crate_type = "lib"] // #![warn(missing_docs, unused_imports)] -mod cipernode_selector; -mod committee_meta; -mod data; -mod e3_request; mod eventbus; -pub mod events; -mod evm_ciphernode_registry; -mod evm_enclave; -mod evm_listener; -mod evm_manager; -mod evm_caller; -mod evm_contracts; -mod fhe; -mod keyshare; -mod logger; -mod main_aggregator; -mod main_ciphernode; +mod events; mod ordered_set; -mod p2p; -mod plaintext_aggregator; -mod plaintext_writer; -mod public_key_writer; -mod publickey_aggregator; -mod sortition; -mod utils; -// TODO: this is too permissive -pub use actix::prelude::*; -pub use cipernode_selector::*; -pub use committee_meta::*; -pub use data::*; -pub use e3_request::*; pub use eventbus::*; pub use events::*; -pub use fhe::*; -pub use keyshare::*; -pub use logger::*; -pub use main_aggregator::*; -pub use main_ciphernode::*; -pub use p2p::*; -pub use plaintext_aggregator::*; -pub use plaintext_writer::*; -pub use public_key_writer::*; -pub use publickey_aggregator::*; -pub use sortition::*; -pub use utils::*; - -// TODO: move these out to a test folder -#[cfg(test)] -mod tests { - use crate::{ - cipernode_selector::CiphernodeSelector, - data::Data, - eventbus::{EventBus, GetHistory}, - events::{E3Requested, E3id, EnclaveEvent, KeyshareCreated, PublicKeyAggregated}, - p2p::P2p, - utils::{setup_crp_params, ParamsWithCrp}, - CiphernodeAdded, CiphernodeSelected, CiphertextOutputPublished, CommitteeMetaFactory, - DecryptionshareCreated, E3RequestManager, FheFactory, KeyshareFactory, PlaintextAggregated, - PlaintextAggregatorFactory, PublicKeyAggregatorFactory, ResetHistory, Seed, SharedRng, - SimpleLogger, Sortition, - }; - use actix::prelude::*; - use alloy::primitives::Address; - use anyhow::*; - use fhe::{ - bfv::{BfvParameters, Encoding, Plaintext, PublicKey, SecretKey}, - mbfv::{AggregateIter, CommonRandomPoly, DecryptionShare, PublicKeyShare}, - }; - use fhe_traits::{FheEncoder, FheEncrypter, Serialize}; - use rand::Rng; - use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; - use std::{sync::Arc, time::Duration}; - use tokio::sync::Mutex; - use tokio::{sync::mpsc::channel, time::sleep}; - - // Simulating a local node - async fn setup_local_ciphernode( - bus: Addr, - rng: SharedRng, - logging: bool, - addr: &str, - ) { - // create data actor for saving data - let data = Data::new(logging).start(); // TODO: Use a sled backed Data Actor - - // create ciphernode actor for managing ciphernode flow - let sortition = Sortition::attach(bus.clone()); - CiphernodeSelector::attach(bus.clone(), sortition.clone(), addr); - - E3RequestManager::builder(bus.clone()) - .add_hook(CommitteeMetaFactory::create()) - .add_hook(FheFactory::create(rng)) - .add_hook(PublicKeyAggregatorFactory::create( - bus.clone(), - sortition.clone(), - )) - .add_hook(PlaintextAggregatorFactory::create( - bus.clone(), - sortition.clone(), - )) - .add_hook(KeyshareFactory::create(bus.clone(), data.clone(), addr)) - .build(); - - SimpleLogger::attach(addr, bus.clone()); - } - - fn generate_pk_share( - params: Arc, - crp: CommonRandomPoly, - rng: SharedRng, - ) -> Result<(PublicKeyShare, SecretKey)> { - let sk = SecretKey::random(¶ms, &mut *rng.lock().unwrap()); - let pk = PublicKeyShare::new(&sk, crp.clone(), &mut *rng.lock().unwrap())?; - Ok((pk, sk)) - } - - #[actix::test] - async fn test_public_key_aggregation_and_decryption() -> Result<()> { - // Setup EventBus - let bus = EventBus::new(true).start(); - let rng = Arc::new(std::sync::Mutex::new(ChaCha20Rng::seed_from_u64(42))); - let seed = Seed(ChaCha20Rng::seed_from_u64(123).get_seed()); - - let eth_addrs: Vec = (0..3) - .map(|_| Address::from_slice(&rand::thread_rng().gen::<[u8; 20]>()).to_string()) - .collect(); - - setup_local_ciphernode(bus.clone(), rng.clone(), true, ð_addrs[0]).await; - setup_local_ciphernode(bus.clone(), rng.clone(), true, ð_addrs[1]).await; - setup_local_ciphernode(bus.clone(), rng.clone(), true, ð_addrs[2]).await; - - let e3_id = E3id::new("1234"); - - let ParamsWithCrp { - crp_bytes, params, .. - } = setup_crp_params( - &[0x3FFFFFFF000001], - 2048, - 1032193, - Arc::new(std::sync::Mutex::new(ChaCha20Rng::from_seed( - seed.clone().into(), - ))), - ); - - let regevt_1 = EnclaveEvent::from(CiphernodeAdded { - address: eth_addrs[0].clone(), - index: 0, - num_nodes: 1, - }); - - bus.send(regevt_1.clone()).await?; - - let regevt_2 = EnclaveEvent::from(CiphernodeAdded { - address: eth_addrs[1].clone(), - index: 1, - num_nodes: 2, - }); - - bus.send(regevt_2.clone()).await?; - - let regevt_3 = EnclaveEvent::from(CiphernodeAdded { - address: eth_addrs[2].clone(), - index: 2, - num_nodes: 3, - }); - - bus.send(regevt_3.clone()).await?; - - let event = EnclaveEvent::from(E3Requested { - e3_id: e3_id.clone(), - threshold_m: 3, - seed: seed.clone(), - params: params.to_bytes(), - }); - // Send the computation requested event - bus.send(event.clone()).await?; - - // Test that we cannot send the same event twice - bus.send(event).await?; - - let history = bus.send(GetHistory).await?; - - let rng_test = Arc::new(std::sync::Mutex::new(ChaCha20Rng::seed_from_u64(42))); - - let crpoly = CommonRandomPoly::deserialize(&crp_bytes.clone(), ¶ms)?; - - let (p1, sk1) = generate_pk_share(params.clone(), crpoly.clone(), rng_test.clone())?; - let (p2, sk2) = generate_pk_share(params.clone(), crpoly.clone(), rng_test.clone())?; - let (p3, sk3) = generate_pk_share(params.clone(), crpoly.clone(), rng_test.clone())?; - - let pubkey: PublicKey = vec![p1.clone(), p2.clone(), p3.clone()] - .into_iter() - .aggregate()?; - - assert_eq!(history.len(), 9); - assert_eq!( - history, - vec![ - regevt_1, - regevt_2, - regevt_3, - EnclaveEvent::from(E3Requested { - e3_id: e3_id.clone(), - threshold_m: 3, - seed: seed.clone(), - params: params.to_bytes() - }), - EnclaveEvent::from(CiphernodeSelected { - e3_id: e3_id.clone(), - threshold_m: 3, - }), - EnclaveEvent::from(KeyshareCreated { - pubkey: p1.to_bytes(), - e3_id: e3_id.clone(), - node: eth_addrs[0].clone() - }), - EnclaveEvent::from(KeyshareCreated { - pubkey: p2.to_bytes(), - e3_id: e3_id.clone(), - node: eth_addrs[1].clone() - }), - EnclaveEvent::from(KeyshareCreated { - pubkey: p3.to_bytes(), - e3_id: e3_id.clone(), - node: eth_addrs[2].clone() - }), - EnclaveEvent::from(PublicKeyAggregated { - pubkey: pubkey.to_bytes(), - e3_id: e3_id.clone() - }) - ] - ); - - // Aggregate decryption - bus.send(ResetHistory).await?; - fn pad_end(input: &[u64], pad: u64, total: usize) -> Vec { - let len = input.len(); - let mut cop = input.to_vec(); - cop.extend(std::iter::repeat(pad).take(total - len)); - cop - } - // 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 yes = 1234u64; - let no = 873827u64; - - let raw_plaintext = vec![yes, no]; - let padded = &pad_end(&raw_plaintext, 0, 2048); - let expected_raw_plaintext = bincode::serialize(&padded)?; - let pt = Plaintext::try_encode(&raw_plaintext, Encoding::poly(), ¶ms)?; - - let ciphertext = pubkey.try_encrypt(&pt, &mut ChaCha20Rng::seed_from_u64(42))?; - - let event = EnclaveEvent::from(CiphertextOutputPublished { - ciphertext_output: ciphertext.to_bytes(), - e3_id: e3_id.clone(), - }); - - let arc_ct = Arc::new(ciphertext); - - let ds1 = DecryptionShare::new(&sk1, &arc_ct, &mut *rng_test.lock().unwrap())?.to_bytes(); - let ds2 = DecryptionShare::new(&sk2, &arc_ct, &mut *rng_test.lock().unwrap())?.to_bytes(); - let ds3 = DecryptionShare::new(&sk3, &arc_ct, &mut *rng_test.lock().unwrap())?.to_bytes(); - - // let ds1 = sk1 - bus.send(event.clone()).await?; - - sleep(Duration::from_millis(1)).await; // need to push to next tick - let history = bus.send(GetHistory).await?; - - assert_eq!(history.len(), 5); - - assert_eq!( - history, - vec![ - event.clone(), - EnclaveEvent::from(DecryptionshareCreated { - decryption_share: ds1.clone(), - e3_id: e3_id.clone(), - node: eth_addrs[0].clone() - }), - EnclaveEvent::from(DecryptionshareCreated { - decryption_share: ds2.clone(), - e3_id: e3_id.clone(), - node: eth_addrs[1].clone() - }), - EnclaveEvent::from(DecryptionshareCreated { - decryption_share: ds3.clone(), - e3_id: e3_id.clone(), - node: eth_addrs[2].clone() - }), - EnclaveEvent::from(PlaintextAggregated { - e3_id: e3_id.clone(), - decrypted_output: expected_raw_plaintext.clone() - }) - ] - ); - - Ok(()) - } - - #[actix::test] - async fn test_p2p_actor_forwards_events_to_network() -> Result<()> { - // Setup elements in test - let (tx, mut output) = channel(100); // Transmit byte events to the network - let (input, rx) = channel(100); // Receive byte events from the network - let bus = EventBus::new(true).start(); - P2p::spawn_and_listen(bus.clone(), tx.clone(), rx); - - // Capture messages from output on msgs vec - let msgs: Arc>>> = Arc::new(Mutex::new(Vec::new())); - let msgs_loop = msgs.clone(); - - tokio::spawn(async move { - while let Some(msg) = output.recv().await { - msgs_loop.lock().await.push(msg.clone()); - let _ = input.send(msg).await; - // loopback to simulate a rebroadcast message - // if this manages to broadcast an event to the - // event bus we will expect to see an extra event on - // the bus - } - }); - - let evt_1 = EnclaveEvent::from(PlaintextAggregated { - e3_id: E3id::new("1235"), - decrypted_output: vec![1, 2, 3, 4], - }); - - let evt_2 = EnclaveEvent::from(PlaintextAggregated { - e3_id: E3id::new("1236"), - decrypted_output: vec![1, 2, 3, 4], - }); - - let local_evt_3 = EnclaveEvent::from(CiphernodeSelected { - e3_id: E3id::new("1235"), - threshold_m: 3, - }); - - bus.do_send(evt_1.clone()); - bus.do_send(evt_2.clone()); - bus.do_send(local_evt_3.clone()); // This is a local event which should not be broadcast to the network - - sleep(Duration::from_millis(1)).await; // need to push to next tick - - // check the history of the event bus - let history = bus.send(GetHistory).await?; - - assert_eq!( - *msgs.lock().await, - vec![evt_1.to_bytes()?, evt_2.to_bytes()?], // notice no local events - "P2p did not transmit correct events to the network" - ); - - assert_eq!( - history, - vec![evt_1, evt_2, local_evt_3], // all local events that have been broadcast but no - // events from the loopback - "P2p must not retransmit forwarded event to event bus" - ); - - Ok(()) - } - - #[actix::test] - async fn test_p2p_actor_forwards_events_to_bus() -> Result<()> { - let seed = Seed(ChaCha20Rng::seed_from_u64(123).get_seed()); - - // Setup elements in test - let (tx, _) = channel(100); // Transmit byte events to the network - let (input, rx) = channel(100); // Receive byte events from the network - let bus = EventBus::new(true).start(); - P2p::spawn_and_listen(bus.clone(), tx.clone(), rx); - - // Capture messages from output on msgs vec - let event = EnclaveEvent::from(E3Requested { - e3_id: E3id::new("1235"), - threshold_m: 3, - seed: seed.clone(), - params: vec![1, 2, 3, 4], - }); - - // lets send an event from the network - let _ = input.send(event.to_bytes()?).await; - - sleep(Duration::from_millis(1)).await; // need to push to next tick - - // check the history of the event bus - let history = bus.send(GetHistory).await?; - - assert_eq!(history, vec![event]); - - Ok(()) - } -} +pub use ordered_set::*; diff --git a/packages/ciphernode/core/src/ordered_set.rs b/packages/ciphernode/core/src/ordered_set.rs index c263e30a..31eaedfa 100644 --- a/packages/ciphernode/core/src/ordered_set.rs +++ b/packages/ciphernode/core/src/ordered_set.rs @@ -1,6 +1,6 @@ use std::collections::BTreeSet; -use std::hash::{Hash, Hasher}; use std::fmt; +use std::hash::{Hash, Hasher}; #[derive(Clone)] pub struct OrderedSet(BTreeSet); diff --git a/packages/ciphernode/data/Cargo.toml b/packages/ciphernode/data/Cargo.toml new file mode 100644 index 00000000..5624cf4f --- /dev/null +++ b/packages/ciphernode/data/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "data" +version = "0.1.0" +edition = "2021" +description = "Data persistence for enclave" +repository = "https://github.com/gnosisguild/enclave/packages/ciphernode" + +[dependencies] +actix = { workspace = true } diff --git a/packages/ciphernode/core/src/data.rs b/packages/ciphernode/data/src/data.rs similarity index 99% rename from packages/ciphernode/core/src/data.rs rename to packages/ciphernode/data/src/data.rs index 7da55390..2b6804c8 100644 --- a/packages/ciphernode/core/src/data.rs +++ b/packages/ciphernode/data/src/data.rs @@ -1,6 +1,5 @@ -use std::collections::BTreeMap; - use actix::{Actor, Context, Handler, Message}; +use std::collections::BTreeMap; // TODO: replace with sled version diff --git a/packages/ciphernode/data/src/lib.rs b/packages/ciphernode/data/src/lib.rs new file mode 100644 index 00000000..3ad1dc3a --- /dev/null +++ b/packages/ciphernode/data/src/lib.rs @@ -0,0 +1,2 @@ +mod data; +pub use data::*; diff --git a/packages/ciphernode/enclave/Cargo.toml b/packages/ciphernode/enclave/Cargo.toml index dba66d24..9bb7bd96 100644 --- a/packages/ciphernode/enclave/Cargo.toml +++ b/packages/ciphernode/enclave/Cargo.toml @@ -8,7 +8,8 @@ repository = "https://github.com/gnosisguild/enclave/packages/ciphernode" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-std = "1.12.0" -fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +enclave_node = { path = "../enclave_node" } +alloy = { workspace = true } +clap = { workspace = true } +actix-rt = { workspace = true } +tokio = { workspace = true } diff --git a/packages/ciphernode/enclave_node/src/bin/aggregator.rs b/packages/ciphernode/enclave/src/bin/aggregator.rs similarity index 84% rename from packages/ciphernode/enclave_node/src/bin/aggregator.rs rename to packages/ciphernode/enclave/src/bin/aggregator.rs index 54b70c61..dc3b1745 100644 --- a/packages/ciphernode/enclave_node/src/bin/aggregator.rs +++ b/packages/ciphernode/enclave/src/bin/aggregator.rs @@ -1,6 +1,6 @@ use alloy::primitives::Address; use clap::Parser; -use enclave_core::MainAggregator; +use enclave_node::MainAggregator; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -25,11 +25,8 @@ async fn main() -> Result<(), Box> { println!("LAUNCHING AGGREGATOR"); let registry_contract = Address::parse_checksummed(&args.registry_contract, None).expect("Invalid address"); - let registry_filter_contract = Address::parse_checksummed( - &args.registry_filter_contract, - None, - ) - .expect("Invalid address"); + let registry_filter_contract = + Address::parse_checksummed(&args.registry_filter_contract, None).expect("Invalid address"); let enclave_contract = Address::parse_checksummed(&args.enclave_contract, None).expect("Invalid address"); let (_, handle) = MainAggregator::attach( @@ -38,7 +35,7 @@ async fn main() -> Result<(), Box> { registry_contract, registry_filter_contract, args.pubkey_write_path.as_deref(), - args.plaintext_write_path.as_deref() + args.plaintext_write_path.as_deref(), ) .await; let _ = tokio::join!(handle); diff --git a/packages/ciphernode/enclave/src/main.rs b/packages/ciphernode/enclave/src/main.rs index 2008bce5..5b8fabe7 100644 --- a/packages/ciphernode/enclave/src/main.rs +++ b/packages/ciphernode/enclave/src/main.rs @@ -1,9 +1,48 @@ -fn main() { - // read cli inputs and or config options +use alloy::primitives::Address; +use clap::Parser; +use enclave_node::MainCiphernode; - // any preflight checks +const OWO: &str = r#" + ___ ___ ___ ___ ___ + /\__\ /\ \ /\__\ /\ \ ___ /\__\ + /:/ _/_ \:\ \ /:/ / /::\ \ /\ \ /:/ _/_ + /:/ /\__\ \:\ \ /:/ / /:/\:\ \ \:\ \ /:/ /\__\ + /:/ /:/ _/_ _____\:\ \ /:/ / ___ ___ ___ /:/ /::\ \ \:\ \ /:/ /:/ _/_ + /:/_/:/ /\__\ /::::::::\__\ /:/__/ /\__\ /\ \ /\__\ /:/_/:/\:\__\ ___ \:\__\ /:/_/:/ /\__\ + \:\/:/ /:/ / \:\~~\~~\/__/ \:\ \ /:/ / \:\ \ /:/ / \:\/:/ \/__/ /\ \ |:| | \:\/:/ /:/ / + \::/_/:/ / \:\ \ \:\ /:/ / \:\ /:/ / \::/__/ \:\ \|:| | \::/_/:/ / + \:\/:/ / \:\ \ \:\/:/ / \:\/:/ / \:\ \ \:\__|:|__| \:\/:/ / + \::/ / \:\__\ \::/ / \::/ / \:\__\ \::::/__/ \::/ / + \/__/ \/__/ \/__/ \/__/ \/__/ ~~~~ \/__/ + +"#; - // launch production ready enclave node +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[arg(short = 'a', long)] + address: String, + #[arg(short = 'r', long)] + rpc: String, + #[arg(short = 'e', long = "enclave-contract")] + enclave_contract: String, + #[arg(short = 'c', long = "registry-contract")] + registry_contract: String, +} - println!("Hello, cipher world!"); -} \ No newline at end of file +#[actix_rt::main] +async fn main() -> Result<(), Box> { + println!("\n\n\n\n\n{}", OWO); + println!("\n\n\n\n"); + let args = Args::parse(); + let address = Address::parse_checksummed(&args.address, None).expect("Invalid address"); + println!("LAUNCHING CIPHERNODE: ({})", address); + let registry_contract = + Address::parse_checksummed(&args.registry_contract, None).expect("Invalid address"); + let enclave_contract = + Address::parse_checksummed(&args.enclave_contract, None).expect("Invalid address"); + let (_, handle) = + MainCiphernode::attach(address, &args.rpc, enclave_contract, registry_contract).await; + let _ = tokio::join!(handle); + Ok(()) +} diff --git a/packages/ciphernode/enclave_node/Cargo.toml b/packages/ciphernode/enclave_node/Cargo.toml index 4fabfe41..dc40e29d 100644 --- a/packages/ciphernode/enclave_node/Cargo.toml +++ b/packages/ciphernode/enclave_node/Cargo.toml @@ -8,22 +8,25 @@ repository = "https://github.com/gnosisguild/enclave/packages/ciphernode" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -p2p = { path = "../p2p" } +actix = { workspace = true } +actix-rt = { workspace = true } +aggregator = { path = "../aggregator" } +alloy = { workspace = true } +alloy-primitives = { workspace = true } +anyhow = { workspace = true } bfv = { path = "../bfv" } -sortition = { path = "../sortition" } +bincode = { workspace = true } +clap = { workspace = true } +data = { path = "../data" } enclave-core = { path = "../core" } -async-std = "1.12.0" -fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -tokio = { version = "1.38", features = ["full"] } -actix-rt = "2.10.0" -alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } - -alloy = { version = "0.3.3", features = ["full"] } -clap = { version = "4.5.17", features = ["derive"] } -rand_chacha = "0.3.1" -rand = "0.8.5" -bincode = "1.3.3" -base91 = "0.1.0" -base64 = "0.22.1" +evm = { path = "../evm" } +fhe = { path = "../fhe" } +keyshare = { path = "../keyshare" } +logger = { path = "../logger" } +p2p = { path = "../p2p" } +rand = { workspace = true } +rand_chacha = { workspace = true } +router = { path = "../router" } +sortition = { path = "../sortition" } +test-helpers = { path = "../test_helpers" } +tokio = { workspace = true } diff --git a/packages/ciphernode/core/src/main_aggregator.rs b/packages/ciphernode/enclave_node/src/aggregator.rs similarity index 70% rename from packages/ciphernode/core/src/main_aggregator.rs rename to packages/ciphernode/enclave_node/src/aggregator.rs index 743414f5..d1048a8e 100644 --- a/packages/ciphernode/core/src/main_aggregator.rs +++ b/packages/ciphernode/enclave_node/src/aggregator.rs @@ -1,22 +1,25 @@ -use crate::{ - committee_meta::CommitteeMetaFactory, evm_ciphernode_registry::connect_evm_ciphernode_registry, - evm_enclave::connect_evm_enclave, public_key_writer::PublicKeyWriter, E3RequestManager, - EventBus, FheFactory, P2p, PlaintextAggregatorFactory, PlaintextWriter, - PublicKeyAggregatorFactory, SimpleLogger, Sortition, - evm_caller::connect_evm_caller, -}; use actix::{Actor, Addr, Context}; use alloy::primitives::Address; +use enclave_core::EventBus; +use evm::{connect_evm_caller, connect_evm_ciphernode_registry, connect_evm_enclave}; +use logger::SimpleLogger; +use p2p::P2p; use rand::SeedableRng; use rand_chacha::rand_core::OsRng; +use router::{ + CommitteeMetaFactory, E3RequestRouter, LazyFhe, LazyPlaintextAggregator, + LazyPublicKeyAggregator, +}; +use sortition::Sortition; use std::sync::{Arc, Mutex}; +use test_helpers::{PlaintextWriter, PublicKeyWriter}; use tokio::task::JoinHandle; /// Main Ciphernode Actor /// Suprvises all children // TODO: add supervision logic pub struct MainAggregator { - e3_manager: Addr, + e3_manager: Addr, bus: Addr, sortition: Addr, p2p: Addr, @@ -27,7 +30,7 @@ impl MainAggregator { bus: Addr, sortition: Addr, p2p: Addr, - e3_manager: Addr, + e3_manager: Addr, ) -> Self { Self { e3_manager, @@ -54,16 +57,22 @@ impl MainAggregator { connect_evm_enclave(bus.clone(), rpc_url, enclave_contract).await; let _ = connect_evm_ciphernode_registry(bus.clone(), rpc_url, registry_contract).await; - let _ = connect_evm_caller(bus.clone(), sortition.clone(), rpc_url, enclave_contract, registry_filter_contract).await; + let _ = connect_evm_caller( + bus.clone(), + sortition.clone(), + rpc_url, + enclave_contract, + registry_filter_contract, + ) + .await; - let e3_manager = E3RequestManager::builder(bus.clone()) - .add_hook(CommitteeMetaFactory::create()) - .add_hook(FheFactory::create(rng)) - .add_hook(PublicKeyAggregatorFactory::create( + let e3_manager = E3RequestRouter::builder(bus.clone()) + .add_hook(LazyFhe::create(rng)) + .add_hook(LazyPublicKeyAggregator::create( bus.clone(), sortition.clone(), )) - .add_hook(PlaintextAggregatorFactory::create( + .add_hook(LazyPlaintextAggregator::create( bus.clone(), sortition.clone(), )) diff --git a/packages/ciphernode/enclave_node/src/bin/node.rs b/packages/ciphernode/enclave_node/src/bin/node.rs deleted file mode 100644 index a3432110..00000000 --- a/packages/ciphernode/enclave_node/src/bin/node.rs +++ /dev/null @@ -1,28 +0,0 @@ -use alloy::primitives::Address; -use clap::Parser; -use enclave_core::MainCiphernode; - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Args { - #[arg(short = 'a', long)] - address: String, - #[arg(short='r', long)] - rpc: String, - #[arg(short = 'e', long = "enclave-contract")] - enclave_contract: String, - #[arg(short = 'c', long = "registry-contract")] - registry_contract: String -} - -#[actix_rt::main] -async fn main() -> Result<(), Box> { - let args = Args::parse(); - let address = Address::parse_checksummed(&args.address, None).expect("Invalid address"); - println!("LAUNCHING CIPHERNODE: ({})", address); - let registry_contract = Address::parse_checksummed(&args.registry_contract, None).expect("Invalid address"); - let enclave_contract = Address::parse_checksummed(&args.enclave_contract, None).expect("Invalid address"); - let (_, handle) = MainCiphernode::attach(address, &args.rpc, enclave_contract, registry_contract).await; - let _ = tokio::join!(handle); - Ok(()) -} diff --git a/packages/ciphernode/core/src/main_ciphernode.rs b/packages/ciphernode/enclave_node/src/ciphernode.rs similarity index 80% rename from packages/ciphernode/core/src/main_ciphernode.rs rename to packages/ciphernode/enclave_node/src/ciphernode.rs index ceb399c6..e55914ac 100644 --- a/packages/ciphernode/core/src/main_ciphernode.rs +++ b/packages/ciphernode/enclave_node/src/ciphernode.rs @@ -1,14 +1,15 @@ -use std::sync::{Arc, Mutex}; - -use crate::{ - evm_ciphernode_registry::connect_evm_ciphernode_registry, - evm_enclave::connect_evm_enclave, CiphernodeSelector, CommitteeMetaFactory, Data, - E3RequestManager, EventBus, FheFactory, KeyshareFactory, P2p, SimpleLogger, Sortition, -}; use actix::{Actor, Addr, Context}; use alloy::primitives::Address; +use data::Data; +use enclave_core::EventBus; +use evm::{connect_evm_ciphernode_registry, connect_evm_enclave}; +use logger::SimpleLogger; +use p2p::P2p; use rand::SeedableRng; use rand_chacha::rand_core::OsRng; +use router::{CiphernodeSelector, CommitteeMetaFactory, E3RequestRouter, LazyFhe, LazyKeyshare}; +use sortition::Sortition; +use std::sync::{Arc, Mutex}; use tokio::task::JoinHandle; /// Main Ciphernode Actor @@ -20,7 +21,7 @@ pub struct MainCiphernode { data: Addr, sortition: Addr, selector: Addr, - e3_manager: Addr, + e3_manager: Addr, p2p: Addr, } @@ -32,7 +33,7 @@ impl MainCiphernode { sortition: Addr, selector: Addr, p2p: Addr, - e3_manager: Addr, + e3_manager: Addr, ) -> Self { Self { addr, @@ -63,10 +64,9 @@ impl MainCiphernode { connect_evm_enclave(bus.clone(), rpc_url, enclave_contract).await; let _ = connect_evm_ciphernode_registry(bus.clone(), rpc_url, registry_contract).await; - let e3_manager = E3RequestManager::builder(bus.clone()) - .add_hook(CommitteeMetaFactory::create()) - .add_hook(FheFactory::create(rng)) - .add_hook(KeyshareFactory::create( + let e3_manager = E3RequestRouter::builder(bus.clone()) + .add_hook(LazyFhe::create(rng)) + .add_hook(LazyKeyshare::create( bus.clone(), data.clone(), &address.to_string(), diff --git a/packages/ciphernode/enclave_node/src/lib.rs b/packages/ciphernode/enclave_node/src/lib.rs new file mode 100644 index 00000000..a0e3ff23 --- /dev/null +++ b/packages/ciphernode/enclave_node/src/lib.rs @@ -0,0 +1,5 @@ +mod aggregator; +mod ciphernode; + +pub use aggregator::*; +pub use ciphernode::*; diff --git a/packages/ciphernode/enclave_node/src/main.rs b/packages/ciphernode/enclave_node/src/main.rs index c2079c01..6ca69372 100644 --- a/packages/ciphernode/enclave_node/src/main.rs +++ b/packages/ciphernode/enclave_node/src/main.rs @@ -1,5 +1,6 @@ use std::error::Error; +use alloy::primitives::address; use bfv::EnclaveBFV; use p2p::EnclaveRouter; use sortition::DistanceSortition; @@ -7,7 +8,6 @@ use tokio::{ self, io::{self, AsyncBufReadExt, BufReader}, }; -use alloy::primitives::address; const OWO: &str = r#" ___ ___ ___ ___ ___ diff --git a/packages/ciphernode/evm/Cargo.toml b/packages/ciphernode/evm/Cargo.toml new file mode 100644 index 00000000..d99406de --- /dev/null +++ b/packages/ciphernode/evm/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "evm" +version = "0.1.0" +edition = "2021" + +[dependencies] +actix = { workspace = true } +alloy = { workspace = true } +alloy-primitives = { workspace = true } +anyhow = { workspace = true } +enclave-core = { path = "../core" } +futures-util = { workspace = true } +sortition = { path = "../sortition" } diff --git a/packages/ciphernode/core/src/evm_caller.rs b/packages/ciphernode/evm/src/caller.rs similarity index 75% rename from packages/ciphernode/core/src/evm_caller.rs rename to packages/ciphernode/evm/src/caller.rs index 1a3d7149..9f2c8b5c 100644 --- a/packages/ciphernode/core/src/evm_caller.rs +++ b/packages/ciphernode/evm/src/caller.rs @@ -3,12 +3,11 @@ use alloy::primitives::{Address, Bytes, U256}; use std::collections::HashMap; use std::sync::Arc; -use crate::{ - events::EnclaveEvent, - evm_contracts::EVMContract, - sortition::{GetNodes, Sortition}, - EventBus, -}; +use enclave_core::{EnclaveEvent, EventBus, Subscribe}; +use sortition::{GetNodes, Sortition}; + +use super::EVMContract; + pub struct EvmCaller { contracts: HashMap>, bus: Addr, @@ -20,10 +19,7 @@ impl Actor for EvmCaller { } impl EvmCaller { - pub fn new( - bus: Addr, - sortition: Addr, - ) -> Self { + pub fn new(bus: Addr, sortition: Addr) -> Self { Self { contracts: HashMap::new(), bus, @@ -35,18 +31,15 @@ impl EvmCaller { self.contracts.insert(name.to_string(), contract); } - pub fn attach( - bus: Addr, - sortition: Addr, - ) -> Addr { + pub fn attach(bus: Addr, sortition: Addr) -> Addr { let addr = Self::new(bus.clone(), sortition).start(); - bus.do_send(crate::Subscribe::new( + bus.do_send(Subscribe::new( "PublicKeyAggregated", addr.clone().recipient(), )); - bus.do_send(crate::Subscribe::new( + bus.do_send(Subscribe::new( "PlaintextAggregated", addr.clone().recipient(), )); @@ -94,10 +87,15 @@ impl Handler for EvmCaller { nodes, Bytes::from(data.pubkey), ) - .await + .await { - Ok(tx) => println!("Published committee public key {:?}", tx.transaction_hash), - Err(e) => eprintln!("Failed to publish committee public key: {:?}", e), + Ok(tx) => println!( + "Published committee public key {:?}", + tx.transaction_hash + ), + Err(e) => { + eprintln!("Failed to publish committee public key: {:?}", e) + } } } } @@ -112,7 +110,9 @@ impl Handler for EvmCaller { ) .await { - Ok(tx) => println!("Published plaintext output {:?}", tx.transaction_hash), + Ok(tx) => { + println!("Published plaintext output {:?}", tx.transaction_hash) + } Err(e) => eprintln!("Failed to publish plaintext: {:?}", e), } } @@ -126,7 +126,6 @@ impl Handler for EvmCaller { } } - pub async fn connect_evm_caller( bus: Addr, sortition: Addr, @@ -139,15 +138,19 @@ pub async fn connect_evm_caller( let enclave_instance = EVMContract::new(rpc_url, enclave_contract).await?; let registry_instance = EVMContract::new(rpc_url, registry_contract).await?; - evm_caller.send(AddContract { - name: "enclave".to_string(), - contract: Arc::new(enclave_instance), - }).await?; + evm_caller + .send(AddContract { + name: "enclave".to_string(), + contract: Arc::new(enclave_instance), + }) + .await?; - evm_caller.send(AddContract { - name: "registry".to_string(), - contract: Arc::new(registry_instance), - }).await?; + evm_caller + .send(AddContract { + name: "registry".to_string(), + contract: Arc::new(registry_instance), + }) + .await?; Ok(evm_caller) -} \ No newline at end of file +} diff --git a/packages/ciphernode/core/src/evm_ciphernode_registry.rs b/packages/ciphernode/evm/src/ciphernode_registry.rs similarity index 78% rename from packages/ciphernode/core/src/evm_ciphernode_registry.rs rename to packages/ciphernode/evm/src/ciphernode_registry.rs index b6f9f209..a1a1283b 100644 --- a/packages/ciphernode/core/src/evm_ciphernode_registry.rs +++ b/packages/ciphernode/evm/src/ciphernode_registry.rs @@ -1,12 +1,9 @@ -use crate::{ - events, - evm_listener::{AddEventHandler, ContractEvent, StartListening}, - evm_manager::{AddListener, EvmContractManager}, - EnclaveEvent, EventBus, -}; +use super::ContractEvent; +use crate::{AddEventHandler, AddListener, EvmContractManager, StartListening}; use actix::Addr; use alloy::{primitives::Address, sol}; use anyhow::Result; +use enclave_core::{EnclaveEvent, EventBus}; sol! { #[derive(Debug)] @@ -26,9 +23,9 @@ sol! { ); } -impl From for events::CiphernodeAdded { +impl From for enclave_core::CiphernodeAdded { fn from(value: CiphernodeAdded) -> Self { - events::CiphernodeAdded { + enclave_core::CiphernodeAdded { address: value.node.to_string(), // TODO: limit index and numNodes to uint32 at the solidity level index: value @@ -43,9 +40,9 @@ impl From for events::CiphernodeAdded { } } -impl From for events::CiphernodeRemoved { +impl From for enclave_core::CiphernodeRemoved { fn from(value: CiphernodeRemoved) -> Self { - events::CiphernodeRemoved { + enclave_core::CiphernodeRemoved { address: value.node.to_string(), index: value .index @@ -61,7 +58,7 @@ impl From for events::CiphernodeRemoved { impl ContractEvent for CiphernodeAdded { fn process(&self, bus: Addr) -> Result<()> { - let data: events::CiphernodeAdded = self.clone().into(); + let data: enclave_core::CiphernodeAdded = self.clone().into(); bus.do_send(EnclaveEvent::from(data)); Ok(()) } @@ -69,7 +66,7 @@ impl ContractEvent for CiphernodeAdded { impl ContractEvent for CiphernodeRemoved { fn process(&self, bus: Addr) -> Result<()> { - let data: events::CiphernodeRemoved = self.clone().into(); + let data: enclave_core::CiphernodeRemoved = self.clone().into(); bus.do_send(EnclaveEvent::from(data)); Ok(()) } @@ -81,9 +78,7 @@ pub async fn connect_evm_ciphernode_registry( contract_address: Address, ) -> Result<()> { let evm_manager = EvmContractManager::attach(bus.clone(), rpc_url).await; - let evm_listener = evm_manager - .send(AddListener { contract_address }) - .await?; + let evm_listener = evm_manager.send(AddListener { contract_address }).await?; evm_listener .send(AddEventHandler::::new()) diff --git a/packages/ciphernode/core/src/evm_contracts.rs b/packages/ciphernode/evm/src/contracts.rs similarity index 98% rename from packages/ciphernode/core/src/evm_contracts.rs rename to packages/ciphernode/evm/src/contracts.rs index d38ea758..557dac21 100644 --- a/packages/ciphernode/core/src/evm_contracts.rs +++ b/packages/ciphernode/evm/src/contracts.rs @@ -55,7 +55,7 @@ impl EVMContract { Ok(Self { provider: Arc::new(provider), - contract_address: contract_address, + contract_address, }) } @@ -82,4 +82,4 @@ impl EVMContract { let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } -} \ No newline at end of file +} diff --git a/packages/ciphernode/core/src/evm_enclave.rs b/packages/ciphernode/evm/src/enclave.rs similarity index 78% rename from packages/ciphernode/core/src/evm_enclave.rs rename to packages/ciphernode/evm/src/enclave.rs index 42e10d91..9e482cb0 100644 --- a/packages/ciphernode/core/src/evm_enclave.rs +++ b/packages/ciphernode/evm/src/enclave.rs @@ -1,16 +1,9 @@ -use crate::{ - events, - evm_listener::{AddEventHandler, ContractEvent, StartListening}, - evm_manager::{AddListener, EvmContractManager}, - EnclaveEvent, EventBus, -}; +use super::listener::ContractEvent; +use crate::{AddEventHandler, AddListener, EvmContractManager, StartListening}; use actix::Addr; -use alloy::{ - primitives::{Address}, - sol, - sol_types::SolValue, -}; -use anyhow::{Context, Result}; +use alloy::{primitives::Address, sol}; +use anyhow::Result; +use enclave_core::{EnclaveEvent, EventBus}; sol! { #[derive(Debug)] @@ -45,11 +38,11 @@ sol! { ); } -impl TryFrom<&E3Requested> for events::E3Requested { +impl TryFrom<&E3Requested> for enclave_core::E3Requested { type Error = anyhow::Error; fn try_from(value: &E3Requested) -> Result { let program_params = value.e3.e3ProgramParams.to_vec(); - Ok(events::E3Requested { + Ok(enclave_core::E3Requested { params: program_params.into(), threshold_m: value.e3.threshold[0] as usize, seed: value.e3.seed.into(), @@ -58,9 +51,9 @@ impl TryFrom<&E3Requested> for events::E3Requested { } } -impl From for events::CiphertextOutputPublished { +impl From for enclave_core::CiphertextOutputPublished { fn from(value: CiphertextOutputPublished) -> Self { - events::CiphertextOutputPublished { + enclave_core::CiphertextOutputPublished { e3_id: value.e3Id.to_string().into(), ciphertext_output: value.ciphertextOutput.to_vec(), } @@ -69,7 +62,7 @@ impl From for events::CiphertextOutputPublished { impl ContractEvent for E3Requested { fn process(&self, bus: Addr) -> Result<()> { - let data: events::E3Requested = self.try_into()?; + let data: enclave_core::E3Requested = self.try_into()?; bus.do_send(EnclaveEvent::from(data)); Ok(()) @@ -78,7 +71,7 @@ impl ContractEvent for E3Requested { impl ContractEvent for CiphertextOutputPublished { fn process(&self, bus: Addr) -> Result<()> { - let data: events::CiphertextOutputPublished = self.clone().into(); + let data: enclave_core::CiphertextOutputPublished = self.clone().into(); bus.do_send(EnclaveEvent::from(data)); Ok(()) } @@ -104,4 +97,3 @@ pub async fn connect_evm_enclave(bus: Addr, rpc_url: &str, contract_ad println!("Evm is listening to {}", contract_address); } - diff --git a/packages/ciphernode/evm/src/lib.rs b/packages/ciphernode/evm/src/lib.rs new file mode 100644 index 00000000..c977102d --- /dev/null +++ b/packages/ciphernode/evm/src/lib.rs @@ -0,0 +1,13 @@ +mod caller; +mod ciphernode_registry; +mod contracts; +mod enclave; +mod listener; +mod manager; + +pub use caller::*; +pub use ciphernode_registry::*; +pub use contracts::*; +pub use enclave::*; +pub use listener::*; +pub use manager::*; diff --git a/packages/ciphernode/core/src/evm_listener.rs b/packages/ciphernode/evm/src/listener.rs similarity index 94% rename from packages/ciphernode/core/src/evm_listener.rs rename to packages/ciphernode/evm/src/listener.rs index f12a09e7..1bac6d7d 100644 --- a/packages/ciphernode/core/src/evm_listener.rs +++ b/packages/ciphernode/evm/src/listener.rs @@ -7,13 +7,12 @@ use alloy::{ transports::BoxTransport, }; use anyhow::Result; +use enclave_core::{EnclaveErrorType, EnclaveEvent, EventBus, FromError}; use futures_util::stream::StreamExt; use std::collections::HashMap; use std::marker::PhantomData; use std::sync::Arc; -use crate::{EnclaveErrorType, EnclaveEvent, EventBus, FromError}; - pub trait ContractEvent: Send + Sync + 'static { fn process(&self, bus: Addr) -> Result<()>; } @@ -65,10 +64,7 @@ impl EvmEventListener { // Send enclave error to bus self.bus .clone() - .do_send(EnclaveEvent::from_error( - EnclaveErrorType::Evm, - err, - )); + .do_send(EnclaveEvent::from_error(EnclaveErrorType::Evm, err)); } } } diff --git a/packages/ciphernode/core/src/evm_manager.rs b/packages/ciphernode/evm/src/manager.rs similarity index 96% rename from packages/ciphernode/core/src/evm_manager.rs rename to packages/ciphernode/evm/src/manager.rs index e00d659a..7c061fdd 100644 --- a/packages/ciphernode/core/src/evm_manager.rs +++ b/packages/ciphernode/evm/src/manager.rs @@ -1,7 +1,3 @@ -use crate::{ - evm_listener::{EvmEventListener, StartListening}, - EventBus, -}; use actix::{Actor, Addr, Context, Handler, Message}; use alloy::{ primitives::Address, @@ -10,8 +6,11 @@ use alloy::{ transports::BoxTransport, }; use anyhow::Result; +use enclave_core::EventBus; use std::sync::Arc; +use super::{EvmEventListener, StartListening}; + pub struct EvmContractManager { bus: Addr, provider: Arc>, @@ -73,4 +72,3 @@ impl Handler for EvmContractManager { } } } - diff --git a/packages/ciphernode/fhe/Cargo.toml b/packages/ciphernode/fhe/Cargo.toml new file mode 100644 index 00000000..05cf7414 --- /dev/null +++ b/packages/ciphernode/fhe/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "fhe" +version = "0.1.0" +edition = "2021" + +[dependencies] +enclave-core = { path = "../core" } +anyhow = { workspace = true } +fhe_rs = { workspace = true } +fhe-traits = { workspace = true } +fhe-util = { workspace = true } +rand = { workspace = true } +rand_chacha = { workspace = true } +bincode = { workspace = true } +serde = { workspace = true } diff --git a/packages/ciphernode/core/src/fhe.rs b/packages/ciphernode/fhe/src/fhe.rs similarity index 88% rename from packages/ciphernode/core/src/fhe.rs rename to packages/ciphernode/fhe/src/fhe.rs index 2dee23d0..3cab9650 100644 --- a/packages/ciphernode/core/src/fhe.rs +++ b/packages/ciphernode/fhe/src/fhe.rs @@ -1,6 +1,7 @@ -use crate::{ordered_set::OrderedSet, set_up_crp, ActorFactory, E3Requested, EnclaveEvent, Seed}; +use super::set_up_crp; use anyhow::*; -use fhe::{ +use enclave_core::{E3Requested, EnclaveEvent, OrderedSet, Seed}; +use fhe_rs::{ bfv::{ BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, }, @@ -121,25 +122,6 @@ impl Fhe { Ok(bincode::serialize(&decoded)?) } } - -pub struct FheFactory; - -impl FheFactory { - pub fn create(rng: SharedRng) -> ActorFactory { - Box::new(move |ctx, evt| { - // Saving the fhe on Committee Requested - let EnclaveEvent::E3Requested { data, .. } = evt else { - return; - }; - let E3Requested { params, seed, .. } = data; - - ctx.fhe = Some(Arc::new( - Fhe::from_encoded(¶ms, seed, rng.clone()).unwrap(), - )); - }) - } -} - struct SecretKeySerializer { pub inner: SecretKey, } diff --git a/packages/ciphernode/fhe/src/lib.rs b/packages/ciphernode/fhe/src/lib.rs new file mode 100644 index 00000000..75b9bd75 --- /dev/null +++ b/packages/ciphernode/fhe/src/lib.rs @@ -0,0 +1,5 @@ +mod fhe; +mod utils; + +pub use fhe::*; +pub use utils::*; diff --git a/packages/ciphernode/core/src/utils.rs b/packages/ciphernode/fhe/src/utils.rs similarity index 68% rename from packages/ciphernode/core/src/utils.rs rename to packages/ciphernode/fhe/src/utils.rs index 73b37191..4708021e 100644 --- a/packages/ciphernode/core/src/utils.rs +++ b/packages/ciphernode/fhe/src/utils.rs @@ -1,11 +1,12 @@ -use crate::SharedRng; use anyhow::{Context, Result}; -use fhe::{ +use fhe_rs::{ bfv::{BfvParameters, BfvParametersBuilder}, mbfv::CommonRandomPoly, }; use fhe_traits::{Deserialize, Serialize}; -use std::{fs, io::Write, path::Path, sync::Arc}; +use std::sync::Arc; + +use super::SharedRng; pub struct ParamsWithCrp { pub moduli: Vec, @@ -58,24 +59,3 @@ pub fn decode_params(bytes: &[u8]) -> Result> { pub fn set_up_crp(params: Arc, rng: SharedRng) -> CommonRandomPoly { CommonRandomPoly::new(¶ms, &mut *rng.lock().unwrap()).unwrap() } - -pub fn write_file_with_dirs(path: &str, content: &[u8]) -> std::io::Result<()> { - let abs_path = if Path::new(path).is_absolute() { - Path::new(path).to_path_buf() - } else { - let cwd = std::env::current_dir()?; - cwd.join(path) - }; - - // Ensure the directory structure exists - if let Some(parent) = abs_path.parent() { - fs::create_dir_all(parent)?; - } - - // Open the file (creates it if it doesn't exist) and write the content - let mut file = fs::File::create(&abs_path)?; - file.write_all(content)?; - - println!("File written successfully: {:?}", abs_path); - Ok(()) -} diff --git a/packages/ciphernode/keyshare/Cargo.toml b/packages/ciphernode/keyshare/Cargo.toml new file mode 100644 index 00000000..29ad7d0d --- /dev/null +++ b/packages/ciphernode/keyshare/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "keyshare" +version = "0.1.0" +edition = "2021" + +[dependencies] +data = { path = "../data" } +enclave-core = { path = "../core" } +fhe = { path = "../fhe" } +actix = { workspace = true } +anyhow = { workspace = true } diff --git a/packages/ciphernode/core/src/keyshare.rs b/packages/ciphernode/keyshare/src/keyshare.rs similarity index 81% rename from packages/ciphernode/core/src/keyshare.rs rename to packages/ciphernode/keyshare/src/keyshare.rs index 5d87bfc1..0a36ee6a 100644 --- a/packages/ciphernode/core/src/keyshare.rs +++ b/packages/ciphernode/keyshare/src/keyshare.rs @@ -1,10 +1,12 @@ -use std::sync::Arc; - -use crate::{ - data::{Data, Insert}, eventbus::EventBus, events::{EnclaveEvent, KeyshareCreated}, fhe::Fhe, ActorFactory, CiphernodeSelected, CiphertextOutputPublished, DecryptCiphertext, DecryptionshareCreated, EnclaveErrorType, FromError, Get -}; use actix::prelude::*; use anyhow::{anyhow, Context, Result}; +use data::{Data, Get, Insert}; +use enclave_core::{ + CiphernodeSelected, CiphertextOutputPublished, DecryptionshareCreated, EnclaveErrorType, + EnclaveEvent, EventBus, FromError, KeyshareCreated, +}; +use fhe::{DecryptCiphertext, Fhe}; +use std::sync::Arc; pub struct Keyshare { fhe: Arc, @@ -133,23 +135,3 @@ async fn on_decryption_requested( Ok(()) } - -pub struct KeyshareFactory; -impl KeyshareFactory { - pub fn create(bus: Addr, data: Addr, address: &str) -> ActorFactory { - let address = address.to_string(); - Box::new(move |ctx, evt| { - // Save Ciphernode on CiphernodeSelected - let EnclaveEvent::CiphernodeSelected { .. } = evt else { - return; - }; - - let Some(ref fhe) = ctx.fhe else { - return; - }; - - ctx.keyshare = - Some(Keyshare::new(bus.clone(), data.clone(), fhe.clone(), &address).start()) - }) - } -} diff --git a/packages/ciphernode/keyshare/src/lib.rs b/packages/ciphernode/keyshare/src/lib.rs new file mode 100644 index 00000000..46e4b5c9 --- /dev/null +++ b/packages/ciphernode/keyshare/src/lib.rs @@ -0,0 +1,2 @@ +mod keyshare; +pub use keyshare::*; diff --git a/packages/ciphernode/logger/Cargo.toml b/packages/ciphernode/logger/Cargo.toml new file mode 100644 index 00000000..3e70edd8 --- /dev/null +++ b/packages/ciphernode/logger/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "logger" +version = "0.1.0" +edition = "2021" + +[dependencies] +enclave-core = { path = "../core" } +actix = { workspace = true } +base64 = { workspace = true } diff --git a/packages/ciphernode/logger/src/lib.rs b/packages/ciphernode/logger/src/lib.rs new file mode 100644 index 00000000..8a86b49f --- /dev/null +++ b/packages/ciphernode/logger/src/lib.rs @@ -0,0 +1,2 @@ +mod logger; +pub use logger::*; diff --git a/packages/ciphernode/core/src/logger.rs b/packages/ciphernode/logger/src/logger.rs similarity index 81% rename from packages/ciphernode/core/src/logger.rs rename to packages/ciphernode/logger/src/logger.rs index c2e736a6..fd241329 100644 --- a/packages/ciphernode/core/src/logger.rs +++ b/packages/ciphernode/logger/src/logger.rs @@ -1,7 +1,8 @@ -use crate::{EnclaveEvent, EventBus, Subscribe}; use actix::{Actor, Addr, Context, Handler}; use base64::prelude::*; +use enclave_core::{EnclaveEvent, EventBus, Subscribe}; + pub struct SimpleLogger { name: String, } @@ -41,10 +42,13 @@ impl Handler for SimpleLogger { } EnclaveEvent::CiphernodeAdded { data, .. } => { println!("[{}]: CiphernodeAdded({})", self.name, data.address); - }, - EnclaveEvent::E3Requested { data,.. } => { - println!("[{}]: E3Requested(e3_id: {}, threshold_m: {} , seed: {})", self.name, data.e3_id, data.threshold_m, data.seed); - }, + } + EnclaveEvent::E3Requested { data, .. } => { + println!( + "[{}]: E3Requested(e3_id: {}, threshold_m: {} , seed: {})", + self.name, data.e3_id, data.threshold_m, data.seed + ); + } EnclaveEvent::EnclaveError { data, .. } => { println!("[{}]: EnclaveError('{}')", self.name, data.message); } diff --git a/packages/ciphernode/p2p/Cargo.toml b/packages/ciphernode/p2p/Cargo.toml index 487b5a53..fa79f116 100644 --- a/packages/ciphernode/p2p/Cargo.toml +++ b/packages/ciphernode/p2p/Cargo.toml @@ -8,16 +8,26 @@ repository = "https://github.com/gnosisguild/enclave/packages/ciphernode" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bfv = { path = "../bfv" } -fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -async-std = { version = "1.12", features = ["attributes"] } -async-trait = "0.1" -futures = "0.3.30" -libp2p = { version = "0.53.2", features = [ "async-std", "identify", "macros", "noise", "ping", "rendezvous", "tcp", "tokio", "yamux", "mdns", "gossipsub", "quic"] } -tokio = { version = "1.38", features = ["full"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - - +async-std = { workspace = true, features = ["attributes"] } +async-trait = { workspace = true } +futures = { workspace = true } +libp2p = { workspace = true, features = [ + "async-std", + "identify", + "macros", + "noise", + "ping", + "rendezvous", + "tcp", + "tokio", + "yamux", + "mdns", + "gossipsub", + "quic", +] } +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } +enclave-core = { path = "../core" } +anyhow = { workspace = true } +actix = { workspace = true } diff --git a/packages/ciphernode/p2p/src/lib.rs b/packages/ciphernode/p2p/src/lib.rs index 9cf789cf..cc7025e7 100644 --- a/packages/ciphernode/p2p/src/lib.rs +++ b/packages/ciphernode/p2p/src/lib.rs @@ -2,179 +2,8 @@ #![crate_type = "lib"] #![warn(missing_docs, unused_imports)] -use futures::stream::StreamExt; -use libp2p::{ - gossipsub, identity, mdns, noise, swarm::NetworkBehaviour, swarm::SwarmEvent, tcp, yamux, -}; -use std::collections::hash_map::DefaultHasher; -use std::error::Error; -use std::hash::{Hash, Hasher}; -use std::time::Duration; -use tokio::sync::mpsc::{channel, Receiver, Sender}; -use tokio::{io, select}; -use tracing_subscriber::EnvFilter; +mod libp2p_router; +mod p2p; -#[derive(NetworkBehaviour)] -pub struct MyBehaviour { - gossipsub: gossipsub::Behaviour, - mdns: mdns::tokio::Behaviour, -} - - -pub struct EnclaveRouter { - pub identity: Option, - pub gossipsub_config: gossipsub::Config, - pub swarm: Option>, - pub topic: Option, - evt_tx: Sender>, - cmd_rx: Receiver> -} - -impl EnclaveRouter { - pub fn new() -> Result<(Self, Sender>, Receiver>), Box> { - let (evt_tx, evt_rx) = channel(100); // TODO : tune this param - let (cmd_tx, cmd_rx) = channel(100); // TODO : tune this param - let message_id_fn = |message: &gossipsub::Message| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - gossipsub::MessageId::from(s.finish().to_string()) - }; - - // TODO: Allow for config inputs to new() - let gossipsub_config = gossipsub::ConfigBuilder::default() - .heartbeat_interval(Duration::from_secs(10)) - .validation_mode(gossipsub::ValidationMode::Strict) - .message_id_fn(message_id_fn) - .build() - .map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?; - - Ok(( - Self { - identity: None, - gossipsub_config, - swarm: None, - topic: None, - evt_tx, - cmd_rx - }, - cmd_tx, - evt_rx, - )) - } - - pub fn with_identiy(&mut self, keypair: identity::Keypair) { - self.identity = Some(keypair); - } - - pub fn connect_swarm(&mut self, discovery_type: String) -> Result<&Self, Box> { - match discovery_type.as_str() { - "mdns" => { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - - // TODO: Use key if assigned already - let mut swarm = libp2p::SwarmBuilder::with_new_identity() - .with_tokio() - .with_tcp( - tcp::Config::default(), - noise::Config::new, - yamux::Config::default, - )? - .with_quic() - .with_behaviour(|key| { - let gossipsub = gossipsub::Behaviour::new( - gossipsub::MessageAuthenticity::Signed(key.clone()), - self.gossipsub_config.clone(), - )?; - - let mdns = mdns::tokio::Behaviour::new( - mdns::Config::default(), - key.public().to_peer_id(), - )?; - Ok(MyBehaviour { gossipsub, mdns }) - })? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) - .build(); - self.swarm = Some(swarm); - } - _ => println!("Defaulting to MDNS discovery"), - } - Ok(self) - } - - pub fn join_topic(&mut self, topic_name: &str) -> Result<&Self, Box> { - let topic = gossipsub::IdentTopic::new(topic_name); - self.topic = Some(topic.clone()); - self.swarm - .as_mut() - .unwrap() - .behaviour_mut() - .gossipsub - .subscribe(&topic)?; - Ok(self) - } - - pub async fn start(&mut self) -> Result<(), Box> { - self.swarm - .as_mut() - .unwrap() - .listen_on("/ip4/0.0.0.0/udp/0/quic-v1".parse()?)?; - self.swarm - .as_mut() - .unwrap() - .listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; - loop { - select! { - Some(line) = self.cmd_rx.recv() => { - if let Err(e) = self.swarm.as_mut().unwrap() - .behaviour_mut().gossipsub - .publish(self.topic.as_mut().unwrap().clone(), line) { - println!("Publish error: {e:?}"); - } - } - event = self.swarm.as_mut().unwrap().select_next_some() => match event { - SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Discovered(list))) => { - for (peer_id, _multiaddr) in list { - // println!("mDNS discovered a new peer: {peer_id}"); - self.swarm.as_mut().unwrap().behaviour_mut().gossipsub.add_explicit_peer(&peer_id); - } - }, - SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Expired(list))) => { - for (peer_id, _multiaddr) in list { - // println!("mDNS discover peer has expired: {peer_id}"); - self.swarm.as_mut().unwrap().behaviour_mut().gossipsub.remove_explicit_peer(&peer_id); - } - }, - SwarmEvent::Behaviour(MyBehaviourEvent::Gossipsub(gossipsub::Event::Message { - propagation_source: peer_id, - message_id: id, - message, - })) => { - // println!( - // "Got message with id: {id} from peer: {peer_id}", - // ); - // println!("{:?}", message); - self.evt_tx.send(message.data).await?; - }, - SwarmEvent::NewListenAddr { address, .. } => { - // println!("Local node is listening on {address}"); - } - _ => {} - } - } - } - } -} - -// #[tokio::main] -// async fn main() -> Result<(), Box> { - -// let mut p2p = EnclaveRouter::new()?; -// p2p.connect_swarm("mdns".to_string())?; -// p2p.join_topic("enclave-keygen-01")?; -// p2p.start().await?; - -// println!("Hello, cipher world!"); -// Ok(()) -// } +pub use libp2p_router::*; +pub use p2p::*; diff --git a/packages/ciphernode/p2p/src/libp2p_router.rs b/packages/ciphernode/p2p/src/libp2p_router.rs new file mode 100644 index 00000000..55511bff --- /dev/null +++ b/packages/ciphernode/p2p/src/libp2p_router.rs @@ -0,0 +1,175 @@ +use futures::stream::StreamExt; +use libp2p::{ + gossipsub, identity, mdns, noise, swarm::NetworkBehaviour, swarm::SwarmEvent, tcp, yamux, +}; +use std::collections::hash_map::DefaultHasher; +use std::error::Error; +use std::hash::{Hash, Hasher}; +use std::time::Duration; +use tokio::sync::mpsc::{channel, Receiver, Sender}; +use tokio::{io, select}; +use tracing_subscriber::EnvFilter; + +#[derive(NetworkBehaviour)] +pub struct MyBehaviour { + gossipsub: gossipsub::Behaviour, + mdns: mdns::tokio::Behaviour, +} + +pub struct EnclaveRouter { + pub identity: Option, + pub gossipsub_config: gossipsub::Config, + pub swarm: Option>, + pub topic: Option, + evt_tx: Sender>, + cmd_rx: Receiver>, +} + +impl EnclaveRouter { + pub fn new() -> Result<(Self, Sender>, Receiver>), Box> { + let (evt_tx, evt_rx) = channel(100); // TODO : tune this param + let (cmd_tx, cmd_rx) = channel(100); // TODO : tune this param + let message_id_fn = |message: &gossipsub::Message| { + let mut s = DefaultHasher::new(); + message.data.hash(&mut s); + gossipsub::MessageId::from(s.finish().to_string()) + }; + + // TODO: Allow for config inputs to new() + let gossipsub_config = gossipsub::ConfigBuilder::default() + .heartbeat_interval(Duration::from_secs(10)) + .validation_mode(gossipsub::ValidationMode::Strict) + .message_id_fn(message_id_fn) + .build() + .map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?; + + Ok(( + Self { + identity: None, + gossipsub_config, + swarm: None, + topic: None, + evt_tx, + cmd_rx, + }, + cmd_tx, + evt_rx, + )) + } + + pub fn with_identiy(&mut self, keypair: identity::Keypair) { + self.identity = Some(keypair); + } + + pub fn connect_swarm(&mut self, discovery_type: String) -> Result<&Self, Box> { + match discovery_type.as_str() { + "mdns" => { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + + // TODO: Use key if assigned already + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_quic() + .with_behaviour(|key| { + let gossipsub = gossipsub::Behaviour::new( + gossipsub::MessageAuthenticity::Signed(key.clone()), + self.gossipsub_config.clone(), + )?; + + let mdns = mdns::tokio::Behaviour::new( + mdns::Config::default(), + key.public().to_peer_id(), + )?; + Ok(MyBehaviour { gossipsub, mdns }) + })? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) + .build(); + self.swarm = Some(swarm); + } + _ => println!("Defaulting to MDNS discovery"), + } + Ok(self) + } + + pub fn join_topic(&mut self, topic_name: &str) -> Result<&Self, Box> { + let topic = gossipsub::IdentTopic::new(topic_name); + self.topic = Some(topic.clone()); + self.swarm + .as_mut() + .unwrap() + .behaviour_mut() + .gossipsub + .subscribe(&topic)?; + Ok(self) + } + + pub async fn start(&mut self) -> Result<(), Box> { + self.swarm + .as_mut() + .unwrap() + .listen_on("/ip4/0.0.0.0/udp/0/quic-v1".parse()?)?; + self.swarm + .as_mut() + .unwrap() + .listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; + loop { + select! { + Some(line) = self.cmd_rx.recv() => { + if let Err(e) = self.swarm.as_mut().unwrap() + .behaviour_mut().gossipsub + .publish(self.topic.as_mut().unwrap().clone(), line) { + println!("Publish error: {e:?}"); + } + } + event = self.swarm.as_mut().unwrap().select_next_some() => match event { + SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Discovered(list))) => { + for (peer_id, _multiaddr) in list { + // println!("mDNS discovered a new peer: {peer_id}"); + self.swarm.as_mut().unwrap().behaviour_mut().gossipsub.add_explicit_peer(&peer_id); + } + }, + SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Expired(list))) => { + for (peer_id, _multiaddr) in list { + // println!("mDNS discover peer has expired: {peer_id}"); + self.swarm.as_mut().unwrap().behaviour_mut().gossipsub.remove_explicit_peer(&peer_id); + } + }, + SwarmEvent::Behaviour(MyBehaviourEvent::Gossipsub(gossipsub::Event::Message { + propagation_source: peer_id, + message_id: id, + message, + })) => { + // println!( + // "Got message with id: {id} from peer: {peer_id}", + // ); + // println!("{:?}", message); + self.evt_tx.send(message.data).await?; + }, + SwarmEvent::NewListenAddr { address, .. } => { + // println!("Local node is listening on {address}"); + } + _ => {} + } + } + } + } +} + +// #[tokio::main] +// async fn main() -> Result<(), Box> { + +// let mut p2p = EnclaveRouter::new()?; +// p2p.connect_swarm("mdns".to_string())?; +// p2p.join_topic("enclave-keygen-01")?; +// p2p.start().await?; + +// println!("Hello, cipher world!"); +// Ok(()) +// } diff --git a/packages/ciphernode/core/src/p2p.rs b/packages/ciphernode/p2p/src/p2p.rs similarity index 96% rename from packages/ciphernode/core/src/p2p.rs rename to packages/ciphernode/p2p/src/p2p.rs index 7c8cb9a8..fd70c42f 100644 --- a/packages/ciphernode/core/src/p2p.rs +++ b/packages/ciphernode/p2p/src/p2p.rs @@ -1,15 +1,12 @@ use std::{collections::HashSet, error::Error}; +use crate::libp2p_router::EnclaveRouter; /// Actor for connecting to an libp2p client via it's mpsc channel interface /// This Actor should be responsible for use actix::prelude::*; -use p2p::EnclaveRouter; use tokio::sync::mpsc::{Receiver, Sender}; -use crate::{ - eventbus::{EventBus, Subscribe}, - events::{EnclaveEvent, EventId}, -}; +use enclave_core::{EnclaveEvent, EventBus, EventId, Subscribe}; pub struct P2p { bus: Addr, diff --git a/packages/ciphernode/rendezvous/Cargo.toml b/packages/ciphernode/rendezvous/Cargo.toml index 23a35b16..2d573bd6 100644 --- a/packages/ciphernode/rendezvous/Cargo.toml +++ b/packages/ciphernode/rendezvous/Cargo.toml @@ -8,14 +8,23 @@ repository = "https://github.com/gnosisguild/enclave/packages/ciphernode" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -async-std = { version = "1.12", features = ["attributes"] } -async-trait = "0.1" -futures = "0.3.30" -libp2p = { version = "0.53.2", features = [ "async-std", "identify", "macros", "noise", "ping", "rendezvous", "tcp", "tokio", "yamux"] } -tokio = { version = "1.38", features = ["rt-multi-thread", "macros", "time"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - +fhe = { workspace = true } +fhe-traits = { workspace = true } +fhe-util = { workspace = true } +async-std = { workspace = true, features = ["attributes"] } +async-trait = { workspace = true } +futures = { workspace = true } +libp2p = { workspace = true, features = [ + "async-std", + "identify", + "macros", + "noise", + "ping", + "rendezvous", + "tcp", + "tokio", + "yamux", +] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/packages/ciphernode/router/Cargo.toml b/packages/ciphernode/router/Cargo.toml new file mode 100644 index 00000000..aef11dc7 --- /dev/null +++ b/packages/ciphernode/router/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "router" +version = "0.1.0" +edition = "2021" + +[dependencies] +actix = { workspace = true } +enclave-core = { path = "../core" } +sortition = { path = "../sortition" } +fhe = { path = "../fhe" } +data = { path = "../data" } +keyshare = { path = "../keyshare" } +aggregator = { path = "../aggregator" } diff --git a/packages/ciphernode/core/src/cipernode_selector.rs b/packages/ciphernode/router/src/ciphernode_selector.rs similarity index 79% rename from packages/ciphernode/core/src/cipernode_selector.rs rename to packages/ciphernode/router/src/ciphernode_selector.rs index 3c31667d..d3cacdb2 100644 --- a/packages/ciphernode/core/src/cipernode_selector.rs +++ b/packages/ciphernode/router/src/ciphernode_selector.rs @@ -1,13 +1,13 @@ +/// CiphernodeSelector is an actor that determines if a ciphernode is part of a committee and if so +/// forwards a CiphernodeSelected event to the event bus use actix::prelude::*; - -use crate::{ - CiphernodeSelected, EnclaveEvent, EventBus, GetHasNode, Sortition, Subscribe, -}; +use enclave_core::{CiphernodeSelected, EnclaveEvent, EventBus, Subscribe}; +use sortition::{GetHasNode, Sortition}; pub struct CiphernodeSelector { bus: Addr, sortition: Addr, - address: String, + address: String, } impl Actor for CiphernodeSelector { @@ -19,17 +19,14 @@ impl CiphernodeSelector { Self { bus, sortition, - address:address.to_owned(), + address: address.to_owned(), } } pub fn attach(bus: Addr, sortition: Addr, address: &str) -> Addr { let addr = CiphernodeSelector::new(bus.clone(), sortition, address).start(); - bus.do_send(Subscribe::new( - "E3Requested", - addr.clone().recipient(), - )); + bus.do_send(Subscribe::new("E3Requested", addr.clone().recipient())); addr } diff --git a/packages/ciphernode/core/src/committee_meta.rs b/packages/ciphernode/router/src/committee_meta.rs similarity index 60% rename from packages/ciphernode/core/src/committee_meta.rs rename to packages/ciphernode/router/src/committee_meta.rs index 603c6baa..24103ab2 100644 --- a/packages/ciphernode/core/src/committee_meta.rs +++ b/packages/ciphernode/router/src/committee_meta.rs @@ -1,4 +1,6 @@ -use crate::{ActorFactory, E3Requested, EnclaveEvent, Seed}; +use enclave_core::{E3Requested, EnclaveEvent, Seed}; + +use super::EventHook; #[derive(Clone, Debug, PartialEq, Eq)] pub struct CommitteeMeta { @@ -9,15 +11,13 @@ pub struct CommitteeMeta { pub struct CommitteeMetaFactory; impl CommitteeMetaFactory { - pub fn create() -> ActorFactory { + pub fn create() -> EventHook { Box::new(move |ctx, evt| { - let EnclaveEvent::E3Requested { data, .. }: crate::EnclaveEvent = evt else { + let EnclaveEvent::E3Requested { data, .. } = evt else { return; }; let E3Requested { - threshold_m, - seed, - .. + threshold_m, seed, .. } = data; ctx.meta = Some(CommitteeMeta { threshold_m, seed }); diff --git a/packages/ciphernode/core/src/e3_request.rs b/packages/ciphernode/router/src/e3_request_router.rs similarity index 57% rename from packages/ciphernode/core/src/e3_request.rs rename to packages/ciphernode/router/src/e3_request_router.rs index 0df4c472..0d46c689 100644 --- a/packages/ciphernode/core/src/e3_request.rs +++ b/packages/ciphernode/router/src/e3_request_router.rs @@ -1,21 +1,16 @@ -use std::{collections::HashMap, sync::Arc}; +use crate::CommitteeMetaFactory; -use actix::{Actor, Addr, Context, Handler, Recipient}; +use super::CommitteeMeta; +use aggregator::PlaintextAggregator; +use aggregator::PublicKeyAggregator; +use enclave_core::{E3id, EnclaveEvent, EventBus, Subscribe}; +use fhe::Fhe; +use keyshare::Keyshare; -use crate::{ - Keyshare, CommitteeMeta, E3id, EnclaveEvent, EventBus, Fhe, PlaintextAggregator, - PublicKeyAggregator, Subscribe, -}; +use actix::{Actor, Addr, Context, Handler, Recipient}; +use std::{collections::HashMap, sync::Arc}; -#[derive(Default)] -// TODO: Set this up with a Typestate pattern -pub struct E3RequestContext { - pub keyshare: Option>, - pub fhe: Option>, - pub plaintext: Option>, - pub publickey: Option>, - pub meta: Option, -} +/// Helper class to buffer events for downstream instances incase events arrive in the wrong order #[derive(Default)] struct EventBuffer { buffer: HashMap>, @@ -34,6 +29,17 @@ impl EventBuffer { } } +/// Context that is set to each event hook. Hooks can use this context to gather dependencies if +/// they need to instantiate struct instances or actors. +#[derive(Default)] +pub struct E3RequestContext { + pub keyshare: Option>, + pub fhe: Option>, + pub plaintext: Option>, + pub publickey: Option>, + pub meta: Option, +} + impl E3RequestContext { fn recipients(&self) -> Vec<(String, Option>)> { vec![ @@ -66,54 +72,35 @@ impl E3RequestContext { } } -pub type ActorFactory = Box; +/// Format of the hook that needs to be passed to E3RequestRouter +pub type EventHook = Box; -// TODO: setup typestate pattern so that we have to place factories within correct order of +/// E3RequestRouter will register hooks that receive an E3_id specific context. After hooks +/// have run e3_id specific messages are forwarded to all instances on the context. This enables +/// hooks to lazily register instances that have the correct dependencies available per e3_id +/// request +// TODO: setup typestate pattern so that we have to place hooks within correct order of // dependencies -pub struct E3RequestManager { +pub struct E3RequestRouter { contexts: HashMap, - factories: Vec, + hooks: Vec, buffer: EventBuffer, } -impl E3RequestManager { - pub fn builder(bus: Addr) -> E3RequestManagerBuilder { - E3RequestManagerBuilder { - bus, - factories: vec![], - } - } -} +impl E3RequestRouter { + pub fn builder(bus: Addr) -> E3RequestRouterBuilder { + let builder = E3RequestRouterBuilder { bus, hooks: vec![] }; -pub struct E3RequestManagerBuilder { - pub bus: Addr, - pub factories: Vec, -} -impl E3RequestManagerBuilder { - pub fn add_hook(mut self, listener: ActorFactory) -> Self { - self.factories.push(listener); - self - } - - pub fn build(self) -> Addr { - let e3r = E3RequestManager { - contexts: HashMap::new(), - factories: self.factories, - buffer: EventBuffer::default(), - }; - - let addr = e3r.start(); - self.bus - .do_send(Subscribe::new("*", addr.clone().recipient())); - addr + // Everything needs the committe meta factory so adding it here by default + builder.add_hook(CommitteeMetaFactory::create()) } } -impl Actor for E3RequestManager { +impl Actor for E3RequestRouter { type Context = Context; } -impl Handler for E3RequestManager { +impl Handler for E3RequestRouter { type Result = (); fn handle(&mut self, msg: EnclaveEvent, _: &mut Self::Context) -> Self::Result { let Some(e3_id) = msg.get_e3_id() else { @@ -122,10 +109,35 @@ impl Handler for E3RequestManager { let context = self.contexts.entry(e3_id).or_default(); - for factory in &mut self.factories { - factory(context, msg.clone()); + for hook in &mut self.hooks { + hook(context, msg.clone()); } context.forward_message(&msg, &mut self.buffer); } } + +/// Builder for E3RequestRouter +pub struct E3RequestRouterBuilder { + pub bus: Addr, + pub hooks: Vec, +} +impl E3RequestRouterBuilder { + pub fn add_hook(mut self, listener: EventHook) -> Self { + self.hooks.push(listener); + self + } + + pub fn build(self) -> Addr { + let e3r = E3RequestRouter { + contexts: HashMap::new(), + hooks: self.hooks, + buffer: EventBuffer::default(), + }; + + let addr = e3r.start(); + self.bus + .do_send(Subscribe::new("*", addr.clone().recipient())); + addr + } +} diff --git a/packages/ciphernode/router/src/hooks.rs b/packages/ciphernode/router/src/hooks.rs new file mode 100644 index 00000000..f904f8e0 --- /dev/null +++ b/packages/ciphernode/router/src/hooks.rs @@ -0,0 +1,111 @@ +use crate::EventHook; +use actix::{Actor, Addr}; +use aggregator::{PlaintextAggregator, PublicKeyAggregator}; +use data::Data; +use enclave_core::{E3Requested, EnclaveEvent, EventBus}; +use fhe::{Fhe, SharedRng}; +use keyshare::Keyshare; +use sortition::Sortition; +use std::sync::Arc; + +pub struct LazyFhe; + +impl LazyFhe { + pub fn create(rng: SharedRng) -> EventHook { + Box::new(move |ctx, evt| { + // Saving the fhe on Committee Requested + let EnclaveEvent::E3Requested { data, .. } = evt else { + return; + }; + let E3Requested { params, seed, .. } = data; + + ctx.fhe = Some(Arc::new( + Fhe::from_encoded(¶ms, seed, rng.clone()).unwrap(), + )); + }) + } +} + +pub struct LazyKeyshare; +impl LazyKeyshare { + pub fn create(bus: Addr, data: Addr, address: &str) -> EventHook { + let address = address.to_string(); + Box::new(move |ctx, evt| { + // Save Ciphernode on CiphernodeSelected + let EnclaveEvent::CiphernodeSelected { .. } = evt else { + return; + }; + + let Some(ref fhe) = ctx.fhe else { + return; + }; + + ctx.keyshare = + Some(Keyshare::new(bus.clone(), data.clone(), fhe.clone(), &address).start()) + }) + } +} + +pub struct LazyPlaintextAggregator; +impl LazyPlaintextAggregator { + pub fn create(bus: Addr, sortition: Addr) -> EventHook { + Box::new(move |ctx, evt| { + // Save plaintext aggregator + let EnclaveEvent::CiphertextOutputPublished { data, .. } = evt else { + return; + }; + let Some(ref fhe) = ctx.fhe else { + return; + }; + let Some(ref meta) = ctx.meta else { + return; + }; + + ctx.plaintext = Some( + PlaintextAggregator::new( + fhe.clone(), + bus.clone(), + sortition.clone(), + data.e3_id, + meta.threshold_m, + meta.seed, + data.ciphertext_output, + ) + .start(), + ); + }) + } +} + +pub struct LazyPublicKeyAggregator; +impl LazyPublicKeyAggregator { + pub fn create(bus: Addr, sortition: Addr) -> EventHook { + Box::new(move |ctx, evt| { + // Saving the publickey aggregator with deps on E3Requested + let EnclaveEvent::E3Requested { data, .. } = evt else { + return; + }; + + let Some(ref fhe) = ctx.fhe else { + println!("fhe was not on ctx"); + return; + }; + let Some(ref meta) = ctx.meta else { + println!("meta was not on ctx"); + return; + }; + + ctx.publickey = Some( + PublicKeyAggregator::new( + fhe.clone(), + bus.clone(), + sortition.clone(), + data.e3_id, + meta.threshold_m, + meta.seed, + ) + .start(), + ); + }) + } +} diff --git a/packages/ciphernode/router/src/lib.rs b/packages/ciphernode/router/src/lib.rs new file mode 100644 index 00000000..1f9fae2f --- /dev/null +++ b/packages/ciphernode/router/src/lib.rs @@ -0,0 +1,9 @@ +mod ciphernode_selector; +mod committee_meta; +mod e3_request_router; +mod hooks; + +pub use ciphernode_selector::*; +pub use committee_meta::*; +pub use e3_request_router::*; +pub use hooks::*; diff --git a/packages/ciphernode/scripts/launch.sh b/packages/ciphernode/scripts/launch.sh index 547e3dbb..d0afb599 100755 --- a/packages/ciphernode/scripts/launch.sh +++ b/packages/ciphernode/scripts/launch.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -RUSTFLAGS="-A warnings" cargo run --bin node -- "$@" +RUSTFLAGS="-A warnings" cargo run --bin enclave -- "$@" diff --git a/packages/ciphernode/sortition/Cargo.toml b/packages/ciphernode/sortition/Cargo.toml index 5680e858..8e540191 100644 --- a/packages/ciphernode/sortition/Cargo.toml +++ b/packages/ciphernode/sortition/Cargo.toml @@ -9,12 +9,8 @@ path = "src/lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -num = "0.4.3" -rand = "0.8.5" - -# Ethereum -futures-util = "0.3" -eyre = "0.6" -alloy = { version = "0.3.3", features = ["full"] } -alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } -alloy-sol-types = { version = "0.6" } +num = { workspace = true } +rand = { workspace = true } +alloy = { workspace = true, features = ["full"] } +actix = { workspace = true } +enclave-core = { path = "../core" } diff --git a/packages/ciphernode/sortition/src/distance.rs b/packages/ciphernode/sortition/src/distance.rs index bf11f405..421f8130 100644 --- a/packages/ciphernode/sortition/src/distance.rs +++ b/packages/ciphernode/sortition/src/distance.rs @@ -35,4 +35,3 @@ impl DistanceSortition { result } } - diff --git a/packages/ciphernode/sortition/src/index.rs b/packages/ciphernode/sortition/src/index.rs index da7162cc..3c2bcf0e 100644 --- a/packages/ciphernode/sortition/src/index.rs +++ b/packages/ciphernode/sortition/src/index.rs @@ -8,7 +8,11 @@ pub struct IndexSortition { impl IndexSortition { pub fn new(random_seed: u64, num_nodes: usize, size: usize) -> Self { - Self { random_seed, num_nodes, size } + Self { + random_seed, + num_nodes, + size, + } } fn get_committee(&mut self) -> Vec { @@ -33,4 +37,4 @@ impl IndexSortition { // Return the leaf indices of the selected committee committee } -} \ No newline at end of file +} diff --git a/packages/ciphernode/sortition/src/lib.rs b/packages/ciphernode/sortition/src/lib.rs index 6a99e9df..792f64fc 100644 --- a/packages/ciphernode/sortition/src/lib.rs +++ b/packages/ciphernode/sortition/src/lib.rs @@ -4,6 +4,8 @@ mod distance; mod index; +mod sortition; pub use distance::*; -pub use index::*; \ No newline at end of file +pub use index::*; +pub use sortition::*; diff --git a/packages/ciphernode/core/src/sortition.rs b/packages/ciphernode/sortition/src/sortition.rs similarity index 96% rename from packages/ciphernode/core/src/sortition.rs rename to packages/ciphernode/sortition/src/sortition.rs index 2fbd6418..ca33e3dd 100644 --- a/packages/ciphernode/core/src/sortition.rs +++ b/packages/ciphernode/sortition/src/sortition.rs @@ -1,9 +1,8 @@ use std::collections::HashSet; +use crate::DistanceSortition; use actix::prelude::*; -use sortition::DistanceSortition; - -use crate::{CiphernodeAdded, CiphernodeRemoved, EnclaveEvent, EventBus, Seed, Subscribe}; +use enclave_core::{CiphernodeAdded, CiphernodeRemoved, EnclaveEvent, EventBus, Seed, Subscribe}; #[derive(Message, Clone, Debug, PartialEq, Eq)] #[rtype(result = "bool")] @@ -66,7 +65,6 @@ impl SortitionList for SortitionModule { #[rtype(result = "Vec")] pub struct GetNodes; - pub struct Sortition { list: SortitionModule, } @@ -138,4 +136,3 @@ impl Handler for Sortition { self.get_nodes() } } - diff --git a/packages/ciphernode/test_helpers/Cargo.toml b/packages/ciphernode/test_helpers/Cargo.toml new file mode 100644 index 00000000..52fa85a7 --- /dev/null +++ b/packages/ciphernode/test_helpers/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "test-helpers" +version = "0.1.0" +edition = "2021" + +[dependencies] +actix = { workspace = true } +enclave-core = { path = "../core" } +fhe = { path = "../fhe" } +bincode = { workspace = true } +clap = { workspace = true, features = ["derive"] } +fhe_rs = { workspace = true } +fhe-traits = { workspace = true } +rand_chacha = { workspace = true } +rand = { workspace = true } diff --git a/packages/ciphernode/enclave_node/src/bin/fake_encrypt.rs b/packages/ciphernode/test_helpers/src/bin/fake_encrypt.rs similarity index 94% rename from packages/ciphernode/enclave_node/src/bin/fake_encrypt.rs rename to packages/ciphernode/test_helpers/src/bin/fake_encrypt.rs index 9a1531f2..e1947b58 100644 --- a/packages/ciphernode/enclave_node/src/bin/fake_encrypt.rs +++ b/packages/ciphernode/test_helpers/src/bin/fake_encrypt.rs @@ -1,7 +1,7 @@ // This is a test script designed to encrypt some fixed data to a fhe public key use clap::Parser; -use enclave_core::setup_bfv_params; -use fhe::bfv::{Encoding, Plaintext, PublicKey}; +use fhe::setup_bfv_params; +use fhe_rs::bfv::{Encoding, Plaintext, PublicKey}; use fhe_traits::{DeserializeParametrized, FheEncoder, FheEncrypter, Serialize}; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; diff --git a/packages/ciphernode/enclave_node/src/bin/pack_e3_params.rs b/packages/ciphernode/test_helpers/src/bin/pack_e3_params.rs similarity index 96% rename from packages/ciphernode/enclave_node/src/bin/pack_e3_params.rs rename to packages/ciphernode/test_helpers/src/bin/pack_e3_params.rs index fa0ff8ce..d35ecc1e 100644 --- a/packages/ciphernode/enclave_node/src/bin/pack_e3_params.rs +++ b/packages/ciphernode/test_helpers/src/bin/pack_e3_params.rs @@ -1,5 +1,5 @@ use clap::{command, Parser}; -use enclave_core::encode_bfv_params; +use fhe::encode_bfv_params; use std::{error::Error, num::ParseIntError, process}; fn parse_hex(arg: &str) -> Result { diff --git a/packages/ciphernode/test_helpers/src/lib.rs b/packages/ciphernode/test_helpers/src/lib.rs new file mode 100644 index 00000000..fe3792c5 --- /dev/null +++ b/packages/ciphernode/test_helpers/src/lib.rs @@ -0,0 +1,7 @@ +mod plaintext_writer; +mod public_key_writer; +mod utils; + +pub use plaintext_writer::*; +pub use public_key_writer::*; +pub use utils::*; diff --git a/packages/ciphernode/core/src/plaintext_writer.rs b/packages/ciphernode/test_helpers/src/plaintext_writer.rs similarity index 90% rename from packages/ciphernode/core/src/plaintext_writer.rs rename to packages/ciphernode/test_helpers/src/plaintext_writer.rs index 29ad330b..967b15d2 100644 --- a/packages/ciphernode/core/src/plaintext_writer.rs +++ b/packages/ciphernode/test_helpers/src/plaintext_writer.rs @@ -1,5 +1,6 @@ -use crate::{write_file_with_dirs, EnclaveEvent, EventBus, Subscribe}; +use super::write_file_with_dirs; use actix::{Actor, Addr, Context, Handler}; +use enclave_core::{EnclaveEvent, EventBus, Subscribe}; pub struct PlaintextWriter { path: String, @@ -34,8 +35,7 @@ impl Handler for PlaintextWriter { // NOTE: panicking is kind of what we want here for now as we don't really need to handle the // error yet not knowing if this feature will be in production - write_file_with_dirs(&self.path, format!("{}", contents.join(",")).as_bytes()) - .unwrap(); + write_file_with_dirs(&self.path, format!("{}", contents.join(",")).as_bytes()).unwrap(); } } } diff --git a/packages/ciphernode/core/src/public_key_writer.rs b/packages/ciphernode/test_helpers/src/public_key_writer.rs similarity index 91% rename from packages/ciphernode/core/src/public_key_writer.rs rename to packages/ciphernode/test_helpers/src/public_key_writer.rs index 55a72541..e797dd73 100644 --- a/packages/ciphernode/core/src/public_key_writer.rs +++ b/packages/ciphernode/test_helpers/src/public_key_writer.rs @@ -1,5 +1,6 @@ -use crate::{write_file_with_dirs, EnclaveEvent, EventBus, Subscribe}; +use super::write_file_with_dirs; use actix::{Actor, Addr, Context, Handler}; +use enclave_core::{EnclaveEvent, EventBus, Subscribe}; pub struct PublicKeyWriter { path: String, diff --git a/packages/ciphernode/test_helpers/src/utils.rs b/packages/ciphernode/test_helpers/src/utils.rs new file mode 100644 index 00000000..f100610b --- /dev/null +++ b/packages/ciphernode/test_helpers/src/utils.rs @@ -0,0 +1,21 @@ +use std::{fs, io::Write, path::Path}; +pub fn write_file_with_dirs(path: &str, content: &[u8]) -> std::io::Result<()> { + let abs_path = if Path::new(path).is_absolute() { + Path::new(path).to_path_buf() + } else { + let cwd = std::env::current_dir()?; + cwd.join(path) + }; + + // Ensure the directory structure exists + if let Some(parent) = abs_path.parent() { + fs::create_dir_all(parent)?; + } + + // Open the file (creates it if it doesn't exist) and write the content + let mut file = fs::File::create(&abs_path)?; + file.write_all(content)?; + + println!("File written successfully: {:?}", abs_path); + Ok(()) +} diff --git a/packages/ciphernode/tests/Cargo.toml b/packages/ciphernode/tests/Cargo.toml new file mode 100644 index 00000000..cd179984 --- /dev/null +++ b/packages/ciphernode/tests/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "tests" +version = "0.1.0" +edition = "2021" + +[dependencies] +p2p = { path = "../p2p" } +bfv = { path = "../bfv" } +sortition = { path = "../sortition" } +enclave-core = { path = "../core" } +evm = { path = "../evm" } +logger = { path = "../logger" } +fhe = { path = "../fhe" } +data = { path = "../data" } +keyshare = { path = "../keyshare" } +aggregator = { path = "../aggregator" } +router = { path = "../router" } +test-helpers = { path = "../test_helpers" } +fhe_rs = { workspace = true } +fhe-traits = { workspace = true } +fhe-util = { workspace = true } +async-std = { workspace = true } +tokio = { workspace = true } +actix-rt = { workspace = true } +alloy-primitives = { workspace = true } +alloy = { workspace = true } +clap = { workspace = true } +rand_chacha = { workspace = true } +rand = { workspace = true } +bincode = { workspace = true } +base64 = { workspace = true } +actix = { workspace = true } +anyhow = { workspace = true } diff --git a/packages/ciphernode/tests/src/main.rs b/packages/ciphernode/tests/src/main.rs new file mode 100644 index 00000000..4c8d6791 --- /dev/null +++ b/packages/ciphernode/tests/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Intentially left blank"); +} diff --git a/packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs b/packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs new file mode 100644 index 00000000..c9a45590 --- /dev/null +++ b/packages/ciphernode/tests/tests/test_aggregation_and_decryption.rs @@ -0,0 +1,344 @@ +use data::Data; +use enclave_core::{ + CiphernodeAdded, CiphernodeSelected, CiphertextOutputPublished, DecryptionshareCreated, + E3Requested, E3id, EnclaveEvent, EventBus, GetHistory, KeyshareCreated, PlaintextAggregated, + PublicKeyAggregated, ResetHistory, Seed, +}; +use fhe::{setup_crp_params, ParamsWithCrp, SharedRng}; +use logger::SimpleLogger; +use p2p::P2p; +use router::{ + CiphernodeSelector, E3RequestRouter, LazyFhe, LazyKeyshare, LazyPlaintextAggregator, + LazyPublicKeyAggregator, +}; +use sortition::Sortition; + +use actix::prelude::*; +use alloy::primitives::Address; +use anyhow::*; +use fhe_rs::{ + bfv::{BfvParameters, Encoding, Plaintext, PublicKey, SecretKey}, + mbfv::{AggregateIter, CommonRandomPoly, DecryptionShare, PublicKeyShare}, +}; +use fhe_traits::{FheEncoder, FheEncrypter, Serialize}; +use rand::Rng; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; +use std::{sync::Arc, time::Duration}; +use tokio::sync::Mutex; +use tokio::{sync::mpsc::channel, time::sleep}; + +// Simulating a local node +async fn setup_local_ciphernode(bus: Addr, rng: SharedRng, logging: bool, addr: &str) { + // create data actor for saving data + let data = Data::new(logging).start(); // TODO: Use a sled backed Data Actor + + // create ciphernode actor for managing ciphernode flow + let sortition = Sortition::attach(bus.clone()); + CiphernodeSelector::attach(bus.clone(), sortition.clone(), addr); + + E3RequestRouter::builder(bus.clone()) + .add_hook(LazyFhe::create(rng)) + .add_hook(LazyPublicKeyAggregator::create( + bus.clone(), + sortition.clone(), + )) + .add_hook(LazyPlaintextAggregator::create( + bus.clone(), + sortition.clone(), + )) + .add_hook(LazyKeyshare::create(bus.clone(), data.clone(), addr)) + .build(); + + SimpleLogger::attach(addr, bus.clone()); +} + +fn generate_pk_share( + params: Arc, + crp: CommonRandomPoly, + rng: SharedRng, +) -> Result<(PublicKeyShare, SecretKey)> { + let sk = SecretKey::random(¶ms, &mut *rng.lock().unwrap()); + let pk = PublicKeyShare::new(&sk, crp.clone(), &mut *rng.lock().unwrap())?; + Ok((pk, sk)) +} + +#[actix::test] +async fn test_public_key_aggregation_and_decryption() -> Result<()> { + // Setup EventBus + let bus = EventBus::new(true).start(); + let rng = Arc::new(std::sync::Mutex::new(ChaCha20Rng::seed_from_u64(42))); + let seed = Seed(ChaCha20Rng::seed_from_u64(123).get_seed()); + + let eth_addrs: Vec = (0..3) + .map(|_| Address::from_slice(&rand::thread_rng().gen::<[u8; 20]>()).to_string()) + .collect(); + + setup_local_ciphernode(bus.clone(), rng.clone(), true, ð_addrs[0]).await; + setup_local_ciphernode(bus.clone(), rng.clone(), true, ð_addrs[1]).await; + setup_local_ciphernode(bus.clone(), rng.clone(), true, ð_addrs[2]).await; + + let e3_id = E3id::new("1234"); + + let ParamsWithCrp { + crp_bytes, params, .. + } = setup_crp_params( + &[0x3FFFFFFF000001], + 2048, + 1032193, + Arc::new(std::sync::Mutex::new(ChaCha20Rng::from_seed( + seed.clone().into(), + ))), + ); + + let regevt_1 = EnclaveEvent::from(CiphernodeAdded { + address: eth_addrs[0].clone(), + index: 0, + num_nodes: 1, + }); + + bus.send(regevt_1.clone()).await?; + + let regevt_2 = EnclaveEvent::from(CiphernodeAdded { + address: eth_addrs[1].clone(), + index: 1, + num_nodes: 2, + }); + + bus.send(regevt_2.clone()).await?; + + let regevt_3 = EnclaveEvent::from(CiphernodeAdded { + address: eth_addrs[2].clone(), + index: 2, + num_nodes: 3, + }); + + bus.send(regevt_3.clone()).await?; + + let event = EnclaveEvent::from(E3Requested { + e3_id: e3_id.clone(), + threshold_m: 3, + seed: seed.clone(), + params: params.to_bytes(), + }); + // Send the computation requested event + bus.send(event.clone()).await?; + + // Test that we cannot send the same event twice + bus.send(event).await?; + + let history = bus.send(GetHistory).await?; + + let rng_test = Arc::new(std::sync::Mutex::new(ChaCha20Rng::seed_from_u64(42))); + + let crpoly = CommonRandomPoly::deserialize(&crp_bytes.clone(), ¶ms)?; + + let (p1, sk1) = generate_pk_share(params.clone(), crpoly.clone(), rng_test.clone())?; + let (p2, sk2) = generate_pk_share(params.clone(), crpoly.clone(), rng_test.clone())?; + let (p3, sk3) = generate_pk_share(params.clone(), crpoly.clone(), rng_test.clone())?; + + let pubkey: PublicKey = vec![p1.clone(), p2.clone(), p3.clone()] + .into_iter() + .aggregate()?; + + assert_eq!(history.len(), 9); + assert_eq!( + history, + vec![ + regevt_1, + regevt_2, + regevt_3, + EnclaveEvent::from(E3Requested { + e3_id: e3_id.clone(), + threshold_m: 3, + seed: seed.clone(), + params: params.to_bytes() + }), + EnclaveEvent::from(CiphernodeSelected { + e3_id: e3_id.clone(), + threshold_m: 3, + }), + EnclaveEvent::from(KeyshareCreated { + pubkey: p1.to_bytes(), + e3_id: e3_id.clone(), + node: eth_addrs[0].clone() + }), + EnclaveEvent::from(KeyshareCreated { + pubkey: p2.to_bytes(), + e3_id: e3_id.clone(), + node: eth_addrs[1].clone() + }), + EnclaveEvent::from(KeyshareCreated { + pubkey: p3.to_bytes(), + e3_id: e3_id.clone(), + node: eth_addrs[2].clone() + }), + EnclaveEvent::from(PublicKeyAggregated { + pubkey: pubkey.to_bytes(), + e3_id: e3_id.clone() + }) + ] + ); + + // Aggregate decryption + bus.send(ResetHistory).await?; + fn pad_end(input: &[u64], pad: u64, total: usize) -> Vec { + let len = input.len(); + let mut cop = input.to_vec(); + cop.extend(std::iter::repeat(pad).take(total - len)); + cop + } + // 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 yes = 1234u64; + let no = 873827u64; + + let raw_plaintext = vec![yes, no]; + let padded = &pad_end(&raw_plaintext, 0, 2048); + let expected_raw_plaintext = bincode::serialize(&padded)?; + let pt = Plaintext::try_encode(&raw_plaintext, Encoding::poly(), ¶ms)?; + + let ciphertext = pubkey.try_encrypt(&pt, &mut ChaCha20Rng::seed_from_u64(42))?; + + let event = EnclaveEvent::from(CiphertextOutputPublished { + ciphertext_output: ciphertext.to_bytes(), + e3_id: e3_id.clone(), + }); + + let arc_ct = Arc::new(ciphertext); + + let ds1 = DecryptionShare::new(&sk1, &arc_ct, &mut *rng_test.lock().unwrap())?.to_bytes(); + let ds2 = DecryptionShare::new(&sk2, &arc_ct, &mut *rng_test.lock().unwrap())?.to_bytes(); + let ds3 = DecryptionShare::new(&sk3, &arc_ct, &mut *rng_test.lock().unwrap())?.to_bytes(); + + // let ds1 = sk1 + bus.send(event.clone()).await?; + + sleep(Duration::from_millis(1)).await; // need to push to next tick + let history = bus.send(GetHistory).await?; + + assert_eq!(history.len(), 5); + + assert_eq!( + history, + vec![ + event.clone(), + EnclaveEvent::from(DecryptionshareCreated { + decryption_share: ds1.clone(), + e3_id: e3_id.clone(), + node: eth_addrs[0].clone() + }), + EnclaveEvent::from(DecryptionshareCreated { + decryption_share: ds2.clone(), + e3_id: e3_id.clone(), + node: eth_addrs[1].clone() + }), + EnclaveEvent::from(DecryptionshareCreated { + decryption_share: ds3.clone(), + e3_id: e3_id.clone(), + node: eth_addrs[2].clone() + }), + EnclaveEvent::from(PlaintextAggregated { + e3_id: e3_id.clone(), + decrypted_output: expected_raw_plaintext.clone() + }) + ] + ); + + Ok(()) +} + +#[actix::test] +async fn test_p2p_actor_forwards_events_to_network() -> Result<()> { + // Setup elements in test + let (tx, mut output) = channel(100); // Transmit byte events to the network + let (input, rx) = channel(100); // Receive byte events from the network + let bus = EventBus::new(true).start(); + P2p::spawn_and_listen(bus.clone(), tx.clone(), rx); + + // Capture messages from output on msgs vec + let msgs: Arc>>> = Arc::new(Mutex::new(Vec::new())); + let msgs_loop = msgs.clone(); + + tokio::spawn(async move { + while let Some(msg) = output.recv().await { + msgs_loop.lock().await.push(msg.clone()); + let _ = input.send(msg).await; + // loopback to simulate a rebroadcast message + // if this manages to broadcast an event to the + // event bus we will expect to see an extra event on + // the bus + } + }); + + let evt_1 = EnclaveEvent::from(PlaintextAggregated { + e3_id: E3id::new("1235"), + decrypted_output: vec![1, 2, 3, 4], + }); + + let evt_2 = EnclaveEvent::from(PlaintextAggregated { + e3_id: E3id::new("1236"), + decrypted_output: vec![1, 2, 3, 4], + }); + + let local_evt_3 = EnclaveEvent::from(CiphernodeSelected { + e3_id: E3id::new("1235"), + threshold_m: 3, + }); + + bus.do_send(evt_1.clone()); + bus.do_send(evt_2.clone()); + bus.do_send(local_evt_3.clone()); // This is a local event which should not be broadcast to the network + + sleep(Duration::from_millis(1)).await; // need to push to next tick + + // check the history of the event bus + let history = bus.send(GetHistory).await?; + + assert_eq!( + *msgs.lock().await, + vec![evt_1.to_bytes()?, evt_2.to_bytes()?], // notice no local events + "P2p did not transmit correct events to the network" + ); + + assert_eq!( + history, + vec![evt_1, evt_2, local_evt_3], // all local events that have been broadcast but no + // events from the loopback + "P2p must not retransmit forwarded event to event bus" + ); + + Ok(()) +} + +#[actix::test] +async fn test_p2p_actor_forwards_events_to_bus() -> Result<()> { + let seed = Seed(ChaCha20Rng::seed_from_u64(123).get_seed()); + + // Setup elements in test + let (tx, _) = channel(100); // Transmit byte events to the network + let (input, rx) = channel(100); // Receive byte events from the network + let bus = EventBus::new(true).start(); + P2p::spawn_and_listen(bus.clone(), tx.clone(), rx); + + // Capture messages from output on msgs vec + let event = EnclaveEvent::from(E3Requested { + e3_id: E3id::new("1235"), + threshold_m: 3, + seed: seed.clone(), + params: vec![1, 2, 3, 4], + }); + + // lets send an event from the network + let _ = input.send(event.to_bytes()?).await; + + sleep(Duration::from_millis(1)).await; // need to push to next tick + + // check the history of the event bus + let history = bus.send(GetHistory).await?; + + assert_eq!(history, vec![event]); + + Ok(()) +} diff --git a/tests/basic_integration/lib/prebuild.sh b/tests/basic_integration/lib/prebuild.sh index 6a765c9c..a3cd9e88 100755 --- a/tests/basic_integration/lib/prebuild.sh +++ b/tests/basic_integration/lib/prebuild.sh @@ -1,3 +1,3 @@ #!/usr/bin/env sh -cd packages/ciphernode && RUSTFLAGS="-A warnings" cargo build --bin fake_encrypt --bin node --bin aggregator; +cd packages/ciphernode && RUSTFLAGS="-A warnings" cargo build --bin fake_encrypt --bin enclave --bin aggregator; diff --git a/tests/basic_integration/test.sh b/tests/basic_integration/test.sh index 9b4c96c7..942a4576 100755 --- a/tests/basic_integration/test.sh +++ b/tests/basic_integration/test.sh @@ -12,19 +12,23 @@ if [[ "$ROOT_DIR" != "$(pwd)" ]]; then exit 1 fi + # Environment variables -export RPC_URL="ws://localhost:8545" +RPC_URL="ws://localhost:8545" + +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + # These contracts are based on the deterministic order of hardhat deploy # We _may_ wish to get these off the hardhat environment somehow? -export ENCLAVE_CONTRACT="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -export REGISTRY_CONTRACT="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -export REGISTRY_FILTER_CONTRACT="0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -export INPUT_VALIDATOR_CONTRACT="0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +ENCLAVE_CONTRACT="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +REGISTRY_CONTRACT="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +REGISTRY_FILTER_CONTRACT="0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +INPUT_VALIDATOR_CONTRACT="0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" # These are random addresses for now -export CIPHERNODE_ADDRESS_1="0x2546BcD3c84621e976D8185a91A922aE77ECEc30" -export CIPHERNODE_ADDRESS_2="0xbDA5747bFD65F08deb54cb465eB87D40e51B197E" -export CIPHERNODE_ADDRESS_3="0xdD2FD4581271e230360230F9337D5c0430Bf44C0" -export CIPHERNODE_ADDRESS_4="0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199" +CIPHERNODE_ADDRESS_1="0x2546BcD3c84621e976D8185a91A922aE77ECEc30" +CIPHERNODE_ADDRESS_2="0xbDA5747bFD65F08deb54cb465eB87D40e51B197E" +CIPHERNODE_ADDRESS_3="0xdD2FD4581271e230360230F9337D5c0430Bf44C0" +CIPHERNODE_ADDRESS_4="0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199" # Function to clean up background processes cleanup() { @@ -106,11 +110,11 @@ heading "Launch ciphernode $CIPHERNODE_ADDRESS_4" yarn ciphernode:launch --address $CIPHERNODE_ADDRESS_4 --rpc "$RPC_URL" --enclave-contract $ENCLAVE_CONTRACT --registry-contract $REGISTRY_CONTRACT & # NOTE: This node is configured to be an aggregator -yarn ciphernode:aggregator --rpc "$RPC_URL" --enclave-contract $ENCLAVE_CONTRACT --registry-contract $REGISTRY_CONTRACT --registry-filter-contract $REGISTRY_FILTER_CONTRACT --pubkey-write-path "$SCRIPT_DIR/output/pubkey.bin" --plaintext-write-path "$SCRIPT_DIR/output/plaintext.txt" & +PRIVATE_KEY=$PRIVATE_KEY yarn ciphernode:aggregator --rpc "$RPC_URL" --enclave-contract $ENCLAVE_CONTRACT --registry-contract $REGISTRY_CONTRACT --registry-filter-contract $REGISTRY_FILTER_CONTRACT --pubkey-write-path "$SCRIPT_DIR/output/pubkey.bin" --plaintext-write-path "$SCRIPT_DIR/output/plaintext.txt" & sleep 1 -waiton-files "$ROOT_DIR/packages/ciphernode/target/debug/node" "$ROOT_DIR/packages/ciphernode/target/debug/aggregator" "$ROOT_DIR/packages/ciphernode/target/debug/fake_encrypt" +waiton-files "$ROOT_DIR/packages/ciphernode/target/debug/enclave" "$ROOT_DIR/packages/ciphernode/target/debug/aggregator" "$ROOT_DIR/packages/ciphernode/target/debug/fake_encrypt" heading "Add ciphernode $CIPHERNODE_ADDRESS_1" yarn ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_1 --network localhost