Skip to content

Commit 4145e0d

Browse files
tholopcopybara-github
authored andcommitted
Create Shell decryptor util to test client functionality.
PiperOrigin-RevId: 845392033
1 parent 49c639e commit 4145e0d

File tree

4 files changed

+140
-44
lines changed

4 files changed

+140
-44
lines changed

willow/src/testing_utils/BUILD

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,25 @@ rust_test(
8989
"@crate_index//:googletest",
9090
],
9191
)
92+
93+
rust_library(
94+
name = "shell_testing_decryptor",
95+
testonly = 1,
96+
srcs = [
97+
"shell_testing_decryptor.rs",
98+
],
99+
deps = [
100+
":shell_testing_parameters",
101+
"//shell_wrapper:status",
102+
"//willow/src/api:aggregation_config",
103+
"//willow/src/shell:kahe_shell",
104+
"//willow/src/shell:parameters_shell",
105+
"//willow/src/shell:single_thread_hkdf",
106+
"//willow/src/shell:vahe_shell",
107+
"//willow/src/traits:ahe_traits",
108+
"//willow/src/traits:kahe_traits",
109+
"//willow/src/traits:messages",
110+
"//willow/src/traits:prng_traits",
111+
"//willow/src/traits:vahe_traits",
112+
],
113+
)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
use aggregation_config::AggregationConfig;
18+
use ahe_traits::{AheBase, AheKeygen, PartialDec};
19+
use kahe_shell::ShellKahe;
20+
use kahe_traits::{KaheBase, KaheDecrypt, TrySecretKeyFrom};
21+
use messages::ClientMessage;
22+
use parameters_shell::create_shell_configs;
23+
use prng_traits::SecurePrng;
24+
use single_thread_hkdf::SingleThreadHkdfPrng;
25+
use status::{StatusError, StatusErrorCode};
26+
use vahe_shell::ShellVahe;
27+
use vahe_traits::Recover;
28+
29+
/// Basic implementation of a single decryptor that uses Shell operations directly. Useful for
30+
/// testing Shell clients, by checking that encrypted messages can be decrypted properly.
31+
pub struct ShellTestingDecryptor {
32+
kahe: ShellKahe,
33+
vahe: ShellVahe,
34+
prng: SingleThreadHkdfPrng,
35+
secret_key: Option<<ShellVahe as AheBase>::SecretKeyShare>,
36+
}
37+
38+
impl ShellTestingDecryptor {
39+
/// Creates a new ShellTestingDecryptor, using the given context string to seed KAHE and AHE
40+
/// public parameters.
41+
pub fn new(
42+
aggregation_config: &AggregationConfig,
43+
context_string: &[u8],
44+
) -> Result<ShellTestingDecryptor, StatusError> {
45+
let (kahe_config, ahe_config) = create_shell_configs(aggregation_config)?;
46+
let kahe = ShellKahe::new(kahe_config, context_string)?;
47+
let vahe = ShellVahe::new(ahe_config, context_string)?;
48+
let seed = SingleThreadHkdfPrng::generate_seed()?;
49+
let prng = SingleThreadHkdfPrng::create(&seed)?;
50+
Ok(ShellTestingDecryptor { kahe, vahe, prng, secret_key: None })
51+
}
52+
53+
/// Generates a new AHE public key, and stores the corresponding secret key.
54+
pub fn generate_public_key(
55+
&mut self,
56+
) -> Result<<ShellVahe as AheBase>::PublicKey, StatusError> {
57+
let (sk_share, pk_share, _) = self.vahe.key_gen(&mut self.prng)?;
58+
self.secret_key = Some(sk_share);
59+
let public_key = self.vahe.aggregate_public_key_shares(&[pk_share])?;
60+
Ok(public_key)
61+
}
62+
63+
/// Decrypts a client message using the stored AHE secret key, by recovering the KAHE key from
64+
/// the AHE ciphertext and then decrypting the KAHE ciphertext. Does not verify the client proof
65+
/// contained in the message.
66+
pub fn decrypt(
67+
&mut self,
68+
client_message: &ClientMessage<ShellKahe, ShellVahe>,
69+
) -> Result<<ShellKahe as KaheBase>::Plaintext, StatusError> {
70+
let decryption_request =
71+
self.vahe.get_partial_dec_ciphertext(&client_message.ahe_ciphertext)?;
72+
let rest_of_ciphertext =
73+
self.vahe.get_recover_ciphertext(&client_message.ahe_ciphertext)?;
74+
match &self.secret_key {
75+
None => Err(StatusError::new_with_current_location(
76+
StatusErrorCode::InvalidArgument,
77+
"No secret key available",
78+
)),
79+
Some(sk_share) => {
80+
let partial_decryption =
81+
self.vahe.partial_decrypt(&decryption_request, sk_share, &mut self.prng)?;
82+
let decrypted_kahe_key =
83+
self.vahe.recover(&partial_decryption, &rest_of_ciphertext, None)?;
84+
let decrypted_kahe_key = self.kahe.try_secret_key_from(decrypted_kahe_key)?;
85+
let decrypted_plaintext =
86+
self.kahe.decrypt(&client_message.kahe_ciphertext, &decrypted_kahe_key)?;
87+
Ok(decrypted_plaintext)
88+
}
89+
}
90+
}
91+
}

