/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2024 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::time::Instant;
use rayon::prelude::*;
use tfhe::integer::ciphertext::RadixCiphertext;
use tfhe::integer::{IntegerCiphertext, ServerKey};
use crate::NUMBER_OF_BLOCKS;
fn compute_prefix_sum(server_key: &ServerKey, arr: &[RadixCiphertext]) -> Vec {
if arr.is_empty() {
return arr.to_vec();
}
let mut prefix_sum: Vec = (0..arr.len().next_power_of_two())
.into_par_iter()
.map(|i| {
if i < arr.len() {
arr[i].clone()
} else {
server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS)
}
})
.collect();
for d in 0..prefix_sum.len().ilog2() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let mut left = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut left)
});
}
let last = prefix_sum.last().unwrap().clone();
*prefix_sum.last_mut().unwrap() = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
for d in (0..prefix_sum.len().ilog2()).rev() {
prefix_sum
.par_chunks_exact_mut(2_usize.pow(d + 1))
.for_each(move |chunk| {
let length = chunk.len();
let temp = chunk.last().unwrap().clone();
let mut mid = chunk.get((length - 1) / 2).unwrap().clone();
server_key.smart_add_assign_parallelized(chunk.last_mut().unwrap(), &mut mid);
chunk[(length - 1) / 2] = temp;
});
}
prefix_sum.push(last);
prefix_sum[1..=arr.len()].to_vec()
}
fn fill_orders(
server_key: &ServerKey,
total_orders: &RadixCiphertext,
orders: &mut [RadixCiphertext],
prefix_sum_arr: &[RadixCiphertext],
) {
orders
.into_par_iter()
.enumerate()
.for_each(move |(i, order)| {
// (total_orders - previous_prefix_sum).max(0)
let mut diff = if i == 0 {
total_orders.clone()
} else {
let previous_prefix_sum = &prefix_sum_arr[i - 1];
// total_orders - previous_prefix_sum
let mut diff = server_key.smart_sub_parallelized(
&mut total_orders.clone(),
&mut previous_prefix_sum.clone(),
);
// total_orders > prefix_sum
let mut cond = server_key
.smart_gt_parallelized(
&mut total_orders.clone(),
&mut previous_prefix_sum.clone(),
)
.into_radix(diff.blocks().len(), server_key);
// (total_orders - previous_prefix_sum) * (total_orders > previous_prefix_sum)
// = (total_orders - previous_prefix_sum).max(0)
server_key.smart_mul_parallelized(&mut cond, &mut diff)
};
// (total_orders - previous_prefix_sum).max(0).min(*order);
*order = server_key.smart_min_parallelized(&mut diff, order);
});
}
/// FHE implementation of the volume matching algorithm.
///
/// In this function, the implemented algorithm is modified to utilize more concurrency.
///
/// Matches the given encrypted [sell_orders] with encrypted [buy_orders] using the given
/// [server_key]. The amount of the orders that are successfully filled is written over the original
/// order count.
pub fn volume_match(
sell_orders: &mut [RadixCiphertext],
buy_orders: &mut [RadixCiphertext],
server_key: &ServerKey,
) {
println!("Creating prefix sum arrays...");
let time = Instant::now();
let (prefix_sum_sell_orders, prefix_sum_buy_orders) = rayon::join(
|| compute_prefix_sum(server_key, sell_orders),
|| compute_prefix_sum(server_key, buy_orders),
);
println!("Created prefix sum arrays in {:?}", time.elapsed());
let zero = server_key.create_trivial_zero_radix(NUMBER_OF_BLOCKS);
let total_buy_orders = prefix_sum_buy_orders.last().unwrap_or(&zero);
let total_sell_orders = prefix_sum_sell_orders.last().unwrap_or(&zero);
println!("Matching orders...");
let time = Instant::now();
rayon::join(
|| {
fill_orders(
server_key,
total_sell_orders,
buy_orders,
&prefix_sum_buy_orders,
)
},
|| {
fill_orders(
server_key,
total_buy_orders,
sell_orders,
&prefix_sum_sell_orders,
)
},
);
println!("Matched orders in {:?}", time.elapsed());
}