diff --git a/Cargo.lock b/Cargo.lock index 480696eca9c0..0bf3a97ba969 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12612,6 +12612,7 @@ dependencies = [ "ic-ed25519 0.6.0", "ic-management-canister-types 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "ic-secp256k1", + "ic-secp256r1", "ic-vetkeys", ] diff --git a/packages/ic-pub-key/BUILD.bazel b/packages/ic-pub-key/BUILD.bazel index c3c4581c08bb..a7d6365dba1f 100644 --- a/packages/ic-pub-key/BUILD.bazel +++ b/packages/ic-pub-key/BUILD.bazel @@ -2,19 +2,23 @@ load("@rules_rust//rust:defs.bzl", "rust_doc", "rust_doc_test", "rust_library", package(default_visibility = ["//visibility:public"]) +FEATURES = [ + "secp256k1", + "secp256r1", + "ed25519", + "vetkeys", +] + rust_library( name = "ic-pub-key", srcs = glob(["src/**/*.rs"]), - crate_features = [ - "secp256k1", - "ed25519", - "vetkeys", - ], + crate_features = FEATURES, crate_name = "ic_pub_key", version = "0.3.0", deps = [ "//packages/ic-ed25519", "//packages/ic-secp256k1", + "//packages/ic-secp256r1", "@crate_index//:ic-cdk-management-canister", "@crate_index//:ic-management-canister-types", "@crate_index//:ic-vetkeys", @@ -27,42 +31,26 @@ rust_library( rust_doc( name = "doc", crate = ":ic-pub-key", - rustdoc_flags = [ - "--cfg=feature=\"secp256k1\"", - "--cfg=feature=\"ed25519\"", - "--cfg=feature=\"vetkeys\"", - ], + rustdoc_flags = ["--cfg=feature=\"%s\"" % feature for feature in FEATURES], ) rust_doc_test( name = "doc_test", crate = ":ic-pub-key", - crate_features = [ - "secp256k1", - "ed25519", - "vetkeys", - ], + crate_features = FEATURES, deps = [":ic-pub-key"], ) rust_test( name = "unit_tests", crate = ":ic-pub-key", - crate_features = [ - "secp256k1", - "ed25519", - "vetkeys", - ], + crate_features = FEATURES, ) rust_test_suite( name = "integration_tests", srcs = glob(["tests/**/*.rs"]), - crate_features = [ - "secp256k1", - "ed25519", - "vetkeys", - ], + crate_features = FEATURES, deps = [ ":ic-pub-key", "@crate_index//:hex", diff --git a/packages/ic-pub-key/CHANGELOG.md b/packages/ic-pub-key/CHANGELOG.md index e33e9ef50f7b..38ac6f469fa5 100644 --- a/packages/ic-pub-key/CHANGELOG.md +++ b/packages/ic-pub-key/CHANGELOG.md @@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add offline derivation for ECDSA keys over the secp256r1 curve, behind the new (default) `secp256r1` feature. It supports the mainnet keys `key_1` and `test_key_1` as well as the PocketIC keys. + ### Changed - Upgrade the `ic-management-canister-types` dependency from `0.5.0` to `0.11.0`. The re-exported types now come from that version. +- Upgrade the (optional) `ic-vetkeys` dependency from `0.6.0` to `0.7.0`. +- The `vetkeys` feature now also depends on `ic-cdk-management-canister`. ## [0.3.0] - 2026-02-04 diff --git a/packages/ic-pub-key/Cargo.toml b/packages/ic-pub-key/Cargo.toml index 6decacb4a980..3531057f3b61 100644 --- a/packages/ic-pub-key/Cargo.toml +++ b/packages/ic-pub-key/Cargo.toml @@ -11,14 +11,16 @@ edition.workspace = true documentation.workspace = true [features] -default = ["secp256k1", "ed25519", "vetkeys"] +default = ["secp256k1", "secp256r1", "ed25519", "vetkeys"] secp256k1 = ["dep:ic-secp256k1"] +secp256r1 = ["dep:ic-secp256r1"] ed25519 = ["dep:ic-ed25519"] vetkeys = ["dep:ic-vetkeys", "dep:ic-cdk-management-canister"] [dependencies] ic-secp256k1 = { path = "../ic-secp256k1", version = "0.3.0", optional = true } +ic-secp256r1 = { path = "../ic-secp256r1", version = "0.3.0", optional = true } ic-ed25519 = { path = "../ic-ed25519", version = "0.6.0", optional = true } ic-vetkeys = { workspace = true, optional = true } ic-cdk-management-canister = { workspace = true, optional = true } diff --git a/packages/ic-pub-key/README.md b/packages/ic-pub-key/README.md index b0d35e3764d8..fe1674afa2b3 100644 --- a/packages/ic-pub-key/README.md +++ b/packages/ic-pub-key/README.md @@ -9,3 +9,11 @@ context data. This derivation can be performed "online" by a canister using a query call, but this is inconvenient in some usage scenarios. This crate offers an alternative, namely fully *offline* key derivation. + +### Features + +Each kind of key needs its feature, and all of them are enabled by default: +`secp256k1` (ECDSA over secp256k1 and BIP340 Schnorr), `secp256r1` (ECDSA over +secp256r1), `ed25519` (Ed25519 Schnorr) and `vetkeys` (VetKD). To build only some +of them, for example to keep a canister's Wasm small, depend on this crate with +`default-features = false` and enable the features you need. diff --git a/packages/ic-pub-key/src/lib.rs b/packages/ic-pub-key/src/lib.rs index f557cdd4686e..ee0636e302d6 100644 --- a/packages/ic-pub-key/src/lib.rs +++ b/packages/ic-pub-key/src/lib.rs @@ -26,9 +26,29 @@ //! //! As an extension of the management canister interface, this crate also allows //! deriving keys relative to the hardcoded test master keys used by PocketIC. - -#[cfg(not(any(feature = "secp256k1", feature = "ed25519", feature = "vetkeys")))] -compile_error!("At least one of the features (secp256k1, ed25519, vetkeys) must be enabled"); +//! +//! # Features +//! +//! Each kind of key needs its feature, and all of them are enabled by default: +//! +//! * `secp256k1`: ECDSA over secp256k1 and BIP340 Schnorr +//! * `secp256r1`: ECDSA over secp256r1 (P-256) +//! * `ed25519`: Ed25519 Schnorr +//! * `vetkeys`: VetKD +//! +//! Deriving a key whose feature is disabled fails with [`Error::AlgorithmNotSupported`]. +//! To build only some of them, for example to keep a canister's Wasm small, depend on +//! this crate with `default-features = false` and enable the features you need. + +#[cfg(not(any( + feature = "secp256k1", + feature = "secp256r1", + feature = "ed25519", + feature = "vetkeys" +)))] +compile_error!( + "At least one of the features (secp256k1, secp256r1, ed25519, vetkeys) must be enabled" +); pub use ic_management_canister_types::{ CanisterId, EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgs, EcdsaPublicKeyResult, SchnorrAlgorithm, @@ -59,6 +79,8 @@ enum MasterPublicKeyInner { EcdsaSecp256k1(ic_secp256k1::PublicKey), #[cfg(feature = "secp256k1")] Bip340Secp256k1(ic_secp256k1::PublicKey), + #[cfg(feature = "secp256r1")] + EcdsaSecp256r1(ic_secp256r1::PublicKey), #[cfg(feature = "ed25519")] Ed25519(ic_ed25519::PublicKey), #[cfg(feature = "vetkeys")] @@ -88,6 +110,13 @@ impl MasterPublicKey { )]); DerivedPublicKeyInner::Bip340Secp256k1(mk.derive_subkey(&path)) } + #[cfg(feature = "secp256r1")] + MasterPublicKeyInner::EcdsaSecp256r1(mk) => { + let path = ic_secp256r1::DerivationPath::new(vec![ic_secp256r1::DerivationIndex( + canister_id.as_slice().to_vec(), + )]); + DerivedPublicKeyInner::EcdsaSecp256r1(mk.derive_subkey(&path)) + } #[cfg(feature = "ed25519")] MasterPublicKeyInner::Ed25519(mk) => { let path = ic_ed25519::DerivationPath::new(vec![ic_ed25519::DerivationIndex( @@ -107,48 +136,76 @@ impl MasterPublicKey { impl TryFrom<&EcdsaKeyId> for MasterPublicKey { type Error = Error; + #[cfg_attr( + not(any(feature = "secp256k1", feature = "secp256r1")), + allow(unused_variables) + )] fn try_from(key_id: &EcdsaKeyId) -> Result { - if key_id.curve != EcdsaCurve::Secp256k1 { - return Err(Error::AlgorithmNotSupported); - } - #[cfg(feature = "secp256k1")] { - let mk = match (key_id.curve, key_id.name.as_ref()) { - (EcdsaCurve::Secp256k1, "key_1") => { - ic_secp256k1::PublicKey::mainnet_key(ic_secp256k1::MasterPublicKeyId::EcdsaKey1) - } - (EcdsaCurve::Secp256k1, "test_key_1") => ic_secp256k1::PublicKey::mainnet_key( - ic_secp256k1::MasterPublicKeyId::EcdsaTestKey1, - ), - (EcdsaCurve::Secp256k1, "pocketic_key_1") => ic_secp256k1::PublicKey::pocketic_key( - ic_secp256k1::PocketIcMasterPublicKeyId::EcdsaKey1, - ), - (EcdsaCurve::Secp256k1, "pocketic_test_key_1") => { - ic_secp256k1::PublicKey::pocketic_key( + if key_id.curve == EcdsaCurve::Secp256k1 { + let mk = match key_id.name.as_ref() { + "key_1" => ic_secp256k1::PublicKey::mainnet_key( + ic_secp256k1::MasterPublicKeyId::EcdsaKey1, + ), + "test_key_1" => ic_secp256k1::PublicKey::mainnet_key( + ic_secp256k1::MasterPublicKeyId::EcdsaTestKey1, + ), + "pocketic_key_1" => ic_secp256k1::PublicKey::pocketic_key( + ic_secp256k1::PocketIcMasterPublicKeyId::EcdsaKey1, + ), + "pocketic_test_key_1" => ic_secp256k1::PublicKey::pocketic_key( ic_secp256k1::PocketIcMasterPublicKeyId::EcdsaTestKey1, - ) - } - (EcdsaCurve::Secp256k1, "dfx_test_key") => ic_secp256k1::PublicKey::pocketic_key( - ic_secp256k1::PocketIcMasterPublicKeyId::EcdsaDfxTestKey, - ), - (_, _) => return Err(Error::UnknownKeyIdentifier), - }; + ), + "dfx_test_key" => ic_secp256k1::PublicKey::pocketic_key( + ic_secp256k1::PocketIcMasterPublicKeyId::EcdsaDfxTestKey, + ), + _ => return Err(Error::UnknownKeyIdentifier), + }; - let inner = MasterPublicKeyInner::EcdsaSecp256k1(mk); - Ok(Self { inner }) + let inner = MasterPublicKeyInner::EcdsaSecp256k1(mk); + return Ok(Self { inner }); + } } - #[cfg(not(feature = "secp256k1"))] + #[cfg(feature = "secp256r1")] { - Err(Error::AlgorithmNotSupported) + if key_id.curve == EcdsaCurve::Secp256r1 { + let mk = match key_id.name.as_ref() { + "key_1" => ic_secp256r1::PublicKey::mainnet_key( + ic_secp256r1::MasterPublicKeyId::EcdsaKey1, + ), + "test_key_1" => ic_secp256r1::PublicKey::mainnet_key( + ic_secp256r1::MasterPublicKeyId::EcdsaTestKey1, + ), + "pocketic_key_1" => ic_secp256r1::PublicKey::pocketic_key( + ic_secp256r1::PocketIcMasterPublicKeyId::EcdsaKey1, + ), + "pocketic_test_key_1" => ic_secp256r1::PublicKey::pocketic_key( + ic_secp256r1::PocketIcMasterPublicKeyId::EcdsaTestKey1, + ), + "dfx_test_key" => ic_secp256r1::PublicKey::pocketic_key( + ic_secp256r1::PocketIcMasterPublicKeyId::EcdsaDfxTestKey, + ), + _ => return Err(Error::UnknownKeyIdentifier), + }; + + let inner = MasterPublicKeyInner::EcdsaSecp256r1(mk); + return Ok(Self { inner }); + } } + + Err(Error::AlgorithmNotSupported) } } impl TryFrom<&SchnorrKeyId> for MasterPublicKey { type Error = Error; + #[cfg_attr( + not(any(feature = "secp256k1", feature = "ed25519")), + allow(unused_variables) + )] fn try_from(key_id: &SchnorrKeyId) -> Result { #[cfg(feature = "secp256k1")] { @@ -211,6 +268,7 @@ impl TryFrom<&SchnorrKeyId> for MasterPublicKey { impl TryFrom<&VetKDKeyId> for MasterPublicKey { type Error = Error; + #[cfg_attr(not(feature = "vetkeys"), allow(unused_variables))] fn try_from(key_id: &VetKDKeyId) -> Result { #[cfg(feature = "vetkeys")] { @@ -242,12 +300,46 @@ enum DerivedPublicKeyInner { EcdsaSecp256k1((ic_secp256k1::PublicKey, [u8; 32])), #[cfg(feature = "secp256k1")] Bip340Secp256k1((ic_secp256k1::PublicKey, [u8; 32])), + #[cfg(feature = "secp256r1")] + EcdsaSecp256r1((ic_secp256r1::PublicKey, [u8; 32])), #[cfg(feature = "ed25519")] Ed25519((ic_ed25519::PublicKey, [u8; 32])), #[cfg(feature = "vetkeys")] VetKD(ic_vetkeys::DerivedPublicKey), } +impl DerivedPublicKeyInner { + fn serialize(&self) -> Vec { + match self { + #[cfg(feature = "secp256k1")] + Self::EcdsaSecp256k1(ck) => ck.0.serialize_sec1(true), + #[cfg(feature = "secp256k1")] + Self::Bip340Secp256k1(ck) => ck.0.serialize_sec1(true), + #[cfg(feature = "secp256r1")] + Self::EcdsaSecp256r1(ck) => ck.0.serialize_sec1(true), + #[cfg(feature = "ed25519")] + Self::Ed25519(ck) => ck.0.serialize_raw().to_vec(), + #[cfg(feature = "vetkeys")] + Self::VetKD(ck) => ck.serialize(), + } + } + + fn chain_code(&self) -> Option> { + match self { + #[cfg(feature = "secp256k1")] + Self::EcdsaSecp256k1(ck) => Some(ck.1.to_vec()), + #[cfg(feature = "secp256k1")] + Self::Bip340Secp256k1(ck) => Some(ck.1.to_vec()), + #[cfg(feature = "secp256r1")] + Self::EcdsaSecp256r1(ck) => Some(ck.1.to_vec()), + #[cfg(feature = "ed25519")] + Self::Ed25519(ck) => Some(ck.1.to_vec()), + #[cfg(feature = "vetkeys")] + Self::VetKD(_ck) => None, + } + } +} + /// The canister's master public key of a threshold signature system /// /// Each canister gets its own canister master key, which is derived from @@ -286,6 +378,15 @@ impl CanisterMasterKey { ck.0.derive_subkey_with_chain_code(&path, &ck.1), ) } + #[cfg(feature = "secp256r1")] + DerivedPublicKeyInner::EcdsaSecp256r1(ck) => { + let path = ic_secp256r1::DerivationPath::new(vec![ic_secp256r1::DerivationIndex( + context.to_vec(), + )]); + DerivedPublicKeyInner::EcdsaSecp256r1( + ck.0.derive_subkey_with_chain_code(&path, &ck.1), + ) + } #[cfg(feature = "ed25519")] DerivedPublicKeyInner::Ed25519(ck) => { let path = ic_ed25519::DerivationPath::new(vec![ic_ed25519::DerivationIndex( @@ -331,6 +432,18 @@ impl CanisterMasterKey { ck.0.derive_subkey_with_chain_code(&path, &ck.1), ) } + #[cfg(feature = "secp256r1")] + DerivedPublicKeyInner::EcdsaSecp256r1(ck) => { + let path = ic_secp256r1::DerivationPath::new( + path.iter() + .cloned() + .map(ic_secp256r1::DerivationIndex) + .collect(), + ); + DerivedPublicKeyInner::EcdsaSecp256r1( + ck.0.derive_subkey_with_chain_code(&path, &ck.1), + ) + } #[cfg(feature = "ed25519")] DerivedPublicKeyInner::Ed25519(ck) => { let path = ic_ed25519::DerivationPath::new( @@ -354,32 +467,14 @@ impl CanisterMasterKey { /// Return the serialized encoding of the canister master public key pub fn serialize(&self) -> Vec { - match &self.inner { - #[cfg(feature = "secp256k1")] - DerivedPublicKeyInner::EcdsaSecp256k1(ck) => ck.0.serialize_sec1(true), - #[cfg(feature = "secp256k1")] - DerivedPublicKeyInner::Bip340Secp256k1(ck) => ck.0.serialize_sec1(true), - #[cfg(feature = "ed25519")] - DerivedPublicKeyInner::Ed25519(ck) => ck.0.serialize_raw().to_vec(), - #[cfg(feature = "vetkeys")] - DerivedPublicKeyInner::VetKD(ck) => ck.serialize(), - } + self.inner.serialize() } /// Return the chain code used for further derivation, if relevant /// /// Returns None if not applicable for this algorithm pub fn chain_code(&self) -> Option> { - match &self.inner { - #[cfg(feature = "secp256k1")] - DerivedPublicKeyInner::EcdsaSecp256k1(ck) => Some(ck.1.to_vec()), - #[cfg(feature = "secp256k1")] - DerivedPublicKeyInner::Bip340Secp256k1(ck) => Some(ck.1.to_vec()), - #[cfg(feature = "ed25519")] - DerivedPublicKeyInner::Ed25519(ck) => Some(ck.1.to_vec()), - #[cfg(feature = "vetkeys")] - DerivedPublicKeyInner::VetKD(_ck) => None, - } + self.inner.chain_code() } } @@ -391,32 +486,14 @@ pub struct DerivedPublicKey { impl DerivedPublicKey { /// Return the serialized encoding of the derived public key pub fn serialize(&self) -> Vec { - match &self.inner { - #[cfg(feature = "secp256k1")] - DerivedPublicKeyInner::EcdsaSecp256k1(ck) => ck.0.serialize_sec1(true), - #[cfg(feature = "secp256k1")] - DerivedPublicKeyInner::Bip340Secp256k1(ck) => ck.0.serialize_sec1(true), - #[cfg(feature = "ed25519")] - DerivedPublicKeyInner::Ed25519(ck) => ck.0.serialize_raw().to_vec(), - #[cfg(feature = "vetkeys")] - DerivedPublicKeyInner::VetKD(ck) => ck.serialize(), - } + self.inner.serialize() } /// Return the chain code used for further derivation, if relevant /// /// Returns None if not applicable for this algorithm pub fn chain_code(&self) -> Option> { - match &self.inner { - #[cfg(feature = "secp256k1")] - DerivedPublicKeyInner::EcdsaSecp256k1(ck) => Some(ck.1.to_vec()), - #[cfg(feature = "secp256k1")] - DerivedPublicKeyInner::Bip340Secp256k1(ck) => Some(ck.1.to_vec()), - #[cfg(feature = "ed25519")] - DerivedPublicKeyInner::Ed25519(ck) => Some(ck.1.to_vec()), - #[cfg(feature = "vetkeys")] - DerivedPublicKeyInner::VetKD(_ck) => None, - } + self.inner.chain_code() } } @@ -440,6 +517,10 @@ impl DerivedPublicKey { /// * `pocketic_test_key_1`: The key used by PocketIC when a request to the management canister /// uses `test_key_1`. /// * `dfx_test_key`: PocketIC specific test key +/// +/// All of them exist for both curves: secp256k1 needs the `secp256k1` feature, and +/// secp256r1 needs the `secp256r1` feature. Without the feature for the requested +/// curve, this returns [`Error::AlgorithmNotSupported`]. pub fn derive_ecdsa_key(args: &EcdsaPublicKeyArgs) -> Result { let canister_id = args.canister_id.ok_or(Error::CanisterIdMissing)?; diff --git a/packages/ic-pub-key/tests/tests.rs b/packages/ic-pub-key/tests/tests.rs index 7b0018f44f97..ab78ab7c031c 100644 --- a/packages/ic-pub-key/tests/tests.rs +++ b/packages/ic-pub-key/tests/tests.rs @@ -1,10 +1,13 @@ use ic_pub_key::*; +#[cfg(any(feature = "secp256k1", feature = "secp256r1"))] fn test_ecdsa_derivation( + curve: EcdsaCurve, key_name: &'static str, canister_id: &'static str, derivation_path: &[&'static str], expected_dpk: &'static str, + expected_chain_code: &'static str, ) { use std::str::FromStr; @@ -17,7 +20,7 @@ fn test_ecdsa_derivation( canister_id: Some(ic_pub_key::CanisterId::from_str(canister_id).unwrap()), derivation_path, key_id: EcdsaKeyId { - curve: EcdsaCurve::Secp256k1, + curve, name: key_name.to_string(), }, }; @@ -25,8 +28,46 @@ fn test_ecdsa_derivation( let dpk = derive_ecdsa_key(&args).unwrap(); assert_eq!(hex::encode(dpk.public_key), expected_dpk); + assert_eq!(hex::encode(dpk.chain_code), expected_chain_code); } +#[cfg(feature = "secp256k1")] +fn test_ecdsa_secp256k1_derivation( + key_name: &'static str, + canister_id: &'static str, + derivation_path: &[&'static str], + expected_dpk: &'static str, + expected_chain_code: &'static str, +) { + test_ecdsa_derivation( + EcdsaCurve::Secp256k1, + key_name, + canister_id, + derivation_path, + expected_dpk, + expected_chain_code, + ); +} + +#[cfg(feature = "secp256r1")] +fn test_ecdsa_secp256r1_derivation( + key_name: &'static str, + canister_id: &'static str, + derivation_path: &[&'static str], + expected_dpk: &'static str, + expected_chain_code: &'static str, +) { + test_ecdsa_derivation( + EcdsaCurve::Secp256r1, + key_name, + canister_id, + derivation_path, + expected_dpk, + expected_chain_code, + ); +} + +#[cfg(any(feature = "secp256k1", feature = "ed25519"))] fn test_schnorr_derivation( algorithm: SchnorrAlgorithm, key_name: &'static str, @@ -55,6 +96,7 @@ fn test_schnorr_derivation( assert_eq!(hex::encode(dpk.public_key), expected_dpk,); } +#[cfg(feature = "ed25519")] fn test_ed25519_derivation( key_name: &'static str, canister_id: &'static str, @@ -70,6 +112,7 @@ fn test_ed25519_derivation( ); } +#[cfg(feature = "secp256k1")] fn test_bip340_derivation( key_name: &'static str, canister_id: &'static str, @@ -268,7 +311,7 @@ fn test_bip340_derivation_pocketic_dfx_test_key() { #[cfg(feature = "secp256k1")] #[test] fn test_ecdsa_secp256k1_derivation_key1() { - test_ecdsa_derivation( + test_ecdsa_secp256k1_derivation( "key_1", "h5jwf-5iaaa-aaaan-qmvoa-cai", &[ @@ -281,20 +324,22 @@ fn test_ecdsa_secp256k1_derivation_key1() { "key_1", ], "03e99cc5403dfefbc1de767ab34b637ab93c11cecffe89d6475e9701cffa2d51b4", + "e4613655c9f0c9daa769ac5215cd4ef28d02f424c32a0b702a9cd89cbfbf28d7", ); } #[cfg(feature = "secp256k1")] #[test] fn test_ecdsa_secp256k1_derivation_test_key1() { - test_ecdsa_derivation( + test_ecdsa_secp256k1_derivation( "test_key_1", "h5jwf-5iaaa-aaaan-qmvoa-cai", &["Hello", "Threshold", "Signatures"], "0315ae8bb8c6e9f78eec2167f5ac773067f37a39da1a1efbc585f9e90658d1c620", + "366057592ee6212c33dd76f5b4de2ac44a20e861078347fc77d252cbc9d70a5c", ); - test_ecdsa_derivation( + test_ecdsa_secp256k1_derivation( "test_key_1", "h5jwf-5iaaa-aaaan-qmvoa-cai", &[ @@ -307,13 +352,54 @@ fn test_ecdsa_secp256k1_derivation_test_key1() { "test_key_1", ], "0262c4eb6534f278ceebcc8f4172d38acf7d76e1c74ee1e10f362d59f73658ae50", + "95040337537953fc480879186261f689c7fa6a99aab1d1c27c1245692cfa8455", + ); +} + +#[cfg(feature = "secp256r1")] +#[test] +fn test_ecdsa_secp256r1_derivation_key1() { + test_ecdsa_secp256r1_derivation( + "key_1", + "h5jwf-5iaaa-aaaan-qmvoa-cai", + &[ + "Test", + "Derivation", + "For", + "Mainnet", + "ECDSA", + "secp256r1", + "key_1", + ], + "02cda0a98456eaf736d750a6b3885433df81b9a5e76c2ee74e4552d9de1a3b38a5", + "a06fa0ac7c5d36631ee816b70a423d35c50af364d589c3e71e956a5b5e4457b4", + ); +} + +#[cfg(feature = "secp256r1")] +#[test] +fn test_ecdsa_secp256r1_derivation_test_key1() { + test_ecdsa_secp256r1_derivation( + "test_key_1", + "h5jwf-5iaaa-aaaan-qmvoa-cai", + &[ + "Test", + "Derivation", + "For", + "Mainnet", + "ECDSA", + "secp256r1", + "test_key_1", + ], + "038bee03c1155d5b11e08b90fbdebb584c84ba617b916498b9c3d7bdc7a3d32eb8", + "5bea193fe95011f0274a55679d441b9f822abfd829a6482a9b259dc3ed9215e5", ); } #[cfg(feature = "secp256k1")] #[test] fn test_ecdsa_secp256k1_derivation_pocketic_key1() { - test_ecdsa_derivation( + test_ecdsa_secp256k1_derivation( "pocketic_key_1", "uzt4z-lp777-77774-qaabq-cai", &[ @@ -326,13 +412,14 @@ fn test_ecdsa_secp256k1_derivation_pocketic_key1() { "key_1", ], "03bca84b5629dc70a37cadb5b3cda0bfc35abc5658f1d9bf8335b10199785a3836", + "69797aab9c7b3d2498669bab8c5c77656ba168159c9173544d60a6e9c3d7cf25", ); } #[cfg(feature = "secp256k1")] #[test] fn test_ecdsa_secp256k1_derivation_pocketic_test_key1() { - test_ecdsa_derivation( + test_ecdsa_secp256k1_derivation( "pocketic_test_key_1", "uzt4z-lp777-77774-qaabq-cai", &[ @@ -345,13 +432,14 @@ fn test_ecdsa_secp256k1_derivation_pocketic_test_key1() { "test_key_1", ], "022ff35c84bd4cf899707789cfe5db76ce4a650563e678c53e8b128cb4bf4c3763", + "603256c83731d211d638aacfbb82b1964eaabf257dbdcfcf8c2bce5d8c41a425", ); } #[cfg(feature = "secp256k1")] #[test] fn test_ecdsa_secp256k1_derivation_pocketic_dfx_test_key() { - test_ecdsa_derivation( + test_ecdsa_secp256k1_derivation( "dfx_test_key", "uzt4z-lp777-77774-qaabq-cai", &[ @@ -364,9 +452,101 @@ fn test_ecdsa_secp256k1_derivation_pocketic_dfx_test_key() { "dfx_test_key", ], "03f005cf69911ae75f622ce0a621ccddba1a30ea1f6c3d67dd56acbbddb88a9374", + "a0898d1325d632c5851045f32a70f9b635ac74d6eb42a0d4029e8bb5570f4b14", + ); +} + +#[cfg(feature = "secp256r1")] +#[test] +fn test_ecdsa_secp256r1_derivation_pocketic_key1() { + test_ecdsa_secp256r1_derivation( + "pocketic_key_1", + "uzt4z-lp777-77774-qaabq-cai", + &[ + "Test", + "Derivation", + "For", + "PocketIC", + "ECDSA", + "secp256r1", + "key_1", + ], + "02d3536db48ecef1eb8249f04553046902fbe7d979140530e5294452b94f0e38ad", + "5bda5d523723999fcac4a482118b98b23faef6a9a0968c8984393891ca10a11b", ); } +#[cfg(feature = "secp256r1")] +#[test] +fn test_ecdsa_secp256r1_derivation_pocketic_test_key1() { + test_ecdsa_secp256r1_derivation( + "pocketic_test_key_1", + "uzt4z-lp777-77774-qaabq-cai", + &[ + "Test", + "Derivation", + "For", + "PocketIC", + "ECDSA", + "secp256r1", + "test_key_1", + ], + "03080d5850ca18f277d7f39c3cfd37635b6527a73ba63b774d26d4cdf725e1ce39", + "2ceb3a125d9d9a936b6da671f8b185da23d295ee84855adbca671598c728ff75", + ); +} + +#[cfg(feature = "secp256r1")] +#[test] +fn test_ecdsa_secp256r1_derivation_pocketic_dfx_test_key() { + test_ecdsa_secp256r1_derivation( + "dfx_test_key", + "uzt4z-lp777-77774-qaabq-cai", + &[ + "Test", + "Derivation", + "For", + "PocketIC", + "ECDSA", + "secp256r1", + "dfx_test_key", + ], + "036cd2e52401b60ec8b86041f9c624438eb69ab271def9c9a439bd9fb49fa88ac7", + "3599e883d6781d34d0bf342c0ac973d67fd06e72a93a6fbad691da461d4076f2", + ); +} + +#[cfg(any(feature = "secp256k1", feature = "secp256r1"))] +fn test_ecdsa_derivation_rejects_unknown_key(curve: EcdsaCurve) { + use std::str::FromStr; + + let args = EcdsaPublicKeyArgs { + canister_id: Some(ic_pub_key::CanisterId::from_str("h5jwf-5iaaa-aaaan-qmvoa-cai").unwrap()), + derivation_path: vec![], + key_id: EcdsaKeyId { + curve, + name: "key_2".to_string(), + }, + }; + + assert_eq!( + derive_ecdsa_key(&args).unwrap_err(), + Error::UnknownKeyIdentifier + ); +} + +#[cfg(feature = "secp256k1")] +#[test] +fn test_ecdsa_secp256k1_derivation_rejects_unknown_key() { + test_ecdsa_derivation_rejects_unknown_key(EcdsaCurve::Secp256k1); +} + +#[cfg(feature = "secp256r1")] +#[test] +fn test_ecdsa_secp256r1_derivation_rejects_unknown_key() { + test_ecdsa_derivation_rejects_unknown_key(EcdsaCurve::Secp256r1); +} + #[test] fn test_vetkd_derivation_using_test_key_1() { // This test data was generated on mainnet using test_key_1