Skip to content

Commit 2152ebf

Browse files
committed
feat: core/service keygen endpoint
1 parent ff17568 commit 2152ebf

File tree

18 files changed

+823
-102
lines changed

18 files changed

+823
-102
lines changed

core/grpc/proto/kms.v1.proto

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ message StandardKeySetConfig {
3434

3535
// Configuration for generating compressed vs uncompressed keys.
3636
// Compressed keys use XOF-seeded compression and are smaller.
37+
// (We may rename this to KeyGenPublicKeyConfig.)
3738
CompressedKeyConfig compressed_key_config = 3;
3839
}
3940

@@ -44,14 +45,18 @@ enum ComputeKeyType {
4445
}
4546

4647
message KeySetAddedInfo {
47-
// Must be set if KeyGenSecretKeyConfig::UseExisting is used
48+
// Must be set if KeyGenSecretKeyConfig::UseExistingCompressionSecretKey is used
4849
RequestId compression_keyset_id = 1;
4950

5051
// Must be set if KeySetType::DecompressionOnly is used
5152
RequestId from_keyset_id_decompression_only = 2;
5253

5354
// Must be set if KeySetType::DecompressionOnly is used
5455
RequestId to_keyset_id_decompression_only = 3;
56+
57+
// Must be set if KeyGenSecretKeyConfig::UseExisting is used
58+
RequestId existing_keyset_id = 4;
59+
RequestId existing_epoch_id = 5;
5560
}
5661

5762
// The keyset configuration message.

core/grpc/src/rpc_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,7 @@ mod tests {
11341134
assert_eq!(v1::ComputeKeyType::default(), v1::ComputeKeyType::Cpu);
11351135
assert_eq!(
11361136
v1::KeyGenSecretKeyConfig::default(),
1137-
v1::KeyGenSecretKeyConfig::Generate
1137+
v1::KeyGenSecretKeyConfig::GenerateAll
11381138
);
11391139
}
11401140

core/service/src/client/tests/centralized/key_gen_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ async fn test_decompression_key_gen_centralized() {
5959
standard_keyset_config: None,
6060
}),
6161
Some(KeySetAddedInfo {
62-
compression_keyset_id: None,
6362
from_keyset_id_decompression_only: Some(request_id_1.into()),
6463
to_keyset_id_decompression_only: Some(request_id_2.into()),
64+
..Default::default()
6565
}),
6666
)
6767
.await;
@@ -103,9 +103,9 @@ async fn default_decompression_key_gen_centralized() {
103103
standard_keyset_config: None,
104104
}),
105105
Some(KeySetAddedInfo {
106-
compression_keyset_id: None,
107106
from_keyset_id_decompression_only: Some(request_id_1.into()),
108107
to_keyset_id_decompression_only: Some(request_id_2.into()),
108+
..Default::default()
109109
}),
110110
)
111111
.await;

core/service/src/client/tests/common.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,31 @@ pub(crate) fn compressed_keygen_config() -> (Option<KeySetConfig>, Option<KeySet
4343
)
4444
}
4545

46+
/// Returns compressed keygen config that reuses existing secret key shares
47+
#[cfg(feature = "slow_tests")]
48+
pub(crate) fn compressed_from_existing_keygen_config(
49+
existing_keyset_id: &RequestId,
50+
existing_epoch_id: &kms_grpc::identifiers::EpochId,
51+
) -> (Option<KeySetConfig>, Option<KeySetAddedInfo>) {
52+
(
53+
Some(KeySetConfig {
54+
keyset_type: KeySetType::Standard.into(),
55+
standard_keyset_config: Some(kms_grpc::kms::v1::StandardKeySetConfig {
56+
compute_key_type: 0,
57+
secret_key_config: kms_grpc::kms::v1::KeyGenSecretKeyConfig::UseExisting.into(),
58+
compressed_key_config: CompressedKeyConfig::CompressedAll.into(),
59+
}),
60+
}),
61+
Some(KeySetAddedInfo {
62+
compression_keyset_id: None,
63+
from_keyset_id_decompression_only: None,
64+
to_keyset_id_decompression_only: None,
65+
existing_keyset_id: Some((*existing_keyset_id).into()),
66+
existing_epoch_id: Some((*existing_epoch_id).into()),
67+
}),
68+
)
69+
}
70+
4671
/// Returns decompression-only keygen config
4772
#[cfg(feature = "slow_tests")]
4873
pub(crate) fn decompression_keygen_config(
@@ -58,6 +83,8 @@ pub(crate) fn decompression_keygen_config(
5883
compression_keyset_id: None,
5984
from_keyset_id_decompression_only: Some((*from_keyset_id).into()),
6085
to_keyset_id_decompression_only: Some((*to_keyset_id).into()),
86+
existing_keyset_id: None,
87+
existing_epoch_id: None,
6188
}),
6289
)
6390
}