willow/src/willow_v1/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ rust_test(
4343
"@crate_index//:googletest",
4444
"//willow/src/api:aggregation_config",
4545
"//willow/src/shell:kahe_shell",
46+
"//willow/src/shell:parameters_shell",
4647
"//willow/src/shell:single_thread_hkdf",
4748
"//willow/src/shell:vahe_shell",
4849
"//willow/src/testing_utils",
50+
"//willow/src/testing_utils:shell_testing_decryptor",
4951
"//willow/src/testing_utils:shell_testing_parameters",
5052
"//willow/src/traits:prng_traits",
5153
],

willow/src/willow_v1/client.rs

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use client_traits::SecureAggregationClient;
1616
use kahe_traits::{HasKahe, KaheBase, KaheEncrypt, KaheKeygen, TrySecretKeyInto};
1717
use messages::{ClientMessage, DecryptorPublicKey};
18-
use prng_traits::SecurePrng;
1918
use vahe_traits::{HasVahe, VaheBase, VerifiableEncrypt};
2019

2120
/// Lightweight client directly exposing KAHE/VAHE types.
@@ -84,18 +83,17 @@ mod test {
8483
use super::*;
8584

8685
use aggregation_config::AggregationConfig;
87-
use ahe_traits::{AheBase, AheKeygen, PartialDec};
86+
use ahe_traits::AheBase;
8887
use googletest::prelude::container_eq;
8988
use googletest::{gtest, verify_eq, verify_that};
9089
use kahe_shell::ShellKahe;
91-
use kahe_traits::{KaheDecrypt, TrySecretKeyFrom};
90+
use parameters_shell::create_shell_configs;
9291
use prng_traits::SecurePrng;
93-
use shell_testing_parameters::{make_ahe_config, make_kahe_config};
92+
use shell_testing_decryptor::ShellTestingDecryptor;
9493
use single_thread_hkdf::SingleThreadHkdfPrng;
9594
use std::collections::HashMap;
9695
use testing_utils::generate_random_nonce;
9796
use vahe_shell::ShellVahe;
98-
use vahe_traits::Recover;
9997

10098
const CONTEXT_STRING: &[u8] = b"test_context_string";
10199

@@ -111,36 +109,27 @@ mod test {
111109
};
112110

113111
// Create a client.
114-
let kahe = ShellKahe::new(make_kahe_config(&aggregation_config), CONTEXT_STRING).unwrap();
115-
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING).unwrap();
112+
let (kahe_config, ahe_config) = create_shell_configs(&aggregation_config)?;
113+
let kahe = ShellKahe::new(kahe_config, CONTEXT_STRING)?;
114+
let vahe = ShellVahe::new(ahe_config, CONTEXT_STRING)?;
116115
let client_seed = SingleThreadHkdfPrng::generate_seed()?;
117116
let prng = SingleThreadHkdfPrng::create(&client_seed)?;
118117
let mut client = WillowV1Client { kahe, vahe, prng };
119118

120119
// Generate AHE keys.
121-
let kahe = ShellKahe::new(make_kahe_config(&aggregation_config), CONTEXT_STRING).unwrap();
122-
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING).unwrap();
123-
let seed = SingleThreadHkdfPrng::generate_seed()?;
124-
let mut prng = SingleThreadHkdfPrng::create(&seed)?;
125-
let (sk_share, pk_share, _) = vahe.key_gen(&mut prng)?;
126-
let public_key = vahe.aggregate_public_key_shares(&[pk_share])?;
120+
let mut testing_decryptor =
121+
ShellTestingDecryptor::new(&aggregation_config, CONTEXT_STRING)?;
122+
let public_key = testing_decryptor.generate_public_key()?;
127123

128124
// Create client message.
129125
let input_values = vec![1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1];
130126
let client_plaintext = HashMap::from([(default_id.as_str(), input_values.as_slice())]);
131127
let nonce = generate_random_nonce();
132128
let client_message =
133-
client.create_client_message(&client_plaintext, &public_key, &nonce).unwrap();
129+
client.create_client_message(&client_plaintext, &public_key, &nonce)?;
134130

