From 871a7c5ca756e2b3cb712c29d06d5d9625bd1996 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:43:09 +0100 Subject: [PATCH 1/4] Adding Protocol --- crates/contract-interface/src/types/state.rs | 2 - crates/contract/src/dto_mapping.rs | 3 +- crates/contract/src/lib.rs | 12 +- crates/contract/src/primitives/domain.rs | 251 ++++++++++-------- crates/contract/src/primitives/test_utils.rs | 12 +- crates/contract/src/state/running.rs | 12 +- crates/contract/src/utils.rs | 6 +- .../tests/inprocess/attestation_submission.rs | 6 +- crates/contract/tests/sandbox/common.rs | 14 +- .../tests/sandbox/participants_gas.rs | 4 +- .../sandbox/upgrade_to_current_contract.rs | 6 +- crates/contract/tests/sandbox/utils/consts.rs | 3 +- .../contract/tests/sandbox/utils/interface.rs | 1 - .../tests/sandbox/utils/shared_key_utils.rs | 2 +- crates/contract/tests/sandbox/vote.rs | 6 +- crates/devnet/src/contracts.rs | 8 +- crates/devnet/src/loadtest.rs | 4 +- crates/devnet/src/mpc.rs | 4 +- crates/node/src/coordinator.rs | 6 +- crates/node/src/key_events.rs | 39 +-- crates/node/src/keyshare.rs | 4 - crates/node/src/mpc_client.rs | 19 -- crates/node/src/tests.rs | 8 +- crates/node/src/tests/basic_cluster.rs | 10 +- .../src/tests/changing_participant_details.rs | 6 +- crates/node/src/tests/faulty.rs | 8 +- crates/node/src/tests/multidomain.rs | 28 +- crates/node/src/tests/onboarding.rs | 6 +- crates/node/src/tests/resharing.rs | 18 +- pytest/tests/test_request_during_resharing.py | 2 + 30 files changed, 250 insertions(+), 260 deletions(-) diff --git a/crates/contract-interface/src/types/state.rs b/crates/contract-interface/src/types/state.rs index 70a4ba474..047bcdeab 100644 --- a/crates/contract-interface/src/types/state.rs +++ b/crates/contract-interface/src/types/state.rs @@ -156,8 +156,6 @@ pub enum SignatureScheme { Secp256k1, Ed25519, Bls12381, - /// Robust ECDSA variant. - V2Secp256k1, } /// The purpose that a domain serves. diff --git a/crates/contract/src/dto_mapping.rs b/crates/contract/src/dto_mapping.rs index 9377604a1..75946eac1 100644 --- a/crates/contract/src/dto_mapping.rs +++ b/crates/contract/src/dto_mapping.rs @@ -517,7 +517,6 @@ impl IntoInterfaceType for Curve { Curve::Secp256k1 => dtos::SignatureScheme::Secp256k1, Curve::Edwards25519 => dtos::SignatureScheme::Ed25519, Curve::Bls12381 => dtos::SignatureScheme::Bls12381, - Curve::V2Secp256k1 => dtos::SignatureScheme::V2Secp256k1, } } } @@ -526,7 +525,7 @@ impl IntoInterfaceType for &DomainConfig { fn into_dto_type(self) -> dtos::DomainConfig { dtos::DomainConfig { id: self.id.into_dto_type(), - scheme: self.curve.into_dto_type(), + scheme: self.key_config.curve.into_dto_type(), purpose: Some(self.purpose), } } diff --git a/crates/contract/src/lib.rs b/crates/contract/src/lib.rs index 6d92a84ab..dcaaa87ec 100644 --- a/crates/contract/src/lib.rs +++ b/crates/contract/src/lib.rs @@ -254,8 +254,8 @@ impl MpcContract { // ensure the signer sent a valid signature request // It's important we fail here because the MPC nodes will fail in an identical way. // This allows users to get the error message - match domain_config.curve { - Curve::Secp256k1 | Curve::V2Secp256k1 => { + match domain_config.key_config.curve { + Curve::Secp256k1 => { let hash = *request.payload.as_ecdsa().expect("Payload is not Ecdsa"); k256::Scalar::from_repr(hash.into()) .into_option() @@ -2064,7 +2064,7 @@ mod tests { NUM_CURVES, }; use crate::primitives::{ - domain::{infer_purpose_from_curve, Curve, DomainConfig, DomainId}, + domain::{infer_key_config_from_curve, infer_purpose_from_curve, Curve, DomainConfig, DomainId}, participants::Participants, signature::{Payload, Tweak}, test_utils::gen_participants, @@ -2170,7 +2170,7 @@ mod tests { rng: &mut impl CryptoRngCore, ) -> (dtos::PublicKey, SharedSecretKey) { match domain_curve { - Curve::Secp256k1 | Curve::V2Secp256k1 => { + Curve::Secp256k1 => { let (pk, sk) = new_secp256k1(rng); (pk.into(), SharedSecretKey::Secp256k1(sk)) } @@ -2207,7 +2207,7 @@ mod tests { let domain_id = DomainId::default(); let domains = vec![DomainConfig { id: domain_id, - curve, + key_config: infer_key_config_from_curve(curve), purpose, }]; let epoch_id = EpochId::new(0); @@ -4065,7 +4065,7 @@ mod tests { let domain_id = DomainId::default(); let domains = vec![DomainConfig { id: domain_id, - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }]; let (pk, _) = make_public_key_for_domain(Curve::Secp256k1, &mut OsRng); diff --git a/crates/contract/src/primitives/domain.rs b/crates/contract/src/primitives/domain.rs index d7fc47b18..224f56fbf 100644 --- a/crates/contract/src/primitives/domain.rs +++ b/crates/contract/src/primitives/domain.rs @@ -33,6 +33,22 @@ impl Display for DomainId { } } +/// Specifies what protocol the nodes need to run for a domain. +#[near(serializers=[borsh, json])] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Protocol { + OtBasedEcdsa, + Frost, + ConfidentialKeyDerivation, + DamgardEtAl, +} + +impl Default for Protocol { + fn default() -> Self { + Self::OtBasedEcdsa + } +} + /// Elliptic curve used by a domain. /// More curves may be added in the future. When adding new curves, both Borsh /// *and* JSON serialization must be kept compatible. @@ -43,7 +59,6 @@ pub enum Curve { #[serde(rename = "Ed25519")] Edwards25519, Bls12381, - V2Secp256k1, // Robust ECDSA } impl Default for Curve { @@ -52,6 +67,30 @@ impl Default for Curve { } } +/// Number of shares required to reconstruct the secret key for a domain. +/// For legacy domains this matches the global threshold; new domains can set it independently. +#[near(serializers=[borsh, json])] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct ReconstructionThreshold(pub u64); + +impl ReconstructionThreshold { + pub fn new(val: u64) -> Self { + Self(val) + } + pub fn value(&self) -> u64 { + self.0 + } +} + +/// Specifies the key configuration for a domain: protocol, curve, and reconstruction threshold. +#[near(serializers=[borsh, json])] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct KeyConfig { + pub protocol: Protocol, + pub curve: Curve, + pub reconstruction_threshold: ReconstructionThreshold, +} + /// Infer a default purpose from the curve. /// Used during migration from old state that lacks the `purpose` field. pub fn infer_purpose_from_curve(curve: Curve) -> DomainPurpose { @@ -61,48 +100,81 @@ pub fn infer_purpose_from_curve(curve: Curve) -> DomainPurpose { } } +/// Infer the protocol from a curve for **legacy domains only**. +/// +/// This mapping is only valid for domains that existed before the `Protocol` field +/// was introduced. It is NOT a general-purpose mapping: e.g. both `OtBasedEcdsa` +/// and `DamgardEtAl` use `Secp256k1`, but only `OtBasedEcdsa` was ever deployed. +pub fn infer_legacy_protocol_from_curve(curve: Curve) -> Protocol { + match curve { + Curve::Secp256k1 => Protocol::OtBasedEcdsa, + Curve::Edwards25519 => Protocol::Frost, + Curve::Bls12381 => Protocol::ConfidentialKeyDerivation, + } +} + +/// Build a `KeyConfig` from just a curve, inferring the protocol from legacy rules. +/// `reconstruction_threshold` defaults to 0 (must be set by the caller/migration). +pub fn infer_key_config_from_curve(curve: Curve) -> KeyConfig { + KeyConfig { + protocol: infer_legacy_protocol_from_curve(curve), + curve, + reconstruction_threshold: ReconstructionThreshold::default(), + } +} + /// Returns whether the given curve is valid for the given purpose. pub fn is_valid_curve_for_purpose(purpose: DomainPurpose, curve: Curve) -> bool { matches!( (purpose, curve), (DomainPurpose::Sign, Curve::Secp256k1) - | (DomainPurpose::Sign, Curve::V2Secp256k1) | (DomainPurpose::Sign, Curve::Edwards25519) | (DomainPurpose::ForeignTx, Curve::Secp256k1) | (DomainPurpose::CKD, Curve::Bls12381) ) } -/// Describes the configuration of a domain: the domain ID and the curve it uses. +/// Describes the configuration of a domain: the domain ID, key configuration, and purpose. #[near(serializers=[borsh, json])] #[serde(from = "DomainConfigCompat")] #[derive(Debug, Clone, PartialEq, Eq)] pub struct DomainConfig { pub id: DomainId, - #[serde(rename = "scheme")] - pub curve: Curve, + pub key_config: KeyConfig, pub purpose: DomainPurpose, } /// JSON-only compatibility helper: -/// old 3.4.x state omitted `purpose`, so we infer it from `curve` when absent. +/// - Old 3.4.x format: `{ id, scheme: "Secp256k1" }` (no `purpose`, no `key_config`) +/// - Previous format: `{ id, scheme: "Secp256k1", purpose: "Sign" }` (no `key_config`) +/// - New format: `{ id, key_config: { protocol, curve, reconstruction_threshold }, purpose }` #[derive(serde::Deserialize)] struct DomainConfigCompat { id: DomainId, + /// New format: full key configuration. + key_config: Option, + /// Old format fallback: bare curve (field was called "scheme" in older JSON). #[serde(alias = "scheme")] - curve: Curve, + curve: Option, #[serde(default)] purpose: Option, } impl From for DomainConfig { fn from(value: DomainConfigCompat) -> Self { + let key_config = value.key_config.unwrap_or_else(|| { + let curve = value + .curve + .expect("DomainConfig JSON must contain either `key_config` or `curve`/`scheme`"); + infer_key_config_from_curve(curve) + }); + let purpose = value + .purpose + .unwrap_or_else(|| infer_purpose_from_curve(key_config.curve)); Self { id: value.id, - curve: value.curve, - purpose: value - .purpose - .unwrap_or_else(|| infer_purpose_from_curve(value.curve)), + key_config, + purpose, } } } @@ -125,16 +197,23 @@ impl DomainRegistry { /// Migration from legacy: creates a DomainRegistry with a single ecdsa key. pub fn new_single_ecdsa_key_from_legacy() -> Self { let mut registry = Self::default(); - registry.add_domain(Curve::Secp256k1, DomainPurpose::Sign); + registry.add_domain( + KeyConfig { + protocol: Protocol::OtBasedEcdsa, + curve: Curve::Secp256k1, + reconstruction_threshold: ReconstructionThreshold::default(), + }, + DomainPurpose::Sign, + ); registry } - /// Add a single domain with the given protocol and purpose, returning the DomainId of the - /// added domain. - fn add_domain(&mut self, curve: Curve, purpose: DomainPurpose) -> DomainId { + /// Add a single domain with the given key configuration and purpose, returning the DomainId + /// of the added domain. + fn add_domain(&mut self, key_config: KeyConfig, purpose: DomainPurpose) -> DomainId { let domain = DomainConfig { id: DomainId(self.next_domain_id), - curve, + key_config, purpose, }; self.next_domain_id += 1; @@ -148,7 +227,7 @@ impl DomainRegistry { pub fn add_domains(&self, domains: Vec) -> Result { let mut new_registry = self.clone(); for domain in domains { - let new_domain_id = new_registry.add_domain(domain.curve, domain.purpose); + let new_domain_id = new_registry.add_domain(domain.key_config, domain.purpose); if new_domain_id != domain.id { return Err(DomainError::NewDomainIdsNotContiguous { expected_id: new_domain_id, @@ -181,7 +260,7 @@ impl DomainRegistry { self.domains .iter() .rev() - .find(|domain| domain.curve == curve) + .find(|domain| domain.key_config.curve == curve) .map(|domain| domain.id) } @@ -267,8 +346,9 @@ impl AddDomainsVotes { #[cfg(test)] pub mod tests { use super::{ - infer_purpose_from_curve, is_valid_curve_for_purpose, AddDomainsVotes, Curve, DomainConfig, - DomainId, DomainPurpose, DomainRegistry, Participants, + infer_key_config_from_curve, infer_purpose_from_curve, is_valid_curve_for_purpose, + AddDomainsVotes, Curve, DomainConfig, DomainId, DomainPurpose, DomainRegistry, + KeyConfig, Participants, Protocol, ReconstructionThreshold, }; use crate::primitives::key_state::AuthenticatedParticipantId; use crate::primitives::test_utils::{gen_participant, gen_participants}; @@ -276,33 +356,33 @@ pub mod tests { use near_sdk::testing_env; use rstest::rstest; + fn make_domain(id: u64, curve: Curve, purpose: DomainPurpose) -> DomainConfig { + DomainConfig { + id: DomainId(id), + key_config: infer_key_config_from_curve(curve), + purpose, + } + } + #[test] fn test_add_domains() { let registry = DomainRegistry::default(); let domains1 = vec![ - DomainConfig { - id: DomainId(0), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }, - DomainConfig { - id: DomainId(1), - curve: Curve::Edwards25519, - purpose: DomainPurpose::Sign, - }, + make_domain(0, Curve::Secp256k1, DomainPurpose::Sign), + make_domain(1, Curve::Edwards25519, DomainPurpose::Sign), ]; let new_registry = registry.add_domains(domains1.clone()).unwrap(); assert_eq!(new_registry.domains, domains1); let domains2 = vec![ - DomainConfig { - id: DomainId(2), - curve: Curve::Bls12381, - purpose: DomainPurpose::CKD, - }, + make_domain(2, Curve::Bls12381, DomainPurpose::CKD), DomainConfig { id: DomainId(3), - curve: Curve::V2Secp256k1, + key_config: KeyConfig { + protocol: Protocol::DamgardEtAl, + curve: Curve::Secp256k1, + reconstruction_threshold: ReconstructionThreshold::default(), + }, purpose: DomainPurpose::Sign, }, ]; @@ -311,25 +391,13 @@ pub mod tests { assert_eq!(&new_registry.domains[2..4], &domains2); // This fails because the domain ID does not start from next_domain_id. - let domains3 = vec![DomainConfig { - id: DomainId(5), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }]; + let domains3 = vec![make_domain(5, Curve::Secp256k1, DomainPurpose::Sign)]; let _ = new_registry.add_domains(domains3).unwrap_err(); // This fails because the domain IDs are not sorted. let domains4 = vec![ - DomainConfig { - id: DomainId(5), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }, - DomainConfig { - id: DomainId(4), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }, + make_domain(5, Curve::Secp256k1, DomainPurpose::Sign), + make_domain(4, Curve::Secp256k1, DomainPurpose::Sign), ]; let _ = new_registry.add_domains(domains4).unwrap_err(); } @@ -337,24 +405,16 @@ pub mod tests { #[test] fn test_retain_domains() { let expected = vec![ - DomainConfig { - id: DomainId(0), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }, - DomainConfig { - id: DomainId(2), - curve: Curve::Edwards25519, - purpose: DomainPurpose::Sign, - }, - DomainConfig { - id: DomainId(3), - curve: Curve::Bls12381, - purpose: DomainPurpose::CKD, - }, + make_domain(0, Curve::Secp256k1, DomainPurpose::Sign), + make_domain(2, Curve::Edwards25519, DomainPurpose::Sign), + make_domain(3, Curve::Bls12381, DomainPurpose::CKD), DomainConfig { id: DomainId(4), - curve: Curve::V2Secp256k1, + key_config: KeyConfig { + protocol: Protocol::DamgardEtAl, + curve: Curve::Secp256k1, + reconstruction_threshold: ReconstructionThreshold::default(), + }, purpose: DomainPurpose::Sign, }, ]; @@ -376,21 +436,9 @@ pub mod tests { fn test_most_recent_domain_for_curve() { let registry = DomainRegistry::from_raw_validated( vec![ - DomainConfig { - id: DomainId(0), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }, - DomainConfig { - id: DomainId(2), - curve: Curve::Edwards25519, - purpose: DomainPurpose::Sign, - }, - DomainConfig { - id: DomainId(3), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }, + make_domain(0, Curve::Secp256k1, DomainPurpose::Sign), + make_domain(2, Curve::Edwards25519, DomainPurpose::Sign), + make_domain(3, Curve::Secp256k1, DomainPurpose::Sign), ], 6, ) @@ -407,17 +455,15 @@ pub mod tests { #[test] fn test_serialization_format() { - let domain_config = DomainConfig { - id: DomainId(3), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }; + let domain_config = make_domain(3, Curve::Secp256k1, DomainPurpose::Sign); let json = serde_json::to_string(&domain_config).unwrap(); - assert_eq!(json, r#"{"id":3,"scheme":"Secp256k1","purpose":"Sign"}"#); + let expected = r#"{"id":3,"key_config":{"protocol":"OtBasedEcdsa","curve":"Secp256k1","reconstruction_threshold":0},"purpose":"Sign"}"#; + assert_eq!(json, expected); let domain_config: DomainConfig = serde_json::from_str(&json).unwrap(); assert_eq!(domain_config.id, DomainId(3)); - assert_eq!(domain_config.curve, Curve::Secp256k1); + assert_eq!(domain_config.key_config.curve, Curve::Secp256k1); + assert_eq!(domain_config.key_config.protocol, Protocol::OtBasedEcdsa); assert_eq!(domain_config.purpose, DomainPurpose::Sign); } @@ -434,21 +480,20 @@ pub mod tests { Curve::Secp256k1, DomainPurpose::Sign )] - fn test_deserialization_without_purpose( + fn test_deserialization_without_key_config( #[case] json: &str, #[case] expected_curve: Curve, #[case] expected_purpose: DomainPurpose, ) { - // Simulates JSON from a 3.4.1 contract that lacks the `purpose` field. + // Simulates JSON from older contracts that lack the `key_config` field. let config: DomainConfig = serde_json::from_str(json).unwrap(); - assert_eq!(config.curve, expected_curve); + assert_eq!(config.key_config.curve, expected_curve); assert_eq!(config.purpose, expected_purpose); } #[rstest] #[case(Curve::Secp256k1, DomainPurpose::Sign)] #[case(Curve::Edwards25519, DomainPurpose::Sign)] - #[case(Curve::V2Secp256k1, DomainPurpose::Sign)] #[case(Curve::Bls12381, DomainPurpose::CKD)] fn test_infer_purpose_from_curve(#[case] curve: Curve, #[case] expected: DomainPurpose) { assert_eq!(infer_purpose_from_curve(curve), expected); @@ -457,7 +502,6 @@ pub mod tests { #[rstest] // Valid combinations #[case(DomainPurpose::Sign, Curve::Secp256k1, true)] - #[case(DomainPurpose::Sign, Curve::V2Secp256k1, true)] #[case(DomainPurpose::Sign, Curve::Edwards25519, true)] #[case(DomainPurpose::ForeignTx, Curve::Secp256k1, true)] #[case(DomainPurpose::CKD, Curve::Bls12381, true)] @@ -465,7 +509,6 @@ pub mod tests { #[case(DomainPurpose::Sign, Curve::Bls12381, false)] #[case(DomainPurpose::ForeignTx, Curve::Edwards25519, false)] #[case(DomainPurpose::ForeignTx, Curve::Bls12381, false)] - #[case(DomainPurpose::ForeignTx, Curve::V2Secp256k1, false)] #[case(DomainPurpose::CKD, Curve::Secp256k1, false)] fn test_valid_curve_purpose_combinations( #[case] purpose: DomainPurpose, @@ -494,11 +537,7 @@ pub mod tests { } fn sample_proposal() -> Vec { - vec![DomainConfig { - id: DomainId(0), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }] + vec![make_domain(0, Curve::Secp256k1, DomainPurpose::Sign)] } #[test] @@ -572,16 +611,8 @@ pub mod tests { fn test_get_remaining_votes_preserves_different_proposals() { // Given let (participants, auth_ids) = setup_participants(3); - let proposal_a = vec![DomainConfig { - id: DomainId(0), - curve: Curve::Secp256k1, - purpose: DomainPurpose::Sign, - }]; - let proposal_b = vec![DomainConfig { - id: DomainId(0), - curve: Curve::Edwards25519, - purpose: DomainPurpose::Sign, - }]; + let proposal_a = vec![make_domain(0, Curve::Secp256k1, DomainPurpose::Sign)]; + let proposal_b = vec![make_domain(0, Curve::Edwards25519, DomainPurpose::Sign)]; let mut votes = AddDomainsVotes::default(); votes.vote(proposal_a.clone(), &auth_ids[0]); votes.vote(proposal_b.clone(), &auth_ids[1]); diff --git a/crates/contract/src/primitives/test_utils.rs b/crates/contract/src/primitives/test_utils.rs index 8a5615755..1a8f1f28a 100644 --- a/crates/contract/src/primitives/test_utils.rs +++ b/crates/contract/src/primitives/test_utils.rs @@ -1,4 +1,7 @@ -use super::domain::{infer_purpose_from_curve, Curve, DomainConfig, DomainId, DomainRegistry}; +use super::domain::{ + infer_key_config_from_curve, infer_purpose_from_curve, Curve, DomainConfig, DomainId, + DomainRegistry, +}; use crate::{ crypto_shared::types::{serializable::SerializableEdwardsPoint, PublicKeyExtended}, primitives::{ @@ -11,11 +14,10 @@ use near_account_id::AccountId; use rand::{distributions::Uniform, Rng}; use std::collections::BTreeMap; -const ALL_CURVES: [Curve; 4] = [ +const ALL_CURVES: [Curve; 3] = [ Curve::Secp256k1, Curve::Edwards25519, Curve::Bls12381, - Curve::V2Secp256k1, ]; pub const NUM_CURVES: usize = ALL_CURVES.len(); @@ -26,7 +28,7 @@ pub fn gen_domain_registry(num_domains: usize) -> DomainRegistry { let curve = ALL_CURVES[i % ALL_CURVES.len()]; domains.push(DomainConfig { id: DomainId(i as u64 * 2), - curve, + key_config: infer_key_config_from_curve(curve), purpose: infer_purpose_from_curve(curve), }); } @@ -40,7 +42,7 @@ pub fn gen_domains_to_add(registry: &DomainRegistry, num_domains: usize) -> Vec< let curve = ALL_CURVES[i % ALL_CURVES.len()]; new_domains.push(DomainConfig { id: DomainId(registry.next_domain_id() + i as u64), - curve, + key_config: infer_key_config_from_curve(curve), purpose: infer_purpose_from_curve(curve), }); } diff --git a/crates/contract/src/state/running.rs b/crates/contract/src/state/running.rs index b0d3c8c08..789260273 100644 --- a/crates/contract/src/state/running.rs +++ b/crates/contract/src/state/running.rs @@ -167,10 +167,12 @@ impl RunningContractState { return Err(DomainError::AddDomainsMustAddAtLeastOneDomain.into()); } for domain in &domains { - if !crate::primitives::domain::is_valid_curve_for_purpose(domain.purpose, domain.curve) - { + if !crate::primitives::domain::is_valid_curve_for_purpose( + domain.purpose, + domain.key_config.curve, + ) { return Err(DomainError::InvalidCurvePurposeCombination { - curve: domain.curve, + curve: domain.key_config.curve, purpose: domain.purpose, } .into()); @@ -209,7 +211,7 @@ pub mod running_tests { use rstest::rstest; use crate::primitives::domain::{ - AddDomainsVotes, Curve, DomainConfig, DomainId, DomainPurpose, + infer_key_config_from_curve, AddDomainsVotes, Curve, DomainConfig, DomainId, DomainPurpose, }; use crate::primitives::test_utils::{gen_threshold_params, NUM_CURVES}; use crate::state::key_event::tests::Environment; @@ -362,7 +364,7 @@ pub mod running_tests { let invalid_domain = vec![DomainConfig { id: DomainId(next_id), - curve, + key_config: infer_key_config_from_curve(curve), purpose, }]; diff --git a/crates/contract/src/utils.rs b/crates/contract/src/utils.rs index b51d19630..30a8a58a9 100644 --- a/crates/contract/src/utils.rs +++ b/crates/contract/src/utils.rs @@ -29,7 +29,7 @@ pub fn protocol_state_to_string(contract_state: &ProtocolContractState) -> Strin output.push_str(&format!(" Epoch: {}\n", state.generating_key.epoch_id())); output.push_str(" Domains:\n"); for (i, domain) in state.domains.domains().iter().enumerate() { - output.push_str(&format!(" Domain {}: {:?}, ", domain.id, domain.curve)); + output.push_str(&format!(" Domain {}: {:?}, ", domain.id, domain.key_config.curve)); #[allow(clippy::comparison_chain)] if i < state.generated_keys.len() { output.push_str(&format!( @@ -73,7 +73,7 @@ pub fn protocol_state_to_string(contract_state: &ProtocolContractState) -> Strin { output.push_str(&format!( " Domain {}: {:?}, key from attempt {}\n", - domain.id, domain.curve, key.attempt + domain.id, domain.key_config.curve, key.attempt )); } output.push_str(" Parameters:\n"); @@ -96,7 +96,7 @@ pub fn protocol_state_to_string(contract_state: &ProtocolContractState) -> Strin { output.push_str(&format!( " Domain {}: {:?}, original key from attempt {}, ", - domain.id, domain.curve, state.previous_running_state.keyset.domains[i].attempt + domain.id, domain.key_config.curve, state.previous_running_state.keyset.domains[i].attempt )); #[allow(clippy::comparison_chain)] diff --git a/crates/contract/tests/inprocess/attestation_submission.rs b/crates/contract/tests/inprocess/attestation_submission.rs index 9a5365a1f..3f5db85cc 100644 --- a/crates/contract/tests/inprocess/attestation_submission.rs +++ b/crates/contract/tests/inprocess/attestation_submission.rs @@ -2,7 +2,7 @@ use contract_interface::types::{Attestation, InitConfig, MockAttestation, Protoc use mpc_contract::{ crypto_shared::types::PublicKeyExtended, primitives::{ - domain::{Curve, DomainConfig, DomainId, DomainPurpose}, + domain::{infer_key_config_from_curve, Curve, DomainConfig, DomainId, DomainPurpose}, key_state::{AttemptId, EpochId, KeyForDomain, Keyset}, participants::{ParticipantId, ParticipantInfo}, test_utils::{bogus_ed25519_near_public_key, gen_participants}, @@ -101,7 +101,7 @@ impl TestSetupBuilder { let domains = vec![DomainConfig { id: DomainId::default(), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }]; @@ -148,7 +148,7 @@ impl TestSetupBuilder { .contract .vote_add_domains(vec![DomainConfig { id: DomainId(1), - curve: Curve::Edwards25519, + key_config: infer_key_config_from_curve(Curve::Edwards25519), purpose: DomainPurpose::Sign, }]) .unwrap(); diff --git a/crates/contract/tests/sandbox/common.rs b/crates/contract/tests/sandbox/common.rs index 60120e286..d814d3940 100644 --- a/crates/contract/tests/sandbox/common.rs +++ b/crates/contract/tests/sandbox/common.rs @@ -13,7 +13,7 @@ use dtos::ProtocolContractState; use mpc_contract::{ crypto_shared::types::PublicKeyExtended, primitives::{ - domain::{infer_purpose_from_curve, Curve, DomainConfig, DomainId, DomainPurpose}, + domain::{infer_key_config_from_curve, infer_purpose_from_curve, Curve, DomainConfig, DomainId, DomainPurpose}, key_state::{AttemptId, EpochId, KeyForDomain, Keyset}, participants::{ParticipantInfo, Participants}, test_utils::bogus_ed25519_near_public_key, @@ -213,14 +213,14 @@ pub async fn init_with_candidates( public_key: key.clone(), config: DomainConfig { id: domain_id, - curve, + key_config: infer_key_config_from_curve(curve), purpose, }, }); ( DomainConfig { id: domain_id, - curve, + key_config: infer_key_config_from_curve(curve), purpose, }, KeyForDomain { @@ -462,7 +462,7 @@ pub async fn call_contract_key_generation( start_keygen_instance(contract, accounts, key_event_id) .await .unwrap(); - let (public_key, shared_secret_key) = make_key_for_domain(domain.curve); + let (public_key, shared_secret_key) = make_key_for_domain(domain.key_config.curve); domain_keys.push(DomainKey { domain_config: domain.clone(), @@ -529,17 +529,17 @@ pub async fn execute_key_generation_and_add_random_state( let domains_to_add = [ DomainConfig { id: 0.into(), - curve: Curve::Edwards25519, + key_config: infer_key_config_from_curve(Curve::Edwards25519), purpose: DomainPurpose::Sign, }, DomainConfig { id: 1.into(), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }, DomainConfig { id: 2.into(), - curve: Curve::Edwards25519, + key_config: infer_key_config_from_curve(Curve::Edwards25519), purpose: DomainPurpose::Sign, }, ]; diff --git a/crates/contract/tests/sandbox/participants_gas.rs b/crates/contract/tests/sandbox/participants_gas.rs index f7e02280f..3c303bf53 100644 --- a/crates/contract/tests/sandbox/participants_gas.rs +++ b/crates/contract/tests/sandbox/participants_gas.rs @@ -17,7 +17,7 @@ use crate::sandbox::{ use mpc_contract::{ crypto_shared::types::PublicKeyExtended, primitives::{ - domain::{Curve, DomainConfig, DomainId, DomainPurpose}, + domain::{infer_key_config_from_curve, Curve, DomainConfig, DomainId, DomainPurpose}, key_state::{AttemptId, EpochId, KeyForDomain, Keyset}, }, }; @@ -273,7 +273,7 @@ async fn setup_test_env_with_state(n_participants: usize, running_state: bool) - let domain_id = DomainId(0); let domain = DomainConfig { id: domain_id, - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }; let (dto_pk, _) = new_secp256k1(); diff --git a/crates/contract/tests/sandbox/upgrade_to_current_contract.rs b/crates/contract/tests/sandbox/upgrade_to_current_contract.rs index ab747d26f..ac801a0a0 100644 --- a/crates/contract/tests/sandbox/upgrade_to_current_contract.rs +++ b/crates/contract/tests/sandbox/upgrade_to_current_contract.rs @@ -16,7 +16,7 @@ use contract_interface::types::ProtocolContractState; use mpc_contract::{ crypto_shared::CKDResponse, primitives::{ - domain::{Curve, DomainConfig, DomainPurpose}, + domain::{infer_key_config_from_curve, Curve, DomainConfig, DomainPurpose}, key_state::{EpochId, Keyset}, participants::Participants, thresholds::{Threshold, ThresholdParameters}, @@ -350,12 +350,12 @@ async fn upgrade_allows_new_request_types( let domains_to_add = [ DomainConfig { id: first_available_domain_id.into(), - curve: Curve::Bls12381, + key_config: infer_key_config_from_curve(Curve::Bls12381), purpose: DomainPurpose::CKD, }, DomainConfig { id: (first_available_domain_id + 1).into(), - curve: Curve::Edwards25519, + key_config: infer_key_config_from_curve(Curve::Edwards25519), purpose: DomainPurpose::Sign, }, ]; diff --git a/crates/contract/tests/sandbox/utils/consts.rs b/crates/contract/tests/sandbox/utils/consts.rs index f209ec106..972b5f468 100644 --- a/crates/contract/tests/sandbox/utils/consts.rs +++ b/crates/contract/tests/sandbox/utils/consts.rs @@ -5,11 +5,10 @@ use near_sdk::{Gas, NearToken}; /* --- Protocol defaults --- */ pub const PARTICIPANT_LEN: usize = 10; -pub const ALL_CURVES: &[Curve; 4] = &[ +pub const ALL_CURVES: &[Curve; 3] = &[ Curve::Secp256k1, Curve::Edwards25519, Curve::Bls12381, - Curve::V2Secp256k1, ]; /* --- Gas constants --- */ diff --git a/crates/contract/tests/sandbox/utils/interface.rs b/crates/contract/tests/sandbox/utils/interface.rs index c17a224e1..5c62bd4a4 100644 --- a/crates/contract/tests/sandbox/utils/interface.rs +++ b/crates/contract/tests/sandbox/utils/interface.rs @@ -21,7 +21,6 @@ impl IntoInterfaceType for Curve { Curve::Secp256k1 => dtos::SignatureScheme::Secp256k1, Curve::Edwards25519 => dtos::SignatureScheme::Ed25519, Curve::Bls12381 => dtos::SignatureScheme::Bls12381, - Curve::V2Secp256k1 => dtos::SignatureScheme::V2Secp256k1, } } } diff --git a/crates/contract/tests/sandbox/utils/shared_key_utils.rs b/crates/contract/tests/sandbox/utils/shared_key_utils.rs index 194819760..d2eef3193 100644 --- a/crates/contract/tests/sandbox/utils/shared_key_utils.rs +++ b/crates/contract/tests/sandbox/utils/shared_key_utils.rs @@ -59,7 +59,7 @@ pub fn new_secp256k1() -> (dtos::PublicKey, ts_ecdsa::KeygenOutput) { pub fn make_key_for_domain(domain_curve: Curve) -> (dtos::PublicKey, SharedSecretKey) { match domain_curve { - Curve::Secp256k1 | Curve::V2Secp256k1 => { + Curve::Secp256k1 => { let (pk, sk) = new_secp256k1(); (pk, SharedSecretKey::Secp256k1(sk)) } diff --git a/crates/contract/tests/sandbox/vote.rs b/crates/contract/tests/sandbox/vote.rs index fd2109d15..167398ea4 100644 --- a/crates/contract/tests/sandbox/vote.rs +++ b/crates/contract/tests/sandbox/vote.rs @@ -19,7 +19,7 @@ use dtos::{AttemptId, KeyEventId, ProtocolContractState, RunningContractState}; use mpc_contract::{ errors::InvalidParameters, primitives::{ - domain::{infer_purpose_from_curve, Curve, DomainConfig, DomainPurpose}, + domain::{infer_key_config_from_curve, infer_purpose_from_curve, Curve, DomainConfig, DomainPurpose}, thresholds::{Threshold, ThresholdParameters}, }, }; @@ -48,7 +48,7 @@ async fn test_keygen() -> anyhow::Result<()> { &mpc_signer_accounts, &[DomainConfig { id: domain_id.into(), - curve, + key_config: infer_key_config_from_curve(curve), purpose: DomainPurpose::Sign, }], ) @@ -150,7 +150,7 @@ async fn test_cancel_keygen() -> anyhow::Result<()> { &mpc_signer_accounts, &[DomainConfig { id: next_domain_id.into(), - curve: *curve, + key_config: infer_key_config_from_curve(*curve), purpose: infer_purpose_from_curve(*curve), }], ) diff --git a/crates/devnet/src/contracts.rs b/crates/devnet/src/contracts.rs index 0dedea5ae..35a055f93 100644 --- a/crates/devnet/src/contracts.rs +++ b/crates/devnet/src/contracts.rs @@ -53,8 +53,8 @@ pub fn make_actions(call: ContractActionCall) -> ActionCall { let mut eddsa_calls_by_domain = BTreeMap::new(); let mut ckd_calls_by_domain = BTreeMap::new(); for (domain, prot_calls) in args.calls_by_domain { - match domain.curve { - Curve::Secp256k1 | Curve::V2Secp256k1 => { + match domain.key_config.curve { + Curve::Secp256k1 => { ecdsa_calls_by_domain.insert(domain.id.0, prot_calls); } Curve::Edwards25519 => { @@ -90,7 +90,7 @@ pub fn make_actions(call: ContractActionCall) -> ActionCall { request: SignRequestArgs { domain_id: Some(args.domain_config.id), path: "".to_string(), - payload_v2: Some(make_payload(args.domain_config.curve)), + payload_v2: Some(make_payload(args.domain_config.key_config.curve)), ..Default::default() }, }) @@ -167,7 +167,7 @@ struct ParallelSignArgsV2 { fn make_payload(curve: Curve) -> Payload { match curve { - Curve::Secp256k1 | Curve::V2Secp256k1 => { + Curve::Secp256k1 => { Payload::Ecdsa(Bytes::new(rand::random::<[u8; 32]>().to_vec()).unwrap()) } Curve::Edwards25519 => { diff --git a/crates/devnet/src/loadtest.rs b/crates/devnet/src/loadtest.rs index 08c3233f6..35072ff4b 100644 --- a/crates/devnet/src/loadtest.rs +++ b/crates/devnet/src/loadtest.rs @@ -281,14 +281,14 @@ impl RunLoadtestCmd { let domain_config = contract_state .get_domain_config(DomainId(domain_id)) .expect("require valid domain id"); - match domain_config.curve { + match domain_config.key_config.curve { Curve::Bls12381 => { ContractActionCall::Ckd(crate::contracts::RequestActionCallArgs { mpc_contract: mpc_account, domain_config, }) } - Curve::Edwards25519 | Curve::Secp256k1 | Curve::V2Secp256k1 => { + Curve::Edwards25519 | Curve::Secp256k1 => { ContractActionCall::Sign(crate::contracts::RequestActionCallArgs { mpc_contract: mpc_account, domain_config, diff --git a/crates/devnet/src/mpc.rs b/crates/devnet/src/mpc.rs index c3ccd6c71..5f2cbb8b1 100644 --- a/crates/devnet/src/mpc.rs +++ b/crates/devnet/src/mpc.rs @@ -21,7 +21,7 @@ use ed25519_dalek::{SigningKey, VerifyingKey}; use mpc_contract::tee::proposal::MpcDockerImageHash; use mpc_contract::{ primitives::{ - domain::{infer_purpose_from_curve, Curve, DomainConfig, DomainId}, + domain::{infer_key_config_from_curve, infer_purpose_from_curve, Curve, DomainConfig, DomainId}, key_state::EpochId, participants::{ParticipantInfo, Participants}, thresholds::{Threshold, ThresholdParameters}, @@ -615,7 +615,7 @@ impl MpcVoteAddDomainsCmd { for scheme in &schemes { proposal.push(DomainConfig { id: DomainId(next_domain), - curve: *scheme, + key_config: infer_key_config_from_curve(*scheme), purpose: infer_purpose_from_curve(*scheme), }); next_domain += 1; diff --git a/crates/node/src/coordinator.rs b/crates/node/src/coordinator.rs index b34522be9..0727289c2 100644 --- a/crates/node/src/coordinator.rs +++ b/crates/node/src/coordinator.rs @@ -530,7 +530,7 @@ where ); let mut ecdsa_keyshares: HashMap = HashMap::new(); - let mut robust_ecdsa_keyshares: HashMap = + let robust_ecdsa_keyshares: HashMap = HashMap::new(); let mut eddsa_keyshares: HashMap = HashMap::new(); let mut ckd_keyshares: HashMap< @@ -554,10 +554,6 @@ where ckd_keyshares.insert(keyshare.key_id.domain_id, data); domain_to_curve.insert(domain_id, Curve::Bls12381); } - KeyshareData::V2Secp256k1(data) => { - robust_ecdsa_keyshares.insert(keyshare.key_id.domain_id, data); - domain_to_curve.insert(domain_id, Curve::V2Secp256k1); - } } } diff --git a/crates/node/src/key_events.rs b/crates/node/src/key_events.rs index fa4f0bd10..22becd9ca 100644 --- a/crates/node/src/key_events.rs +++ b/crates/node/src/key_events.rs @@ -17,7 +17,7 @@ use crate::{ keyshare::{Keyshare, KeyshareData, KeyshareStorage}, network::NetworkTaskChannel, providers::{ - CKDProvider, EcdsaSignatureProvider, RobustEcdsaSignatureProvider, SignatureProvider, + CKDProvider, EcdsaSignatureProvider, SignatureProvider, }, }; use contract_interface::types as dtos; @@ -58,7 +58,7 @@ pub async fn keygen_computation_inner( key_id ); - let (keyshare, public_key) = match domain.curve { + let (keyshare, public_key) = match domain.key_config.curve { Curve::Secp256k1 => { let keyshare = EcdsaSignatureProvider::run_key_generation_client(threshold, channel).await?; @@ -67,14 +67,6 @@ pub async fn keygen_computation_inner( )?); (KeyshareData::Secp256k1(keyshare), public_key) } - Curve::V2Secp256k1 => { - let keyshare = - RobustEcdsaSignatureProvider::run_key_generation_client(threshold, channel).await?; - let public_key = dtos::PublicKey::Secp256k1(dtos::Secp256k1PublicKey::try_from( - keyshare.public_key.to_element().to_affine(), - )?); - (KeyshareData::V2Secp256k1(keyshare), public_key) - } Curve::Edwards25519 => { let keyshare = EddsaSignatureProvider::run_key_generation_client(threshold, channel).await?; @@ -210,7 +202,7 @@ async fn resharing_computation_inner( let public_key = dtos::PublicKey::from(previous_public_key.clone()); - let keyshare_data = match (public_key, domain.curve) { + let keyshare_data = match (public_key, domain.key_config.curve) { (contract_interface::types::PublicKey::Secp256k1(inner_public_key), Curve::Secp256k1) => { let pk = k256::PublicKey::try_from(&inner_public_key)?; let public_key = frost_secp256k1::VerifyingKey::new(pk.to_projective()); @@ -230,25 +222,6 @@ async fn resharing_computation_inner( .await?; KeyshareData::Secp256k1(res) } - (contract_interface::types::PublicKey::Secp256k1(inner_public_key), Curve::V2Secp256k1) => { - let pk = k256::PublicKey::try_from(&inner_public_key)?; - let public_key = frost_secp256k1::VerifyingKey::new(pk.to_projective()); - let my_share = existing_keyshare - .map(|keyshare| match keyshare.data { - KeyshareData::V2Secp256k1(data) => Ok(data.private_share), - _ => Err(anyhow::anyhow!("Expected ecdsa keyshare!")), - }) - .transpose()?; - let res = RobustEcdsaSignatureProvider::run_key_resharing_client( - args.new_threshold, - my_share, - public_key, - &args.old_participants, - channel, - ) - .await?; - KeyshareData::V2Secp256k1(res) - } (contract_interface::types::PublicKey::Ed25519(inner_public_key), Curve::Edwards25519) => { let public_key = frost_ed25519::VerifyingKey::deserialize(inner_public_key.as_ref())?; let my_share = existing_keyshare @@ -712,7 +685,9 @@ mod tests { use crate::indexer::participants::{ContractKeyEventInstance, KeyEventIdComparisonResult}; use crate::indexer::tx_sender::{TransactionProcessorError, TransactionStatus}; use crate::keyshare::KeyStorageConfig; - use mpc_contract::primitives::domain::{Curve, DomainConfig, DomainId, DomainPurpose}; + use mpc_contract::primitives::domain::{ + infer_key_config_from_curve, Curve, DomainConfig, DomainId, DomainPurpose, + }; use mpc_contract::primitives::key_state::{AttemptId, EpochId, KeyEventId}; use std::collections::BTreeSet; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -871,7 +846,7 @@ mod tests { id: key_event_id, domain: DomainConfig { id: key_event_id.domain_id, - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }, started, diff --git a/crates/node/src/keyshare.rs b/crates/node/src/keyshare.rs index bdeb42965..a1deb4521 100644 --- a/crates/node/src/keyshare.rs +++ b/crates/node/src/keyshare.rs @@ -23,7 +23,6 @@ pub enum KeyshareData { Secp256k1(threshold_signatures::ecdsa::KeygenOutput), Ed25519(threshold_signatures::frost::eddsa::KeygenOutput), Bls12381(threshold_signatures::confidential_key_derivation::KeygenOutput), - V2Secp256k1(threshold_signatures::ecdsa::KeygenOutput), } /// A single keyshare, corresponding to one epoch, one domain, one attempt. @@ -45,9 +44,6 @@ impl Keyshare { KeyshareData::Bls12381(data) => Ok(PublicKey::Bls12381(Bls12381G2PublicKey::from( &data.public_key.to_element(), ))), - KeyshareData::V2Secp256k1(data) => Ok(PublicKey::Secp256k1( - Secp256k1PublicKey::try_from(data.public_key.to_element().to_affine())?, - )), } } diff --git a/crates/node/src/mpc_client.rs b/crates/node/src/mpc_client.rs index a38f7f919..c63448459 100644 --- a/crates/node/src/mpc_client.rs +++ b/crates/node/src/mpc_client.rs @@ -425,23 +425,6 @@ where "Incorrect protocol for domain: {:?}", signature_attempt.request.domain.clone() )), - Some(Curve::V2Secp256k1) => { - let (signature, public_key) = timeout( - Duration::from_secs(this.config.signature.timeout_sec), - this.robust_ecdsa_signature_provider - .clone() - .make_signature(signature_attempt.request.id), - ) - .await??; - - let response = ChainSignatureRespondArgs::new_ecdsa( - &signature_attempt.request, - &signature, - &public_key, - )?; - - Ok(response) - } None => Err(anyhow::anyhow!( "Signature scheme is not found for domain: {:?}", signature_attempt.request.domain.clone() @@ -524,7 +507,6 @@ where Ok(response) } Some(Curve::Secp256k1) - | Some(Curve::V2Secp256k1) | Some(Curve::Edwards25519) => Err(anyhow::anyhow!( "Signature scheme is not allowed for domain: {:?}", ckd_attempt.request.domain_id.clone() @@ -616,7 +598,6 @@ where Ok(response) } Some(Curve::Bls12381) - | Some(Curve::V2Secp256k1) | Some(Curve::Edwards25519) => Err(anyhow::anyhow!( "Signature scheme is not allowed for domain: {:?}", verify_foreign_tx_attempt.request.domain_id.clone() diff --git a/crates/node/src/tests.rs b/crates/node/src/tests.rs index 36bbfbd85..2e26af519 100644 --- a/crates/node/src/tests.rs +++ b/crates/node/src/tests.rs @@ -272,8 +272,8 @@ pub async fn request_signature_and_await_response( domain: &DomainConfig, timeout_sec: std::time::Duration, ) -> Option { - let payload = match domain.curve { - Curve::Secp256k1 | Curve::V2Secp256k1 => { + let payload = match domain.key_config.curve { + Curve::Secp256k1 => { let mut payload = [0; 32]; rand::thread_rng().fill_bytes(payload.as_mut()); Payload::Ecdsa(Bytes::new(payload.to_vec()).unwrap()) @@ -354,7 +354,7 @@ pub async fn request_ckd_and_await_response( timeout_sec: std::time::Duration, ) -> Option { assert_matches!( - domain.curve, + domain.key_config.curve, Curve::Bls12381, "`request_ckd_and_await_response` must be called with a compatible domain", ); @@ -440,7 +440,7 @@ pub async fn request_verify_foreign_tx_and_await_response( timeout_sec: std::time::Duration, ) -> Option { assert_matches!( - domain.curve, + domain.key_config.curve, Curve::Secp256k1, "`request_ckd_and_await_response` must be called with a compatible domain", ); diff --git a/crates/node/src/tests/basic_cluster.rs b/crates/node/src/tests/basic_cluster.rs index e0471254a..aee3c805a 100644 --- a/crates/node/src/tests/basic_cluster.rs +++ b/crates/node/src/tests/basic_cluster.rs @@ -5,7 +5,9 @@ use crate::tests::{ DEFAULT_BLOCK_TIME, DEFAULT_MAX_PROTOCOL_WAIT_TIME, DEFAULT_MAX_SIGNATURE_WAIT_TIME, }; use crate::tracking::AutoAbortTask; -use mpc_contract::primitives::domain::{Curve, DomainConfig, DomainId, DomainPurpose}; +use mpc_contract::primitives::domain::{ + infer_key_config_from_curve, Curve, DomainConfig, DomainId, DomainPurpose, +}; use near_time::Clock; // Make a cluster of four nodes, test that we can generate keyshares @@ -31,19 +33,19 @@ async fn test_basic_cluster() { let signature_domain_ecdsa = DomainConfig { id: DomainId(0), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }; let signature_domain_eddsa = DomainConfig { id: DomainId(1), - curve: Curve::Edwards25519, + key_config: infer_key_config_from_curve(Curve::Edwards25519), purpose: DomainPurpose::Sign, }; let ckd_domain = DomainConfig { id: DomainId(2), - curve: Curve::Bls12381, + key_config: infer_key_config_from_curve(Curve::Bls12381), purpose: DomainPurpose::CKD, }; diff --git a/crates/node/src/tests/changing_participant_details.rs b/crates/node/src/tests/changing_participant_details.rs index 954f65731..1ad8fe76c 100644 --- a/crates/node/src/tests/changing_participant_details.rs +++ b/crates/node/src/tests/changing_participant_details.rs @@ -7,7 +7,9 @@ use crate::tests::{ }; use crate::tests::{make_key_storage_config, DEFAULT_BLOCK_TIME}; use crate::tracking::AutoAbortTask; -use mpc_contract::primitives::domain::{Curve, DomainConfig, DomainId, DomainPurpose}; +use mpc_contract::primitives::domain::{ + infer_key_config_from_curve, Curve, DomainConfig, DomainId, DomainPurpose, +}; use mpc_contract::state::ProtocolContractState; use near_time::Clock; @@ -48,7 +50,7 @@ async fn test_changing_participant_set_test_keyshare_import() { let domain = DomainConfig { id: DomainId(0), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }; diff --git a/crates/node/src/tests/faulty.rs b/crates/node/src/tests/faulty.rs index 8e0447443..e53d6709d 100644 --- a/crates/node/src/tests/faulty.rs +++ b/crates/node/src/tests/faulty.rs @@ -5,7 +5,9 @@ use crate::tests::{ DEFAULT_MAX_PROTOCOL_WAIT_TIME, DEFAULT_MAX_SIGNATURE_WAIT_TIME, }; use crate::tracking::AutoAbortTask; -use mpc_contract::primitives::domain::{Curve, DomainConfig, DomainId, DomainPurpose}; +use mpc_contract::primitives::domain::{ + infer_key_config_from_curve, Curve, DomainConfig, DomainId, DomainPurpose, +}; use near_account_id::AccountId; use near_time::Clock; use rand::Rng; @@ -36,7 +38,7 @@ async fn test_faulty_cluster() { let domain = DomainConfig { id: DomainId(0), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }; @@ -171,7 +173,7 @@ async fn test_indexer_stuck() { let domain = DomainConfig { id: DomainId(0), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }; diff --git a/crates/node/src/tests/multidomain.rs b/crates/node/src/tests/multidomain.rs index 10bc12b81..dd6777a4c 100644 --- a/crates/node/src/tests/multidomain.rs +++ b/crates/node/src/tests/multidomain.rs @@ -5,7 +5,9 @@ use crate::tests::{ DEFAULT_MAX_PROTOCOL_WAIT_TIME, DEFAULT_MAX_SIGNATURE_WAIT_TIME, }; use crate::tracking::AutoAbortTask; -use mpc_contract::primitives::domain::{Curve, DomainConfig, DomainId, DomainPurpose}; +use mpc_contract::primitives::domain::{ + infer_key_config_from_curve, Curve, DomainConfig, DomainId, DomainPurpose, +}; use near_time::Clock; // Make a cluster of four nodes, test that we can generate keyshares @@ -35,17 +37,17 @@ async fn test_basic_multidomain() { let mut domains = vec![ DomainConfig { id: DomainId(0), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }, DomainConfig { id: DomainId(1), - curve: Curve::Edwards25519, + key_config: infer_key_config_from_curve(Curve::Edwards25519), purpose: DomainPurpose::Sign, }, DomainConfig { id: DomainId(2), - curve: Curve::Bls12381, + key_config: infer_key_config_from_curve(Curve::Bls12381), purpose: DomainPurpose::CKD, }, ]; @@ -73,8 +75,8 @@ async fn test_basic_multidomain() { tracing::info!("requesting signature"); for domain in &domains { - match domain.curve { - Curve::Secp256k1 | Curve::Edwards25519 | Curve::V2Secp256k1 => { + match domain.key_config.curve { + Curve::Secp256k1 | Curve::Edwards25519 => { assert!(request_signature_and_await_response( &mut setup.indexer, &format!("user{}", domain.id.0), @@ -99,17 +101,17 @@ async fn test_basic_multidomain() { let new_domains = vec![ DomainConfig { id: DomainId(3), - curve: Curve::Edwards25519, + key_config: infer_key_config_from_curve(Curve::Edwards25519), purpose: DomainPurpose::Sign, }, DomainConfig { id: DomainId(4), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }, DomainConfig { id: DomainId(5), - curve: Curve::Bls12381, + key_config: infer_key_config_from_curve(Curve::Bls12381), purpose: DomainPurpose::CKD, }, ]; @@ -138,8 +140,8 @@ async fn test_basic_multidomain() { .expect("must not exceed timeout"); for domain in &domains { - match domain.curve { - Curve::Secp256k1 | Curve::Edwards25519 | Curve::V2Secp256k1 => { + match domain.key_config.curve { + Curve::Secp256k1 | Curve::Edwards25519 => { assert!(request_signature_and_await_response( &mut setup.indexer, &format!("user{}", domain.id.0), @@ -185,8 +187,8 @@ async fn test_basic_multidomain() { .expect("must not exceed timeout"); for domain in &domains { - match domain.curve { - Curve::Secp256k1 | Curve::Edwards25519 | Curve::V2Secp256k1 => { + match domain.key_config.curve { + Curve::Secp256k1 | Curve::Edwards25519 => { assert!(request_signature_and_await_response( &mut setup.indexer, &format!("user{}", domain.id.0), diff --git a/crates/node/src/tests/onboarding.rs b/crates/node/src/tests/onboarding.rs index 4759c2a17..b2a6d1e8d 100644 --- a/crates/node/src/tests/onboarding.rs +++ b/crates/node/src/tests/onboarding.rs @@ -17,7 +17,9 @@ use crate::tracking::AutoAbortTask; use contract_interface::types::Ed25519PublicKey; use ed25519_dalek::{SigningKey, VerifyingKey}; use mpc_contract::node_migrations::{BackupServiceInfo, DestinationNodeInfo}; -use mpc_contract::primitives::domain::{Curve, DomainConfig, DomainId, DomainPurpose}; +use mpc_contract::primitives::domain::{ + infer_key_config_from_curve, Curve, DomainConfig, DomainId, DomainPurpose, +}; use mpc_contract::state::ProtocolContractState; use near_time::Clock; use rand::rngs::OsRng; @@ -103,7 +105,7 @@ async fn test_onboarding() { let domain = DomainConfig { id: DomainId(0), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }; diff --git a/crates/node/src/tests/resharing.rs b/crates/node/src/tests/resharing.rs index e360d9284..415eef103 100644 --- a/crates/node/src/tests/resharing.rs +++ b/crates/node/src/tests/resharing.rs @@ -7,7 +7,8 @@ use crate::tests::{ }; use crate::tracking::AutoAbortTask; use mpc_contract::primitives::domain::{ - infer_purpose_from_curve, Curve, DomainConfig, DomainId, DomainPurpose, + infer_key_config_from_curve, infer_purpose_from_curve, Curve, DomainConfig, DomainId, + DomainPurpose, }; use near_time::Clock; use rstest::rstest; @@ -23,7 +24,6 @@ use super::DEFAULT_BLOCK_TIME; #[case(1, Curve::Edwards25519, 3)] #[case(2, Curve::Bls12381, 3)] // TODO(#1946): re-enable once it is no longer flaky -// #[case(3, Curve::V2Secp256k1, 5)] async fn test_key_resharing_simple( #[case] case: u16, #[case] curve: Curve, @@ -50,7 +50,7 @@ async fn test_key_resharing_simple( let domain = DomainConfig { id: DomainId(0), - curve, + key_config: infer_key_config_from_curve(curve), purpose: infer_purpose_from_curve(curve), }; @@ -76,8 +76,8 @@ async fn test_key_resharing_simple( .expect("must not exceed timeout"); // Sanity check. - match domain.curve { - Curve::Secp256k1 | Curve::Edwards25519 | Curve::V2Secp256k1 => { + match domain.key_config.curve { + Curve::Secp256k1 | Curve::Edwards25519 => { assert!(request_signature_and_await_response( &mut setup.indexer, "user1", @@ -120,8 +120,8 @@ async fn test_key_resharing_simple( .await .expect("Timeout waiting for resharing to complete"); - match domain.curve { - Curve::Secp256k1 | Curve::Edwards25519 | Curve::V2Secp256k1 => { + match domain.key_config.curve { + Curve::Secp256k1 | Curve::Edwards25519 => { assert!(request_signature_and_await_response( &mut setup.indexer, "user1", @@ -172,7 +172,7 @@ async fn test_key_resharing_multistage() { let domain = DomainConfig { id: DomainId(0), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }; @@ -377,7 +377,7 @@ async fn test_signature_requests_in_resharing_are_processed() { let domain = DomainConfig { id: DomainId(0), - curve: Curve::Secp256k1, + key_config: infer_key_config_from_curve(Curve::Secp256k1), purpose: DomainPurpose::Sign, }; diff --git a/pytest/tests/test_request_during_resharing.py b/pytest/tests/test_request_during_resharing.py index 898633701..7449f8f59 100644 --- a/pytest/tests/test_request_during_resharing.py +++ b/pytest/tests/test_request_during_resharing.py @@ -17,6 +17,7 @@ import pathlib import sys +import pytest from common_lib.contract_state import ProtocolState sys.path.append(str(pathlib.Path(__file__).resolve().parents[1])) @@ -59,6 +60,7 @@ def test_threshold_from_previous_running_state_is_maintained(): ) +@pytest.mark.skip(reason="V2Secp256k1 removed from DTO; re-enable when DamgardEtAl protocol is surfaced via KeyConfig DTO") def test_threshold_from_previous_running_state_is_maintained_robust_ecdsa_only(): number_of_nodes = 6 threshold = 5 From 27df798dea9ca4aec4c5afdd8b8db911b84bbce9 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:05:08 +0100 Subject: [PATCH 2/4] Adding pytests --- .../snapshots/abi__abi_has_not_changed.snap | 72 ++++++++++++------- pytest/tests/robust_ecdsa/__init__.py | 7 ++ 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap b/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap index 4c5353eba..0820ad6c0 100644 --- a/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap +++ b/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap @@ -1673,8 +1673,7 @@ expression: abi "enum": [ "Secp256k1", "Ed25519", - "Bls12381", - "V2Secp256k1" + "Bls12381" ] }, "DestinationNodeInfo": { @@ -1694,22 +1693,22 @@ expression: abi } }, "DomainConfig": { - "description": "Describes the configuration of a domain: the domain ID and the curve it uses.", + "description": "Describes the configuration of a domain: the domain ID, key configuration, and purpose.", "type": "object", "required": [ "id", - "purpose", - "scheme" + "key_config", + "purpose" ], "properties": { "id": { "$ref": "#/definitions/DomainId" }, + "key_config": { + "$ref": "#/definitions/KeyConfig" + }, "purpose": { "$ref": "#/definitions/DomainPurpose" - }, - "scheme": { - "$ref": "#/definitions/Curve" } } }, @@ -2212,6 +2211,26 @@ expression: abi } } }, + "KeyConfig": { + "description": "Specifies the key configuration for a domain: protocol, curve, and reconstruction threshold.", + "type": "object", + "required": [ + "curve", + "protocol", + "reconstruction_threshold" + ], + "properties": { + "curve": { + "$ref": "#/definitions/Curve" + }, + "protocol": { + "$ref": "#/definitions/Protocol" + }, + "reconstruction_threshold": { + "$ref": "#/definitions/ReconstructionThreshold" + } + } + }, "KeyEvent": { "description": "Key generation or resharing event state.", "type": "object", @@ -2758,6 +2777,16 @@ expression: abi } } }, + "Protocol": { + "description": "Specifies what protocol the nodes need to run for a domain.", + "type": "string", + "enum": [ + "OtBasedEcdsa", + "Frost", + "ConfidentialKeyDerivation", + "DamgardEtAl" + ] + }, "ProtocolContractState": { "description": "The main protocol contract state enum.", "oneOf": [ @@ -3006,6 +3035,12 @@ expression: abi } ] }, + "ReconstructionThreshold": { + "description": "Number of shares required to reconstruct the secret key for a domain. For legacy domains this matches the global threshold; new domains can set it independently.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "ResharingContractState": { "description": "State when the contract is resharing keys to new participants.", "type": "object", @@ -3222,22 +3257,11 @@ expression: abi }, "SignatureScheme": { "description": "Supported signature schemes.", - "oneOf": [ - { - "type": "string", - "enum": [ - "Secp256k1", - "Ed25519", - "Bls12381" - ] - }, - { - "description": "Robust ECDSA variant.", - "type": "string", - "enum": [ - "V2Secp256k1" - ] - } + "type": "string", + "enum": [ + "Secp256k1", + "Ed25519", + "Bls12381" ] }, "SolanaExtractor": { diff --git a/pytest/tests/robust_ecdsa/__init__.py b/pytest/tests/robust_ecdsa/__init__.py index 17289d636..053087eaf 100644 --- a/pytest/tests/robust_ecdsa/__init__.py +++ b/pytest/tests/robust_ecdsa/__init__.py @@ -1 +1,8 @@ # TODO(#1690): tests in this module are almost the same as the shared_cluster_tests. Eventually they should be unified +import pytest + +# V2Secp256k1 has been removed from the DTO SignatureScheme enum. +# These tests need to be re-enabled once the DamgardEtAl protocol is surfaced via the KeyConfig DTO. +pytestmark = pytest.mark.skip( + reason="V2Secp256k1 removed from DTO; re-enable when DamgardEtAl protocol is surfaced via KeyConfig DTO" +) From f02269e4ca5a77e01b4fd29807c29b343bd79f03 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Fri, 13 Mar 2026 18:06:01 +0100 Subject: [PATCH 3/4] Updating code --- crates/contract/src/primitives/domain.rs | 28 +++++++++++++++++++++++- crates/node/src/providers.rs | 1 - 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/crates/contract/src/primitives/domain.rs b/crates/contract/src/primitives/domain.rs index 224f56fbf..f67deada5 100644 --- a/crates/contract/src/primitives/domain.rs +++ b/crates/contract/src/primitives/domain.rs @@ -136,7 +136,7 @@ pub fn is_valid_curve_for_purpose(purpose: DomainPurpose, curve: Curve) -> bool /// Describes the configuration of a domain: the domain ID, key configuration, and purpose. #[near(serializers=[borsh, json])] -#[serde(from = "DomainConfigCompat")] +#[serde(from = "DomainConfigCompat", into = "DomainConfigSer")] #[derive(Debug, Clone, PartialEq, Eq)] pub struct DomainConfig { pub id: DomainId, @@ -144,6 +144,32 @@ pub struct DomainConfig { pub purpose: DomainPurpose, } +/// Serialization helper for [`DomainConfig`]. +/// +/// Emits a `scheme` field (the curve name) alongside `key_config` so that +/// older contracts that only understand `{ scheme: "Secp256k1" }` can still +/// parse the JSON. Newer contracts use [`DomainConfigCompat`] which prefers +/// `key_config` when present. +#[derive(serde::Serialize)] +struct DomainConfigSer { + id: DomainId, + /// Backward-compat: older contracts read this field. + scheme: Curve, + key_config: KeyConfig, + purpose: DomainPurpose, +} + +impl From for DomainConfigSer { + fn from(d: DomainConfig) -> Self { + Self { + id: d.id, + scheme: d.key_config.curve, + key_config: d.key_config, + purpose: d.purpose, + } + } +} + /// JSON-only compatibility helper: /// - Old 3.4.x format: `{ id, scheme: "Secp256k1" }` (no `purpose`, no `key_config`) /// - Previous format: `{ id, scheme: "Secp256k1", purpose: "Sign" }` (no `key_config`) diff --git a/crates/node/src/providers.rs b/crates/node/src/providers.rs index 3825294c9..069e1d73e 100644 --- a/crates/node/src/providers.rs +++ b/crates/node/src/providers.rs @@ -19,7 +19,6 @@ use crate::types::SignatureId; pub use ckd::CKDProvider; pub use ecdsa::EcdsaSignatureProvider; pub use ecdsa::EcdsaTaskId; -pub use robust_ecdsa::RobustEcdsaSignatureProvider; use std::sync::Arc; use threshold_signatures::ReconstructionLowerBound; From c26addad33dfb466f66598ccf9e8d37cb8939350 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Fri, 13 Mar 2026 20:03:54 +0100 Subject: [PATCH 4/4] adjusting domain --- crates/contract/src/primitives/domain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/contract/src/primitives/domain.rs b/crates/contract/src/primitives/domain.rs index f67deada5..f394c204e 100644 --- a/crates/contract/src/primitives/domain.rs +++ b/crates/contract/src/primitives/domain.rs @@ -483,7 +483,7 @@ pub mod tests { fn test_serialization_format() { let domain_config = make_domain(3, Curve::Secp256k1, DomainPurpose::Sign); let json = serde_json::to_string(&domain_config).unwrap(); - let expected = r#"{"id":3,"key_config":{"protocol":"OtBasedEcdsa","curve":"Secp256k1","reconstruction_threshold":0},"purpose":"Sign"}"#; + let expected = r#"{"id":3,"scheme":"Secp256k1","key_config":{"protocol":"OtBasedEcdsa","curve":"Secp256k1","reconstruction_threshold":0},"purpose":"Sign"}"#; assert_eq!(json, expected); let domain_config: DomainConfig = serde_json::from_str(&json).unwrap();