Skip to content

Commit

Permalink
Implement EIP712 signatures on inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
david-zk committed Sep 10, 2024
1 parent 4015580 commit bca4c6b
Show file tree
Hide file tree
Showing 14 changed files with 2,418 additions and 121 deletions.
2,322 changes: 2,212 additions & 110 deletions fhevm-engine/Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions fhevm-engine/coprocessor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tonic-health = "0.12"
tonic-types = "0.12"
tokio-util = "0.7"
sqlx = { version = "0.7", features = ["runtime-tokio", "tls-rustls", "postgres", "uuid"] }
alloy = { version = "0.3.2", features = ["eip712", "sol-types", "signer-local"] }
serde_json = "1.0"
regex = "1.10.5"
lazy_static = "1.5.0"
Expand Down
1 change: 1 addition & 0 deletions fhevm-engine/coprocessor/coprocessor.key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x7ec8ada6642fc4ccfb7729bc29c17cf8d21b61abd5642d1db992c0b8672ab901
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ CREATE TABLE IF NOT EXISTS input_blobs (
CREATE TABLE IF NOT EXISTS tenants (
tenant_id SERIAL PRIMARY KEY,
tenant_api_key UUID NOT NULL DEFAULT gen_random_uuid(),
-- for EIP712 signatures
chain_id INT NOT NULL,
-- for EIP712 signatures
verifying_contract_address TEXT NOT NULL,
pks_key BYTEA NOT NULL,
sks_key BYTEA NOT NULL,
-- for debugging, can be null
Expand Down
Git LFS file not shown
4 changes: 3 additions & 1 deletion fhevm-engine/coprocessor/migrations/gen-keys.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#!/bin/sh

echo "
INSERT INTO tenants(tenant_api_key, pks_key, sks_key, cks_key)
INSERT INTO tenants(tenant_api_key, chain_id, verifying_contract_address, pks_key, sks_key, cks_key)
VALUES (
'a1503fb6-d79b-4e9e-826d-44cf262f3e05',
12345,
'0x6819e3aDc437fAf9D533490eD3a7552493fCE3B1',
decode('$(cat ../../fhevm-keys/pks | xxd -p | tr -d '\n')','hex'),
decode('$(cat ../../fhevm-keys/sks | xxd -p | tr -d '\n')','hex'),
decode('$(cat ../../fhevm-keys/cks | xxd -p | tr -d '\n')','hex')
Expand Down
5 changes: 5 additions & 0 deletions fhevm-engine/coprocessor/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ pub struct Args {
/// Postgres database url. If unspecified DATABASE_URL environment variable is used
#[arg(long)]
pub database_url: Option<String>,

/// Coprocessor private key file path.
/// Private key is in plain text 0x1234.. format.
#[arg(long, default_value = "./coprocessor.key")]
pub coprocessor_private_key: String,
}

pub fn parse_args() -> Args {
Expand Down
24 changes: 20 additions & 4 deletions fhevm-engine/coprocessor/src/db_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,27 @@ pub async fn check_if_ciphertexts_exist_in_db(
Ok(result)
}

pub struct FetchTenantKeyResult {
pub chain_id: i32,
pub verifying_contract_address: String,
pub server_key: tfhe::ServerKey,
}

/// Returns chain id and verifying contract address for EIP712 signature and tfhe server key
pub async fn fetch_tenant_server_key<'a, T>(tenant_id: i32, pool: T, tenant_key_cache: &std::sync::Arc<tokio::sync::RwLock<lru::LruCache<i32, TfheTenantKeys>>>)
-> Result<tfhe::ServerKey, Box<dyn std::error::Error + Send + Sync>>
-> Result<FetchTenantKeyResult, Box<dyn std::error::Error + Send + Sync>>
where T: sqlx::PgExecutor<'a> + Copy
{
// try getting from cache until it succeeds with populating cache
loop {
{
let mut w = tenant_key_cache.write().await;
if let Some(key) = w.get(&tenant_id) {
return Ok(key.sks.clone());
return Ok(FetchTenantKeyResult {
chain_id: key.chain_id,
verifying_contract_address: key.verifying_contract_address.clone(),
server_key: key.sks.clone(),
});
}
}

Expand All @@ -118,7 +129,7 @@ where T: sqlx::PgExecutor<'a>
let mut res = Vec::with_capacity(tenants_to_query.len());
let keys = query!(
"
SELECT tenant_id, pks_key, sks_key
SELECT tenant_id, chain_id, verifying_contract_address, pks_key, sks_key
FROM tenants
WHERE tenant_id = ANY($1::INT[])
",
Expand All @@ -132,7 +143,12 @@ where T: sqlx::PgExecutor<'a>
.expect("We can't deserialize our own validated sks key");
let pks: tfhe::CompactPublicKey = bincode::deserialize(&key.pks_key)
.expect("We can't deserialize our own validated pks key");
res.push(TfheTenantKeys { tenant_id: key.tenant_id, sks, pks });
res.push(TfheTenantKeys {
tenant_id: key.tenant_id,
sks, pks,
chain_id: key.chain_id,
verifying_contract_address: key.verifying_contract_address,
});
}

Ok(res)
Expand Down
106 changes: 104 additions & 2 deletions fhevm-engine/coprocessor/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use std::collections::BTreeMap;
use std::num::NonZeroUsize;
use std::str::FromStr;

use crate::db_queries::{check_if_api_key_is_valid, check_if_ciphertexts_exist_in_db, fetch_tenant_server_key};
use crate::server::coprocessor::GenericResponse;
use alloy::signers::local::PrivateKeySigner;
use alloy::signers::SignerSync;
use alloy::sol_types::SolStruct;
use bigdecimal::num_bigint::BigUint;
use fhevm_engine_common::tfhe_ops::{check_fhe_operand_types, current_ciphertext_version, debug_trivial_encrypt_be_bytes, deserialize_fhe_ciphertext, try_expand_ciphertext_list};
use fhevm_engine_common::types::{FhevmError, SupportedFheCiphertexts};
Expand All @@ -26,6 +30,7 @@ pub struct CoprocessorService {
pool: sqlx::Pool<sqlx::Postgres>,
args: crate::cli::Args,
tenant_key_cache: std::sync::Arc<tokio::sync::RwLock<lru::LruCache<i32, TfheTenantKeys>>>,
signer: PrivateKeySigner,
}

pub async fn run_server(
Expand All @@ -37,6 +42,13 @@ pub async fn run_server(
.expect("Can't parse server address");
let db_url = crate::utils::db_url(&args);

let coprocessor_key_file =
tokio::fs::read_to_string(&args.coprocessor_private_key)
.await?;

let signer = PrivateKeySigner::from_str(coprocessor_key_file.trim())?;
println!("Coprocessor signer address: {}", signer.address());

println!("Coprocessor listening on {}", addr);
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(args.pg_pool_max_connections)
Expand All @@ -48,7 +60,7 @@ pub async fn run_server(
NonZeroUsize::new(args.tenant_key_cache_size as usize).unwrap(),
)));

let service = CoprocessorService { pool, args, tenant_key_cache };
let service = CoprocessorService { pool, args, tenant_key_cache, signer };

Server::builder()
.add_service(
Expand All @@ -62,6 +74,39 @@ pub async fn run_server(
Ok(())
}

// for EIP712 signature
alloy::sol! {
struct CiphertextVerification {
uint256[] handlesList;
address contractAddress;
address callerAddress;
}
}

// copied from go coprocessor
// theData := signerApi.TypedData{
// Types: signerApi.Types{
// "EIP712Domain": domainType,
// "CiphertextVerification": []signerApi.Type{
// {Name: "handlesList", Type: "uint256[]"},
// {Name: "contractAddress", Type: "address"},
// {Name: "callerAddress", Type: "address"},
// },
// },
// Domain: signerApi.TypedDataDomain{
// Name: "FHEVMCoprocessor",
// Version: "1",
// ChainId: chainId,
// VerifyingContract: verifyingContract.Hex(),
// },
// PrimaryType: "CiphertextVerification",
// Message: signerApi.TypedDataMessage{
// "handlesList": hexInputs,
// "contractAddress": contractAddress.Hex(),
// "callerAddress": callerAddress.Hex(),
// },
// }

#[tonic::async_trait]
impl coprocessor::fhevm_coprocessor_server::FhevmCoprocessor for CoprocessorService {
async fn upload_inputs(
Expand All @@ -87,17 +132,55 @@ impl coprocessor::fhevm_coprocessor_server::FhevmCoprocessor for CoprocessorServ
return Ok(tonic::Response::new(response));
}

let server_key = {
let fetch_key_response = {
fetch_tenant_server_key(tenant_id, &self.pool, &self.tenant_key_cache)
.await
.map_err(|e| {
tonic::Status::from_error(e)
})?
};
let chain_id = fetch_key_response.chain_id;
let server_key = fetch_key_response.server_key;
let verifying_contract_address = fetch_key_response.verifying_contract_address;
let verifying_contract_address = alloy::primitives::Address::from_str(&verifying_contract_address)
.map_err(|e| {
tonic::Status::from_error(Box::new(CoprocessorError::CannotParseTenantEthereumAddress {
bad_address: verifying_contract_address.clone(),
parsing_error: e.to_string(),
}))
})?;

let eip_712_domain = alloy::sol_types::eip712_domain! {
name: "FHEVMCoprocessor",
version: "1",
chain_id: chain_id as u64,
verifying_contract: verifying_contract_address,
};

let mut tfhe_work_set = tokio::task::JoinSet::new();

// server key is biiig, clone the pointer
let server_key = std::sync::Arc::new(server_key);
let mut contract_addresses = Vec::with_capacity(req.input_ciphertexts.len());
let mut caller_addresses = Vec::with_capacity(req.input_ciphertexts.len());
for ci in &req.input_ciphertexts {
// parse addresses
contract_addresses.push(alloy::primitives::Address::from_str(&ci.contract_address)
.map_err(|e| {
CoprocessorError::CannotParseEthereumAddress {
bad_address: ci.contract_address.clone(),
parsing_error: e.to_string(),
}
})?);
caller_addresses.push(alloy::primitives::Address::from_str(&ci.caller_address)
.map_err(|e| {
CoprocessorError::CannotParseEthereumAddress {
bad_address: ci.contract_address.clone(),
parsing_error: e.to_string(),
}
})?);
}

for (idx, ci) in req.input_ciphertexts.iter().enumerate() {
let cloned_input = ci.clone();
let server_key = server_key.clone();
Expand Down Expand Up @@ -156,8 +239,18 @@ impl coprocessor::fhevm_coprocessor_server::FhevmCoprocessor for CoprocessorServ
", tenant_id, &blob_hash, &input_blob.input_payload, corresponding_unpacked.len() as i32)
.execute(trx.as_mut()).await.map_err(Into::<CoprocessorError>::into)?;

let mut ct_verification = CiphertextVerification {
contractAddress: contract_addresses[idx],
callerAddress: caller_addresses[idx],
handlesList: Vec::with_capacity(corresponding_unpacked.len()),
};

let mut ct_resp = InputCiphertextResponse {
input_handles: Vec::with_capacity(corresponding_unpacked.len()),
eip712_signature: Vec::new(),
eip712_contract_address: contract_addresses[idx].to_string(),
eip712_caller_address: caller_addresses[idx].to_string(),
eip712_signer_address: self.signer.address().to_string(),
};

for (ct_idx, the_ct) in corresponding_unpacked.iter().enumerate() {
Expand Down Expand Up @@ -189,12 +282,21 @@ impl coprocessor::fhevm_coprocessor_server::FhevmCoprocessor for CoprocessorServ
", tenant_id, &handle, &serialized_ct, ciphertext_version, serialized_type, &blob_hash, ct_idx as i32)
.execute(trx.as_mut()).await.map_err(Into::<CoprocessorError>::into)?;

ct_verification.handlesList.push(alloy::primitives::U256::from_be_slice(&handle));
ct_resp.input_handles.push(InputCiphertextResponseHandle {
handle: handle.to_vec(),
ciphertext_type: serialized_type as i32,
});
}

let signing_hash = ct_verification.eip712_signing_hash(&eip_712_domain);
let eip_712_signature = self.signer.sign_hash_sync(&signing_hash)
.map_err(|e| {
CoprocessorError::Eip712SigningFailure { error: e.to_string() }
})?;

ct_resp.eip712_signature = eip_712_signature.as_bytes().to_vec();

response.upload_responses.push(ct_resp);
}

Expand Down
10 changes: 9 additions & 1 deletion fhevm-engine/coprocessor/src/tests/errors.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::str::FromStr;

use tonic::metadata::MetadataValue;
use crate::{db_queries::query_tenant_keys, server::{common::FheOperation, coprocessor::{async_computation_input::Input, fhevm_coprocessor_client::FhevmCoprocessorClient, AsyncComputation, AsyncComputationInput, AsyncComputeRequest, InputToUpload, InputUploadBatch}}, tests::utils::{default_api_key, default_tenant_id, setup_test_app}};
use crate::{db_queries::query_tenant_keys, server::{common::FheOperation, coprocessor::{async_computation_input::Input, fhevm_coprocessor_client::FhevmCoprocessorClient, AsyncComputation, AsyncComputationInput, AsyncComputeRequest, InputToUpload, InputUploadBatch}}, tests::{inputs::{test_random_caller_address, test_random_contract_address}, utils::{default_api_key, default_tenant_id, setup_test_app}}};

#[tokio::test]
async fn test_coprocessor_input_errors() -> Result<(), Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -36,6 +36,8 @@ async fn test_coprocessor_input_errors() -> Result<(), Box<dyn std::error::Error
input_ciphertexts.push(InputToUpload {
input_payload: serialized.clone(),
signature: Vec::new(),
caller_address: test_random_caller_address(),
contract_address: test_random_contract_address(),
});
}

Expand Down Expand Up @@ -74,6 +76,8 @@ async fn test_coprocessor_input_errors() -> Result<(), Box<dyn std::error::Error
input_ciphertexts.push(InputToUpload {
input_payload: serialized[0..32].to_vec(),
signature: Vec::new(),
caller_address: test_random_caller_address(),
contract_address: test_random_contract_address(),
});

println!("Encrypting inputs...");
Expand Down Expand Up @@ -108,6 +112,8 @@ async fn test_coprocessor_input_errors() -> Result<(), Box<dyn std::error::Error
input_ciphertexts.push(InputToUpload {
input_payload: serialized,
signature: Vec::new(),
caller_address: test_random_caller_address(),
contract_address: test_random_contract_address(),
});

println!("Encrypting inputs...");
Expand Down Expand Up @@ -259,6 +265,8 @@ async fn test_coprocessor_computation_errors() -> Result<(), Box<dyn std::error:
input_ciphertexts.push(InputToUpload {
input_payload: serialized,
signature: Vec::new(),
caller_address: test_random_caller_address(),
contract_address: test_random_contract_address(),
});

println!("Encrypting inputs...");
Expand Down
13 changes: 13 additions & 0 deletions fhevm-engine/coprocessor/src/tests/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ use tonic::metadata::MetadataValue;

use crate::{db_queries::query_tenant_keys, server::coprocessor::{fhevm_coprocessor_client::FhevmCoprocessorClient, DebugDecryptRequest, InputToUpload, InputUploadBatch}, tests::utils::{default_api_key, default_tenant_id, setup_test_app}};

pub fn test_random_caller_address() -> String {
let _private_key = "bd2400c676871534a682ca1c5e4cd647ec9c3e122f188c6e3f54e6900d586c7b";
let public_key = "0x1BdA2a485c339C95a9AbfDe52E80ca38e34C199E";
public_key.to_string()
}

pub fn test_random_contract_address() -> String {
"0x76c222560Db6b8937B291196eAb4Dad8930043aE".to_string()
}

#[tokio::test]
async fn test_fhe_inputs() -> Result<(), Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -45,6 +54,8 @@ async fn test_fhe_inputs() -> Result<(), Box<dyn std::error::Error>> {
InputToUpload {
input_payload: serialized,
signature: Vec::new(),
caller_address: test_random_caller_address(),
contract_address: test_random_contract_address(),
}
]
});
Expand Down Expand Up @@ -139,6 +150,8 @@ async fn custom_insert_inputs() -> Result<(), Box<dyn std::error::Error>> {
InputToUpload {
input_payload: serialized,
signature: Vec::new(),
caller_address: test_random_caller_address(),
contract_address: test_random_contract_address(),
}
]
});
Expand Down
1 change: 1 addition & 0 deletions fhevm-engine/coprocessor/src/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub async fn setup_test_app_custom_docker() -> Result<TestInstance, Box<dyn std:
server_addr: format!("127.0.0.1:{app_port}"),
database_url: Some(db_url.clone()),
maximimum_compact_inputs_upload: 10,
coprocessor_private_key: "./coprocessor.key".to_string(),
};

std::thread::spawn(move || {
Expand Down
Loading

0 comments on commit bca4c6b

Please sign in to comment.