From 27e46264c86b4b0ad94c5bb8f180e71e6130bb6c Mon Sep 17 00:00:00 2001 From: han0110 Date: Thu, 10 Oct 2024 06:38:56 +0000 Subject: [PATCH 1/4] feat: update `phantom-zone` to use ring packing --- Cargo.lock | 8 +- Cargo.toml | 2 +- src/client.rs | 84 +++- src/phantom.rs | 1146 ++++++++++++++++++++++++++++++++---------------- src/server.rs | 30 +- src/types.rs | 55 ++- 6 files changed, 905 insertions(+), 420 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac3ff82..20314b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1166,7 +1166,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phantom-zone-crypto" version = "0.1.0" -source = "git+https://github.com/gausslabs/phantom-zone?rev=5232e55bfa811487438c34f745cf830b9b1f22d9#5232e55bfa811487438c34f745cf830b9b1f22d9" +source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94e69d992d86b1c72a0fddcd585d9be39afd8815" dependencies = [ "itertools", "num-traits", @@ -1179,7 +1179,7 @@ dependencies = [ [[package]] name = "phantom-zone-derive" version = "0.1.0" -source = "git+https://github.com/gausslabs/phantom-zone?rev=5232e55bfa811487438c34f745cf830b9b1f22d9#5232e55bfa811487438c34f745cf830b9b1f22d9" +source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94e69d992d86b1c72a0fddcd585d9be39afd8815" dependencies = [ "proc-macro2", "quote", @@ -1189,7 +1189,7 @@ dependencies = [ [[package]] name = "phantom-zone-evaluator" version = "0.1.0" -source = "git+https://github.com/gausslabs/phantom-zone?rev=5232e55bfa811487438c34f745cf830b9b1f22d9#5232e55bfa811487438c34f745cf830b9b1f22d9" +source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94e69d992d86b1c72a0fddcd585d9be39afd8815" dependencies = [ "itertools", "paste", @@ -1203,7 +1203,7 @@ dependencies = [ [[package]] name = "phantom-zone-math" version = "0.1.0" -source = "git+https://github.com/gausslabs/phantom-zone?rev=5232e55bfa811487438c34f745cf830b9b1f22d9#5232e55bfa811487438c34f745cf830b9b1f22d9" +source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94e69d992d86b1c72a0fddcd585d9be39afd8815" dependencies = [ "bincode", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 2c77ea9..7ad92a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] rocket = { version = "0.5.1", features = ["json", "msgpack"] } -phantom-zone-evaluator = { git = "https://github.com/gausslabs/phantom-zone", rev = "5232e55bfa811487438c34f745cf830b9b1f22d9", features = [ +phantom-zone-evaluator = { git = "https://github.com/han0110/phantom-zone", branch = "rewrite/ring-packing", features = [ "serde", "dev", ] } diff --git a/src/client.rs b/src/client.rs index 1ab8fc4..15aa7a9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,5 @@ use crate::{ - phantom::Client as PhantomClient, + phantom::{PhantomClient, PhantomOps, PhantomPrimeOps}, server::*, types::{ BskShareSubmission, Cipher, CreateTaskSubmission, Decryptable, DecryptionShareSubmission, @@ -10,7 +10,8 @@ use crate::{ use anyhow::{bail, Error}; use indicatif::{ProgressBar, ProgressStyle}; -use phantom_zone_evaluator::boolean::fhew::prelude::{NonNativePowerOfTwo, PrimeRing}; +use itertools::Itertools; +use rand::{rngs::StdRng, Rng, SeedableRng}; use reqwest::{self, header::CONTENT_TYPE, Client}; use rocket::{serde::msgpack, uri}; use serde::{Deserialize, Serialize}; @@ -55,9 +56,17 @@ impl Wallet { bail!("Timed out waiting for server state: {:?}", desired_state) } - async fn acquire_pk(&self, user_id: UserId, pk_share: Vec) -> Result, Error> { + async fn acquire_pk( + &self, + user_id: UserId, + pk_share: Vec, + rpk_share: Vec, + ) -> Result, Error> { // Submit the public key share - let _: UserId = self.rc.submit_pk_shares(user_id, pk_share).await?; + let _: UserId = self + .rc + .submit_pk_shares(user_id, pk_share, rpk_share) + .await?; for _ in 0..10 { if let Ok(server_pk) = self.rc.get_aggregated_pk().await { return Ok(server_pk); @@ -73,14 +82,24 @@ impl Wallet { pub async fn run_setup(&self) -> Result { let (param, crs) = self.rc.get_param_crs().await?; let user_id = self.rc.register().await?; - let mut pc = PhantomClient::::new(param, crs, user_id); + let seed = StdRng::from_entropy().gen(); + let mut pc = + PhantomClient::::new(param, crs, user_id, seed, None).unwrap(); self.wait_for_server_state(ServerState::ReadyForPkShares) .await?; - let server_pk = self.acquire_pk(user_id, pc.pk_share_gen()).await?; - pc.receive_pk(&server_pk); + let server_pk = self + .acquire_pk( + user_id, + pc.serialize_pk_share(&pc.pk_share_gen())?, + pc.serialize_rp_key_share(&pc.rp_key_share_gen())?, + ) + .await?; + pc.with_pk(pc.deserialize_pk(&server_pk)?); self.wait_for_server_state(ServerState::ReadyForBskShares) .await?; - self.rc.submit_bsks(user_id, pc.bs_key_share_gen()).await?; + self.rc + .submit_bsks(user_id, pc.serialize_bs_key_share(&pc.bs_key_share_gen())?) + .await?; Ok(SetupWallet { rc: self.rc.clone(), @@ -94,7 +113,7 @@ impl Wallet { pub struct SetupWallet { rc: ProductionClient, pub(crate) user_id: UserId, - pc: PhantomClient, + pc: PhantomClient, tasks: HashMap, } @@ -106,7 +125,9 @@ impl SetupWallet { input: Vec, ) -> Result { // Encrypt input - let cipher = self.pc.pk_encrypt_bit(input); + let cipher = self + .pc + .serialize_batched_ct(&self.pc.batched_pk_encrypt(input))?; // Create task on server let task_id = self @@ -146,7 +167,9 @@ impl SetupWallet { TaskStatus::WaitingForInput => { if !task.inputs.contains_key(&self.user_id) { let input = self.get_input_for_task(task.id)?; // Implement this method - let cipher = self.pc.pk_encrypt_bit(input); + let cipher = self + .pc + .serialize_batched_ct(&self.pc.batched_pk_encrypt(input))?; self.rc .submit_task_input(task.id, self.user_id, cipher) .await?; @@ -169,7 +192,11 @@ impl SetupWallet { decryptable: Decryptable, ) -> Result<(), Error> { if decryptable.should_contribute(self.user_id) { - let share = self.pc.decrypt_share_bits(&decryptable.word); + let share = self.pc.serialize_rp_dec_share( + &self + .pc + .rp_decrypt_share(&self.pc.deserialize_rp_ct(&decryptable.word)?), + )?; self.rc .submit_decryption_share(task_id, decryptable.id, self.user_id, share) .await?; @@ -189,18 +216,30 @@ impl SetupWallet { // You might want to decrypt the result here for decryptable in task.decryptables.iter() { let plain = match decryptable.vis { - Visibility::Public => self - .pc - .decrypt_bits(&decryptable.word, &decryptable.get_shares()), + Visibility::Public => self.pc.aggregate_rp_decryption_shares( + &self.pc.deserialize_rp_ct(&decryptable.word).unwrap(), + &decryptable + .get_shares() + .iter() + .map(|share| self.pc.deserialize_rp_dec_share(share)) + .try_collect::<_, Vec<_>, _>() + .unwrap(), + ), Visibility::Designated(user_id) => { debug_assert_eq!(user_id, self.user_id); - let my_share = self.pc.decrypt_share_bits(&decryptable.word); + let rp_ct = self.pc.deserialize_rp_ct(&decryptable.word).unwrap(); + let my_share = self.pc.rp_decrypt_share(&rp_ct); let all_shares = { - let mut other_shares = decryptable.get_shares(); + let mut other_shares = decryptable + .get_shares() + .iter() + .map(|share| self.pc.deserialize_rp_dec_share(share)) + .try_collect::<_, Vec<_>, _>() + .unwrap(); other_shares.push(my_share); other_shares }; - self.pc.decrypt_bits(&decryptable.word, &all_shares) + self.pc.aggregate_rp_decryption_shares(&rp_ct, &all_shares) } }; println!("User {} Decrypted plain {:?}", self.user_id, plain); @@ -302,10 +341,15 @@ impl ProductionClient { &self, user_id: UserId, pk_share: Vec, + rpk_share: Vec, ) -> Result { self.post_msgpack( - uri!(submit_pk_shares), - &PkShareSubmission { user_id, pk_share }, + uri!(submit_pk_and_rpk_shares), + &PkShareSubmission { + user_id, + pk_share, + rpk_share, + }, ) .await } diff --git a/src/phantom.rs b/src/phantom.rs index 14d14d5..12a3189 100644 --- a/src/phantom.rs +++ b/src/phantom.rs @@ -1,372 +1,716 @@ -use crate::types::ParamCRS; +#![allow(clippy::type_complexity)] + +use core::{fmt::Debug, ops::Deref}; use itertools::Itertools; -use phantom_zone_evaluator::boolean::{fhew::prelude::*, FheBool}; -use rand::{rngs::StdRng, RngCore, SeedableRng}; +use phantom_zone_evaluator::boolean::fhew::prelude::*; +use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; use std::ops::{BitAnd, BitOr, BitXor}; -pub(crate) struct Client { +pub trait PhantomOps: Debug { + type Ring: RingOps; + type EvaluationRing: RingOps>; + type KeySwitchMod: ModulusOps; + type PackingRing: RingOps; + + fn new(param: FhewBoolParam) -> Self; + + fn param(&self) -> &FhewBoolParam; + + fn ring_packing_param(&self) -> RingPackingParam { + let param = self.param(); + RingPackingParam { + modulus: self.ring_rp().modulus(), + ring_size: param.ring_size, + sk_distribution: param.sk_distribution, + noise_distribution: param.noise_distribution, + auto_decomposition_param: param.auto_decomposition_param, + } + } + + fn ring(&self) -> &Self::Ring; + + fn mod_ks(&self) -> &Self::KeySwitchMod; + + fn ring_rp(&self) -> &Self::PackingRing; + + /// Batched encrypt bits by public key. + fn batched_pk_encrypt( + &self, + pk: &RlwePublicKeyOwned>, + ms: impl IntoIterator, + ) -> FhewBoolBatchedCiphertextOwned> { + FhewBoolBatchedCiphertextOwned::pk_encrypt( + self.param(), + self.ring(), + pk, + ms, + &mut StdLweRng::from_entropy(), + ) + } + + /// Pack LWE ciphertexts into RLWE ciphertexts (ring packing ciphertext). + fn pack<'a>( + &self, + rp_key: &RingPackingKeyOwned<::EvalPrep>, + cts: impl IntoIterator>>, + ) -> FhewBoolPackedCiphertextOwned>; + + /// Aggregate decryption shares of ring packing ciphertext + fn aggregate_rp_decryption_shares<'a>( + &self, + ct: &FhewBoolPackedCiphertextOwned>, + dec_shares: impl IntoIterator>>, + ) -> Vec { + ct.aggregate_decryption_shares(self.ring_rp(), dec_shares) + } + + /// Serialize public key share + fn serialize_pk_share( + &self, + pk_share: &SeededRlwePublicKeyOwned>, + ) -> bincode::Result> { + bincode::serialize(&pk_share.compact(self.ring())) + } + + /// Deserialize public key share + fn deserialize_pk_share( + &self, + bytes: &[u8], + ) -> bincode::Result>> { + let pk_share_compact: SeededRlwePublicKey = bincode::deserialize(bytes)?; + Ok(pk_share_compact.uncompact(self.ring())) + } + + /// Serialize ring packing key share + fn serialize_rp_key_share( + &self, + rp_key_share: &RingPackingKeyShareOwned>, + ) -> bincode::Result> { + bincode::serialize(&rp_key_share.compact(self.ring())) + } + + /// Deserialize ring packing key share + fn deserialize_rp_key_share( + &self, + bytes: &[u8], + ) -> bincode::Result>> { + let rp_key_share_compact: RingPackingKeyShareCompact = bincode::deserialize(bytes)?; + Ok(rp_key_share_compact.uncompact(self.ring())) + } + + /// Serialize bootstrapping key share + fn serialize_bs_key_share( + &self, + bs_key_share: &FhewBoolMpiKeyShareOwned, Elem>, + ) -> bincode::Result> { + bincode::serialize(&bs_key_share.compact(self.ring(), self.mod_ks())) + } + + /// Deserialize bootstrapping key share + fn deserialize_bs_key_share( + &self, + bytes: &[u8], + ) -> bincode::Result, Elem>> { + let bs_key_share_compact: FhewBoolMpiKeyShareCompact = bincode::deserialize(bytes)?; + Ok(bs_key_share_compact.uncompact(self.ring(), self.mod_ks())) + } + + /// Serialize public key + fn serialize_pk(&self, pk: &RlwePublicKeyOwned>) -> bincode::Result> { + bincode::serialize(&pk.compact(self.ring())) + } + + /// Deserialize public key + fn deserialize_pk( + &self, + bytes: &[u8], + ) -> bincode::Result>> { + let pk_compact: RlwePublicKey = bincode::deserialize(bytes)?; + Ok(pk_compact.uncompact(self.ring())) + } + + /// Serialize ring packing key + fn serialize_rp_key( + &self, + rp_key: &RingPackingKeyOwned>, + ) -> bincode::Result> { + bincode::serialize(&rp_key.compact(self.ring_rp())) + } + + /// Deserialize ring packing key + fn deserialize_rp_key( + &self, + bytes: &[u8], + ) -> bincode::Result>> { + let rp_key_compact: RingPackingKeyCompact = bincode::deserialize(bytes)?; + Ok(rp_key_compact.uncompact(self.ring_rp())) + } + + /// Serialize ring packing key + fn serialize_bs_key( + &self, + bs_key: &FhewBoolKeyOwned, Elem>, + ) -> bincode::Result> { + bincode::serialize(&bs_key.compact(self.ring(), self.mod_ks())) + } + + /// Deserialize ring packing key + fn deserialize_bs_key( + &self, + bytes: &[u8], + ) -> bincode::Result, Elem>> + { + let bs_key_compact: FhewBoolKeyCompact = bincode::deserialize(bytes)?; + Ok(bs_key_compact.uncompact(self.ring(), self.mod_ks())) + } + + /// Serialize batched ciphertext + fn serialize_batched_ct( + &self, + ct: &FhewBoolBatchedCiphertextOwned>, + ) -> bincode::Result> { + bincode::serialize(&ct.compact(self.ring())) + } + + /// Deserialize batched ciphertext + fn deserialize_batched_ct( + &self, + bytes: &[u8], + ) -> bincode::Result>> { + let ct_compact: FhewBoolBatchedCiphertext = bincode::deserialize(bytes)?; + Ok(ct_compact.uncompact(self.ring())) + } + + /// Serialize ring packing ciphertext + fn serialize_rp_ct( + &self, + ct: &FhewBoolPackedCiphertextOwned>, + ) -> bincode::Result> { + bincode::serialize(&ct.compact(self.ring_rp())) + } + + /// Deserialize ring packing ciphertext + fn deserialize_rp_ct( + &self, + bytes: &[u8], + ) -> bincode::Result>> { + let ct_compact: FhewBoolPackedCiphertext = bincode::deserialize(bytes)?; + Ok(ct_compact.uncompact(self.ring_rp())) + } + + /// Serialize decryption share of ring packing ciphertext + fn serialize_rp_dec_share( + &self, + dec_share: &RlweDecryptionShareListOwned>, + ) -> bincode::Result> { + bincode::serialize(&dec_share.compact(self.ring_rp())) + } + + /// Deserialize decryption share of ring packing ciphertext + fn deserialize_rp_dec_share( + &self, + bytes: &[u8], + ) -> bincode::Result>> { + let dec_share_compact: RlweDecryptionShareList = bincode::deserialize(bytes)?; + Ok(dec_share_compact.uncompact(self.ring_rp())) + } +} + +#[derive(Clone, Debug)] +pub struct PhantomNativeOps { + param: FhewBoolParam, + ring: NativeRing, + mod_ks: NonNativePowerOfTwo, + ring_rp: PrimeRing, +} + +impl PhantomOps for PhantomNativeOps { + type Ring = NativeRing; + type EvaluationRing = NoisyNativeRing; + type KeySwitchMod = NonNativePowerOfTwo; + type PackingRing = PrimeRing; + + fn new(param: FhewBoolParam) -> Self { + Self { + param, + ring: RingOps::new(param.modulus, param.ring_size), + mod_ks: ModulusOps::new(param.lwe_modulus), + ring_rp: RingOps::new(Modulus::Prime(2305843009213554689), param.ring_size), + } + } + + fn param(&self) -> &FhewBoolParam { + &self.param + } + + fn ring(&self) -> &Self::Ring { + &self.ring + } + + fn mod_ks(&self) -> &Self::KeySwitchMod { + &self.mod_ks + } + + fn ring_rp(&self) -> &Self::PackingRing { + &self.ring_rp + } + + fn pack<'a>( + &self, + rp_key: &RingPackingKeyOwned<::EvalPrep>, + cts: impl IntoIterator>>, + ) -> FhewBoolPackedCiphertextOwned> { + FhewBoolPackedCiphertext::pack_ms(self.ring(), self.ring_rp(), rp_key, cts) + } +} + +#[derive(Clone, Debug)] +pub struct PhantomPrimeOps { + param: FhewBoolParam, + ring: PrimeRing, + mod_ks: NonNativePowerOfTwo, +} + +impl PhantomOps for PhantomPrimeOps { + type Ring = PrimeRing; + type EvaluationRing = NoisyPrimeRing; + type KeySwitchMod = NonNativePowerOfTwo; + type PackingRing = PrimeRing; + + fn new(param: FhewBoolParam) -> Self { + Self { + param, + ring: RingOps::new(param.modulus, param.ring_size), + mod_ks: ModulusOps::new(param.lwe_modulus), + } + } + + fn param(&self) -> &FhewBoolParam { + &self.param + } + + fn ring(&self) -> &Self::Ring { + &self.ring + } + + fn mod_ks(&self) -> &Self::KeySwitchMod { + &self.mod_ks + } + + fn ring_rp(&self) -> &Self::PackingRing { + &self.ring + } + + fn pack<'a>( + &self, + rp_key: &RingPackingKeyOwned<::EvalPrep>, + cts: impl IntoIterator>>, + ) -> FhewBoolPackedCiphertextOwned> { + FhewBoolPackedCiphertext::pack(self.ring_rp(), rp_key, cts) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct PhantomCrs(::Seed); + +impl PhantomCrs { + pub fn new(seed: ::Seed) -> Self { + Self(seed) + } + + pub fn from_entropy() -> Self { + Self::new(thread_rng().gen()) + } + + fn fhew(&self) -> FhewBoolMpiCrs { + FhewBoolMpiCrs::new(StdRng::from_hierarchical_seed(self.0, &[0]).gen()) + } + + fn ring_packing(&self) -> RingPackingCrs { + RingPackingCrs::new(StdRng::from_hierarchical_seed(self.0, &[1]).gen()) + } +} + +#[derive(Clone, Debug)] +pub struct PhantomClient { param: FhewBoolMpiParam, - crs: FhewBoolMpiCrs, + crs: PhantomCrs, + ops: O, share_idx: usize, - sk_seed: ::Seed, - pk: RlwePublicKeyOwned, - ring: R, - mod_ks: M, + seed: ::Seed, + pk: Option>>, +} + +impl Deref for PhantomClient { + type Target = O; + + fn deref(&self) -> &Self::Target { + &self.ops + } } -impl Client { - pub(crate) fn new( +impl PhantomClient { + pub fn new( param: FhewBoolMpiParam, - crs: FhewBoolMpiCrs, + crs: PhantomCrs, share_idx: usize, - ) -> Self { - let mut sk_seed = ::Seed::default(); - StdRng::from_entropy().fill_bytes(sk_seed.as_mut()); - Self { + seed: ::Seed, + pk_bytes: Option<&[u8]>, + ) -> bincode::Result { + let mut client = Self { param, crs, + ops: O::new(*param), share_idx, - sk_seed, - pk: RlwePublicKey::allocate(param.ring_size), - ring: RingOps::new(param.modulus, param.ring_size), - mod_ks: M::new(param.lwe_modulus), + seed, + pk: None, + }; + if let Some(pk_bytes) = pk_bytes { + client.with_pk(client.deserialize_pk(pk_bytes)?); } + Ok(client) } - pub(crate) fn sk(&self) -> RlweSecretKeyOwned { + /// Returns seed. + pub fn seed(&self) -> ::Seed { + self.seed + } + + fn sk(&self) -> RlweSecretKeyOwned { RlweSecretKey::sample( self.param.ring_size, self.param.sk_distribution, - &mut StdRng::from_hierarchical_seed(self.sk_seed, &[0]), + &mut StdRng::from_hierarchical_seed(self.seed, &[0, 0]), ) } - /// Key-Switched secret key - pub(crate) fn sk_ks(&self) -> LweSecretKeyOwned { + fn sk_ks(&self) -> LweSecretKeyOwned { LweSecretKey::sample( self.param.lwe_dimension, self.param.lwe_sk_distribution, - &mut StdRng::from_hierarchical_seed(self.sk_seed, &[1]), + &mut StdRng::from_hierarchical_seed(self.seed, &[0, 1]), ) } - pub(crate) fn pk_share_gen(&self) -> Vec { + /// Returns aggregated public key. + /// + /// # Panics + /// + /// Panics if `pk` is not given in [`Self::new`], or [`Self::with_pk`] is + /// not invoked yet. + pub fn pk(&self) -> &RlwePublicKeyOwned> { + self.pk.as_ref().unwrap() + } + + /// Generate public key share. + pub fn pk_share_gen(&self) -> SeededRlwePublicKeyOwned> { let mut pk = SeededRlwePublicKey::allocate(self.param.ring_size); pk_share_gen( - &self.ring, + self.ring(), &mut pk, &self.param, - &self.crs, + &self.crs.fhew(), &self.sk(), - &mut StdRng::from_entropy(), + &mut StdRng::from_hierarchical_seed(self.seed, &[1, 0]), ); - serialize_pk_share(&self.ring, &pk) + pk } - pub(crate) fn receive_pk(&mut self, pk: &[u8]) { - self.pk = deserialize_pk(&self.ring, &pk); + /// Generate ring packing share. + pub fn rp_key_share_gen(&self) -> RingPackingKeyShareOwned> { + let mut rp_key = RingPackingKeyShareOwned::allocate(self.ring_packing_param()); + rp_key_share_gen( + self.ring_rp(), + &mut rp_key, + &self.crs.ring_packing(), + &self.sk(), + &mut StdRng::from_hierarchical_seed(self.seed, &[1, 1]), + ); + rp_key + } + + /// Set aggregated public key. + pub fn with_pk(&mut self, pk: RlwePublicKeyOwned>) { + self.pk = Some(pk.cloned()); } - pub(crate) fn bs_key_share_gen(&self) -> Vec { - let mut bs_key_share = FhewBoolMpiKeyShare::allocate(self.param, self.share_idx); + /// Generate bootstrapping key share. + /// + /// # Panics + /// + /// Panics if `pk` is not given in [`Self::new`], or [`Self::with_pk`] is + /// not invoked yet. + pub fn bs_key_share_gen( + &self, + ) -> FhewBoolMpiKeyShareOwned, Elem> { + let mut bs_key_share = FhewBoolMpiKeyShareOwned::allocate(self.param, self.share_idx); bs_key_share_gen( - &self.ring, - &self.mod_ks, + self.ring(), + self.mod_ks(), &mut bs_key_share, - &self.crs, + &self.crs.fhew(), &self.sk(), - &self.pk, + self.pk(), &self.sk_ks(), - &mut StdRng::from_entropy(), + &mut StdRng::from_hierarchical_seed(self.seed, &[1, 2]), ); - serialize_bs_key_share(&self.ring, &self.mod_ks, &bs_key_share) - } - - pub(crate) fn pk_encrypt_bit(&self, m: impl IntoIterator) -> Vec { - let cts = pk_encrypt_bit(&self.param, &self.ring, &self.pk, m); - serialize_cts_bits(&self.ring, &cts) - } - - pub(crate) fn pk_encrypt_u8(&self, m: u8) -> [FhewBoolCiphertextOwned; 8] { - pk_encrypt_u8(&self.param, &self.ring, &self.pk, m) - } - pub(crate) fn decrypt_share_u8(&self, ct: &[u8]) -> Vec { - let ct = deserialize_cts_u8(&self.ring, &ct); - let ds: [LweDecryptionShare; 8] = ct.map(|ct| { - ct.decrypt_share( - &self.ring, - self.sk().as_view(), - self.param.noise_distribution, - &mut StdLweRng::from_entropy(), - ) - }); - serialize_decryption_share_u8::(&ds) - } - - pub(crate) fn decrypt_share_bits(&self, ct: &[u8]) -> Vec { - let cbits = deserialize_cts_bits(&self.ring, &ct); - let ds = cbits - .iter() - .map(|cbit| { - cbit.decrypt_share( - &self.ring, - self.sk().as_view(), - self.param.noise_distribution, - &mut StdLweRng::from_entropy(), - ) - }) - .collect_vec(); - serialize_decryption_share_bits::(&ds) + bs_key_share } - pub(crate) fn decrypt_u8(&self, ct: &[u8], dec_shares: &[Vec]) -> u8 { - let ct = deserialize_cts_u8(&self.ring, &ct); - let dec_shares = dec_shares - .iter() - .map(|ds| deserialize_decryption_share_u8::(ds)) - .collect_vec(); - aggregate_decryption_shares_u8(&self.ring, ct, &dec_shares) + /// Batched encrypt bits by public key. + /// + /// # Panics + /// + /// Panics if `pk` is not given in [`Self::new`], or [`Self::with_pk`] is + /// not invoked yet. + pub fn batched_pk_encrypt( + &self, + ms: impl IntoIterator, + ) -> FhewBoolBatchedCiphertextOwned> { + self.ops.batched_pk_encrypt(self.pk(), ms) } - pub(crate) fn decrypt_bits(&self, ct: &[u8], dec_shares: &[Vec]) -> Vec { - let cbits = deserialize_cts_bits(&self.ring, &ct); - // for each client - let dec_shares = dec_shares - .iter() - .map(|ds| deserialize_decryption_share_bits::(ds)) - .collect_vec(); - aggregate_decryption_shares_bits(&self.ring, &cbits, &dec_shares) + /// Generate decryption share of ring packing ciphertext. + pub fn rp_decrypt_share( + &self, + ct: &FhewBoolPackedCiphertextOwned>, + ) -> RlweDecryptionShareListOwned> { + ct.decrypt_share( + &self.param, + self.ring_rp(), + self.sk().as_view(), + &mut StdLweRng::from_entropy(), + ) + } + + /// Serialize aggregated public key. + pub fn serialize_pk(&self) -> bincode::Result> { + self.ops.serialize_pk(self.pk()) } } -#[derive(Serialize, Deserialize, Clone)] -#[serde(bound(serialize = "", deserialize = ""))] -pub(crate) struct Server { +#[derive(Clone, Debug)] +pub struct PhantomServer { param: FhewBoolMpiParam, - crs: FhewBoolMpiCrs, - pk: RlwePublicKeyOwned, - #[serde(rename = "bs_key")] - evaluator: FhewBoolEvaluator, + crs: PhantomCrs, + ops: O, + pk: Option>>, + rp_key: Option>>, + rp_key_prep: Option::EvalPrep>>, + bs_key: Option, Elem>>, + evaluator: Option>, } -impl Server { - pub(crate) fn new(param: FhewBoolMpiParam) -> Self { - Self { +impl Deref for PhantomServer { + type Target = O; + + fn deref(&self) -> &Self::Target { + &self.ops + } +} + +impl PhantomServer { + /// Returns server. + pub fn new( + param: FhewBoolMpiParam, + crs: PhantomCrs, + pk_bytes: Option<&[u8]>, + rp_key_bytes: Option<&[u8]>, + bs_key_bytes: Option<&[u8]>, + ) -> bincode::Result { + let mut server = Self { param, - crs: FhewBoolMpiCrs::sample(StdRng::from_entropy()), - pk: RlwePublicKey::allocate(param.ring_size), - evaluator: FhewBoolEvaluator::new(FhewBoolKey::allocate(*param)), + crs, + ops: O::new(*param), + pk: None, + rp_key: None, + rp_key_prep: None, + bs_key: None, + evaluator: None, + }; + if let Some(pk_bytes) = pk_bytes { + server.with_pk(server.deserialize_pk(pk_bytes)?); + } + if let Some(rp_key_bytes) = rp_key_bytes { + server.with_rp_key(server.deserialize_rp_key(rp_key_bytes)?); } + if let Some(bs_key_bytes) = bs_key_bytes { + server.with_bs_key(server.deserialize_bs_key(bs_key_bytes)?); + } + Ok(server) } - pub(crate) fn get_param_crs(&self) -> ParamCRS { - (self.param, self.crs) + /// Returns parameter. + pub fn param(&self) -> &FhewBoolMpiParam { + &self.param } - fn ring(&self) -> &R { - self.evaluator.ring() + /// Returns common reference string. + pub fn crs(&self) -> &PhantomCrs { + &self.crs } - fn mod_ks(&self) -> &M { - self.evaluator.mod_ks() + /// Returns aggregated public key. + /// + /// # Panics + /// + /// Panics if `pk` is not given in [`Self::new`], or + /// [`Self::aggregate_pk_shares`] and [`Self::with_pk`] are not invoked yet. + pub fn pk(&self) -> &RlwePublicKeyOwned> { + self.pk.as_ref().unwrap() } - /// Mutate server's public key - pub(crate) fn aggregate_pk_shares(&mut self, pk_shares: &[Vec]) { - let deserialized_pk_shares = pk_shares - .iter() - .map(|bytes| deserialize_pk_share(self.ring(), bytes)) - .collect_vec(); - aggregate_pk_shares( - self.evaluator.ring(), - &mut self.pk, - &self.crs, - &deserialized_pk_shares, - ); + /// Returns aggregated ring packing key. + /// + /// # Panics + /// + /// Panics if `rp_key` is not given in [`Self::new`], or + /// [`Self::aggregate_rp_key_shares`] and [`Self::with_rp_key`] is not + /// invoked yet. + pub fn rp_key(&self) -> &RingPackingKeyOwned> { + self.rp_key.as_ref().unwrap() + } + + fn rp_key_prep(&self) -> &RingPackingKeyOwned<::EvalPrep> { + self.rp_key_prep.as_ref().unwrap() + } + + /// Returns evaluator. + /// + /// # Panics + /// + /// Panics if `bs_key` is not given in [`Self::new`], or + /// [`Self::aggregate_bs_key_shares`] and [`Self::with_bs_key`] is not + /// invoked yet. + pub fn evaluator(&self) -> &FhewBoolEvaluator { + self.evaluator.as_ref().unwrap() + } + + /// Returns aggregated bootstrapping key. + /// + /// # Panics + /// + /// Panics if `bs_key` is not given in [`Self::new`], or + /// [`Self::aggregate_bs_key_shares`] and [`Self::with_bs_key`] is not + /// invoked yet. + pub fn bs_key(&self) -> &FhewBoolKeyOwned, Elem> { + self.bs_key.as_ref().unwrap() } - /// Must be called after aggregate_pk_shares - pub(crate) fn serialize_pk(&self) -> Vec { - serialize_pk(self.ring(), &self.pk) + /// Set aggregated public key. + pub fn with_pk(&mut self, pk: RlwePublicKeyOwned>) { + self.pk = Some(pk) } - pub(crate) fn aggregate_bs_key_shares>( + /// Set aggregated ring packing key. + pub fn with_rp_key(&mut self, rp_key: RingPackingKeyOwned>) { + let mut rp_key_prep = RingPackingKeyOwned::allocate_eval( + self.ring_packing_param(), + self.ring_rp().eval_size(), + ); + prepare_rp_key(self.ring_rp(), &mut rp_key_prep, &rp_key); + self.rp_key = Some(rp_key); + self.rp_key_prep = Some(rp_key_prep); + } + + /// Set aggregated bootstrapping key. + pub fn with_bs_key( &mut self, - bs_key_shares: &[Vec], + bs_key: FhewBoolKeyOwned, Elem>, ) { - let bs_key_shares = bs_key_shares - .into_iter() - .map(|bytes| deserialize_bs_key_share(self.ring(), self.mod_ks(), &bytes)) - .collect_vec(); - - let bs_key = { - let ring = ::new(self.param.modulus, self.param.ring_size); - let mut bs_key = FhewBoolKey::allocate(*self.param); - aggregate_bs_key_shares(&ring, self.mod_ks(), &mut bs_key, &self.crs, &bs_key_shares); - bs_key - }; let bs_key_prep = { - let mut bs_key_prep = FhewBoolKey::allocate_eval(*self.param, self.ring().eval_size()); - prepare_bs_key(self.ring(), &mut bs_key_prep, &bs_key); + let ring: O::EvaluationRing = RingOps::new(self.param.modulus, self.param.ring_size); + let mut bs_key_prep = FhewBoolKeyOwned::allocate_eval(*self.param, ring.eval_size()); + prepare_bs_key(&ring, &mut bs_key_prep, &bs_key); bs_key_prep }; - self.evaluator = FhewBoolEvaluator::new(bs_key_prep); + self.bs_key = Some(bs_key); + self.evaluator = Some(FhewBoolEvaluator::new(bs_key_prep)); } - pub(crate) fn deserialize_cts_bits(&self, cts: &[u8]) -> Vec>> { - let cts = deserialize_cts_bits(self.ring(), cts); - cts.iter() - .map(|ct| FheBool::new(&self.evaluator, ct.cloned())) - .collect_vec() + /// Aggregate public key shares and set it as aggregated public key. + pub fn aggregate_pk_shares(&mut self, pk_shares: &[SeededRlwePublicKeyOwned>]) { + let crs = self.crs.fhew(); + let mut pk = RlwePublicKey::allocate(self.param.ring_size); + aggregate_pk_shares(self.ring(), &mut pk, &crs, pk_shares); + self.with_pk(pk); } - pub(crate) fn serialize_cts_bits(&self, cts: &[FheBool>]) -> Vec { - let cts = cts.iter().map(|ct| ct.ct().cloned()).collect_vec(); - serialize_cts_bits(self.ring(), &cts) + /// Aggregate ring packing key shares and set it as aggregated ring packing key. + pub fn aggregate_rp_key_shares( + &mut self, + rp_key_shares: &[RingPackingKeyShareOwned>], + ) { + let crs = self.crs.ring_packing(); + let mut rp_key = RingPackingKeyOwned::allocate(self.ring_packing_param()); + aggregate_rp_key_shares(self.ring_rp(), &mut rp_key, &crs, rp_key_shares); + self.with_rp_key(rp_key); } - pub(crate) fn pk_encrypt_bits( - &self, - m: impl IntoIterator, - ) -> Vec> { - pk_encrypt_bit(&self.param, self.ring(), &self.pk, m) + /// Aggregate bootstrapping key shares and set it as aggregated bootstrapping key. + pub fn aggregate_bs_key_shares( + &mut self, + bs_key_shares: &[FhewBoolMpiKeyShareOwned, Elem>], + ) { + let crs = self.crs.fhew(); + let bs_key = { + let mut bs_key = FhewBoolKeyOwned::allocate(*self.param); + aggregate_bs_key_shares(self.ring(), self.mod_ks(), &mut bs_key, &crs, bs_key_shares); + bs_key + }; + self.with_bs_key(bs_key); } - pub(crate) fn pk_encrypt_u8(&self, m: u8) -> [FhewBoolCiphertextOwned; 8] { - pk_encrypt_u8(&self.param, self.ring(), &self.pk, m) + /// Wrap batched ciphertext into [`Vec`] of [`FheBool`] for FHE evaluation. + pub fn wrap_batched_ct( + &self, + ct: &FhewBoolBatchedCiphertextOwned>, + ) -> Vec>> { + ct.extract_all(self.ring()) + .into_iter() + .map(|ct| FheBool::new(self.evaluator(), ct)) + .collect_vec() } -} - -fn pk_encrypt_u8( - param: &FhewBoolParam, - ring: &R, - pk: &RlwePublicKeyOwned, - m: u8, -) -> [FhewBoolCiphertextOwned; 8] { - FhewBoolCiphertext::batched_pk_encrypt( - param, - ring, - pk, - (0..8).map(|idx| (m >> idx) & 1 == 1), - &mut StdLweRng::from_entropy(), - ) - .try_into() - .unwrap() -} - -fn pk_encrypt_bit( - param: &FhewBoolParam, - ring: &R, - pk: &RlwePublicKeyOwned, - m: impl IntoIterator, -) -> Vec> { - FhewBoolCiphertext::batched_pk_encrypt(param, ring, pk, m, &mut StdLweRng::from_entropy()) -} - -fn aggregate_decryption_shares_u8( - ring: &R, - ct: [FhewBoolCiphertextOwned; 8], - dec_shares: &[[LweDecryptionShare; 8]], -) -> u8 { - (0..8) - .map(|idx| { - let dec_shares = dec_shares.iter().map(|dec_shares| &dec_shares[idx]); - ct[idx].aggregate_decryption_shares(ring, dec_shares) - }) - .rev() - .fold(0, |m, b| (m << 1) | b as u8) -} -fn aggregate_decryption_shares_bits( - ring: &R, - cbits: &[FhewBoolCiphertextOwned], - dec_shares: &[Vec>], -) -> Vec { - cbits - .iter() - .enumerate() - .map(|(bit_idx, cbit)| { - let shares = dec_shares - .iter() - .map(|client_share| client_share[bit_idx]) - .collect_vec(); - cbit.aggregate_decryption_shares(ring, &shares) - }) - .collect_vec() -} - -fn serialize_pk_share( - ring: &R, - pk_share: &SeededRlwePublicKeyOwned, -) -> Vec { - bincode::serialize(&pk_share.compact(ring)).unwrap() -} -fn deserialize_pk_share(ring: &R, bytes: &[u8]) -> SeededRlwePublicKeyOwned { - let pk_share_compact: SeededRlwePublicKey = bincode::deserialize(bytes).unwrap(); - pk_share_compact.uncompact(ring) -} -fn serialize_pk(ring: &R, pk: &RlwePublicKeyOwned) -> Vec { - bincode::serialize(&pk.compact(ring)).unwrap() -} - -fn deserialize_pk(ring: &R, bytes: &[u8]) -> RlwePublicKeyOwned { - let pk_compact: RlwePublicKey = bincode::deserialize(bytes).unwrap(); - pk_compact.uncompact(ring) -} - -fn serialize_bs_key_share( - ring: &R, - mod_ks: &M, - bs_key_share: &FhewBoolMpiKeyShareOwned, -) -> Vec { - bincode::serialize(&bs_key_share.compact(ring, mod_ks)).unwrap() -} - -fn serialize_decryption_share_u8(share: &[LweDecryptionShare; 8]) -> Vec { - bincode::serialize(&share).unwrap() -} - -fn serialize_decryption_share_bits(share: &[LweDecryptionShare]) -> Vec { - bincode::serialize(&share).unwrap() -} - -fn deserialize_decryption_share_u8(share: &[u8]) -> [LweDecryptionShare; 8] { - bincode::deserialize(&share).unwrap() -} - -fn deserialize_decryption_share_bits(share: &[u8]) -> Vec> { - bincode::deserialize(&share).unwrap() -} - -fn deserialize_bs_key_share( - ring: &R, - mod_ks: &M, - bytes: &[u8], -) -> FhewBoolMpiKeyShareOwned { - let bs_key_share_compact: FhewBoolMpiKeyShareCompact = bincode::deserialize(bytes).unwrap(); - bs_key_share_compact.uncompact(ring, mod_ks) -} + /// Serialize aggregated public key. + pub fn serialize_pk(&self) -> bincode::Result> { + self.ops.serialize_pk(self.pk()) + } -fn serialize_cts_u8(ring: &R, cts: [FhewBoolCiphertextOwned; 8]) -> Vec { - bincode::serialize(&cts.map(|ct| ct.compact(ring))).unwrap() -} + /// Serialize aggregated ring packing key. + pub fn serialize_rp_key(&self) -> bincode::Result> { + self.ops.serialize_rp_key(self.rp_key()) + } -fn serialize_cts_bits(ring: &R, cts: &[FhewBoolCiphertextOwned]) -> Vec { - bincode::serialize(&cts.iter().map(|ct| ct.compact(ring)).collect_vec()).unwrap() + /// Serialize aggregated bootstrapping key. + pub fn serialize_bs_key(&self) -> bincode::Result> { + self.ops.serialize_bs_key(self.bs_key()) + } } -fn deserialize_cts_u8(ring: &R, bytes: &[u8]) -> [FhewBoolCiphertextOwned; 8] { - let cts: [FhewBoolCiphertext; 8] = bincode::deserialize(bytes).unwrap(); - cts.map(|ct| ct.uncompact(ring)) +impl PhantomServer { + /// Pack LWE ciphertexts into RLWE ciphertexts (ring packing ciphertext). + pub fn pack<'a>( + &self, + cts: impl IntoIterator< + Item = &'a FhewBoolCiphertextOwned::Ring>>, + >, + ) -> FhewBoolPackedCiphertextOwned::PackingRing>> { + self.ops.pack(self.rp_key_prep(), cts) + } } -fn deserialize_cts_bits( - ring: &R, - bytes: &[u8], -) -> Vec> { - let cts: Vec> = bincode::deserialize(bytes).unwrap(); - cts.iter().map(|ct| ct.uncompact(ring)).collect_vec() +impl PhantomServer { + /// Pack LWE ciphertexts into RLWE ciphertexts (ring packing ciphertext). + pub fn pack<'a>( + &self, + cts: impl IntoIterator< + Item = &'a FhewBoolCiphertextOwned::Ring>>, + >, + ) -> FhewBoolPackedCiphertextOwned::PackingRing>> { + self.ops.pack(self.rp_key_prep(), cts) + } } pub(crate) trait BitOps: @@ -390,129 +734,183 @@ where #[cfg(test)] mod tests { use super::*; - use core::{array::from_fn, num::Wrapping}; - use num_traits::NumOps; - use phantom_zone_evaluator::boolean::fhew::param::I_4P; + use core::array::from_fn; + use itertools::{izip, Itertools}; + use phantom_zone_evaluator::boolean::{dev::MockBoolEvaluator, fhew::param::I_4P}; use rand::Rng; - - fn function(a: &T, b: &T, c: &T, d: &T, e: &T) -> T - where - T: for<'t> NumOps<&'t T, T>, - for<'t> &'t T: NumOps<&'t T, T>, - { - (((a + b) - c) * d) % e + use std::iter::repeat_with; + + fn function<'a, E: BoolEvaluator>( + a: &[FheBool<'a, E>], + b: &[FheBool<'a, E>], + c: &[FheBool<'a, E>], + d: &[FheBool<'a, E>], + ) -> Vec> { + a.iter() + .zip(b) + .zip(c) + .zip(d) + .map(|(((a, b), c), d)| a ^ b ^ c ^ d) + .collect() } + #[test] - fn test_phantom() { - let mut server = Server::::new(I_4P); - let mut clients = (0..server.param.total_shares) + fn test_phantom_bits() { + let crs = PhantomCrs::from_entropy(); + let param = I_4P; + let mut server = + PhantomServer::::new(param, crs, None, None, None).unwrap(); + let mut clients = (0..param.total_shares) .map(|share_idx| { - Client::::new(server.param, server.crs, share_idx) + let seed = StdRng::from_entropy().gen(); + PhantomClient::::new(param, crs, share_idx, seed, None).unwrap() }) .collect_vec(); - // Round 1 + // Key generation round 1. - // Clients generate public key shares - let pk_shares = clients - .iter() - .map(|client| client.pk_share_gen()) - .collect_vec(); + let (pk_shares, rp_key_shares) = { + let pk_shares = clients + .iter() + .map(|client| client.serialize_pk_share(&client.pk_share_gen()).unwrap()) + .collect_vec(); + let rp_key_shares = clients + .iter() + .map(|client| { + client + .serialize_rp_key_share(&client.rp_key_share_gen()) + .unwrap() + }) + .collect_vec(); + (pk_shares, rp_key_shares) + }; - // Server aggregates public key shares - server.aggregate_pk_shares(&pk_shares); - let pk = server.serialize_pk(); + let pk = { + server.aggregate_pk_shares( + &pk_shares + .into_iter() + .map(|bytes| server.deserialize_pk_share(&bytes).unwrap()) + .collect_vec(), + ); + server.aggregate_rp_key_shares( + &rp_key_shares + .into_iter() + .map(|bytes| server.deserialize_rp_key_share(&bytes).unwrap()) + .collect_vec(), + ); + server.serialize_pk().unwrap() + }; - // Round 2 + // Key generation round 2. - // Clients generate bootstrapping key shares let bs_key_shares = clients .iter_mut() .map(|client| { - client.receive_pk(&pk); - client.bs_key_share_gen() + client.with_pk(client.deserialize_pk(&pk).unwrap()); + client + .serialize_bs_key_share(&client.bs_key_share_gen()) + .unwrap() }) .collect_vec(); - // Server aggregates bootstrapping key shares - server.aggregate_bs_key_shares::(&bs_key_shares); + server.aggregate_bs_key_shares( + &bs_key_shares + .into_iter() + .map(|bytes| server.deserialize_bs_key_share(&bytes).unwrap()) + .collect_vec(), + ); + + // FHE evaluation. - // Server performs FHE evaluation - let m = from_fn(|_| StdRng::from_entropy().gen()); - let g = { - let [a, b, c, d, e] = &m.map(Wrapping); - function(a, b, c, d, e).0 + let ms: [Vec; 4] = { + let mut rng = StdRng::from_entropy(); + let n = 10; + from_fn(|_| repeat_with(|| rng.gen()).take(n).collect()) }; - let ct_g = { - let [a, b, c, d, e] = - &m.map(|m| FheU8::from_cts(&server.evaluator, server.pk_encrypt_u8(m))); - serialize_cts_u8(server.ring(), function(a, b, c, d, e).into_cts()) + let out = { + let [a, b, c, d] = &ms + .clone() + .map(|m| m.into_iter().map(|m| m.into()).collect_vec()); + function::(a, b, c, d) + .into_iter() + .map(FheBool::into_ct) + .collect_vec() }; - // Clients generate decryption share of evaluation output - let ct_g_dec_shares = clients - .iter() - .map(|client| client.decrypt_share_u8(&ct_g)) - .collect_vec(); - - // Aggregate decryption shares - assert_eq!(g, clients[0].decrypt_u8(&ct_g, &ct_g_dec_shares)); - } + // Run FHE evaluation. + + let run = |server: &PhantomServer, + clients: &[PhantomClient]| { + let cts = { + from_fn(|i| { + let ct = clients[i].batched_pk_encrypt(ms[i].clone()); + clients[i].serialize_batched_ct(&ct).unwrap() + }) + }; + + let ct_out = { + let [a, b, c, d] = &cts.map(|bytes| { + let ct = server.deserialize_batched_ct(&bytes).unwrap(); + server.wrap_batched_ct(&ct) + }); + function(a, b, c, d) + .into_iter() + .map(FheBool::into_ct) + .collect_vec() + }; + + let rp_ct_out = server.serialize_rp_ct(&server.pack(&ct_out)).unwrap(); + + let rp_ct_out_dec_shares = clients + .iter() + .map(|client| { + let dec_share = + client.rp_decrypt_share(&client.deserialize_rp_ct(&rp_ct_out).unwrap()); + client.serialize_rp_dec_share(&dec_share).unwrap() + }) + .collect_vec(); - #[test] - fn test_phantom_bits() { - let mut server = Server::::new(I_4P); - let mut clients = (0..server.param.total_shares) - .map(|share_idx| { - Client::::new(server.param, server.crs, share_idx) - }) - .collect_vec(); + assert_eq!( + out.to_vec(), + clients[0].aggregate_rp_decryption_shares( + &clients[0].deserialize_rp_ct(&rp_ct_out).unwrap(), + &rp_ct_out_dec_shares + .iter() + .map(|dec_share| clients[0].deserialize_rp_dec_share(dec_share).unwrap()) + .collect_vec(), + ) + ); + }; - // Round 1 + run(&server, &clients); - // Clients generate public key shares - let pk_shares = clients - .iter() - .map(|client| client.pk_share_gen()) - .collect_vec(); + // Store aggregated keys and restart server. - // Server aggregates public key shares - server.aggregate_pk_shares(&pk_shares); - let pk = server.serialize_pk(); + let pk_bytes = server.serialize_pk().unwrap(); + let rp_key_bytes = server.serialize_rp_key().unwrap(); + let bs_key_bytes = server.serialize_bs_key().unwrap(); + let server = PhantomServer::::new( + param, + crs, + Some(&pk_bytes), + Some(&rp_key_bytes), + Some(&bs_key_bytes), + ) + .unwrap(); - // Round 2 + // Store seed and restart clients. - // Clients generate bootstrapping key shares - let bs_key_shares = clients - .iter_mut() - .map(|client| { - client.receive_pk(&pk); - client.bs_key_share_gen() + let pk_bytes = clients[0].serialize_pk().unwrap(); + let seeds = clients.iter().map(|client| client.seed()).collect_vec(); + let clients = izip!(0.., seeds) + .map(|(share_idx, seed)| { + PhantomClient::::new(param, crs, share_idx, seed, Some(&pk_bytes)) + .unwrap() }) .collect_vec(); - // Server aggregates bootstrapping key shares - server.aggregate_bs_key_shares::(&bs_key_shares); - - // Server performs FHE evaluation - let m = from_fn(|_| StdRng::from_entropy().gen()); - let g = { - let [a, b, c, d] = &m; - function_bit(a, b, c, d) - }; - let ct_g = { - let bytes = clients[0].pk_encrypt_bit(m); - let [a, b, c, d] = server.deserialize_cts_bits(&bytes).try_into().unwrap(); - server.serialize_cts_bits(&[function_bit(&a, &b, &c, &d)]) - }; - - // Clients generate decryption share of evaluation output - let ct_g_dec_shares = clients - .iter() - .map(|client| client.decrypt_share_bits(&ct_g)) - .collect_vec(); + // Run FHE evaluation again. - // Aggregate decryption shares - assert_eq!(vec![g], clients[0].decrypt_bits(&ct_g, &ct_g_dec_shares)); + run(&server, &clients); } } diff --git a/src/server.rs b/src/server.rs index 2169630..955a6e4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,10 +1,11 @@ -use crate::phantom::function_bit; +use crate::phantom::{function_bit, PhantomOps}; use crate::types::{ BskShareSubmission, CreateTaskSubmission, Decryptable, DecryptableBuilder, DecryptionShareSubmission, ErrorResponse, MutexServerStorage, ParamCRS, PkShareSubmission, ServerState, ServerStorage, Task, TaskId, TaskInputSubmission, TaskStatus, UserId, UserStorage, }; +use itertools::Itertools; use phantom_zone_evaluator::boolean::{fhew::param::I_4P, FheBool}; use rocket::serde::json::Json; use rocket::serde::msgpack::MsgPack; @@ -40,8 +41,8 @@ async fn get_status(ss: &State) -> Result, } /// The user submits Public Key shares -#[post("/submit_pk_shares", data = "", format = "msgpack")] -async fn submit_pk_shares( +#[post("/submit_pk_and_rpk_shares", data = "", format = "msgpack")] +async fn submit_pk_and_rpk_shares( submission: MsgPack, ss: &State, ) -> Result, ErrorResponse> { @@ -49,13 +50,18 @@ async fn submit_pk_shares( ss.ensure(ServerState::ReadyForPkShares)?; - let PkShareSubmission { user_id, pk_share } = submission.0; + let PkShareSubmission { + user_id, + pk_share, + rpk_share, + } = submission.0; let user = ss.get_user(user_id)?; - user.storage = UserStorage::PkShare(pk_share); + user.storage = UserStorage::PkAndRpkShare(pk_share, rpk_share); if ss.check_pk_share_submission() { ss.aggregate_pk_shares()?; + ss.aggregate_rpk_shares()?; ss.transit(ServerState::ReadyForBskShares); } @@ -70,7 +76,7 @@ async fn get_aggregated_pk(ss: &State) -> Result>> = task .inputs .values() - .map(|cipher| ps.deserialize_cts_bits(cipher)) - .collect(); + .map(|cipher| { + ps.deserialize_batched_ct(cipher) + .map(|ct| ps.wrap_batched_ct(&ct)) + }) + .try_collect() + .unwrap(); let [user_1_input, user_2_input] = inputs.try_into().unwrap(); let [a, b]: [FheBool<_>; 2] = user_1_input.try_into().unwrap(); @@ -187,7 +197,7 @@ async fn background_computation(ss: MutexServerStorage) { let g: FheBool<_> = function_bit(&a, &b, &c, &d); // For now, we'll just simulate a computation by waiting and returning the input // Should be decryptables - let g = ps.serialize_cts_bits(&[g]); + let g = ps.serialize_rp_ct(&ps.pack(&[g.into_ct()])).unwrap(); // Create decryptables from the result let decryptables = vec![builder.new_public(g.clone()), builder.new_designated(g, 1)]; // Replace with actual decryptables @@ -240,7 +250,7 @@ pub fn rocket(n_users: usize) -> Rocket { get_param, register, get_status, - submit_pk_shares, + submit_pk_and_rpk_shares, get_aggregated_pk, submit_bsks, create_task, diff --git a/src/types.rs b/src/types.rs index 2b5ce07..ed4fb99 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,6 @@ -use crate::phantom::Server as PhantomServer; +use crate::phantom::{PhantomCrs, PhantomOps, PhantomPrimeOps, PhantomServer}; +use itertools::Itertools; use phantom_zone_evaluator::boolean::fhew::prelude::*; -use rand::rngs::StdRng; use rocket::serde::{Deserialize, Serialize}; use rocket::tokio::sync::Mutex; use rocket::Responder; @@ -15,7 +15,7 @@ pub type UserId = usize; pub type DecryptionShare = Vec; pub type Word = Vec; pub type ServerKeyShare = Vec; -pub type ParamCRS = (FhewBoolMpiParam, FhewBoolMpiCrs); +pub type ParamCRS = (FhewBoolMpiParam, PhantomCrs); pub type Cipher = Vec; pub type TaskId = usize; @@ -139,6 +139,8 @@ pub(crate) enum Error { }, #[error("Unexpected decryption share from user #{user_id}")] UnexpectedDecryptionShare { user_id: UserId }, + #[error("Bincode de/serialization error")] + Bincode(bincode::Error), } #[derive(Responder)] @@ -161,6 +163,18 @@ impl From for ErrorResponse { } } +impl From for ErrorResponse { + fn from(error: bincode::Error) -> Self { + Self::ServerError(error.to_string()) + } +} + +impl From for Error { + fn from(error: bincode::Error) -> Self { + Self::Bincode(error) + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum ServerState { /// Users are allowed to join the computation @@ -200,7 +214,7 @@ pub(crate) type MutexServerStorage = Arc>; pub(crate) struct ServerStorage { /// Close registration when this number is reached pub(crate) n_users: usize, - pub(crate) ps: PhantomServer, + pub(crate) ps: PhantomServer, pub(crate) state: ServerState, pub(crate) users: Vec, pub(crate) task_queue: VecDeque, @@ -220,7 +234,7 @@ impl ServerStorage { pub(crate) fn new(param: FhewBoolMpiParam, n_users: usize) -> Self { Self { n_users, - ps: PhantomServer::new(param), + ps: PhantomServer::new(param, PhantomCrs::from_entropy(), None, None, None).unwrap(), state: ServerState::ReadyForJoining, users: vec![], task_queue: VecDeque::new(), @@ -229,7 +243,7 @@ impl ServerStorage { } pub(crate) fn get_param_crs(&self) -> ParamCRS { - self.ps.get_param_crs() + (*self.ps.param(), *self.ps.crs()) } pub(crate) fn is_users_full(&self) -> bool { @@ -264,14 +278,14 @@ impl ServerStorage { pub(crate) fn check_pk_share_submission(&self) -> bool { self.users .iter() - .all(|user| matches!(user.storage, UserStorage::PkShare(..))) + .all(|user| matches!(user.storage, UserStorage::PkAndRpkShare(_, _))) } pub(crate) fn aggregate_pk_shares(&mut self) -> Result<(), Error> { let mut pk_shares = Vec::new(); for (user_id, user) in self.users.iter().enumerate() { - if let UserStorage::PkShare(pk_share) = &user.storage { - pk_shares.push(pk_share.clone()); + if let UserStorage::PkAndRpkShare(pk_share, _) = &user.storage { + pk_shares.push(self.ps.deserialize_pk_share(pk_share)?); } else { return Err(Error::PkShareNotFound { user_id }); } @@ -280,6 +294,19 @@ impl ServerStorage { Ok(()) } + pub(crate) fn aggregate_rpk_shares(&mut self) -> Result<(), Error> { + let mut pk_shares = Vec::new(); + for (user_id, user) in self.users.iter().enumerate() { + if let UserStorage::PkAndRpkShare(_, rpk_share) = &user.storage { + pk_shares.push(self.ps.deserialize_rp_key_share(rpk_share)?); + } else { + return Err(Error::PkShareNotFound { user_id }); + } + } + self.ps.aggregate_rp_key_shares(&pk_shares); + Ok(()) + } + pub(crate) fn check_bsk_share_submission(&self) -> bool { self.users .iter() @@ -295,7 +322,12 @@ impl ServerStorage { return Err(Error::BskShareNotFound { user_id }); } } - self.ps.aggregate_bs_key_shares::(&bsk_shares); + self.ps.aggregate_bs_key_shares( + &bsk_shares + .iter() + .map(|bsk_share| self.ps.deserialize_bs_key_share(bsk_share)) + .try_collect::<_, Vec<_>, _>()?, + ); Ok(bsk_shares) } @@ -422,7 +454,7 @@ pub(crate) struct UserRecord { #[derive(Debug, Clone)] pub(crate) enum UserStorage { Empty, - PkShare(Vec), + PkAndRpkShare(Vec, Vec), BskShare(Box>), } @@ -440,6 +472,7 @@ impl UserStorage { pub(crate) struct PkShareSubmission { pub(crate) user_id: UserId, pub(crate) pk_share: Vec, + pub(crate) rpk_share: Vec, } #[derive(Serialize, Deserialize)] From 17a696bf2be03cc1ba0f7632146c17854aefea6e Mon Sep 17 00:00:00 2001 From: han0110 Date: Fri, 11 Oct 2024 15:42:30 +0000 Subject: [PATCH 2/4] feat: rework and support multiple workers --- .gitignore | 1 + Cargo.lock | 1805 ++++++--------------------------------- Cargo.toml | 50 +- README.md | 5 + Rocket.toml | 4 - bin/server.rs | 23 + bin/worker.rs | 23 + src/circuit.rs | 20 + src/client.rs | 582 ++++--------- src/lib.rs | 15 +- src/phantom.rs | 996 +++++---------------- src/server.rs | 299 ++----- src/server/app.rs | 675 +++++++++++++++ src/server/scheduler.rs | 204 +++++ src/server/util.rs | 46 + src/test.rs | 161 ++++ src/tests.rs | 108 --- src/types.rs | 591 ------------- src/user.rs | 248 ++++++ src/worker.rs | 167 ++++ 20 files changed, 2296 insertions(+), 3727 deletions(-) delete mode 100644 Rocket.toml create mode 100644 bin/server.rs create mode 100644 bin/worker.rs create mode 100644 src/circuit.rs create mode 100644 src/server/app.rs create mode 100644 src/server/scheduler.rs create mode 100644 src/server/util.rs create mode 100644 src/test.rs delete mode 100644 src/tests.rs delete mode 100644 src/types.rs create mode 100644 src/user.rs create mode 100644 src/worker.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..9026c77 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.vscode diff --git a/Cargo.lock b/Cargo.lock index 20314b1..c800a9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,37 +28,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", @@ -66,31 +44,68 @@ dependencies = [ ] [[package]] -name = "atomic" -version = "0.5.3" +name = "autocfg" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] -name = "atomic" -version = "0.6.0" +name = "axum" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ - "bytemuck", + "async-trait", + "axum-core", + "base64 0.22.1", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper 1.0.1", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "atomic-waker" -version = "1.1.2" +name = "axum-core" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.1", + "tower-layer", + "tower-service", + "tracing", +] [[package]] name = "backtrace" @@ -109,15 +124,15 @@ dependencies = [ [[package]] name = "base64" -version = "0.22.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "binascii" -version = "0.1.4" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" @@ -129,22 +144,13 @@ dependencies = [ ] [[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytemuck" -version = "1.16.3" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "byteorder" @@ -154,15 +160,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.10" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -171,110 +180,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "console" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "unicode-width", - "windows-sys 0.52.0", -] - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" +name = "cpufeatures" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ - "core-foundation-sys", "libc", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "devise" -version = "0.4.1" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "devise_codegen", - "devise_core", + "generic-array", + "typenum", ] [[package]] -name = "devise_codegen" -version = "0.4.1" +name = "data-encoding" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" -dependencies = [ - "devise_core", - "quote", -] +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] -name = "devise_core" -version = "0.4.1" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "bitflags", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.74", + "block-buffer", + "crypto-common", ] [[package]] @@ -283,78 +220,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - -[[package]] -name = "figment" -version = "0.10.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" -dependencies = [ - "atomic 0.6.0", - "pear", - "serde", - "toml", - "uncased", - "version_check", -] - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -364,59 +235,26 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -425,45 +263,39 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-macro", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] -name = "generator" -version = "0.7.5" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows", + "typenum", + "version_check", ] [[package]] @@ -484,75 +316,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +name = "haunted" +version = "0.1.0" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", + "anyhow", + "axum", + "bincode", "futures-util", - "http 0.2.12", - "indexmap", - "slab", + "headers", + "http-body-util", + "hyper", + "hyper-util", + "itertools", + "phantom-zone-evaluator", + "rand", + "serde", "tokio", - "tokio-util", + "tokio-tungstenite", + "tower", "tracing", + "tracing-subscriber", + "uuid", ] [[package]] -name = "h2" -version = "0.4.6" +name = "headers" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ - "atomic-waker", + "base64 0.21.7", "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.1.0", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "headers-core" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "haunted" -version = "0.1.0" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "anyhow", - "bincode", - "futures", - "indicatif", - "itertools", - "num-traits", - "phantom-zone-evaluator", - "rand", - "rayon", - "reqwest", - "rocket", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-util", + "http", ] [[package]] @@ -561,17 +369,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -583,17 +380,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -601,7 +387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -612,16 +398,16 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -629,30 +415,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.4.1" @@ -662,10 +424,10 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -673,125 +435,25 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.4.1", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.4.1", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "hyper 1.4.1", + "http", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" -dependencies = [ - "equivalent", - "hashbrown", - "serde", -] - -[[package]] -name = "indicatif" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" -dependencies = [ - "console", - "instant", - "number_prefix", - "portable-atomic", - "unicode-width", -] - -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "itertools" version = "0.13.0" @@ -807,15 +469,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -837,43 +490,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - [[package]] name = "matchers" version = "0.1.0" @@ -883,6 +505,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.4" @@ -913,43 +541,7 @@ dependencies = [ "hermit-abi", "libc", "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 1.1.0", - "httparse", - "memchr", - "mime", - "spin", - "tokio", - "tokio-util", - "version_check", -] - -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "windows-sys", ] [[package]] @@ -988,12 +580,6 @@ dependencies = [ "serde", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-integer" version = "0.1.46" @@ -1024,138 +610,32 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "object" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "openssl" -version = "0.10.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.103" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "pear_codegen" -version = "0.2.9" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.74", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -1166,7 +646,6 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phantom-zone-crypto" version = "0.1.0" -source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94e69d992d86b1c72a0fddcd585d9be39afd8815" dependencies = [ "itertools", "num-traits", @@ -1179,7 +658,6 @@ dependencies = [ [[package]] name = "phantom-zone-derive" version = "0.1.0" -source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94e69d992d86b1c72a0fddcd585d9be39afd8815" dependencies = [ "proc-macro2", "quote", @@ -1189,7 +667,6 @@ dependencies = [ [[package]] name = "phantom-zone-evaluator" version = "0.1.0" -source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94e69d992d86b1c72a0fddcd585d9be39afd8815" dependencies = [ "itertools", "paste", @@ -1203,7 +680,6 @@ dependencies = [ [[package]] name = "phantom-zone-math" version = "0.1.0" -source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94e69d992d86b1c72a0fddcd585d9be39afd8815" dependencies = [ "bincode", "itertools", @@ -1219,26 +695,6 @@ dependencies = [ "unroll", ] -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1251,24 +707,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "portable-atomic" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1296,19 +734,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", - "version_check", - "yansi", -] - [[package]] name = "quote" version = "1.0.36" @@ -1358,65 +783,16 @@ dependencies = [ "rand", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ref-cast" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1430,13 +806,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1447,174 +823,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "reqwest" -version = "0.12.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.4.6", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.4.1", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "windows-registry", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rmp" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" -dependencies = [ - "byteorder", - "num-traits", - "paste", -] - -[[package]] -name = "rmp-serde" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" -dependencies = [ - "byteorder", - "rmp", - "serde", -] - -[[package]] -name = "rocket" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f" -dependencies = [ - "async-stream", - "async-trait", - "atomic 0.5.3", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand", - "ref-cast", - "rmp-serde", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", -] - -[[package]] -name = "rocket_codegen" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" -dependencies = [ - "devise", - "glob", - "indexmap", - "proc-macro2", - "quote", - "rocket_http", - "syn 2.0.74", - "unicode-xid", - "version_check", -] - -[[package]] -name = "rocket_http" -version = "0.5.1" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9" -dependencies = [ - "cookie", - "either", - "futures", - "http 0.2.12", - "hyper 0.14.30", - "indexmap", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "serde", - "smallvec", - "stable-pattern", - "state", - "time", - "tokio", - "uncased", -] +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" @@ -1637,59 +848,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.23.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" -dependencies = [ - "base64", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" - -[[package]] -name = "rustls-webpki" -version = "0.102.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.17" @@ -1702,50 +860,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.209" @@ -1777,9 +891,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1788,11 +902,12 @@ dependencies = [ ] [[package]] -name = "serde_spanned" -version = "0.6.7" +name = "serde_path_to_error" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ + "itoa", "serde", ] @@ -1808,6 +923,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1817,6 +943,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1848,7 +980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1857,36 +989,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - -[[package]] -name = "state" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" -dependencies = [ - "loom", -] - [[package]] name = "strength_reduce" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "syn" version = "1.0.109" @@ -1899,73 +1007,42 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -dependencies = [ - "futures-core", -] - -[[package]] -name = "system-configuration" -version = "0.6.0" +name = "syn" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "system-configuration-sys" -version = "0.6.0" +name = "sync_wrapper" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] -name = "tempfile" -version = "3.12.0" +name = "sync_wrapper" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -1982,68 +1059,21 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -2058,97 +1088,31 @@ dependencies = [ ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.11" +name = "tokio-tungstenite" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", + "futures-util", + "log", "tokio", -] - -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", + "tungstenite", ] [[package]] name = "tower" -version = "0.4.13" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper 0.1.2", "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -2169,6 +1133,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2241,29 +1206,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "ubyte" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" -dependencies = [ - "serde", -] - -[[package]] -name = "uncased" -version = "0.9.10" +name = "tungstenite" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ - "serde", - "version_check", + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "utf-8", ] [[package]] -name = "unicode-bidi" -version = "0.3.15" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" @@ -2271,27 +1235,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "unroll" version = "0.1.5" @@ -2303,20 +1246,19 @@ dependencies = [ ] [[package]] -name = "untrusted" -version = "0.9.0" +name = "utf-8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] -name = "url" -version = "2.5.2" +name = "uuid" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", + "getrandom", + "serde", ] [[package]] @@ -2325,12 +1267,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -2352,96 +1288,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.74", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wasm-streams" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2464,76 +1310,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -2542,46 +1325,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2594,72 +1359,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" -dependencies = [ - "memchr", -] - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -dependencies = [ - "is-terminal", -] - [[package]] name = "zerocopy" version = "0.7.35" @@ -2680,9 +1403,3 @@ dependencies = [ "quote", "syn 2.0.74", ] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 7ad92a7..63ad4b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,25 +1,37 @@ [package] +edition = "2021" name = "haunted" version = "0.1.0" -edition = "2021" [dependencies] -rocket = { version = "0.5.1", features = ["json", "msgpack"] } -phantom-zone-evaluator = { git = "https://github.com/han0110/phantom-zone", branch = "rewrite/ring-packing", features = [ - "serde", - "dev", -] } -anyhow = { version = "1.0.86" } -thiserror = { version = "1.0.63" } -serde = { version = "1.0.209" } -serde_json = { version = "1.0.127" } -reqwest = { version = "0.12.5", features = ["json", "stream"] } -tokio = { version = "1.39.3", features = ["full"] } -rand = { version = "0.8.5" } +anyhow = "1.0.89" +axum = { version = "0.7.7", features = ["ws"] } +bincode = "1.3.3" +futures-util = "0.3.31" +headers = "0.4.0" +http-body-util = "0.1.2" +hyper = "1.4.1" +hyper-util = { version = "0.1.9", features = ["client-legacy"] } itertools = { version = "0.13.0" } -rayon = { version = "1.10.0" } -tokio-util = { version = "0.7.11" } -indicatif = { version = "0.17.8" } -num-traits = { version = "0.2.19" } -bincode = { version = "1.3.3" } -futures = { version = "0.3.30"} +# phantom-zone-evaluator = { git = "https://github.com/han0110/phantom-zone", branch = "rewrite/ring-packing", features = ["serde"] } +phantom-zone-evaluator = { path = "../phantom-zone/evaluator", features = ["serde"] } +rand = { version = "0.8.5" } +serde = { version = "1.0" } +tokio = { version = "1.0", features = ["rt-multi-thread", "signal"] } +tokio-tungstenite = "0.24.0" +tower = "0.5.1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +uuid = { version = "1.11.0", features = ["v4", "serde"] } + +[dev-dependencies] +# phantom-zone-evaluator = { git = "https://github.com/han0110/phantom-zone", branch = "rewrite/ring-packing", features = ["dev"] } +phantom-zone-evaluator = { path = "../phantom-zone/evaluator", features = ["dev"] } + +[[bin]] +name = "server" +path = "bin/server.rs" + +[[bin]] +name = "worker" +path = "bin/worker.rs" diff --git a/README.md b/README.md index e7a53b0..d159181 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,8 @@ Optional Goals: It is desirable to have these features, but I'm not sure if I'm - Serialize a struct of FheBools into a vector of FheBools and deserialize it back. This way we can remove the need to re-write the logic of re-encrypting struct memebers. - JavaScript support. [RiverRuby/pz-web](https://github.com/RiverRuby/pz-web/blob/main/src/ni_hiring.rs) is a good example to look into. +## End-to-end test + +```bash +cargo test --release --package haunted --lib -- test::e2e --nocapture +``` \ No newline at end of file diff --git a/Rocket.toml b/Rocket.toml deleted file mode 100644 index a7a2052..0000000 --- a/Rocket.toml +++ /dev/null @@ -1,4 +0,0 @@ -[default] -address = "0.0.0.0" -port = 5566 -limits = { msgpack = "700 MB" } diff --git a/bin/server.rs b/bin/server.rs new file mode 100644 index 0000000..2084f10 --- /dev/null +++ b/bin/server.rs @@ -0,0 +1,23 @@ +use haunted::{ + phantom::{PhantomCrs, PhantomParam}, + server::{self, ServerState}, +}; +use std::env; +use tokio::net::TcpListener; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +#[tokio::main] +async fn main() { + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::from_default_env()) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let server_addr = env::var("SERVER_ADDR").unwrap_or("127.0.0.1:3000".to_string()); + let listener = TcpListener::bind(&server_addr).await.unwrap(); + tracing::debug!("listening on {}", listener.local_addr().unwrap()); + + let state = ServerState::new(PhantomParam::I_4P_60, PhantomCrs::from_entropy()); + let router = server::router(state); + axum::serve(listener, router).await.unwrap(); +} diff --git a/bin/worker.rs b/bin/worker.rs new file mode 100644 index 0000000..0d52394 --- /dev/null +++ b/bin/worker.rs @@ -0,0 +1,23 @@ +use haunted::worker; +use std::env; +use tokio::{signal, sync::broadcast}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +#[tokio::main] +async fn main() { + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::from_default_env()) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let (shutdown_tx, shutdown_rx) = broadcast::channel(1); + + let server_uri = env::var("SERVER_URI").unwrap_or("ws://127.0.0.1:3000/worker/ws".to_string()); + let handle = tokio::spawn(worker::run(server_uri, shutdown_rx)); + + if signal::ctrl_c().await.is_ok() { + let _ = shutdown_tx.send(()); + } + + let _ = handle.await; +} diff --git a/src/circuit.rs b/src/circuit.rs new file mode 100644 index 0000000..32e2820 --- /dev/null +++ b/src/circuit.rs @@ -0,0 +1,20 @@ +use crate::phantom::PhantomBool; +use itertools::izip; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum CircuitId { + // Demo circuit that computes `inputs[..inputs.len() / 2] ^ inputs[inputs.len() / 2..]`. + Demo, +} + +impl CircuitId { + pub fn evaluate<'a>(&self, inputs: &[PhantomBool<'a>]) -> Vec> { + match self { + Self::Demo => { + let (l, r) = inputs.split_at(inputs.len() / 2); + izip!(l, r).map(|(a, b)| a ^ b).collect() + } + } + } +} diff --git a/src/client.rs b/src/client.rs index 15aa7a9..4b4394a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,483 +1,245 @@ use crate::{ - phantom::{PhantomClient, PhantomOps, PhantomPrimeOps}, - server::*, - types::{ - BskShareSubmission, Cipher, CreateTaskSubmission, Decryptable, DecryptionShareSubmission, - ParamCRS, PkShareSubmission, ServerState, Task, TaskId, TaskInputSubmission, TaskStatus, - UserId, Visibility, + phantom::{PhantomBsKeyShare, PhantomPackedCtDecShare, PhantomPkShare, PhantomRpKeyShare}, + server::app::{ + Action, CreateUserActionRequest, CreateUserActionResponse, CreateUserBsKeyShareRequest, + CreateUserBsKeyShareResponse, CreateUserPkShareRequest, CreateUserPkShareResponse, + CreateUserRpKeyShareRequest, CreateUserRpKeyShareResponse, GetPkResponse, GetSetupResponse, + GetStatusResponse, GetUserDecryptablesResponse, GetUserTasksResponse, Task, + UpdateUserTasksRequest, UserId, }, + Result, }; - -use anyhow::{bail, Error}; -use indicatif::{ProgressBar, ProgressStyle}; -use itertools::Itertools; -use rand::{rngs::StdRng, Rng, SeedableRng}; -use reqwest::{self, header::CONTENT_TYPE, Client}; -use rocket::{serde::msgpack, uri}; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - pin::Pin, - task::{Context, Poll}, - time::Duration, +use anyhow::{anyhow, bail}; +use axum::body::Body; +use core::future::Future; +use futures_util::TryFutureExt; +use http_body_util::BodyExt; +use hyper::{Request, Response, StatusCode}; +use hyper_util::{ + client::legacy::{connect::HttpConnector, Client as HyperUtilClient}, + rt::TokioExecutor, }; -use tokio::io::AsyncRead; -use tokio::time::sleep; -use tokio_util::io::ReaderStream; +use serde::{de::DeserializeOwned, Serialize}; -pub struct Wallet { - pub(crate) rc: ProductionClient, -} +pub trait HttpClient { + type Body: BodyExt; -impl Wallet { - pub fn new(url: &str) -> Self { - Self { - rc: ProductionClient::new(url), - } - } + fn request(&self, req: Request) -> impl Future>>; } -impl Wallet { - async fn wait_for_server_state( - &self, - desired_state: ServerState, - ) -> Result { - const MAX_ATTEMPTS: u32 = 30; - const DELAY_MS: u64 = 1000; - - for _ in 0..MAX_ATTEMPTS { - if let Ok(status) = self.rc.get_status().await { - if status == desired_state { - return Ok(status); - } - } - sleep(Duration::from_millis(DELAY_MS)).await; - } - bail!("Timed out waiting for server state: {:?}", desired_state) - } - - async fn acquire_pk( - &self, - user_id: UserId, - pk_share: Vec, - rpk_share: Vec, - ) -> Result, Error> { - // Submit the public key share - let _: UserId = self - .rc - .submit_pk_shares(user_id, pk_share, rpk_share) - .await?; - for _ in 0..10 { - if let Ok(server_pk) = self.rc.get_aggregated_pk().await { - return Ok(server_pk); - } - sleep(Duration::from_millis(100)).await; - } - bail!("Failed to get aggregated public key".to_string()); - } +impl HttpClient for HyperUtilClient { + type Body = hyper::body::Incoming; - /// Complete the flow to derive server key shares - /// - /// Wait actions from other users - pub async fn run_setup(&self) -> Result { - let (param, crs) = self.rc.get_param_crs().await?; - let user_id = self.rc.register().await?; - let seed = StdRng::from_entropy().gen(); - let mut pc = - PhantomClient::::new(param, crs, user_id, seed, None).unwrap(); - self.wait_for_server_state(ServerState::ReadyForPkShares) - .await?; - let server_pk = self - .acquire_pk( - user_id, - pc.serialize_pk_share(&pc.pk_share_gen())?, - pc.serialize_rp_key_share(&pc.rp_key_share_gen())?, - ) - .await?; - pc.with_pk(pc.deserialize_pk(&server_pk)?); - self.wait_for_server_state(ServerState::ReadyForBskShares) - .await?; - self.rc - .submit_bsks(user_id, pc.serialize_bs_key_share(&pc.bs_key_share_gen())?) - .await?; - - Ok(SetupWallet { - rc: self.rc.clone(), - user_id, - pc, - tasks: Default::default(), - }) + fn request(&self, req: Request) -> impl Future>> { + self.request(req) + .map_err(|err| anyhow!("failed to send request, {err}")) } } -pub struct SetupWallet { - rc: ProductionClient, - pub(crate) user_id: UserId, - pc: PhantomClient, - tasks: HashMap, +#[derive(Clone)] +pub struct Client> { + uri: String, + inner: T, } -impl SetupWallet { - /// Creates a new task on the server. - pub async fn create_task( - &mut self, - required_inputs: Vec, - input: Vec, - ) -> Result { - // Encrypt input - let cipher = self - .pc - .serialize_batched_ct(&self.pc.batched_pk_encrypt(input))?; - - // Create task on server - let task_id = self - .rc - .create_task(self.user_id, required_inputs, cipher) - .await?; - - // Store task locally - self.tasks.insert(task_id, TaskStatus::WaitingForInput); - - Ok(task_id) - } - - /// Runs background tasks for handling inputs and decryptables. - pub async fn run_background_tasks(&mut self) -> Result<(), Error> { - loop { - // Handle tasks (including input requests) - let tasks = self.rc.get_tasks_for_user(self.user_id).await?; - println!("User {} tasks {:?}", self.user_id, tasks); - for task in tasks { - self.handle_task(task).await?; - } - - // Handle decryptable requests - let decryptables = self.rc.get_decryptables_for_user(self.user_id).await?; - for (task_id, decryptable) in decryptables { - self.handle_decryptable(task_id, decryptable).await?; - } - - // Sleep for a short duration before the next iteration - sleep(Duration::from_secs(5)).await; - } - } - - async fn handle_task(&mut self, task: Task) -> Result<(), Error> { - match task.status { - TaskStatus::WaitingForInput => { - if !task.inputs.contains_key(&self.user_id) { - let input = self.get_input_for_task(task.id)?; // Implement this method - let cipher = self - .pc - .serialize_batched_ct(&self.pc.batched_pk_encrypt(input))?; - self.rc - .submit_task_input(task.id, self.user_id, cipher) - .await?; - } - } - TaskStatus::Done => { - self.process_completed_task(task); - } - _ => { - // Update local task status - self.tasks.insert(task.id, task.status); - } - } - Ok(()) - } - - async fn handle_decryptable( - &self, - task_id: TaskId, - decryptable: Decryptable, - ) -> Result<(), Error> { - if decryptable.should_contribute(self.user_id) { - let share = self.pc.serialize_rp_dec_share( - &self - .pc - .rp_decrypt_share(&self.pc.deserialize_rp_ct(&decryptable.word)?), - )?; - self.rc - .submit_decryption_share(task_id, decryptable.id, self.user_id, share) - .await?; - } - Ok(()) - } - - fn get_input_for_task(&self, _task_id: TaskId) -> Result, Error> { - // Implement logic to get input for the task - // This could involve user interaction or some predefined logic - Ok(vec![true, false]) // Example input - } - - fn process_completed_task(&self, task: Task) { - // Implement logic to handle completed tasks - println!("Task {} completed", task.id); - // You might want to decrypt the result here - for decryptable in task.decryptables.iter() { - let plain = match decryptable.vis { - Visibility::Public => self.pc.aggregate_rp_decryption_shares( - &self.pc.deserialize_rp_ct(&decryptable.word).unwrap(), - &decryptable - .get_shares() - .iter() - .map(|share| self.pc.deserialize_rp_dec_share(share)) - .try_collect::<_, Vec<_>, _>() - .unwrap(), - ), - Visibility::Designated(user_id) => { - debug_assert_eq!(user_id, self.user_id); - let rp_ct = self.pc.deserialize_rp_ct(&decryptable.word).unwrap(); - let my_share = self.pc.rp_decrypt_share(&rp_ct); - let all_shares = { - let mut other_shares = decryptable - .get_shares() - .iter() - .map(|share| self.pc.deserialize_rp_dec_share(share)) - .try_collect::<_, Vec<_>, _>() - .unwrap(); - other_shares.push(my_share); - other_shares - }; - self.pc.aggregate_rp_decryption_shares(&rp_ct, &all_shares) - } - }; - println!("User {} Decrypted plain {:?}", self.user_id, plain); - } - } - pub fn get_task(&self, task_id: &TaskId) -> Option<&TaskStatus> { - self.tasks.get(task_id) +impl Client { + pub fn new(uri: impl ToString) -> Self { + let uri = uri.to_string(); + let inner = HyperUtilClient::builder(TokioExecutor::new()).build(HttpConnector::new()); + Self { uri, inner } } } -#[derive(Debug, Clone)] -pub struct ProductionClient { - url: String, - client: reqwest::Client, -} - -impl ProductionClient { - fn new(url: &str) -> Self { - Self { - url: url.to_string(), - client: Client::new(), - } - } - - fn path(&self, path: impl ToString) -> String { - let path = path.to_string(); - println!("{}", path); - format!("{}/{}", self.url, path) - } - - async fn handle_response Deserialize<'de> + 'static>( - response: reqwest::Response, - ) -> Result { - match response.status().as_u16() { - 200 => Ok(response.json::().await?), - _ => { - let err = response.text().await?; - bail!("Server responded error: {:?}", err) - } +impl Client { + async fn call(&self, request: Request) -> Result { + let (method, uri) = (request.method().clone(), request.uri().clone()); + tracing::debug!("send {method} {uri}"); + let response = self.inner.request(request).await?; + let status = response.status(); + let body = response + .into_body() + .collect() + .await + .map_err(|err| anyhow!("failed to collect body, {err}"))?; + if !matches!(status, StatusCode::OK) { + let bytes = body.to_bytes(); + let msg = String::from_utf8_lossy(&bytes); + bail!("failed to {method} {uri}, {msg}",) } + Ok(bincode::deserialize(&body.to_bytes())?) } - async fn get Deserialize<'de> + 'static>( - &self, - path: impl ToString, - ) -> Result { - let response = self.client.get(self.path(path)).send().await?; - Self::handle_response(response).await + async fn get(&self, path: impl AsRef) -> Result { + let uri = format!("{}{}", &self.uri, path.as_ref()); + let request = Request::get(&uri).body(Body::empty()).unwrap(); + self.call(request).await } - async fn post_nobody Deserialize<'de> + 'static>( + async fn post( &self, - path: impl ToString, - ) -> Result { - let response = self.client.post(self.path(path)).send().await?; - Self::handle_response(response).await + path: impl AsRef, + body: &U, + ) -> Result { + let uri = format!("{}{}", &self.uri, path.as_ref()); + let body = bincode::serialize(body)?; + let request = Request::post(&uri).body(Body::from(body)).unwrap(); + self.call(request).await } - async fn post Deserialize<'de> + 'static>( + async fn put( &self, - path: impl ToString, - body: Vec, - ) -> Result { - let response = self.client.post(self.path(path)).body(body).send().await?; - Self::handle_response(response).await + path: impl AsRef, + body: &U, + ) -> Result { + let uri = format!("{}{}", &self.uri, path.as_ref()); + let body = bincode::serialize(body)?; + let request = Request::put(&uri).body(Body::from(body)).unwrap(); + self.call(request).await } - async fn post_msgpack Deserialize<'de> + 'static>( - &self, - path: impl ToString, - body: &impl Serialize, - ) -> Result { - let body = msgpack::to_compact_vec(body)?; - let reader = ProgressReader::new(&body, 128 * 1024); - let stream = ReaderStream::new(reader); - let response = self - .client - .post(self.path(path)) - .header(CONTENT_TYPE, "application/msgpack") - .body(reqwest::Body::wrap_stream(stream)) - .send() - .await?; - Self::handle_response(response).await + pub async fn get_status(&self) -> Result { + self.get("/status").await } - pub async fn get_param_crs(&self) -> Result { - self.get(uri!(get_param)).await + pub async fn get_setup(&self) -> Result { + self.get("/setup").await } - pub async fn register(&self) -> Result { - self.post_nobody(uri!(register)).await + pub async fn get_pk(&self) -> Result { + self.get("/pk").await } - pub async fn get_status(&self) -> Result { - self.get(uri!(get_status)).await - } - - pub async fn submit_pk_shares( + pub async fn create_user_pk_share( &self, user_id: UserId, - pk_share: Vec, - rpk_share: Vec, - ) -> Result { - self.post_msgpack( - uri!(submit_pk_and_rpk_shares), - &PkShareSubmission { - user_id, - pk_share, - rpk_share, - }, + pk_share: PhantomPkShare, + ) -> Result { + self.post( + format!("/users/{user_id}/pk_share"), + &CreateUserPkShareRequest { pk_share }, ) .await } - pub async fn get_aggregated_pk(&self) -> Result, Error> { - self.get(uri!(get_aggregated_pk)).await - } - - pub async fn submit_bsks(&self, user_id: UserId, bsk_share: Vec) -> Result { - self.post_msgpack( - uri!(submit_bsks), - &BskShareSubmission { user_id, bsk_share }, + pub async fn create_user_rp_key_share( + &self, + user_id: UserId, + rp_key_share: PhantomRpKeyShare, + ) -> Result { + self.post( + format!("/users/{user_id}/rp_key_share"), + &CreateUserRpKeyShareRequest { rp_key_share }, ) .await } - pub async fn create_task( + pub async fn create_user_bs_key_share( &self, - initiator: UserId, - required_inputs: Vec, - initiator_input: Cipher, - ) -> Result { - self.post_msgpack( - uri!(create_task), - &CreateTaskSubmission { - initiator, - required_inputs, - initiator_input, - }, + user_id: UserId, + bs_key_share: PhantomBsKeyShare, + ) -> Result { + self.post( + format!("/users/{user_id}/bs_key_share"), + &CreateUserBsKeyShareRequest { bs_key_share }, ) .await } - pub async fn get_tasks_for_user(&self, user_id: UserId) -> Result, Error> { - self.get(uri!(get_tasks_for_user(user_id))).await + pub async fn get_user_decryptables( + &self, + user_id: UserId, + ) -> Result { + self.get(format!("/users/{user_id}/decryptables")).await + } + + pub async fn get_user_tasks(&self, user_id: UserId) -> Result { + self.get(format!("/users/{user_id}/tasks")).await } - pub async fn submit_task_input( + pub async fn create_decryption_share( &self, - task_id: TaskId, user_id: UserId, - input: Cipher, - ) -> Result<(), Error> { - self.post_msgpack( - uri!(submit_task_input), - &TaskInputSubmission { - task_id, - user_id, - input, + dec_shares: Vec<(usize, PhantomPackedCtDecShare)>, + ) -> Result<()> { + self.put( + format!("/users/{user_id}/tasks"), + &UpdateUserTasksRequest { + tasks: dec_shares + .into_iter() + .map(|(decryptable_id, dec_share)| Task::CreateDecShare { + decryptable_id, + packed: None, + dec_share: Some(dec_share), + }) + .collect(), }, ) .await } - pub async fn get_decryptables_for_user( + pub async fn create_user_actions( &self, user_id: UserId, - ) -> Result, Error> { - self.get(uri!(get_decryptables_for_user(user_id))).await - } - - pub async fn submit_decryption_share( - &self, - task_id: TaskId, - decryptable_id: usize, - user_id: UserId, - share: Vec, - ) -> Result<(), Error> { - self.post_msgpack( - uri!(submit_decryption_share), - &DecryptionShareSubmission { - task_id, - decryptable_id, - user_id, - share, - }, + action: Action, + ) -> Result { + self.post( + format!("/users/{user_id}/actions"), + &CreateUserActionRequest { action }, ) .await } } -struct ProgressReader { - inner: Vec, - progress_bar: ProgressBar, - position: usize, - chunk_size: usize, -} - -impl ProgressReader { - fn new(body: &[u8], chunk_size: usize) -> Self { - let total_bytes = body.len() as u64; - println!("Total size {} B", total_bytes); - let bar = ProgressBar::new(total_bytes); - bar.set_style( - ProgressStyle::with_template( - "[{elapsed_precise}] {bar:40.cyan/blue} {percent}% {bytes_per_sec} {msg}", - ) - .unwrap() - .progress_chars("##-"), - ); - bar.set_message("Uploading..."); - - Self { - inner: body.to_vec(), - progress_bar: bar, - position: 0, - chunk_size, +#[cfg(test)] +pub(crate) mod test { + use crate::{ + client::{Client, HttpClient}, + server::{app, ServerState}, + Result, + }; + use axum::{body::Body, routing::RouterIntoService, Router}; + use core::{future::Future, ops::DerefMut}; + use hyper::{Request, Response}; + use std::sync::{Arc, Mutex}; + use tower::Service; + + #[derive(Clone)] + pub(crate) struct MockHttpClient { + router: Arc>>, + } + + impl MockHttpClient { + fn new(router: Router) -> Self { + MockHttpClient { + router: Arc::new(Mutex::new(router.into_service())), + } } } -} -impl AsyncRead for ProgressReader { - fn poll_read( - mut self: Pin<&mut Self>, - _cx: &mut Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - let remaining = self.inner.len() - self.position; - let to_read = self.chunk_size.min(remaining.min(buf.remaining())); - let end = self.position + to_read; - buf.put_slice(&self.inner[self.position..end]); - self.position = end; - self.progress_bar.set_position(self.position as u64); + impl HttpClient for MockHttpClient { + type Body = Body; - if to_read == 0 { - self.progress_bar.finish_with_message("Upload complete") + fn request( + &self, + req: Request, + ) -> impl Future>> { + return inner(self.router.lock().unwrap(), req); + + async fn inner( + mut router: impl DerefMut>, + req: Request, + ) -> Result> { + Ok(router.call(req).await.unwrap()) + } } + } - Poll::Ready(Ok(())) + impl Client { + pub(crate) fn mock(state: Arc>) -> Self { + Self { + uri: "http://mock".to_string(), + inner: MockHttpClient::new(app::router().with_state(state)), + } + } } } diff --git a/src/lib.rs b/src/lib.rs index 99fba37..2a122e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,11 @@ -mod client; -mod server; -mod types; -mod phantom; +pub mod circuit; +pub mod client; +pub mod phantom; +pub mod server; +pub mod user; +pub mod worker; -pub use client::Wallet; -pub use server::rocket; +pub use anyhow::{Error, Result}; #[cfg(test)] -mod tests; +pub mod test; diff --git a/src/phantom.rs b/src/phantom.rs index 12a3189..f29cfa6 100644 --- a/src/phantom.rs +++ b/src/phantom.rs @@ -1,916 +1,326 @@ -#![allow(clippy::type_complexity)] - use core::{fmt::Debug, ops::Deref}; -use itertools::Itertools; -use phantom_zone_evaluator::boolean::fhew::prelude::*; +use phantom_zone_evaluator::boolean::fhew::{self, param::I_4P_60, prelude::*}; use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; -use std::ops::{BitAnd, BitOr, BitXor}; - -pub trait PhantomOps: Debug { - type Ring: RingOps; - type EvaluationRing: RingOps>; - type KeySwitchMod: ModulusOps; - type PackingRing: RingOps; - - fn new(param: FhewBoolParam) -> Self; - - fn param(&self) -> &FhewBoolParam; - - fn ring_packing_param(&self) -> RingPackingParam { - let param = self.param(); - RingPackingParam { - modulus: self.ring_rp().modulus(), - ring_size: param.ring_size, - sk_distribution: param.sk_distribution, - noise_distribution: param.noise_distribution, - auto_decomposition_param: param.auto_decomposition_param, - } - } - - fn ring(&self) -> &Self::Ring; - - fn mod_ks(&self) -> &Self::KeySwitchMod; - - fn ring_rp(&self) -> &Self::PackingRing; - - /// Batched encrypt bits by public key. - fn batched_pk_encrypt( - &self, - pk: &RlwePublicKeyOwned>, - ms: impl IntoIterator, - ) -> FhewBoolBatchedCiphertextOwned> { - FhewBoolBatchedCiphertextOwned::pk_encrypt( - self.param(), - self.ring(), - pk, - ms, - &mut StdLweRng::from_entropy(), - ) - } - /// Pack LWE ciphertexts into RLWE ciphertexts (ring packing ciphertext). - fn pack<'a>( - &self, - rp_key: &RingPackingKeyOwned<::EvalPrep>, - cts: impl IntoIterator>>, - ) -> FhewBoolPackedCiphertextOwned>; +pub use fhew::prelude::HierarchicalSeedableRng; - /// Aggregate decryption shares of ring packing ciphertext - fn aggregate_rp_decryption_shares<'a>( - &self, - ct: &FhewBoolPackedCiphertextOwned>, - dec_shares: impl IntoIterator>>, - ) -> Vec { - ct.aggregate_decryption_shares(self.ring_rp(), dec_shares) - } - - /// Serialize public key share - fn serialize_pk_share( - &self, - pk_share: &SeededRlwePublicKeyOwned>, - ) -> bincode::Result> { - bincode::serialize(&pk_share.compact(self.ring())) - } - - /// Deserialize public key share - fn deserialize_pk_share( - &self, - bytes: &[u8], - ) -> bincode::Result>> { - let pk_share_compact: SeededRlwePublicKey = bincode::deserialize(bytes)?; - Ok(pk_share_compact.uncompact(self.ring())) - } - - /// Serialize ring packing key share - fn serialize_rp_key_share( - &self, - rp_key_share: &RingPackingKeyShareOwned>, - ) -> bincode::Result> { - bincode::serialize(&rp_key_share.compact(self.ring())) - } - - /// Deserialize ring packing key share - fn deserialize_rp_key_share( - &self, - bytes: &[u8], - ) -> bincode::Result>> { - let rp_key_share_compact: RingPackingKeyShareCompact = bincode::deserialize(bytes)?; - Ok(rp_key_share_compact.uncompact(self.ring())) - } - - /// Serialize bootstrapping key share - fn serialize_bs_key_share( - &self, - bs_key_share: &FhewBoolMpiKeyShareOwned, Elem>, - ) -> bincode::Result> { - bincode::serialize(&bs_key_share.compact(self.ring(), self.mod_ks())) - } - - /// Deserialize bootstrapping key share - fn deserialize_bs_key_share( - &self, - bytes: &[u8], - ) -> bincode::Result, Elem>> { - let bs_key_share_compact: FhewBoolMpiKeyShareCompact = bincode::deserialize(bytes)?; - Ok(bs_key_share_compact.uncompact(self.ring(), self.mod_ks())) - } +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct PhantomParam { + param: FhewBoolMpiParam, + ring_packing_modulus: Option, + ring_packing_auto_decomposition_param: DecompositionParam, +} - /// Serialize public key - fn serialize_pk(&self, pk: &RlwePublicKeyOwned>) -> bincode::Result> { - bincode::serialize(&pk.compact(self.ring())) - } +impl PhantomParam { + pub const I_4P_60: Self = Self { + param: I_4P_60, + ring_packing_modulus: Some(Modulus::Prime(2305843009213554689)), + ring_packing_auto_decomposition_param: DecompositionParam { + log_base: 20, + level: 1, + }, + }; +} - /// Deserialize public key - fn deserialize_pk( - &self, - bytes: &[u8], - ) -> bincode::Result>> { - let pk_compact: RlwePublicKey = bincode::deserialize(bytes)?; - Ok(pk_compact.uncompact(self.ring())) - } +impl Deref for PhantomParam { + type Target = FhewBoolMpiParam; - /// Serialize ring packing key - fn serialize_rp_key( - &self, - rp_key: &RingPackingKeyOwned>, - ) -> bincode::Result> { - bincode::serialize(&rp_key.compact(self.ring_rp())) + fn deref(&self) -> &Self::Target { + &self.param } +} - /// Deserialize ring packing key - fn deserialize_rp_key( - &self, - bytes: &[u8], - ) -> bincode::Result>> { - let rp_key_compact: RingPackingKeyCompact = bincode::deserialize(bytes)?; - Ok(rp_key_compact.uncompact(self.ring_rp())) - } +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct PhantomCrs(::Seed); - /// Serialize ring packing key - fn serialize_bs_key( - &self, - bs_key: &FhewBoolKeyOwned, Elem>, - ) -> bincode::Result> { - bincode::serialize(&bs_key.compact(self.ring(), self.mod_ks())) +impl PhantomCrs { + pub const fn new(seed: ::Seed) -> Self { + Self(seed) } - /// Deserialize ring packing key - fn deserialize_bs_key( - &self, - bytes: &[u8], - ) -> bincode::Result, Elem>> - { - let bs_key_compact: FhewBoolKeyCompact = bincode::deserialize(bytes)?; - Ok(bs_key_compact.uncompact(self.ring(), self.mod_ks())) + pub fn from_entropy() -> Self { + Self::new(thread_rng().gen()) } - /// Serialize batched ciphertext - fn serialize_batched_ct( - &self, - ct: &FhewBoolBatchedCiphertextOwned>, - ) -> bincode::Result> { - bincode::serialize(&ct.compact(self.ring())) + fn fhew(&self) -> FhewBoolMpiCrs { + FhewBoolMpiCrs::new(StdRng::from_hierarchical_seed(self.0, &[0]).gen()) } - /// Deserialize batched ciphertext - fn deserialize_batched_ct( - &self, - bytes: &[u8], - ) -> bincode::Result>> { - let ct_compact: FhewBoolBatchedCiphertext = bincode::deserialize(bytes)?; - Ok(ct_compact.uncompact(self.ring())) + fn ring_packing(&self) -> RingPackingCrs { + RingPackingCrs::new(StdRng::from_hierarchical_seed(self.0, &[1]).gen()) } +} - /// Serialize ring packing ciphertext - fn serialize_rp_ct( - &self, - ct: &FhewBoolPackedCiphertextOwned>, - ) -> bincode::Result> { - bincode::serialize(&ct.compact(self.ring_rp())) - } +type Ring = NativeRing; - /// Deserialize ring packing ciphertext - fn deserialize_rp_ct( - &self, - bytes: &[u8], - ) -> bincode::Result>> { - let ct_compact: FhewBoolPackedCiphertext = bincode::deserialize(bytes)?; - Ok(ct_compact.uncompact(self.ring_rp())) - } +type EvaluationRing = NoisyNativeRing; - /// Serialize decryption share of ring packing ciphertext - fn serialize_rp_dec_share( - &self, - dec_share: &RlweDecryptionShareListOwned>, - ) -> bincode::Result> { - bincode::serialize(&dec_share.compact(self.ring_rp())) - } +type KeySwitchMod = NonNativePowerOfTwo; - /// Deserialize decryption share of ring packing ciphertext - fn deserialize_rp_dec_share( - &self, - bytes: &[u8], - ) -> bincode::Result>> { - let dec_share_compact: RlweDecryptionShareList = bincode::deserialize(bytes)?; - Ok(dec_share_compact.uncompact(self.ring_rp())) - } -} +type PackingRing = PrimeRing; #[derive(Clone, Debug)] -pub struct PhantomNativeOps { - param: FhewBoolParam, +pub struct PhantomOps { + param: PhantomParam, ring: NativeRing, mod_ks: NonNativePowerOfTwo, ring_rp: PrimeRing, } -impl PhantomOps for PhantomNativeOps { - type Ring = NativeRing; - type EvaluationRing = NoisyNativeRing; - type KeySwitchMod = NonNativePowerOfTwo; - type PackingRing = PrimeRing; - - fn new(param: FhewBoolParam) -> Self { +impl PhantomOps { + pub fn new(param: PhantomParam) -> Self { Self { param, ring: RingOps::new(param.modulus, param.ring_size), mod_ks: ModulusOps::new(param.lwe_modulus), - ring_rp: RingOps::new(Modulus::Prime(2305843009213554689), param.ring_size), + ring_rp: RingOps::new(param.ring_packing_modulus.unwrap(), param.ring_size), } } - fn param(&self) -> &FhewBoolParam { + pub fn param(&self) -> &PhantomParam { &self.param } - fn ring(&self) -> &Self::Ring { - &self.ring + pub fn fhew_param(&self) -> &FhewBoolParam { + self.param() } - fn mod_ks(&self) -> &Self::KeySwitchMod { - &self.mod_ks - } - - fn ring_rp(&self) -> &Self::PackingRing { - &self.ring_rp - } - - fn pack<'a>( - &self, - rp_key: &RingPackingKeyOwned<::EvalPrep>, - cts: impl IntoIterator>>, - ) -> FhewBoolPackedCiphertextOwned> { - FhewBoolPackedCiphertext::pack_ms(self.ring(), self.ring_rp(), rp_key, cts) - } -} - -#[derive(Clone, Debug)] -pub struct PhantomPrimeOps { - param: FhewBoolParam, - ring: PrimeRing, - mod_ks: NonNativePowerOfTwo, -} - -impl PhantomOps for PhantomPrimeOps { - type Ring = PrimeRing; - type EvaluationRing = NoisyPrimeRing; - type KeySwitchMod = NonNativePowerOfTwo; - type PackingRing = PrimeRing; - - fn new(param: FhewBoolParam) -> Self { - Self { - param, - ring: RingOps::new(param.modulus, param.ring_size), - mod_ks: ModulusOps::new(param.lwe_modulus), + pub fn ring_packing_param(&self) -> RingPackingParam { + RingPackingParam { + modulus: self + .param() + .ring_packing_modulus + .unwrap_or_else(|| self.param().modulus), + ring_size: self.param().ring_size, + sk_distribution: self.param().sk_distribution, + noise_distribution: self.param().noise_distribution, + auto_decomposition_param: self.param().ring_packing_auto_decomposition_param, } } - fn param(&self) -> &FhewBoolParam { - &self.param - } - - fn ring(&self) -> &Self::Ring { + pub fn ring(&self) -> &Ring { &self.ring } - fn mod_ks(&self) -> &Self::KeySwitchMod { + pub fn mod_ks(&self) -> &KeySwitchMod { &self.mod_ks } - fn ring_rp(&self) -> &Self::PackingRing { - &self.ring - } - - fn pack<'a>( - &self, - rp_key: &RingPackingKeyOwned<::EvalPrep>, - cts: impl IntoIterator>>, - ) -> FhewBoolPackedCiphertextOwned> { - FhewBoolPackedCiphertext::pack(self.ring_rp(), rp_key, cts) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct PhantomCrs(::Seed); - -impl PhantomCrs { - pub fn new(seed: ::Seed) -> Self { - Self(seed) - } - - pub fn from_entropy() -> Self { - Self::new(thread_rng().gen()) - } - - fn fhew(&self) -> FhewBoolMpiCrs { - FhewBoolMpiCrs::new(StdRng::from_hierarchical_seed(self.0, &[0]).gen()) - } - - fn ring_packing(&self) -> RingPackingCrs { - RingPackingCrs::new(StdRng::from_hierarchical_seed(self.0, &[1]).gen()) - } -} - -#[derive(Clone, Debug)] -pub struct PhantomClient { - param: FhewBoolMpiParam, - crs: PhantomCrs, - ops: O, - share_idx: usize, - seed: ::Seed, - pk: Option>>, -} - -impl Deref for PhantomClient { - type Target = O; - - fn deref(&self) -> &Self::Target { - &self.ops - } -} - -impl PhantomClient { - pub fn new( - param: FhewBoolMpiParam, - crs: PhantomCrs, - share_idx: usize, - seed: ::Seed, - pk_bytes: Option<&[u8]>, - ) -> bincode::Result { - let mut client = Self { - param, - crs, - ops: O::new(*param), - share_idx, - seed, - pk: None, - }; - if let Some(pk_bytes) = pk_bytes { - client.with_pk(client.deserialize_pk(pk_bytes)?); - } - Ok(client) - } - - /// Returns seed. - pub fn seed(&self) -> ::Seed { - self.seed + pub fn ring_rp(&self) -> &PackingRing { + &self.ring_rp } - fn sk(&self) -> RlweSecretKeyOwned { - RlweSecretKey::sample( - self.param.ring_size, - self.param.sk_distribution, - &mut StdRng::from_hierarchical_seed(self.seed, &[0, 0]), + pub fn sk_gen(&self, mut rng: StdRng) -> PhantomSk { + PhantomSk::sample( + self.param().ring_size, + self.param().sk_distribution, + &mut rng, ) } - fn sk_ks(&self) -> LweSecretKeyOwned { - LweSecretKey::sample( - self.param.lwe_dimension, - self.param.lwe_sk_distribution, - &mut StdRng::from_hierarchical_seed(self.seed, &[0, 1]), + pub fn sk_ks_gen(&self, mut rng: StdRng) -> PhantomSkKs { + PhantomSkKs::sample( + self.param().lwe_dimension, + self.param().lwe_sk_distribution, + &mut rng, ) } - /// Returns aggregated public key. - /// - /// # Panics - /// - /// Panics if `pk` is not given in [`Self::new`], or [`Self::with_pk`] is - /// not invoked yet. - pub fn pk(&self) -> &RlwePublicKeyOwned> { - self.pk.as_ref().unwrap() - } - - /// Generate public key share. - pub fn pk_share_gen(&self) -> SeededRlwePublicKeyOwned> { - let mut pk = SeededRlwePublicKey::allocate(self.param.ring_size); + pub fn pk_share_gen( + &self, + crs: &PhantomCrs, + sk: &PhantomSk, + mut rng: StdRng, + ) -> PhantomPkShare { + let mut pk = PhantomPkShare::allocate(self.param().ring_size); pk_share_gen( self.ring(), &mut pk, - &self.param, - &self.crs.fhew(), - &self.sk(), - &mut StdRng::from_hierarchical_seed(self.seed, &[1, 0]), + self.param(), + &crs.fhew(), + sk, + &mut rng, ); pk } - /// Generate ring packing share. - pub fn rp_key_share_gen(&self) -> RingPackingKeyShareOwned> { - let mut rp_key = RingPackingKeyShareOwned::allocate(self.ring_packing_param()); + pub fn rp_key_share_gen( + &self, + crs: &PhantomCrs, + sk: &PhantomSk, + mut rng: StdRng, + ) -> PhantomRpKeyShare { + let mut rp_key = PhantomRpKeyShare::allocate(self.ring_packing_param()); rp_key_share_gen( self.ring_rp(), &mut rp_key, - &self.crs.ring_packing(), - &self.sk(), - &mut StdRng::from_hierarchical_seed(self.seed, &[1, 1]), + &crs.ring_packing(), + sk, + &mut rng, ); rp_key } - /// Set aggregated public key. - pub fn with_pk(&mut self, pk: RlwePublicKeyOwned>) { - self.pk = Some(pk.cloned()); - } - - /// Generate bootstrapping key share. - /// - /// # Panics - /// - /// Panics if `pk` is not given in [`Self::new`], or [`Self::with_pk`] is - /// not invoked yet. pub fn bs_key_share_gen( &self, - ) -> FhewBoolMpiKeyShareOwned, Elem> { - let mut bs_key_share = FhewBoolMpiKeyShareOwned::allocate(self.param, self.share_idx); + crs: &PhantomCrs, + share_idx: usize, + sk: &PhantomSk, + pk: &PhantomPk, + sk_ks: &PhantomSkKs, + mut rng: StdRng, + ) -> PhantomBsKeyShare { + let mut bs_key_share = PhantomBsKeyShare::allocate(**self.param(), share_idx); bs_key_share_gen( self.ring(), self.mod_ks(), &mut bs_key_share, - &self.crs.fhew(), - &self.sk(), - self.pk(), - &self.sk_ks(), - &mut StdRng::from_hierarchical_seed(self.seed, &[1, 2]), + &crs.fhew(), + sk, + pk, + sk_ks, + &mut rng, ); bs_key_share } - /// Batched encrypt bits by public key. - /// - /// # Panics - /// - /// Panics if `pk` is not given in [`Self::new`], or [`Self::with_pk`] is - /// not invoked yet. pub fn batched_pk_encrypt( &self, + pk: &PhantomPk, ms: impl IntoIterator, - ) -> FhewBoolBatchedCiphertextOwned> { - self.ops.batched_pk_encrypt(self.pk(), ms) - } - - /// Generate decryption share of ring packing ciphertext. - pub fn rp_decrypt_share( - &self, - ct: &FhewBoolPackedCiphertextOwned>, - ) -> RlweDecryptionShareListOwned> { - ct.decrypt_share( - &self.param, - self.ring_rp(), - self.sk().as_view(), - &mut StdLweRng::from_entropy(), + ) -> PhantomBatchedCt { + PhantomBatchedCt::pk_encrypt( + self.fhew_param(), + self.ring(), + pk, + ms, + &mut LweRng::new(StdRng::from_entropy(), StdRng::from_entropy()), ) } - /// Serialize aggregated public key. - pub fn serialize_pk(&self) -> bincode::Result> { - self.ops.serialize_pk(self.pk()) - } -} - -#[derive(Clone, Debug)] -pub struct PhantomServer { - param: FhewBoolMpiParam, - crs: PhantomCrs, - ops: O, - pk: Option>>, - rp_key: Option>>, - rp_key_prep: Option::EvalPrep>>, - bs_key: Option, Elem>>, - evaluator: Option>, -} - -impl Deref for PhantomServer { - type Target = O; - - fn deref(&self) -> &Self::Target { - &self.ops - } -} - -impl PhantomServer { - /// Returns server. - pub fn new( - param: FhewBoolMpiParam, - crs: PhantomCrs, - pk_bytes: Option<&[u8]>, - rp_key_bytes: Option<&[u8]>, - bs_key_bytes: Option<&[u8]>, - ) -> bincode::Result { - let mut server = Self { - param, - crs, - ops: O::new(*param), - pk: None, - rp_key: None, - rp_key_prep: None, - bs_key: None, - evaluator: None, - }; - if let Some(pk_bytes) = pk_bytes { - server.with_pk(server.deserialize_pk(pk_bytes)?); - } - if let Some(rp_key_bytes) = rp_key_bytes { - server.with_rp_key(server.deserialize_rp_key(rp_key_bytes)?); - } - if let Some(bs_key_bytes) = bs_key_bytes { - server.with_bs_key(server.deserialize_bs_key(bs_key_bytes)?); - } - Ok(server) - } - - /// Returns parameter. - pub fn param(&self) -> &FhewBoolMpiParam { - &self.param - } - - /// Returns common reference string. - pub fn crs(&self) -> &PhantomCrs { - &self.crs - } - - /// Returns aggregated public key. - /// - /// # Panics - /// - /// Panics if `pk` is not given in [`Self::new`], or - /// [`Self::aggregate_pk_shares`] and [`Self::with_pk`] are not invoked yet. - pub fn pk(&self) -> &RlwePublicKeyOwned> { - self.pk.as_ref().unwrap() - } - - /// Returns aggregated ring packing key. - /// - /// # Panics - /// - /// Panics if `rp_key` is not given in [`Self::new`], or - /// [`Self::aggregate_rp_key_shares`] and [`Self::with_rp_key`] is not - /// invoked yet. - pub fn rp_key(&self) -> &RingPackingKeyOwned> { - self.rp_key.as_ref().unwrap() - } - - fn rp_key_prep(&self) -> &RingPackingKeyOwned<::EvalPrep> { - self.rp_key_prep.as_ref().unwrap() - } - - /// Returns evaluator. - /// - /// # Panics - /// - /// Panics if `bs_key` is not given in [`Self::new`], or - /// [`Self::aggregate_bs_key_shares`] and [`Self::with_bs_key`] is not - /// invoked yet. - pub fn evaluator(&self) -> &FhewBoolEvaluator { - self.evaluator.as_ref().unwrap() + pub fn aggregate_rp_dec_shares<'a>( + &self, + ct: &PhantomPackedCt, + dec_shares: impl IntoIterator, + ) -> Vec { + ct.aggregate_decryption_shares(self.ring_rp(), dec_shares) } - /// Returns aggregated bootstrapping key. - /// - /// # Panics - /// - /// Panics if `bs_key` is not given in [`Self::new`], or - /// [`Self::aggregate_bs_key_shares`] and [`Self::with_bs_key`] is not - /// invoked yet. - pub fn bs_key(&self) -> &FhewBoolKeyOwned, Elem> { - self.bs_key.as_ref().unwrap() + pub fn aggregate_pk_shares<'a>( + &self, + crs: &PhantomCrs, + pk_shares: impl IntoIterator, + ) -> PhantomPk { + let mut pk = RlwePublicKey::allocate(self.fhew_param().ring_size); + aggregate_pk_shares(self.ring(), &mut pk, &crs.fhew(), pk_shares); + pk } - /// Set aggregated public key. - pub fn with_pk(&mut self, pk: RlwePublicKeyOwned>) { - self.pk = Some(pk) + pub fn aggregate_rp_key_shares<'a>( + &self, + crs: &PhantomCrs, + rp_key_shares: impl IntoIterator, + ) -> PhantomRpKey { + let mut rp_key = PhantomRpKey::allocate(self.ring_packing_param()); + aggregate_rp_key_shares( + self.ring_rp(), + &mut rp_key, + &crs.ring_packing(), + rp_key_shares, + ); + rp_key } - /// Set aggregated ring packing key. - pub fn with_rp_key(&mut self, rp_key: RingPackingKeyOwned>) { - let mut rp_key_prep = RingPackingKeyOwned::allocate_eval( - self.ring_packing_param(), - self.ring_rp().eval_size(), + pub fn aggregate_bs_key_shares<'a>( + &self, + crs: &PhantomCrs, + bs_key_shares: impl IntoIterator, + ) -> PhantomBsKey { + let mut bs_key = PhantomBsKey::allocate(*self.fhew_param()); + aggregate_bs_key_shares( + self.ring(), + self.mod_ks(), + &mut bs_key, + &crs.fhew(), + bs_key_shares, ); - prepare_rp_key(self.ring_rp(), &mut rp_key_prep, &rp_key); - self.rp_key = Some(rp_key); - self.rp_key_prep = Some(rp_key_prep); + bs_key } - /// Set aggregated bootstrapping key. - pub fn with_bs_key( - &mut self, - bs_key: FhewBoolKeyOwned, Elem>, - ) { - let bs_key_prep = { - let ring: O::EvaluationRing = RingOps::new(self.param.modulus, self.param.ring_size); - let mut bs_key_prep = FhewBoolKeyOwned::allocate_eval(*self.param, ring.eval_size()); - prepare_bs_key(&ring, &mut bs_key_prep, &bs_key); - bs_key_prep - }; - self.bs_key = Some(bs_key); - self.evaluator = Some(FhewBoolEvaluator::new(bs_key_prep)); + pub fn aggregate_rp_decryption_shares<'a>( + &self, + ct: &PhantomPackedCt, + dec_shares: impl IntoIterator, + ) -> Vec { + ct.aggregate_decryption_shares(self.ring_rp(), dec_shares) } - /// Aggregate public key shares and set it as aggregated public key. - pub fn aggregate_pk_shares(&mut self, pk_shares: &[SeededRlwePublicKeyOwned>]) { - let crs = self.crs.fhew(); - let mut pk = RlwePublicKey::allocate(self.param.ring_size); - aggregate_pk_shares(self.ring(), &mut pk, &crs, pk_shares); - self.with_pk(pk); + pub fn prepare_rp_key(&self, rp_key: &PhantomRpKey) -> PhantomRpKeyPrep { + let mut rp_key_prep = + PhantomRpKeyPrep::allocate_eval(self.ring_packing_param(), self.ring_rp().eval_size()); + prepare_rp_key(self.ring_rp(), &mut rp_key_prep, rp_key); + rp_key_prep } - /// Aggregate ring packing key shares and set it as aggregated ring packing key. - pub fn aggregate_rp_key_shares( - &mut self, - rp_key_shares: &[RingPackingKeyShareOwned>], - ) { - let crs = self.crs.ring_packing(); - let mut rp_key = RingPackingKeyOwned::allocate(self.ring_packing_param()); - aggregate_rp_key_shares(self.ring_rp(), &mut rp_key, &crs, rp_key_shares); - self.with_rp_key(rp_key); + pub fn prepare_bs_key(&self, bs_key: &PhantomBsKey) -> PhantomBsKeyPrep { + let ring: EvaluationRing = RingOps::new(self.param.modulus, self.param.ring_size); + let mut bs_key_prep = PhantomBsKeyPrep::allocate_eval(**self.param, ring.eval_size()); + prepare_bs_key(&ring, &mut bs_key_prep, bs_key); + bs_key_prep } - /// Aggregate bootstrapping key shares and set it as aggregated bootstrapping key. - pub fn aggregate_bs_key_shares( - &mut self, - bs_key_shares: &[FhewBoolMpiKeyShareOwned, Elem>], - ) { - let crs = self.crs.fhew(); - let bs_key = { - let mut bs_key = FhewBoolKeyOwned::allocate(*self.param); - aggregate_bs_key_shares(self.ring(), self.mod_ks(), &mut bs_key, &crs, bs_key_shares); - bs_key - }; - self.with_bs_key(bs_key); + pub fn evaluator(&self, bs_key: &PhantomBsKey) -> PhantomEvaluator { + FhewBoolEvaluator::new(self.prepare_bs_key(bs_key)) } - /// Wrap batched ciphertext into [`Vec`] of [`FheBool`] for FHE evaluation. - pub fn wrap_batched_ct( + pub fn pack<'a>( &self, - ct: &FhewBoolBatchedCiphertextOwned>, - ) -> Vec>> { - ct.extract_all(self.ring()) - .into_iter() - .map(|ct| FheBool::new(self.evaluator(), ct)) - .collect_vec() + rp_key: &PhantomRpKeyPrep, + cts: impl IntoIterator>>, + ) -> PhantomPackedCt { + PhantomPackedCt::pack_ms(self.ring(), self.ring_rp(), rp_key, cts) } - /// Serialize aggregated public key. - pub fn serialize_pk(&self) -> bincode::Result> { - self.ops.serialize_pk(self.pk()) + pub fn decrypt_share(&self, sk: &PhantomSk, ct: &PhantomPackedCt) -> PhantomPackedCtDecShare { + ct.decrypt_share( + &self.param, + self.ring_rp(), + sk, + &mut LweRng::new(StdRng::from_entropy(), StdRng::from_entropy()), + ) } +} - /// Serialize aggregated ring packing key. - pub fn serialize_rp_key(&self) -> bincode::Result> { - self.ops.serialize_rp_key(self.rp_key()) - } +pub type PhantomSk = RlweSecretKeyOwned; - /// Serialize aggregated bootstrapping key. - pub fn serialize_bs_key(&self) -> bincode::Result> { - self.ops.serialize_bs_key(self.bs_key()) - } -} +pub type PhantomSkKs = LweSecretKeyOwned; -impl PhantomServer { - /// Pack LWE ciphertexts into RLWE ciphertexts (ring packing ciphertext). - pub fn pack<'a>( - &self, - cts: impl IntoIterator< - Item = &'a FhewBoolCiphertextOwned::Ring>>, - >, - ) -> FhewBoolPackedCiphertextOwned::PackingRing>> { - self.ops.pack(self.rp_key_prep(), cts) - } -} +pub type PhantomPkShare = SeededRlwePublicKeyOwned>; -impl PhantomServer { - /// Pack LWE ciphertexts into RLWE ciphertexts (ring packing ciphertext). - pub fn pack<'a>( - &self, - cts: impl IntoIterator< - Item = &'a FhewBoolCiphertextOwned::Ring>>, - >, - ) -> FhewBoolPackedCiphertextOwned::PackingRing>> { - self.ops.pack(self.rp_key_prep(), cts) - } -} +pub type PhantomPk = RlwePublicKeyOwned>; -pub(crate) trait BitOps: - BitAnd + BitOr + BitXor -{ -} +pub type PhantomRpKeyShare = RingPackingKeyShareOwned>; -impl BitOps for T where - T: BitAnd + BitOr + BitXor -{ -} +pub type PhantomRpKey = RingPackingKeyOwned>; -pub(crate) fn function_bit(a: &T, b: &T, c: &T, d: &T) -> T -where - T: for<'t> BitOps<&'t T, T>, - for<'t> &'t T: BitOps<&'t T, T>, -{ - ((a | b) & c) ^ d -} +pub type PhantomRpKeyPrep = RingPackingKeyOwned<::EvalPrep>; -#[cfg(test)] -mod tests { - use super::*; - use core::array::from_fn; - use itertools::{izip, Itertools}; - use phantom_zone_evaluator::boolean::{dev::MockBoolEvaluator, fhew::param::I_4P}; - use rand::Rng; - use std::iter::repeat_with; - - fn function<'a, E: BoolEvaluator>( - a: &[FheBool<'a, E>], - b: &[FheBool<'a, E>], - c: &[FheBool<'a, E>], - d: &[FheBool<'a, E>], - ) -> Vec> { - a.iter() - .zip(b) - .zip(c) - .zip(d) - .map(|(((a, b), c), d)| a ^ b ^ c ^ d) - .collect() - } +pub type PhantomBsKeyShare = FhewBoolMpiKeyShareOwned, Elem>; - #[test] - fn test_phantom_bits() { - let crs = PhantomCrs::from_entropy(); - let param = I_4P; - let mut server = - PhantomServer::::new(param, crs, None, None, None).unwrap(); - let mut clients = (0..param.total_shares) - .map(|share_idx| { - let seed = StdRng::from_entropy().gen(); - PhantomClient::::new(param, crs, share_idx, seed, None).unwrap() - }) - .collect_vec(); - - // Key generation round 1. - - let (pk_shares, rp_key_shares) = { - let pk_shares = clients - .iter() - .map(|client| client.serialize_pk_share(&client.pk_share_gen()).unwrap()) - .collect_vec(); - let rp_key_shares = clients - .iter() - .map(|client| { - client - .serialize_rp_key_share(&client.rp_key_share_gen()) - .unwrap() - }) - .collect_vec(); - (pk_shares, rp_key_shares) - }; - - let pk = { - server.aggregate_pk_shares( - &pk_shares - .into_iter() - .map(|bytes| server.deserialize_pk_share(&bytes).unwrap()) - .collect_vec(), - ); - server.aggregate_rp_key_shares( - &rp_key_shares - .into_iter() - .map(|bytes| server.deserialize_rp_key_share(&bytes).unwrap()) - .collect_vec(), - ); - server.serialize_pk().unwrap() - }; - - // Key generation round 2. - - let bs_key_shares = clients - .iter_mut() - .map(|client| { - client.with_pk(client.deserialize_pk(&pk).unwrap()); - client - .serialize_bs_key_share(&client.bs_key_share_gen()) - .unwrap() - }) - .collect_vec(); - - server.aggregate_bs_key_shares( - &bs_key_shares - .into_iter() - .map(|bytes| server.deserialize_bs_key_share(&bytes).unwrap()) - .collect_vec(), - ); +pub type PhantomBsKey = FhewBoolKeyOwned, Elem>; - // FHE evaluation. - - let ms: [Vec; 4] = { - let mut rng = StdRng::from_entropy(); - let n = 10; - from_fn(|_| repeat_with(|| rng.gen()).take(n).collect()) - }; - let out = { - let [a, b, c, d] = &ms - .clone() - .map(|m| m.into_iter().map(|m| m.into()).collect_vec()); - function::(a, b, c, d) - .into_iter() - .map(FheBool::into_ct) - .collect_vec() - }; - - // Run FHE evaluation. - - let run = |server: &PhantomServer, - clients: &[PhantomClient]| { - let cts = { - from_fn(|i| { - let ct = clients[i].batched_pk_encrypt(ms[i].clone()); - clients[i].serialize_batched_ct(&ct).unwrap() - }) - }; - - let ct_out = { - let [a, b, c, d] = &cts.map(|bytes| { - let ct = server.deserialize_batched_ct(&bytes).unwrap(); - server.wrap_batched_ct(&ct) - }); - function(a, b, c, d) - .into_iter() - .map(FheBool::into_ct) - .collect_vec() - }; - - let rp_ct_out = server.serialize_rp_ct(&server.pack(&ct_out)).unwrap(); - - let rp_ct_out_dec_shares = clients - .iter() - .map(|client| { - let dec_share = - client.rp_decrypt_share(&client.deserialize_rp_ct(&rp_ct_out).unwrap()); - client.serialize_rp_dec_share(&dec_share).unwrap() - }) - .collect_vec(); - - assert_eq!( - out.to_vec(), - clients[0].aggregate_rp_decryption_shares( - &clients[0].deserialize_rp_ct(&rp_ct_out).unwrap(), - &rp_ct_out_dec_shares - .iter() - .map(|dec_share| clients[0].deserialize_rp_dec_share(dec_share).unwrap()) - .collect_vec(), - ) - ); - }; - - run(&server, &clients); - - // Store aggregated keys and restart server. - - let pk_bytes = server.serialize_pk().unwrap(); - let rp_key_bytes = server.serialize_rp_key().unwrap(); - let bs_key_bytes = server.serialize_bs_key().unwrap(); - let server = PhantomServer::::new( - param, - crs, - Some(&pk_bytes), - Some(&rp_key_bytes), - Some(&bs_key_bytes), - ) - .unwrap(); +pub type PhantomBsKeyPrep = + FhewBoolKeyOwned<::EvalPrep, Elem>; - // Store seed and restart clients. +pub type PhantomCt = FhewBoolCiphertextOwned>; - let pk_bytes = clients[0].serialize_pk().unwrap(); - let seeds = clients.iter().map(|client| client.seed()).collect_vec(); - let clients = izip!(0.., seeds) - .map(|(share_idx, seed)| { - PhantomClient::::new(param, crs, share_idx, seed, Some(&pk_bytes)) - .unwrap() - }) - .collect_vec(); +pub type PhantomBatchedCt = FhewBoolBatchedCiphertextOwned>; - // Run FHE evaluation again. +pub type PhantomPackedCt = FhewBoolPackedCiphertextOwned>; - run(&server, &clients); - } -} +pub type PhantomPackedCtDecShare = RlweDecryptionShareListOwned>; + +pub type PhantomEvaluator = FhewBoolEvaluator; + +pub type PhantomBool<'a> = FheBool<'a, PhantomEvaluator>; diff --git a/src/server.rs b/src/server.rs index 955a6e4..d1af0c7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,263 +1,60 @@ -use crate::phantom::{function_bit, PhantomOps}; -use crate::types::{ - BskShareSubmission, CreateTaskSubmission, Decryptable, DecryptableBuilder, - DecryptionShareSubmission, ErrorResponse, MutexServerStorage, ParamCRS, PkShareSubmission, - ServerState, ServerStorage, Task, TaskId, TaskInputSubmission, TaskStatus, UserId, UserStorage, +use crate::{ + phantom::{PhantomCrs, PhantomParam}, + server::{app::AppState, scheduler::SchedulerState}, }; - -use itertools::Itertools; -use phantom_zone_evaluator::boolean::{fhew::param::I_4P, FheBool}; -use rocket::serde::json::Json; -use rocket::serde::msgpack::MsgPack; -use rocket::{get, post, routes, Build, Rocket, State}; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::Mutex; -use tokio::task; -use tokio::time::sleep; - -#[get("/param")] -async fn get_param(ss: &State) -> Json { - let ss = ss.lock().await; - Json(ss.get_param_crs()) -} - -#[post("/register")] -async fn register(ss: &State) -> Result, ErrorResponse> { - let mut ss = ss.lock().await; - ss.ensure(ServerState::ReadyForJoining)?; - let user = ss.add_user(); - if ss.is_users_full() { - ss.transit(ServerState::ReadyForPkShares); +use axum::{extract::connect_info::IntoMakeServiceWithConnectInfo, Router}; +use core::net::SocketAddr; +use std::sync::{Arc, Mutex}; + +pub mod app; +pub mod scheduler; +pub mod util; + +#[derive(Debug)] +pub struct ServerState { + pub app: AppState, + pub scheduler: SchedulerState, +} + +impl ServerState { + pub fn new(param: PhantomParam, crs: PhantomCrs) -> Arc> { + Arc::new(Mutex::new(Self { + app: AppState::new(param, crs), + scheduler: Default::default(), + })) } - Ok(Json(user)) } -/// Get the current server status -#[get("/status")] -async fn get_status(ss: &State) -> Result, ErrorResponse> { - let ss = ss.lock().await; - Ok(Json(ss.state.clone())) +pub fn router( + state: Arc>, +) -> IntoMakeServiceWithConnectInfo { + Router::new() + .merge(app::router()) + .merge(scheduler::router()) + .with_state(state) + .into_make_service_with_connect_info() } -/// The user submits Public Key shares -#[post("/submit_pk_and_rpk_shares", data = "", format = "msgpack")] -async fn submit_pk_and_rpk_shares( - submission: MsgPack, - ss: &State, -) -> Result, ErrorResponse> { - let mut ss = ss.lock().await; - - ss.ensure(ServerState::ReadyForPkShares)?; - - let PkShareSubmission { - user_id, - pk_share, - rpk_share, - } = submission.0; - - let user = ss.get_user(user_id)?; - user.storage = UserStorage::PkAndRpkShare(pk_share, rpk_share); - - if ss.check_pk_share_submission() { - ss.aggregate_pk_shares()?; - ss.aggregate_rpk_shares()?; - ss.transit(ServerState::ReadyForBskShares); - } - - Ok(Json(user_id)) -} - -/// The user acquires the aggregated public key -#[get("/get_aggregated_pk")] -async fn get_aggregated_pk(ss: &State) -> Result>, ErrorResponse> { - let ss = ss.lock().await; - - ss.ensure(ServerState::ReadyForBskShares)?; - - // Serialize the aggregated public key - let aggregated_pk = ss.ps.serialize_pk()?; - - Ok(Json(aggregated_pk)) -} - -/// The user submits Server key shares -#[post("/submit_bsks", data = "", format = "msgpack")] -async fn submit_bsks( - submission: MsgPack, - ss: &State, -) -> Result, ErrorResponse> { - let ss_clone = Arc::clone(&ss); - let mut ss = ss.lock().await; - - ss.ensure(ServerState::ReadyForBskShares)?; - - let BskShareSubmission { user_id, bsk_share } = submission.0; - - let user = ss.get_user(user_id)?; - user.storage = UserStorage::BskShare(Box::new(bsk_share)); - - if ss.check_bsk_share_submission() { - ss.transit(ServerState::ReadyForInputs); - println!("Derive bootstrap key"); - ss.aggregate_bsk_shares()?; - - // Spawn the background computation task - task::spawn(async move { - background_computation(ss_clone).await; - }); - } - - Ok(Json(user_id)) -} - -/// Create a new task -#[post("/create_task", data = "", format = "msgpack")] -async fn create_task( - submission: MsgPack, - ss: &State, -) -> Result, ErrorResponse> { - let mut ss = ss.lock().await; - let CreateTaskSubmission { - initiator, - required_inputs, - initiator_input, - } = submission.0; - let task_id = ss.create_task(initiator, required_inputs, initiator_input); - Ok(Json(task_id)) -} - -/// Get tasks for a user -#[get("/tasks/")] -async fn get_tasks_for_user( - user_id: UserId, - ss: &State, -) -> Result>, ErrorResponse> { - let ss = ss.lock().await; - let tasks = ss.get_tasks_for_user(user_id); - Ok(Json(tasks.into_iter().cloned().collect())) -} - -/// Submit input for a task -#[post("/submit_task_input", data = "", format = "msgpack")] -async fn submit_task_input( - submission: MsgPack, - ss: &State, -) -> Result, ErrorResponse> { - let mut ss = ss.lock().await; - let TaskInputSubmission { - task_id, - user_id, - input, - } = submission.0; - ss.add_input_to_task(task_id, user_id, input)?; - Ok(Json(())) -} - -/// Performs the background computation for tasks. -/// -/// This function continuously checks for ready tasks, processes them, -/// and updates their status. It runs indefinitely until the server is shut down. -/// -async fn background_computation(ss: MutexServerStorage) { - println!("Spawned background computation"); - let (ps, n_users) = { - let ss = ss.lock().await; - (ss.ps.clone(), ss.n_users) +#[cfg(test)] +pub mod test { + use crate::{ + phantom::{PhantomCrs, PhantomParam}, + server::{self, ServerState}, }; - let mut builder = DecryptableBuilder::new(n_users); - loop { - let task_to_process = { - let mut ss = ss.lock().await; - println!("Task queue {:?}", ss.task_queue); - ss.get_next_ready_task() - }; - - if let Some(task_id) = task_to_process { - let mut ss = ss.lock().await; - let task = ss.get_task(task_id).unwrap(); - task.status = TaskStatus::Running; - - // Perform the FHE computation here - println!("Performing computation for task {}", task_id); + use std::net::SocketAddr; + use tokio::{net::TcpListener, task::JoinHandle}; - let inputs: Vec>> = task - .inputs - .values() - .map(|cipher| { - ps.deserialize_batched_ct(cipher) - .map(|ct| ps.wrap_batched_ct(&ct)) - }) - .try_collect() - .unwrap(); + pub const TEST_PARAM: PhantomParam = PhantomParam::I_4P_60; + pub const TEST_CRS: PhantomCrs = PhantomCrs::new([0x42; 32]); - let [user_1_input, user_2_input] = inputs.try_into().unwrap(); - let [a, b]: [FheBool<_>; 2] = user_1_input.try_into().unwrap(); - let [c, d]: [FheBool<_>; 2] = user_2_input.try_into().unwrap(); - let g: FheBool<_> = function_bit(&a, &b, &c, &d); - // For now, we'll just simulate a computation by waiting and returning the input - // Should be decryptables - let g = ps.serialize_rp_ct(&ps.pack(&[g.into_ct()])).unwrap(); + pub async fn test_server() -> (JoinHandle<()>, SocketAddr) { + let listener = TcpListener::bind("localhost:0").await.unwrap(); + let server_addr = listener.local_addr().unwrap(); + tracing::debug!("listening on {server_addr}"); - // Create decryptables from the result - let decryptables = vec![builder.new_public(g.clone()), builder.new_designated(g, 1)]; // Replace with actual decryptables - ss.complete_task(task_id, decryptables).unwrap(); - } else { - // No tasks ready, sleep for a bit before checking again - sleep(Duration::from_secs(5)).await; - } + let state = ServerState::new(TEST_PARAM, TEST_CRS); + let router = server::router(state); + let handle = tokio::spawn(async { axum::serve(listener, router).await.unwrap() }); + (handle, server_addr) } } - -#[get("/decryptables/")] -async fn get_decryptables_for_user( - user_id: UserId, - ss: &State, -) -> Result>, ErrorResponse> { - let ss = ss.lock().await; - let decryptables = ss.get_decryptables_for_user(user_id); - Ok(Json( - decryptables - .into_iter() - .map(|(tid, d)| (tid, d.clone())) - .collect(), - )) -} - -#[post("/submit_decryption_share", data = "", format = "msgpack")] -async fn submit_decryption_share( - submission: MsgPack, - ss: &State, -) -> Result, ErrorResponse> { - let mut ss = ss.lock().await; - let DecryptionShareSubmission { - task_id, - decryptable_id, - user_id, - share, - } = submission.0; - ss.submit_decryption_share(task_id, decryptable_id, user_id, share)?; - Ok(Json(())) -} - -pub fn rocket(n_users: usize) -> Rocket { - let param = I_4P; - let ss = MutexServerStorage::new(Mutex::new(ServerStorage::new(param, n_users))); - - rocket::build().manage(ss).mount( - "/", - routes![ - get_param, - register, - get_status, - submit_pk_and_rpk_shares, - get_aggregated_pk, - submit_bsks, - create_task, - get_tasks_for_user, - submit_task_input, - get_decryptables_for_user, - submit_decryption_share, - ], - ) -} diff --git a/src/server/app.rs b/src/server/app.rs new file mode 100644 index 0000000..0e436a9 --- /dev/null +++ b/src/server/app.rs @@ -0,0 +1,675 @@ +use crate::{ + circuit::CircuitId, + phantom::{ + PhantomBatchedCt, PhantomBsKey, PhantomBsKeyShare, PhantomCrs, PhantomCt, PhantomOps, + PhantomPackedCt, PhantomPackedCtDecShare, PhantomParam, PhantomPk, PhantomPkShare, + PhantomRpKey, PhantomRpKeyPrep, PhantomRpKeyShare, + }, + server::{ + scheduler::{TaskId, TaskRequest, TaskResponse}, + util::{bad_request, internal_server_error, Bincode}, + ServerState, + }, + worker::WorkerKey, + Result, +}; +use anyhow::{anyhow, bail}; +use axum::{ + extract::{DefaultBodyLimit, Path, State}, + http::StatusCode, + routing::{get, post}, + Router, +}; +use core::{cell::OnceCell, mem::take}; +use itertools::{chain, izip, Itertools}; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +pub const BS_KEY_SIZE: usize = 32 << 20; + +pub type UserId = usize; + +#[derive(Clone, Debug, Default)] +struct UserState { + pk_share: OnceCell, + rp_key_share: OnceCell, + bs_key_share: OnceCell, +} + +impl UserState { + fn try_set_pk_share(&self, pk_share: PhantomPkShare) -> Result<(), String> { + self.pk_share + .set(pk_share) + .map_err(|_| "pk_share already set".to_string()) + } + + fn try_set_rp_key_share(&self, rp_key_share: PhantomRpKeyShare) -> Result<(), String> { + self.rp_key_share + .set(rp_key_share) + .map_err(|_| "rp_key_share already set".to_string()) + } + + fn try_set_bs_key_share(&self, bs_key_share: PhantomBsKeyShare) -> Result<(), String> { + self.bs_key_share + .set(bs_key_share) + .map_err(|_| "bs_key_share already set".to_string()) + } +} + +#[derive(Debug)] +struct DecryptableState { + ct_ids: Vec, + packed: PhantomPackedCt, + dec_shares: Vec>, + designated: Option, +} + +impl DecryptableState { + fn new( + ct_ids: Vec, + packed: PhantomPackedCt, + num_users: usize, + designated: Option, + ) -> Self { + Self { + ct_ids, + packed, + dec_shares: vec![None; num_users], + designated, + } + } + + fn is_waiting(&self, user_id: UserId) -> bool { + match self.designated { + Some(designated) if designated == user_id => false, + _ => self.dec_shares[user_id].is_none(), + } + } + + fn is_decryptable(&self, user_id: UserId) -> bool { + match self.designated { + Some(designated) if designated == user_id => self + .dec_shares + .iter() + .enumerate() + .all(|(idx, dec_share)| idx == designated || dec_share.is_some()), + Some(_) => false, + None => self.dec_shares.iter().all(Option::is_some), + } + } + + fn insert_dec_share( + &mut self, + user_id: UserId, + dec_share: PhantomPackedCtDecShare, + ) -> Result<()> { + if matches!(self.designated, Some(designated) if designated == user_id) { + bail!("unexpected dec share from {user_id} of designated ct") + } + if self.dec_shares[user_id].replace(dec_share).is_some() { + bail!("duplicated dec share from {user_id}") + } + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum CtId { + Demo(usize), +} + +#[derive(Debug)] +struct GameState { + round: usize, + state: HashMap, + staged: HashMap, +} + +impl GameState { + fn new(num_users: usize, ops: &PhantomOps, pk: &PhantomPk) -> Self { + let mut rng = StdRng::from_entropy(); + let state = izip!( + chain![(0..10 * (num_users + 1)).map(CtId::Demo)], + ops.batched_pk_encrypt(pk, (0..10 * (num_users + 1)).map(|_| rng.gen())) + .extract_all(ops.ring()) + ) + .collect(); + Self { + round: 0, + state, + staged: HashMap::new(), + } + } + + fn try_commit(&mut self) -> Result<(), ()> { + let ready = self.staged.len() == self.state.len() - 10; + if !ready { + return Err(()); + } + + for (key, value) in take(&mut self.staged) { + self.state.insert(key, value); + } + Ok(()) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AppStatus { + WaitingForPkShare(Vec), + WaitingForRpKeyShare(Vec), + WaitingForBsKeyShare(Vec), + AggregatingKeyShare, + InitializingGame, + PlayingGame(usize), +} + +#[derive(Debug)] +pub struct AppState { + ops: PhantomOps, + crs: PhantomCrs, + users: Vec, + pk: OnceCell, + rp_key: OnceCell, + rp_key_prep: OnceCell, + bs_key: OnceCell, + game: OnceCell, + decryptable: Vec, + scheduled: HashMap>, +} + +impl AppState { + pub(super) fn new(param: PhantomParam, crs: PhantomCrs) -> Self { + Self { + ops: PhantomOps::new(param), + crs, + users: vec![UserState::default(); param.total_shares], + pk: Default::default(), + rp_key: Default::default(), + rp_key_prep: Default::default(), + bs_key: Default::default(), + game: Default::default(), + decryptable: Default::default(), + scheduled: Default::default(), + } + } + + fn status(&self) -> AppStatus { + let pred = |user: &UserState| user.pk_share.get().is_none(); + if self.users.iter().any(pred) { + return AppStatus::WaitingForPkShare(self.users.iter().positions(pred).collect()); + } + let pred = |user: &UserState| user.rp_key_share.get().is_none(); + if self.users.iter().any(pred) { + return AppStatus::WaitingForRpKeyShare(self.users.iter().positions(pred).collect()); + } + let pred = |user: &UserState| user.bs_key_share.get().is_none(); + if self.users.iter().any(pred) { + return AppStatus::WaitingForBsKeyShare(self.users.iter().positions(pred).collect()); + } + if self.bs_key.get().is_none() { + return AppStatus::AggregatingKeyShare; + } + match self.game.get() { + Some(game) => AppStatus::PlayingGame(game.round), + None => AppStatus::InitializingGame, + } + } + + fn parse_user_id(&self, user_id: String) -> Result { + match user_id.parse::() { + Ok(user_id) if user_id < self.users.len() => Ok(user_id), + _ => Err(format!("invalid user id {user_id}")), + } + } + + fn aggregate_pk_shares(&self) { + let pk = self.ops.aggregate_pk_shares( + &self.crs, + self.users.iter().map(|user| user.pk_share.get().unwrap()), + ); + self.pk.set(pk).unwrap(); + } + + fn aggregate_rp_key_shares(&self) { + let rp_key = self.ops.aggregate_rp_key_shares( + &self.crs, + self.users + .iter() + .map(|user| user.rp_key_share.get().unwrap()), + ); + let rp_key_prep = self.ops.prepare_rp_key(&rp_key); + self.rp_key.set(rp_key).unwrap(); + self.rp_key_prep.set(rp_key_prep).unwrap(); + } + + fn aggregate_bs_key_shares(&self) { + let bs_key = self.ops.aggregate_bs_key_shares( + &self.crs, + self.users + .iter() + .map(|user| user.bs_key_share.get().unwrap()), + ); + self.bs_key.set(bs_key).unwrap(); + } + + pub fn worker_key(&self) -> Option { + Some(WorkerKey { + param: *self.ops.param(), + bs_key: self.bs_key.get().cloned()?, + rp_key: self.rp_key.get().cloned()?, + }) + } + + pub fn handle_task(&mut self, response: TaskResponse) { + let game = self.game.get_mut().unwrap(); + let output_ct_ids = self.scheduled.remove(&response.task_id).unwrap(); + izip!(output_ct_ids, response.outputs).for_each(|(ct_id, ct)| { + game.staged.insert(ct_id, ct); + }); + if game.try_commit().is_ok() { + self.make_decryptable(); + } + } + + fn initialize_game(&mut self) { + self.game + .set(GameState::new( + self.users.len(), + &self.ops, + self.pk.get().unwrap(), + )) + .unwrap(); + self.make_decryptable(); + } + + fn make_decryptable(&mut self) { + self.decryptable = chain![ + [self.pack((0..10).map(CtId::Demo), None)], + (0..self.users.len()).map(|user_id| { + self.pack( + (10 * (user_id + 1)..).take(10).map(CtId::Demo), + Some(user_id), + ) + }) + ] + .collect(); + } + + fn pack( + &self, + ct_ids: impl IntoIterator, + designated: Option, + ) -> DecryptableState { + let ct_ids = ct_ids.into_iter().collect_vec(); + let packed = self.ops.pack( + self.rp_key_prep.get().unwrap(), + ct_ids + .iter() + .map(|ct_id| &self.game.get().unwrap().state[ct_id]), + ); + DecryptableState::new(ct_ids, packed, self.users.len(), designated) + } + + fn insert_dec_share( + &mut self, + decryptable_id: usize, + user_id: UserId, + dec_share: PhantomPackedCtDecShare, + ) -> Result<()> { + self.decryptable + .get_mut(decryptable_id) + .ok_or(anyhow!("invalid decryptable id {decryptable_id}"))? + .insert_dec_share(user_id, dec_share) + } +} + +pub fn router() -> Router>> { + Router::new() + .route("/status", get(get_status)) + .route("/setup", get(get_setup)) + .route("/pk", get(get_pk)) + .nest( + "/users/:user_id", + Router::new() + .route("/pk_share", post(create_user_pk_share)) + .route("/rp_key_share", post(create_user_rp_key_share)) + .route("/bs_key_share", post(create_user_bs_key_share)) + .route("/tasks", get(get_user_tasks).put(update_user_tasks)) + .route("/decryptables", get(get_user_decryptables)) + .route("/actions", post(create_user_action)) + .layer(DefaultBodyLimit::max(BS_KEY_SIZE)), + ) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetStatusResponse { + pub status: AppStatus, +} + +async fn get_status( + State(state): State>>, +) -> Result, (StatusCode, String)> { + let ServerState { app, .. } = &*state.lock().unwrap(); + Ok(Bincode(GetStatusResponse { + status: app.status(), + })) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetSetupResponse { + pub param: PhantomParam, + pub crs: PhantomCrs, +} + +async fn get_setup( + State(state): State>>, +) -> Result, (StatusCode, String)> { + let ServerState { app, .. } = &*state.lock().unwrap(); + Ok(Bincode(GetSetupResponse { + param: *app.ops.param(), + crs: app.crs, + })) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetPkResponse { + pub pk: PhantomPk, +} + +async fn get_pk( + State(state): State>>, +) -> Result, (StatusCode, String)> { + let ServerState { app, .. } = &*state.lock().unwrap(); + match app.pk.get() { + Some(pk) => Ok(Bincode(GetPkResponse { pk: pk.clone() })), + None => Err(bad_request("pk is not ready yet")), + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateUserPkShareRequest { + pub pk_share: PhantomPkShare, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateUserPkShareResponse {} + +async fn create_user_pk_share( + Path(user_id): Path, + State(state): State>>, + Bincode(CreateUserPkShareRequest { pk_share }): Bincode, +) -> Result, (StatusCode, String)> { + let all_set = { + let ServerState { app, .. } = &*state.lock().unwrap(); + + let user_id = app.parse_user_id(user_id).map_err(bad_request)?; + app.users[user_id] + .try_set_pk_share(pk_share) + .map_err(bad_request)?; + + app.users.iter().all(|user| user.pk_share.get().is_some()) + }; + + if all_set { + tokio::task::spawn_blocking(move || { + let state = state.lock().unwrap(); + state.app.aggregate_pk_shares(); + }); + } + + Ok(Bincode(CreateUserPkShareResponse {})) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateUserRpKeyShareRequest { + pub rp_key_share: PhantomRpKeyShare, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateUserRpKeyShareResponse {} + +async fn create_user_rp_key_share( + Path(user_id): Path, + State(state): State>>, + Bincode(CreateUserRpKeyShareRequest { rp_key_share }): Bincode, +) -> Result, (StatusCode, String)> { + let all_set = { + let ServerState { app, .. } = &*state.lock().unwrap(); + + let user_id = app.parse_user_id(user_id).map_err(bad_request)?; + app.users[user_id] + .try_set_rp_key_share(rp_key_share) + .map_err(bad_request)?; + + app.users + .iter() + .all(|user| user.rp_key_share.get().is_some()) + }; + + if all_set { + tokio::task::spawn_blocking(move || state.lock().unwrap().app.aggregate_rp_key_shares()); + } + + Ok(Bincode(CreateUserRpKeyShareResponse {})) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateUserBsKeyShareRequest { + pub bs_key_share: PhantomBsKeyShare, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateUserBsKeyShareResponse {} + +async fn create_user_bs_key_share( + Path(user_id): Path, + State(state): State>>, + Bincode(CreateUserBsKeyShareRequest { bs_key_share }): Bincode, +) -> Result, (StatusCode, String)> { + let all_set = { + let ServerState { app, .. } = &*state.lock().unwrap(); + + let user_id = app.parse_user_id(user_id).map_err(bad_request)?; + app.users[user_id] + .try_set_bs_key_share(bs_key_share) + .map_err(bad_request)?; + + app.users + .iter() + .all(|user| user.bs_key_share.get().is_some()) + }; + if all_set { + tokio::task::spawn_blocking(move || { + let mut state = state.lock().unwrap(); + state.app.aggregate_bs_key_shares(); + let worker_key = state.app.worker_key().unwrap(); + state.scheduler.set_key(worker_key); + state.app.initialize_game(); + }); + } + + Ok(Bincode(CreateUserBsKeyShareResponse {})) +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Decryptable { + pub ct_ids: Vec, + pub packed: PhantomPackedCt, + pub dec_shares: Vec, +} + +impl From<&DecryptableState> for Decryptable { + fn from(value: &DecryptableState) -> Self { + Self { + ct_ids: value.ct_ids.clone(), + packed: value.packed.clone(), + dec_shares: value.dec_shares.iter().flatten().cloned().collect(), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetUserDecryptablesResponse { + pub round: usize, + pub decryptables: Vec, +} + +async fn get_user_decryptables( + Path(user_id): Path, + State(state): State>>, +) -> Result, (StatusCode, String)> { + let ServerState { app, .. } = &*state.lock().unwrap(); + + let user_id = app.parse_user_id(user_id).map_err(bad_request)?; + let Some(game) = app.game.get() else { + return Err(bad_request("game has started yet")); + }; + + let decryptables = app + .decryptable + .iter() + .filter(|decryptable| decryptable.is_decryptable(user_id)) + .map(Decryptable::from) + .collect(); + + Ok(Bincode(GetUserDecryptablesResponse { + round: game.round, + decryptables, + })) +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Task { + CreateDecShare { + decryptable_id: usize, + packed: Option, + dec_share: Option, + }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetUserTasksResponse { + pub tasks: Vec, +} + +async fn get_user_tasks( + Path(user_id): Path, + State(state): State>>, +) -> Result, (StatusCode, String)> { + let ServerState { app, .. } = &*state.lock().unwrap(); + + let user_id = app.parse_user_id(user_id).map_err(bad_request)?; + if app.game.get().is_none() { + return Err(bad_request("game has started yet")); + }; + + let tasks = app + .decryptable + .iter() + .enumerate() + .filter(|(_, decryptable)| decryptable.is_waiting(user_id)) + .map(|(decryptable_id, decryptable)| Task::CreateDecShare { + decryptable_id, + packed: Some(decryptable.packed.clone()), + dec_share: None, + }) + .collect(); + + Ok(Bincode(GetUserTasksResponse { tasks })) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UpdateUserTasksRequest { + pub tasks: Vec, +} + +async fn update_user_tasks( + Path(user_id): Path, + State(state): State>>, + Bincode(UpdateUserTasksRequest { tasks }): Bincode, +) -> Result, (StatusCode, String)> { + let ServerState { app, .. } = &mut *state.lock().unwrap(); + + let user_id = app.parse_user_id(user_id).map_err(bad_request)?; + if app.game.get().is_none() { + return Err(bad_request("game has started yet")); + }; + + for task in tasks { + match task { + Task::CreateDecShare { + decryptable_id, + dec_share, + .. + } => app + .insert_dec_share( + decryptable_id, + user_id, + dec_share.ok_or("missing dec_share").map_err(bad_request)?, + ) + .map_err(bad_request)?, + } + } + + Ok(Bincode(())) +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Action { + // Demo action to trigger Demo circuit with inputs. + Demo(PhantomBatchedCt), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateUserActionRequest { + pub action: Action, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateUserActionResponse {} + +async fn create_user_action( + Path(user_id): Path, + State(state): State>>, + Bincode(CreateUserActionRequest { action }): Bincode, +) -> Result, (StatusCode, String)> { + let ServerState { app, scheduler } = &mut *state.lock().unwrap(); + + let user_id = app.parse_user_id(user_id).map_err(bad_request)?; + let Some(game) = app.game.get() else { + return Err(bad_request("game has started yet")); + }; + + match action { + Action::Demo(ct_batched) => { + let task_id = TaskId::new_v4(); + // Input CtId of the demo circuit. + let input_ct_ids = (10 * (user_id + 1)..) + .take(10) + .map(CtId::Demo) + .collect_vec(); + // Output CtId to be updated, same as input for the demo circuit. + let output_ct_ids = input_ct_ids.clone(); + // Remember the output_ct_ids to update later. + app.scheduled.insert(task_id, output_ct_ids); + scheduler + .schedule_task_request(TaskRequest { + task_id, + circuit_id: CircuitId::Demo, + inputs: chain![ + input_ct_ids.iter().map(|idx| game.state[idx].clone()), + ct_batched.extract_all(app.ops.ring()) + ] + .collect(), + }) + .map_err(internal_server_error)? + } + }; + + Ok(Bincode(CreateUserActionResponse {})) +} diff --git a/src/server/scheduler.rs b/src/server/scheduler.rs new file mode 100644 index 0000000..69699f4 --- /dev/null +++ b/src/server/scheduler.rs @@ -0,0 +1,204 @@ +use crate::{ + circuit::CircuitId, + phantom::PhantomCt, + server::ServerState, + worker::{WorkerKey, WorkerMessage}, + Result, +}; +use anyhow::bail; +use axum::{ + extract::{ + ws::{Message, WebSocket}, + ConnectInfo, State, WebSocketUpgrade, + }, + response::IntoResponse, + routing::any, + Router, +}; +use futures_util::{ + stream::{SplitSink, SplitStream}, + SinkExt, StreamExt, +}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + net::SocketAddr, + sync::{Arc, Mutex}, +}; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; + +pub type TaskId = uuid::Uuid; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct TaskRequest { + pub task_id: TaskId, + pub circuit_id: CircuitId, + pub inputs: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TaskResponse { + pub task_id: TaskId, + pub outputs: Vec, +} + +#[derive(Debug)] +struct WorkerState { + tx: UnboundedSender, + scheduled: HashMap, +} + +impl WorkerState { + fn new(tx: UnboundedSender) -> Self { + Self { + tx, + scheduled: HashMap::new(), + } + } + + fn set_key(&mut self, key: WorkerKey) { + self.tx.send(WorkerMessage::SetKey(Box::new(key))).unwrap(); + } + + fn send_task(&mut self, request: TaskRequest) { + self.scheduled.insert(request.task_id, request.clone()); + self.tx.send(WorkerMessage::TaskRequest(request)).unwrap(); + } +} + +#[derive(Debug, Default)] +pub struct SchedulerState { + task_counter: usize, + workers: Vec, +} + +impl SchedulerState { + fn register(&mut self, tx: UnboundedSender, key: Option) { + let mut worker = WorkerState::new(tx); + if let Some(key) = key { + worker.set_key(key); + } + self.workers.push(worker); + + tracing::debug!("registered, current worker count: {}", self.workers.len()); + } + + fn unregister(&mut self, tx: UnboundedSender) { + let idx = self + .workers + .iter() + .position(|worker| worker.tx.same_channel(&tx)) + .unwrap(); + let worker = self.workers.remove(idx); + + tracing::debug!("unregistered, current worker count: {}", self.workers.len()); + + worker.scheduled.into_values().for_each(|request| { + let _ = self.schedule_task_request(request); + }) + } + + fn finish_task(&mut self, task_id: TaskId) { + self.workers.iter_mut().for_each(|worker| { + worker.scheduled.remove(&task_id); + }); + + tracing::debug!("finished task {task_id:?}"); + } + + pub fn set_key(&mut self, key: WorkerKey) { + self.workers + .iter_mut() + .for_each(|worker| worker.set_key(key.clone())); + } + + pub fn schedule_task_request(&mut self, request: TaskRequest) -> Result<()> { + if self.workers.is_empty() { + tracing::error!("failed to schedule task because no worker available"); + bail!("no worker available"); + } + + let idx = self.task_counter % self.workers.len(); + let task_id = request.task_id; + self.task_counter += 1; + self.workers[idx].send_task(request); + + tracing::debug!("scheduled task {task_id:?} to workers[{idx}]"); + + Ok(()) + } +} + +pub fn router() -> Router>> { + Router::new().route("/ws", any(ws)) +} + +async fn ws( + State(state): State>>, + wsu: WebSocketUpgrade, + ConnectInfo(addr): ConnectInfo, +) -> impl IntoResponse { + wsu.on_upgrade(move |mut ws| async move { + if let Err(err) = ws.send(Message::Ping(Vec::new())).await { + tracing::warn!("failed to send ping, {err}"); + return; + } + if let Some(Err(err)) = ws.recv().await { + tracing::warn!("failed to receive pong, {err}"); + return; + } + tracing::debug!("connected to worker {addr}"); + + let (ws_tx, ws_rx) = ws.split(); + + let (tx, rx) = unbounded_channel(); + { + let mut state = state.lock().unwrap(); + let key = state.app.worker_key(); + state.scheduler.register(tx.clone(), key); + } + + tokio::select! { + _ = tokio::spawn(handle_request(ws_tx, rx)) => {}, + _ = tokio::spawn(handle_response(state.clone(), ws_rx)) => {}, + }; + + tracing::debug!("unconnected to worker {addr}"); + + let mut state = state.lock().unwrap(); + state.scheduler.unregister(tx); + }) +} + +async fn handle_request( + mut ws_tx: SplitSink, + mut rx: UnboundedReceiver, +) { + while let Some(request) = rx.recv().await { + let bytes = bincode::serialize(&request).unwrap(); + if let Err(err) = ws_tx.send(Message::Binary(bytes)).await { + tracing::error!("failed to send task request to worker, {err}"); + break; + } + } +} + +async fn handle_response(state: Arc>, mut ws_rx: SplitStream) { + while let Some(Ok(msg)) = ws_rx.next().await { + match msg { + Message::Binary(bytes) => { + let worker_msg: WorkerMessage = bincode::deserialize(&bytes).unwrap(); + match worker_msg { + WorkerMessage::TaskResponse(response) => { + let mut state = state.lock().unwrap(); + state.scheduler.finish_task(response.task_id); + state.app.handle_task(response); + } + _ => tracing::warn!("received unexpected worker msg {worker_msg:?}"), + } + } + Message::Close(_) => break, + _ => tracing::warn!("received unexpected msg {msg:?}"), + } + } +} diff --git a/src/server/util.rs b/src/server/util.rs new file mode 100644 index 0000000..6543daa --- /dev/null +++ b/src/server/util.rs @@ -0,0 +1,46 @@ +use axum::{ + async_trait, + body::Bytes, + extract::{FromRequest, Request}, + response::{IntoResponse, Response}, +}; +use hyper::StatusCode; +use serde::{de::DeserializeOwned, Serialize}; + +pub struct Bincode(pub T); + +#[async_trait] +impl FromRequest for Bincode +where + S: Send + Sync, + T: DeserializeOwned, +{ + type Rejection = (StatusCode, String); + + async fn from_request(req: Request, state: &S) -> Result { + let bytes = Bytes::from_request(req, state) + .await + .map_err(internal_server_error)?; + Ok(Self(bincode::deserialize(&bytes).map_err(bad_request)?)) + } +} + +impl IntoResponse for Bincode +where + T: Serialize, +{ + fn into_response(self) -> Response { + match bincode::serialize(&self.0) { + Ok(bytes) => bytes.into_response(), + Err(err) => internal_server_error(err).into_response(), + } + } +} + +pub fn bad_request(msg: impl ToString) -> (StatusCode, String) { + (StatusCode::BAD_REQUEST, msg.to_string()) +} + +pub fn internal_server_error(msg: impl ToString) -> (StatusCode, String) { + (StatusCode::INTERNAL_SERVER_ERROR, msg.to_string()) +} diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..0f5b1f1 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,161 @@ +use crate::{ + client::Client, + server::test::{test_server, TEST_PARAM}, + user::User, + worker, +}; +use futures_util::{ + future::{join_all, try_join_all}, + FutureExt, TryFuture, TryFutureExt, +}; +use itertools::{chain, Itertools}; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::{future::Future, sync::Arc, time::Duration}; +use tokio::{spawn, sync::broadcast, time::sleep}; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +#[tokio::test] +async fn e2e() { + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::from_default_env() + .add_directive(LevelFilter::INFO.into()), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let (server_handle, server_addr) = test_server().await; + + tracing::info!("server listening on on {server_addr}"); + + let (shutdown_tx, _) = broadcast::channel(4); + let worker_handles = (0..TEST_PARAM.total_shares) + .map(|_| { + let shutdown_rx = shutdown_tx.subscribe(); + spawn(worker::run(format!("ws://{server_addr}/ws"), shutdown_rx)) + }) + .collect_vec(); + + tracing::info!("{} workers connected to server", worker_handles.len()); + + let client = &Client::new(format!("http://{server_addr}")); + let users = (0..TEST_PARAM.total_shares) + .map(|user_id| async move { + User::new(client.clone(), user_id, StdRng::from_entropy().gen()) + .await + .map(Arc::new) + }) + .try_join_vec() + .await + .unwrap(); + + tracing::info!("{} users participating key generation", users.len()); + + users + .iter() + .try_join_for_each(|user| user.participate_key_gen()) + .await + .unwrap(); + users + .iter() + .try_join_for_each(|user| user.wait_until_game_ready()) + .await + .unwrap(); + + tracing::info!("key generation finished"); + + tracing::info!("users participating decryption continuously"); + + let user_decryption_handles = users + .clone() + .into_iter() + .map(|user| tokio::spawn(async move { user.participate_decryption().await.unwrap() })) + .collect_vec(); + + sleep(Duration::from_secs(1)).await; + + let print_decryptable = || async { + tracing::info!("users getting decryptable and decrypt"); + + users + .iter() + .join_for_each(|user| async { + assert!(client + .get_user_tasks(user.user_id()) + .await + .unwrap() + .tasks + .is_empty()); + let decrypted = user + .get_decryptables() + .await + .unwrap() + .into_iter() + .map(|decryptable| { + user.decrypt(&decryptable) + .into_iter() + .map(|bit| format!("{}", bit as usize)) + .join("") + }) + .collect_vec(); + tracing::info!("user {} got {decrypted:?}", user.user_id()) + }) + .await; + }; + + print_decryptable().await; + + tracing::info!("users submit their action (for demo it flips the designated decryptable)",); + + users + .iter() + .try_join_for_each(|user| user.demo([true; 10])) + .await + .unwrap(); + + sleep(Duration::from_secs(1)).await; + + print_decryptable().await; + + shutdown_tx.send(()).unwrap(); + try_join_all(worker_handles).await.unwrap(); + + chain![user_decryption_handles, [server_handle]] + .join_for_each(|handle| async { + handle.abort(); + assert!(handle.await.unwrap_err().is_cancelled()); + }) + .await; +} + +pub trait ItertoolsExt: Itertools { + fn try_join_vec( + self, + ) -> impl Future::Ok>, ::Error>> + where + Self: Sized, + Self::Item: TryFuture, + { + try_join_all(self) + } + + fn join_for_each(self, f: impl Fn(Self::Item) -> F) -> impl Future + where + Self: Sized, + { + join_all(self.map(f)).map(|_| ()) + } + + fn try_join_for_each( + self, + f: impl Fn(Self::Item) -> F, + ) -> impl Future> + where + Self: Sized, + { + try_join_all(self.map(f)).map_ok(|_| ()) + } +} + +impl ItertoolsExt for I {} diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index ccf5b0b..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::client::Wallet; -use crate::server::rocket; -use crate::types::TaskStatus; - -use std::sync::Arc; -use tokio::sync::{oneshot, Mutex}; -use tokio::time::{sleep, Duration}; - -static N_USERS: usize = 2; - -async fn setup_server(shutdown_rx: oneshot::Receiver<()>) { - tokio::spawn(async move { - rocket(N_USERS) - .launch() - .await - .expect("server failed to start"); - }); - - let _ = shutdown_rx.await; -} - -#[rocket::async_test] -async fn test_fullflow() { - let (shutdown_tx, shutdown_rx) = oneshot::channel(); - - let server_handle = tokio::spawn(setup_server(shutdown_rx)); - - // Give the server a moment to start up - sleep(Duration::from_secs(1)).await; - - let url = "http://localhost:5566"; - let user1 = Wallet::new(url); - let user2 = Wallet::new(url); - let user1_setup = user1.run_setup(); - let user2_setup = user2.run_setup(); - - let (user1, user2) = futures::join!(user1_setup, user2_setup); - - let user1 = Arc::new(Mutex::new(user1.unwrap())); - let user2 = Arc::new(Mutex::new(user2.unwrap())); - - // Start background tasks for both users - let user1_clone = Arc::clone(&user1); - let user2_clone = Arc::clone(&user2); - let user1_handle = tokio::spawn(async move { - user1_clone - .lock() - .await - .run_background_tasks() - .await - .unwrap(); - }); - let user2_handle = tokio::spawn(async move { - user2_clone - .lock() - .await - .run_background_tasks() - .await - .unwrap(); - }); - - // User1 initiates a task - let task_id = { - let mut user1 = user1.lock().await; - user1 - .create_task(vec![user2.lock().await.user_id], vec![true, false]) - .await - .unwrap() - }; - - // Wait for both users to decrypt the outputs - let mut decrypted = false; - while !decrypted { - sleep(Duration::from_secs(1)).await; - let user1_tasks = user1.lock().await; - let user2_tasks = user2.lock().await; - - if let Some(status) = user1_tasks.get_task(&task_id) { - if *status == TaskStatus::Done { - decrypted = true; - break; - } - } - - if let Some(status) = user2_tasks.get_task(&task_id) { - if *status == TaskStatus::Done { - decrypted = true; - break; - } - } - } - - // Signal the server to shut down - shutdown_tx.send(()).unwrap(); - - // Wait for the server to shut down - server_handle.await.unwrap(); - - // Stop background tasks - user1_handle.abort(); - user2_handle.abort(); - - // Final assertions - let user1 = user1.lock().await; - let user2 = user2.lock().await; - assert_eq!(user1.get_task(&task_id), Some(&TaskStatus::Done)); - assert_eq!(user2.get_task(&task_id), Some(&TaskStatus::Done)); -} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index ed4fb99..0000000 --- a/src/types.rs +++ /dev/null @@ -1,591 +0,0 @@ -use crate::phantom::{PhantomCrs, PhantomOps, PhantomPrimeOps, PhantomServer}; -use itertools::Itertools; -use phantom_zone_evaluator::boolean::fhew::prelude::*; -use rocket::serde::{Deserialize, Serialize}; -use rocket::tokio::sync::Mutex; -use rocket::Responder; -use std::collections::{HashMap, VecDeque}; -use std::fmt::Display; -use std::fmt::{self, Debug}; -use std::sync::Arc; -use thiserror::Error; - -pub type UserId = usize; -/// Decryption share for a word from one user. -pub type DecryptionShare = Vec; -pub type Word = Vec; -pub type ServerKeyShare = Vec; -pub type ParamCRS = (FhewBoolMpiParam, PhantomCrs); -pub type Cipher = Vec; - -pub type TaskId = usize; - -/// Represents the current state of a task. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum TaskStatus { - /// Task is waiting for input from required users - WaitingForInput, - /// All inputs received, task is ready to be processed - ReadyToRun, - /// Task is currently being processed - Running, - /// Computation complete, waiting for decryption shares - WaitingDecryptionShares, - /// Task is fully complete and results are available - Done, -} - -/// Represents a FHE computation task for the server to perform -/// -/// A task is created by an initiator. It goes through these stages: -/// - Waiting for the inputs from other users -/// - Waiting for the server to run the actual computation -/// - Waiting for users to contribute decryption shares to the computation outputs -/// - The users can decrypt the comutation outputs from the completed task. -#[derive(Serialize, Deserialize, Clone)] -pub struct Task { - pub id: TaskId, - pub initiator: UserId, - /// List of user IDs required to provide input for this task - pub required_inputs: Vec, - pub status: TaskStatus, - /// Collected inputs from users, keyed by user ID - pub inputs: HashMap, - /// Decryptables generated as a result of the task computation - pub decryptables: Vec, -} - -impl Task { - pub fn new( - id: TaskId, - initiator: UserId, - required_inputs: Vec, - initiator_input: Cipher, - ) -> Self { - let mut inputs = HashMap::new(); - inputs.insert(initiator, initiator_input); - Self { - id, - initiator, - required_inputs, - status: TaskStatus::WaitingForInput, - inputs, - decryptables: Vec::new(), - } - } - - pub fn is_ready_to_run(&self) -> bool { - self.required_inputs - .iter() - .all(|user_id| self.inputs.contains_key(user_id)) - } - - pub fn add_input(&mut self, user_id: UserId, input: Cipher) -> Result<(), Error> { - if !self.required_inputs.contains(&user_id) { - return Err(Error::UnexpectedInput { user_id }); - } - self.inputs.insert(user_id, input); - Ok(()) - } - - pub fn get_decryptables_for_user(&self, user_id: UserId) -> Vec<&Decryptable> { - self.decryptables - .iter() - .filter(|d| d.should_contribute(user_id) && !d.shares.contains_key(&user_id)) - .collect() - } -} - -impl fmt::Debug for Task { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Task") - .field("id", &self.id) - .field("initiator", &self.initiator) - .field("required_inputs", &self.required_inputs) - .field("status", &self.status) - .field("inputs", &format!("{} inputs", self.inputs.len())) - .field( - "decryptables", - &format!("{} decryptables", self.decryptables.len()), - ) - .finish() - } -} - -#[derive(Debug, Error)] -pub(crate) enum Error { - #[error("Wrong server state: expect {expect} but got {got}")] - WrongServerState { expect: String, got: String }, - #[error("User #{user_id} is unregistered")] - UnregisteredUser { user_id: usize }, - #[error("The public key share from user #{user_id} not found")] - PkShareNotFound { user_id: UserId }, - #[error("The bootstrap key share from user #{user_id} not found")] - BskShareNotFound { user_id: UserId }, - #[error("Task #{task_id} not found")] - TaskNotFound { task_id: TaskId }, - #[error("Unexpected input from user #{user_id}")] - UnexpectedInput { user_id: UserId }, - #[error("Wrong task state for task #{task_id}: expected {expected:?}, got {got:?}")] - WrongTaskState { - task_id: TaskId, - expected: TaskStatus, - got: TaskStatus, - }, - #[error("Decryptable #{decryptable_id} not found in task #{task_id}")] - DecryptableNotFound { - task_id: TaskId, - decryptable_id: usize, - }, - #[error("Unexpected decryption share from user #{user_id}")] - UnexpectedDecryptionShare { user_id: UserId }, - #[error("Bincode de/serialization error")] - Bincode(bincode::Error), -} - -#[derive(Responder)] -pub(crate) enum ErrorResponse { - #[response(status = 500, content_type = "json")] - ServerError(String), - #[response(status = 404, content_type = "json")] - NotFoundError(String), -} - -impl From for ErrorResponse { - fn from(error: Error) -> Self { - match error { - Error::WrongServerState { .. } => ErrorResponse::ServerError(error.to_string()), - Error::PkShareNotFound { .. } - | Error::BskShareNotFound { .. } - | Error::UnregisteredUser { .. } => ErrorResponse::NotFoundError(error.to_string()), - _ => ErrorResponse::ServerError(error.to_string()), - } - } -} - -impl From for ErrorResponse { - fn from(error: bincode::Error) -> Self { - Self::ServerError(error.to_string()) - } -} - -impl From for Error { - fn from(error: bincode::Error) -> Self { - Self::Bincode(error) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum ServerState { - /// Users are allowed to join the computation - ReadyForJoining, - /// Ready for public key shares - ReadyForPkShares, - /// Ready for bootstrap key shares - ReadyForBskShares, - /// We can now accept ciphertexts - ReadyForInputs, -} - -impl ServerState { - fn ensure(&self, expect: Self) -> Result<&Self, Error> { - if *self == expect { - Ok(self) - } else { - Err(Error::WrongServerState { - expect: expect.to_string(), - got: self.to_string(), - }) - } - } - fn transit(&mut self, next: Self) { - *self = next; - } -} - -impl Display for ServerState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[[ {:?} ]]", self) - } -} - -pub(crate) type MutexServerStorage = Arc>; - -pub(crate) struct ServerStorage { - /// Close registration when this number is reached - pub(crate) n_users: usize, - pub(crate) ps: PhantomServer, - pub(crate) state: ServerState, - pub(crate) users: Vec, - pub(crate) task_queue: VecDeque, - pub(crate) next_task_id: TaskId, -} - -impl fmt::Debug for ServerStorage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ServerStorage") - .field("state", &self.state) - .field("users", &self.users) - .finish() - } -} - -impl ServerStorage { - pub(crate) fn new(param: FhewBoolMpiParam, n_users: usize) -> Self { - Self { - n_users, - ps: PhantomServer::new(param, PhantomCrs::from_entropy(), None, None, None).unwrap(), - state: ServerState::ReadyForJoining, - users: vec![], - task_queue: VecDeque::new(), - next_task_id: 0, - } - } - - pub(crate) fn get_param_crs(&self) -> ParamCRS { - (*self.ps.param(), *self.ps.crs()) - } - - pub(crate) fn is_users_full(&self) -> bool { - self.n_users == self.users.len() - } - - pub(crate) fn add_user(&mut self) -> UserId { - let id = self.users.len(); - self.users.push(UserRecord { - id, - storage: UserStorage::Empty, - }); - id - } - - pub(crate) fn ensure(&self, state: ServerState) -> Result<(), Error> { - self.state.ensure(state)?; - Ok(()) - } - - pub(crate) fn transit(&mut self, state: ServerState) { - self.state.transit(state.clone()); - println!("Sever state {}", state); - } - - pub(crate) fn get_user(&mut self, user_id: UserId) -> Result<&mut UserRecord, Error> { - self.users - .get_mut(user_id) - .ok_or(Error::UnregisteredUser { user_id }) - } - - pub(crate) fn check_pk_share_submission(&self) -> bool { - self.users - .iter() - .all(|user| matches!(user.storage, UserStorage::PkAndRpkShare(_, _))) - } - - pub(crate) fn aggregate_pk_shares(&mut self) -> Result<(), Error> { - let mut pk_shares = Vec::new(); - for (user_id, user) in self.users.iter().enumerate() { - if let UserStorage::PkAndRpkShare(pk_share, _) = &user.storage { - pk_shares.push(self.ps.deserialize_pk_share(pk_share)?); - } else { - return Err(Error::PkShareNotFound { user_id }); - } - } - self.ps.aggregate_pk_shares(&pk_shares); - Ok(()) - } - - pub(crate) fn aggregate_rpk_shares(&mut self) -> Result<(), Error> { - let mut pk_shares = Vec::new(); - for (user_id, user) in self.users.iter().enumerate() { - if let UserStorage::PkAndRpkShare(_, rpk_share) = &user.storage { - pk_shares.push(self.ps.deserialize_rp_key_share(rpk_share)?); - } else { - return Err(Error::PkShareNotFound { user_id }); - } - } - self.ps.aggregate_rp_key_shares(&pk_shares); - Ok(()) - } - - pub(crate) fn check_bsk_share_submission(&self) -> bool { - self.users - .iter() - .all(|user| matches!(user.storage, UserStorage::BskShare(..))) - } - - pub(crate) fn aggregate_bsk_shares(&mut self) -> Result, Error> { - let mut bsk_shares = vec![]; - for (user_id, user) in self.users.iter_mut().enumerate() { - if let Some(bsk_share) = user.storage.get_bsk_share() { - bsk_shares.push(bsk_share.clone()); - } else { - return Err(Error::BskShareNotFound { user_id }); - } - } - self.ps.aggregate_bs_key_shares( - &bsk_shares - .iter() - .map(|bsk_share| self.ps.deserialize_bs_key_share(bsk_share)) - .try_collect::<_, Vec<_>, _>()?, - ); - Ok(bsk_shares) - } - - pub(crate) fn create_task( - &mut self, - initiator: UserId, - required_inputs: Vec, - initiator_input: Cipher, - ) -> TaskId { - let task_id = self.next_task_id; - self.next_task_id += 1; - let task = Task::new(task_id, initiator, required_inputs, initiator_input); - self.task_queue.push_back(task); - task_id - } - - pub(crate) fn get_task(&mut self, task_id: TaskId) -> Result<&mut Task, Error> { - self.task_queue - .iter_mut() - .find(|task| task.id == task_id) - .ok_or(Error::TaskNotFound { task_id }) - } - - pub(crate) fn get_tasks_for_user(&self, _user_id: UserId) -> Vec<&Task> { - self.task_queue.iter().collect() - } - - pub(crate) fn add_input_to_task( - &mut self, - task_id: TaskId, - user_id: UserId, - input: Cipher, - ) -> Result<(), Error> { - let task = self.get_task(task_id)?; - task.add_input(user_id, input)?; - if task.is_ready_to_run() { - task.status = TaskStatus::ReadyToRun; - } - Ok(()) - } - - pub(crate) fn complete_task( - &mut self, - task_id: TaskId, - decryptables: Vec, - ) -> Result<(), Error> { - let task = self.get_task(task_id)?; - task.decryptables = decryptables; - task.status = TaskStatus::WaitingDecryptionShares; - Ok(()) - } - - pub(crate) fn get_next_ready_task(&mut self) -> Option { - self.task_queue - .iter() - .find(|task| task.status == TaskStatus::ReadyToRun) - .map(|task| task.id) - } - - pub fn get_decryptables_for_user(&self, user_id: UserId) -> Vec<(TaskId, &Decryptable)> { - self.task_queue - .iter() - .filter(|task| task.status == TaskStatus::WaitingDecryptionShares) - .flat_map(|task| { - task.get_decryptables_for_user(user_id) - .into_iter() - .map(move |d| (task.id, d)) - }) - .collect() - } - - pub fn submit_decryption_share( - &mut self, - task_id: TaskId, - decryptable_id: usize, - user_id: UserId, - share: DecryptionShare, - ) -> Result<(), Error> { - let task = self.get_task(task_id)?; - - println!( - "User {} submitted decryption share for task {} and decryptable id {}", - user_id, task_id, decryptable_id - ); - - if task.status != TaskStatus::WaitingDecryptionShares { - return Err(Error::WrongTaskState { - task_id, - expected: TaskStatus::WaitingDecryptionShares, - got: task.status.clone(), - }); - } - - let decryptable = task - .decryptables - .iter_mut() - .find(|d| d.id == decryptable_id) - .ok_or(Error::DecryptableNotFound { - task_id, - decryptable_id, - })?; - - if !decryptable.should_contribute(user_id) { - return Err(Error::UnexpectedDecryptionShare { user_id }); - } - - decryptable.add_decryption_share(user_id, share); - - // Check if the task is complete - if task.decryptables.iter().all(|d| d.is_complete) { - task.status = TaskStatus::Done; - } - - Ok(()) - } -} - -#[derive(Debug)] -pub(crate) struct UserRecord { - pub(crate) id: UserId, - pub(crate) storage: UserStorage, -} - -#[derive(Debug, Clone)] -pub(crate) enum UserStorage { - Empty, - PkAndRpkShare(Vec, Vec), - BskShare(Box>), -} - -impl UserStorage { - pub(crate) fn get_bsk_share(&self) -> Option<&Vec> { - match self { - Self::BskShare(bsk_share) => Some(bsk_share), - _ => None, - } - } -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub(crate) struct PkShareSubmission { - pub(crate) user_id: UserId, - pub(crate) pk_share: Vec, - pub(crate) rpk_share: Vec, -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub(crate) struct BskShareSubmission { - pub(crate) user_id: UserId, - pub(crate) bsk_share: Vec, -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub(crate) struct CipherSubmission { - pub(crate) user_id: UserId, - pub(crate) cipher: Vec, -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub(crate) struct CreateTaskSubmission { - pub(crate) initiator: UserId, - pub(crate) required_inputs: Vec, - pub(crate) initiator_input: Cipher, -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub(crate) struct TaskInputSubmission { - pub(crate) task_id: TaskId, - pub(crate) user_id: UserId, - pub(crate) input: Cipher, -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub(crate) struct DecryptionShareSubmission { - pub(crate) task_id: TaskId, - pub(crate) decryptable_id: usize, - pub(crate) user_id: UserId, - pub(crate) share: DecryptionShare, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) enum Visibility { - Public, - Designated(UserId), -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct Decryptable { - pub(crate) id: usize, - pub(crate) vis: Visibility, - pub(crate) word: Word, - shares: HashMap, - n_users: usize, - /// Do we have all decryption shares required? - pub(crate) is_complete: bool, -} - -impl Decryptable { - fn new(id: usize, n_users: usize, word: Word, vis: Visibility) -> Self { - Self { - id, - vis, - word, - shares: HashMap::default(), - n_users, - is_complete: false, - } - } - - pub(crate) fn should_contribute(&self, user_id: UserId) -> bool { - match self.vis { - Visibility::Public => true, - Visibility::Designated(id) => id != user_id, - } - } - - pub(crate) fn get_shares(&self) -> Vec { - self.shares.values().cloned().collect() - } - - fn add_decryption_share(&mut self, user_id: UserId, share: DecryptionShare) { - self.shares.insert(user_id, share); - - self.is_complete = match self.vis { - Visibility::Public => self.shares.len() == self.n_users, - Visibility::Designated(_) => self.shares.len() == self.n_users - 1, - } - } -} - -pub(crate) struct DecryptableBuilder { - n_users: usize, - next_id: usize, -} - -impl DecryptableBuilder { - pub(crate) fn new(n_users: usize) -> Self { - Self { - n_users, - next_id: 0, - } - } - - pub(crate) fn new_public(&mut self, word: Word) -> Decryptable { - let id = self.next_id; - self.next_id += 1; - Decryptable::new(id, self.n_users, word, Visibility::Public) - } - - pub(crate) fn new_designated(&mut self, word: Word, user_id: UserId) -> Decryptable { - let id = self.next_id; - self.next_id += 1; - Decryptable::new(id, self.n_users, word, Visibility::Designated(user_id)) - } -} diff --git a/src/user.rs b/src/user.rs new file mode 100644 index 0000000..c2db329 --- /dev/null +++ b/src/user.rs @@ -0,0 +1,248 @@ +use crate::{ + client::{Client, HttpClient}, + phantom::{ + HierarchicalSeedableRng, PhantomBsKeyShare, PhantomCrs, PhantomOps, PhantomPk, + PhantomPkShare, PhantomRpKeyShare, PhantomSk, PhantomSkKs, + }, + server::app::{Action, AppStatus, Decryptable, GetSetupResponse, Task, UserId}, + Result, +}; +use anyhow::bail; +use axum::body::Body; +use core::time::Duration; +use hyper_util::client::legacy::{connect::HttpConnector, Client as HyperUtilClient}; +use itertools::{chain, Itertools}; +use rand::{rngs::StdRng, SeedableRng}; +use std::sync::OnceLock; +use tokio::time::sleep; + +pub struct User> { + client: Client, + user_id: usize, + seed: ::Seed, + crs: PhantomCrs, + ops: PhantomOps, + pk: OnceLock, +} + +impl User { + pub async fn connect( + server_uri: impl ToString, + user_id: usize, + seed: ::Seed, + ) -> Result { + Self::new(Client::new(server_uri), user_id, seed).await + } +} + +impl User { + pub async fn new( + client: Client, + user_id: usize, + seed: ::Seed, + ) -> Result { + let GetSetupResponse { param, crs } = client.get_setup().await?; + let ops = PhantomOps::new(param); + Ok(Self { + client, + user_id, + seed, + crs, + ops, + pk: Default::default(), + }) + } + + pub fn user_id(&self) -> UserId { + self.user_id + } + + fn deterministic_rng(&self, path: &[usize]) -> StdRng { + StdRng::from_hierarchical_seed(self.seed, path) + } + + fn sk(&self) -> PhantomSk { + self.ops + .sk_gen(StdRng::from_hierarchical_seed(self.seed, &[0, 0])) + } + + fn sk_ks(&self) -> PhantomSkKs { + self.ops + .sk_ks_gen(StdRng::from_hierarchical_seed(self.seed, &[0, 1])) + } + + fn pk_share_gen(&self) -> PhantomPkShare { + self.ops + .pk_share_gen(&self.crs, &self.sk(), self.deterministic_rng(&[1, 0])) + } + + fn rp_key_share_gen(&self) -> PhantomRpKeyShare { + self.ops + .rp_key_share_gen(&self.crs, &self.sk(), self.deterministic_rng(&[1, 1])) + } + + fn bs_key_share_gen(&self) -> PhantomBsKeyShare { + self.ops.bs_key_share_gen( + &self.crs, + self.user_id, + &self.sk(), + self.pk.get().unwrap(), + &self.sk_ks(), + self.deterministic_rng(&[1, 2]), + ) + } + + pub fn decrypt(&self, decryptable: &Decryptable) -> Vec { + assert!(decryptable.dec_shares.len() >= self.ops.param().total_shares - 1); + if decryptable.dec_shares.len() == self.ops.param().total_shares - 1 { + let my_share = self.ops.decrypt_share(&self.sk(), &decryptable.packed); + self.ops.aggregate_rp_dec_shares( + &decryptable.packed, + chain![&decryptable.dec_shares, [&my_share]], + ) + } else { + self.ops + .aggregate_rp_dec_shares(&decryptable.packed, &decryptable.dec_shares) + } + } + + pub async fn get_tasks(&self) -> Result> { + self.client + .get_user_tasks(self.user_id) + .await + .map(|response| response.tasks) + } + + pub async fn get_decryptables(&self) -> Result> { + self.client + .get_user_decryptables(self.user_id) + .await + .map(|response| response.decryptables) + } + + pub async fn demo(&self, ms: [bool; 10]) -> Result<()> { + let Some(pk) = self.pk.get() else { + bail!("pk is not got yet"); + }; + + let ct_batched = self.ops.batched_pk_encrypt(pk, ms); + self.client + .create_user_actions(self.user_id, Action::Demo(ct_batched)) + .await?; + + Ok(()) + } + + pub async fn participate_key_gen(&self) -> Result<()> { + loop { + match self.client.get_status().await?.status { + AppStatus::WaitingForPkShare(user_ids) => { + if user_ids.contains(&self.user_id) { + self.client + .create_user_pk_share(self.user_id, self.pk_share_gen()) + .await?; + } + sleep(Duration::from_millis(100)).await; + } + AppStatus::WaitingForRpKeyShare(user_ids) => { + if user_ids.contains(&self.user_id) { + self.client + .create_user_rp_key_share(self.user_id, self.rp_key_share_gen()) + .await?; + } + sleep(Duration::from_millis(100)).await; + } + AppStatus::WaitingForBsKeyShare(user_ids) => { + if self.pk.get().is_none() { + self.pk.set(self.client.get_pk().await?.pk).unwrap(); + } + if user_ids.contains(&self.user_id) { + self.client + .create_user_bs_key_share(self.user_id, self.bs_key_share_gen()) + .await?; + } + sleep(Duration::from_millis(100)).await; + } + AppStatus::AggregatingKeyShare | AppStatus::InitializingGame => { + sleep(Duration::from_millis(100)).await + } + AppStatus::PlayingGame(_) => break, + } + } + Ok(()) + } + + pub async fn wait_until_game_ready(&self) -> Result<()> { + loop { + let status = self.client.get_status().await.unwrap().status; + match status { + AppStatus::PlayingGame(_) => break Ok(()), + _ => sleep(Duration::from_secs(1)).await, + } + } + } + + pub async fn participate_decryption(&self) -> Result<()> { + loop { + let tasks = self.get_tasks().await?; + if !tasks.is_empty() { + let dec_shares = tasks + .into_iter() + .map(|task| match task { + Task::CreateDecShare { + decryptable_id, + packed, + .. + } => ( + decryptable_id, + self.ops.decrypt_share(&self.sk(), &packed.unwrap()), + ), + }) + .collect_vec(); + self.client + .create_decryption_share(self.user_id, dec_shares) + .await?; + } else { + sleep(Duration::from_millis(500)).await + } + } + } +} + +#[cfg(test)] +mod test { + use crate::{ + client::Client, + server::{ + test::{TEST_CRS, TEST_PARAM}, + ServerState, + }, + test::ItertoolsExt, + user::User, + }; + use rand::{rngs::StdRng, Rng, SeedableRng}; + use std::sync::Arc; + + #[tokio::test] + async fn key_gen() { + let state = ServerState::new(TEST_PARAM, TEST_CRS); + assert!(state.lock().unwrap().app.worker_key().is_none()); + + let client = Client::mock(Arc::clone(&state)); + let users = (0..TEST_PARAM.total_shares) + .map(|user_id| User::new(client.clone(), user_id, StdRng::from_entropy().gen())) + .try_join_vec() + .await + .unwrap(); + users + .iter() + .try_join_for_each(|user| user.participate_key_gen()) + .await + .unwrap(); + users + .iter() + .try_join_for_each(|user| user.wait_until_game_ready()) + .await + .unwrap(); + } +} diff --git a/src/worker.rs b/src/worker.rs new file mode 100644 index 0000000..1e6e8d9 --- /dev/null +++ b/src/worker.rs @@ -0,0 +1,167 @@ +use crate::{ + phantom::{ + PhantomBool, PhantomBsKey, PhantomEvaluator, PhantomOps, PhantomParam, PhantomRpKey, + }, + server::{ + app::BS_KEY_SIZE, + scheduler::{TaskRequest, TaskResponse}, + }, + Result, +}; +use anyhow::bail; +use core::time::Duration; +use futures_util::{ + stream::{SplitSink, SplitStream}, + SinkExt, StreamExt, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tokio::{ + net::TcpStream, + sync::{broadcast, Mutex, RwLock}, + time::sleep, +}; +use tokio_tungstenite::{ + connect_async_with_config, + tungstenite::{protocol::WebSocketConfig, Message}, + MaybeTlsStream, WebSocketStream, +}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum WorkerMessage { + SetKey(Box), + TaskRequest(TaskRequest), + TaskResponse(TaskResponse), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct WorkerKey { + pub param: PhantomParam, + pub rp_key: PhantomRpKey, + pub bs_key: PhantomBsKey, +} + +struct Worker { + // ops: PhantomOps, + // rp_key: RingPackingKeyOwned, + evaluator: PhantomEvaluator, +} + +impl Worker { + fn new(key: &WorkerKey) -> Self { + let ops = PhantomOps::new(key.param); + let evaluator = ops.evaluator(&key.bs_key); + Self { + // ops, + // rp_key: key.rp_key.clone(), + evaluator, + } + } + + fn process_task(&self, request: TaskRequest) -> TaskResponse { + let inputs = request + .inputs + .into_iter() + .map(|input| PhantomBool::new(&self.evaluator, input)) + .collect_vec(); + let outputs = { + let outputs = request.circuit_id.evaluate(&inputs); + outputs + .into_iter() + .map(|input| input.into_ct()) + .collect_vec() + }; + TaskResponse { + task_id: request.task_id, + outputs, + } + } +} + +pub async fn run(server_uri: impl ToString, mut shutdown_rx: broadcast::Receiver<()>) { + let server_uri = server_uri.to_string(); + + loop { + let ws = tokio::select! { + Ok(_) = shutdown_rx.recv() => break, + Ok(ws) = tokio::spawn(connect_until_ok(server_uri.clone())) => ws, + }; + + let (ws_tx, mut ws_rx) = ws.split(); + let ws_tx = Arc::new(Mutex::new(ws_tx)); + + if let Some(Err(err)) = ws_rx.next().await { + tracing::error!("failed to receive ping, {err}"); + return; + } + + tokio::select! { + Ok(_) = shutdown_rx.recv() => { + let _ = ws_tx.lock().await.send(Message::Close(None)).await; + break + }, + Ok(result) = tokio::spawn(handle_task(ws_tx.clone(), ws_rx)) => { + if let Err(err) = result { + tracing::error!("{err}"); + } + }, + }; + } +} + +async fn connect_until_ok(server_uri: String) -> WebSocketStream> { + tracing::debug!("connecting to server {server_uri}"); + let config = WebSocketConfig { + max_frame_size: Some(BS_KEY_SIZE), + ..Default::default() + }; + loop { + match connect_async_with_config(&server_uri, Some(config), false).await { + Ok((ws, _)) => { + tracing::debug!("connected to server {server_uri}"); + break ws; + } + Err(err) => { + tracing::warn!("failed to connect to server {server_uri}, {err}"); + sleep(Duration::from_secs(1)).await; + continue; + } + } + } +} + +async fn handle_task( + ws_tx: Arc>, Message>>>, + mut ws_rx: SplitStream>>, +) -> Result<()> { + let worker = Arc::new(RwLock::new(None)); + while let Some(msg) = ws_rx.next().await { + match msg { + Ok(Message::Binary(bytes)) => { + let msg: WorkerMessage = bincode::deserialize(&bytes).unwrap(); + match msg { + WorkerMessage::SetKey(key) => { + *worker.write().await = Some(Worker::new(&key)); + tracing::debug!("succeeded to set key") + } + WorkerMessage::TaskRequest(request) => { + let Some(worker) = &*worker.read().await else { + bail!("unexpected task before set key"); + }; + let response = WorkerMessage::TaskResponse(worker.process_task(request)); + let bytes = bincode::serialize(&response).unwrap(); + if let Err(err) = ws_tx.lock().await.send(Message::Binary(bytes)).await { + bail!("failed to send task response, {err}"); + } + } + _ => tracing::warn!("received unexpected worker msg {msg:?}"), + } + } + Ok(Message::Close(_)) => break, + Ok(msg) => tracing::warn!("received unexpected msg {msg}"), + Err(err) => bail!("failed to receive message, {err}"), + } + } + Ok(()) +} From 55fb0eaaf8e8759976352da07580c9d1652de3d9 Mon Sep 17 00:00:00 2001 From: han0110 Date: Wed, 16 Oct 2024 12:03:50 +0000 Subject: [PATCH 3/4] fix: dep lnk --- Cargo.lock | 4 ++++ Cargo.toml | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c800a9c..b2b7d0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -646,6 +646,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phantom-zone-crypto" version = "0.1.0" +source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94fb285e8a75db0343ad8d9d85e2d1c61e84e3d6" dependencies = [ "itertools", "num-traits", @@ -658,6 +659,7 @@ dependencies = [ [[package]] name = "phantom-zone-derive" version = "0.1.0" +source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94fb285e8a75db0343ad8d9d85e2d1c61e84e3d6" dependencies = [ "proc-macro2", "quote", @@ -667,6 +669,7 @@ dependencies = [ [[package]] name = "phantom-zone-evaluator" version = "0.1.0" +source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94fb285e8a75db0343ad8d9d85e2d1c61e84e3d6" dependencies = [ "itertools", "paste", @@ -680,6 +683,7 @@ dependencies = [ [[package]] name = "phantom-zone-math" version = "0.1.0" +source = "git+https://github.com/han0110/phantom-zone?branch=rewrite/ring-packing#94fb285e8a75db0343ad8d9d85e2d1c61e84e3d6" dependencies = [ "bincode", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 63ad4b0..6fa32a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,7 @@ http-body-util = "0.1.2" hyper = "1.4.1" hyper-util = { version = "0.1.9", features = ["client-legacy"] } itertools = { version = "0.13.0" } -# phantom-zone-evaluator = { git = "https://github.com/han0110/phantom-zone", branch = "rewrite/ring-packing", features = ["serde"] } -phantom-zone-evaluator = { path = "../phantom-zone/evaluator", features = ["serde"] } +phantom-zone-evaluator = { git = "https://github.com/han0110/phantom-zone", branch = "rewrite/ring-packing", features = ["serde"] } rand = { version = "0.8.5" } serde = { version = "1.0" } tokio = { version = "1.0", features = ["rt-multi-thread", "signal"] } @@ -25,8 +24,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } uuid = { version = "1.11.0", features = ["v4", "serde"] } [dev-dependencies] -# phantom-zone-evaluator = { git = "https://github.com/han0110/phantom-zone", branch = "rewrite/ring-packing", features = ["dev"] } -phantom-zone-evaluator = { path = "../phantom-zone/evaluator", features = ["dev"] } +phantom-zone-evaluator = { git = "https://github.com/han0110/phantom-zone", branch = "rewrite/ring-packing", features = ["dev"] } [[bin]] name = "server" From 6470419c234d84066f17d2714905ee40f9204c0e Mon Sep 17 00:00:00 2001 From: han0110 Date: Wed, 16 Oct 2024 13:42:49 +0000 Subject: [PATCH 4/4] chore --- src/circuit.rs | 2 +- src/client.rs | 79 +++-------------------------------------- src/server/app.rs | 21 +++++++---- src/server/scheduler.rs | 5 +-- src/user.rs | 26 ++++++-------- src/worker.rs | 6 ++-- 6 files changed, 36 insertions(+), 103 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 32e2820..67ebd93 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum CircuitId { - // Demo circuit that computes `inputs[..inputs.len() / 2] ^ inputs[inputs.len() / 2..]`. + /// Demo circuit that computes `inputs[..inputs.len() / 2] ^ inputs[inputs.len() / 2..]`. Demo, } diff --git a/src/client.rs b/src/client.rs index 4b4394a..839b7fb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,35 +11,18 @@ use crate::{ }; use anyhow::{anyhow, bail}; use axum::body::Body; -use core::future::Future; -use futures_util::TryFutureExt; use http_body_util::BodyExt; -use hyper::{Request, Response, StatusCode}; +use hyper::{Request, StatusCode}; use hyper_util::{ client::legacy::{connect::HttpConnector, Client as HyperUtilClient}, rt::TokioExecutor, }; use serde::{de::DeserializeOwned, Serialize}; -pub trait HttpClient { - type Body: BodyExt; - - fn request(&self, req: Request) -> impl Future>>; -} - -impl HttpClient for HyperUtilClient { - type Body = hyper::body::Incoming; - - fn request(&self, req: Request) -> impl Future>> { - self.request(req) - .map_err(|err| anyhow!("failed to send request, {err}")) - } -} - #[derive(Clone)] -pub struct Client> { +pub struct Client { uri: String, - inner: T, + inner: HyperUtilClient, } impl Client { @@ -50,7 +33,7 @@ impl Client { } } -impl Client { +impl Client { async fn call(&self, request: Request) -> Result { let (method, uri) = (request.method().clone(), request.uri().clone()); tracing::debug!("send {method} {uri}"); @@ -189,57 +172,3 @@ impl Client { .await } } - -#[cfg(test)] -pub(crate) mod test { - use crate::{ - client::{Client, HttpClient}, - server::{app, ServerState}, - Result, - }; - use axum::{body::Body, routing::RouterIntoService, Router}; - use core::{future::Future, ops::DerefMut}; - use hyper::{Request, Response}; - use std::sync::{Arc, Mutex}; - use tower::Service; - - #[derive(Clone)] - pub(crate) struct MockHttpClient { - router: Arc>>, - } - - impl MockHttpClient { - fn new(router: Router) -> Self { - MockHttpClient { - router: Arc::new(Mutex::new(router.into_service())), - } - } - } - - impl HttpClient for MockHttpClient { - type Body = Body; - - fn request( - &self, - req: Request, - ) -> impl Future>> { - return inner(self.router.lock().unwrap(), req); - - async fn inner( - mut router: impl DerefMut>, - req: Request, - ) -> Result> { - Ok(router.call(req).await.unwrap()) - } - } - } - - impl Client { - pub(crate) fn mock(state: Arc>) -> Self { - Self { - uri: "http://mock".to_string(), - inner: MockHttpClient::new(app::router().with_state(state)), - } - } - } -} diff --git a/src/server/app.rs b/src/server/app.rs index 0e436a9..cbd689d 100644 --- a/src/server/app.rs +++ b/src/server/app.rs @@ -1,9 +1,9 @@ use crate::{ circuit::CircuitId, phantom::{ - PhantomBatchedCt, PhantomBsKey, PhantomBsKeyShare, PhantomCrs, PhantomCt, PhantomOps, - PhantomPackedCt, PhantomPackedCtDecShare, PhantomParam, PhantomPk, PhantomPkShare, - PhantomRpKey, PhantomRpKeyPrep, PhantomRpKeyShare, + PhantomBatchedCt, PhantomBsKey, PhantomBsKeyShare, PhantomCrs, PhantomCt, PhantomEvaluator, + PhantomOps, PhantomPackedCt, PhantomPackedCtDecShare, PhantomParam, PhantomPk, + PhantomPkShare, PhantomRpKey, PhantomRpKeyPrep, PhantomRpKeyShare, }, server::{ scheduler::{TaskId, TaskRequest, TaskResponse}, @@ -158,6 +158,7 @@ impl GameState { } } +/// App status. #[derive(Debug, Serialize, Deserialize)] pub enum AppStatus { WaitingForPkShare(Vec), @@ -177,8 +178,11 @@ pub struct AppState { rp_key: OnceCell, rp_key_prep: OnceCell, bs_key: OnceCell, + evaluator: OnceCell, game: OnceCell, + /// Decryptables waiting for decryption shares or ready for decryption. decryptable: Vec, + /// Scheduled task corresponding to their output ct ids. scheduled: HashMap>, } @@ -192,6 +196,7 @@ impl AppState { rp_key: Default::default(), rp_key_prep: Default::default(), bs_key: Default::default(), + evaluator: Default::default(), game: Default::default(), decryptable: Default::default(), scheduled: Default::default(), @@ -254,6 +259,7 @@ impl AppState { .iter() .map(|user| user.bs_key_share.get().unwrap()), ); + self.evaluator.set(self.ops.evaluator(&bs_key)).unwrap(); self.bs_key.set(bs_key).unwrap(); } @@ -261,7 +267,7 @@ impl AppState { Some(WorkerKey { param: *self.ops.param(), bs_key: self.bs_key.get().cloned()?, - rp_key: self.rp_key.get().cloned()?, + // rp_key: self.rp_key.get().cloned()?, }) } @@ -288,6 +294,8 @@ impl AppState { } fn make_decryptable(&mut self) { + // Make decryptable for demo, 10 public cts and 10 designated cts + // for each user. self.decryptable = chain![ [self.pack((0..10).map(CtId::Demo), None)], (0..self.users.len()).map(|user_id| { @@ -496,6 +504,7 @@ async fn create_user_bs_key_share( Ok(Bincode(CreateUserBsKeyShareResponse {})) } +/// Decryptable ready for user to decrypt. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Decryptable { pub ct_ids: Vec, @@ -621,7 +630,7 @@ async fn update_user_tasks( #[derive(Debug, Serialize, Deserialize)] pub enum Action { - // Demo action to trigger Demo circuit with inputs. + /// Demo action to trigger Demo circuit with inputs. Demo(PhantomBatchedCt), } @@ -658,7 +667,7 @@ async fn create_user_action( // Remember the output_ct_ids to update later. app.scheduled.insert(task_id, output_ct_ids); scheduler - .schedule_task_request(TaskRequest { + .schedule_task(TaskRequest { task_id, circuit_id: CircuitId::Demo, inputs: chain![ diff --git a/src/server/scheduler.rs b/src/server/scheduler.rs index 69699f4..8eb8e3e 100644 --- a/src/server/scheduler.rs +++ b/src/server/scheduler.rs @@ -93,8 +93,9 @@ impl SchedulerState { tracing::debug!("unregistered, current worker count: {}", self.workers.len()); + // Reschedule if there are tasks unfinished worker.scheduled.into_values().for_each(|request| { - let _ = self.schedule_task_request(request); + let _ = self.schedule_task(request); }) } @@ -112,7 +113,7 @@ impl SchedulerState { .for_each(|worker| worker.set_key(key.clone())); } - pub fn schedule_task_request(&mut self, request: TaskRequest) -> Result<()> { + pub fn schedule_task(&mut self, request: TaskRequest) -> Result<()> { if self.workers.is_empty() { tracing::error!("failed to schedule task because no worker available"); bail!("no worker available"); diff --git a/src/user.rs b/src/user.rs index c2db329..4fdb55d 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,5 +1,5 @@ use crate::{ - client::{Client, HttpClient}, + client::Client, phantom::{ HierarchicalSeedableRng, PhantomBsKeyShare, PhantomCrs, PhantomOps, PhantomPk, PhantomPkShare, PhantomRpKeyShare, PhantomSk, PhantomSkKs, @@ -8,16 +8,14 @@ use crate::{ Result, }; use anyhow::bail; -use axum::body::Body; use core::time::Duration; -use hyper_util::client::legacy::{connect::HttpConnector, Client as HyperUtilClient}; use itertools::{chain, Itertools}; use rand::{rngs::StdRng, SeedableRng}; use std::sync::OnceLock; use tokio::time::sleep; -pub struct User> { - client: Client, +pub struct User { + client: Client, user_id: usize, seed: ::Seed, crs: PhantomCrs, @@ -35,9 +33,9 @@ impl User { } } -impl User { +impl User { pub async fn new( - client: Client, + client: Client, user_id: usize, seed: ::Seed, ) -> Result { @@ -213,22 +211,17 @@ impl User { mod test { use crate::{ client::Client, - server::{ - test::{TEST_CRS, TEST_PARAM}, - ServerState, - }, + server::test::{test_server, TEST_PARAM}, test::ItertoolsExt, user::User, }; use rand::{rngs::StdRng, Rng, SeedableRng}; - use std::sync::Arc; #[tokio::test] async fn key_gen() { - let state = ServerState::new(TEST_PARAM, TEST_CRS); - assert!(state.lock().unwrap().app.worker_key().is_none()); + let (server_handle, server_addr) = test_server().await; - let client = Client::mock(Arc::clone(&state)); + let client = &Client::new(format!("http://{server_addr}")); let users = (0..TEST_PARAM.total_shares) .map(|user_id| User::new(client.clone(), user_id, StdRng::from_entropy().gen())) .try_join_vec() @@ -244,5 +237,8 @@ mod test { .try_join_for_each(|user| user.wait_until_game_ready()) .await .unwrap(); + + server_handle.abort(); + assert!(server_handle.await.unwrap_err().is_cancelled()); } } diff --git a/src/worker.rs b/src/worker.rs index 1e6e8d9..b25a552 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -1,7 +1,5 @@ use crate::{ - phantom::{ - PhantomBool, PhantomBsKey, PhantomEvaluator, PhantomOps, PhantomParam, PhantomRpKey, - }, + phantom::{PhantomBool, PhantomBsKey, PhantomEvaluator, PhantomOps, PhantomParam}, server::{ app::BS_KEY_SIZE, scheduler::{TaskRequest, TaskResponse}, @@ -38,7 +36,7 @@ pub enum WorkerMessage { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WorkerKey { pub param: PhantomParam, - pub rp_key: PhantomRpKey, + // pub rp_key: PhantomRpKey, pub bs_key: PhantomBsKey, }