135131
// Decrypt client message.
136-
let decryption_request = vahe.get_partial_dec_ciphertext(&client_message.ahe_ciphertext)?;
137-
let rest_of_ciphertext = vahe.get_recover_ciphertext(&client_message.ahe_ciphertext)?;
138-
let partial_decryption = vahe.partial_decrypt(&decryption_request, &sk_share, &mut prng)?;
139-
let decrypted_kahe_key = vahe.recover(&partial_decryption, &rest_of_ciphertext, None)?;
140-
let decrypted_kahe_key = kahe.try_secret_key_from(decrypted_kahe_key)?;
141-
let decrypted_plaintext =
142-
kahe.decrypt(&client_message.kahe_ciphertext, &decrypted_kahe_key)?;
143-
132+
let decrypted_plaintext = testing_decryptor.decrypt(&client_message)?;
144133
verify_that!(decrypted_plaintext.keys().collect::<Vec<_>>(), container_eq([&default_id]))?;
145134
let client_plaintext_length = client_plaintext.get(default_id.as_str()).unwrap().len();
146135
verify_eq!(
@@ -161,26 +150,25 @@ mod test {
161150
};
162151

163152
// Create a client.
164-
let kahe = ShellKahe::new(make_kahe_config(&aggregation_config), CONTEXT_STRING).unwrap();
165-
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING).unwrap();
153+
let (kahe_config, ahe_config) = create_shell_configs(&aggregation_config)?;
154+
let kahe = ShellKahe::new(kahe_config, CONTEXT_STRING)?;
155+
let vahe = ShellVahe::new(ahe_config, CONTEXT_STRING)?;
166156
let client1_seed = SingleThreadHkdfPrng::generate_seed()?;
167157
let prng = SingleThreadHkdfPrng::create(&client1_seed)?;
168158
let mut client1 = WillowV1Client { kahe, vahe, prng };
169159

170160
// Create a second client.
171-
let kahe = ShellKahe::new(make_kahe_config(&aggregation_config), CONTEXT_STRING).unwrap();
172-
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING).unwrap();
161+
let (kahe_config, ahe_config) = create_shell_configs(&aggregation_config)?;
162+
let kahe = ShellKahe::new(kahe_config, CONTEXT_STRING)?;
163+
let vahe = ShellVahe::new(ahe_config, CONTEXT_STRING)?;
173164
let client2_seed = SingleThreadHkdfPrng::generate_seed()?;
174165
let prng = SingleThreadHkdfPrng::create(&client2_seed)?;
175166
let mut client2 = WillowV1Client { kahe, vahe, prng };
176167

177168
// Generate AHE keys.
178-
let kahe = ShellKahe::new(make_kahe_config(&aggregation_config), CONTEXT_STRING).unwrap();
179-
let vahe = ShellVahe::new(make_ahe_config(), CONTEXT_STRING).unwrap();
180-
let seed = SingleThreadHkdfPrng::generate_seed()?;
181-
let mut prng = SingleThreadHkdfPrng::create(&seed)?;
182-
let (sk_share, pk_share, _) = vahe.key_gen(&mut prng)?;
183-
let public_key = vahe.aggregate_public_key_shares(&[pk_share])?;
169+
let mut testing_decryptor =
170+
ShellTestingDecryptor::new(&aggregation_config, CONTEXT_STRING)?;
171+
let public_key = testing_decryptor.generate_public_key()?;
184172

185173
// Create client messages.
186174
let input_values1 = vec![1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1];
@@ -190,30 +178,23 @@ mod test {
190178
let expected_output = vec![2, 3, 5, 7, 10, 14, 10, 9, 11, 11, 14, 8, 6, 9, 1];
191179
let nonce1 = generate_random_nonce();
192180
let mut client_message =
193-
client1.create_client_message(&client1_plaintext, &public_key, &nonce1).unwrap();
181+
client1.create_client_message(&client1_plaintext, &public_key, &nonce1)?;
194182
let nonce2 = generate_random_nonce();
195183
let extra_message =
196-
client2.create_client_message(&client2_plaintext, &public_key, &nonce2).unwrap();
184+
client2.create_client_message(&client2_plaintext, &public_key, &nonce2)?;
197185

198186
// Add extra message to the first client message.
199-
kahe.add_ciphertexts_in_place(
187+
client2.kahe.add_ciphertexts_in_place(
200188
&extra_message.kahe_ciphertext,
201189
&mut client_message.kahe_ciphertext,
202190
)?;
203-
vahe.add_ciphertexts_in_place(
191+
client2.vahe.add_ciphertexts_in_place(
204192
&extra_message.ahe_ciphertext,
205193
&mut client_message.ahe_ciphertext,
206194
)?;
207195

208196
// Decrypt client message.
209-
let decryption_request = vahe.get_partial_dec_ciphertext(&client_message.ahe_ciphertext)?;
210-
let rest_of_ciphertext = vahe.get_recover_ciphertext(&client_message.ahe_ciphertext)?;
211-
let partial_decryption = vahe.partial_decrypt(&decryption_request, &sk_share, &mut prng)?;
212-
let decrypted_kahe_key = vahe.recover(&partial_decryption, &rest_of_ciphertext, None)?;
213-
let decrypted_kahe_key = kahe.try_secret_key_from(decrypted_kahe_key)?;
214-
let decrypted_plaintext =
215-
kahe.decrypt(&client_message.kahe_ciphertext, &decrypted_kahe_key)?;
216-
197+
let decrypted_plaintext = testing_decryptor.decrypt(&client_message)?;
217198
verify_that!(decrypted_plaintext.keys().collect::<Vec<_>>(), container_eq([&default_id]))?;
218199
let client_plaintext_length = client1_plaintext.get(default_id.as_str()).unwrap().len();
219200
verify_eq!(

0 commit comments

Comments
 (0)