Skip to content

Commit ad16b14

Browse files
committed
feat: copy migrated key from new key ID to the old one
1 parent 3547c87 commit ad16b14

7 files changed

Lines changed: 1360 additions & 3 deletions

File tree

core/grpc/proto/kms.v1.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ message KeySetAddedInfo {
6060
// This option is only used when KeyGenSecretKeyConfig::UseExisting is used.
6161
// Otherwise it is ignored.
6262
bool use_existing_key_tag = 5;
63+
64+
// If true, after a compressed keygen from existing shares
65+
// (UseExisting + CompressedKeyConfig::All), copy the resulting
66+
// CompressedXofKeySet to the original key ID and replace the original
67+
// ThresholdFheKeys with the migrated one. The CompactPublicKey is
68+
// preserved at both the original and the new key ID.
69+
//
70+
// Only used when KeyGenSecretKeyConfig::UseExisting is used with
71+
// CompressedKeyConfig::All. Otherwise it is ignored.
72+
bool copy_compressed_key_to_original = 4;
6373
}
6474

6575
// The keyset configuration message.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub(crate) fn keygen_config_from_existing(
7878
to_keyset_id_decompression_only: None,
7979
existing_keyset_id: Some((*existing_keyset_id).into()),
8080
use_existing_key_tag,
81+
copy_compressed_key_to_original: false,
8182
}),
8283
)
8384
}
@@ -98,6 +99,7 @@ pub(crate) fn decompression_keygen_config(
9899
to_keyset_id_decompression_only: Some((*to_keyset_id).into()),
99100
existing_keyset_id: None,
100101
use_existing_key_tag: false,
102+
copy_compressed_key_to_original: false,
101103
}),
102104
)
103105
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,7 @@ async fn test_insecure_threshold_decompression_keygen_isolated() -> Result<()> {
807807
to_keyset_id_decompression_only: Some(key_id_2.into()),
808808
existing_keyset_id: None,
809809
use_existing_key_tag: false,
810+
copy_compressed_key_to_original: false,
810811
}),
811812
context_id: None,
812813
epoch_id: None,

core/service/src/engine/keyset_configuration.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,12 @@ impl InternalKeySetConfig {
182182
.is_some_and(|info| info.use_existing_key_tag)
183183
}
184184

185+
pub fn copy_compressed_key_to_original(&self) -> bool {
186+
self.keyset_added_info
187+
.as_ref()
188+
.is_some_and(|info| info.copy_compressed_key_to_original)
189+
}
190+
185191
pub fn validate(&self) -> anyhow::Result<()> {
186192
match &self.keyset_config {
187193
ddec_keyset_config::KeySetConfig::Standard(inner) => match inner.secret_key_config {

core/service/src/engine/threshold/service/key_generator.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1486,7 +1486,7 @@ impl<
14861486
threshold_fhe_keys,
14871487
&compressed_keyset,
14881488
&compact_pk,
1489-
meta_store,
1489+
Arc::clone(&meta_store),
14901490
)
14911491
.await
14921492
{
@@ -1495,6 +1495,50 @@ impl<
14951495
);
14961496
return;
14971497
}
1498+
1499+
// If requested, copy the compressed key to the original key ID.
1500+
//
1501+
// Note: at this point the *new* keygen has already been committed
1502+
// and its meta_store entry is Done — that part of the request
1503+
// succeeded. The copy is a follow-up on a *different* key id
1504+
// (old_key_id), so a failure here does not invalidate the
1505+
// new_key_id material. We log loudly so operators can detect
1506+
// partial success and retry the migration, but we do not try
1507+
// to mark the new keygen itself as failed.
1508+
//
1509+
// TODO if copying fails, do we still call `update_backup_vault`?
1510+
if matches!(
1511+
keyset_config.secret_key_config,
1512+
ddec_keyset_config::KeyGenSecretKeyConfig::UseExisting
1513+
) && internal_keyset_config.copy_compressed_key_to_original()
1514+
{
1515+
let old_key_id = internal_keyset_config
1516+
.get_existing_keyset_id()
1517+
.expect("validated");
1518+
// UseExisting reads the old private shares at the current
1519+
// epoch_id (see key_gen_from_existing_private_keyset), so
1520+
// the copy targets the same (old_key_id, epoch_id) pair.
1521+
if let Err(e) = crypto_storage
1522+
.copy_compressed_key_to_original(
1523+
req_id,
1524+
epoch_id,
1525+
&old_key_id,
1526+
epoch_id,
1527+
&sk,
1528+
&eip712_domain,
1529+
Arc::clone(&meta_store),
1530+
)
1531+
.await
1532+
{
1533+
tracing::error!(
1534+
"Compressed keygen for {req_id} committed successfully, but the \
1535+
follow-up copy to original key id {old_key_id} failed: {e}. \
1536+
The new keys at {req_id} are valid; \
1537+
the migration to {old_key_id} must be retried."
1538+
);
1539+
return;
1540+
}
1541+
}
14981542
}
14991543
}
15001544
// Update the backup and handle potential failures by incrementing backup errors in the metrics

0 commit comments

Comments
 (0)