From 9a543c7e4206be98d8265c18f99fc1f5ab3c4465 Mon Sep 17 00:00:00 2001 From: Franz-Stefan Preiss Date: Fri, 28 Nov 2025 13:23:34 +0000 Subject: [PATCH 1/6] feat(crypto): CRP-2618 allow omitting pre_signatures_to_create_in_advance for keys that don't require it --- .../canister/src/invariants/crypto/tests.rs | 28 +++++++++++++++++-- .../src/mutations/do_create_subnet.rs | 12 ++++---- .../src/mutations/do_recover_subnet.rs | 12 ++++---- .../src/mutations/do_update_subnet.rs | 10 +++---- rs/registry/subnet_features/src/lib.rs | 15 ++++++---- 5 files changed, 51 insertions(+), 26 deletions(-) diff --git a/rs/registry/canister/src/invariants/crypto/tests.rs b/rs/registry/canister/src/invariants/crypto/tests.rs index 513a244373b4..e5aa12cbe7aa 100644 --- a/rs/registry/canister/src/invariants/crypto/tests.rs +++ b/rs/registry/canister/src/invariants/crypto/tests.rs @@ -682,7 +682,7 @@ mod chain_key_enabled_subnet_lists { #[should_panic( expected = "Missing required struct field: KeyConfig::pre_signatures_to_create_in_advance" )] - fn should_fail_if_missing_pre_signatures() { + fn should_fail_if_missing_pre_signatures_for_key_that_requires_pre_signatures() { let mut config = invariant_compliant_chain_key_config(); config.key_configs[1].pre_signatures_to_create_in_advance = None; check_chain_key_config_invariant(config); @@ -692,12 +692,36 @@ mod chain_key_enabled_subnet_lists { #[should_panic( expected = "`pre_signatures_to_create_in_advance` for key ecdsa:Secp256k1:ecdsa_key of subnet ya35z-hhham-aaaaa-aaaap-yai cannot be zero." )] - fn should_fail_if_pre_signatures_is_zero() { + fn should_fail_if_pre_signatures_is_zero_for_key_that_requires_pre_signatures() { let mut config = invariant_compliant_chain_key_config(); config.key_configs[0].pre_signatures_to_create_in_advance = Some(0); check_chain_key_config_invariant(config); } + #[test] + fn should_succeed_if_pre_signatures_is_missing_for_key_that_does_not_require_pre_signatures() { + let mut config = invariant_compliant_chain_key_config(); + let key_config = &mut config.key_configs[2]; + assert!(matches!( + key_config.key_id.as_ref().unwrap().key_id, + Some(master_public_key_id::KeyId::Vetkd(_)) + ),); + key_config.pre_signatures_to_create_in_advance = None; + check_chain_key_config_invariant(config); + } + + #[test] + fn should_succeed_if_pre_signatures_is_zero_for_key_that_does_not_require_pre_signatures() { + let mut config = invariant_compliant_chain_key_config(); + let key_config = &mut config.key_configs[2]; + assert!(matches!( + key_config.key_id.as_ref().unwrap().key_id, + Some(master_public_key_id::KeyId::Vetkd(_)) + ),); + key_config.pre_signatures_to_create_in_advance = Some(0); + check_chain_key_config_invariant(config); + } + #[test] #[should_panic(expected = "Missing required struct field: KeyConfig::max_queue_size")] fn should_fail_if_missing_queue_size() { diff --git a/rs/registry/canister/src/mutations/do_create_subnet.rs b/rs/registry/canister/src/mutations/do_create_subnet.rs index a689fe407854..f66525e7d0f5 100644 --- a/rs/registry/canister/src/mutations/do_create_subnet.rs +++ b/rs/registry/canister/src/mutations/do_create_subnet.rs @@ -413,20 +413,18 @@ impl TryFrom for KeyConfigInternal { let Some(key_id) = key_id else { return Err("KeyConfig.key_id must be specified.".to_string()); }; - - let Some(pre_signatures_to_create_in_advance) = pre_signatures_to_create_in_advance else { - return Err( - "KeyConfig.pre_signatures_to_create_in_advance must be specified.".to_string(), - ); + if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { + return Err(format!( + "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}." + )); }; - let Some(max_queue_size) = max_queue_size else { return Err("KeyConfig.max_queue_size must be specified.".to_string()); }; Ok(Self { key_id, - pre_signatures_to_create_in_advance, + pre_signatures_to_create_in_advance: pre_signatures_to_create_in_advance.unwrap_or(0), max_queue_size, }) } diff --git a/rs/registry/canister/src/mutations/do_recover_subnet.rs b/rs/registry/canister/src/mutations/do_recover_subnet.rs index 53a5291ab41c..0d4f07b10f69 100644 --- a/rs/registry/canister/src/mutations/do_recover_subnet.rs +++ b/rs/registry/canister/src/mutations/do_recover_subnet.rs @@ -377,20 +377,18 @@ impl TryFrom for KeyConfigInternal { let Some(key_id) = key_id else { return Err("KeyConfig.key_id must be specified.".to_string()); }; - - let Some(pre_signatures_to_create_in_advance) = pre_signatures_to_create_in_advance else { - return Err( - "KeyConfig.pre_signatures_to_create_in_advance must be specified.".to_string(), - ); + if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { + return Err(format!( + "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}." + )); }; - let Some(max_queue_size) = max_queue_size else { return Err("KeyConfig.max_queue_size must be specified.".to_string()); }; Ok(Self { key_id, - pre_signatures_to_create_in_advance, + pre_signatures_to_create_in_advance: pre_signatures_to_create_in_advance.unwrap_or(0), max_queue_size, }) } diff --git a/rs/registry/canister/src/mutations/do_update_subnet.rs b/rs/registry/canister/src/mutations/do_update_subnet.rs index 67db8665e04b..9ee3db81a58f 100644 --- a/rs/registry/canister/src/mutations/do_update_subnet.rs +++ b/rs/registry/canister/src/mutations/do_update_subnet.rs @@ -368,10 +368,10 @@ impl TryFrom for KeyConfigInternal { let Some(key_id) = key_id else { return Err("KeyConfig.key_id must be specified.".to_string()); }; - let Some(pre_signatures_to_create_in_advance) = pre_signatures_to_create_in_advance else { - return Err( - "KeyConfig.pre_signatures_to_create_in_advance must be specified.".to_string(), - ); + if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { + return Err(format!( + "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}." + )); }; let Some(max_queue_size) = max_queue_size else { return Err("KeyConfig.max_queue_size must be specified.".to_string()); @@ -379,7 +379,7 @@ impl TryFrom for KeyConfigInternal { Ok(Self { key_id, - pre_signatures_to_create_in_advance, + pre_signatures_to_create_in_advance: pre_signatures_to_create_in_advance.unwrap_or(0), max_queue_size, }) } diff --git a/rs/registry/subnet_features/src/lib.rs b/rs/registry/subnet_features/src/lib.rs index 3f2babda5457..8cd2126c8172 100644 --- a/rs/registry/subnet_features/src/lib.rs +++ b/rs/registry/subnet_features/src/lib.rs @@ -119,12 +119,17 @@ impl TryFrom for KeyConfig { type Error = ProxyDecodeError; fn try_from(value: pb::KeyConfig) -> Result { - Ok(KeyConfig { - pre_signatures_to_create_in_advance: try_from_option_field( - value.pre_signatures_to_create_in_advance, + let key_id: MasterPublicKeyId = try_from_option_field(value.key_id, "KeyConfig::key_id")?; + if key_id.requires_pre_signatures() && value.pre_signatures_to_create_in_advance.is_none() { + return Err(ProxyDecodeError::MissingField( "KeyConfig::pre_signatures_to_create_in_advance", - )?, - key_id: try_from_option_field(value.key_id, "KeyConfig::key_id")?, + )); + } + Ok(KeyConfig { + pre_signatures_to_create_in_advance: value + .pre_signatures_to_create_in_advance + .unwrap_or(0), + key_id, max_queue_size: try_from_option_field( value.max_queue_size, "KeyConfig::max_queue_size", From c8d97584cbcc46e3dbe049f5d3466e10da32886e Mon Sep 17 00:00:00 2001 From: Franz-Stefan Preiss Date: Fri, 28 Nov 2025 21:21:57 +0000 Subject: [PATCH 2/6] fix more tests --- rs/consensus/vetkd/src/test_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/consensus/vetkd/src/test_utils.rs b/rs/consensus/vetkd/src/test_utils.rs index 5bac0ea0612b..06d923fef439 100644 --- a/rs/consensus/vetkd/src/test_utils.rs +++ b/rs/consensus/vetkd/src/test_utils.rs @@ -148,7 +148,7 @@ pub(super) fn fake_signature_request_context( SignWithThresholdContext { request: RequestBuilder::new().build(), args: fake_signature_request_args(key_id), - derivation_path: Arc::new(vec![]), + derivation_path: Arc::new(vec![vec![]]), batch_time: UNIX_EPOCH, pseudo_random_id: [0; 32], matched_pre_signature: None, From d35bcc795c47df5105f1ca9e7cfec4881fbe4328 Mon Sep 17 00:00:00 2001 From: Franz-Stefan Preiss Date: Fri, 28 Nov 2025 22:18:22 +0000 Subject: [PATCH 3/6] Revert "fix more tests" This reverts commit c8d97584cbcc46e3dbe049f5d3466e10da32886e. --- rs/consensus/vetkd/src/test_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/consensus/vetkd/src/test_utils.rs b/rs/consensus/vetkd/src/test_utils.rs index 06d923fef439..5bac0ea0612b 100644 --- a/rs/consensus/vetkd/src/test_utils.rs +++ b/rs/consensus/vetkd/src/test_utils.rs @@ -148,7 +148,7 @@ pub(super) fn fake_signature_request_context( SignWithThresholdContext { request: RequestBuilder::new().build(), args: fake_signature_request_args(key_id), - derivation_path: Arc::new(vec![vec![]]), + derivation_path: Arc::new(vec![]), batch_time: UNIX_EPOCH, pseudo_random_id: [0; 32], matched_pre_signature: None, From de27f077578825a393e3d533faeebb743566d966 Mon Sep 17 00:00:00 2001 From: Franz-Stefan Preiss Date: Sat, 29 Nov 2025 01:06:12 +0000 Subject: [PATCH 4/6] changelog --- rs/registry/canister/unreleased_changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rs/registry/canister/unreleased_changelog.md b/rs/registry/canister/unreleased_changelog.md index fa4cd939dc82..81916ac46361 100644 --- a/rs/registry/canister/unreleased_changelog.md +++ b/rs/registry/canister/unreleased_changelog.md @@ -20,6 +20,8 @@ All node operators can now swap nodes on non-system subnets; later, swapping wil ## Changed +* When performing subnet creation, subnet update, or subnet recovery, it is now allowed to omit the `KeyConfig`'s `pre_signatures_to_create_in_advance` field for keys that do not have pre-signatures. Currently only vetKD keys do not have pre-signatures (unlike Ecdsa/Schnorr keys). When the field is omitted, it is automatically set to zero. + ## Deprecated ## Removed From 320e239a4a5cf1f950836f0c905623a9cd1fec20 Mon Sep 17 00:00:00 2001 From: Franz-Stefan Preiss Date: Mon, 1 Dec 2025 14:52:41 +0000 Subject: [PATCH 5/6] blank lines around pre-sig check --- rs/registry/canister/src/mutations/do_create_subnet.rs | 2 ++ rs/registry/canister/src/mutations/do_recover_subnet.rs | 2 ++ rs/registry/canister/src/mutations/do_update_subnet.rs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/rs/registry/canister/src/mutations/do_create_subnet.rs b/rs/registry/canister/src/mutations/do_create_subnet.rs index f66525e7d0f5..7ac634a281c3 100644 --- a/rs/registry/canister/src/mutations/do_create_subnet.rs +++ b/rs/registry/canister/src/mutations/do_create_subnet.rs @@ -413,11 +413,13 @@ impl TryFrom for KeyConfigInternal { let Some(key_id) = key_id else { return Err("KeyConfig.key_id must be specified.".to_string()); }; + if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { return Err(format!( "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}." )); }; + let Some(max_queue_size) = max_queue_size else { return Err("KeyConfig.max_queue_size must be specified.".to_string()); }; diff --git a/rs/registry/canister/src/mutations/do_recover_subnet.rs b/rs/registry/canister/src/mutations/do_recover_subnet.rs index 0d4f07b10f69..411320d0c5aa 100644 --- a/rs/registry/canister/src/mutations/do_recover_subnet.rs +++ b/rs/registry/canister/src/mutations/do_recover_subnet.rs @@ -377,11 +377,13 @@ impl TryFrom for KeyConfigInternal { let Some(key_id) = key_id else { return Err("KeyConfig.key_id must be specified.".to_string()); }; + if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { return Err(format!( "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}." )); }; + let Some(max_queue_size) = max_queue_size else { return Err("KeyConfig.max_queue_size must be specified.".to_string()); }; diff --git a/rs/registry/canister/src/mutations/do_update_subnet.rs b/rs/registry/canister/src/mutations/do_update_subnet.rs index 9ee3db81a58f..74cac6b9aca0 100644 --- a/rs/registry/canister/src/mutations/do_update_subnet.rs +++ b/rs/registry/canister/src/mutations/do_update_subnet.rs @@ -368,11 +368,13 @@ impl TryFrom for KeyConfigInternal { let Some(key_id) = key_id else { return Err("KeyConfig.key_id must be specified.".to_string()); }; + if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { return Err(format!( "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}." )); }; + let Some(max_queue_size) = max_queue_size else { return Err("KeyConfig.max_queue_size must be specified.".to_string()); }; From 9355b11da01b4c2a97b0faaddc13d74d0a0695dd Mon Sep 17 00:00:00 2001 From: Franz-Stefan Preiss Date: Mon, 1 Dec 2025 14:54:36 +0000 Subject: [PATCH 6/6] Add comment, mentioning invariant --- rs/registry/canister/src/mutations/do_create_subnet.rs | 2 ++ rs/registry/canister/src/mutations/do_recover_subnet.rs | 2 ++ rs/registry/canister/src/mutations/do_update_subnet.rs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/rs/registry/canister/src/mutations/do_create_subnet.rs b/rs/registry/canister/src/mutations/do_create_subnet.rs index 7ac634a281c3..6a2c3aafed72 100644 --- a/rs/registry/canister/src/mutations/do_create_subnet.rs +++ b/rs/registry/canister/src/mutations/do_create_subnet.rs @@ -414,6 +414,8 @@ impl TryFrom for KeyConfigInternal { return Err("KeyConfig.key_id must be specified.".to_string()); }; + // Ensure presence of `pre_signatures_to_create_in_advance` for keys that require pre-signatures. + // Note that an invariant ensures that this field is not zero for keys that require pre-signatures. if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { return Err(format!( "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}." diff --git a/rs/registry/canister/src/mutations/do_recover_subnet.rs b/rs/registry/canister/src/mutations/do_recover_subnet.rs index 411320d0c5aa..b936eae813a7 100644 --- a/rs/registry/canister/src/mutations/do_recover_subnet.rs +++ b/rs/registry/canister/src/mutations/do_recover_subnet.rs @@ -378,6 +378,8 @@ impl TryFrom for KeyConfigInternal { return Err("KeyConfig.key_id must be specified.".to_string()); }; + // Ensure presence of `pre_signatures_to_create_in_advance` for keys that require pre-signatures. + // Note that an invariant ensures that this field is not zero for keys that require pre-signatures. if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { return Err(format!( "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}." diff --git a/rs/registry/canister/src/mutations/do_update_subnet.rs b/rs/registry/canister/src/mutations/do_update_subnet.rs index 74cac6b9aca0..0501330f6ed3 100644 --- a/rs/registry/canister/src/mutations/do_update_subnet.rs +++ b/rs/registry/canister/src/mutations/do_update_subnet.rs @@ -369,6 +369,8 @@ impl TryFrom for KeyConfigInternal { return Err("KeyConfig.key_id must be specified.".to_string()); }; + // Ensure presence of `pre_signatures_to_create_in_advance` for keys that require pre-signatures. + // Note that an invariant ensures that this field is not zero for keys that require pre-signatures. if key_id.requires_pre_signatures() && pre_signatures_to_create_in_advance.is_none() { return Err(format!( "KeyConfig.pre_signatures_to_create_in_advance must be specified for key {key_id}."