diff --git a/fhevm-engine/Cargo.lock b/fhevm-engine/Cargo.lock index 98e8aa7b..5900ca7b 100644 --- a/fhevm-engine/Cargo.lock +++ b/fhevm-engine/Cargo.lock @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-stream" @@ -861,6 +861,14 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "executor" version = "0.1.0" +dependencies = [ + "clap", + "fhevm-engine-common", + "prost", + "tokio", + "tonic", + "tonic-build", +] [[package]] name = "fastrand" diff --git a/fhevm-engine/Cargo.toml b/fhevm-engine/Cargo.toml index 05c3a680..f2dd8d8d 100644 --- a/fhevm-engine/Cargo.toml +++ b/fhevm-engine/Cargo.toml @@ -3,5 +3,8 @@ resolver = "2" members = ["coprocessor", "executor", "fhevm-engine-common"] [workspace.dependencies] -tfhe = { version = "0.8.0-alpha.2", features = ["boolean", "shortint", "integer", "aarch64-unix", "zk-pok"] } +tfhe = { version = "0.8.0-alpha.2", features = ["boolean", "shortint", "integer", "aarch64-unix", "zk-pok", "experimental-force_fft_algo_dif4"] } clap = { version = "4.5", features = ["derive"] } +tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] } +prost = "0.13" +tonic = { version = "0.12", features = ["server"] } diff --git a/fhevm-engine/coprocessor/Cargo.toml b/fhevm-engine/coprocessor/Cargo.toml index 81ad6a63..d6a9cd51 100644 --- a/fhevm-engine/coprocessor/Cargo.toml +++ b/fhevm-engine/coprocessor/Cargo.toml @@ -5,9 +5,9 @@ edition = "2021" [dependencies] # Common dependencies -tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] } -prost = "0.13" -tonic = { version = "0.12", features = ["server"] } +tokio.workspace = true +prost.workspace = true +tonic.workspace = true # Optional dependencies tonic-web = "0.12" tonic-health = "0.12" diff --git a/fhevm-engine/coprocessor/src/main.rs b/fhevm-engine/coprocessor/src/main.rs index e8252515..1527c022 100644 --- a/fhevm-engine/coprocessor/src/main.rs +++ b/fhevm-engine/coprocessor/src/main.rs @@ -1,4 +1,4 @@ -use fhevm_engine_common::generate_fhe_keys; +use fhevm_engine_common::keys::{FhevmKeys, SerializedFhevmKeys}; use tokio::task::JoinSet; mod cli; @@ -82,15 +82,7 @@ async fn async_main( } fn generate_dump_fhe_keys() { - let output_dir = "fhevm-keys"; - println!("Generating keys..."); - let keys = generate_fhe_keys(); - println!("Creating directory {output_dir}"); - std::fs::create_dir_all(output_dir).unwrap(); - println!("Creating file {output_dir}/cks"); - std::fs::write(format!("{output_dir}/cks"), keys.client_key).unwrap(); - println!("Creating file {output_dir}/pks"); - std::fs::write(format!("{output_dir}/pks"), keys.compact_public_key).unwrap(); - println!("Creating file {output_dir}/sks"); - std::fs::write(format!("{output_dir}/sks"), keys.server_key).unwrap(); + let keys = FhevmKeys::new(); + let ser_keys: SerializedFhevmKeys = keys.into(); + ser_keys.save_to_disk(); } diff --git a/fhevm-engine/coprocessor/src/server.rs b/fhevm-engine/coprocessor/src/server.rs index 4172cced..55ea2bcf 100644 --- a/fhevm-engine/coprocessor/src/server.rs +++ b/fhevm-engine/coprocessor/src/server.rs @@ -13,8 +13,12 @@ use coprocessor::{DebugDecryptResponse, DebugDecryptResponseSingle, InputCiphert use sqlx::{query, Acquire}; use tonic::transport::Server; +pub mod common { + tonic::include_proto!("fhevm.common"); +} + pub mod coprocessor { - tonic::include_proto!("coprocessor"); + tonic::include_proto!("fhevm.coprocessor"); } pub struct CoprocessorService { diff --git a/fhevm-engine/coprocessor/src/tests/mod.rs b/fhevm-engine/coprocessor/src/tests/mod.rs index 3ef809c6..e1db56bd 100644 --- a/fhevm-engine/coprocessor/src/tests/mod.rs +++ b/fhevm-engine/coprocessor/src/tests/mod.rs @@ -4,8 +4,9 @@ use crate::server::coprocessor::async_computation_input::Input; use crate::server::coprocessor::fhevm_coprocessor_client::FhevmCoprocessorClient; use crate::server::coprocessor::{ AsyncComputation, AsyncComputationInput, AsyncComputeRequest, DebugDecryptRequest, - DebugEncryptRequest, DebugEncryptRequestSingle, FheOperation, + DebugEncryptRequest, DebugEncryptRequestSingle, }; +use crate::server::common::FheOperation; use tonic::metadata::MetadataValue; use utils::{default_api_key, wait_until_all_ciphertexts_computed}; diff --git a/fhevm-engine/coprocessor/src/tests/operators.rs b/fhevm-engine/coprocessor/src/tests/operators.rs index e3b7ba23..02b2653c 100644 --- a/fhevm-engine/coprocessor/src/tests/operators.rs +++ b/fhevm-engine/coprocessor/src/tests/operators.rs @@ -1,8 +1,9 @@ use crate::server::coprocessor::fhevm_coprocessor_client::FhevmCoprocessorClient; use crate::server::coprocessor::{ AsyncComputation, AsyncComputeRequest, DebugDecryptRequest, DebugEncryptRequest, - DebugEncryptRequestSingle, FheOperation, + DebugEncryptRequestSingle, }; +use crate::server::common::FheOperation; use crate::tests::utils::wait_until_all_ciphertexts_computed; use crate::{ server::coprocessor::{async_computation_input::Input, AsyncComputationInput}, diff --git a/fhevm-engine/executor/Cargo.toml b/fhevm-engine/executor/Cargo.toml index 56a3215c..2491bd0a 100644 --- a/fhevm-engine/executor/Cargo.toml +++ b/fhevm-engine/executor/Cargo.toml @@ -4,3 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] +clap.workspace = true +tokio.workspace = true +prost.workspace = true +tonic.workspace = true +fhevm-engine-common = { path = "../fhevm-engine-common" } + +[build-dependencies] +tonic-build = "0.12" diff --git a/fhevm-engine/executor/build.rs b/fhevm-engine/executor/build.rs new file mode 100644 index 00000000..86adc68b --- /dev/null +++ b/fhevm-engine/executor/build.rs @@ -0,0 +1,9 @@ +use std::{env, path::PathBuf}; + +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + tonic_build::configure() + .file_descriptor_set_path(out_dir.join("executor_descriptor.bin")) + .compile(&["../../proto/executor.proto"], &["../../proto"]) + .unwrap(); +} diff --git a/fhevm-engine/executor/src/cli.rs b/fhevm-engine/executor/src/cli.rs new file mode 100644 index 00000000..bdf4548d --- /dev/null +++ b/fhevm-engine/executor/src/cli.rs @@ -0,0 +1,18 @@ +use clap::Parser; + +#[derive(Parser, Debug, Clone)] +#[command(version, about, long_about = None)] +pub struct Args { + #[arg(long, default_value_t = 4)] + pub tokio_threads: usize, + + #[arg(long, default_value_t = 8)] + pub fhe_compute_threads: usize, + + #[arg(long, default_value = "127.0.0.1:50051")] + pub server_addr: String, +} + +pub fn parse_args() -> Args { + Args::parse() +} diff --git a/fhevm-engine/executor/src/lib.rs b/fhevm-engine/executor/src/lib.rs new file mode 100644 index 00000000..5a9b926e --- /dev/null +++ b/fhevm-engine/executor/src/lib.rs @@ -0,0 +1,2 @@ +pub mod cli; +pub mod server; diff --git a/fhevm-engine/executor/src/main.rs b/fhevm-engine/executor/src/main.rs index e7a11a96..2600cefc 100644 --- a/fhevm-engine/executor/src/main.rs +++ b/fhevm-engine/executor/src/main.rs @@ -1,3 +1,10 @@ -fn main() { - println!("Hello, world!"); +use std::error::Error; + +mod cli; +mod server; + +fn main() -> Result<(), Box> { + let args = cli::parse_args(); + server::start(&args)?; + Ok(()) } diff --git a/fhevm-engine/executor/src/server.rs b/fhevm-engine/executor/src/server.rs new file mode 100644 index 00000000..3cdd0d32 --- /dev/null +++ b/fhevm-engine/executor/src/server.rs @@ -0,0 +1,48 @@ +use std::error::Error; + +use executor::{ + fhevm_executor_server::{FhevmExecutor, FhevmExecutorServer}, + SyncComputeRequest, SyncComputeResponse, +}; +use tonic::{transport::Server, Request, Response}; + +mod common { + tonic::include_proto!("fhevm.common"); +} + +pub mod executor { + tonic::include_proto!("fhevm.executor"); +} + +pub fn start(args: &crate::cli::Args) -> Result<(), Box> { + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(args.tokio_threads) + .max_blocking_threads(args.fhe_compute_threads) + .enable_all() + .build()?; + + let executor = FhevmExecutorService::default(); + let addr = args.server_addr.parse().expect("server address"); + + runtime.block_on(async { + Server::builder() + .add_service(FhevmExecutorServer::new(executor)) + .serve(addr) + .await?; + Ok::<(), Box>(()) + })?; + Ok(()) +} + +#[derive(Default)] +pub struct FhevmExecutorService {} + +#[tonic::async_trait] +impl FhevmExecutor for FhevmExecutorService { + async fn sync_compute( + &self, + req: Request, + ) -> Result, tonic::Status> { + Ok(Response::new(SyncComputeResponse::default())) + } +} diff --git a/fhevm-engine/executor/tests/sync_compute.rs b/fhevm-engine/executor/tests/sync_compute.rs new file mode 100644 index 00000000..813deb3d --- /dev/null +++ b/fhevm-engine/executor/tests/sync_compute.rs @@ -0,0 +1,12 @@ +use executor::server::executor::{fhevm_executor_client::FhevmExecutorClient, SyncComputeRequest}; +use utils::TestInstance; + +mod utils; + +#[tokio::test] +async fn compute_on_ciphertexts() -> Result<(), Box> { + let test_instance = TestInstance::new(); + let mut client = FhevmExecutorClient::connect(test_instance.server_addr).await?; + let resp = client.sync_compute(SyncComputeRequest::default()).await?; + Ok(()) +} diff --git a/fhevm-engine/executor/tests/utils.rs b/fhevm-engine/executor/tests/utils.rs new file mode 100644 index 00000000..77ca40f1 --- /dev/null +++ b/fhevm-engine/executor/tests/utils.rs @@ -0,0 +1,27 @@ +use clap::Parser; +use executor::{cli::Args, server}; +use fhevm_engine_common::keys::{FhevmKeys, SerializedFhevmKeys}; + +pub struct TestInstance { + pub keys: FhevmKeys, + pub server_addr: String, +} + +impl TestInstance { + pub fn new() -> Self { + // Get defaults by parsing a cmd line without any arguments. + let args = Args::parse_from(&["test"]); + + let instance = TestInstance { + keys: SerializedFhevmKeys::load_from_disk().into(), + server_addr: format!("http://{}", args.server_addr), + }; + + std::thread::spawn(move || server::start(&args).expect("start server")); + + // TODO: a hacky way to wait for the server to start + std::thread::sleep(std::time::Duration::from_millis(150)); + + instance + } +} diff --git a/fhevm-engine/fhevm-engine-common/src/bin/generate_keys.rs b/fhevm-engine/fhevm-engine-common/src/bin/generate_keys.rs index 5cb7235b..212127b3 100644 --- a/fhevm-engine/fhevm-engine-common/src/bin/generate_keys.rs +++ b/fhevm-engine/fhevm-engine-common/src/bin/generate_keys.rs @@ -1,15 +1,7 @@ -use fhevm_engine_common::generate_fhe_keys; +use fhevm_engine_common::keys::{FhevmKeys, SerializedFhevmKeys}; fn main() { - let output_dir = "fhevm-keys"; - println!("Generating keys..."); - let keys = generate_fhe_keys(); - println!("Creating directory {output_dir}"); - std::fs::create_dir_all(output_dir).unwrap(); - println!("Creating file {output_dir}/cks"); - std::fs::write(format!("{output_dir}/cks"), keys.client_key).unwrap(); - println!("Creating file {output_dir}/pks"); - std::fs::write(format!("{output_dir}/pks"), keys.compact_public_key).unwrap(); - println!("Creating file {output_dir}/sks"); - std::fs::write(format!("{output_dir}/sks"), keys.server_key).unwrap(); + let keys = FhevmKeys::new(); + let ser_keys: SerializedFhevmKeys = keys.into(); + ser_keys.save_to_disk(); } diff --git a/fhevm-engine/fhevm-engine-common/src/keys.rs b/fhevm-engine/fhevm-engine-common/src/keys.rs new file mode 100644 index 00000000..540dadde --- /dev/null +++ b/fhevm-engine/fhevm-engine-common/src/keys.rs @@ -0,0 +1,104 @@ +use std::fs::read; + +use tfhe::{ + generate_keys, + shortint::parameters::{ + list_compression::COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64, + PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64, + }, + ClientKey, CompactPublicKey, ConfigBuilder, ServerKey, +}; + +pub struct FhevmKeys { + pub server_key: ServerKey, + pub client_key: Option, + pub compact_public_key: Option, +} + +pub struct SerializedFhevmKeys { + pub server_key: Vec, + pub client_key: Option>, + pub compact_public_key: Option>, +} + +impl FhevmKeys { + pub fn new() -> Self { + println!("Generating keys..."); + let config = + ConfigBuilder::with_custom_parameters(PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64) + .enable_compression(COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64) + .build(); + let (client_key, server_key) = generate_keys(config); + let compact_public_key = CompactPublicKey::new(&client_key); + FhevmKeys { + server_key, + client_key: Some(client_key), + compact_public_key: Some(compact_public_key), + } + } +} + +impl SerializedFhevmKeys { + const DIRECTORY: &'static str = "fhevm-keys"; + const SKS: &'static str = "fhevm-keys/sks"; + const CKS: &'static str = "fhevm-keys/cks"; + const PKS: &'static str = "fhevm-keys/pks"; + + pub fn save_to_disk(self) { + println!("Creating directory {}", Self::DIRECTORY); + std::fs::create_dir_all(Self::DIRECTORY).expect("create keys directory"); + + println!("Creating file {}", Self::SKS); + std::fs::write(format!("{}", Self::SKS), self.server_key).expect("write sks"); + + if self.client_key.is_some() { + println!("Creating file {}", Self::CKS); + std::fs::write(format!("{}", Self::CKS), self.client_key.unwrap()).expect("write cks"); + } + + if self.compact_public_key.is_some() { + println!("Creating file {}", Self::PKS); + std::fs::write(format!("{}", Self::PKS), self.compact_public_key.unwrap()) + .expect("write pks"); + } + } + + pub fn load_from_disk() -> Self { + let server_key = read(Self::SKS).expect("read server key"); + let client_key = read(Self::CKS); + let compact_public_key = read(Self::PKS); + SerializedFhevmKeys { + server_key, + client_key: client_key.ok(), + compact_public_key: compact_public_key.ok(), + } + } +} + +impl From for SerializedFhevmKeys { + fn from(f: FhevmKeys) -> Self { + SerializedFhevmKeys { + server_key: bincode::serialize(&f.server_key).expect("serialize server key"), + client_key: f + .client_key + .map(|c| bincode::serialize(&c).expect("serialize client key")), + compact_public_key: f + .compact_public_key + .map(|p| bincode::serialize(&p).expect("serialize compact public key")), + } + } +} + +impl From for FhevmKeys { + fn from(f: SerializedFhevmKeys) -> Self { + FhevmKeys { + server_key: bincode::deserialize(&f.server_key).expect("deserialize server key"), + client_key: f + .client_key + .map(|c| bincode::deserialize(&c).expect("deserialize client key")), + compact_public_key: f + .compact_public_key + .map(|p| bincode::deserialize(&p).expect("deserialize compact public key")), + } + } +} diff --git a/fhevm-engine/fhevm-engine-common/src/lib.rs b/fhevm-engine/fhevm-engine-common/src/lib.rs index 54181609..a8a4b244 100644 --- a/fhevm-engine/fhevm-engine-common/src/lib.rs +++ b/fhevm-engine/fhevm-engine-common/src/lib.rs @@ -1,35 +1,3 @@ -use tfhe::{ - generate_keys, - shortint::parameters::{ - list_compression::COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64, - PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64, - }, - CompactPublicKey, ConfigBuilder, -}; - +pub mod keys; pub mod tfhe_ops; pub mod types; - -pub struct FhevmKeys { - pub server_key: Vec, - pub client_key: Vec, - pub compact_public_key: Vec, -} - -pub fn generate_fhe_keys() -> FhevmKeys { - let config = - ConfigBuilder::with_custom_parameters(PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64) - .enable_compression(COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64) - .build(); - let (client_key, server_key) = generate_keys(config); - let public_key = CompactPublicKey::new(&client_key); - - let client_key = bincode::serialize(&client_key).unwrap(); - let server_key = bincode::serialize(&server_key).unwrap(); - let compact_public_key = bincode::serialize(&public_key).unwrap(); - FhevmKeys { - server_key, - client_key, - compact_public_key, - } -} diff --git a/proto/common.proto b/proto/common.proto new file mode 100644 index 00000000..cbc27a11 --- /dev/null +++ b/proto/common.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.fhevmcommon"; +option java_outer_classname = "FhevmCommon"; + +package fhevm.common; + +enum FheOperation { + FHE_ADD = 0; + FHE_SUB = 1; + FHE_MUL = 2; + FHE_DIV = 3; + FHE_REM = 4; + FHE_BIT_AND = 5; + FHE_BIT_OR = 6; + FHE_BIT_XOR = 7; + FHE_SHL = 8; + FHE_SHR = 9; + FHE_ROTL = 10; + FHE_ROTR = 11; + FHE_EQ = 12; + FHE_NE = 13; + FHE_GE = 14; + FHE_GT = 15; + FHE_LE = 16; + FHE_LT = 17; + FHE_MIN = 18; + FHE_MAX = 19; + FHE_NEG = 20; + FHE_NOT = 21; + FHE_CAST = 30; + FHE_IF_THEN_ELSE = 31; +} diff --git a/proto/coprocessor.proto b/proto/coprocessor.proto index 90124b4b..10ac5d6b 100644 --- a/proto/coprocessor.proto +++ b/proto/coprocessor.proto @@ -4,7 +4,9 @@ option java_multiple_files = true; option java_package = "io.grpc.fhevmcoprocessor"; option java_outer_classname = "FhevmCoprocessor"; -package coprocessor; +package fhevm.coprocessor; + +import "common.proto"; service FhevmCoprocessor { rpc AsyncCompute (AsyncComputeRequest) returns (GenericResponse) {} @@ -40,35 +42,8 @@ message DebugDecryptResponseSingle { string value = 2; } -enum FheOperation { - FHE_ADD = 0; - FHE_SUB = 1; - FHE_MUL = 2; - FHE_DIV = 3; - FHE_REM = 4; - FHE_BIT_AND = 5; - FHE_BIT_OR = 6; - FHE_BIT_XOR = 7; - FHE_SHL = 8; - FHE_SHR = 9; - FHE_ROTL = 10; - FHE_ROTR = 11; - FHE_EQ = 12; - FHE_NE = 13; - FHE_GE = 14; - FHE_GT = 15; - FHE_LE = 16; - FHE_LT = 17; - FHE_MIN = 18; - FHE_MAX = 19; - FHE_NEG = 20; - FHE_NOT = 21; - FHE_CAST = 30; - FHE_IF_THEN_ELSE = 31; -} - message AsyncComputation { - FheOperation operation = 1; + fhevm.common.FheOperation operation = 1; bytes output_handle = 3; repeated AsyncComputationInput inputs = 4; } diff --git a/proto/executor.proto b/proto/executor.proto new file mode 100644 index 00000000..1e7b44ce --- /dev/null +++ b/proto/executor.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.fhevmexecutor"; +option java_outer_classname = "FhevmExecutor"; + +package fhevm.executor; + +import "common.proto"; + +service FhevmExecutor { + // Returns when computation of all operations has been completed. + rpc SyncCompute (SyncComputeRequest) returns (SyncComputeResponse); +} + +message SyncComputeRequest { + // Ordered list of computations. + repeated SyncComputation computations = 1; +} + +message SyncComputeResponse { + oneof result { + SyncComputeError error = 1; + + // Note: handles in `result_ciphertexts` will be the same ones as `SyncComputeRequest.result_handles`. + ResultCiphertexts result_ciphertexts = 2; + } +} + +message ResultCiphertexts { + repeated Ciphertext ciphertexts = 1; +} + +message SyncComputation { + fhevm.common.FheOperation operation = 1; + + // A list of ordered computations. + repeated SyncInput inputs = 2; + + // The result handles after execution the operation on the inputs. + repeated bytes result_handles = 3; + + // The input lists if there are input ciphertexts in the computation. Empty if no inputs. + repeated bytes input_lists = 4; +} + +enum SyncComputeError { + BAD_INPUT = 0; +} + +// Represents a ciphertext that is an expanded input or a result of FHE computation. +message Ciphertext { + bytes handle = 1; + bytes ciphertext = 2; +} + +message SyncInput { + oneof input { + // A ciphertext and its corresponding handle. + Ciphertext ciphertext = 1; + + // A handle that points to an input ciphertext in `SyncComputation.input_lists`. + bytes input_handle = 2; + + // A scalar value. Dependent on the operation, but typically big-endian integers. + bytes scalar = 3; + } +}