From b3cc07f13367191cda55d819089e8821aa62e059 Mon Sep 17 00:00:00 2001 From: Petar Ivanov <29689712+dartdart26@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:57:30 +0300 Subject: [PATCH] feat: inputs in SyncComputeRequest Inputs are global to a SyncComputeRequest. Additionally, introduce a `FHE_GET_INPUT_CIPHERTEXT` FHE operation that allows for a client to get an input ciphertext (that is not part of FHE computation). Would be used by the `verifyCiphertext` function in fhEVM-native. Change ciphertext version to 0. Add an integration test for gettting an input ciphertext. Introduce ciphertext compression - all ciphertexts coming in and going out of `SyncComputeRequest` will be compressed. --- fhevm-engine/Cargo.lock | 3 + fhevm-engine/Cargo.toml | 2 + fhevm-engine/coprocessor/Cargo.toml | 4 +- fhevm-engine/executor/Cargo.toml | 3 + fhevm-engine/executor/src/server.rs | 167 +++++++++++++++++- fhevm-engine/executor/tests/sync_compute.rs | 48 ++++- fhevm-engine/executor/tests/utils.rs | 30 +++- fhevm-engine/fhevm-engine-common/src/keys.rs | 27 ++- .../fhevm-engine-common/src/tfhe_ops.rs | 135 ++++++++------ fhevm-engine/fhevm-engine-common/src/types.rs | 60 ++++++- proto/common.proto | 1 + proto/executor.proto | 16 +- 12 files changed, 389 insertions(+), 107 deletions(-) diff --git a/fhevm-engine/Cargo.lock b/fhevm-engine/Cargo.lock index 5900ca7b..4b56ea2e 100644 --- a/fhevm-engine/Cargo.lock +++ b/fhevm-engine/Cargo.lock @@ -862,9 +862,12 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" name = "executor" version = "0.1.0" dependencies = [ + "bincode", "clap", "fhevm-engine-common", "prost", + "sha3", + "tfhe", "tokio", "tonic", "tonic-build", diff --git a/fhevm-engine/Cargo.toml b/fhevm-engine/Cargo.toml index f2dd8d8d..8898bffd 100644 --- a/fhevm-engine/Cargo.toml +++ b/fhevm-engine/Cargo.toml @@ -8,3 +8,5 @@ clap = { version = "4.5", features = ["derive"] } tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] } prost = "0.13" tonic = { version = "0.12", features = ["server"] } +bincode = "1.3.3" +sha3 = "0.10.8" diff --git a/fhevm-engine/coprocessor/Cargo.toml b/fhevm-engine/coprocessor/Cargo.toml index d6a9cd51..e3b5f295 100644 --- a/fhevm-engine/coprocessor/Cargo.toml +++ b/fhevm-engine/coprocessor/Cargo.toml @@ -24,8 +24,8 @@ hex = "0.4" bigdecimal = "0.4" fhevm-engine-common = { path = "../fhevm-engine-common" } strum = { version = "0.26", features = ["derive"] } -bincode = "1.3.3" -sha3 = "0.10.8" +bincode.workspace = true +sha3.workspace = true [dev-dependencies] testcontainers = "0.21" diff --git a/fhevm-engine/executor/Cargo.toml b/fhevm-engine/executor/Cargo.toml index 2491bd0a..2b28b30d 100644 --- a/fhevm-engine/executor/Cargo.toml +++ b/fhevm-engine/executor/Cargo.toml @@ -8,6 +8,9 @@ clap.workspace = true tokio.workspace = true prost.workspace = true tonic.workspace = true +tfhe.workspace = true +bincode.workspace = true +sha3.workspace = true fhevm-engine-common = { path = "../fhevm-engine-common" } [build-dependencies] diff --git a/fhevm-engine/executor/src/server.rs b/fhevm-engine/executor/src/server.rs index 3cdd0d32..88609676 100644 --- a/fhevm-engine/executor/src/server.rs +++ b/fhevm-engine/executor/src/server.rs @@ -1,12 +1,24 @@ -use std::error::Error; +use std::{cell::Cell, collections::HashMap, error::Error, sync::Arc}; +use common::FheOperation; use executor::{ fhevm_executor_server::{FhevmExecutor, FhevmExecutorServer}, - SyncComputeRequest, SyncComputeResponse, + sync_compute_response::Resp, + sync_input::Input, + Ciphertext, ResultCiphertexts, SyncComputation, SyncComputeError, SyncComputeRequest, + SyncComputeResponse, SyncInput, }; -use tonic::{transport::Server, Request, Response}; +use fhevm_engine_common::{ + keys::{FhevmKeys, SerializedFhevmKeys}, + tfhe_ops::{current_ciphertext_version, try_expand_ciphertext_list}, + types::{FhevmError, Handle, SupportedFheCiphertexts}, +}; +use sha3::{Digest, Keccak256}; +use tfhe::set_server_key; +use tokio::task::spawn_blocking; +use tonic::{transport::Server, Code, Request, Response, Status}; -mod common { +pub mod common { tonic::include_proto!("fhevm.common"); } @@ -21,7 +33,7 @@ pub fn start(args: &crate::cli::Args) -> Result<(), Box> { .enable_all() .build()?; - let executor = FhevmExecutorService::default(); + let executor = FhevmExecutorService::new(); let addr = args.server_addr.parse().expect("server address"); runtime.block_on(async { @@ -34,15 +46,154 @@ pub fn start(args: &crate::cli::Args) -> Result<(), Box> { Ok(()) } +struct InMemoryCiphertext { + expanded: SupportedFheCiphertexts, + compressed: Vec, +} + #[derive(Default)] -pub struct FhevmExecutorService {} +struct ComputationState { + ciphertexts: HashMap, +} + +fn error_response(error: SyncComputeError) -> SyncComputeResponse { + SyncComputeResponse { + resp: Some(Resp::Error(error.into())), + } +} + +fn success_response(cts: Vec) -> SyncComputeResponse { + SyncComputeResponse { + resp: Some(Resp::ResultCiphertexts(ResultCiphertexts { + ciphertexts: cts, + })), + } +} + +struct FhevmExecutorService { + keys: Arc, +} #[tonic::async_trait] impl FhevmExecutor for FhevmExecutorService { async fn sync_compute( &self, req: Request, - ) -> Result, tonic::Status> { - Ok(Response::new(SyncComputeResponse::default())) + ) -> Result, Status> { + let keys = self.keys.clone(); + let resp = spawn_blocking(move || { + // Make sure we only clone the server key if needed. + thread_local! { + static SERVER_KEY_IS_SET: Cell = Cell::new(false); + } + if !SERVER_KEY_IS_SET.get() { + set_server_key(keys.server_key.clone()); + SERVER_KEY_IS_SET.set(true); + } + + // Exapnd inputs that are global to the whole request. + let req = req.get_ref(); + let mut state = ComputationState::default(); + if Self::expand_inputs(&req.input_lists, &keys, &mut state).is_err() { + return error_response(SyncComputeError::BadInputList); + } + + // Execute all computations. + let mut result_cts = Vec::new(); + for computation in &req.computations { + let outcome = Self::process_computation(computation, &mut state); + // Either all succeed or we return on the first failure. + match outcome.resp.unwrap() { + Resp::Error(error) => { + return error_response( + SyncComputeError::try_from(error).expect("correct error value"), + ); + } + Resp::ResultCiphertexts(cts) => result_cts.extend(cts.ciphertexts), + } + } + success_response(result_cts) + }) + .await; + match resp { + Ok(resp) => Ok(Response::new(resp)), + Err(_) => Err(Status::new( + Code::Unknown, + "failed to execute computation via spawn_blocking", + )), + } + } +} + +impl FhevmExecutorService { + fn new() -> Self { + FhevmExecutorService { + keys: Arc::new(SerializedFhevmKeys::load_from_disk().into()), + } + } + + fn process_computation( + comp: &SyncComputation, + state: &mut ComputationState, + ) -> SyncComputeResponse { + let op = FheOperation::try_from(comp.operation); + match op { + Ok(FheOperation::FheGetInputCiphertext) => Self::get_input_ciphertext(comp, &state), + Ok(_) => error_response(SyncComputeError::UnsupportedOperation), + _ => error_response(SyncComputeError::InvalidOperation), + } + } + + fn expand_inputs( + lists: &Vec>, + keys: &FhevmKeys, + state: &mut ComputationState, + ) -> Result<(), FhevmError> { + for list in lists { + let cts = try_expand_ciphertext_list(&list, &keys.server_key)?; + let list_hash: Handle = Keccak256::digest(list).into(); + for (i, ct) in cts.iter().enumerate() { + let mut handle = list_hash; + handle[29] = i as u8; + handle[30] = ct.type_num() as u8; + handle[31] = current_ciphertext_version() as u8; + state.ciphertexts.insert( + handle, + InMemoryCiphertext { + expanded: ct.clone(), + compressed: ct.clone().compress(), + }, + ); + } + } + Ok(()) + } + + fn get_input_ciphertext( + comp: &SyncComputation, + state: &ComputationState, + ) -> SyncComputeResponse { + match (comp.inputs.first(), comp.inputs.len()) { + ( + Some(SyncInput { + input: Some(Input::InputHandle(handle)), + }), + 1, + ) => { + if let Ok(handle) = (handle as &[u8]).try_into() as Result { + if let Some(in_mem_ciphertext) = state.ciphertexts.get(&handle) { + success_response(vec![Ciphertext { + handle: handle.to_vec(), + ciphertext: in_mem_ciphertext.compressed.clone(), + }]) + } else { + error_response(SyncComputeError::UnknownHandle) + } + } else { + error_response(SyncComputeError::BadInputs) + } + } + _ => error_response(SyncComputeError::BadInputs), + } } } diff --git a/fhevm-engine/executor/tests/sync_compute.rs b/fhevm-engine/executor/tests/sync_compute.rs index 813deb3d..61a39e06 100644 --- a/fhevm-engine/executor/tests/sync_compute.rs +++ b/fhevm-engine/executor/tests/sync_compute.rs @@ -1,12 +1,48 @@ -use executor::server::executor::{fhevm_executor_client::FhevmExecutorClient, SyncComputeRequest}; -use utils::TestInstance; +use executor::server::common::FheOperation; +use executor::server::executor::sync_compute_response::Resp; +use executor::server::executor::{ + fhevm_executor_client::FhevmExecutorClient, SyncComputation, SyncComputeRequest, +}; +use executor::server::executor::{sync_input::Input, SyncInput}; +use tfhe::CompactCiphertextListBuilder; +use utils::get_test; 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?; +async fn get_input_ciphertexts() -> Result<(), Box> { + let test = get_test().await; + let mut client = FhevmExecutorClient::connect(test.server_addr.clone()).await?; + let mut builder = CompactCiphertextListBuilder::new(&test.keys.compact_public_key); + let list = bincode::serialize(&builder.push(10_u8).build()).unwrap(); + // TODO: tests for all types and avoiding passing in 2 as an identifier for FheUint8. + let input_handle = test.input_handle(&list, 0, 2); + let sync_input = SyncInput { + input: Some(Input::InputHandle(input_handle.to_vec())), + }; + let computation = SyncComputation { + operation: FheOperation::FheGetInputCiphertext.into(), + result_handles: vec![vec![0xaa]], + inputs: vec![sync_input], + }; + let req = SyncComputeRequest { + computations: vec![computation], + input_lists: vec![list], + }; + let response = client.sync_compute(req).await?; + let sync_compute_response = response.get_ref(); + match &sync_compute_response.resp { + Some(Resp::ResultCiphertexts(cts)) => { + match (cts.ciphertexts.first(), cts.ciphertexts.len()) { + (Some(ct), 1) => { + if ct.handle != input_handle || ct.ciphertext.is_empty() { + assert!(false); + } + } + _ => assert!(false), + } + } + _ => assert!(false), + } Ok(()) } diff --git a/fhevm-engine/executor/tests/utils.rs b/fhevm-engine/executor/tests/utils.rs index 77ca40f1..a5ea2f94 100644 --- a/fhevm-engine/executor/tests/utils.rs +++ b/fhevm-engine/executor/tests/utils.rs @@ -1,6 +1,14 @@ +use std::{sync::Arc, time::Duration}; + use clap::Parser; use executor::{cli::Args, server}; -use fhevm_engine_common::keys::{FhevmKeys, SerializedFhevmKeys}; +use fhevm_engine_common::{ + keys::{FhevmKeys, SerializedFhevmKeys}, + tfhe_ops::current_ciphertext_version, + types::Handle, +}; +use sha3::{Digest, Keccak256}; +use tokio::{sync::OnceCell, time::sleep}; pub struct TestInstance { pub keys: FhevmKeys, @@ -8,7 +16,7 @@ pub struct TestInstance { } impl TestInstance { - pub fn new() -> Self { + pub async fn new() -> Self { // Get defaults by parsing a cmd line without any arguments. let args = Args::parse_from(&["test"]); @@ -20,8 +28,24 @@ impl TestInstance { 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)); + sleep(Duration::from_secs(6)).await; instance } + + pub fn input_handle(&self, list: &[u8], index: u8, ct_type: u8) -> Handle { + let mut handle: Handle = Keccak256::digest(list).into(); + handle[29] = index; + handle[30] = ct_type; + handle[31] = current_ciphertext_version() as u8; + handle + } +} + +static TEST: OnceCell> = OnceCell::const_new(); + +pub async fn get_test() -> Arc { + TEST.get_or_init(|| async { Arc::new(TestInstance::new().await) }) + .await + .clone() } diff --git a/fhevm-engine/fhevm-engine-common/src/keys.rs b/fhevm-engine/fhevm-engine-common/src/keys.rs index 540dadde..f1469e62 100644 --- a/fhevm-engine/fhevm-engine-common/src/keys.rs +++ b/fhevm-engine/fhevm-engine-common/src/keys.rs @@ -12,13 +12,13 @@ use tfhe::{ pub struct FhevmKeys { pub server_key: ServerKey, pub client_key: Option, - pub compact_public_key: Option, + pub compact_public_key: CompactPublicKey, } pub struct SerializedFhevmKeys { pub server_key: Vec, pub client_key: Option>, - pub compact_public_key: Option>, + pub compact_public_key: Vec, } impl FhevmKeys { @@ -33,7 +33,7 @@ impl FhevmKeys { FhevmKeys { server_key, client_key: Some(client_key), - compact_public_key: Some(compact_public_key), + compact_public_key: compact_public_key, } } } @@ -56,21 +56,18 @@ impl SerializedFhevmKeys { 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"); - } + println!("Creating file {}", Self::PKS); + std::fs::write(format!("{}", Self::PKS), self.compact_public_key).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); + let compact_public_key = read(Self::PKS).expect("read compact public key"); SerializedFhevmKeys { server_key, client_key: client_key.ok(), - compact_public_key: compact_public_key.ok(), + compact_public_key: compact_public_key, } } } @@ -82,9 +79,8 @@ impl From for SerializedFhevmKeys { 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")), + compact_public_key: bincode::serialize(&f.compact_public_key) + .expect("serialize compact public key"), } } } @@ -96,9 +92,8 @@ impl From for FhevmKeys { 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")), + compact_public_key: bincode::deserialize(&f.compact_public_key) + .expect("deserialize compact public key"), } } } diff --git a/fhevm-engine/fhevm-engine-common/src/tfhe_ops.rs b/fhevm-engine/fhevm-engine-common/src/tfhe_ops.rs index 3722a0b6..a262f842 100644 --- a/fhevm-engine/fhevm-engine-common/src/tfhe_ops.rs +++ b/fhevm-engine/fhevm-engine-common/src/tfhe_ops.rs @@ -1,35 +1,40 @@ use crate::types::{FheOperationType, FhevmError, SupportedFheCiphertexts, SupportedFheOperations}; use tfhe::{ prelude::{ - CastInto, FheEq, FheMax, FheMin, FheOrd, FheTryTrivialEncrypt, IfThenElse, RotateLeft, RotateRight + CastInto, FheEq, FheMax, FheMin, FheOrd, FheTryTrivialEncrypt, IfThenElse, RotateLeft, + RotateRight, }, FheBool, FheUint16, FheUint32, FheUint64, FheUint8, }; - pub fn deserialize_fhe_ciphertext( input_type: i16, input_bytes: &[u8], ) -> Result { match input_type { 1 => { - let v: tfhe::FheBool = bincode::deserialize(input_bytes).map_err(|e| FhevmError::DeserializationError(e))?; + let v: tfhe::FheBool = bincode::deserialize(input_bytes) + .map_err(|e| FhevmError::DeserializationError(e))?; Ok(SupportedFheCiphertexts::FheBool(v)) } 2 => { - let v: tfhe::FheUint8 = bincode::deserialize(input_bytes).map_err(|e| FhevmError::DeserializationError(e))?; + let v: tfhe::FheUint8 = bincode::deserialize(input_bytes) + .map_err(|e| FhevmError::DeserializationError(e))?; Ok(SupportedFheCiphertexts::FheUint8(v)) } 3 => { - let v: tfhe::FheUint16 = bincode::deserialize(input_bytes).map_err(|e| FhevmError::DeserializationError(e))?; + let v: tfhe::FheUint16 = bincode::deserialize(input_bytes) + .map_err(|e| FhevmError::DeserializationError(e))?; Ok(SupportedFheCiphertexts::FheUint16(v)) } 4 => { - let v: tfhe::FheUint32 = bincode::deserialize(input_bytes).map_err(|e| FhevmError::DeserializationError(e))?; + let v: tfhe::FheUint32 = bincode::deserialize(input_bytes) + .map_err(|e| FhevmError::DeserializationError(e))?; Ok(SupportedFheCiphertexts::FheUint32(v)) } 5 => { - let v: tfhe::FheUint64 = bincode::deserialize(input_bytes).map_err(|e| FhevmError::DeserializationError(e))?; + let v: tfhe::FheUint64 = bincode::deserialize(input_bytes) + .map_err(|e| FhevmError::DeserializationError(e))?; Ok(SupportedFheCiphertexts::FheUint64(v)) } _ => { @@ -84,7 +89,7 @@ pub fn debug_trivial_encrypt_be_bytes( } pub fn current_ciphertext_version() -> i16 { - 1 + 0 } pub fn try_expand_ciphertext_list( @@ -94,17 +99,15 @@ pub fn try_expand_ciphertext_list( let mut res = Vec::new(); let the_list: tfhe::CompactCiphertextList = - bincode::deserialize(input_ciphertext) - .map_err(|e| { - let err: Box<(dyn std::error::Error + Send + Sync)> = e; - FhevmError::DeserializationError(err) - })?; - - let expanded = the_list.expand_with_key(server_key) - .map_err(|e| { - FhevmError::CiphertextExpansionError(e) + bincode::deserialize(input_ciphertext).map_err(|e| { + let err: Box<(dyn std::error::Error + Send + Sync)> = e; + FhevmError::DeserializationError(err) })?; + let expanded = the_list + .expand_with_key(server_key) + .map_err(|e| FhevmError::CiphertextExpansionError(e))?; + for idx in 0..expanded.len() { let Some(data_kind) = expanded.get_kind_of(idx) else { panic!("we're itering over what ciphertext told us how many ciphertexts are there, it must exist") @@ -112,42 +115,49 @@ pub fn try_expand_ciphertext_list( match data_kind { tfhe::FheTypes::Bool => { - let ct: tfhe::FheBool = expanded.get(idx) + let ct: tfhe::FheBool = expanded + .get(idx) .expect("Index must exist") .expect("Must succeed, we just checked this is the type"); res.push(SupportedFheCiphertexts::FheBool(ct)); - }, + } tfhe::FheTypes::Uint8 => { - let ct: tfhe::FheUint8 = expanded.get(idx) + let ct: tfhe::FheUint8 = expanded + .get(idx) .expect("Index must exist") .expect("Must succeed, we just checked this is the type"); res.push(SupportedFheCiphertexts::FheUint8(ct)); - }, + } tfhe::FheTypes::Uint16 => { - let ct: tfhe::FheUint16 = expanded.get(idx) + let ct: tfhe::FheUint16 = expanded + .get(idx) .expect("Index must exist") .expect("Must succeed, we just checked this is the type"); res.push(SupportedFheCiphertexts::FheUint16(ct)); - }, + } tfhe::FheTypes::Uint32 => { - let ct: tfhe::FheUint32 = expanded.get(idx) + let ct: tfhe::FheUint32 = expanded + .get(idx) .expect("Index must exist") .expect("Must succeed, we just checked this is the type"); res.push(SupportedFheCiphertexts::FheUint32(ct)); - }, + } tfhe::FheTypes::Uint64 => { - let ct: tfhe::FheUint64 = expanded.get(idx) + let ct: tfhe::FheUint64 = expanded + .get(idx) .expect("Index must exist") .expect("Must succeed, we just checked this is the type"); res.push(SupportedFheCiphertexts::FheUint64(ct)); - }, + } other => { - return Err(FhevmError::CiphertextExpansionUnsupportedCiphertextKind(other)); + return Err(FhevmError::CiphertextExpansionUnsupportedCiphertextKind( + other, + )); } } } @@ -167,7 +177,9 @@ pub fn check_fhe_operand_types( let fhe_op: SupportedFheOperations = fhe_operation.try_into()?; let fhe_bool_type = 1; - let scalar_operands = is_input_handle_scalar.iter().enumerate() + let scalar_operands = is_input_handle_scalar + .iter() + .enumerate() .filter(|(_, is_scalar)| **is_scalar) .collect::>(); @@ -184,7 +196,8 @@ pub fn check_fhe_operand_types( if is_scalar { assert_eq!( - scalar_operands.len(), 1, + scalar_operands.len(), + 1, "We checked already that not more than 1 scalar operand can be present" ); @@ -197,7 +210,7 @@ pub fn check_fhe_operand_types( }); } - let scalar_input_index =scalar_operands[0].0; + let scalar_input_index = scalar_operands[0].0; if scalar_input_index != 1 { return Err(FhevmError::FheOperationOnlySecondOperandCanBeScalar { scalar_input_index, @@ -219,13 +232,11 @@ pub fn check_fhe_operand_types( } if !is_scalar && input_types[0] != input_types[1] { - return Err( - FhevmError::FheOperationDoesntHaveUniformTypesAsInput { - fhe_operation, - fhe_operation_name: format!("{:?}", fhe_op), - operand_types: input_types.to_vec(), - }, - ); + return Err(FhevmError::FheOperationDoesntHaveUniformTypesAsInput { + fhe_operation, + fhe_operation_name: format!("{:?}", fhe_op), + operand_types: input_types.to_vec(), + }); } if input_types[0] == fhe_bool_type && !fhe_op.supports_bool_inputs() { @@ -305,12 +316,14 @@ pub fn check_fhe_operand_types( } if input_types[1] != input_types[2] { - return Err(FhevmError::FheIfThenElseMismatchingSecondAndThirdOperatorTypes { - fhe_operation, - fhe_operation_name: format!("{:?}", fhe_op), - second_operand_type: input_types[1], - third_operand_type: input_types[2], - }); + return Err( + FhevmError::FheIfThenElseMismatchingSecondAndThirdOperatorTypes { + fhe_operation, + fhe_operation_name: format!("{:?}", fhe_op), + second_operand_type: input_types[1], + third_operand_type: input_types[2], + }, + ); } Ok(input_types[1]) @@ -330,12 +343,14 @@ pub fn check_fhe_operand_types( (false, true) => { let op = &input_handles[1]; if op.len() != 1 { - return Err(FhevmError::UnexpectedCastOperandSizeForScalarOperand { - fhe_operation, - fhe_operation_name: format!("{:?}", fhe_op), - expected_scalar_operand_bytes: 1, - got_bytes: op.len(), - }); + return Err( + FhevmError::UnexpectedCastOperandSizeForScalarOperand { + fhe_operation, + fhe_operation_name: format!("{:?}", fhe_op), + expected_scalar_operand_bytes: 1, + got_bytes: op.len(), + }, + ); } let output_type = op[0] as i32; @@ -343,9 +358,8 @@ pub fn check_fhe_operand_types( Ok(output_type as i16) } (other_left, other_right) => { - let bool_to_op = |inp| { - (if inp { "scalar" } else { "handle" }).to_string() - }; + let bool_to_op = + |inp| (if inp { "scalar" } else { "handle" }).to_string(); return Err(FhevmError::UnexpectedCastOperandTypes { fhe_operation, @@ -371,7 +385,9 @@ pub fn check_fhe_operand_types( } pub fn validate_fhe_type(input_type: i32) -> Result<(), FhevmError> { - let i16_type: i16 = input_type.try_into().or(Err(FhevmError::UnknownFheType(input_type)))?; + let i16_type: i16 = input_type + .try_into() + .or(Err(FhevmError::UnknownFheType(input_type)))?; match i16_type { 1 | 2 | 3 | 4 | 5 => Ok(()), _ => Err(FhevmError::UnknownFheType(input_type)), @@ -401,10 +417,10 @@ pub fn does_fhe_operation_support_both_encrypted_operands(op: &SupportedFheOpera } pub fn perform_fhe_operation( - fhe_operation: i16, + fhe_operation_int: i16, input_operands: &[SupportedFheCiphertexts], ) -> Result { - let fhe_operation: SupportedFheOperations = fhe_operation.try_into()?; + let fhe_operation: SupportedFheOperations = fhe_operation_int.try_into()?; match fhe_operation { SupportedFheOperations::FheAdd => { assert_eq!(input_operands.len(), 2); @@ -1382,7 +1398,7 @@ pub fn perform_fhe_operation( panic!("Mismatch between cmux operand types") } } - }, + } SupportedFheOperations::FheCast => match (&input_operands[0], &input_operands[1]) { (SupportedFheCiphertexts::FheBool(inp), SupportedFheCiphertexts::Scalar(op)) => { let (l, h) = op.to_low_high_u128(); @@ -1533,5 +1549,8 @@ pub fn perform_fhe_operation( panic!("unknown cast pair") } }, + SupportedFheOperations::FheGetInputCiphertext => { + Err(FhevmError::UnknownFheOperation(fhe_operation_int as i32)) + } } -} \ No newline at end of file +} diff --git a/fhevm-engine/fhevm-engine-common/src/types.rs b/fhevm-engine/fhevm-engine-common/src/types.rs index d07a0e22..ab120285 100644 --- a/fhevm-engine/fhevm-engine-common/src/types.rs +++ b/fhevm-engine/fhevm-engine-common/src/types.rs @@ -1,5 +1,6 @@ use tfhe::integer::U256; use tfhe::prelude::FheDecrypt; +use tfhe::CompressedCiphertextListBuilder; #[derive(Debug)] pub enum FhevmError { @@ -86,13 +87,17 @@ impl std::fmt::Display for FhevmError { } Self::DeserializationError(e) => { write!(f, "error deserializing ciphertext: {:?}", e) - }, + } Self::CiphertextExpansionError(e) => { write!(f, "error expanding compact ciphertext list: {:?}", e) - }, + } Self::CiphertextExpansionUnsupportedCiphertextKind(e) => { - write!(f, "unsupported tfhe type found while expanding ciphertexts: {:?}", e) - }, + write!( + f, + "unsupported tfhe type found while expanding ciphertexts: {:?}", + e + ) + } Self::FheOperationDoesntSupportScalar { fhe_operation, fhe_operation_name, @@ -152,19 +157,36 @@ impl std::fmt::Display for FhevmError { } => { write!(f, "unexpected operand size for cast, fhe operation: {fhe_operation}, fhe operation name: {fhe_operation_name}, expected bytes: {}, got bytes: {}", expected_scalar_operand_bytes, got_bytes) } - Self::FheIfThenElseUnexpectedOperandTypes { fhe_operation, fhe_operation_name, first_operand_type, first_expected_operand_type, .. } => { + Self::FheIfThenElseUnexpectedOperandTypes { + fhe_operation, + fhe_operation_name, + first_operand_type, + first_expected_operand_type, + .. + } => { write!(f, "fhe if then else first operand should always be FheBool, fhe operation: {fhe_operation}, fhe operation name: {fhe_operation_name}, first operand type: {first_operand_type}, first operand expected type: {first_expected_operand_type}") } - Self::FheIfThenElseMismatchingSecondAndThirdOperatorTypes { fhe_operation, fhe_operation_name, second_operand_type, third_operand_type } => { + Self::FheIfThenElseMismatchingSecondAndThirdOperatorTypes { + fhe_operation, + fhe_operation_name, + second_operand_type, + third_operand_type, + } => { write!(f, "fhe if then else second and third operand types don't match, fhe operation: {fhe_operation}, fhe operation name: {fhe_operation_name}, second operand type: {second_operand_type}, third operand type: {third_operand_type}") } - Self::FheOperationOnlyOneOperandCanBeScalar { fhe_operation, fhe_operation_name, scalar_operand_count, max_scalar_operands } => { + Self::FheOperationOnlyOneOperandCanBeScalar { + fhe_operation, + fhe_operation_name, + scalar_operand_count, + max_scalar_operands, + } => { write!(f, "only one operand can be scalar, fhe operation: {fhe_operation}, fhe operation name: {fhe_operation_name}, second operand count: {scalar_operand_count}, max scalar operands: {max_scalar_operands}") } } } } +#[derive(Clone)] pub enum SupportedFheCiphertexts { FheBool(tfhe::FheBool), FheUint8(tfhe::FheUint8), @@ -201,6 +223,7 @@ pub enum SupportedFheOperations { FheNot = 21, FheCast = 30, FheIfThenElse = 31, + FheGetInputCiphertext = 32, } #[derive(PartialEq, Eq)] @@ -259,6 +282,23 @@ impl SupportedFheCiphertexts { } } } + + pub fn compress(self) -> Vec { + let mut builder = CompressedCiphertextListBuilder::new(); + match self { + SupportedFheCiphertexts::FheBool(c) => builder.push(c), + SupportedFheCiphertexts::FheUint8(c) => builder.push(c), + SupportedFheCiphertexts::FheUint16(c) => builder.push(c), + SupportedFheCiphertexts::FheUint32(c) => builder.push(c), + SupportedFheCiphertexts::FheUint64(c) => builder.push(c), + SupportedFheCiphertexts::Scalar(_) => { + // TODO: Need to fix that, scalars are not ciphertexts. + panic!("cannot compress a scalar"); + } + }; + let list = builder.build().expect("ciphertext compression"); + bincode::serialize(&list).expect("compressed list serialization") + } } impl SupportedFheOperations { @@ -290,6 +330,7 @@ impl SupportedFheOperations { SupportedFheOperations::FheIfThenElse | SupportedFheOperations::FheCast => { FheOperationType::Other } + SupportedFheOperations::FheGetInputCiphertext => FheOperationType::Other, } } @@ -344,6 +385,7 @@ impl TryFrom for SupportedFheOperations { 21 => Ok(SupportedFheOperations::FheNot), 30 => Ok(SupportedFheOperations::FheCast), 31 => Ok(SupportedFheOperations::FheIfThenElse), + 32 => Ok(SupportedFheOperations::FheGetInputCiphertext), _ => Err(FhevmError::UnknownFheOperation(value as i32)), }; @@ -375,4 +417,6 @@ impl From for i16 { fn from(value: SupportedFheOperations) -> Self { value as i16 } -} \ No newline at end of file +} + +pub type Handle = [u8; 32]; diff --git a/proto/common.proto b/proto/common.proto index cbc27a11..30ac4ab8 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -31,4 +31,5 @@ enum FheOperation { FHE_NOT = 21; FHE_CAST = 30; FHE_IF_THEN_ELSE = 31; + FHE_GET_INPUT_CIPHERTEXT = 32; } diff --git a/proto/executor.proto b/proto/executor.proto index 1e7b44ce..3ef76a37 100644 --- a/proto/executor.proto +++ b/proto/executor.proto @@ -16,10 +16,13 @@ service FhevmExecutor { message SyncComputeRequest { // Ordered list of computations. repeated SyncComputation computations = 1; + + // The input lists if there are input ciphertexts in the computation. Empty if no inputs. + repeated bytes input_lists = 2; } message SyncComputeResponse { - oneof result { + oneof resp { SyncComputeError error = 1; // Note: handles in `result_ciphertexts` will be the same ones as `SyncComputeRequest.result_handles`. @@ -39,13 +42,14 @@ message SyncComputation { // 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; + BAD_INPUT_LIST = 0; + INVALID_OPERATION = 1; + UNSUPPORTED_OPERATION = 2; + BAD_INPUTS = 3; + UNKNOWN_HANDLE = 4; } // Represents a ciphertext that is an expanded input or a result of FHE computation. @@ -59,7 +63,7 @@ message SyncInput { // A ciphertext and its corresponding handle. Ciphertext ciphertext = 1; - // A handle that points to an input ciphertext in `SyncComputation.input_lists`. + // A handle that points to an input ciphertext in `SyncComputeRequest.input_lists`. bytes input_handle = 2; // A scalar value. Dependent on the operation, but typically big-endian integers.