Skip to content

Commit

Permalink
Working integration with go-ethereum
Browse files Browse the repository at this point in the history
- fhevm-go-coproc library
- adjust types/operations enums to match solidity library
- fixed dependency computation from false positives
  • Loading branch information
david-zk committed Sep 2, 2024
1 parent 508dc47 commit daf83a9
Show file tree
Hide file tree
Showing 20 changed files with 4,302 additions and 58 deletions.
23 changes: 17 additions & 6 deletions fhevm-engine/coprocessor/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,12 @@ impl coprocessor::fhevm_coprocessor_server::FhevmCoprocessor for CoprocessorServ
let ciphertext_version = current_ciphertext_version();
let mut handle_hash = Keccak256::new();
handle_hash.update(&blob_hash);
handle_hash.update(&[idx as u8]);
handle_hash.update(&[ct_idx as u8]);
let mut handle = handle_hash.finalize().to_vec();
assert_eq!(handle.len(), 32);
// idx cast to u8 must succeed because we don't allow
// more handles than u8 size
handle[29] = idx as u8;
handle[29] = ct_idx as u8;
handle[30] = serialized_type as u8;
handle[31] = ciphertext_version as u8;

Expand Down Expand Up @@ -424,6 +424,11 @@ impl coprocessor::fhevm_coprocessor_server::FhevmCoprocessor for CoprocessorServ
return Err(tonic::Status::not_found("tenant private key not found"));
}

let mut ct_indexes: BTreeMap<&[u8], usize> = BTreeMap::new();
for (idx, h) in req.handles.iter().enumerate() {
ct_indexes.insert(h.as_slice(), idx);
}

assert_eq!(priv_key.len(), 1);

