|
@@ -16,84 +16,115 @@
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
-use tfhe::{
|
|
|
|
|
- boolean::prelude::{gen_keys as boolean_gen_keys, *},
|
|
|
|
|
- integer::gen_keys_radix,
|
|
|
|
|
- shortint::prelude::{gen_keys as shortint_gen_keys, *},
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-fn main() {
|
|
|
|
|
- // ===============
|
|
|
|
|
- // Boolean circuit
|
|
|
|
|
- // ===============
|
|
|
|
|
- // Generate a set of client/server keys, using the default parameters.
|
|
|
|
|
- // The client generates both keys. The server key is meant to be published
|
|
|
|
|
- // so that homomorphic circuits can be computed.
|
|
|
|
|
- let (client_key, server_key) = boolean_gen_keys();
|
|
|
|
|
-
|
|
|
|
|
- // Encrypt two messages using the (private) client key:
|
|
|
|
|
- let msg1 = true;
|
|
|
|
|
- let msg2 = false;
|
|
|
|
|
- let ct_1 = client_key.encrypt(msg1);
|
|
|
|
|
- let ct_2 = client_key.encrypt(msg2);
|
|
|
|
|
-
|
|
|
|
|
- // We use the server public key to execute a boolean circuit:
|
|
|
|
|
- // if ((NOT ct_2) NAND (ct_1 AND ct_2)) then (NOT ct_2) else (ct_1 AND ct_2)
|
|
|
|
|
- let ct_3 = server_key.not(&ct_2);
|
|
|
|
|
- let ct_4 = server_key.and(&ct_1, &ct_2);
|
|
|
|
|
- let ct_5 = server_key.nand(&ct_3, &ct_4);
|
|
|
|
|
- let ct_6 = server_key.mux(&ct_5, &ct_3, &ct_4);
|
|
|
|
|
-
|
|
|
|
|
- // We use the client key to decrypt the output of the circuit
|
|
|
|
|
- let output = client_key.decrypt(&ct_6);
|
|
|
|
|
- assert!(output);
|
|
|
|
|
-
|
|
|
|
|
- // ================
|
|
|
|
|
- // Shortint circuit
|
|
|
|
|
- // ================
|
|
|
|
|
- // Generate a set of client/server keys
|
|
|
|
|
- // with 2 bits of message and 2 bits of carry
|
|
|
|
|
- let (client_key, server_key) = shortint_gen_keys(PARAM_MESSAGE_2_CARRY_2);
|
|
|
|
|
-
|
|
|
|
|
- let msg1 = 3;
|
|
|
|
|
- let msg2 = 2;
|
|
|
|
|
-
|
|
|
|
|
- // Encrypt two messages using the (private) client key:
|
|
|
|
|
- let ct_1 = client_key.encrypt(msg1);
|
|
|
|
|
- let ct_2 = client_key.encrypt(msg2);
|
|
|
|
|
-
|
|
|
|
|
- // Homomorphically compute an addition
|
|
|
|
|
- let ct_add = server_key.unchecked_add(&ct_1, &ct_2);
|
|
|
|
|
-
|
|
|
|
|
- // Define the Hamming weight function
|
|
|
|
|
- // f: x -> sum of the bits of x
|
|
|
|
|
- let f = |x: u64| x.count_ones() as u64;
|
|
|
|
|
-
|
|
|
|
|
- // Generate the accumulator for the function
|
|
|
|
|
- let acc = server_key.generate_accumulator(f);
|
|
|
|
|
-
|
|
|
|
|
- // Compute the function over the ciphertext using the PBS
|
|
|
|
|
- let ct_res = server_key.apply_lookup_table(&ct_add, &acc);
|
|
|
|
|
-
|
|
|
|
|
- // Decrypt the ciphertext using the (private) client key
|
|
|
|
|
- let output = client_key.decrypt(&ct_res);
|
|
|
|
|
- assert_eq!(output, f(msg1 + msg2));
|
|
|
|
|
-
|
|
|
|
|
- // ===============
|
|
|
|
|
- // Integer circuit
|
|
|
|
|
- // ===============
|
|
|
|
|
- // We create keys to create 16 bits integers
|
|
|
|
|
- // using 8 blocks of 2 bits
|
|
|
|
|
- let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, 8);
|
|
|
|
|
|
|
+use std::time::Instant;
|
|
|
|
|
+
|
|
|
|
|
+use tfhe::integer::ciphertext::RadixCiphertext;
|
|
|
|
|
+use tfhe::integer::keycache::IntegerKeyCache;
|
|
|
|
|
+use tfhe::integer::{IntegerKeyKind, ServerKey};
|
|
|
|
|
+use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
|
|
|
|
|
+
|
|
|
|
|
+mod fhe;
|
|
|
|
|
+mod improved_parallel_fhe;
|
|
|
|
|
+mod improved_plain;
|
|
|
|
|
+mod parallel_fhe;
|
|
|
|
|
+mod plain;
|
|
|
|
|
+
|
|
|
|
|
+/// The number of blocks to be used in the Radix.
|
|
|
|
|
+const NUMBER_OF_BLOCKS: usize = 8;
|
|
|
|
|
+
|
|
|
|
|
+#[allow(clippy::type_complexity)]
|
|
|
|
|
+fn test_cases() -> Vec<(String, (Vec<u16>, Vec<u16>, Vec<u16>, Vec<u16>))> {
|
|
|
|
|
+ vec![
|
|
|
|
|
+ (
|
|
|
|
|
+ "empty sell orders".to_owned(),
|
|
|
|
|
+ (vec![], (1..11).collect::<Vec<_>>(), vec![], vec![0; 10]),
|
|
|
|
|
+ ),
|
|
|
|
|
+ (
|
|
|
|
|
+ "empty buy orders".to_owned(),
|
|
|
|
|
+ ((1..11).collect::<Vec<_>>(), vec![], vec![0; 10], vec![]),
|
|
|
|
|
+ ),
|
|
|
|
|
+ (
|
|
|
|
|
+ "exact matching of sell and buy orders".to_owned(),
|
|
|
|
|
+ (
|
|
|
|
|
+ (1..11).collect::<Vec<_>>(),
|
|
|
|
|
+ (1..11).collect::<Vec<_>>(),
|
|
|
|
|
+ (1..11).collect::<Vec<_>>(),
|
|
|
|
|
+ (1..11).collect::<Vec<_>>(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ (
|
|
|
|
|
+ "a case where there are more buy orders than sell orders".to_owned(),
|
|
|
|
|
+ (vec![10; 10], vec![200], vec![10; 10], vec![100]),
|
|
|
|
|
+ ),
|
|
|
|
|
+ (
|
|
|
|
|
+ "a case where there are more sell orders than buy orders".to_owned(),
|
|
|
|
|
+ (vec![200], vec![10; 10], vec![100], vec![10; 10]),
|
|
|
|
|
+ ),
|
|
|
|
|
+ (
|
|
|
|
|
+ "maximum input size for sell and buy orders".to_owned(),
|
|
|
|
|
+ (
|
|
|
|
|
+ vec![100; 499],
|
|
|
|
|
+ vec![100; 499],
|
|
|
|
|
+ vec![100; 499],
|
|
|
|
|
+ vec![100; 499],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ]
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- let clear_a = 2382u16;
|
|
|
|
|
- let clear_b = 29374u16;
|
|
|
|
|
|
|
+/// Runs the given [tester] function with the test cases for volume matching algorithm.
|
|
|
|
|
+fn run_test_cases(tester: impl Fn(&[u16], &[u16], &[u16], &[u16])) {
|
|
|
|
|
+ for (test_name, test_case) in &test_cases() {
|
|
|
|
|
+ println!("Testing {test_name}...");
|
|
|
|
|
+ tester(&test_case.0, &test_case.1, &test_case.2, &test_case.3);
|
|
|
|
|
+ println!();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- let mut a = cks.encrypt(clear_a as u64);
|
|
|
|
|
- let mut b = cks.encrypt(clear_b as u64);
|
|
|
|
|
|
|
+fn test_volume_match_plain(function: fn(&mut [u16], &mut [u16])) {
|
|
|
|
|
+ println!("Running test cases for the plain implementation");
|
|
|
|
|
+ run_test_cases(|a, b, c, d| plain::tester(a, b, c, d, function));
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- let encrypted_max = sks.smart_max_parallelized(&mut a, &mut b);
|
|
|
|
|
- let decrypted_max: u64 = cks.decrypt(&encrypted_max);
|
|
|
|
|
|
|
+fn test_volume_match_fhe(
|
|
|
|
|
+ fhe_function: fn(&mut [RadixCiphertext], &mut [RadixCiphertext], &ServerKey),
|
|
|
|
|
+) {
|
|
|
|
|
+ println!("Generating keys...");
|
|
|
|
|
+ let time = Instant::now();
|
|
|
|
|
+ let (client_key, server_key) =
|
|
|
|
|
+ IntegerKeyCache.get_from_params(PARAM_MESSAGE_2_CARRY_2_KS_PBS, IntegerKeyKind::Radix);
|
|
|
|
|
+ println!("Keys generated in {:?}", time.elapsed());
|
|
|
|
|
+
|
|
|
|
|
+ println!("Running test cases for the FHE implementation");
|
|
|
|
|
+ run_test_cases(|a, b, c, d| fhe::tester(&client_key, &server_key, a, b, c, d, fhe_function));
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- assert_eq!(decrypted_max as u16, clear_a.max(clear_b))
|
|
|
|
|
|
|
+fn main() {
|
|
|
|
|
+ for argument in std::env::args() {
|
|
|
|
|
+ if argument == "plain" {
|
|
|
|
|
+ println!("Running plain version");
|
|
|
|
|
+ test_volume_match_plain(plain::volume_match);
|
|
|
|
|
+ println!();
|
|
|
|
|
+ }
|
|
|
|
|
+ if argument == "plain-improved" {
|
|
|
|
|
+ println!("Running plain improved version");
|
|
|
|
|
+ test_volume_match_plain(improved_plain::volume_match);
|
|
|
|
|
+ println!();
|
|
|
|
|
+ }
|
|
|
|
|
+ if argument == "fhe" {
|
|
|
|
|
+ println!("Running fhe version");
|
|
|
|
|
+ test_volume_match_fhe(fhe::volume_match);
|
|
|
|
|
+ println!();
|
|
|
|
|
+ }
|
|
|
|
|
+ if argument == "fhe-parallel" {
|
|
|
|
|
+ println!("Running parallelized fhe version");
|
|
|
|
|
+ test_volume_match_fhe(parallel_fhe::volume_match);
|
|
|
|
|
+ println!();
|
|
|
|
|
+ }
|
|
|
|
|
+ if argument == "fhe-improved" {
|
|
|
|
|
+ println!("Running improved parallelized fhe fhe version");
|
|
|
|
|
+ test_volume_match_fhe(improved_parallel_fhe::volume_match);
|
|
|
|
|
+ println!();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|