core/service/src/client/tests/threshold/common.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,16 @@ pub async fn threshold_insecure_key_gen_isolated(
293293
/// * `Ok(())` if preprocessing and key generation succeeded on all parties
294294
/// * `Err` if any party failed
295295
#[cfg(feature = "slow_tests")]
296+
#[allow(clippy::too_many_arguments)]
296297
pub async fn threshold_key_gen_secure_isolated(
297298
clients: &HashMap<u32, CoreServiceEndpointClient<Channel>>,
298299
preproc_id: &kms_grpc::RequestId,
299300
keygen_id: &kms_grpc::RequestId,
300301
params: kms_grpc::kms::v1::FheParameter,
302+
keyset_config: Option<kms_grpc::kms::v1::KeySetConfig>,
303+
keyset_added_info: Option<kms_grpc::kms::v1::KeySetAddedInfo>,
304+
context_id: Option<kms_grpc::kms::v1::RequestId>,
305+
epoch_id: Option<kms_grpc::kms::v1::RequestId>,
301306
) -> anyhow::Result<()> {
302307
use crate::dummy_domain;
303308
use crate::testing::helpers::domain_to_msg;
@@ -314,9 +319,9 @@ pub async fn threshold_key_gen_secure_isolated(
314319
request_id: Some((*preproc_id).into()),
315320
params: params as i32,
316321
domain: Some(domain_msg.clone()),
317-
keyset_config: None,
318-
context_id: None,
319-
epoch_id: None,
322+
keyset_config,
323+
context_id: context_id.clone(),
324+
epoch_id: epoch_id.clone(),
320325
};
321326
preproc_tasks.spawn(async move {
322327
cur_client
@@ -353,10 +358,10 @@ pub async fn threshold_key_gen_secure_isolated(
353358
params: Some(params as i32),
354359
preproc_id: Some((*preproc_id).into()),
355360
domain: Some(domain_msg.clone()),
356-
keyset_config: None,
357-
keyset_added_info: None,
358-
context_id: None,
359-
epoch_id: None,
361+
keyset_config,
362+
keyset_added_info: keyset_added_info.clone(),
363+
context_id: context_id.clone(),
364+
epoch_id: epoch_id.clone(),
360365
};
361366
keygen_tasks
362367
.spawn(async move { cur_client.key_gen(tonic::Request::new(keygen_req)).await });

core/service/src/client/tests/threshold/custodian_backup_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ async fn decrypt_after_recovery(amount_custodians: usize, threshold: u32) {
389389
&mut kms_servers,
390390
&mut kms_clients,
391391
&mut internal_client,
392+
None,
392393
&req_key_id,
393394
None,
394395
vec![TestingPlaintext::U8(u8::MAX)],

core/service/src/client/tests/threshold/key_gen_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ pub(crate) async fn preproc_and_keygen(
971971
&mut kms_servers,
972972
&mut kms_clients,
973973
&mut internal_client,
974+
None,
974975
key_id,
975976
None,
976977
vec![TestingPlaintext::U8(u8::MAX)],
@@ -1036,6 +1037,7 @@ pub(crate) async fn preproc_and_keygen(
10361037
&mut kms_servers,
10371038
&mut kms_clients,
10381039
&mut internal_client,
1040+
None,
10391041
key_id,
10401042
None,
10411043
vec![TestingPlaintext::U8(u8::MAX)],

core/service/src/client/tests/threshold/key_gen_tests_isolated.rs

Lines changed: 179 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,17 @@ async fn secure_threshold_keygen_isolated() -> Result<()> {
137137
let keygen_id = derive_request_id("secure_threshold_keygen")?;
138138

139139
// Run secure key generation with preprocessing
140-
threshold_key_gen_secure_isolated(&env.clients, &preproc_id, &keygen_id, FheParameter::Test)
141-
.await?;
140+
threshold_key_gen_secure_isolated(
141+
&env.clients,
142+
&preproc_id,
143+
&keygen_id,
144+
FheParameter::Test,
145+
None,
146+
None,
147+
None,
148+
None,
149+
)
150+
.await?;
142151

143152
// Verify key was generated on all parties
144153
for client in env.all_clients() {
@@ -374,6 +383,170 @@ async fn secure_threshold_keygen_crash_preprocessing_isolated() -> Result<()> {
374383
Ok(())
375384
}
376385

386+
/// Test secure threshold compressed key generation from existing secret shares.
387+
///
388+
/// Generates a standard keyset first, then performs compressed key generation
389+
/// reusing the existing secret key shares from the first keygen. This validates
390+
/// the end-to-end flow of compressed keygen from existing secrets through the
391+
/// gRPC service layer.
392+
///
393+
/// **Workflow:**
394+
/// 1. Standard keygen (preprocessing + online) to produce secret shares
395+
/// 2. Preprocessing for compressed keygen from existing
396+
/// 3. Compressed keygen from existing shares
397+
/// 4. Verify both keygens completed on all parties
398+
///
399+
/// **Requires:**
400+
/// - `slow_tests` feature flag
401+
///
402+
/// **Run with:** `cargo test --lib --features slow_tests,testing secure_threshold_compressed_keygen_from_existing_isolated`
403+
#[tokio::test]
404+
#[cfg(feature = "slow_tests")]
405+
async fn secure_threshold_compressed_keygen_from_existing_isolated() -> Result<()> {
406+
use crate::client::tests::common::compressed_from_existing_keygen_config;
407+
use crate::consts::DEFAULT_EPOCH_ID;
408+
409+
let env = ThresholdTestEnv::builder()
410+
.with_test_name("compressed_from_existing_keygen")
411+
.with_party_count(4)
412+
.with_threshold(1)
413+
.with_prss()
414+
.build()
415+
.await?;
416+
417+
let clients = &env.clients;
418+
419+
// Step 1: Standard keygen (preprocessing + online)
420+
let preproc_id_1 = derive_request_id("compressed_existing_preproc_1")?;
421+
let keygen_id_1 = derive_request_id("compressed_existing_keygen_1")?;
422+
423+
threshold_key_gen_secure_isolated(
424+
clients,
425+
&preproc_id_1,
426+
&keygen_id_1,
427+
FheParameter::Test,
428+
None,
429+
None,
430+
None,
431+
None,
432+
)
433+
.await?;
434+
435+
// Verify standard keygen completed on all parties
436+
for client in env.all_clients() {
437+
let mut cur_client = client.clone();
438+
let result = cur_client
439+
.get_key_gen_result(tonic::Request::new(keygen_id_1.into()))
440+
.await?;
441+
assert_eq!(result.into_inner().request_id, Some(keygen_id_1.into()));
442+
}
443+
444+
// Step 2: Compressed keygen from existing secret shares (preprocessing + online)
445+
let preproc_id_2 = derive_request_id("compressed_existing_preproc_2")?;
446+
let keygen_id_2 = derive_request_id("compressed_existing_keygen_2")?;
447+
448+
let (keyset_config, keyset_added_info) =
449+
compressed_from_existing_keygen_config(&keygen_id_1, &DEFAULT_EPOCH_ID);
450+
451+
threshold_key_gen_secure_isolated(
452+
clients,
453+
&preproc_id_2,
454+
&keygen_id_2,
455+
FheParameter::Test,
456+
keyset_config,
457+
keyset_added_info,
458+
None,
459+
None,
460+
)
461+
.await?;
462+
463+
// Verify compressed keygen completed on all parties
464+
for client in env.all_clients() {
465+
let mut cur_client = client.clone();
466+
let result = cur_client
467+
.get_key_gen_result(tonic::Request::new(keygen_id_2.into()))
468+
.await?;
469+
assert_eq!(result.into_inner().request_id, Some(keygen_id_2.into()));
470+
}
471+
472+
// Do distributed decryption to verify the generated key is ok
473+
// TODO this could be refactored
474+
use crate::client::tests::threshold::public_decryption_tests::run_decryption_threshold;
475+
use crate::util::key_setup::test_tools::{EncryptionConfig, TestingPlaintext};
476+
let material_dir = env.material_dir;
477+
let mut servers = env.servers;
478+
let mut clients = env.clients;
479+
480+
let material_path = material_dir.path();
481+
let pub_storage_prefixes = &crate::consts::PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..4];
482+
483+
// Create internal client for decryption
484+
let mut pub_storage_map = std::collections::HashMap::new();
485+
for (i, prefix) in pub_storage_prefixes.iter().enumerate() {
486+
pub_storage_map.insert(
487+
(i + 1) as u32,
488+
FileStorage::new(Some(material_path), StorageType::PUB, prefix.as_deref())?,
489+
);
490+
}
491+
let client_storage = FileStorage::new(Some(material_path), StorageType::CLIENT, None)?;
492+
let mut internal_client = crate::client::client_wasm::Client::new_client(
493+
client_storage,
494+
pub_storage_map,
495+
&crate::consts::TEST_PARAM,
496+
None,
497+
)
498+
.await?;
499+
500+
// Run ddec with the new keyset
501+
run_decryption_threshold(
502+
4,
503+
&mut servers,
504+
&mut clients,
505+
&mut internal_client,
506+
None,
507+
&keygen_id_2,
508+
None,
509+
vec![TestingPlaintext::U32(66)],
510+
EncryptionConfig {
511+
compression: true,
512+
precompute_sns: true,
513+
},
514+
None,
515+
1,
516+
Some(material_path),
517+
true,
518+
)
519+
.await;
520+
521+
// Run ddec by encrypting using the old public key but
522+
// still the new shares from the new keyset
523+
run_decryption_threshold(
524+
4,
525+
&mut servers,
526+
&mut clients,
527+
&mut internal_client,
528+
Some(&keygen_id_1),
529+
&keygen_id_2,
530+
None,
531+
vec![TestingPlaintext::U32(55)],
532+
EncryptionConfig {
533+
compression: true,
534+
precompute_sns: true,
535+
},
536+
None,
537+
1,
538+
Some(material_path),
539+
false, // we do not used compressed_keys since that was the old public key
540+
)
541+
.await;
542+
543+
for (_, server) in servers {
544+
server.assert_shutdown().await;
545+
}
546+
547+
Ok(())
548+
}
549+
377550
/// Test insecure threshold decompression key generation with decryption validation.
378551
///
379552
/// Generates two regular keysets using insecure mode, then generates a decompression key
@@ -480,6 +653,8 @@ async fn test_insecure_threshold_decompression_keygen_isolated() -> Result<()> {
480653
compression_keyset_id: None,
481654
from_keyset_id_decompression_only: Some(key_id_1.into()),
482655
to_keyset_id_decompression_only: Some(key_id_2.into()),
656+
existing_keyset_id: None,
657+
existing_epoch_id: None,
483658
}),
484659
context_id: None,
485660
epoch_id: None,
@@ -561,12 +736,13 @@ async fn test_insecure_threshold_decompression_keygen_isolated() -> Result<()> {
561736
&mut servers,
562737
&mut clients,
563738
&mut internal_client,
739+
None,
564740
&key_id_1,
565741
None,
566742
vec![TestingPlaintext::U32(42)],
567743
EncryptionConfig {
568744
compression: true,
569-
precompute_sns: false,
745+
precompute_sns: true,
570746
},
571747
None,
572748
1,

0 commit comments

Comments
 (0)