Skip to content

Commit 1bb48e9

Browse files
mathsjamescopybara-github
authored andcommitted
Add traits and messages for multi-decryptor secure aggregation.
Introduces traits for the Coordinator as well as reputable and non-reputable decryptors. Introduces all messages that are required to be sent between them, though most protos and functions for (de)serialization are not included yet. PiperOrigin-RevId: 884415715
1 parent 087f29f commit 1bb48e9

12 files changed

Lines changed: 443 additions & 41 deletions

File tree

willow/benches/shell_benchmarks.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,10 @@ fn setup_server_recover_aggregation_result(args: &Args) -> ServerRecoverInputs {
324324
let pd_ct = inputs.verifier.create_partial_decryption_request(inputs.verifier_state).unwrap();
325325

326326
// Decryptor creates partial decryption.
327-
let pd =
328-
inputs.decryptor.handle_partial_decryption_request(pd_ct, &inputs.decryptor_state).unwrap();
327+
let pd = inputs
328+
.decryptor
329+
.handle_partial_decryption_request(pd_ct, &mut inputs.decryptor_state)
330+
.unwrap();
329331

330332
// Server handles the partial decryption.
331333
inputs.server.handle_partial_decryption(pd, &mut inputs.server_state).unwrap();
@@ -384,7 +386,7 @@ fn run_decryptor_partial_decryption(inputs: &mut DecryptorInputs) {
384386
.decryptor
385387
.handle_partial_decryption_request(
386388
black_box(inputs.partial_decryption_request.clone()),
387-
black_box(&inputs.decryptor_state),
389+
black_box(&mut inputs.decryptor_state),
388390
)
389391
.unwrap();
390392
let _ = black_box(res); // Prevent optimization.

willow/proto/willow/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ proto_library(
109109
name = "messages_proto",
110110
srcs = ["messages.proto"],
111111
deps = [
112+
":aggregation_config_proto",
112113
"//willow/proto/shell:shell_ciphertexts_proto",
113114
"//willow/proto/zk:proofs_proto",
114115
],

willow/proto/willow/messages.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ syntax = "proto3";
1717
package secure_aggregation.willow;
1818

1919
import "willow/proto/shell/ciphertexts.proto";
20+
import "willow/proto/willow/aggregation_config.proto";
2021
import "willow/proto/zk/proofs.proto";
2122

2223
option java_multiple_files = true;
@@ -32,10 +33,13 @@ message ClientMessage {
3233

3334
message PartialDecryptionRequest {
3435
ShellAhePartialDecCiphertext partial_dec_ciphertext = 1;
36+
AggregationConfigProto aggregation_config = 2;
3537
}
3638

3739
message PartialDecryptionResponse {
3840
ShellAhePartialDecryption partial_decryption = 1;
41+
// Noise contribution to the final aggregated result.
42+
CiphertextContribution dp_ciphertext_contribution = 2;
3943
}
4044

4145
message CiphertextContribution {
@@ -51,6 +55,7 @@ message DecryptionRequestContribution {
5155

5256
message DecryptorStateProto {
5357
ShellAheSecretKeyShare sk_share = 1;
58+
AggregationConfigProto aggregation_config = 2;
5459
}
5560

5661
message ServerStateProto {

willow/protocol/BUILD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ rust_library(
2929
deps = [
3030
"@protobuf//rust:protobuf",
3131
"//ffi_utils:status",
32+
"//willow/api:aggregation_config",
3233
"//willow/api:proto_serialization_traits",
3334
"//willow/crypto:ahe_traits",
3435
"//willow/crypto:kahe_traits",
3536
"//willow/crypto:vahe_traits",
3637
"//willow/proto/shell:shell_ciphertexts_rust_proto",
38+
"//willow/proto/willow:aggregation_config_rust_proto",
3739
"//willow/proto/willow:messages_rust_proto",
3840
"//willow/proto/zk:proofs_rust_proto",
3941
],
@@ -74,6 +76,8 @@ rust_library(
7476
deps = [
7577
":messages",
7678
"//ffi_utils:status",
79+
"//willow/crypto:ahe_traits",
80+
"//willow/crypto:kahe_traits",
7781
"//willow/crypto:vahe_traits",
7882
],
7983
)
@@ -130,8 +134,10 @@ rust_test(
130134
"@crate_index//:googletest",
131135
"//willow/api:proto_serialization_traits",
132136
"//willow/crypto:ahe_traits",
137+
"//willow/crypto:shell_kahe",
133138
"//willow/crypto:shell_parameters",
134139
"//willow/crypto:shell_vahe",
140+
"//willow/testing_utils",
135141
],
136142
)
137143

@@ -145,9 +151,13 @@ rust_library(
145151
":messages",
146152
"@protobuf//rust:protobuf",
147153
"//ffi_utils:status",
154+
"//willow/api:aggregation_config",
148155
"//willow/api:proto_serialization_traits",
149156
"//willow/crypto:ahe_traits",
157+
"//willow/crypto:kahe_traits",
150158
"//willow/crypto:prng_traits",
159+
"//willow/crypto:shell_kahe",
160+
"//willow/crypto:shell_parameters",
151161
"//willow/crypto:vahe_traits",
152162
"//willow/proto/shell:shell_ciphertexts_rust_proto",
153163
"//willow/proto/willow:messages_rust_proto",

willow/protocol/decryptor_traits.rs

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2025 Google LLC
1+
// Copyright 2026 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -12,7 +12,13 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use messages::{DecryptorPublicKeyShare, PartialDecryptionRequest, PartialDecryptionResponse};
15+
use ahe_traits::AheBase;
16+
use kahe_traits::KaheBase;
17+
use messages::{
18+
DecryptorPublicKey, DecryptorPublicKeyShare, PartialDecryptionRequest,
19+
PartialDecryptionResponse, RecoveryRequest, RecoveryResponse, SetupContribution,
20+
VerifyKeyContributionsRequest,
21+
};
1622
use status::StatusError;
1723
use vahe_traits::HasVahe;
1824

@@ -28,11 +34,142 @@ pub trait SecureAggregationDecryptor: HasVahe {
2834
decryptor_state: &mut Self::DecryptorState,
2935
) -> Result<DecryptorPublicKeyShare<<Self as HasVahe>::Vahe>, StatusError>;
3036

37+
type Kahe: KaheBase;
38+
3139
/// Handles a partial decryption request received from the Server. Returns a
3240
/// partial decryption to the Server.
3341
fn handle_partial_decryption_request(
3442
&self,
3543
partial_decryption_request: PartialDecryptionRequest<<Self as HasVahe>::Vahe>,
36-
decryptor_state: &Self::DecryptorState,
37-
) -> Result<PartialDecryptionResponse<<Self as HasVahe>::Vahe>, StatusError>;
44+
decryptor_state: &mut Self::DecryptorState,
45+
) -> Result<PartialDecryptionResponse<Self::Kahe, <Self as HasVahe>::Vahe>, StatusError>;
46+
}
47+
48+
/// Trait for reputable/non-recoverable decryptors (e.g. TEEs) in a multi-decryptor committee.
49+
pub trait SecureAggregationBaseMultiDecryptor: HasVahe {
50+
/// The state held by the Decryptor between messages.
51+
type DecryptorState: Default;
52+
53+
/// Creates a public key share, a ZK proof of knowledge of the secret key,
54+
/// and encrypted shares of the randomness used for key generation.
55+
///
56+
/// The randomness shares are encrypted for other committee members.
57+
fn create_setup_contribution(
58+
&self,
59+
decryptor_state: &mut Self::DecryptorState,
60+
) -> Result<SetupContribution<Self::Vahe>, StatusError>;
61+
62+
/// Creates a public key share to be sent to the Server, updating the
63+
/// decryptor state.
64+
fn create_public_key_share(
65+
&self,
66+
decryptor_state: &mut Self::DecryptorState,
67+
) -> Result<DecryptorPublicKeyShare<<Self as HasVahe>::Vahe>, StatusError>;
68+
69+
/// Handles a partial decryption request received from the Server. Returns a
70+
/// partial decryption to the Server.
71+
fn handle_partial_decryption_request<Kahe: KaheBase>(
72+
&self,
73+
partial_decryption_request: PartialDecryptionRequest<<Self as HasVahe>::Vahe>,
74+
kahe: Option<&Kahe>,
75+
decryptor_state: &mut Self::DecryptorState,
76+
) -> Result<PartialDecryptionResponse<Kahe, <Self as HasVahe>::Vahe>, StatusError>;
77+
}
78+
79+
/// Trait for the reputable decryptors in a multi-decryptor committee.
80+
///
81+
/// Reputable decryptors are assumed to be stable and do not share their
82+
/// randomness for recovery.
83+
pub trait SecureAggregationReputableDecryptor: SecureAggregationBaseMultiDecryptor {
84+
/// Verifies the ZK proofs of knowledge of the secret key for all public key
85+
/// shares, and returns the aggregated public key. Calling code should sign
86+
/// the aggregated public key for the aggregation.
87+
fn verify_and_aggregate_key_contributions(
88+
&self,
89+
request: VerifyKeyContributionsRequest<<Self as HasVahe>::Vahe>,
90+
) -> Result<DecryptorPublicKey<<Self as HasVahe>::Vahe>, StatusError>;
91+
}
92+
93+
/// Trait for the non-reputable decryptors in a multi-decryptor committee.
94+
pub trait SecureAggregationNonReputableMultiDecryptor: SecureAggregationBaseMultiDecryptor {
95+
/// Handles a request to decrypt shares of dropped decryptors.
96+
///
97+
/// The decryptor should verify they are not being asked to decrypt more than
98+
/// the allowed threshold of shares.
99+
fn handle_recovery_request(
100+
&self,
101+
recovery_request: RecoveryRequest,
102+
decryptor_state: &mut Self::DecryptorState,
103+
) -> Result<RecoveryResponse, StatusError>;
104+
}
105+
106+
/// Trait for the protocol coordinator managing the multi-decryptor committee.
107+
///
108+
/// The coordinator manages protocol flow and aggregates messages from all
109+
/// decryptors. The coordinator itself does not contribute to the public key.
110+
pub trait SecureAggregationCoordinator: HasVahe {
111+
/// The state held by the Coordinator between protocol rounds.
112+
type CoordinatorState: Default;
113+
114+
/// Verifies and stores setup contributions from all decryptors.
115+
fn handle_setup_submissions(
116+
&self,
117+
non_reputable_contributions: Vec<SetupContribution<Self::Vahe>>,
118+
reputable_contributions: Vec<SetupContribution<Self::Vahe>>,
119+
coordinator_state: &mut Self::CoordinatorState,
120+
) -> Result<(), StatusError>;
121+
122+
/// Aggregates all public key shares into the final multi-decryptor public key.
123+
///
124+
/// In the multiple reputable decryptor case, the coordinator also creates a verification
125+
/// request to be sent to reputable decryptors to check the proofs of all key contributions.
126+
fn aggregate_public_key_and_create_verification_request(
127+
&self,
128+
coordinator_state: &mut Self::CoordinatorState,
129+
) -> Result<
130+
(VerifyKeyContributionsRequest<Self::Vahe>, DecryptorPublicKey<Self::Vahe>),
131+
StatusError,
132+
>;
133+
134+
/// Combines the verifier's ciphertext half with the accumulated AHE components.
135+
///
136+
/// The result should be forwarded to decryptors for partial decryption.
137+
fn prepare_decryption_request(
138+
&self,
139+
verifier_ciphertext: &<Self::Vahe as AheBase>::PartialDecCiphertext,
140+
coordinator_state: &mut Self::CoordinatorState,
141+
) -> Result<PartialDecryptionRequest<Self::Vahe>, StatusError>;
142+
143+
/// Accumulates partial decryptions from responding decryptors.
144+
fn aggregate_partial_decryptions<Kahe: KaheBase>(
145+
&self,
146+
partial_responses: Vec<PartialDecryptionResponse<Kahe, Self::Vahe>>,
147+
kahe: Option<&Kahe>,
148+
coordinator_state: &mut Self::CoordinatorState,
149+
) -> Result<(), StatusError>;
150+
151+
/// Creates recovery requests for surviving decryptors to decrypt shares of dropped client
152+
/// decryptors.
153+
///
154+
/// If the vector is empty, there are no dropped decryptors to recover and
155+
/// recover_dropped_decryptors can be called immediately with an empty vector.
156+
fn create_recovery_requests(
157+
&self,
158+
coordinator_state: &mut Self::CoordinatorState,
159+
) -> Result<Vec<RecoveryRequest>, StatusError>;
160+
161+
/// Finalizes the decryption by recovering randomness from dropped decryptors.
162+
///
163+
/// Uses decrypted shares from survivors to simulate missing partial decryptions.
164+
fn recover_dropped_decryptors(
165+
&self,
166+
recovery_responses: Vec<RecoveryResponse>,
167+
coordinator_state: &mut Self::CoordinatorState,
168+
) -> Result<(), StatusError>;
169+
170+
/// Returns the resulting plaintext from the final decryption, if available.
171+
fn get_plaintext(
172+
&self,
173+
coordinator_state: &mut Self::CoordinatorState,
174+
) -> Result<<Self::Vahe as AheBase>::Plaintext, StatusError>;
38175
}

0 commit comments

Comments
 (0)