diff --git a/willow/api/server_accumulator.rs b/willow/api/server_accumulator.rs index e8549d3..3588d25 100644 --- a/willow/api/server_accumulator.rs +++ b/willow/api/server_accumulator.rs @@ -17,7 +17,7 @@ use aggregation_config::AggregationConfig; use aggregation_config_rust_proto::AggregationConfigProto; use ahe_traits::AheBase; use kahe_traits::KaheBase; -use messages::{ClientMessage, PartialDecryptionResponse}; +use messages::{ClientMessage, FinalizedPartialDecryption, PartialDecryptionResponse}; use messages_rust_proto::PartialDecryptionResponse as PartialDecryptionResponseProto; use proto_serialization_traits::{FromProto, ToProto}; use protobuf::prelude::*; @@ -187,7 +187,7 @@ impl ServerAccumulator { self.accumulator.split_client_message(client_message)?; self.verifier.verify_and_include(decryption_request_contribution, verifier_state)?; self.accumulator - .handle_ciphertext_contribution(ciphertext_contribution, accumulator_state)?; + .accumulate_ciphertext_contribution(ciphertext_contribution, accumulator_state)?; Ok(()) } @@ -630,9 +630,10 @@ impl FinalResultDecryptor { // Receives a single partial decryption response and attempts to recover right away. // This only works in the single-decryptor case. - self.accumulator.handle_partial_decryption(pd, &mut self.accumulator_state)?; + let finalized_pd = + FinalizedPartialDecryption { partial_decryption_sum: pd.partial_decryption }; let aggregation_result = - self.accumulator.recover_aggregation_result(&self.accumulator_state)?; + self.accumulator.recover_aggregation_result(&self.accumulator_state, &finalized_pd)?; // `aggregation_result` is a Kahe::Plaintext, i.e. HashMap> // Flatten hashmap for FFI like in shell_testing_decryptor.rs diff --git a/willow/benches/shell_benchmarks.rs b/willow/benches/shell_benchmarks.rs index 5a6408c..315c24c 100644 --- a/willow/benches/shell_benchmarks.rs +++ b/willow/benches/shell_benchmarks.rs @@ -27,7 +27,7 @@ use decryptor_traits::SecureAggregationDecryptor; use kahe_traits::KaheBase; use messages::{ CiphertextContribution, DecryptionRequestContribution, DecryptorPublicKey, - PartialDecryptionRequest, + FinalizedPartialDecryption, PartialDecryptionRequest, }; use shell_kahe::ShellKahe; use shell_parameters::create_shell_configs; @@ -275,7 +275,7 @@ fn setup_server_handle_client_message(args: &Args) -> ServerInputs { fn run_server_handle_client_message(inputs: &mut ServerInputs) { inputs .accumulator - .handle_ciphertext_contribution( + .accumulate_ciphertext_contribution( black_box(inputs.ciphertext_contributions.pop().unwrap()), black_box(&mut inputs.accumulator_state), ) @@ -288,6 +288,7 @@ fn run_server_handle_client_message(inputs: &mut ServerInputs) { struct ServerRecoverInputs { accumulator: WillowV1CiphertextAccumulator, accumulator_state: CiphertextAccumulatorState, + finalized_partial_decryption: FinalizedPartialDecryption, } fn setup_server_recover_aggregation_result(args: &Args) -> ServerRecoverInputs { @@ -314,7 +315,7 @@ fn setup_server_recover_aggregation_result(args: &Args) -> ServerRecoverInputs { // Accumulator handles its part. inputs .accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut inputs.accumulator_state) + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut inputs.accumulator_state) .unwrap(); // Verifier creates the partial decryption request. @@ -327,18 +328,22 @@ fn setup_server_recover_aggregation_result(args: &Args) -> ServerRecoverInputs { .unwrap(); // Accumulator handles the partial decryption. - inputs.accumulator.handle_partial_decryption(pd, &mut inputs.accumulator_state).unwrap(); + let finalized_pd = FinalizedPartialDecryption { partial_decryption_sum: pd.partial_decryption }; ServerRecoverInputs { accumulator: inputs.accumulator, accumulator_state: inputs.accumulator_state, + finalized_partial_decryption: finalized_pd, } } fn run_server_recover_aggregation_result(inputs: &mut ServerRecoverInputs) { let res = inputs .accumulator - .recover_aggregation_result(black_box(&inputs.accumulator_state)) + .recover_aggregation_result( + black_box(&inputs.accumulator_state), + black_box(&inputs.finalized_partial_decryption), + ) .unwrap(); let _ = black_box(res); // Prevent optimization. } @@ -368,7 +373,7 @@ fn setup_decryptor_partial_decryption(args: &Args) -> DecryptorInputs { // The accumulator and verifier each handle their part of the client message. inputs .accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut inputs.accumulator_state) + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut inputs.accumulator_state) .unwrap(); inputs .verifier diff --git a/willow/proto/willow/messages.proto b/willow/proto/willow/messages.proto index cace9bf..3b062e4 100644 --- a/willow/proto/willow/messages.proto +++ b/willow/proto/willow/messages.proto @@ -62,7 +62,7 @@ message ServerState { map decryptor_public_key_shares = 1; ShellKaheCiphertext client_sum_kahe = 2; ShellAheRecoverCiphertext client_sum_ahe_recover = 3; - ShellAhePartialDecryption partial_decryption_sum = 4; + ShellAhePartialDecryption partial_decryption_sum = 4 [deprecated = true]; } message VerifierState { diff --git a/willow/protocol/accumulator_traits.rs b/willow/protocol/accumulator_traits.rs index 8fed16e..7d06dda 100644 --- a/willow/protocol/accumulator_traits.rs +++ b/willow/protocol/accumulator_traits.rs @@ -14,7 +14,8 @@ use kahe_traits::HasKahe; use messages::{ - CiphertextContribution, ClientMessage, DecryptionRequestContribution, PartialDecryptionResponse, + CiphertextContribution, ClientMessage, DecryptionRequestContribution, + FinalizedPartialDecryption, }; use status::StatusError; use vahe_traits::HasVahe; @@ -25,7 +26,6 @@ type Vahe = ::Vahe; /// Base trait for the secure aggregation accumulator. Also includes the Coordinator /// functionality of the threshold AHE scheme. -/// pub trait SecureAggregationCiphertextAccumulator: HasKahe + HasVahe { /// The state held by the accumulator between messages. type CiphertextAccumulatorState: Default + Clone; @@ -42,26 +42,19 @@ pub trait SecureAggregationCiphertextAccumulator: HasKahe + HasVahe { StatusError, >; - /// Handles a single client message, updating the accumulator state. - fn handle_ciphertext_contribution( + /// Accumulates a single client ciphertext contribution into the accumulator state. + fn accumulate_ciphertext_contribution( &self, ciphertext_contribution: CiphertextContribution, Vahe>, accumulator_state: &mut Self::CiphertextAccumulatorState, ) -> Result<(), StatusError>; - /// Handles a partial decryption received from a Decryptor, updating the - /// accumulator state. - fn handle_partial_decryption( - &self, - partial_decryption_response: PartialDecryptionResponse, Vahe>, - accumulator_state: &mut Self::CiphertextAccumulatorState, - ) -> Result<(), StatusError>; - - /// Recovers the aggregation result after enough partial decryptions have - /// been received from Decryptors. + /// Recovers the aggregation result from the accumulated ciphertext contributions + /// and the finalized partial decryption. fn recover_aggregation_result( &self, accumulator_state: &Self::CiphertextAccumulatorState, + finalized_partial_decryption: &FinalizedPartialDecryption>, ) -> Result; /// Merges two accumulator states into one. diff --git a/willow/protocol/willow_v1_accumulator.rs b/willow/protocol/willow_v1_accumulator.rs index 5c86ad8..3926cdc 100644 --- a/willow/protocol/willow_v1_accumulator.rs +++ b/willow/protocol/willow_v1_accumulator.rs @@ -16,7 +16,8 @@ use accumulator_traits::SecureAggregationCiphertextAccumulator; use ahe_traits::PartialDec; use kahe_traits::{HasKahe, KaheBase, KaheDecrypt, TrySecretKeyFrom}; use messages::{ - CiphertextContribution, ClientMessage, DecryptionRequestContribution, PartialDecryptionResponse, + CiphertextContribution, ClientMessage, DecryptionRequestContribution, + FinalizedPartialDecryption, }; use messages_rust_proto::ServerState as ServerStateProto; use proto_serialization_traits::{FromProto, ToProto}; @@ -54,24 +55,19 @@ impl HasVahe for WillowV1CiphertextAccumulator { /// Running sum of client ciphertexts. client_sum: Option<(Kahe::Ciphertext, Vahe::RecoverCiphertext)>, - /// Running sum of partial decryptions. - partial_decryption_sum: Option, } impl Default for CiphertextAccumulatorState { fn default() -> Self { - Self { client_sum: None, partial_decryption_sum: None } + Self { client_sum: None } } } impl Clone for CiphertextAccumulatorState { fn clone(&self) -> Self { - Self { - client_sum: self.client_sum.clone(), - partial_decryption_sum: self.partial_decryption_sum.clone(), - } + Self { client_sum: self.client_sum.clone() } } } @@ -82,7 +78,6 @@ where Vahe: VaheBase + PartialDec + 'a, Kahe::Ciphertext: ToProto<&'a Kahe, Proto = ShellKaheCiphertext>, // TODO: Rename protos to be generic once cl/836370582 has landed. Vahe::RecoverCiphertext: ToProto<&'a Vahe, Proto = ShellAheRecoverCiphertext>, - Vahe::PartialDecryption: ToProto<&'a Vahe, Proto = ShellAhePartialDecryption>, { type Proto = ServerStateProto; @@ -94,10 +89,6 @@ where proto.set_client_sum_ahe_recover(ahe.to_proto(context.vahe())?); } - if let Some(pd) = &self.partial_decryption_sum { - proto.set_partial_decryption_sum(pd.to_proto(context.vahe())?); - } - Ok(proto) } } @@ -109,7 +100,6 @@ where Vahe: VaheBase + PartialDec + 'a, Kahe::Ciphertext: FromProto<&'a Kahe, Proto = ShellKaheCiphertext>, Vahe::RecoverCiphertext: FromProto<&'a Vahe, Proto = ShellAheRecoverCiphertext>, - Vahe::PartialDecryption: FromProto<&'a Vahe, Proto = ShellAhePartialDecryption>, { type Proto = ServerStateProto; @@ -136,16 +126,7 @@ where )); }; - let partial_decryption_sum = if proto.has_partial_decryption_sum() { - Some(Vahe::PartialDecryption::from_proto( - proto.partial_decryption_sum(), - context.vahe(), - )?) - } else { - None - }; - - Ok(CiphertextAccumulatorState { client_sum, partial_decryption_sum }) + Ok(CiphertextAccumulatorState { client_sum }) } } @@ -186,8 +167,8 @@ where )) } - /// Handles a single client's ciphertext contribution, updating the accumulator state. - fn handle_ciphertext_contribution( + /// Accumulates a single client's ciphertext contribution, updating the accumulator state. + fn accumulate_ciphertext_contribution( &self, contribution: CiphertextContribution, accumulator_state: &mut Self::CiphertextAccumulatorState, @@ -207,46 +188,20 @@ where Ok(()) } - /// Handles a partial decryption response received from a Decryptor, updating the - /// accumulator state. - fn handle_partial_decryption( - &self, - partial_decryption_response: PartialDecryptionResponse, - accumulator_state: &mut Self::CiphertextAccumulatorState, - ) -> Result<(), status::StatusError> { - if partial_decryption_response.dp_ciphertext_contribution.is_some() { - return Err(status::failed_precondition( - "DP ciphertext contributions are not yet supported.", - )); - } - let partial_decryption = partial_decryption_response.partial_decryption; - if let Some(ref mut partial_decryption_sum) = accumulator_state.partial_decryption_sum { - self.vahe - .add_partial_decryptions_in_place(&partial_decryption, partial_decryption_sum)?; - } else { - accumulator_state.partial_decryption_sum = Some(partial_decryption); - } - Ok(()) - } - - /// Recovers the aggregation result after enough partial decryptions have - /// been received from Decryptors. + /// Recovers the aggregation result from the accumulated ciphertext contributions + /// and the finalized partial decryption. fn recover_aggregation_result( &self, accumulator_state: &Self::CiphertextAccumulatorState, + finalized_partial_decryption: &FinalizedPartialDecryption, ) -> Result { if let Some((ref kahe_ciphertext, ref recover_ciphertext)) = accumulator_state.client_sum { - if let Some(ref partial_decryption_sum) = accumulator_state.partial_decryption_sum { - let ahe_plaintext = - self.vahe.recover(&partial_decryption_sum, &recover_ciphertext, None)?; - let kahe_secret_key = self.kahe.try_secret_key_from(ahe_plaintext)?; - let kahe_plaintext = self.kahe.decrypt(kahe_ciphertext, &kahe_secret_key)?; - Ok(kahe_plaintext) - } else { - Err(status::failed_precondition( - "Must handle at least one partial decryption before requesting recovery", - ))? - } + let partial_decryption_sum = &finalized_partial_decryption.partial_decryption_sum; + let ahe_plaintext = + self.vahe.recover(partial_decryption_sum, &recover_ciphertext, None)?; + let kahe_secret_key = self.kahe.try_secret_key_from(ahe_plaintext)?; + let kahe_plaintext = self.kahe.decrypt(kahe_ciphertext, &kahe_secret_key)?; + Ok(kahe_plaintext) } else { Err(status::failed_precondition( "Must handle at least one client message before requesting recovery", @@ -285,19 +240,6 @@ where (None, None) => None, }; - merged_accumulator_state.partial_decryption_sum = match ( - accumulator_state_1.partial_decryption_sum, - accumulator_state_2.partial_decryption_sum, - ) { - (Some(sum1), Some(sum2)) => { - let mut merged_sum = sum1; - self.vahe.add_partial_decryptions_in_place(&sum2, &mut merged_sum)?; - Some(merged_sum) - } - (Some(s), None) | (None, Some(s)) => Some(s), - (None, None) => None, - }; - Ok(merged_accumulator_state) } } @@ -364,7 +306,6 @@ mod tests { let accumulator_state_roundtrip = CiphertextAccumulatorState::from_proto(accumulator_state_proto, &accumulator)?; verify_true!(accumulator_state_roundtrip.client_sum.is_none())?; - verify_true!(accumulator_state_roundtrip.partial_decryption_sum.is_none())?; // Populate accumulator state. let public_key_share = decryptor.create_public_key_share(&mut decryptor_state)?; @@ -372,7 +313,7 @@ mod tests { let public_key = vahe.aggregate_public_key_shares(std::iter::once(&public_key_share))?; let client_plaintext = HashMap::from([( DEFAULT_VECTOR_ID.to_string(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1], + vec![1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1], )]); let nonce = generate_random_nonce(); let client_message = client.create_client_message( @@ -384,19 +325,23 @@ mod tests { accumulator.split_client_message(client_message)?; verifier.verify_and_include(decryption_request_contribution, &mut verifier_state)?; accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state)?; + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state)?; let pd_ct = verifier.create_partial_decryption_request(verifier_state)?; let pd = decryptor.handle_partial_decryption_request(pd_ct, &mut decryptor_state)?; - accumulator.handle_partial_decryption(pd, &mut accumulator_state)?; // Check populated state serialization verify_true!(accumulator_state.client_sum.is_some())?; - verify_true!(accumulator_state.partial_decryption_sum.is_some())?; let accumulator_state_proto = accumulator_state.to_proto(&accumulator)?; let accumulator_state_roundtrip = CiphertextAccumulatorState::from_proto(accumulator_state_proto, &accumulator)?; verify_true!(accumulator_state_roundtrip.client_sum.is_some())?; - verify_true!(accumulator_state_roundtrip.partial_decryption_sum.is_some())?; + + // Check recovery + let finalized_pd = + FinalizedPartialDecryption { partial_decryption_sum: pd.partial_decryption }; + let recovered = + accumulator.recover_aggregation_result(&accumulator_state, &finalized_pd)?; + verify_eq!(recovered, client_plaintext)?; Ok(()) } diff --git a/willow/tests/BUILD b/willow/tests/BUILD index 4bf9586..4b45a16 100644 --- a/willow/tests/BUILD +++ b/willow/tests/BUILD @@ -44,6 +44,7 @@ rust_test( "//willow/protocol:verifier_traits", "//willow/protocol:willow_v1_accumulator", "//willow/protocol:willow_v1_client", + "//willow/protocol:willow_v1_coordinator", "//willow/protocol:willow_v1_decryptor", "//willow/protocol:willow_v1_verifier", "//willow/testing_utils", diff --git a/willow/tests/willow_v1_shell.rs b/willow/tests/willow_v1_shell.rs index 72bf6fd..2cb74b0 100644 --- a/willow/tests/willow_v1_shell.rs +++ b/willow/tests/willow_v1_shell.rs @@ -16,13 +16,13 @@ use ahe_traits::AheBase; use client_traits::SecureAggregationClient; use accumulator_traits::SecureAggregationCiphertextAccumulator; -use decryptor_traits::SecureAggregationDecryptor; use googletest::prelude::container_eq; use googletest::{gtest, verify_eq, verify_that}; use kahe_traits::KaheBase; use messages::{ - CiphertextContribution, ClientMessage, DecryptionRequestContribution, DecryptorPublicKeyShare, - PartialDecryptionRequest, PartialDecryptionResponse, + CiphertextContribution, ClientMessage, CoordinatorState, CoordinatorStatus, + DecryptionRequestContribution, DecryptorPublicKeyShare, PartialDecryptionRequest, + PartialDecryptionResponse, }; use proto_serialization_traits::{FromProto, ToProto}; use shell_kahe::ShellKahe; @@ -38,6 +38,7 @@ use testing_utils::{ use verifier_traits::SecureAggregationVerifier; use willow_v1_accumulator::{CiphertextAccumulatorState, WillowV1CiphertextAccumulator}; use willow_v1_client::WillowV1Client; +use willow_v1_coordinator::WillowV1Coordinator; use willow_v1_decryptor::{DecryptorState, WillowV1Decryptor}; use willow_v1_verifier::{VerifierState, WillowV1Verifier}; @@ -46,6 +47,7 @@ const CONTEXT_STRING: &[u8] = b"testing_context_string"; /// Encrypt and decrypt with a single decryptor and single client. #[gtest] fn encrypt_decrypt_one() -> googletest::Result<()> { + use decryptor_traits::SecureAggregationDecryptor; let default_id = String::from("default"); let aggregation_config = generate_aggregation_config(default_id.clone(), 16, 10, 1, 1); let max_number_of_decryptors = aggregation_config.max_number_of_decryptors; @@ -95,7 +97,7 @@ fn encrypt_decrypt_one() -> googletest::Result<()> { accumulator.split_client_message(client_message).unwrap(); verifier.verify_and_include(decryption_request_contribution, &mut verifier_state).unwrap(); accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) .unwrap(); // Verifier creates the partial decryption request. @@ -104,11 +106,11 @@ fn encrypt_decrypt_one() -> googletest::Result<()> { // Decryptor creates partial decryption. let pd = decryptor.handle_partial_decryption_request(pd_ct, &mut decryptor_state).unwrap(); - // Accumulator handles the partial decryption. - accumulator.handle_partial_decryption(pd, &mut accumulator_state).unwrap(); - // Accumulator recovers the aggregation result. - let aggregation_result = accumulator.recover_aggregation_result(&accumulator_state).unwrap(); + let finalized_pd = + messages::FinalizedPartialDecryption { partial_decryption_sum: pd.partial_decryption }; + let aggregation_result = + accumulator.recover_aggregation_result(&accumulator_state, &finalized_pd).unwrap(); // Check that the (padded) result matches the client plaintext. verify_that!(aggregation_result.keys().collect::>(), container_eq([&default_id]))?; @@ -122,6 +124,7 @@ fn encrypt_decrypt_one() -> googletest::Result<()> { /// Encrypt and decrypt with a single decryptor and single client, using serialization. #[gtest] fn encrypt_decrypt_one_serialized() -> googletest::Result<()> { + use decryptor_traits::SecureAggregationDecryptor; let default_id = String::from("default"); let aggregation_config = generate_aggregation_config(default_id.clone(), 16, 10, 1, 1); let max_number_of_decryptors = aggregation_config.max_number_of_decryptors; @@ -211,7 +214,7 @@ fn encrypt_decrypt_one_serialized() -> googletest::Result<()> { verifier.verify_and_include(decryption_request_contribution, &mut verifier_state).unwrap(); accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) .unwrap(); // Verifier creates the partial decryption request. @@ -230,11 +233,11 @@ fn encrypt_decrypt_one_serialized() -> googletest::Result<()> { let pd: PartialDecryptionResponse = PartialDecryptionResponse::from_proto(pd_proto, &accumulator)?; - // Accumulator handles the partial decryption. - accumulator.handle_partial_decryption(pd, &mut accumulator_state).unwrap(); - // Accumulator recovers the aggregation result. - let aggregation_result = accumulator.recover_aggregation_result(&accumulator_state).unwrap(); + let finalized_pd = + messages::FinalizedPartialDecryption { partial_decryption_sum: pd.partial_decryption }; + let aggregation_result = + accumulator.recover_aggregation_result(&accumulator_state, &finalized_pd).unwrap(); // Check that the (padded) result matches the client plaintext. verify_that!(aggregation_result.keys().collect::>(), container_eq([&default_id]))?; @@ -248,6 +251,7 @@ fn encrypt_decrypt_one_serialized() -> googletest::Result<()> { // Encrypt and decrypt with multiple clients and a single decryptor. #[gtest] fn encrypt_decrypt_multiple_clients() -> googletest::Result<()> { + use decryptor_traits::SecureAggregationDecryptor; const NUM_CLIENTS: i64 = 10; let default_id = String::from("default"); let aggregation_config = @@ -318,7 +322,7 @@ fn encrypt_decrypt_multiple_clients() -> googletest::Result<()> { accumulator.split_client_message(client_message).unwrap(); verifier.verify_and_include(decryption_request_contribution, &mut verifier_state).unwrap(); accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) .unwrap(); } @@ -336,7 +340,7 @@ fn encrypt_decrypt_multiple_clients() -> googletest::Result<()> { let verifier_state_merged = verifier.merge_states(verifier_state_1, verifier_state_2).unwrap(); // Run the rest of the protocol twice, once with each of the the two copies of the verifier state. - for (mut accumulator_state, verifier_state) in + for (accumulator_state, verifier_state) in [(accumulator_state.clone(), verifier_state), (accumulator_state, verifier_state_merged)] { // Verifier creates the partial decryption request. @@ -345,12 +349,11 @@ fn encrypt_decrypt_multiple_clients() -> googletest::Result<()> { // Decryptor creates partial decryption. let pd = decryptor.handle_partial_decryption_request(pd_ct, &mut decryptor_state).unwrap(); - // Accumulator handles the partial decryption. - accumulator.handle_partial_decryption(pd, &mut accumulator_state).unwrap(); - // Accumulator recovers the aggregation result. + let finalized_pd = + messages::FinalizedPartialDecryption { partial_decryption_sum: pd.partial_decryption }; let aggregation_result = - accumulator.recover_aggregation_result(&accumulator_state).unwrap(); + accumulator.recover_aggregation_result(&accumulator_state, &finalized_pd).unwrap(); // Check that the (padded) result matches the client plaintext. verify_that!(aggregation_result.keys().collect::>(), container_eq([&default_id]))?; @@ -365,6 +368,7 @@ fn encrypt_decrypt_multiple_clients() -> googletest::Result<()> { // Encrypt and decrypt with multiple clients including invalid client proofs and a single decryptor. #[gtest] fn encrypt_decrypt_multiple_clients_including_invalid_proofs() -> googletest::Result<()> { + use decryptor_traits::SecureAggregationDecryptor; const NUM_MAX_CLIENTS: i64 = 10; const NUM_GOOD_CLIENTS: i64 = 10; const NUM_BAD_CLIENTS: i64 = 5; @@ -446,7 +450,7 @@ fn encrypt_decrypt_multiple_clients_including_invalid_proofs() -> googletest::Re accumulator.split_client_message(client_message).unwrap(); verifier.verify_and_include(decryption_request_contribution, &mut verifier_state).unwrap(); accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) .unwrap(); } @@ -492,11 +496,11 @@ fn encrypt_decrypt_multiple_clients_including_invalid_proofs() -> googletest::Re // Decryptor creates partial decryption. let pd = decryptor.handle_partial_decryption_request(pd_ct, &mut decryptor_state).unwrap(); - // Accumulator handles the partial decryption. - accumulator.handle_partial_decryption(pd, &mut accumulator_state).unwrap(); - // Accumulator recovers the aggregation result. - let aggregation_result = accumulator.recover_aggregation_result(&accumulator_state).unwrap(); + let finalized_pd = + messages::FinalizedPartialDecryption { partial_decryption_sum: pd.partial_decryption }; + let aggregation_result = + accumulator.recover_aggregation_result(&accumulator_state, &finalized_pd).unwrap(); // Check that the (padded) result matches the client plaintext. verify_that!(aggregation_result.keys().collect::>(), container_eq([&default_id]))?; @@ -510,6 +514,10 @@ fn encrypt_decrypt_multiple_clients_including_invalid_proofs() -> googletest::Re /// Note: This test uses RLWE parameters for production use. #[gtest] fn encrypt_decrypt_many_clients_decryptors() -> googletest::Result<()> { + use decryptor_traits::{ + SecureAggregationBaseMultiDecryptor, SecureAggregationCoordinator, + SecureAggregationReputableDecryptor, + }; const INPUT_LENGTH: isize = 100_000; // 100K const INPUT_DOMAIN: i64 = 1i64 << 32; const MAX_NUM_CLIENTS: i64 = 10_000_000; // used to generate parameters. @@ -549,33 +557,39 @@ fn encrypt_decrypt_many_clients_decryptors() -> googletest::Result<()> { let verifier = WillowV1Verifier { vahe: Rc::clone(&vahe) }; let mut verifier_state = VerifierState::default(); + // Create coordinator. + let coord = WillowV1Coordinator { vahe: Rc::clone(&vahe) }; + let mut coord_state = CoordinatorState::default(); + // Create decryptors. let mut decryptors = vec![]; let mut decryptor_states = vec![]; - let mut public_key_shares = vec![]; + let mut setup_contributions = vec![]; for _ in 0..NUM_DECRYPTORS { let mut decryptor_state = DecryptorState::default(); - let decryptor = - WillowV1Decryptor::new_with_randomly_generated_seed(Rc::clone(&vahe)).unwrap(); + let decryptor = WillowV1Decryptor::new_with_randomly_generated_seed(Rc::clone(&vahe))?; - // Decryptor generates public key share. - let public_key_share = decryptor.create_public_key_share(&mut decryptor_state).unwrap(); - public_key_shares.push(public_key_share); + // Decryptor generates its setup contribution. + let contribution = decryptor.create_setup_contribution(&mut decryptor_state)?; + setup_contributions.push(contribution); decryptors.push(decryptor); decryptor_states.push(decryptor_state); } - // Aggregate public key shares directly. - let public_key = vahe.aggregate_public_key_shares(public_key_shares.iter()).unwrap(); + // Coordinator processes setup. + let verify_request = + coord.handle_setup_submissions(vec![], setup_contributions, &mut coord_state)?; + + // Reputable decryptor verifies and aggregates the public key. + let public_key = decryptors[0].verify_and_aggregate_key_contributions(verify_request)?; // Create clients, and each client generates their messages. let mut expected_output = vec![0; INPUT_LENGTH as usize]; let mut client_messages = vec![]; for _ in 0..NUM_CLIENTS { let client = - WillowV1Client::new_with_randomly_generated_seed(Rc::clone(&kahe), Rc::clone(&vahe)) - .unwrap(); + WillowV1Client::new_with_randomly_generated_seed(Rc::clone(&kahe), Rc::clone(&vahe))?; let client_input_values = generate_random_unsigned_vector(INPUT_LENGTH as usize, INPUT_DOMAIN as u64); @@ -586,7 +600,7 @@ fn encrypt_decrypt_many_clients_decryptors() -> googletest::Result<()> { HashMap::from([(default_id.as_str(), client_input_values.as_slice())]); let nonce = generate_random_nonce(); let client_message = - client.create_client_message(&client_plaintext, &public_key, &nonce).unwrap(); + client.create_client_message(&client_plaintext, &public_key, &nonce)?; client_messages.push(client_message); } @@ -597,29 +611,41 @@ fn encrypt_decrypt_many_clients_decryptors() -> googletest::Result<()> { for client_message in client_messages { // The client message is split and handled by the accumulator and verifier. let (ciphertext_contribution, decryption_request_contribution) = - accumulator.split_client_message(client_message).unwrap(); - verifier.verify_and_include(decryption_request_contribution, &mut verifier_state).unwrap(); + accumulator.split_client_message(client_message)?; + verifier.verify_and_include(decryption_request_contribution, &mut verifier_state)?; accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) - .unwrap(); + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state)?; } // Verifier creates the partial decryption request. - let pd_ct = verifier.create_partial_decryption_request(verifier_state).unwrap(); + let pd_ct = verifier.create_partial_decryption_request(verifier_state)?; + + // Coordinator prepares decryption request. + let pd_request = + coord.prepare_decryption_request(&pd_ct.partial_dec_ciphertext, &mut coord_state)?; // Decryptors perform partial decryption. + let mut partial_responses = vec![]; for i in 0..NUM_DECRYPTORS { // Each decryptor creates partial decryption. - let pd = decryptors[i] - .handle_partial_decryption_request(pd_ct.clone(), &mut decryptor_states[i]) - .unwrap(); - - // Accumulator handles the partial decryption. - accumulator.handle_partial_decryption(pd, &mut accumulator_state).unwrap(); + let pd = decryptors[i].handle_partial_decryption_request( + pd_request.clone(), + Some(kahe.as_ref()), + &mut decryptor_states[i], + )?; + partial_responses.push(pd); } + coord.aggregate_partial_decryptions( + partial_responses, + Some(kahe.as_ref()), + &mut coord_state, + )?; + let finalized_pd = coord.finalize_partial_decryption(&mut coord_state)?; + // Accumulator recovers the aggregation result. - let aggregation_result = accumulator.recover_aggregation_result(&accumulator_state).unwrap(); + let aggregation_result = + accumulator.recover_aggregation_result(&accumulator_state, &finalized_pd)?; // Check that the (padded) result matches the client plaintext. verify_that!(aggregation_result.keys().collect::>(), container_eq([&default_id]))?; @@ -632,6 +658,10 @@ fn encrypt_decrypt_many_clients_decryptors() -> googletest::Result<()> { // Encrypt and decrypt with multiple clients and multiple decryptors, but no dropout. #[gtest] fn encrypt_decrypt_no_dropout() -> googletest::Result<()> { + use decryptor_traits::{ + SecureAggregationBaseMultiDecryptor, SecureAggregationCoordinator, + SecureAggregationReputableDecryptor, + }; const NUM_CLIENTS: i64 = 10; const NUM_DECRYPTORS: i64 = 10; let default_id = String::from("default"); @@ -643,21 +673,18 @@ fn encrypt_decrypt_no_dropout() -> googletest::Result<()> { let max_number_of_decryptors = 1; // Create common KAHE/VAHE instances. - let vahe = Rc::new( - ShellVahe::new(create_shell_ahe_config(max_number_of_decryptors).unwrap(), CONTEXT_STRING) - .unwrap(), - ); - let kahe = Rc::new( - ShellKahe::new(create_shell_kahe_config(&aggregation_config).unwrap(), CONTEXT_STRING) - .unwrap(), - ); + let vahe = Rc::new(ShellVahe::new( + create_shell_ahe_config(max_number_of_decryptors)?, + CONTEXT_STRING, + )?); + let kahe = + Rc::new(ShellKahe::new(create_shell_kahe_config(&aggregation_config)?, CONTEXT_STRING)?); // Create clients. let mut clients = vec![]; for _ in 0..NUM_CLIENTS { let client = - WillowV1Client::new_with_randomly_generated_seed(Rc::clone(&kahe), Rc::clone(&vahe)) - .unwrap(); + WillowV1Client::new_with_randomly_generated_seed(Rc::clone(&kahe), Rc::clone(&vahe))?; clients.push(client); } @@ -666,8 +693,7 @@ fn encrypt_decrypt_no_dropout() -> googletest::Result<()> { let mut decryptors = vec![]; for _ in 0..NUM_DECRYPTORS { let decryptor_state = DecryptorState::default(); - let decryptor = - WillowV1Decryptor::new_with_randomly_generated_seed(Rc::clone(&vahe)).unwrap(); + let decryptor = WillowV1Decryptor::new_with_randomly_generated_seed(Rc::clone(&vahe))?; decryptor_states.push(decryptor_state); decryptors.push(decryptor); } @@ -681,16 +707,23 @@ fn encrypt_decrypt_no_dropout() -> googletest::Result<()> { let verifier = WillowV1Verifier { vahe: Rc::clone(&vahe) }; let mut verifier_state = VerifierState::default(); - // Decryptors generate public key shares. - let mut public_key_shares = vec![]; + // Create coordinator. + let coord = WillowV1Coordinator { vahe: Rc::clone(&vahe) }; + let mut coord_state = CoordinatorState::default(); + + // Decryptors generate setup contributions. + let mut setup_contributions = vec![]; for i in 0..decryptors.len() { - let public_key_share = - decryptors[i].create_public_key_share(&mut decryptor_states[i]).unwrap(); - public_key_shares.push(public_key_share); + let contribution = decryptors[i].create_setup_contribution(&mut decryptor_states[i])?; + setup_contributions.push(contribution); } - // Aggregate public key shares directly. - let public_key = vahe.aggregate_public_key_shares(public_key_shares.iter()).unwrap(); + // Coordinator processes setup. + let verify_request = + coord.handle_setup_submissions(vec![], setup_contributions, &mut coord_state)?; + + // Reputable decryptor verifies and aggregates the public key. + let public_key = decryptors[0].verify_and_aggregate_key_contributions(verify_request)?; // Clients encrypt. let mut expected_output = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; @@ -704,7 +737,7 @@ fn encrypt_decrypt_no_dropout() -> googletest::Result<()> { HashMap::from([(default_id.as_str(), client_input_values.as_slice())]); let nonce = generate_random_nonce(); let client_message = - client.create_client_message(&client_plaintext, &public_key, &nonce).unwrap(); + client.create_client_message(&client_plaintext, &public_key, &nonce)?; client_messages.push(client_message); } @@ -715,27 +748,40 @@ fn encrypt_decrypt_no_dropout() -> googletest::Result<()> { for client_message in client_messages { // The client message is split and handled by the accumulator and verifier. let (ciphertext_contribution, decryption_request_contribution) = - accumulator.split_client_message(client_message).unwrap(); - verifier.verify_and_include(decryption_request_contribution, &mut verifier_state).unwrap(); + accumulator.split_client_message(client_message)?; + verifier.verify_and_include(decryption_request_contribution, &mut verifier_state)?; accumulator - .handle_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state) - .unwrap(); + .accumulate_ciphertext_contribution(ciphertext_contribution, &mut accumulator_state)?; } // Verifier creates the partial decryption request. - let pd_ct = verifier.create_partial_decryption_request(verifier_state).unwrap(); + let pd_ct = verifier.create_partial_decryption_request(verifier_state)?; + + // Coordinator prepares decryption request. + let pd_request = + coord.prepare_decryption_request(&pd_ct.partial_dec_ciphertext, &mut coord_state)?; // Decryptors perform partial decryption. + let mut partial_responses = vec![]; for i in 0..decryptors.len() { - let pd = decryptors[i] - .handle_partial_decryption_request(pd_ct.clone(), &mut decryptor_states[i]) - .unwrap(); - // Accumulator handles the partial decryption. - accumulator.handle_partial_decryption(pd, &mut accumulator_state).unwrap(); + let pd = decryptors[i].handle_partial_decryption_request( + pd_request.clone(), + Some(kahe.as_ref()), + &mut decryptor_states[i], + )?; + partial_responses.push(pd); } + coord.aggregate_partial_decryptions( + partial_responses, + Some(kahe.as_ref()), + &mut coord_state, + )?; + let finalized_pd = coord.finalize_partial_decryption(&mut coord_state)?; + // Accumulator recovers the aggregation result. - let aggregation_result = accumulator.recover_aggregation_result(&accumulator_state).unwrap(); + let aggregation_result = + accumulator.recover_aggregation_result(&accumulator_state, &finalized_pd)?; // Check that the (padded) result matches the client plaintext. verify_that!(aggregation_result.keys().collect::>(), container_eq([&default_id]))?;