diff --git a/core-client/tests/integration/integration_test.rs b/core-client/tests/integration/integration_test.rs index e6f31ece5d..a0be27b710 100644 --- a/core-client/tests/integration/integration_test.rs +++ b/core-client/tests/integration/integration_test.rs @@ -388,8 +388,8 @@ async fn setup_isolated_threshold_cli_test_with_custodian_backup( /// /// # Note /// Uses Default FHE parameters (production-like, slower than Test params) with `ensure_default_prss=false`. -/// Internally uses `TestMaterialSpec::threshold_default_no_prss` — PRSS is excluded from -/// required material and is not used at all (no pre-generated PRSS needed). +/// Internally uses `TestMaterialSpec::threshold_default` — PRSS is bootstrapped at runtime +/// when `ensure_default_prss=true`. /// /// # Example /// ```no_run @@ -555,7 +555,7 @@ async fn setup_isolated_threshold_cli_test_impl_with_spec( let default_material_spec = match fhe_params { FheParameter::Default => { - kms_lib::testing::material::TestMaterialSpec::threshold_default_no_prss(party_count) + kms_lib::testing::material::TestMaterialSpec::threshold_default(party_count) } _ => kms_lib::testing::material::TestMaterialSpec::threshold_basic(party_count), }; diff --git a/core/service/src/client/test_tools.rs b/core/service/src/client/test_tools.rs index ca37c80693..6eb853ff18 100644 --- a/core/service/src/client/test_tools.rs +++ b/core/service/src/client/test_tools.rs @@ -1,5 +1,4 @@ -use crate::client::client_wasm::Client; -use crate::conf::{CoreConfig, Keychain, SecretSharingKeychain, init_conf}; +use crate::conf::{CoreConfig, init_conf}; use crate::conf::{ ServiceEndpoint, threshold::{PeerConf, ThresholdPartyConf}, @@ -11,26 +10,20 @@ use crate::engine::context_manager::create_default_centralized_context_in_storag use crate::engine::threshold::service::{RealThresholdKms, new_real_threshold_kms}; use crate::engine::{Shutdown, run_server}; use crate::grpc::MetaStoreStatusServiceImpl; -use crate::util::key_setup::test_tools::file_backup_vault; -use crate::util::key_setup::test_tools::setup::ensure_testing_material_exists; use crate::util::rate_limiter::RateLimiterConfig; use crate::vault::Vault; use crate::vault::storage::StorageExt; -use crate::vault::storage::{ - Storage, StorageType, crypto_material::get_core_signing_key, file::FileStorage, -}; +use crate::vault::storage::{Storage, crypto_material::get_core_signing_key, file::FileStorage}; use futures_util::FutureExt; use itertools::Itertools; use kms_grpc::kms_service::v1::core_service_endpoint_client::CoreServiceEndpointClient; use kms_grpc::kms_service::v1::core_service_endpoint_server::CoreServiceEndpointServer; use kms_grpc::rpc_types::KMSType; use std::collections::HashMap; -use std::path::Path; use std::str::FromStr; use std::sync::Arc; use test_utils::random_free_port::get_listeners_random_free_ports; use threshold_execution::endpoints::decryption::DecryptionMode; -use threshold_execution::tfhe_internals::parameters::DKGParams; use threshold_networking::grpc::GrpcServer; use tokio::task::{JoinHandle, JoinSet}; use tonic::server::NamedService; @@ -40,9 +33,6 @@ use tonic_health::pb::HealthCheckRequest; use tonic_health::pb::health_client::HealthClient; use tonic_health::server::HealthReporter; -#[cfg(feature = "slow_tests")] -use crate::util::key_setup::test_tools::setup::ensure_default_material_exists; - // Put gRPC size limit to 100 MB. // We need a high limit because ciphertexts may be large after SnS. const GRPC_MAX_MESSAGE_SIZE: usize = 100 * 1024 * 1024; @@ -804,48 +794,6 @@ pub(crate) async fn setup_centralized< (server_handle, client) } -/// Spin up a centralized KMS server with the custodian-flavored backup vault and -/// return the server handle, gRPC client, and internal client. -pub async fn centralized_custodian_handles( - param: &DKGParams, - rate_limiter_conf: Option, - test_data_path: Option<&Path>, - pub_storage_prefix: Option<&str>, - backup_storage_prefix: Option<&str>, -) -> (ServerHandle, CoreServiceEndpointClient, Client) { - let backup_vault = file_backup_vault( - Some(&Keychain::SecretSharing(SecretSharingKeychain {})), - test_data_path, - test_data_path, - pub_storage_prefix, - backup_storage_prefix, - ) - .await; - - let priv_storage = FileStorage::new(test_data_path, StorageType::PRIV, None).unwrap(); - let pub_storage = FileStorage::new(test_data_path, StorageType::PUB, None).unwrap(); - - ensure_testing_material_exists(test_data_path).await; - #[cfg(feature = "slow_tests")] - ensure_default_material_exists().await; - - let (kms_server, kms_client) = setup_centralized( - pub_storage, - priv_storage, - Some(backup_vault), - rate_limiter_conf, - ) - .await; - let pub_storage = HashMap::from_iter([( - 1, - FileStorage::new(test_data_path, StorageType::PUB, None).unwrap(), - )]); - let client_storage = FileStorage::new(test_data_path, StorageType::CLIENT, None).unwrap(); - let internal_client = Client::new_client(client_storage, pub_storage, param, None) - .await - .unwrap(); - (kms_server, kms_client, internal_client) -} /// Wait for a server to be ready for requests. I.e. wait until it enters the SERVING state. /// Note that this method may panic if the server does not become ready within a certain time frame. pub async fn await_server_ready(service_name: &str, port: u16) { diff --git a/core/service/src/client/tests/centralized/crs_gen_tests.rs b/core/service/src/client/tests/centralized/crs_gen_tests.rs index 87c41cfddf..ca3d99ac23 100644 --- a/core/service/src/client/tests/centralized/crs_gen_tests.rs +++ b/core/service/src/client/tests/centralized/crs_gen_tests.rs @@ -3,7 +3,6 @@ use crate::engine::base::safe_serialize_hash_element_versioned; use crate::testing::setup::CentralizedTestEnv; use crate::vault::storage::{StorageType, file::FileStorage}; use crate::{ - client::tests::common::TIME_TO_SLEEP_MS, consts::TEST_PARAM, cryptography::internal_crypto_types::WrappedDKGParams, dummy_domain, @@ -70,8 +69,6 @@ async fn crs_gen_centralized_manual( test_name: &str, params: Option, ) -> Result<()> { - // TODO(dp): remove this? - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; let env = CentralizedTestEnv::builder() .with_test_name(test_name) .with_backup_vault() @@ -168,7 +165,6 @@ pub async fn crs_gen_centralized( keygen: 1, new_epoch: 1, }; - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; let env = CentralizedTestEnv::builder() .with_test_name(test_name) .with_backup_vault() diff --git a/core/service/src/client/tests/centralized/custodian_backup_tests.rs b/core/service/src/client/tests/centralized/custodian_backup_tests.rs index 6d3d8d32a4..3bce5edb9d 100644 --- a/core/service/src/client/tests/centralized/custodian_backup_tests.rs +++ b/core/service/src/client/tests/centralized/custodian_backup_tests.rs @@ -2,23 +2,26 @@ use crate::backup::BackupCiphertext; use crate::backup::custodian::Custodian; use crate::backup::seed_phrase::custodian_from_seed_phrase; use crate::client::client_wasm::Client; -use crate::client::test_tools::{ServerHandle, centralized_custodian_handles}; +use crate::client::test_tools::ServerHandle; use crate::client::tests::centralized::crs_gen_tests::run_crs_centralized; use crate::client::tests::centralized::custodian_context_tests::run_new_cus_context; use crate::client::tests::centralized::key_gen_tests::run_key_gen_centralized; use crate::client::tests::centralized::public_decryption_tests::run_decryption_centralized; use crate::consts::{DEFAULT_EPOCH_ID, DEFAULT_MPC_CONTEXT, SAFE_SER_SIZE_LIMIT, SIGNING_KEY_ID}; -use crate::cryptography::signatures::{PrivateSigKey, PublicSigKey}; +use crate::cryptography::signatures::PublicSigKey; use crate::engine::context::ContextInfo; +use crate::testing::setup::CentralizedTestEnv; use crate::util::key_setup::test_tools::{EncryptionConfig, TestingPlaintext}; use crate::util::key_setup::test_tools::{ purge_backup, read_custodian_backup_files, read_custodian_backup_files_with_epoch, }; +use crate::vault::storage::crypto_material::data_exists_at_epoch; use crate::vault::storage::file::FileStorage; -use crate::vault::storage::{StorageType, read_context_at_id, read_versioned_at_request_id}; +use crate::vault::storage::{ + StorageType, delete_at_request_and_epoch_id, read_context_at_id, read_versioned_at_request_id, +}; use crate::{ - client::tests::common::TIME_TO_SLEEP_MS, cryptography::internal_crypto_types::WrappedDKGParams, - engine::base::derive_request_id, util::key_setup::test_tools::purge_priv, + cryptography::internal_crypto_types::WrappedDKGParams, engine::base::derive_request_id, }; use aes_prng::AesRng; use kms_grpc::kms::v1::{ @@ -47,19 +50,26 @@ struct CentralizedBackupTestEnv { internal_client: Option, mnemonics: Vec, req_new_cus: RequestId, - temp_dir: tempfile::TempDir, + material_dir: tempfile::TempDir, } impl CentralizedBackupTestEnv { async fn new(test_name: &str, amount_custodians: usize, threshold: u32) -> Self { let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - let temp_dir = tempfile::tempdir().unwrap(); - let test_path = Some(temp_dir.path()); let req_new_cus: RequestId = derive_request_id(test_name).unwrap(); - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; - let (kms_server, mut kms_client, mut internal_client) = - centralized_custodian_handles(&dkg_param, None, test_path, None, None).await; + let test_env = CentralizedTestEnv::builder() + .with_test_name(test_name) + .with_custodian_keychain() + .build() + .await + .unwrap(); + let mut internal_client = test_env.create_internal_client(&dkg_param).await.unwrap(); + let CentralizedTestEnv { + material_dir, + server: kms_server, + client: mut kms_client, + } = test_env; let mnemonics = run_new_cus_context( &mut kms_client, &mut internal_client, @@ -75,12 +85,12 @@ impl CentralizedBackupTestEnv { internal_client: Some(internal_client), mnemonics, req_new_cus, - temp_dir, + material_dir, } } fn test_path(&self) -> Option<&Path> { - Some(self.temp_dir.path()) + Some(self.material_dir.path()) } /// Shut down server and drop clients. The env remains usable for @@ -92,6 +102,36 @@ impl CentralizedBackupTestEnv { server.assert_shutdown().await; } } + + /// Spawn a fresh KMS server attached to this env's material directory. Use to assert state persists across server + /// lifetimes. The wrapper must outlive the returned pair. + async fn spawn_server_on_existing_material( + &self, + ) -> (ServerHandle, CoreServiceEndpointClient) { + CentralizedTestEnv::builder() + .with_custodian_keychain() + .from_path(self.material_dir.path()) + .await + .unwrap() + } + + /// Construct a fresh internal Client backed by this env's material dir. + async fn create_internal_client( + &self, + dkg_param: &threshold_execution::tfhe_internals::parameters::DKGParams, + ) -> Client { + let path = self.material_dir.path(); + let pub_storage = FileStorage::new(Some(path), StorageType::PUB, None).unwrap(); + let client_storage = FileStorage::new(Some(path), StorageType::CLIENT, None).unwrap(); + Client::new_client( + client_storage, + std::collections::HashMap::from([(1u32, pub_storage)]), + dkg_param, + None, + ) + .await + .unwrap() + } } #[tokio::test(flavor = "multi_thread")] @@ -123,9 +163,7 @@ async fn auto_update_backup(amount_custodians: usize, threshold: u32) { purge_backup(env.test_path(), &[None]).await; // Check that the backup is still there after reboot - let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - let (_kms_server, _kms_client, _internal_client) = - centralized_custodian_handles(&dkg_param, None, env.test_path(), None, None).await; + let (_kms_server, _kms_client) = env.spawn_server_on_existing_material().await; let _reread_backup = read_custodian_backup_files( env.test_path(), &env.req_new_cus, @@ -157,7 +195,7 @@ async fn backup_after_crs(amount_custodians: usize, threshold: u32) { &crs_req, FheParameter::Test, true, - Some(env.temp_dir.path()), + Some(env.material_dir.path()), ) .await; // Sleep briefly to allow backup to be written (since backup is done asynchronously after generation) @@ -177,9 +215,7 @@ async fn backup_after_crs(amount_custodians: usize, threshold: u32) { env.shutdown().await; // Check that the backup is still there and unmodified after reboot - let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - let (_kms_server, _kms_client, _internal_client) = - centralized_custodian_handles(&dkg_param, None, env.test_path(), None, None).await; + let (_kms_server, _kms_client) = env.spawn_server_on_existing_material().await; let reread_crss = read_custodian_backup_files_with_epoch( env.test_path(), &env.req_new_cus, @@ -218,33 +254,41 @@ async fn decrypt_after_recovery(amount_custodians: usize, threshold: u32) { FheParameter::Test, None, None, - Some(env.temp_dir.path()), + Some(env.material_dir.path()), ) .await; env.shutdown().await; let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - // Read the private signing key for reference - let priv_store = FileStorage::new(env.test_path(), StorageType::PRIV, None).unwrap(); - let sig_key: PrivateSigKey = read_versioned_at_request_id( - &priv_store, - &SIGNING_KEY_ID, - &PrivDataType::SigningKey.to_string(), + // Delete only the FHE private key for this `key_id`, leaving signing keys intact so the server can boot without + // help. The test then verifies that custodian backup recovery restores the deleted FHE key (proven by a successful + // decryption call at the end). + let mut priv_storage = FileStorage::new(env.test_path(), StorageType::PRIV, None).unwrap(); + delete_at_request_and_epoch_id( + &mut priv_storage, + &key_id, + &epoch_id, + &PrivDataType::FhePrivateKey.to_string(), ) .await .unwrap(); + // Sanity check that the key is indeed gone. + assert!( + !data_exists_at_epoch( + &priv_storage, + &key_id, + &epoch_id, + &PrivDataType::FhePrivateKey.to_string() + ) + .await + .unwrap() + ); - // Purge the private storage to test the backup - purge_priv(env.test_path(), &[None]).await; - - // Reboot the servers - let (kms_server, mut kms_client, internal_client) = - centralized_custodian_handles(&dkg_param, None, env.test_path(), None, None).await; - // Purge the private storage again to delete the signing key - purge_priv(env.test_path(), &[None]).await; + // Reboot the server. + let (kms_server, mut kms_client) = env.spawn_server_on_existing_material().await; - // Execute the backup restoring + // Execute the backup restoring. let mut rng = AesRng::seed_from_u64(13); let recovery_req_resp = kms_client .custodian_recovery_init(tonic::Request::new(CustodianRecoveryInitRequest { @@ -270,23 +314,12 @@ async fn decrypt_after_recovery(amount_custodians: usize, threshold: u32) { .await .unwrap(); - // Check that the key material is back - let sk: PrivateSigKey = read_versioned_at_request_id( - &priv_store, - &SIGNING_KEY_ID, - &PrivDataType::SigningKey.to_string(), - ) - .await - .unwrap(); - assert_eq!(sk, sig_key); - + // Decryption succeeds only if the FHE private key was correctly restored + // by the custodian recovery + restore_from_backup calls above. kms_server.assert_shutdown().await; drop(kms_client); - drop(internal_client); - let (_kms_server, kms_client, mut internal_client) = - centralized_custodian_handles(&dkg_param, None, env.test_path(), None, None).await; - - // Check the data is correctly recovered + let (_kms_server, kms_client) = env.spawn_server_on_existing_material().await; + let mut internal_client = env.create_internal_client(&dkg_param).await; run_decryption_centralized( &kms_client, &mut internal_client, @@ -304,7 +337,8 @@ async fn decrypt_after_recovery(amount_custodians: usize, threshold: u32) { } /// Two custodians submit corrupted signcryption; those outputs are rejected and recovery still -/// completes with the remaining valid shares (see `assert_eq!(sig_key, new_sig_key)` at end). +/// completes with the remaining valid shares — proven by a successful decryption call on a +/// recovered FHE private key at the end. #[tokio::test] async fn test_decrypt_after_recovery_centralized_negative() { decrypt_after_recovery_negative(5, 2).await; @@ -317,30 +351,42 @@ async fn decrypt_after_recovery_negative(amount_custodians: usize, threshold: u3 threshold, ) .await; + let key_id: RequestId = derive_request_id(&format!( + "decrypt_after_recovery_central_negative_key_{amount_custodians}_{threshold}" + )) + .unwrap(); + let epoch_id = *DEFAULT_EPOCH_ID; + + run_key_gen_centralized( + env.kms_client.as_mut().unwrap(), + env.internal_client.as_ref().unwrap(), + &key_id, + &epoch_id, + FheParameter::Test, + None, + None, + Some(env.material_dir.path()), + ) + .await; env.shutdown().await; let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - // Read the private signing key for reference - let priv_store = FileStorage::new(env.test_path(), StorageType::PRIV, None).unwrap(); - let sig_key: PrivateSigKey = read_versioned_at_request_id( - &priv_store, - &SIGNING_KEY_ID, - &PrivDataType::SigningKey.to_string(), + // Delete the FHE privkey. Leave the signing keys so the server can reboot. + let mut priv_storage = FileStorage::new(env.test_path(), StorageType::PRIV, None).unwrap(); + delete_at_request_and_epoch_id( + &mut priv_storage, + &key_id, + &epoch_id, + &PrivDataType::FhePrivateKey.to_string(), ) .await .unwrap(); - // Purge the private storage to test the backup - purge_priv(env.test_path(), &[None]).await; + let (kms_server, mut kms_client) = env.spawn_server_on_existing_material().await; - // Reboot the servers - let (_kms_server, mut kms_client, _internal_client) = - centralized_custodian_handles(&dkg_param, None, env.test_path(), None, None).await; - // Purge the private storage again to delete the signing key - purge_priv(env.test_path(), &[None]).await; - - // Execute the backup restoring + // Tamper with two of the five custodian recovery outputs. Recovery must + // still succeed because threshold=2 allows for 2 invalid contributions. let mut rng = AesRng::seed_from_u64(13); let recovery_req_resp = kms_client .custodian_recovery_init(tonic::Request::new(CustodianRecoveryInitRequest { @@ -357,8 +403,7 @@ async fn decrypt_after_recovery_negative(amount_custodians: usize, threshold: u3 env.test_path(), ) .await; - // Change a bit in two of the custodians contribution to the recover requests to make them invalid - // First custodian 1 + // Flip a bit in custodian #1's signcryption (byte 11). cus_rec_req .custodian_recovery_outputs .get_mut(0) @@ -366,10 +411,9 @@ async fn decrypt_after_recovery_negative(amount_custodians: usize, threshold: u3 inner .backup_output .as_mut() - // Flip a bit in the 11th byte .map(|back_out| back_out.signcryption[11] ^= 1) }); - // Then in custodian 3 + // Flip a bit in custodian #3's signcryption (byte 7). cus_rec_req .custodian_recovery_outputs .get_mut(2) @@ -377,7 +421,6 @@ async fn decrypt_after_recovery_negative(amount_custodians: usize, threshold: u3 inner .backup_output .as_mut() - // Flip a bit in the 7th byte .map(|back_out| back_out.signcryption[7] ^= 1) }); let _recovery_output = kms_client @@ -389,16 +432,26 @@ async fn decrypt_after_recovery_negative(amount_custodians: usize, threshold: u3 .await .unwrap(); - // Check that the key material is back - let new_sig_key: PrivateSigKey = read_versioned_at_request_id( - &priv_store, - &SIGNING_KEY_ID, - &PrivDataType::SigningKey.to_string(), + // Decryption succeeds means that the recovered FHE private key is correct even with the tampered custodian outputs + // from the quorum. + kms_server.assert_shutdown().await; + drop(kms_client); + let (_kms_server, kms_client) = env.spawn_server_on_existing_material().await; + let mut internal_client = env.create_internal_client(&dkg_param).await; + run_decryption_centralized( + &kms_client, + &mut internal_client, + &key_id, + None, + vec![TestingPlaintext::U8(u8::MAX)], + EncryptionConfig { + compression: false, + precompute_sns: false, + }, + 1, + env.test_path(), ) - .await - .unwrap(); - // Check the data is correctly recovered - assert_eq!(sig_key, new_sig_key); + .await; } /// Test that FHE key material is present in the custodian backup vault @@ -417,7 +470,7 @@ async fn test_keygen_backup_presence_central() { FheParameter::Test, None, None, - Some(env.temp_dir.path()), + Some(env.material_dir.path()), ) .await; // Sleep briefly to allow backup to be written (since backup is done asynchronously after keygen) diff --git a/core/service/src/client/tests/centralized/custodian_context_tests.rs b/core/service/src/client/tests/centralized/custodian_context_tests.rs index 597c56b21b..f74c7a694d 100644 --- a/core/service/src/client/tests/centralized/custodian_context_tests.rs +++ b/core/service/src/client/tests/centralized/custodian_context_tests.rs @@ -1,7 +1,7 @@ use crate::client::client_wasm::Client; -use crate::client::test_tools::centralized_custodian_handles; use crate::consts::DEFAULT_MPC_CONTEXT; use crate::consts::SIGNING_KEY_ID; +use crate::testing::setup::CentralizedTestEnv; use crate::util::key_setup::test_tools::backup_exists; use crate::util::key_setup::test_tools::read_custodian_backup_files; use crate::{ @@ -25,12 +25,20 @@ pub(crate) async fn new_custodian_context( let req_new_cus: RequestId = derive_request_id("test_new_custodian_context_central").unwrap(); let req_new_cus2: RequestId = derive_request_id("test_new_custodian_context_central_2").unwrap(); - let temp_dir = tempfile::tempdir().unwrap(); - let test_path = Some(temp_dir.path()); let dkg_param: WrappedDKGParams = parameter.into(); - let (kms_server, mut kms_client, mut internal_client) = - centralized_custodian_handles(&dkg_param, None, test_path, None, None).await; + let test_env = CentralizedTestEnv::builder() + .with_custodian_keychain() + .build() + .await + .unwrap(); + let mut internal_client = test_env.create_internal_client(&dkg_param).await.unwrap(); + let CentralizedTestEnv { + material_dir, + server: kms_server, + client: mut kms_client, + } = test_env; + let test_path = Some(material_dir.path()); run_new_cus_context( &mut kms_client, &mut internal_client, @@ -76,8 +84,11 @@ pub(crate) async fn new_custodian_context( kms_server.assert_shutdown().await; drop(kms_client); drop(internal_client); - let (_kms_server, _kms_client, _internal_client) = - centralized_custodian_handles(&dkg_param, None, test_path, None, None).await; + let (_kms_server, _kms_client) = CentralizedTestEnv::builder() + .with_custodian_keychain() + .from_path(material_dir.path()) + .await + .unwrap(); let reboot_sig_keys = read_custodian_backup_files( test_path, &req_new_cus2, diff --git a/core/service/src/client/tests/centralized/key_gen_tests.rs b/core/service/src/client/tests/centralized/key_gen_tests.rs index 6225af9f5e..f96f77438f 100644 --- a/core/service/src/client/tests/centralized/key_gen_tests.rs +++ b/core/service/src/client/tests/centralized/key_gen_tests.rs @@ -1,6 +1,6 @@ use crate::client::client_wasm::Client; +use crate::client::tests::common::OptKeySetConfigAccessor; use crate::client::tests::common::keygen_config; -use crate::client::tests::common::{OptKeySetConfigAccessor, TIME_TO_SLEEP_MS}; use crate::consts::DEFAULT_EPOCH_ID; use crate::cryptography::internal_crypto_types::WrappedDKGParams; use crate::dummy_domain; @@ -95,8 +95,6 @@ async fn decompression_key_gen_centralized( keygen: 100, new_epoch: 1, }; - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; - let env = crate::testing::setup::CentralizedTestEnv::builder() .with_test_name(test_name) .with_backup_vault() @@ -215,7 +213,6 @@ pub(crate) async fn key_gen_centralized( keygen: 100, new_epoch: 1, }; - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; let env = crate::testing::setup::CentralizedTestEnv::builder() .with_test_name(test_name) .with_backup_vault() diff --git a/core/service/src/client/tests/centralized/misc_tests.rs b/core/service/src/client/tests/centralized/misc_tests.rs index 7c6ffe2346..69610d0793 100644 --- a/core/service/src/client/tests/centralized/misc_tests.rs +++ b/core/service/src/client/tests/centralized/misc_tests.rs @@ -2,7 +2,7 @@ //! //! These tests run in isolated temporary directories with pre-generated cryptographic material. -use crate::client::tests::common::{TIME_TO_SLEEP_MS, get_pub_dec_resp, send_dec_reqs}; +use crate::client::tests::common::{get_pub_dec_resp, send_dec_reqs}; use crate::consts::TEST_CENTRAL_KEY_ID; use crate::engine::centralized::central_kms::RealCentralizedKms; use crate::testing::prelude::*; @@ -38,7 +38,6 @@ async fn test_central_health_endpoint_availability() -> Result<()> { .build() .await?; - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; let mut health_client = get_health_client(env.server.service_port) .await .expect("Failed to get health client"); diff --git a/core/service/src/client/tests/centralized/public_decryption_tests.rs b/core/service/src/client/tests/centralized/public_decryption_tests.rs index aebf7d952a..92d28d27d2 100644 --- a/core/service/src/client/tests/centralized/public_decryption_tests.rs +++ b/core/service/src/client/tests/centralized/public_decryption_tests.rs @@ -1,5 +1,5 @@ use crate::client::client_wasm::Client; -use crate::client::tests::common::{TIME_TO_SLEEP_MS, assert_plaintext}; +use crate::client::tests::common::assert_plaintext; use crate::consts::DEFAULT_CENTRAL_KEY_ID; use crate::consts::DEFAULT_PARAM; use crate::consts::TEST_CENTRAL_KEY_ID; @@ -143,7 +143,6 @@ pub(crate) async fn decryption_centralized( parallelism: usize, ) -> Result<()> { assert!(parallelism > 0); - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; let spec = match material_type { MaterialType::Testing => TestMaterialSpec::centralized_basic(), MaterialType::Default => TestMaterialSpec::centralized_default(), diff --git a/core/service/src/client/tests/centralized/user_decryption_tests.rs b/core/service/src/client/tests/centralized/user_decryption_tests.rs index b9c6a0624f..24d8b5449f 100644 --- a/core/service/src/client/tests/centralized/user_decryption_tests.rs +++ b/core/service/src/client/tests/centralized/user_decryption_tests.rs @@ -1,5 +1,4 @@ use crate::client::client_wasm::ServerIdentities; -use crate::client::tests::common::TIME_TO_SLEEP_MS; use crate::client::user_decryption_wasm::ParsedUserDecryptionRequest; use crate::consts::DEFAULT_CENTRAL_KEY_ID; use crate::consts::DEFAULT_PARAM; @@ -190,7 +189,6 @@ pub(crate) async fn user_decryption_centralized( secure: bool, ) -> Result<()> { assert!(parallelism > 0); - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; let spec = match material_type { MaterialType::Testing => TestMaterialSpec::centralized_basic(), MaterialType::Default => TestMaterialSpec::centralized_default(), diff --git a/core/service/src/client/tests/common.rs b/core/service/src/client/tests/common.rs index 1490d7ae42..60b2e18a74 100644 --- a/core/service/src/client/tests/common.rs +++ b/core/service/src/client/tests/common.rs @@ -23,9 +23,6 @@ use tokio::task::JoinSet; use tokio::time::{Duration, Instant, sleep}; use tonic::transport::Channel; -// Time to sleep to ensure that previous servers and tests have shut down properly. -pub(crate) const TIME_TO_SLEEP_MS: u64 = 500; - /// Poll storage until it contains the given (`request_id`, `epoch_id`, `data_type`) tuple. Give up after 30s. // TODO(dp): Not the most elegant solution; what's a better way? Came about because tests like e.g. `test_insecure_threshold_crs_backup` // would try to inspect state before backup actually had time to happen. diff --git a/core/service/src/client/tests/threshold/common.rs b/core/service/src/client/tests/threshold/common.rs index e824debb87..865b48ddf1 100644 --- a/core/service/src/client/tests/threshold/common.rs +++ b/core/service/src/client/tests/threshold/common.rs @@ -1,245 +1,16 @@ use core::future::Future; -use crate::client::client_wasm::Client; -use crate::client::test_tools::ServerHandle; -use crate::conf::{Keychain, SecretSharingKeychain}; -use crate::consts::{ - BACKUP_STORAGE_PREFIX_THRESHOLD_ALL, DEFAULT_EPOCH_ID, DEFAULT_MPC_CONTEXT, MAX_TRIES, - PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL, PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL, SIGNING_KEY_ID, -}; -use crate::engine::base::derive_request_id; -use crate::util::key_setup::test_tools::file_backup_vault; -#[cfg(feature = "slow_tests")] -use crate::util::key_setup::test_tools::setup::ensure_default_material_exists; -use crate::util::key_setup::test_tools::setup::{ensure_dir_exist, ensure_testing_material_exists}; -use crate::util::key_setup::{ - ThresholdSigningKeyConfig, ensure_client_keys_exist, - ensure_threshold_server_signing_keys_exist, max_threshold, -}; -use crate::util::rate_limiter::RateLimiterConfig; -use crate::vault::Vault; -use crate::vault::storage::delete_at_request_id; -use crate::vault::storage::{StorageType, file::FileStorage}; +use crate::consts::{DEFAULT_EPOCH_ID, DEFAULT_MPC_CONTEXT, MAX_TRIES}; use kms_grpc::RequestId; use kms_grpc::kms_service::v1::core_service_endpoint_client::CoreServiceEndpointClient; -use kms_grpc::rpc_types::PrivDataType; use std::collections::HashMap; -use std::path::Path; use std::pin::Pin; -use tfhe::core_crypto::commons::utils::ZipChecked; -use threshold_execution::endpoints::decryption::DecryptionMode; -use threshold_execution::tfhe_internals::parameters::DKGParams; use tonic::transport::Channel; use tonic::{Request, Response, Status}; /// RequestIds as they are represented in the current version of the ProtoBuf API. type ProtoRequestId = kms_grpc::kms::v1::RequestId; -#[allow(clippy::too_many_arguments)] -async fn threshold_handles_w_vaults( - params: DKGParams, - amount_parties: usize, - ensure_default_prss: bool, - generate_test_material: bool, - rate_limiter_conf: Option, - decryption_mode: Option, - vaults: Vec>, - test_data_path: Option<&Path>, -) -> ( - HashMap, - HashMap>, - Client, -) { - // Compute threshold < amount_parties/3 - let threshold = max_threshold(amount_parties); - let mut pub_storage = Vec::new(); - let mut priv_storage = Vec::new(); - let pub_storage_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - let priv_storage_prefixes = &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - for (pub_prefix, priv_prefix) in pub_storage_prefixes - .iter() - .zip(priv_storage_prefixes.iter()) - { - pub_storage.push( - FileStorage::new(test_data_path, StorageType::PUB, pub_prefix.as_deref()).unwrap(), - ); - let mut cur_priv_storage = - FileStorage::new(test_data_path, StorageType::PRIV, priv_prefix.as_deref()).unwrap(); - if ensure_default_prss { - // Note that migration will move legacy prss (whose ID depends on amount of parties) to the new type, which does not - // this means that when mixing tests of different amount of parties using the same storage, we need to redo PRSS - // Since migation is only done for the 13/4 configuration this is the only legacy PRSS we need to clear - let req_z64 = - derive_request_id(&format!("PRSSSetup_Z64_ID_{}_13_4", *DEFAULT_EPOCH_ID)).unwrap(); - let req_z128 = - derive_request_id(&format!("PRSSSetup_Z128_ID_{}_13_4", *DEFAULT_EPOCH_ID)) - .unwrap(); - delete_at_request_id( - &mut cur_priv_storage, - &req_z64, - &PrivDataType::PrssSetup.to_string(), - ) - .await - .unwrap(); - delete_at_request_id( - &mut cur_priv_storage, - &req_z128, - &PrivDataType::PrssSetup.to_string(), - ) - .await - .unwrap(); - delete_at_request_id( - &mut cur_priv_storage, - &(*DEFAULT_EPOCH_ID).into(), - &PrivDataType::PrssSetupCombined.to_string(), - ) - .await - .unwrap(); - } - priv_storage.push(cur_priv_storage); - } - if generate_test_material { - ensure_testing_material_exists(test_data_path).await; - #[cfg(feature = "slow_tests")] - ensure_default_material_exists().await; - } else { - // Only ensure that the signing key is there s.t. the KMS can start - // TODO(#2491) this will be handled better when we add contexts s.t. we have different signing keys - ensure_dir_exist(test_data_path).await; - ensure_client_keys_exist(test_data_path, &SIGNING_KEY_ID, true).await; - let _ = ensure_threshold_server_signing_keys_exist( - &mut pub_storage, - &mut priv_storage, - &SIGNING_KEY_ID, - true, - ThresholdSigningKeyConfig::AllParties( - (1..=amount_parties).map(|i| format!("party-{i}")).collect(), - ), - true, - ) - .await - .unwrap(); - } - - let (kms_servers, kms_clients) = crate::client::test_tools::setup_threshold( - threshold as u8, - pub_storage, - priv_storage, - vaults, - ensure_default_prss, - rate_limiter_conf, - decryption_mode, - ) - .await; - let mut pub_storage = HashMap::with_capacity(amount_parties); - for (i, prefix) in pub_storage_prefixes.iter().enumerate() { - pub_storage.insert( - (i + 1) as u32, - FileStorage::new(test_data_path, StorageType::PUB, prefix.as_deref()).unwrap(), - ); - } - let client_storage = FileStorage::new(test_data_path, StorageType::CLIENT, None).unwrap(); - let internal_client = Client::new_client(client_storage, pub_storage, ¶ms, decryption_mode) - .await - .unwrap(); - (kms_servers, kms_clients, internal_client) -} - -/// Reads the testing keys for the threshold servers and starts them up, and returns a hash map -/// of the servers, based on their ID, which starts from 1. A similar map is also returned -/// is the client endpoints needed to talk with each of the servers, finally the internal -/// client is returned (which is responsible for constructing requests and validating -/// responses). -/// This provides a setup _without_ custodian backup. Instead the backup vaults are just realized using -/// an uncrypted file storage. -#[cfg(feature = "slow_tests")] -pub(crate) async fn threshold_handles( - params: DKGParams, - amount_parties: usize, - ensure_default_prss: bool, - rate_limiter_conf: Option, - decryption_mode: Option, -) -> ( - HashMap, - HashMap>, - Client, -) { - let mut vaults = Vec::new(); - let pub_storage_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - let backup_storage_prefixes = &BACKUP_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - for (pub_prefix, backup_prefix) in pub_storage_prefixes - .iter() - .zip(backup_storage_prefixes.iter()) - { - let cur_vault = file_backup_vault( - None, - None, - None, - pub_prefix.as_deref(), - backup_prefix.as_deref(), - ) - .await; - vaults.push(Some(cur_vault)); - } - threshold_handles_w_vaults( - params, - amount_parties, - ensure_default_prss, - true, - rate_limiter_conf, - decryption_mode, - vaults, - None, // Default test path - ) - .await -} - -/// Setup servers for backup tests -/// This means that secret sharing based custodian backup gets setup -/// with testing material _optionally_ being generated -pub(crate) async fn threshold_handles_custodian_backup( - params: DKGParams, - amount_parties: usize, - ensure_default_prss: bool, - generate_test_material: bool, - rate_limiter_conf: Option, - decryption_mode: Option, - test_data_path: Option<&Path>, -) -> ( - HashMap, - HashMap>, - Client, -) { - let mut vaults = Vec::new(); - let pub_storage_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - let backup_storage_prefixes = &BACKUP_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - for (pub_prefix, backup_prefix) in pub_storage_prefixes - .iter() - .zip_checked(backup_storage_prefixes.iter()) - { - let cur_vault = file_backup_vault( - Some(&Keychain::SecretSharing(SecretSharingKeychain {})), - test_data_path, - test_data_path, - pub_prefix.as_deref(), - backup_prefix.as_deref(), - ) - .await; - vaults.push(Some(cur_vault)); - } - threshold_handles_w_vaults( - params, - amount_parties, - ensure_default_prss, - generate_test_material, - rate_limiter_conf, - decryption_mode, - vaults, - test_data_path, - ) - .await -} - // ============================================================================= // ISOLATED TEST HELPERS // ============================================================================= diff --git a/core/service/src/client/tests/threshold/crs_gen_tests.rs b/core/service/src/client/tests/threshold/crs_gen_tests.rs index 82d7aca090..70f8977f2d 100644 --- a/core/service/src/client/tests/threshold/crs_gen_tests.rs +++ b/core/service/src/client/tests/threshold/crs_gen_tests.rs @@ -23,25 +23,21 @@ use tonic::transport::Channel; cfg_if::cfg_if! { if #[cfg(feature = "slow_tests")] { use std::sync::Arc; - use crate::client::tests::{common::TIME_TO_SLEEP_MS, threshold::common::threshold_handles}; - use crate::consts::PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL; - use crate::util::key_setup::test_tools::purge; + use crate::testing::setup::ThresholdTestEnv; + use crate::testing::prelude::{MaterialType, TestMaterialSpec}; }} + #[tokio::test(flavor = "multi_thread")] async fn test_insecure_crs_gen_threshold() -> anyhow::Result<()> { use crate::consts::TEST_PARAM; - use crate::testing::prelude::{KeyType, TestMaterialSpec, ThresholdTestEnv}; + use crate::testing::prelude::{TestMaterialSpec, ThresholdTestEnv}; let amount_parties = 4; let parameter = FheParameter::Test; let max_bits = Some(16); - // Signing keys (request auth) + PRSS (distributed ceremony). FHE keys unused. - let spec = { - let mut s = TestMaterialSpec::threshold_signing_only(amount_parties); - s.required_keys.insert(KeyType::PrssSetup); - s - }; + // Signing keys for request auth. + let spec = TestMaterialSpec::threshold_signing_only(amount_parties); let env = ThresholdTestEnv::builder() .with_test_name("insecure_crs_gen_threshold") @@ -121,17 +117,13 @@ async fn secure_threshold_crs() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn test_crs_gen_threshold() -> anyhow::Result<()> { use crate::consts::TEST_PARAM; - use crate::testing::prelude::{KeyType, TestMaterialSpec, ThresholdTestEnv}; + use crate::testing::prelude::{TestMaterialSpec, ThresholdTestEnv}; let amount_parties = 4; let parameter = FheParameter::Test; let max_bits = Some(2048); - let spec = { - let mut s = TestMaterialSpec::threshold_signing_only(amount_parties); - s.required_keys.insert(KeyType::PrssSetup); - s - }; + let spec = TestMaterialSpec::threshold_signing_only(amount_parties); let env = ThresholdTestEnv::builder() .with_test_name("test_crs_gen_threshold") @@ -163,9 +155,6 @@ async fn test_crs_gen_threshold() -> anyhow::Result<()> { Ok(()) } -// TODO(dp): legacy global-storage path — only `nightly_tests.rs` callers -// (slow_tests-gated) still use this. Port them to `ThresholdTestEnv::builder()` -// and delete this helper. #[cfg(feature = "slow_tests")] pub(crate) async fn crs_gen( amount_parties: usize, @@ -174,29 +163,25 @@ pub(crate) async fn crs_gen( iterations: usize, concurrent: bool, ) { - let pub_storage_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - let priv_storage_prefixes = &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - for i in 0..iterations { - let req_crs: RequestId = derive_request_id(&format!( - "full_crs_{amount_parties}_{max_bits:?}_{parameter:?}_{i}" - )) - .unwrap(); - purge( - None, - None, - &req_crs, - pub_storage_prefixes, - priv_storage_prefixes, - ) - .await; - } let dkg_param: WrappedDKGParams = parameter.into(); - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; - // The threshold handle should only be started after the storage is purged - // since the threshold parties will load the CRS from private storage - let (_kms_servers, kms_clients, internal_client) = - threshold_handles(*dkg_param, amount_parties, true, None, None).await; + // CRS gen needs signing keys (for request auth) but not FHE keys. PRSS is bootstrapped + // at runtime via `.with_prss()` below. + let mut spec = TestMaterialSpec::threshold_signing_only(amount_parties); + if matches!(parameter, FheParameter::Default) { + spec.material_type = MaterialType::Default; + } + + let env = ThresholdTestEnv::builder() + .with_test_name(format!("crs_gen_{amount_parties}_{parameter:?}")) + .with_party_count(amount_parties) + .with_material_spec(spec) + .with_prss() + .build() + .await + .unwrap(); + let internal_client = env.create_internal_client(&dkg_param, None).await.unwrap(); + let (kms_clients, _kms_servers, material_path, _guards) = env.into_parts(); if concurrent { let arc_clients = Arc::new(kms_clients); @@ -210,6 +195,7 @@ pub(crate) async fn crs_gen( crs_set.spawn({ let clients_clone = Arc::clone(&arc_clients); let internalclient_clone = Arc::clone(&arc_internalclient); + let path_clone = material_path.clone(); async move { let _ = run_crs( parameter, @@ -218,7 +204,7 @@ pub(crate) async fn crs_gen( false, &cur_id, max_bits, - None, + Some(path_clone.as_path()), ) .await; } @@ -239,7 +225,7 @@ pub(crate) async fn crs_gen( false, &cur_id, max_bits, - None, + Some(material_path.as_path()), ) .await; } diff --git a/core/service/src/client/tests/threshold/custodian_backup_tests.rs b/core/service/src/client/tests/threshold/custodian_backup_tests.rs index d1433a0651..0f40eda860 100644 --- a/core/service/src/client/tests/threshold/custodian_backup_tests.rs +++ b/core/service/src/client/tests/threshold/custodian_backup_tests.rs @@ -18,27 +18,26 @@ use crate::consts::{ use crate::cryptography::internal_crypto_types::WrappedDKGParams; use crate::cryptography::signatures::PrivateSigKey; use crate::cryptography::signatures::PublicSigKey; +use crate::engine::base::derive_request_id; use crate::engine::base::{ CrsGenMetadata, DSEP_PUBDATA_KEY, INSECURE_PREPROCESSING_ID, safe_serialize_hash_element_versioned, }; use crate::engine::context::ContextInfo; +use crate::testing::setup::ThresholdTestEnv; use crate::util::key_setup::test_tools::EncryptionConfig; use crate::util::key_setup::test_tools::TestingPlaintext; -use crate::util::key_setup::test_tools::purge_priv; use crate::util::key_setup::test_tools::{ purge_backup, read_custodian_backup_files, read_custodian_backup_files_with_epoch, }; use crate::vault::storage::StorageType; +use crate::vault::storage::crypto_material::{data_exists, data_exists_at_epoch}; +use crate::vault::storage::delete_at_request_and_epoch_id; +use crate::vault::storage::delete_at_request_id; use crate::vault::storage::file::FileStorage; use crate::vault::storage::read_context_at_id; use crate::vault::storage::read_versioned_at_request_and_epoch_id; use crate::vault::storage::read_versioned_at_request_id; -use crate::{ - client::tests::common::TIME_TO_SLEEP_MS, - client::tests::threshold::common::threshold_handles_custodian_backup, - engine::base::derive_request_id, -}; use aes_prng::AesRng; use alloy_primitives::Address; use kms_grpc::identifiers::EpochId; @@ -70,7 +69,7 @@ struct ThresholdBackupTestEnv { internal_client: Option, mnemonics: Vec, req_new_cus: RequestId, - temp_dir: tempfile::TempDir, + material_dir: tempfile::TempDir, } impl ThresholdBackupTestEnv { @@ -78,23 +77,23 @@ impl ThresholdBackupTestEnv { async fn new(test_name: &str, amount_custodians: usize, threshold: u32) -> Self { let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - let temp_dir = tempfile::tempdir().unwrap(); - let test_path = Some(temp_dir.path()); let req_new_cus: RequestId = derive_request_id(test_name).unwrap(); - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; - let (kms_servers, kms_clients, mut internal_client) = threshold_handles_custodian_backup( - *dkg_param, - Self::AMOUNT_PARTIES, - true, - false, - None, - None, - test_path, - ) - .await; + let (material_dir, servers, clients, mut internal_client) = { + let env = ThresholdTestEnv::builder() + .with_test_name(test_name) + .with_party_count(Self::AMOUNT_PARTIES) + .with_custodian_keychain() + .with_prss() + .build() + .await + .unwrap(); + let internal_client = env.create_internal_client(&dkg_param, None).await.unwrap(); + (env.material_dir, env.servers, env.clients, internal_client) + }; + let mnemonics = run_new_cus_context( - &kms_clients, + &clients, &mut internal_client, &req_new_cus, amount_custodians, @@ -103,17 +102,17 @@ impl ThresholdBackupTestEnv { .await; Self { - kms_servers: Some(kms_servers), - kms_clients: Some(kms_clients), + kms_servers: Some(servers), + kms_clients: Some(clients), internal_client: Some(internal_client), mnemonics, req_new_cus, - temp_dir, + material_dir, } } fn test_path(&self) -> Option<&std::path::Path> { - Some(self.temp_dir.path()) + Some(self.material_dir.path()) } fn backup_prefixes(&self) -> &[Option] { @@ -147,6 +146,44 @@ impl ThresholdBackupTestEnv { } } } + + /// Spawn a fresh KMS server attached to this env's material directory. Use to assert state persists across server + /// lifetimes. The wrapper must outlive the returned pair. + async fn spawn_server_on_existing_material( + &self, + ) -> ( + HashMap, + HashMap>, + ) { + ThresholdTestEnv::builder() + .with_party_count(Self::AMOUNT_PARTIES) + .with_custodian_keychain() + .with_prss() + .from_path(self.material_dir.path()) + .await + .unwrap() + } + + /// Construct a fresh internal Client backed by this env's material dir. + async fn create_internal_client( + &self, + dkg_param: &threshold_execution::tfhe_internals::parameters::DKGParams, + ) -> Client { + let path = self.material_dir.path(); + let mut pub_storage_map = HashMap::new(); + for (i, prefix) in PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..Self::AMOUNT_PARTIES] + .iter() + .enumerate() + { + let pub_storage = + FileStorage::new(Some(path), StorageType::PUB, prefix.as_deref()).unwrap(); + pub_storage_map.insert((i + 1) as u32, pub_storage); + } + let client_storage = FileStorage::new(Some(path), StorageType::CLIENT, None).unwrap(); + Client::new_client(client_storage, pub_storage_map, dkg_param, None) + .await + .unwrap() + } } #[tokio::test(flavor = "multi_thread")] @@ -182,10 +219,7 @@ async fn auto_update_backup(amount_custodians: usize, threshold: u32) { // Purge backup purge_backup(env.test_path(), env.backup_prefixes()).await; // Check that the backup is still there after reboot - let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - let (_kms_servers, _kms_clients, _internal_client) = - threshold_handles_custodian_backup(*dkg_param, n, true, false, None, None, env.test_path()) - .await; + let (_kms_servers, _kms_clients) = env.spawn_server_on_existing_material().await; let _reread_backup: Vec = read_custodian_backup_files( env.test_path(), &env.req_new_cus, @@ -272,22 +306,32 @@ async fn backup_after_crs(amount_custodians: usize, threshold: u32) { env.shutdown().await; - // Capture operator verification keys before purging + // Capture operator verification keys (needed for `run_full_custodian_recovery` below). let operator_verf_keys = operator_verf_key_map(env.test_path(), env.pub_prefixes()).await; let custodian_context_id = env.req_new_cus; - // Purge the private storage to test the backup recovery - purge_priv(env.test_path(), env.priv_prefixes()).await; + // Delete only the CRS metadata for each party. Signing keys stay intact so the cluster can spawn via + // `spawn_server_on_existing_material`. + for storage_prefix in env.priv_prefixes().iter() { + let mut cur_priv_store = FileStorage::new( + env.test_path(), + StorageType::PRIV, + storage_prefix.as_deref(), + ) + .unwrap(); + delete_at_request_and_epoch_id( + &mut cur_priv_store, + &crs_req, + &DEFAULT_EPOCH_ID, + &PrivDataType::CrsInfo.to_string(), + ) + .await + .unwrap(); + } - // Reboot the servers - let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - let (kms_servers, kms_clients, internal_client) = - threshold_handles_custodian_backup(*dkg_param, n, true, false, None, None, env.test_path()) - .await; - // Purge the private storage again to delete the signing key - purge_priv(env.test_path(), env.priv_prefixes()).await; + let (kms_servers, kms_clients) = env.spawn_server_on_existing_material().await; - // Execute the backup restoring + // Execute the backup restore run_full_custodian_recovery( &kms_clients, &operator_verf_keys, @@ -317,9 +361,10 @@ async fn backup_after_crs(amount_custodians: usize, threshold: u32) { assert_eq!(recovered_meta, original_crs_metadata[i]); } - // Shut down the servers - shutdown_servers_and_client(kms_servers, kms_clients, internal_client).await; + shutdown_servers(kms_servers).await; + drop(kms_clients); } + #[tokio::test(flavor = "multi_thread")] #[rstest::rstest] #[case(7, 3)] @@ -327,6 +372,7 @@ async fn backup_after_crs(amount_custodians: usize, threshold: u32) { async fn test_decrypt_after_recovery_threshold(#[case] custodians: usize, #[case] threshold: u32) { decrypt_after_recovery(custodians, threshold).await; } + async fn decrypt_after_recovery(amount_custodians: usize, threshold: u32) { let n = ThresholdBackupTestEnv::AMOUNT_PARTIES; let mut env = ThresholdBackupTestEnv::new( @@ -359,22 +405,61 @@ async fn decrypt_after_recovery(amount_custodians: usize, threshold: u32) { env.shutdown().await; let dkg_param: WrappedDKGParams = FheParameter::Test.into(); - // Read the private signing keys for reference + // Capture state while disk is intact. `sig_keys` is the byte-level snapshot we'll compare against after recovery to + // verify signing keys are preserved exactly. let sig_keys = read_signing_keys(env.test_path(), env.priv_prefixes()).await; let operator_verf_keys = operator_verf_key_map(env.test_path(), env.pub_prefixes()).await; let custodian_context_id = env.req_new_cus; - // Purge the private storage to test the backup - purge_priv(env.test_path(), env.priv_prefixes()).await; + // Boot fresh servers. + let (kms_servers, kms_clients) = env.spawn_server_on_existing_material().await; - // Reboot the servers - let (kms_servers, kms_clients, internal_client) = - threshold_handles_custodian_backup(*dkg_param, n, true, false, None, None, env.test_path()) - .await; - // Purge the private storage again to delete the signing key - purge_priv(env.test_path(), env.priv_prefixes()).await; + // Delete the FHE shares and the per-party signing keys so we can check them after recovery. + for storage_prefix in env.priv_prefixes().iter() { + let mut cur_priv_store = FileStorage::new( + env.test_path(), + StorageType::PRIV, + storage_prefix.as_deref(), + ) + .unwrap(); + delete_at_request_and_epoch_id( + &mut cur_priv_store, + &req_key_id, + &DEFAULT_EPOCH_ID, + &PrivDataType::FheKeyInfo.to_string(), + ) + .await + .unwrap(); + delete_at_request_id( + &mut cur_priv_store, + &SIGNING_KEY_ID, + &PrivDataType::SigningKey.to_string(), + ) + .await + .unwrap(); + + assert!( + !data_exists_at_epoch( + &cur_priv_store, + &req_key_id, + &DEFAULT_EPOCH_ID, + &PrivDataType::FheKeyInfo.to_string() + ) + .await + .unwrap() + ); + assert!( + !data_exists( + &cur_priv_store, + &SIGNING_KEY_ID, + &PrivDataType::SigningKey.to_string() + ) + .await + .unwrap() + ); + } - // Execute the backup restoring + // Execute the backup restore. run_full_custodian_recovery( &kms_clients, &operator_verf_keys, @@ -385,17 +470,15 @@ async fn decrypt_after_recovery(amount_custodians: usize, threshold: u32) { ) .await; - // Check that the key material is back - let recovered_keys = read_signing_keys(env.test_path(), env.priv_prefixes()).await; - for (i, key) in recovered_keys.iter().enumerate() { - assert_eq!(key, &sig_keys[i]); - } + // Signing keys must be back on disk, byte-equal to the originals. + let recovered_sig_keys = read_signing_keys(env.test_path(), env.priv_prefixes()).await; + assert_eq!(recovered_sig_keys, sig_keys); - // Reboot the servers and try to decrypt - shutdown_servers_and_client(kms_servers, kms_clients, internal_client).await; - let (mut kms_servers, mut kms_clients, mut internal_client) = - threshold_handles_custodian_backup(*dkg_param, n, true, false, None, None, env.test_path()) - .await; + // Reboot the servers and verify decryption + shutdown_servers(kms_servers).await; + drop(kms_clients); + let (mut kms_servers, mut kms_clients) = env.spawn_server_on_existing_material().await; + let mut internal_client = env.create_internal_client(&dkg_param).await; run_decryption_threshold( n, &mut kms_servers, @@ -422,6 +505,7 @@ async fn decrypt_after_recovery(amount_custodians: usize, threshold: u32) { async fn test_decrypt_after_recovery_threshold_negative() { decrypt_after_recovery_negative(5, 2).await; } + fn corrupt_custodian_outputs(cus_out: &mut HashMap) { // Change a bit in two of the custodians contribution to the recover requests to make them invalid for (_, cur_payload) in cus_out.values_mut() { @@ -449,6 +533,7 @@ fn corrupt_custodian_outputs(cus_out: &mut HashMap, - _kms_clients: HashMap>, - _internal_client: Client, -) { +async fn shutdown_servers(kms_servers: HashMap) { for (_, kms_server) in kms_servers { kms_server.assert_shutdown().await; } - // here we will drop kms_clients and internal_client } #[allow(clippy::type_complexity)] async fn run_full_custodian_recovery( diff --git a/core/service/src/client/tests/threshold/custodian_context_tests.rs b/core/service/src/client/tests/threshold/custodian_context_tests.rs index e9bd7fad26..ad965a49dd 100644 --- a/core/service/src/client/tests/threshold/custodian_context_tests.rs +++ b/core/service/src/client/tests/threshold/custodian_context_tests.rs @@ -1,9 +1,8 @@ use crate::client::client_wasm::Client; -use crate::client::tests::threshold::common::threshold_handles_custodian_backup; use crate::consts::{BACKUP_STORAGE_PREFIX_THRESHOLD_ALL, DEFAULT_MPC_CONTEXT, SIGNING_KEY_ID}; +use crate::testing::setup::ThresholdTestEnv; use crate::util::key_setup::test_tools::backup_exists; use crate::util::key_setup::test_tools::read_custodian_backup_files; -use crate::util::key_setup::test_tools::setup::ensure_testing_material_exists; use crate::{ cryptography::internal_crypto_types::WrappedDKGParams, engine::base::derive_request_id, }; @@ -29,10 +28,7 @@ async fn new_custodian_context( amount_custodians: usize, threshold: u32, ) { - let temp_dir = tempfile::tempdir().unwrap(); - let test_path = Some(temp_dir.path()); let backup_storage_prefixes = &BACKUP_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - ensure_testing_material_exists(test_path).await; let req_new_cus: RequestId = derive_request_id(&format!( "test_new_custodian_context_threshold_{amount_parties}" )) @@ -43,18 +39,19 @@ async fn new_custodian_context( .unwrap(); let dkg_param: WrappedDKGParams = parameter.into(); - // The threshold handle should only be started after the storage is purged - // since the threshold parties will load the CRS from private storage - let (kms_servers, kms_clients, mut internal_client) = threshold_handles_custodian_backup( - *dkg_param, - amount_parties, - true, - false, - None, - None, - test_path, - ) - .await; + let (material_dir, kms_servers, kms_clients, mut internal_client) = { + let env = ThresholdTestEnv::builder() + .with_party_count(amount_parties) + .with_custodian_keychain() + .with_prss() + .build() + .await + .unwrap(); + let internal_client = env.create_internal_client(&dkg_param, None).await.unwrap(); + (env.material_dir, env.servers, env.clients, internal_client) + }; + let test_path = Some(material_dir.path()); + run_new_cus_context( &kms_clients, &mut internal_client, @@ -110,16 +107,13 @@ async fn new_custodian_context( } drop(kms_clients); drop(internal_client); - let (_kms_servers, _kms_clients, _internal_client) = threshold_handles_custodian_backup( - *dkg_param, - amount_parties, - true, - false, - None, - None, - test_path, - ) - .await; + let (_kms_servers, _kms_clients) = ThresholdTestEnv::builder() + .with_party_count(amount_parties) + .with_custodian_keychain() + .with_prss() + .from_path(material_dir.path()) + .await + .unwrap(); let reboot_sig_keys = read_custodian_backup_files( test_path, &req_new_cus2, diff --git a/core/service/src/client/tests/threshold/key_gen_tests.rs b/core/service/src/client/tests/threshold/key_gen_tests.rs index fdee797405..0089ff7b15 100644 --- a/core/service/src/client/tests/threshold/key_gen_tests.rs +++ b/core/service/src/client/tests/threshold/key_gen_tests.rs @@ -1,31 +1,24 @@ -// TODO(dp): the imports here are a noisy mess — two `cfg_if!` blocks plus -// dozens of individually `#[cfg(...)]`-gated `use` lines. Consolidate into -// a single `cfg_if!` per feature combo (or pull the shared imports up out -// of the gates) on a dedicated cleanup pass. cfg_if::cfg_if! { if #[cfg(feature = "slow_tests")] { use crate::client::tests::common::default_isolated_extra_data; - use crate::client::tests::threshold::common::threshold_handles; use crate::cryptography::internal_crypto_types::WrappedDKGParams; use crate::engine::base::{DSEP_PUBDATA_KEY, KeyGenMetadata, compute_info_uncompressed_keygen}; - use crate::util::key_setup::test_tools::purge; use crate::vault::storage::{ delete_at_request_and_epoch_id, delete_at_request_id, read_versioned_at_request_and_epoch_id, read_versioned_at_request_id, store_versioned_at_request_and_epoch_id, store_versioned_at_request_id, }; use crate::vault::storage::crypto_material::get_core_signing_key; - + use crate::testing::helpers::domain_to_msg; use kms_grpc::rpc_types::PrivDataType; + use crate::testing::material::MaterialType; }} use crate::client::client_wasm::Client; use crate::client::key_gen::tests::check_conformance; use crate::client::tests::common::OptKeySetConfigAccessor; use crate::client::tests::common::keygen_config; #[cfg(feature = "slow_tests")] -use crate::client::tests::common::{ - TIME_TO_SLEEP_MS, decompression_keygen_config, uncompressed_keygen_config, -}; +use crate::client::tests::common::{decompression_keygen_config, uncompressed_keygen_config}; use crate::client::tests::threshold::common::threshold_insecure_key_gen; #[cfg(feature = "slow_tests")] use crate::client::tests::threshold::common::threshold_key_gen_secure; @@ -42,9 +35,7 @@ use crate::engine::base::INSECURE_PREPROCESSING_ID; use crate::engine::base::derive_request_id; use crate::engine::threshold::service::ThresholdFheKeys; use crate::engine::utils::make_extra_data; -#[cfg(feature = "slow_tests")] -use crate::testing::helpers::domain_to_msg; -use crate::testing::material::{KeyType, TestMaterialSpec}; +use crate::testing::material::TestMaterialSpec; use crate::testing::setup::threshold::ThresholdTestEnv; use crate::util::key_setup::max_threshold; #[cfg(feature = "slow_tests")] @@ -174,12 +165,8 @@ async fn test_insecure_compressed_dkg(#[case] amount_parties: usize) -> anyhow:: "test_insecure_compressed_dkg_key_{amount_parties}_{TEST_PARAM:?}" ))?; - // Test generates its own FHE keys; only signing material + PRSS are needed pre-generated. - let spec = { - let mut s = TestMaterialSpec::threshold_signing_only(amount_parties); - s.required_keys.insert(KeyType::PrssSetup); - s - }; + // Test generates its own FHE keys; only signing material is needed pre-staged. + let spec = TestMaterialSpec::threshold_signing_only(amount_parties); let env = ThresholdTestEnv::builder() .with_test_name("test_insecure_compressed_dkg") @@ -458,8 +445,6 @@ pub(crate) async fn run_threshold_decompression_keygen( parameter: FheParameter, insecure: bool, ) { - let pub_storage_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - let priv_storage_prefixes = &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; let preproc_id_1 = if insecure { *INSECURE_PREPROCESSING_ID } else { @@ -470,14 +455,6 @@ pub(crate) async fn run_threshold_decompression_keygen( }; let key_id_1: RequestId = derive_request_id(&format!("decom_dkg_key_{amount_parties}_{parameter:?}_1")).unwrap(); - purge( - None, - None, - &key_id_1, - pub_storage_prefixes, - priv_storage_prefixes, - ) - .await; let preproc_id_2 = if insecure { *INSECURE_PREPROCESSING_ID @@ -489,14 +466,6 @@ pub(crate) async fn run_threshold_decompression_keygen( }; let key_id_2: RequestId = derive_request_id(&format!("decom_dkg_key_{amount_parties}_{parameter:?}_2")).unwrap(); - purge( - None, - None, - &key_id_2, - pub_storage_prefixes, - priv_storage_prefixes, - ) - .await; let preproc_id_3 = derive_request_id(&format!( "decom_dkg_preproc_{amount_parties}_{parameter:?}_3" @@ -504,19 +473,29 @@ pub(crate) async fn run_threshold_decompression_keygen( .unwrap(); let key_id_3: RequestId = derive_request_id(&format!("decom_dkg_key_{amount_parties}_{parameter:?}_3")).unwrap(); - purge( - None, - None, - &key_id_3, - pub_storage_prefixes, - priv_storage_prefixes, - ) - .await; - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; let dkg_param: WrappedDKGParams = parameter.into(); - let (kms_servers, kms_clients, internal_client) = - threshold_handles(*dkg_param, amount_parties, true, None, None).await; + // No FHE keys needed; PRSS is bootstrapped at runtime via `.with_prss()` below. + let mut spec = TestMaterialSpec::threshold_signing_only(amount_parties); + if matches!(parameter, FheParameter::Default) { + spec.material_type = MaterialType::Default; + } + + let (material_dir, kms_servers, kms_clients, internal_client) = { + let env = ThresholdTestEnv::builder() + .with_test_name(format!( + "decom_dkg_{amount_parties}_{parameter:?}_{insecure}" + )) + .with_party_count(amount_parties) + .with_material_spec(spec) + .with_prss() + .build() + .await + .unwrap(); + let internal_client = env.create_internal_client(&dkg_param, None).await.unwrap(); + (env.material_dir, env.servers, env.clients, internal_client) + }; + let test_path = material_dir.path(); if !insecure { run_preproc( @@ -542,7 +521,7 @@ pub(crate) async fn run_threshold_decompression_keygen( keyset_config, keyset_added_info, insecure, - None, + Some(test_path), 0, ) .await @@ -573,7 +552,7 @@ pub(crate) async fn run_threshold_decompression_keygen( keyset_config, keyset_added_info, insecure, - None, + Some(test_path), 0, ) .await @@ -604,7 +583,7 @@ pub(crate) async fn run_threshold_decompression_keygen( keyset_config, keyset_added_info, insecure, - None, + Some(test_path), 0, ) .await @@ -679,8 +658,6 @@ pub(crate) async fn preproc_and_keygen( newly_crashed } - let pub_storage_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - let priv_storage_prefixes = &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; let mut preproc_ids = vec![]; let mut key_ids = vec![]; for i in 0..iterations { @@ -688,28 +665,12 @@ pub(crate) async fn preproc_and_keygen( "full_dkg_preproc_{amount_parties}_{parameter:?}_{compressed}_{i}" )) .unwrap(); - purge( - None, - None, - &req_preproc, - pub_storage_prefixes, - priv_storage_prefixes, - ) - .await; preproc_ids.push(req_preproc); let req_key: RequestId = derive_request_id(&format!( "full_dkg_key_{amount_parties}_{parameter:?}_{compressed}_{i}" )) .unwrap(); - purge( - None, - None, - &req_key, - pub_storage_prefixes, - priv_storage_prefixes, - ) - .await; key_ids.push(req_key); } @@ -728,15 +689,28 @@ pub(crate) async fn preproc_and_keygen( new_epoch: 1, }; - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; - let (mut kms_servers, mut kms_clients, mut internal_client) = threshold_handles( - *dkg_param, - amount_parties, - true, - Some(rate_limiter_conf), - None, - ) - .await; + // No FHE keys needed; PRSS is bootstrapped at runtime via `.with_prss()` below. + let mut spec = TestMaterialSpec::threshold_signing_only(amount_parties); + if matches!(parameter, FheParameter::Default) { + spec.material_type = MaterialType::Default; + } + + let (material_dir, mut kms_servers, mut kms_clients, mut internal_client) = { + let env = ThresholdTestEnv::builder() + .with_test_name(format!( + "preproc_and_keygen_{amount_parties}_{parameter:?}_{compressed}_{insecure_key_gen}" + )) + .with_party_count(amount_parties) + .with_material_spec(spec) + .with_prss() + .with_rate_limiter(rate_limiter_conf) + .build() + .await + .unwrap(); + let internal_client = env.create_internal_client(&dkg_param, None).await.unwrap(); + (env.material_dir, env.servers, env.clients, internal_client) + }; + let test_path = material_dir.path().to_path_buf(); let mut expected_num_parties_crashed = party_ids_to_crash_preproc.as_ref().map_or(0, |v| v.len()); @@ -799,6 +773,7 @@ pub(crate) async fn preproc_and_keygen( keyset.spawn({ let clients_clone = Arc::clone(&arc_clients); let internalclient_clone = Arc::clone(&arc_internalclient); + let path_clone = test_path.clone(); async move { // todo proper use of insecure to skip preproc ( @@ -812,7 +787,7 @@ pub(crate) async fn preproc_and_keygen( keyset_config, keyset_added_info, insecure_key_gen, - None, + Some(path_clone.as_path()), expected_num_parties_crashed, ) .await @@ -845,7 +820,7 @@ pub(crate) async fn preproc_and_keygen( }, None, 1, - None, + Some(test_path.as_path()), ) .await; } @@ -886,7 +861,7 @@ pub(crate) async fn preproc_and_keygen( keyset_config, keyset_added_info, insecure_key_gen, - None, + Some(test_path.as_path()), expected_num_parties_crashed, ) .await @@ -910,7 +885,7 @@ pub(crate) async fn preproc_and_keygen( }, None, 1, - None, + Some(test_path.as_path()), ) .await; } @@ -1436,7 +1411,7 @@ async fn test_insecure_dkg() -> anyhow::Result<()> { async fn default_insecure_dkg() -> anyhow::Result<()> { // Use Default material spec for production-like keys. // PRSS is generated at server startup via `with_prss()`. - let spec = TestMaterialSpec::threshold_default_no_prss(4); + let spec = TestMaterialSpec::threshold_default(4); let env = ThresholdTestEnv::builder() .with_test_name("default_insecure_dkg") diff --git a/core/service/src/client/tests/threshold/misc_tests.rs b/core/service/src/client/tests/threshold/misc_tests.rs index 1cec306165..a1fa4a9d87 100644 --- a/core/service/src/client/tests/threshold/misc_tests.rs +++ b/core/service/src/client/tests/threshold/misc_tests.rs @@ -5,7 +5,6 @@ use crate::client::test_tools::{ await_server_ready, check_port_is_closed, get_health_client, get_status, }; -use crate::client::tests::common::TIME_TO_SLEEP_MS; use crate::client::tests::common::send_dec_reqs; use crate::consts::TEST_THRESHOLD_KEY_ID_4P; use crate::consts::{DEFAULT_EPOCH_ID, DEFAULT_MPC_CONTEXT}; @@ -27,13 +26,10 @@ use tonic_health::pb::health_check_response::ServingStatus; async fn test_threshold_health_endpoint_availability() -> Result<()> { let amount_parties = 4; - // DON'T setup PRSS in order to ensure the server is not ready yet - let spec = { - use crate::testing::material::KeyType; - let mut s = TestMaterialSpec::threshold_signing_only(amount_parties); - s.required_keys.insert(KeyType::FheKeys); - s - }; + // DON'T setup PRSS in order to ensure the server is not ready yet. + // `threshold_basic` gives ClientKeys + SigningKeys + ServerSigningKeys + FheKeys, + // no PRSS. + let spec = TestMaterialSpec::threshold_basic(amount_parties); let env = ThresholdTestEnv::builder() .with_test_name("health_endpoint") .with_party_count(amount_parties) @@ -151,8 +147,6 @@ async fn test_threshold_health_endpoint_availability() -> Result<()> { /// Validate that dropping the server signal triggers the server to shut down #[tokio::test(flavor = "multi_thread")] async fn test_threshold_close_after_drop() -> Result<()> { - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; - let env = ThresholdTestEnv::builder() .with_test_name("close_after_drop") .with_party_count(4) @@ -223,8 +217,6 @@ async fn test_threshold_shutdown() -> Result<()> { let pub_storage_prefixes = &crate::consts::PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - tokio::time::sleep(tokio::time::Duration::from_millis(TIME_TO_SLEEP_MS)).await; - let env = ThresholdTestEnv::builder() .with_test_name("shutdown") .with_party_count(amount_parties) diff --git a/core/service/src/client/tests/threshold/mpc_epoch_tests.rs b/core/service/src/client/tests/threshold/mpc_epoch_tests.rs index caf926b51a..f653b8c144 100644 --- a/core/service/src/client/tests/threshold/mpc_epoch_tests.rs +++ b/core/service/src/client/tests/threshold/mpc_epoch_tests.rs @@ -13,13 +13,14 @@ use threshold_types::role::Role; use tokio::task::JoinSet; use tonic::{Response, Status, transport::Channel}; +use crate::testing::prelude::{MaterialType, TestMaterialSpec, ThresholdTestEnv}; + use crate::{ client::{ client_wasm::Client, tests::{ common::keygen_config, threshold::{ - common::threshold_handles, crs_gen_tests::run_crs, key_gen_tests::{ TestKeyGenResult, run_preproc, run_threshold_keygen, verify_keygen_responses, @@ -28,10 +29,7 @@ use crate::{ }, }, }, - consts::{ - DEFAULT_EPOCH_ID, DEFAULT_MPC_CONTEXT, PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL, - PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL, - }, + consts::{DEFAULT_EPOCH_ID, DEFAULT_MPC_CONTEXT, PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL}, cryptography::internal_crypto_types::WrappedDKGParams, dummy_domain, engine::{ @@ -42,7 +40,7 @@ use crate::{ util::{ key_setup::{ max_threshold, - test_tools::{EncryptionConfig, TestingPlaintext, purge}, + test_tools::{EncryptionConfig, TestingPlaintext}, }, rate_limiter::RateLimiterConfig, }, @@ -75,7 +73,6 @@ pub(crate) async fn new_epoch_with_reshare_and_crs( keygen: 100, new_epoch: 1, }; - // Need to purge before creating the clients let mut preproc_ids = Vec::new(); let mut key_ids = Vec::new(); for key_id in 0..num_keys { @@ -83,49 +80,21 @@ pub(crate) async fn new_epoch_with_reshare_and_crs( "reshare_dkg_preproc_{amount_parties}_{key_id}_{parameters:?}" )) .unwrap(); - let pub_storage_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - let priv_storage_prefixes = &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties]; - purge( - None, - None, - &preproc_req_id, - pub_storage_prefixes, - priv_storage_prefixes, - ) - .await; - preproc_ids.push(preproc_req_id); let key_req_id: RequestId = derive_request_id(&format!( "reshare_dkg_key_{amount_parties}_{key_id}_{parameters:?}" )) .unwrap(); - purge( - None, - None, - &key_req_id, - pub_storage_prefixes, - priv_storage_prefixes, - ) - .await; key_ids.push(key_req_id); } let mut crs_ids: Vec = Vec::new(); - for crs_id in 0..num_crs { let req_id = derive_request_id(&format!( "reshare_crs_{amount_parties}_{crs_id}_{parameters:?}" )) .unwrap(); - purge( - None, - None, - &req_id, - &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties], - &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties], - ) - .await; crs_ids.push(req_id); } @@ -133,24 +102,31 @@ pub(crate) async fn new_epoch_with_reshare_and_crs( derive_request_id(&format!("new_epoch_id__{amount_parties}_{parameters:?}")) .unwrap() .into(); - purge( - None, - None, - &new_epoch_id.into(), - &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties], - &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..amount_parties], - ) - .await; + + // No FHE keys needed; PRSS is bootstrapped at runtime via `.with_prss()` below. + let mut spec = TestMaterialSpec::threshold_signing_only(amount_parties); + if matches!(parameters, FheParameter::Default) { + spec.material_type = MaterialType::Default; + } + // Setting ensure_default_prss to true to // to create the default context and epoch with its PRSS init - let (mut kms_servers, mut kms_clients, mut internal_client) = threshold_handles( - *dkg_param, - amount_parties, - true, - Some(rate_limiter_conf), - None, - ) - .await; + let (material_dir, mut kms_servers, mut kms_clients, mut internal_client) = { + let env = ThresholdTestEnv::builder() + .with_test_name(format!( + "new_epoch_with_reshare_{amount_parties}_{parameters:?}" + )) + .with_party_count(amount_parties) + .with_material_spec(spec) + .with_prss() + .with_rate_limiter(rate_limiter_conf) + .build() + .await + .unwrap(); + let internal_client = env.create_internal_client(&dkg_param, None).await.unwrap(); + (env.material_dir, env.servers, env.clients, internal_client) + }; + let test_path = material_dir.path(); let mut keys_info = Vec::new(); let mut keysets = Vec::new(); @@ -180,7 +156,7 @@ pub(crate) async fn new_epoch_with_reshare_and_crs( keyset_config, keyset_added_info, false, - None, + Some(test_path), expected_num_parties_crashed, ) .await; @@ -218,7 +194,7 @@ pub(crate) async fn new_epoch_with_reshare_and_crs( false, crs_id, Some(2048), - None, + Some(test_path), ) .await; assert_eq!(crs.len(), 1); @@ -249,6 +225,7 @@ pub(crate) async fn new_epoch_with_reshare_and_crs( new_context_id, new_epoch_id, resharing, + test_path, ) .await .unwrap(); @@ -360,7 +337,7 @@ pub(crate) async fn new_epoch_with_reshare_and_crs( }, None, 1, - None, + Some(test_path), ) .await; } @@ -374,6 +351,7 @@ async fn run_new_epoch( new_context_id: ContextId, new_epoch_id: EpochId, resharing: Option, + test_path: &std::path::Path, ) -> Option)>> { let num_keys = resharing .as_ref() @@ -483,7 +461,7 @@ async fn run_new_epoch( let key_id = key_id.as_ref().unwrap().try_into().unwrap(); let out = verify_keygen_responses( responses, - None, + Some(test_path), internal_client, &preproc_id, &key_id, @@ -532,7 +510,7 @@ async fn run_new_epoch( .expect("each party should have a CRS response") .clone(); let storage = FileStorage::new( - None, + Some(test_path), StorageType::PUB, PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[*party_idx as usize - 1].as_deref(), ) diff --git a/core/service/src/client/tests/threshold/public_decryption_tests.rs b/core/service/src/client/tests/threshold/public_decryption_tests.rs index 471af40d17..1b127d56c0 100644 --- a/core/service/src/client/tests/threshold/public_decryption_tests.rs +++ b/core/service/src/client/tests/threshold/public_decryption_tests.rs @@ -10,7 +10,7 @@ use crate::consts::TEST_PARAM; use crate::consts::TEST_THRESHOLD_KEY_ID_4P; use crate::dummy_domain; use crate::engine::base::derive_request_id; -use crate::testing::prelude::{KeyType, TestMaterialSpec, ThresholdTestEnv}; +use crate::testing::prelude::{TestMaterialSpec, ThresholdTestEnv}; use crate::util::key_setup::max_threshold; use crate::util::key_setup::test_tools::{ EncryptionConfig, TestingPlaintext, compute_cipher_from_stored_key, @@ -212,16 +212,12 @@ pub async fn decryption_threshold( new_epoch: 1, }; - // Decryption needs pre-generated FHE keys (identified by `key_id`) plus - // signing keys + PRSS. Material type follows the DKG params. - let spec = { - let mut s = if dkg_params == TEST_PARAM { - TestMaterialSpec::threshold_basic(amount_parties) - } else { - TestMaterialSpec::threshold_default(amount_parties) - }; - s.required_keys.insert(KeyType::PrssSetup); - s + // Decryption needs pre-generated FHE keys (identified by `key_id`) plus signing keys. PRSS is bootstrapped at + // runtime via `.with_prss()` below. + let spec = if dkg_params == TEST_PARAM { + TestMaterialSpec::threshold_basic(amount_parties) + } else { + TestMaterialSpec::threshold_default(amount_parties) }; let mut builder = ThresholdTestEnv::builder() diff --git a/core/service/src/client/tests/threshold/user_decryption_tests.rs b/core/service/src/client/tests/threshold/user_decryption_tests.rs index 4aa027bdb5..e22f4c5ee9 100644 --- a/core/service/src/client/tests/threshold/user_decryption_tests.rs +++ b/core/service/src/client/tests/threshold/user_decryption_tests.rs @@ -11,6 +11,7 @@ use crate::cryptography::signatures::{PrivateSigKey, internal_sign}; use crate::dummy_domain; use crate::engine::base::derive_request_id; use crate::engine::validation::DSEP_USER_DECRYPTION; +use crate::testing::prelude::{TestMaterialSpec, ThresholdTestEnv}; #[cfg(feature = "wasm_tests")] use crate::util::file_handling::write_element; use crate::util::key_setup::max_threshold; @@ -347,20 +348,14 @@ pub(crate) async fn user_decryption_threshold( malicious_parties: Option>, decryption_mode: Option, ) { - use crate::testing::prelude::{KeyType, TestMaterialSpec, ThresholdTestEnv}; - assert!(parallelism > 0); - // Spec: pre-gen FHE keys for `key_id` plus signing + PRSS. Material - // type follows the DKG params. - let spec = { - let mut s = if dkg_params == TEST_PARAM { - TestMaterialSpec::threshold_basic(amount_parties) - } else { - TestMaterialSpec::threshold_default(amount_parties) - }; - s.required_keys.insert(KeyType::PrssSetup); - s + // Spec: pre-gen FHE keys for `key_id` plus signing. PRSS is bootstrapped + // at runtime via `.with_prss()`. Material type follows the DKG params. + let spec = if dkg_params == TEST_PARAM { + TestMaterialSpec::threshold_basic(amount_parties) + } else { + TestMaterialSpec::threshold_default(amount_parties) }; let mut builder = ThresholdTestEnv::builder() diff --git a/core/service/src/testing/helpers.rs b/core/service/src/testing/helpers.rs index c1b9c7bb5b..b76a7d4e55 100644 --- a/core/service/src/testing/helpers.rs +++ b/core/service/src/testing/helpers.rs @@ -2,13 +2,6 @@ //! //! This module provides reusable helper functions for test setup and utilities. use super::material::TestMaterialManager; -use crate::consts::{ - DEFAULT_EPOCH_ID, OTHER_CENTRAL_TEST_ID, SIGNING_KEY_ID, TEST_CENTRAL_KEY_ID, TEST_PARAM, -}; -use crate::util::key_setup::{ensure_central_keys_exist, ensure_central_server_signing_keys_exist}; -use crate::vault::storage::{delete_at_request_id, file::FileStorage}; -use anyhow::Result; -use kms_grpc::rpc_types::{PrivDataType, PubDataType}; /// Create test material manager with workspace test-material path /// @@ -43,7 +36,7 @@ pub fn create_test_material_manager() -> TestMaterialManager { ), None => tracing::warn!( "Could not find test-material directory (searched from: {}). \ - Tests requiring pre-generated material may fail. \ + Tests requiring pre-generated material will fail at setup. \ Run 'cargo run -p generate-test-material -- --output ./test-material --profile insecure --parties 4' from workspace root.", std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| "".to_string()) ), @@ -52,103 +45,6 @@ pub fn create_test_material_manager() -> TestMaterialManager { TestMaterialManager::new(workspace_root.map(|p| p.join("test-material"))) } -/// Regenerate central server keys for tests -/// -/// Deletes existing keys and regenerates all central server keys -/// (both private and public) with correct, matching RequestIds. -/// This ensures test material has consistent key pairs including: -/// - Server signing keys (VerfKey, VerfAddress, SigningKey) -/// - FHE keys (CompressedXofKeySet, FhePrivateKey) -/// -/// # Arguments -/// * `pub_storage` - Public storage for regenerated keys -/// * `priv_storage` - Private storage for regenerated keys -pub async fn regenerate_central_keys( - pub_storage: &mut FileStorage, - priv_storage: &mut FileStorage, -) -> Result<()> { - tracing::info!( - "regenerate_central_keys: Ensuring signing keys exist in {} and {}", - pub_storage.root_dir().display(), - priv_storage.root_dir().display() - ); - - // Delete all signing-related directories to force complete regeneration - remove_dir_if_exists(priv_storage.root_dir().join("SigningKey")).await; - remove_dir_if_exists(pub_storage.root_dir().join("VerfKey")).await; - remove_dir_if_exists(pub_storage.root_dir().join("VerfAddress")).await; - - // Regenerate signing keys (VerfKey, VerfAddress, SigningKey) - let generated = ensure_central_server_signing_keys_exist( - pub_storage, - priv_storage, - &SIGNING_KEY_ID, - true, // deterministic - ) - .await; - - if !generated { - return Err(anyhow::anyhow!( - "Failed to generate central server signing keys" - )); - } - - // Delete any pre-existing FHE key artifacts to force clean regeneration. - // ensure_central_keys_exist short-circuits on existing PublicKey, so we - // remove PublicKey + ServerKey + CompressedXofKeySet + DecompressionKey - // (and the FhePrivateKey dir below) to avoid stale data from previous - // runs or from `test-material/` source fixtures (which include - // CompressedXofKeySet that would otherwise be inconsistent with freshly - // regenerated PublicKey/ServerKey, causing a "Server key digest - // mismatch" at server boot). `delete_at_request_id` is a no-op when the - // artifact isn't present, which is fine — we'll regenerate either way. - for key_id in [&*TEST_CENTRAL_KEY_ID, &*OTHER_CENTRAL_TEST_ID] { - for data_type in [ - PubDataType::PublicKey, - PubDataType::ServerKey, - PubDataType::CompressedXofKeySet, - PubDataType::DecompressionKey, - ] { - delete_at_request_id(pub_storage, key_id, &data_type.to_string()) - .await - .map_err(|e| anyhow::anyhow!("Failed to delete {data_type:?} for {key_id}: {e}"))?; - } - } - - remove_dir_if_exists( - priv_storage - .root_dir() - .join(PrivDataType::FhePrivateKey.to_string()), - ) - .await; - - // Regenerate FHE keys - if !ensure_central_keys_exist( - pub_storage, - priv_storage, - TEST_PARAM, - &TEST_CENTRAL_KEY_ID, - &OTHER_CENTRAL_TEST_ID, - &DEFAULT_EPOCH_ID, - true, // deterministic - ) - .await - { - return Err(anyhow::anyhow!("Failed to generate central FHE keys")); - } - - Ok(()) -} - -/// Remove a directory if it exists, logging the outcome. -async fn remove_dir_if_exists(path: std::path::PathBuf) { - match tokio::fs::remove_dir_all(&path).await { - Ok(()) => tracing::info!("Removed directory: {}", path.display()), - Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} // nothing to remove - Err(e) => tracing::warn!("Failed to remove {}: {}", path.display(), e), - } -} - /// Convert Eip712Domain to Eip712DomainMsg for gRPC requests /// /// This is a common conversion needed in many tests that work with EIP-712 signatures. diff --git a/core/service/src/testing/material/manager.rs b/core/service/src/testing/material/manager.rs index ddfae6e7b9..8cb1788b67 100644 --- a/core/service/src/testing/material/manager.rs +++ b/core/service/src/testing/material/manager.rs @@ -6,8 +6,8 @@ use super::spec::{KeyType, MaterialType, TestMaterialSpec}; use super::{material_subdir, threshold_crs_id_name, threshold_key_id_name}; use crate::consts::{ DEFAULT_CENTRAL_CRS_ID, DEFAULT_CENTRAL_KEY_ID, KEY_PATH_PREFIX, OTHER_CENTRAL_DEFAULT_ID, - OTHER_CENTRAL_TEST_ID, PRSS_INIT_REQ_ID, SIGNING_KEY_ID, TEST_CENTRAL_CRS_ID, - TEST_CENTRAL_KEY_ID, TMP_PATH_PREFIX, + OTHER_CENTRAL_TEST_ID, SIGNING_KEY_ID, TEST_CENTRAL_CRS_ID, TEST_CENTRAL_KEY_ID, + TMP_PATH_PREFIX, }; use crate::engine::base::derive_request_id; use crate::vault::storage::StorageType; @@ -20,6 +20,17 @@ use tempfile::TempDir; use threshold_types::role::Role; use tokio::fs; +fn generation_hint(material_type: MaterialType) -> &'static str { + match material_type { + MaterialType::Testing => { + "cargo run -p generate-test-material -- --output ./test-material --profile insecure --parties 4" + } + MaterialType::Default => { + "cargo run -p generate-test-material -- --output ./test-material --profile secure --parties 4,13" + } + } +} + /// Helper function to compute storage path like FileStorage does fn compute_storage_path( base_path: Option<&Path>, @@ -90,35 +101,27 @@ impl TestMaterialManager { Ok(temp_dir) } - /// Verify that source material exists for the requested material type + /// Verify that source material exists for the requested material type. #[cfg(any(test, feature = "testing"))] fn verify_material_exists(&self, spec: &TestMaterialSpec) -> Result<()> { - use super::spec::MaterialType; - - // If no source path is configured, skip verification - // This allows tests to work without pre-generated material - let source_path = match &self.source_path { - Some(path) => path, - None => { - tracing::debug!("No source path configured, skipping material verification"); - return Ok(()); - } - }; + let source_path = self.source_path.as_ref().ok_or_else(|| { + anyhow!( + "Test material source path is not configured. \ + Tests requiring pre-generated material need a `test-material/` directory at the workspace root.\n\ + Run: {}", + generation_hint(spec.material_type) + ) + })?; - // Determine subdirectory based on material type let material_path = source_path.join(material_subdir(spec.material_type)); if !material_path.exists() { - let generation_hint = match spec.material_type { - MaterialType::Testing => "generate-test-material --profile insecure --parties 4", - MaterialType::Default => "generate-test-material --profile secure --parties 4,13", - }; return Err(anyhow!( "Material not found for {:?} at: {}\n\ Run: {}", spec.material_type, material_path.display(), - generation_hint + generation_hint(spec.material_type) )); } @@ -208,34 +211,24 @@ impl TestMaterialManager { } else { Either::Right(ready(Ok(()))) }; - let copy_prss_setup = if spec.requires_key_type(KeyType::PrssSetup) && spec.is_threshold() { - Either::Left(self.copy_prss_setup(source_base_ref, dest_base, spec)) - } else { - Either::Right(ready(Ok(()))) - }; tokio::try_join!( copy_client_keys, copy_signing_keys, copy_fhe_keys, copy_crs_keys, - copy_prss_setup )?; Ok(()) } - /// Copy client keys + /// Copy client keys. Returns error if the folder is missing. async fn copy_client_keys(&self, source_base: Option<&Path>, dest_base: &Path) -> Result<()> { let source_client_path = compute_storage_path(source_base, StorageType::CLIENT, None); let dest_client_path = compute_storage_path(Some(dest_base), StorageType::CLIENT, None); - if fs::try_exists(&source_client_path).await? { - self.copy_directory_contents(&source_client_path, &dest_client_path) - .await?; - } - - Ok(()) + self.copy_directory_contents(&source_client_path, &dest_client_path) + .await } /// Copy signing keys @@ -421,39 +414,7 @@ impl TestMaterialManager { Ok(()) } - /// Copy PRSS setup for threshold tests - async fn copy_prss_setup( - &self, - source_base: Option<&Path>, - dest_base: &Path, - spec: &TestMaterialSpec, - ) -> Result<()> { - for i in 1..=spec.party_count() { - let role = Role::indexed_from_one(i); - let source_priv = compute_storage_path(source_base, StorageType::PRIV, Some(role)); - let dest_priv = compute_storage_path(Some(dest_base), StorageType::PRIV, Some(role)); - - // Copy PRSS setup files - self.copy_key_files( - &source_priv, - &dest_priv, - &PrivDataType::PrssSetupCombined.to_string(), - PRSS_INIT_REQ_ID, - ) - .await?; - self.copy_key_files( - &source_priv, - &dest_priv, - &PrivDataType::ContextInfo.to_string(), - PRSS_INIT_REQ_ID, - ) - .await?; - } - - Ok(()) - } - - /// Copy specific key files (creates directories if needed) + /// Copy a specific key file. Errors if the source file is missing. async fn copy_key_files( &self, source_dir: &Path, @@ -465,7 +426,10 @@ impl TestMaterialManager { let source_file = source_type_dir.join(key_id); if !fs::try_exists(&source_file).await? { - return Ok(()); + return Err(anyhow!( + "Required test material is missing: {}", + source_file.display() + )); } let dest_type_dir = dest_dir.join(key_type); @@ -483,7 +447,8 @@ impl TestMaterialManager { Ok(()) } - /// Copy epoch-based key files (e.g., FhePrivateKey which is stored at {root}/{key_type}/{epoch_id}/{key_id}) + /// Copy epoch-based key files (e.g., FhePrivateKey, stored at `{root}/{key_type}/{epoch_id}/{key_id}`). Errors when + /// the source type directory is missing. async fn copy_epoch_key_files( &self, source_dir: &Path, @@ -496,7 +461,12 @@ impl TestMaterialManager { let mut entries = match fs::read_dir(&source_type_dir).await { Ok(entries) => entries, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + return Err(anyhow!( + "Required test material directory is missing: {}", + source_type_dir.display() + )); + } Err(error) => return Err(error.into()), }; while let Some(entry) = entries.next_entry().await? { @@ -523,7 +493,7 @@ impl TestMaterialManager { Ok(()) } - /// Copy entire directory contents + /// Copy entire directory contents. A missing source directory is a hard error. #[allow(clippy::only_used_in_recursion)] fn copy_directory_contents<'a>( &'a self, @@ -531,11 +501,12 @@ impl TestMaterialManager { dest: &'a Path, ) -> std::pin::Pin> + 'a>> { Box::pin(async move { - let mut entries = match fs::read_dir(source).await { - Ok(entries) => entries, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()), - Err(error) => return Err(error.into()), - }; + let mut entries = fs::read_dir(source).await.with_context(|| { + format!( + "Failed to read required test-material directory: {}", + source.display() + ) + })?; fs::create_dir_all(dest).await?; diff --git a/core/service/src/testing/material/spec.rs b/core/service/src/testing/material/spec.rs index 238451ab87..e27fc4fa20 100644 --- a/core/service/src/testing/material/spec.rs +++ b/core/service/src/testing/material/spec.rs @@ -45,8 +45,6 @@ pub enum KeyType { CrsKeys, /// Decompression keys for compressed ciphertexts DecompressionKeys, - /// PRSS setup for threshold protocols - PrssSetup, } impl TestMaterialSpec { @@ -110,7 +108,6 @@ impl TestMaterialSpec { if party_count.is_some() { required_keys.insert(KeyType::ServerSigningKeys); - required_keys.insert(KeyType::PrssSetup); } Self { @@ -140,18 +137,6 @@ impl TestMaterialSpec { pub fn threshold_default(party_count: usize) -> Self { let mut spec = Self::threshold_basic(party_count); spec.material_type = MaterialType::Default; - spec.required_keys.insert(KeyType::PrssSetup); - spec - } - - /// Create specification for threshold test with Default parameters but without PRSS setup - /// - /// Uses production-like key sizes (MaterialType::Default) while excluding PRSS material. - /// This is intended for tests that run with Default parameters but do not initialize PRSS - /// at server startup (`ensure_default_prss=false`). - pub fn threshold_default_no_prss(party_count: usize) -> Self { - let mut spec = Self::threshold_default(party_count); - spec.required_keys.remove(&KeyType::PrssSetup); spec } @@ -180,71 +165,6 @@ impl Default for TestMaterialSpec { #[cfg(test)] mod tests { use super::*; - use kms_grpc::rpc_types::{PrivDataType, PubDataType}; - use strum::IntoEnumIterator; - - /// Maps a PrivDataType to its corresponding KeyType(s). - /// This function must be exhaustive - the compiler will error if new variants are added. - fn priv_data_type_to_key_types(pdt: PrivDataType) -> Vec { - match pdt { - PrivDataType::SigningKey => vec![KeyType::SigningKeys, KeyType::ServerSigningKeys], - PrivDataType::FheKeyInfo => vec![KeyType::FheKeys], // Threshold FHE key info - PrivDataType::CrsInfo => vec![KeyType::CrsKeys], - PrivDataType::FhePrivateKey => vec![KeyType::FheKeys], // Centralized FHE private key - #[expect(deprecated)] - PrivDataType::PrssSetup => vec![KeyType::PrssSetup], - PrivDataType::PrssSetupCombined => vec![KeyType::PrssSetup], - PrivDataType::ContextInfo => vec![KeyType::PrssSetup], // MPC context stored with PRSS - } - } - - /// Maps a PubDataType to its corresponding KeyType(s). - /// This function must be exhaustive - the compiler will error if new variants are added. - fn pub_data_type_to_key_types(pdt: PubDataType) -> Vec { - match pdt { - PubDataType::ServerKey => vec![KeyType::FheKeys], - PubDataType::PublicKey => vec![KeyType::FheKeys], - #[allow(deprecated)] - PubDataType::PublicKeyMetadata => vec![KeyType::FheKeys], - PubDataType::CRS => vec![KeyType::CrsKeys], - PubDataType::VerfKey => vec![KeyType::SigningKeys], - PubDataType::VerfAddress => vec![KeyType::SigningKeys], - PubDataType::DecompressionKey => vec![KeyType::DecompressionKeys], - PubDataType::CACert => vec![KeyType::ServerSigningKeys], // TLS certs for MPC nodes - PubDataType::RecoveryMaterial => vec![KeyType::ClientKeys], // Backup recovery - PubDataType::CompressedXofKeySet => vec![KeyType::FheKeys], // Compressed server key - } - } - - /// Ensures KeyType covers all PrivDataType variants. - /// If a new PrivDataType is added, the exhaustive match in priv_data_type_to_key_types - /// will cause a compile error, forcing an update to both the mapping and KeyType if needed. - #[test] - fn test_key_type_covers_all_priv_data_types() { - for pdt in PrivDataType::iter() { - let key_types = priv_data_type_to_key_types(pdt); - assert!( - !key_types.is_empty(), - "PrivDataType::{:?} must map to at least one KeyType", - pdt - ); - } - } - - /// Ensures KeyType covers all PubDataType variants. - /// If a new PubDataType is added, the exhaustive match in pub_data_type_to_key_types - /// will cause a compile error, forcing an update to both the mapping and KeyType if needed. - #[test] - fn test_key_type_covers_all_pub_data_types() { - for pdt in PubDataType::iter() { - let key_types = pub_data_type_to_key_types(pdt); - assert!( - !key_types.is_empty(), - "PubDataType::{:?} must map to at least one KeyType", - pdt - ); - } - } #[test] fn test_centralized_basic_spec() { @@ -256,7 +176,6 @@ mod tests { assert!(spec.requires_key_type(KeyType::ClientKeys)); assert!(spec.requires_key_type(KeyType::SigningKeys)); assert!(spec.requires_key_type(KeyType::FheKeys)); - assert!(!spec.requires_key_type(KeyType::PrssSetup)); } #[test] @@ -269,7 +188,6 @@ mod tests { assert!(spec.requires_key_type(KeyType::ClientKeys)); assert!(spec.requires_key_type(KeyType::SigningKeys)); assert!(spec.requires_key_type(KeyType::ServerSigningKeys)); - assert!(!spec.requires_key_type(KeyType::PrssSetup)); } #[test] @@ -282,27 +200,12 @@ mod tests { } #[test] - fn test_threshold_default_spec_requires_prss() { + fn test_threshold_default_spec() { let spec = TestMaterialSpec::threshold_default(4); assert_eq!(spec.material_type, MaterialType::Default); assert!(spec.is_threshold()); assert_eq!(spec.party_count(), 4); - assert!(spec.requires_key_type(KeyType::PrssSetup)); - } - - #[test] - fn test_threshold_default_no_prss_spec() { - let spec = TestMaterialSpec::threshold_default_no_prss(4); - - assert_eq!(spec.material_type, MaterialType::Default); - assert!(spec.is_threshold()); - assert_eq!(spec.party_count(), 4); - assert!(spec.requires_key_type(KeyType::ClientKeys)); - assert!(spec.requires_key_type(KeyType::SigningKeys)); - assert!(spec.requires_key_type(KeyType::ServerSigningKeys)); - assert!(spec.requires_key_type(KeyType::FheKeys)); - assert!(!spec.requires_key_type(KeyType::PrssSetup)); } #[test] diff --git a/core/service/src/testing/mod.rs b/core/service/src/testing/mod.rs index 38908adb6c..a97fdc84cc 100644 --- a/core/service/src/testing/mod.rs +++ b/core/service/src/testing/mod.rs @@ -51,9 +51,7 @@ pub mod prelude { }; // Helper functions - pub use super::helpers::{ - create_test_material_manager, domain_to_msg, regenerate_central_keys, - }; + pub use super::helpers::{create_test_material_manager, domain_to_msg}; // Test utilities pub use super::utils::{ diff --git a/core/service/src/testing/setup/centralized.rs b/core/service/src/testing/setup/centralized.rs index ac45e17c39..dbab997455 100644 --- a/core/service/src/testing/setup/centralized.rs +++ b/core/service/src/testing/setup/centralized.rs @@ -2,15 +2,20 @@ //! //! This module provides a builder pattern for setting up isolated centralized KMS //! test environments with automatic cleanup. +use crate::client::test_tools::setup_centralized; +use crate::conf::{Keychain, SecretSharingKeychain}; use crate::consts::SIGNING_KEY_ID; -use crate::testing::helpers::{create_test_material_manager, regenerate_central_keys}; -use crate::testing::material::{MaterialType, TestMaterialManager, TestMaterialSpec}; +use crate::testing::helpers::create_test_material_manager; +use crate::testing::material::{TestMaterialManager, TestMaterialSpec}; use crate::testing::types::ServerHandle; use crate::util::key_setup::ensure_client_keys_exist; -use crate::vault::storage::{StorageType, file::FileStorage}; +use crate::vault::Vault; +use crate::vault::keychain::make_keychain_proxy; +use crate::vault::storage::{StorageProxy, StorageType, file::FileStorage}; use anyhow::Result; use kms_grpc::kms_service::v1::core_service_endpoint_client::CoreServiceEndpointClient; use std::collections::HashMap; +use std::path::Path; use tempfile::TempDir; use tonic::transport::Channel; @@ -144,68 +149,19 @@ impl CentralizedTestEnvBuilder { // Setup isolated material let material_dir = manager.setup_test_material_temp(&spec, &test_name).await?; - let mut pub_storage = FileStorage::new(Some(material_dir.path()), StorageType::PUB, None)?; - let mut priv_storage = - FileStorage::new(Some(material_dir.path()), StorageType::PRIV, None)?; - - // For `Testing` material we (re)generate centralized keys to guarantee freshness; - // for `Default` we trust the pre-generated `test-material/default/` fixture as-is. - // TODO(dp): runs even when the just-copied fixture is already valid. - // Could be skipped if `regenerate_central_keys`'s outputs are already - // present in the dest tempdir. - if spec.material_type == MaterialType::Testing { - regenerate_central_keys(&mut pub_storage, &mut priv_storage).await?; - } + let pub_storage = FileStorage::new(Some(material_dir.path()), StorageType::PUB, None)?; + let priv_storage = FileStorage::new(Some(material_dir.path()), StorageType::PRIV, None)?; // Ensure client signing/verification keys exist ensure_client_keys_exist(Some(material_dir.path()), &SIGNING_KEY_ID, true).await; - // Setup KMS server with optional backup vault let backup_vault = if self.with_backup_vault { - use crate::conf::{Keychain, SecretSharingKeychain}; - use crate::vault::Vault; - use crate::vault::keychain::make_keychain_proxy; - use std::fs; - - // Create BACKUP directory - let backup_dir = material_dir.path().join("BACKUP"); - fs::create_dir_all(&backup_dir)?; - - let backup_proxy = crate::vault::storage::StorageProxy::from(FileStorage::new( - Some(material_dir.path()), - StorageType::BACKUP, - None, - )?); - - let keychain = if self.with_custodian_keychain { - let pub_proxy = crate::vault::storage::StorageProxy::from(FileStorage::new( - Some(material_dir.path()), - StorageType::PUB, - None, - )?); - Some( - make_keychain_proxy( - &Keychain::SecretSharing(SecretSharingKeychain {}), - None, - None, - Some(&pub_proxy), - false, - ) - .await?, - ) - } else { - None - }; - - Some(Vault { - storage: backup_proxy, - keychain, - }) + Some(build_backup_vault(material_dir.path(), self.with_custodian_keychain).await?) } else { None }; - let (server, client) = crate::client::test_tools::setup_centralized( + let (server, client) = setup_centralized( pub_storage, priv_storage, backup_vault, @@ -219,4 +175,64 @@ impl CentralizedTestEnvBuilder { client, }) } + + /// Spin up a KMS server on an existing material directory. Use to verify + /// state persists across server restarts. `path` must outlive the + /// returned pair. + pub async fn from_path( + self, + path: &Path, + ) -> Result<(ServerHandle, CoreServiceEndpointClient)> { + let pub_storage = FileStorage::new(Some(path), StorageType::PUB, None)?; + let priv_storage = FileStorage::new(Some(path), StorageType::PRIV, None)?; + let backup_vault = if self.with_backup_vault { + Some(build_backup_vault(path, self.with_custodian_keychain).await?) + } else { + None + }; + Ok(setup_centralized( + pub_storage, + priv_storage, + backup_vault, + self.rate_limiter_conf, + ) + .await) + } +} + +async fn build_backup_vault(material_path: &Path, with_custodian_keychain: bool) -> Result { + // Create BACKUP directory + let backup_dir = material_path.join("BACKUP"); + std::fs::create_dir_all(&backup_dir)?; + + let backup_proxy = StorageProxy::from(FileStorage::new( + Some(material_path), + StorageType::BACKUP, + None, + )?); + + let keychain = if with_custodian_keychain { + let pub_proxy = StorageProxy::from(FileStorage::new( + Some(material_path), + StorageType::PUB, + None, + )?); + Some( + make_keychain_proxy( + &Keychain::SecretSharing(SecretSharingKeychain {}), + None, + None, + Some(&pub_proxy), + false, + ) + .await?, + ) + } else { + None + }; + + Ok(Vault { + storage: backup_proxy, + keychain, + }) } diff --git a/core/service/src/testing/setup/threshold.rs b/core/service/src/testing/setup/threshold.rs index 4043ef83a6..46a63d4d87 100644 --- a/core/service/src/testing/setup/threshold.rs +++ b/core/service/src/testing/setup/threshold.rs @@ -2,6 +2,8 @@ //! //! This module provides a builder pattern for setting up isolated threshold KMS //! test environments with automatic cleanup. +use crate::client::test_tools::setup_threshold_isolated; +use crate::conf::{Keychain, SecretSharingKeychain}; use crate::consts::{ BACKUP_STORAGE_PREFIX_THRESHOLD_ALL, PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL, PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL, SIGNING_KEY_ID, @@ -13,10 +15,13 @@ pub use crate::testing::types::ThresholdTestConfig; use crate::util::key_setup::{ ThresholdSigningKeyConfig, ensure_client_keys_exist, ensure_threshold_server_signing_keys_exist, }; -use crate::vault::storage::{StorageType, file::FileStorage}; +use crate::vault::Vault; +use crate::vault::keychain::make_keychain_proxy; +use crate::vault::storage::{StorageProxy, StorageType, file::FileStorage}; use anyhow::Result; use kms_grpc::kms_service::v1::core_service_endpoint_client::CoreServiceEndpointClient; use std::collections::HashMap; +use std::path::Path; use tempfile::TempDir; use tonic::transport::Channel; @@ -338,52 +343,13 @@ impl ThresholdTestEnvBuilder { // Ensure client signing/verification keys exist ensure_client_keys_exist(Some(material_dir.path()), &SIGNING_KEY_ID, true).await; - // Create backup vaults for each party if requested - let vaults: Vec> = if self.with_backup_vault { - use crate::conf::{Keychain, SecretSharingKeychain}; - use crate::vault::Vault; - use crate::vault::keychain::make_keychain_proxy; - use std::fs; - - let mut vaults = Vec::new(); - let backup_prefixes = &BACKUP_STORAGE_PREFIX_THRESHOLD_ALL[0..self.party_count]; - for (backup_prefix, pub_prefix) in backup_prefixes.iter().zip(pub_prefixes) { - // Create BACKUP directory for this party - let backup_dir = material_dir.path().join(backup_prefix.as_deref().unwrap()); - fs::create_dir_all(&backup_dir)?; - - let backup_proxy = crate::vault::storage::StorageProxy::from(FileStorage::new( - Some(material_dir.path()), - StorageType::BACKUP, - backup_prefix.as_deref(), - )?); - - let keychain = if self.with_custodian_keychain { - let pub_proxy = crate::vault::storage::StorageProxy::from(FileStorage::new( - Some(material_dir.path()), - StorageType::PUB, - pub_prefix.as_deref(), - )?); - Some( - make_keychain_proxy( - &Keychain::SecretSharing(SecretSharingKeychain {}), - None, - None, - Some(&pub_proxy), - false, - ) - .await?, - ) - } else { - None - }; - - vaults.push(Some(Vault { - storage: backup_proxy, - keychain, - })); - } - vaults + let vaults = if self.with_backup_vault { + build_threshold_vaults( + material_dir.path(), + self.party_count, + self.with_custodian_keychain, + ) + .await? } else { (0..self.party_count).map(|_| None).collect() }; @@ -393,7 +359,6 @@ impl ThresholdTestEnvBuilder { .threshold .unwrap_or_else(|| ((self.party_count - 1) / 3).max(1) as u8); - // Setup threshold KMS let config = ThresholdTestConfig { ensure_default_prss: self.ensure_default_prss, rate_limiter_conf: self.rate_limiter_conf, @@ -401,14 +366,8 @@ impl ThresholdTestEnvBuilder { test_material_path: Some(material_dir.path()), }; - let (servers, clients) = crate::client::test_tools::setup_threshold_isolated( - threshold, - pub_storages, - priv_storages, - vaults, - config, - ) - .await; + let (servers, clients) = + setup_threshold_isolated(threshold, pub_storages, priv_storages, vaults, config).await; Ok(ThresholdTestEnv { material_dir, @@ -416,4 +375,96 @@ impl ThresholdTestEnvBuilder { clients, }) } + + /// Spin up a fresh threshold KMS on an existing material directory. Use + /// to verify that state persists across server restarts. `path` must outlive + /// the returned pair. + pub async fn from_path( + self, + path: &Path, + ) -> Result<( + HashMap, + HashMap>, + )> { + let pub_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..self.party_count]; + let priv_prefixes = &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..self.party_count]; + let mut pub_storages = Vec::with_capacity(self.party_count); + let mut priv_storages = Vec::with_capacity(self.party_count); + for (pub_prefix, priv_prefix) in pub_prefixes.iter().zip(priv_prefixes) { + pub_storages.push(FileStorage::new( + Some(path), + StorageType::PUB, + pub_prefix.as_deref(), + )?); + priv_storages.push(FileStorage::new( + Some(path), + StorageType::PRIV, + priv_prefix.as_deref(), + )?); + } + + let vaults = if self.with_backup_vault { + build_threshold_vaults(path, self.party_count, self.with_custodian_keychain).await? + } else { + (0..self.party_count).map(|_| None).collect() + }; + + let threshold = self + .threshold + .unwrap_or_else(|| ((self.party_count - 1) / 3).max(1) as u8); + let config = ThresholdTestConfig { + ensure_default_prss: self.ensure_default_prss, + rate_limiter_conf: self.rate_limiter_conf, + decryption_mode: self.decryption_mode, + test_material_path: Some(path), + }; + + Ok(setup_threshold_isolated(threshold, pub_storages, priv_storages, vaults, config).await) + } +} + +async fn build_threshold_vaults( + material_path: &Path, + party_count: usize, + with_custodian_keychain: bool, +) -> Result>> { + let pub_prefixes = &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..party_count]; + let backup_prefixes = &BACKUP_STORAGE_PREFIX_THRESHOLD_ALL[0..party_count]; + let mut vaults = Vec::with_capacity(party_count); + for (backup_prefix, pub_prefix) in backup_prefixes.iter().zip(pub_prefixes) { + let backup_dir = material_path.join(backup_prefix.as_deref().unwrap()); + std::fs::create_dir_all(&backup_dir)?; + + let backup_proxy = StorageProxy::from(FileStorage::new( + Some(material_path), + StorageType::BACKUP, + backup_prefix.as_deref(), + )?); + + let keychain = if with_custodian_keychain { + let pub_proxy = StorageProxy::from(FileStorage::new( + Some(material_path), + StorageType::PUB, + pub_prefix.as_deref(), + )?); + Some( + make_keychain_proxy( + &Keychain::SecretSharing(SecretSharingKeychain {}), + None, + None, + Some(&pub_proxy), + false, + ) + .await?, + ) + } else { + None + }; + + vaults.push(Some(Vault { + storage: backup_proxy, + keychain, + })); + } + Ok(vaults) } diff --git a/core/service/src/testing/utils.rs b/core/service/src/testing/utils.rs index 2879a76c44..e2e8804d46 100644 --- a/core/service/src/testing/utils.rs +++ b/core/service/src/testing/utils.rs @@ -471,22 +471,6 @@ pub mod setup { } } - pub async fn ensure_testing_material_exists(path: Option<&Path>) { - generate_material_to_path(MaterialType::Testing, path, &[4, 10]) - .await - .expect("testing material generation should succeed"); - } - - pub async fn ensure_default_material_exists() { - ensure_default_material_exists_to_path(None).await; - } - - pub async fn ensure_default_material_exists_to_path(path: Option<&Path>) { - generate_material_to_path(MaterialType::Default, path, &[4, 10, 13]) - .await - .expect("default material generation should succeed"); - } - pub async fn generate_material_to_path( material_type: MaterialType, path: Option<&Path>, diff --git a/core/service/src/util/key_setup/test_tools.rs b/core/service/src/util/key_setup/test_tools.rs index e70b65ba52..3d11c7c271 100644 --- a/core/service/src/util/key_setup/test_tools.rs +++ b/core/service/src/util/key_setup/test_tools.rs @@ -586,236 +586,3 @@ async fn read_custodian_backup_files_impl( } files } - -#[cfg(any(test, feature = "testing"))] -pub(crate) mod setup { - use crate::consts::DEFAULT_EPOCH_ID; - use crate::consts::{ - PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL, PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL, - }; - #[cfg(feature = "slow_tests")] - use crate::consts::{TEST_THRESHOLD_CRS_ID_13P, TEST_THRESHOLD_KEY_ID_13P}; - use crate::util::key_setup::{ - ThresholdSigningKeyConfig, ensure_central_crs_exists, ensure_central_keys_exist, - ensure_client_keys_exist, - }; - use crate::{ - consts::{ - KEY_PATH_PREFIX, OTHER_CENTRAL_TEST_ID, SIGNING_KEY_ID, TEST_CENTRAL_CRS_ID, - TEST_CENTRAL_KEY_ID, TEST_PARAM, TEST_THRESHOLD_CRS_ID_4P, TEST_THRESHOLD_KEY_ID_4P, - TMP_PATH_PREFIX, - }, - util::key_setup::ensure_central_server_signing_keys_exist, - }; - use crate::{ - util::key_setup::{ - ensure_threshold_crs_exists, ensure_threshold_keys_exist, - ensure_threshold_server_signing_keys_exist, - }, - vault::storage::{StorageType, file::FileStorage}, - }; - use kms_grpc::RequestId; - use kms_grpc::identifiers::EpochId; - use std::path::Path; - use threshold_execution::tfhe_internals::parameters::DKGParams; - - pub async fn ensure_dir_exist(path: Option<&Path>) { - match path { - Some(p) => { - tokio::fs::create_dir_all(p.join(TMP_PATH_PREFIX)) - .await - .unwrap(); - tokio::fs::create_dir_all(p.join(KEY_PATH_PREFIX)) - .await - .unwrap(); - } - None => { - tokio::fs::create_dir_all(TMP_PATH_PREFIX).await.unwrap(); - tokio::fs::create_dir_all(KEY_PATH_PREFIX).await.unwrap(); - } - } - } - - async fn testing_material(path: Option<&Path>) { - ensure_dir_exist(path).await; - let epoch_id = *DEFAULT_EPOCH_ID; - ensure_client_keys_exist(path, &SIGNING_KEY_ID, true).await; - central_material( - &TEST_PARAM, - &TEST_CENTRAL_KEY_ID, - &OTHER_CENTRAL_TEST_ID, - &TEST_CENTRAL_CRS_ID, - &epoch_id, - path, - ) - .await; - let epoch_id = *DEFAULT_EPOCH_ID; - threshold_material( - &TEST_PARAM, - &TEST_THRESHOLD_KEY_ID_4P, - &TEST_THRESHOLD_CRS_ID_4P, - &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..4], - &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..4], - &epoch_id, - path, - ) - .await; - #[cfg(feature = "slow_tests")] - threshold_material( - &TEST_PARAM, - &TEST_THRESHOLD_KEY_ID_13P, - &TEST_THRESHOLD_CRS_ID_13P, - &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..13], - &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..13], - &epoch_id, - path, - ) - .await; - } - - pub(crate) async fn ensure_testing_material_exists(path: Option<&Path>) { - testing_material(path).await; - } - - #[allow(dead_code)] - async fn default_material() { - use crate::consts::{ - DEFAULT_CENTRAL_CRS_ID, DEFAULT_CENTRAL_KEY_ID, DEFAULT_PARAM, - DEFAULT_THRESHOLD_CRS_ID_4P, DEFAULT_THRESHOLD_CRS_ID_13P, DEFAULT_THRESHOLD_KEY_ID_4P, - DEFAULT_THRESHOLD_KEY_ID_13P, OTHER_CENTRAL_DEFAULT_ID, - }; - ensure_dir_exist(None).await; - let epoch_id = *DEFAULT_EPOCH_ID; - ensure_client_keys_exist(None, &SIGNING_KEY_ID, true).await; - central_material( - &DEFAULT_PARAM, - &DEFAULT_CENTRAL_KEY_ID, - &OTHER_CENTRAL_DEFAULT_ID, - &DEFAULT_CENTRAL_CRS_ID, - &epoch_id, - None, - ) - .await; - threshold_material( - &DEFAULT_PARAM, - &DEFAULT_THRESHOLD_KEY_ID_4P, - &DEFAULT_THRESHOLD_CRS_ID_4P, - &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..4], - &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..4], - &epoch_id, - None, - ) - .await; - threshold_material( - &DEFAULT_PARAM, - &DEFAULT_THRESHOLD_KEY_ID_13P, - &DEFAULT_THRESHOLD_CRS_ID_13P, - &PUBLIC_STORAGE_PREFIX_THRESHOLD_ALL[0..13], - &PRIVATE_STORAGE_PREFIX_THRESHOLD_ALL[0..13], - &epoch_id, - None, - ) - .await; - } - - async fn central_material( - params: &DKGParams, - fhe_key_id: &RequestId, - other_fhe_key_id: &RequestId, - crs_id: &RequestId, - epoch_id: &EpochId, - path: Option<&Path>, - ) { - let mut central_pub_storage = FileStorage::new(path, StorageType::PUB, None).unwrap(); - let mut central_priv_storage = FileStorage::new(path, StorageType::PRIV, None).unwrap(); - - ensure_central_server_signing_keys_exist( - &mut central_pub_storage, - &mut central_priv_storage, - &SIGNING_KEY_ID, - true, - ) - .await; - ensure_central_keys_exist( - &mut central_pub_storage, - &mut central_priv_storage, - params.to_owned(), - fhe_key_id, - other_fhe_key_id, - epoch_id, - true, - ) - .await; - ensure_central_crs_exists( - &mut central_pub_storage, - &mut central_priv_storage, - params.to_owned(), - crs_id, - epoch_id, - true, - ) - .await; - } - - async fn threshold_material( - params: &DKGParams, - fhe_key_id: &RequestId, - crs_id: &RequestId, - public_storage_prefixes: &[Option], - private_storage_prefixes: &[Option], - epoch_id: &EpochId, - path: Option<&Path>, - ) { - assert_eq!( - public_storage_prefixes.len(), - private_storage_prefixes.len() - ); - let amount_parties = public_storage_prefixes.len(); - let mut threshold_pub_storages = Vec::with_capacity(amount_parties); - for storage_prefix in public_storage_prefixes.iter() { - threshold_pub_storages - .push(FileStorage::new(path, StorageType::PUB, storage_prefix.as_deref()).unwrap()); - } - let mut threshold_priv_storages = Vec::with_capacity(amount_parties); - for storage_prefix in private_storage_prefixes.iter() { - threshold_priv_storages.push( - FileStorage::new(path, StorageType::PRIV, storage_prefix.as_deref()).unwrap(), - ); - } - - let _ = ensure_threshold_server_signing_keys_exist( - &mut threshold_pub_storages, - &mut threshold_priv_storages, - &SIGNING_KEY_ID, - true, - ThresholdSigningKeyConfig::AllParties( - (1..=amount_parties).map(|i| format!("party-{i}")).collect(), - ), - false, - ) - .await; - ensure_threshold_keys_exist( - &mut threshold_pub_storages, - &mut threshold_priv_storages, - params.to_owned(), - fhe_key_id, - epoch_id, - true, - ) - .await; - ensure_threshold_crs_exists( - &mut threshold_pub_storages, - &mut threshold_priv_storages, - params.to_owned(), - crs_id, - epoch_id, - true, - ) - .await; - } - - #[allow(dead_code)] - pub(crate) async fn ensure_default_material_exists() { - default_material().await; - } -}