diff --git a/lib/zama/src/lib.rs b/lib/zama/src/lib.rs index 5de244e..2124024 100644 --- a/lib/zama/src/lib.rs +++ b/lib/zama/src/lib.rs @@ -3,7 +3,9 @@ use std::os::raw::c_char; use tfhe::prelude::*; use tfhe::{ConfigBuilder, FheUint8, FheUint32, generate_keys, set_server_key}; -use bincode; // Make sure to add bincode to Cargo.toml +use bincode; + +// use crate::genome_processing::{encode_genotype, get_genotype_encoding_map}; #[unsafe(no_mangle)] pub extern "C" fn zama_run() -> *mut c_char { @@ -71,7 +73,41 @@ pub extern "C" fn zama_get_client_key() -> *mut c_char { // Key generation let (client_key, server_keys) = generate_keys(config); + CString::new(serde_json::to_string(&client_key).unwrap()) + .unwrap() + .into_raw() +} + + +#[unsafe(no_mangle)] +pub extern "C" fn zama_get_server_key() -> *mut c_char { + // Basic configuration to use homomorphic integers + let config = ConfigBuilder::default().build(); + + // Key generation + let (client_key, server_keys) = generate_keys(config); + CString::new(serde_json::to_string(&server_keys).unwrap()) .unwrap() .into_raw() } + +// TODO: use a single function in getting the keys +#[unsafe(no_mangle)] +pub extern "C" fn zama_get_keys() -> *mut c_char { + let config = ConfigBuilder::default().build(); + + let (client_key, server_keys) = generate_keys(config); + + // Serialize both keys into a JSON object + let keys = serde_json::json!({ + "client_key": client_key, + "server_keys": server_keys, + }); + + // Serialize the entire JSON object to a string + let json_str = serde_json::to_string(&keys).unwrap(); + + // Convert to a CString and return a raw pointer + CString::new(json_str).unwrap().into_raw() +} \ No newline at end of file diff --git a/lib/zama/src/libzama.a b/lib/zama/src/libzama.a new file mode 100644 index 0000000..8d56240 Binary files /dev/null and b/lib/zama/src/libzama.a differ diff --git a/monadic_ios/includes/lib_headers.h b/monadic_ios/includes/lib_headers.h index 57d694d..d75303d 100644 --- a/monadic_ios/includes/lib_headers.h +++ b/monadic_ios/includes/lib_headers.h @@ -5,9 +5,19 @@ #include #include #include +#include char* zama_run(); char* zama_get_client_key(); +char* zama_get_server_key(); +char* zama_get_keys(); + +int32_t zama_process_genome_data( + const char* filename, + size_t num_lines, + const char* client_key_json, + const char* server_key_json +); #endif \ No newline at end of file diff --git a/monadic_ios/libs/libzama.a b/monadic_ios/libs/libzama.a index e85c79a..ed8e856 100644 Binary files a/monadic_ios/libs/libzama.a and b/monadic_ios/libs/libzama.a differ diff --git a/monadic_ios/libs/libzama_poc.a b/monadic_ios/libs/libzama_poc.a new file mode 100644 index 0000000..291d17d Binary files /dev/null and b/monadic_ios/libs/libzama_poc.a differ diff --git a/monadic_ios/monadic_ios.xcodeproj/project.pbxproj b/monadic_ios/monadic_ios.xcodeproj/project.pbxproj index 09157c3..c9c95a5 100644 --- a/monadic_ios/monadic_ios.xcodeproj/project.pbxproj +++ b/monadic_ios/monadic_ios.xcodeproj/project.pbxproj @@ -7,8 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - CA445A852CFE803400BF9EFC /* libzama.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CA445A842CFE802B00BF9EFC /* libzama.a */; }; + CA42209D2D06772700AD2E5A /* libzama.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CA42209C2D06771E00AD2E5A /* libzama.a */; }; CA445A8B2CFE855A00BF9EFC /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = CA445A8A2CFE855A00BF9EFC /* KeychainSwift */; }; + CAB71F242D06C2E8004A4B4E /* libzama_poc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CAB71F232D06C2DE004A4B4E /* libzama_poc.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -29,11 +30,12 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - CA445A842CFE802B00BF9EFC /* libzama.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libzama.a; path = libs/libzama.a; sourceTree = ""; }; + CA42209C2D06771E00AD2E5A /* libzama.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libzama.a; path = libs/libzama.a; sourceTree = ""; }; CA98B90A2CF5E864006ABA9E /* monadic_ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = monadic_ios.app; sourceTree = BUILT_PRODUCTS_DIR; }; CA98B91A2CF5E866006ABA9E /* monadic_iosTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = monadic_iosTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CA98B9242CF5E866006ABA9E /* monadic_iosUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = monadic_iosUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CA98BA822CF876B8006ABA9E /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; + CAB71F232D06C2DE004A4B4E /* libzama_poc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libzama_poc.a; path = libs/libzama_poc.a; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -59,8 +61,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CAB71F242D06C2E8004A4B4E /* libzama_poc.a in Frameworks */, + CA42209D2D06772700AD2E5A /* libzama.a in Frameworks */, CA445A8B2CFE855A00BF9EFC /* KeychainSwift in Frameworks */, - CA445A852CFE803400BF9EFC /* libzama.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -106,7 +109,8 @@ CA98BA7D2CF87545006ABA9E /* Frameworks */ = { isa = PBXGroup; children = ( - CA445A842CFE802B00BF9EFC /* libzama.a */, + CAB71F232D06C2DE004A4B4E /* libzama_poc.a */, + CA42209C2D06771E00AD2E5A /* libzama.a */, ); name = Frameworks; sourceTree = ""; @@ -431,7 +435,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/libs"; + LIBRARY_SEARCH_PATHS = ( + "$(PROJECT_DIR)/libs", + "$(PROJECT_DIR)", + ); "LIBRARY_SEARCH_PATHS[arch=*]" = "$(PROJECT_DIR)/libs"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.aisha.monadic-ios"; @@ -463,7 +470,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/libs"; + LIBRARY_SEARCH_PATHS = ( + "$(PROJECT_DIR)/libs", + "$(PROJECT_DIR)", + ); "LIBRARY_SEARCH_PATHS[arch=*]" = "$(PROJECT_DIR)/libs"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.aisha.monadic-ios"; diff --git a/monadic_ios/monadic_ios.xcodeproj/project.xcworkspace/xcuserdata/aisha.xcuserdatad/UserInterfaceState.xcuserstate b/monadic_ios/monadic_ios.xcodeproj/project.xcworkspace/xcuserdata/aisha.xcuserdatad/UserInterfaceState.xcuserstate index fd6fd96..322d8ba 100644 Binary files a/monadic_ios/monadic_ios.xcodeproj/project.xcworkspace/xcuserdata/aisha.xcuserdatad/UserInterfaceState.xcuserstate and b/monadic_ios/monadic_ios.xcodeproj/project.xcworkspace/xcuserdata/aisha.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/monadic_ios/monadic_ios/ContentView.swift b/monadic_ios/monadic_ios/ContentView.swift index 167f628..2410dd5 100644 --- a/monadic_ios/monadic_ios/ContentView.swift +++ b/monadic_ios/monadic_ios/ContentView.swift @@ -11,11 +11,18 @@ struct ContentView: View { @State private var statusText: String = "" @State private var clientKeyText: String = "" + // Define the number of lines + @State private var numLines = 20 + + @State private var presentImporter = false + + var body: some View { VStack { + Text(statusText) .padding() - + Button("Run Zama Code") { if let result = zama_run() { let resultString = String(cString: result) @@ -24,8 +31,27 @@ struct ContentView: View { } storeClientKey() } - - + + Button("Upload Genome Data") { + presentImporter = true + }.fileImporter(isPresented: $presentImporter, allowedContentTypes: [.text]) { result in + uploadAndProcessGenomeData(result: result) + } + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(8) + + Button("Retrieve Server Key") { + guard let serverKey = retrieveServerKey() else { + clientKeyText = "No server key found in keychain" + return + } + let serverKeyLength = strlen(serverKey) + print("Length of server key: \(serverKeyLength)") + } + + Button("Retrieve Client Key") { guard let clientKey = retrieveClientKey() else { clientKeyText = "No client key found in keychain" @@ -35,16 +61,35 @@ struct ContentView: View { print("Length of client key: \(clientKeyLength)") // clientKeyText = clientKey - + clientKeyText = "Length of client key: \(clientKeyLength)" - + } } } + + func uploadAndProcessGenomeData(result: Result) { + guard let url = try? result.get() else { + print("File upload failed:", result) + return + } + + guard url.startAccessingSecurityScopedResource() else { + print("Failed to access file resource.") + return + } + defer { url.stopAccessingSecurityScopedResource() } + + guard let filename = url.path.removingPercentEncoding else { + print("Filename is invalid.") + return + } + + processGenomeData(filename: filename, numLines: numLines) + } } - #Preview { ContentView() } diff --git a/monadic_ios/monadic_ios/utils/zama.swift b/monadic_ios/monadic_ios/utils/zama.swift index 22c572c..c1ce7b0 100644 --- a/monadic_ios/monadic_ios/utils/zama.swift +++ b/monadic_ios/monadic_ios/utils/zama.swift @@ -10,6 +10,14 @@ import KeychainSwift let keychain = KeychainSwift() +struct ClientKey { + var key: [UInt8] +} + +struct ServerKey { + var key: [UInt8] +} + func storeClientKey() { guard let clientKey = zama_get_client_key() else { print("Failed to retrieve client key.") @@ -17,10 +25,51 @@ func storeClientKey() { } let clientKeyString = String(cString: clientKey) + guard let serverKey = zama_get_server_key() else { + print("Failed to retrieve server key.") + return + } + let serverKeyString = String(cString: serverKey) + + keychain.set(clientKeyString, forKey: "zamaClientKey") + keychain.set(serverKeyString, forKey: "zamaServerKey") + print("Successfully stored client key.") } func retrieveClientKey() -> String? { return keychain.get("zamaClientKey") } +func retrieveServerKey() -> String? { + return keychain.get("zamaServerKey") +} + + +func processGenomeData(filename: String, numLines: Int) { + // Retrieve keys from Keychain + guard let clientKey = retrieveClientKey(), let serverKey = retrieveServerKey() else { + print("Failed to retrieve keys from Keychain.") + return + } + + // Convert keys to C strings + let clientKeyCString = (clientKey as NSString).utf8String + let serverKeyCString = (serverKey as NSString).utf8String + + + // Call zama_process_genome_data + let result = zama_process_genome_data( + filename, + numLines, + clientKeyCString, + serverKeyCString + ) + + if result == 0 { + print("Iteration completed successfully.") + } else { + print("Iteration failed.") + } +} + diff --git a/zama-poc/Cargo.lock b/zama-poc/Cargo.lock index e55e7ae..8714c59 100644 --- a/zama-poc/Cargo.lock +++ b/zama-poc/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "actix-codec" @@ -786,6 +786,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + [[package]] name = "futures-sink" version = "0.3.30" @@ -805,9 +811,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", + "futures-io", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] diff --git a/zama-poc/Cargo.toml b/zama-poc/Cargo.toml index b0cc991..1feb041 100644 --- a/zama-poc/Cargo.toml +++ b/zama-poc/Cargo.toml @@ -11,7 +11,7 @@ bincode = "1.3.3" chrono = "0.4.38" env_logger = "0.11.3" log = "0.4.22" -reqwest = { version = "0.11", features = ["json"] } +reqwest = { version = "0.11", features = ["blocking", "json"] } rusqlite = { version = "0.26", features = ["bundled"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -19,6 +19,10 @@ tfhe = { version = "0.7.1", features = [ "boolean", "shortint", "integer", "aarc tokio = { version = "1", features = ["full"] } tokio-stream = "0.1" +[lib] +crate-type = ["staticlib"] + + [[bin]] name = "main" path = "src/main.rs" @@ -30,3 +34,4 @@ path = "src/server.rs" [[bin]] name = "client" path = "src/client.rs" + diff --git a/zama-poc/src/ffi/mod.rs b/zama-poc/src/ffi/mod.rs new file mode 100644 index 0000000..267af4a --- /dev/null +++ b/zama-poc/src/ffi/mod.rs @@ -0,0 +1 @@ +pub mod processing; diff --git a/zama-poc/src/ffi/processing.rs b/zama-poc/src/ffi/processing.rs new file mode 100644 index 0000000..0267795 --- /dev/null +++ b/zama-poc/src/ffi/processing.rs @@ -0,0 +1,64 @@ +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +use std::io::Error; +use tfhe::prelude::{FheDecrypt, FheEq, FheTryEncrypt}; +use crate::genome_processing::{process_file, encode_genotype, get_genotype_encoding_map}; + +use tfhe::{ClientKey, ServerKey, set_server_key}; + +use crate::zama_compute::{process_and_encrypt_genome_data, encrypt_genotypes_for_zama, serialize_encrypted_genotypes}; + + +#[no_mangle] +pub extern "C" fn zama_process_genome_data( + filename: *const c_char, + num_lines: usize, + client_key_json: *const c_char, + server_key_json: *const c_char, +) -> i32 { + // Convert C-style strings to Rust strings + let filename_str = unsafe { CStr::from_ptr(filename).to_string_lossy().into_owned() }; + println!("Filename: {}", filename_str); + + let client_key_str = unsafe { CStr::from_ptr(client_key_json).to_string_lossy().into_owned() }; + + let server_key_str = unsafe { CStr::from_ptr(server_key_json).to_string_lossy().into_owned() }; + + // Deserialize the JSON keys into appropriate Rust types + let client_key: ClientKey = match serde_json::from_str(&client_key_str) { + Ok(key) => { + println!("Successfully deserialized client key."); + key + }, + Err(err) => { + println!("Failed to deserialize client key: {}", err); + return -1; + }, + }; + + let server_key: ServerKey = match serde_json::from_str(&server_key_str) { + Ok(key) => { + println!("Successfully deserialized server key."); + key + }, + Err(err) => { + println!("Failed to deserialize server key: {}", err); + return -1; + }, + }; + + // Run the iteration process + println!("Running iteration with {} lines.", num_lines); + match process_and_encrypt_genome_data(&filename_str, num_lines, &client_key, &server_key) { + Ok(_) => { + println!("Iteration completed successfully."); + 0 + }, + Err(err) => { + println!("Iteration failed with error: {:?}", err); + -1 + }, + } +} + diff --git a/zama-poc/src/genome_processing/mod.rs b/zama-poc/src/genome_processing/mod.rs index 77e92dd..cf70e63 100644 --- a/zama-poc/src/genome_processing/mod.rs +++ b/zama-poc/src/genome_processing/mod.rs @@ -5,8 +5,23 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use log::info; +use std::path::Path; +use reqwest::blocking::get; + pub fn process_file(filename: &str, num_lines: usize) -> io::Result> { - let file = File::open(filename)?; + let file_path: String; + + // Check if filename is a URL + if filename.starts_with("http://") || filename.starts_with("https://") { + println!("Downloading file from URL: {}", filename); + file_path = download_file(filename, "genome_data_file.txt")?; + } else { + file_path = filename.to_string(); + } + + + + let file = File::open(file_path)?; let reader = BufReader::new(file); let mut results = HashMap::new(); @@ -60,3 +75,12 @@ pub fn encode_genotype(genotype: &str) -> u8 { let encoding_map = get_genotype_encoding_map(); *encoding_map.get(genotype).unwrap_or(&0) } + +fn download_file(url: &str, output_path: &str) -> io::Result { + let response = get(url).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + let mut file = File::create(output_path)?; + std::io::copy(&mut response.bytes().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?.as_ref(), &mut file)?; + + Ok(output_path.to_string()) +} diff --git a/zama-poc/src/zama_compute/mod.rs b/zama-poc/src/zama_compute/mod.rs index df62e6c..9e1dcfa 100644 --- a/zama-poc/src/zama_compute/mod.rs +++ b/zama-poc/src/zama_compute/mod.rs @@ -1,3 +1,4 @@ +use std::time::Instant; use std::collections::HashMap; use tfhe::{ClientKey, CompressedFheUint8, ConfigBuilder, FheBool, FheUint8, generate_keys, ServerKey, set_server_key}; use std::result; @@ -128,3 +129,72 @@ pub fn get_encrypted_genotype_encoding_map(client_key: &ClientKey) -> HashMap<&' }) .collect() } + +pub fn process_and_encrypt_genome_data( + filename: &str, + num_lines: usize, + client_key: &ClientKey, + server_key: &ServerKey, + ) -> result::Result<(), Error> { + println!("Cloning server key..."); + + let cloned_server_key = server_key.clone(); + set_server_key(server_key.clone()); + + println!("Number of lines to process: {:?}", num_lines); + + let start_processed_data = Instant::now(); + + let processed_data = genome_processing::process_file(filename, num_lines)?; + println!("Lines of processed data: {:?}", processed_data.len()); + + let processed_data_duration = start_processed_data.elapsed(); + println!("genome_processing took: {:?}", processed_data_duration); + + + let start_encrypted_genotypes = Instant::now(); + let encrypted_genotypes = encrypt_genotypes_for_zama(&processed_data, client_key.clone())?; + println!("Lines of encrypted data: {:?}", encrypted_genotypes.len()); + + let encrypted_genotypes_duration = start_encrypted_genotypes.elapsed(); + println!("encrypt_genotypes_for_zama took: {:?}", encrypted_genotypes_duration); + + + let target_genotype = "CC"; + let target_rsid = "rs75333668"; + + let start_check_genotype = Instant::now(); + let encoded_result = check_genotype(&encrypted_genotypes, target_rsid, target_genotype)?; + let decoded_result = encoded_result.decrypt(&client_key); + println!("Lookup result: {:?}", decoded_result); + + let check_genotype_duration = start_check_genotype.elapsed(); + println!("check_genotype took: {:?}", check_genotype_duration); + + + let target_genotype = "AA"; + let target_rsid = "rs75333668"; + let encoded_result = check_genotype(&encrypted_genotypes, target_rsid, target_genotype)?; + let decoded_result = encoded_result.decrypt(&client_key); + println!("Lookup result: {:?}", decoded_result); + + let genotype_frequencies = get_genotype_frequencies(&encrypted_genotypes, 10); + println!("Genotype frequencies:"); + + for (i, frequency) in genotype_frequencies.iter().enumerate() { + let decrypted_frequency: u8 = frequency.decrypt(&client_key); + println!("{:?}: {:?}", i, decrypted_frequency); + } + + let start_serialized_data = Instant::now(); + + let mut serialized_data = Vec::new(); + serialize_encrypted_genotypes(&cloned_server_key, &encrypted_genotypes, &mut serialized_data); + println!("Serialized data: {:?}", serialized_data.len()); + + let serialized_data_duration = start_serialized_data.elapsed(); + println!("serialize_encrypted_genotypes took: {:?}", serialized_data_duration); + + + Ok(()) +} \ No newline at end of file