let cts = sqlx::query!(
Expand All @@ -448,25 +453,31 @@ impl coprocessor::fhevm_coprocessor_server::FhevmCoprocessor for CoprocessorServ

let priv_key = priv_key.pop().unwrap().cks_key.unwrap();

let values = tokio::task::spawn_blocking(move || {
let mut values = tokio::task::spawn_blocking(move || {
let client_key: tfhe::ClientKey = bincode::deserialize(&priv_key).unwrap();

let mut decrypted: Vec<DebugDecryptResponseSingle> = Vec::with_capacity(cts.len());
let mut decrypted: Vec<(Vec<u8>, DebugDecryptResponseSingle)> = Vec::with_capacity(cts.len());
for ct in cts {
let deserialized =
deserialize_fhe_ciphertext(ct.ciphertext_type, &ct.ciphertext)
.unwrap();
decrypted.push(DebugDecryptResponseSingle {
decrypted.push((ct.handle, DebugDecryptResponseSingle {
output_type: ct.ciphertext_type as i32,
value: deserialized.decrypt(&client_key),
});
}));
}

decrypted
})
.await
.unwrap();

values.sort_by_key(|(h, _)| {
ct_indexes.get(h.as_slice()).unwrap()
});

let values = values.into_iter().map(|i| i.1).collect::<Vec<_>>();

return Ok(tonic::Response::new(DebugDecryptResponse { values }));
}

Expand Down
93 changes: 92 additions & 1 deletion fhevm-engine/coprocessor/src/tests/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,105 @@ async fn test_fhe_inputs() -> Result<(), Box<dyn std::error::Error>> {
let resp = resp.get_ref();
assert_eq!(resp.values.len(), 5);

assert_eq!(resp.values[0].output_type, 1);
assert_eq!(resp.values[0].output_type, 0);
assert_eq!(resp.values[0].value, "false");
assert_eq!(resp.values[1].output_type, 2);
assert_eq!(resp.values[1].value, "1");
assert_eq!(resp.values[2].output_type, 3);
assert_eq!(resp.values[2].value, "2");
assert_eq!(resp.values[3].output_type, 4);
assert_eq!(resp.values[3].value, "3");
assert_eq!(resp.values[4].output_type, 5);
assert_eq!(resp.values[4].value, "4");

Ok(())
}

#[ignore]
#[tokio::test]
// custom function for integration testing in development environment
// uploads ciphertext batch from inputs to local coprocessor database
async fn custom_insert_inputs() -> Result<(), Box<dyn std::error::Error>> {
let grpc_url = "http://127.0.0.1:50051";
let db_url = "postgres://postgres:postgres@localhost/coprocessor";
let api_key_header = format!("bearer {}", default_api_key());

let mut client = FhevmCoprocessorClient::connect(grpc_url).await?;
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(2)
.connect(&db_url)
.await?;

let keys = query_tenant_keys(vec![default_tenant_id()], &pool).await.map_err(|e| {
let e: Box<dyn std::error::Error> = e;
e
})?;
let keys = &keys[0];

let mut builder = tfhe::CompactCiphertextListBuilder::new(&keys.pks);
let the_list = builder
.push(false)
.push(1u8)
.push(2u16)
.push(3u32)
.push(4u64)
.push(5u64)
.build();

let serialized = bincode::serialize(&the_list).unwrap();

println!("Encrypting inputs...");
let mut input_request = tonic::Request::new(InputUploadBatch {
input_ciphertexts: vec![
InputToUpload {
input_payload: serialized,
signature: Vec::new(),
}
]
});
input_request.metadata_mut().append(
"authorization",
MetadataValue::from_str(&api_key_header).unwrap(),
);

let uploaded = client.upload_inputs(input_request).await?;
let response = uploaded.get_ref();

for (idx, ur) in response.upload_responses.iter().enumerate() {
println!("request {idx}");
for (idx, h) in ur.input_handles.iter().enumerate() {
println!(" ct {idx} 0x{}", hex::encode(&h.handle));
}
}

Ok(())
}

#[ignore]
#[tokio::test]
// custom function to decrypt the ciphertext from grpc
// ct_to_decrypt should be changed to your environment
async fn custom_decrypt_ct() -> Result<(), Box<dyn std::error::Error>> {
let grpc_url = "http://127.0.0.1:50051";
let api_key_header = format!("bearer {}", default_api_key());
let ct_to_decrypt = "5bcaeef7d5bee3b5dffff3dfbfafcfb73cf57ddbbff73f777ffdfe677ebc0500";

let mut client = FhevmCoprocessorClient::connect(grpc_url).await?;
println!("Encrypting inputs...");
let mut input_request = tonic::Request::new(DebugDecryptRequest {
handles: vec![
hex::decode(ct_to_decrypt).unwrap()
]
});
input_request.metadata_mut().append(
"authorization",
MetadataValue::from_str(&api_key_header).unwrap(),
);

let uploaded = client.debug_decrypt_ciphertext(input_request).await?;
let response = uploaded.get_ref();

println!("{:#?}", response);

Ok(())
}
24 changes: 16 additions & 8 deletions fhevm-engine/coprocessor/src/tests/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@ fn supported_bits() -> &'static [i32] {

fn supported_types() -> &'static [i32] {
&[
1, // bool
0, // bool
// 1, TODO: add 4 bit support
2, // 8 bit
3, // 16 bit
4, // 32 bit
5, // 64 bit
// 6, TODO: add 128 bit support
// 7, TODO: add 160 bit support
// 8, TODO: add 256 bit support
]
}

Expand Down Expand Up @@ -332,6 +336,7 @@ async fn test_fhe_casts() -> Result<(), Box<dyn std::error::Error>> {
expected_result: String,
}

let fhe_bool = 0;
let mut output_handles = Vec::new();
let mut enc_request_payload = Vec::new();
let mut async_computations = Vec::new();
Expand All @@ -342,7 +347,7 @@ async fn test_fhe_casts() -> Result<(), Box<dyn std::error::Error>> {
let output_handle = next_handle();
let input = 7;
let (_, inp_bytes) = BigInt::from(input).to_bytes_be();
let output = if *type_to == 1 || *type_from == 1 {
let output = if *type_to == fhe_bool || *type_from == fhe_bool {
// if bool output is 1
1
} else {
Expand All @@ -361,7 +366,7 @@ async fn test_fhe_casts() -> Result<(), Box<dyn std::error::Error>> {
type_from: *type_from,
type_to: *type_to,
input,
expected_result: if *type_to == 1 {
expected_result: if *type_to == fhe_bool {
(output > 0).to_string()
} else {
output.to_string()
Expand Down Expand Up @@ -472,23 +477,25 @@ async fn test_fhe_if_then_else() -> Result<(), Box<dyn std::error::Error>> {
let mut async_computations = Vec::new();
let mut if_then_else_outputs: Vec<IfThenElseOutput> = Vec::new();

let fhe_bool_type = 0;
let false_handle = next_handle();
let true_handle = next_handle();
enc_request_payload.push(DebugEncryptRequestSingle {
handle: false_handle.clone(),
le_value: BigInt::from(0).to_bytes_be().1,
output_type: 1,
output_type: fhe_bool_type,
});
enc_request_payload.push(DebugEncryptRequestSingle {
handle: true_handle.clone(),
le_value: BigInt::from(1).to_bytes_be().1,
output_type: 1,
output_type: fhe_bool_type,
});

let fhe_bool_type = 0;
for input_types in supported_types() {
let left_handle = next_handle();
let right_handle = next_handle();
let is_input_bool = *input_types == 1;
let is_input_bool = *input_types == fhe_bool_type;
let (left_input, right_input) =
if is_input_bool {
(0, 1)
Expand Down Expand Up @@ -516,7 +523,7 @@ async fn test_fhe_if_then_else() -> Result<(), Box<dyn std::error::Error>> {
input_bool: test_value,
left_input,
right_input,
expected_result: if *input_types == 1 {
expected_result: if *input_types == fhe_bool_type {
(expected_result > 0).to_string()
} else {
expected_result.to_string()
Expand Down Expand Up @@ -623,8 +630,9 @@ fn generate_binary_test_cases() -> Vec<BinaryOperatorTestCase> {
}
let expected_output = compute_expected_binary_output(&lhs, &rhs, op, bits);
let operand = op as i32;
let fhe_bool_type = 0;
let expected_output_type = if op.is_comparison() {
1 // FheBool
fhe_bool_type
} else {
supported_bits_to_bit_type_in_db(bits)
};
Expand Down
73 changes: 51 additions & 22 deletions fhevm-engine/coprocessor/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,35 +98,64 @@ pub fn sort_computations_by_dependencies<'a>(

// least dependencies goes to the left, most dependencies to the right
computation_dependencies.sort_by(|(_, deps_a), (_, deps_b)| deps_a.cmp(deps_b));

let mut simulation_completed_outputs: HashSet<&[u8]> = HashSet::new();
for (inp_idx, _) in computation_dependencies {
let async_comp = &input[inp_idx];

for ih in &async_comp.inputs {
if let Some(Input::InputHandle(ih)) = &ih.input {
// this must be loop if we don't see that handle is completed here, for example
// [output: 1, deps: [0, 2, 3]]
// [output: 0, deps: [1, 2, 3]]
if !handles_to_check_in_db.contains(ih.as_slice())
&& !simulation_completed_outputs.contains(ih.as_slice())
{
return Err(
CoprocessorError::CiphertextComputationDependencyLoopDetected {
uncomputable_output_handle: format!(
"0x{}",
hex::encode(&async_comp.output_handle)
),
uncomputable_handle_dependency: format!("0x{}", hex::encode(ih)),
},
);
loop {
let mut progress_made_in_iteration = false;
let mut new_computation_dependencies: Vec<(usize, Vec<usize>)> = Vec::new();

let mut first_uncomputable_handle: &[u8] = [].as_slice();
let mut first_uncomputable_handle_dependency: &[u8] = [].as_slice();

for (inp_idx, deps) in computation_dependencies {
let async_comp = &input[inp_idx];

let mut can_compute_this = true;
for ih in &async_comp.inputs {
if let Some(Input::InputHandle(ih)) = &ih.input {
if !handles_to_check_in_db.contains(ih.as_slice())
&& !simulation_completed_outputs.contains(ih.as_slice())
{
if first_uncomputable_handle.is_empty() {
first_uncomputable_handle = async_comp.output_handle.as_slice();
first_uncomputable_handle_dependency = ih.as_slice();
}
can_compute_this = false;
}
}
}

if can_compute_this {
progress_made_in_iteration = true;
simulation_completed_outputs.insert(&async_comp.output_handle);
res.push(async_comp);
} else {
// push uncomputable to new queue to try again later
new_computation_dependencies.push((inp_idx, deps));
}
}

simulation_completed_outputs.insert(&async_comp.output_handle);
if !progress_made_in_iteration {
// this must be loop if we don't see progress made
// [output: 1, deps: [0, 2, 3]]
// [output: 0, deps: [1, 2, 3]]
return Err(
CoprocessorError::CiphertextComputationDependencyLoopDetected {
uncomputable_output_handle: format!(
"0x{}",
hex::encode(&first_uncomputable_handle)
),
uncomputable_handle_dependency: format!("0x{}", hex::encode(first_uncomputable_handle_dependency)),
},
);
}

if new_computation_dependencies.is_empty() {
// everything computed, break loop
break;
}

res.push(async_comp);
computation_dependencies = new_computation_dependencies;
}

Ok((res, handles_to_check_in_db))
Expand Down
Loading

0 comments on commit daf83a9

Please sign in to comment.