From 0171232511faff9f8d0a0fc88b8a8ba3cbe91555 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Tue, 28 Nov 2023 19:27:31 +0100 Subject: [PATCH 01/39] todo --- hierarchical_deterministic/Cargo.toml | 3 - .../src/derivation/derivation.rs | 6 +- .../hierarchical_deterministic_public_key.rs | 16 ++ .../derivation/mnemonic_with_passphrase.rs | 22 +-- .../src/derivation/mod.rs | 1 + .../src/keys/key_extensions.rs | 28 ---- hierarchical_deterministic/src/keys/mod.rs | 1 - hierarchical_deterministic/src/lib.rs | 1 - .../tests/slip10_vectors_test.rs | 10 +- profile/src/v100/entity/account/account.rs | 145 +++++++++++------- .../src/v100/entity/account/appearance_id.rs | 20 ++- profile/src/v100/entity/display_name.rs | 56 +++++++ .../entity_security_state.rs | 93 ++++++++++- .../unsecured_entity_control.rs | 66 +++++++- .../factor_instance/badge_virtual_source.rs | 5 + .../factor_instance/factor_instance.rs | 16 ++ .../factor_instance/factor_instance_badge.rs | 8 + .../src/v100/factors/factor_instance/mod.rs | 3 + profile/src/v100/factors/factor_source.rs | 1 - profile/src/v100/factors/factor_source_id.rs | 1 - .../factors/factor_source_id_from_hash.rs | 4 +- ...rarchical_deterministic_factor_instance.rs | 56 +++++++ profile/src/v100/factors/mod.rs | 1 + wallet_kit_common/Cargo.toml | 3 + wallet_kit_common/src/error.rs | 6 + .../src/types/keys/ed25519/mod.rs | 2 + .../src/types/keys/ed25519/private_key.rs | 73 +++++++++ .../src/types/keys/ed25519/public_key.rs | 29 ++++ wallet_kit_common/src/types/keys/mod.rs | 4 + .../src/types/keys/private_key.rs | 19 +++ .../src/types/keys/public_key.rs | 18 +++ .../src/types/keys/secp256k1/mod.rs | 2 + .../src/types/keys/secp256k1/private_key.rs | 74 +++++++++ .../src/types/keys/secp256k1/public_key.rs | 28 ++++ wallet_kit_common/src/types/mod.rs | 1 + 35 files changed, 706 insertions(+), 116 deletions(-) create mode 100644 hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs delete mode 100644 hierarchical_deterministic/src/keys/key_extensions.rs delete mode 100644 hierarchical_deterministic/src/keys/mod.rs create mode 100644 profile/src/v100/factors/factor_instance/badge_virtual_source.rs create mode 100644 profile/src/v100/factors/factor_instance/factor_instance.rs create mode 100644 profile/src/v100/factors/factor_instance/factor_instance_badge.rs create mode 100644 profile/src/v100/factors/factor_instance/mod.rs create mode 100644 wallet_kit_common/src/types/keys/ed25519/mod.rs create mode 100644 wallet_kit_common/src/types/keys/ed25519/private_key.rs create mode 100644 wallet_kit_common/src/types/keys/ed25519/public_key.rs create mode 100644 wallet_kit_common/src/types/keys/mod.rs create mode 100644 wallet_kit_common/src/types/keys/private_key.rs create mode 100644 wallet_kit_common/src/types/keys/public_key.rs create mode 100644 wallet_kit_common/src/types/keys/secp256k1/mod.rs create mode 100644 wallet_kit_common/src/types/keys/secp256k1/private_key.rs create mode 100644 wallet_kit_common/src/types/keys/secp256k1/public_key.rs diff --git a/hierarchical_deterministic/Cargo.toml b/hierarchical_deterministic/Cargo.toml index 82749852..6212e6f1 100644 --- a/hierarchical_deterministic/Cargo.toml +++ b/hierarchical_deterministic/Cargo.toml @@ -21,8 +21,5 @@ memoize = "0.4.1" radix-engine-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "038ddee8b0f57aa90e36375c69946c4eb634efeb", features = [ "serde", ] } -transaction = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "038ddee8b0f57aa90e36375c69946c4eb634efeb", features = [ - "serde", -] } bip32 = "0.5.1" # slip10 crate does not support `secp256k1`. hex = "0.4.3" diff --git a/hierarchical_deterministic/src/derivation/derivation.rs b/hierarchical_deterministic/src/derivation/derivation.rs index ca957c33..2f14ee3e 100644 --- a/hierarchical_deterministic/src/derivation/derivation.rs +++ b/hierarchical_deterministic/src/derivation/derivation.rs @@ -1,4 +1,4 @@ -use crate::bip32::hd_path::HDPath; +use crate::bip32::{hd_path::HDPath, hd_path_component::HDPathComponent}; use super::derivation_path_scheme::DerivationPathScheme; @@ -9,4 +9,8 @@ pub trait Derivation: Sized { self.hd_path().to_string() } fn scheme(&self) -> DerivationPathScheme; + + fn last_component(&self) -> &HDPathComponent { + self.hd_path().components().last().unwrap() + } } diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs new file mode 100644 index 00000000..5240f9b3 --- /dev/null +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs @@ -0,0 +1,16 @@ +use wallet_kit_common::types::keys::public_key::PublicKey; + +use crate::derivation::derivation_path::DerivationPath; + +/// The **source** of a virtual hierarchical deterministic badge, contains a +/// derivation path and public key, from which a private key is derived which +/// produces virtual badges (signatures). +/// +/// The `.device` `FactorSource` produces `FactorInstance`s with this kind if badge source. +pub struct HierarchicalDeterministicPublicKey { + /// The expected public key of the private key derived at `derivationPath` + pub publicKey: PublicKey, + + /// The HD derivation path for the key pair which produces virtual badges (signatures). + pub derivationPath: DerivationPath, +} diff --git a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs index d3abb6a8..dec56b9c 100644 --- a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs +++ b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs @@ -1,9 +1,12 @@ use itertools::Itertools; use serde::{Deserialize, Serialize}; -use transaction::signing::{ - ed25519::Ed25519PrivateKey, secp256k1::Secp256k1PrivateKey, PrivateKey, +use wallet_kit_common::{ + error::Error, + types::keys::{ + ed25519::private_key::Ed25519PrivateKey, private_key::PrivateKey, + secp256k1::private_key::Secp256k1PrivateKey, + }, }; -use wallet_kit_common::error::Error; use crate::{ bip32::hd_path::HDPath, @@ -110,7 +113,6 @@ mod tests { bip39::mnemonic::Mnemonic, bip44::bip44_like_path::BIP44LikePath, cap26::{cap26_path::paths::account_path::AccountPath, cap26_repr::CAP26Repr}, - keys::key_extensions::{private_key_hex, public_key_hex_from_private}, }; use wallet_kit_common::json::assert_eq_after_json_roundtrip; @@ -148,16 +150,16 @@ mod tests { "".to_string(), ); - let private_key: transaction::prelude::PrivateKey = + let private_key = mwp.derive_private_key(AccountPath::from_str("m/44H/1022H/12H/525H/1460H/0H").unwrap()); assert_eq!( "13e971fb16cb2c816d6b9f12176e9b8ab9af1831d006114d344d119ab2715506", - private_key_hex(&private_key) + private_key.hex() ); assert_eq!( "451152a1cef7be603205086d4ebac0a0b78fda2ff4684b9dea5ca9ef003d4e7d", - public_key_hex_from_private(&private_key) + private_key.public_key().hex() ); } @@ -172,17 +174,17 @@ mod tests { "".to_string(), ); - let private_key: transaction::prelude::PrivateKey = + let private_key = mwp.derive_private_key(BIP44LikePath::from_str("m/44H/1022H/0H/0/5H").unwrap()); assert_eq!( "111323d507d9d690836798e3ef2e5292cfd31092b75b9b59fa584ff593a3d7e4", - private_key_hex(&private_key) + private_key.hex() ); assert_eq!( "03e78cdb2e0b7ea6e55e121a58560ccf841a913d3a4a9b8349e0ef00c2102f48d8", - public_key_hex_from_private(&private_key) + private_key.public_key().hex() ); } diff --git a/hierarchical_deterministic/src/derivation/mod.rs b/hierarchical_deterministic/src/derivation/mod.rs index 2c931b0f..e3afc5b5 100644 --- a/hierarchical_deterministic/src/derivation/mod.rs +++ b/hierarchical_deterministic/src/derivation/mod.rs @@ -1,5 +1,6 @@ pub mod derivation; pub mod derivation_path; pub mod derivation_path_scheme; +pub mod hierarchical_deterministic_public_key; pub mod mnemonic_with_passphrase; pub mod slip10_curve; diff --git a/hierarchical_deterministic/src/keys/key_extensions.rs b/hierarchical_deterministic/src/keys/key_extensions.rs deleted file mode 100644 index d04bb093..00000000 --- a/hierarchical_deterministic/src/keys/key_extensions.rs +++ /dev/null @@ -1,28 +0,0 @@ -use radix_engine_common::crypto::PublicKey; -use transaction::signing::PrivateKey; - -pub fn private_key_bytes(private_key: &PrivateKey) -> Vec { - match private_key { - PrivateKey::Ed25519(key) => key.to_bytes(), - PrivateKey::Secp256k1(key) => key.to_bytes(), - } -} - -pub fn public_key_bytes(public_key: &PublicKey) -> Vec { - match public_key { - PublicKey::Ed25519(key) => key.to_vec(), - PublicKey::Secp256k1(key) => key.to_vec(), - } -} - -pub fn private_key_hex(private_key: &PrivateKey) -> String { - hex::encode(private_key_bytes(private_key)) -} - -pub fn public_key_hex(public_key: &PublicKey) -> String { - hex::encode(public_key_bytes(public_key)) -} - -pub fn public_key_hex_from_private(private_key: &PrivateKey) -> String { - public_key_hex(&private_key.public_key()) -} diff --git a/hierarchical_deterministic/src/keys/mod.rs b/hierarchical_deterministic/src/keys/mod.rs deleted file mode 100644 index 90db8841..00000000 --- a/hierarchical_deterministic/src/keys/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod key_extensions; diff --git a/hierarchical_deterministic/src/lib.rs b/hierarchical_deterministic/src/lib.rs index a6b4e6e7..835e91a5 100644 --- a/hierarchical_deterministic/src/lib.rs +++ b/hierarchical_deterministic/src/lib.rs @@ -4,4 +4,3 @@ pub mod bip44; pub mod cap26; pub mod derivation; pub mod hdpath_error; -pub mod keys; diff --git a/hierarchical_deterministic/tests/slip10_vectors_test.rs b/hierarchical_deterministic/tests/slip10_vectors_test.rs index 592e9fd6..efc8f18d 100644 --- a/hierarchical_deterministic/tests/slip10_vectors_test.rs +++ b/hierarchical_deterministic/tests/slip10_vectors_test.rs @@ -1,8 +1,6 @@ use hierarchical_deterministic::{ - bip39::mnemonic::Mnemonic, - bip44::bip44_like_path::BIP44LikePath, + bip39::mnemonic::Mnemonic, bip44::bip44_like_path::BIP44LikePath, derivation::mnemonic_with_passphrase::MnemonicWithPassphrase, - keys::key_extensions::{private_key_hex, public_key_hex_from_private}, }; #[test] @@ -15,16 +13,16 @@ fn derive_a_secp256k1_key_with_bip44_olympia() { "".to_string(), ); - let private_key: transaction::prelude::PrivateKey = + let private_key = mwp.derive_private_key(BIP44LikePath::from_str("m/44H/1022H/0H/0/5H").unwrap()); assert_eq!( "111323d507d9d690836798e3ef2e5292cfd31092b75b9b59fa584ff593a3d7e4", - private_key_hex(&private_key) + private_key.hex() ); assert_eq!( "03e78cdb2e0b7ea6e55e121a58560ccf841a913d3a4a9b8349e0ef00c2102f48d8", - public_key_hex_from_private(&private_key) + private_key.public_key().hex() ); } diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index 71b32f3e..41fca2be 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -1,4 +1,6 @@ -use hierarchical_deterministic::derivation::derivation_path::DerivationPath; +use hierarchical_deterministic::derivation::{ + derivation::Derivation, derivation_path::DerivationPath, +}; use radix_engine_common::crypto::PublicKey; use serde::{Deserialize, Serialize}; use std::{cell::RefCell, cmp::Ordering, fmt::Display}; @@ -95,25 +97,12 @@ impl Account { flags: RefCell::new(EntityFlags::default()), on_ledger_settings: RefCell::new(OnLedgerSettings::default()), security_state: EntitySecurityState::Unsecured(UnsecuredEntityControl::new( - 0, HierarchicalDeterministicFactorInstance::placeholder(), )), } } } -impl HierarchicalDeterministicFactorInstance { - pub fn placeholder() -> Self { - let private_key = Ed25519PrivateKey::from_u64(1337).unwrap(); - let public_key = private_key.public_key(); - Self::new( - FactorSourceIDFromHash::placeholder(), - PublicKey::Ed25519(public_key), - DerivationPath::placeholder(), - ) - } -} - // Getters impl Account { /// Returns this accounts `display_name` as **a clone**. @@ -165,9 +154,11 @@ impl Account { impl Ord for Account { fn cmp(&self, other: &Self) -> Ordering { match (&self.security_state, &other.security_state) { - (EntitySecurityState::Unsecured(l), EntitySecurityState::Unsecured(r)) => { - l.entity_index.cmp(&r.entity_index) - } + (EntitySecurityState::Unsecured(l), EntitySecurityState::Unsecured(r)) => l + .transaction_signing + .derivation_path + .last_component() + .cmp(r.transaction_signing.derivation_path.last_component()), } } } @@ -247,6 +238,7 @@ mod tests { use std::{cell::RefCell, collections::BTreeSet}; use hierarchical_deterministic::bip32::hd_path_component::HDPathValue; + use wallet_kit_common::json::assert_eq_after_json_roundtrip; use crate::v100::{ address::account_address::AccountAddress, @@ -398,44 +390,91 @@ mod tests { ); } - #[test] - fn compare() { - let make = |index: HDPathValue| -> Account { - let address: AccountAddress = - "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" - .try_into() - .unwrap(); - - let account = Account { - address: address.clone(), - network_id: address.network_id, - display_name: RefCell::new(DisplayName::new("Test").unwrap()), - appearance_id: RefCell::new(AppearanceID::default()), - flags: RefCell::new(EntityFlags::default()), - on_ledger_settings: RefCell::new(OnLedgerSettings::default()), - security_state: EntitySecurityState::Unsecured(UnsecuredEntityControl::new( - index, - HierarchicalDeterministicFactorInstance::placeholder(), - )), - }; - account - }; - let a = make(0); - let b = make(1); - assert!(a < b); - } + // #[test] + // fn compare() { + // let make = |index: HDPathValue| -> Account { + // let address: AccountAddress = + // "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" + // .try_into() + // .unwrap(); + + // let account = Account { + // address: address.clone(), + // network_id: address.network_id, + // display_name: RefCell::new(DisplayName::new("Test").unwrap()), + // appearance_id: RefCell::new(AppearanceID::default()), + // flags: RefCell::new(EntityFlags::default()), + // on_ledger_settings: RefCell::new(OnLedgerSettings::default()), + // security_state: EntitySecurityState::Unsecured(UnsecuredEntityControl::new( + // index, + // HierarchicalDeterministicFactorInstance::placeholder(), + // )), + // }; + // account + // }; + // let a = make(0); + // let b = make(1); + // assert!(a < b); + // } #[test] fn json_roundtrip() { - // let model = assert_eq_after_json_roundtrip( - // &model, - // r#" - // { - // "id": "66f07ca2-a9d9-49e5-8152-77aca3d1dd74", - // "date": "2023-09-11T16:05:56", - // "description": "iPhone" - // } - // "#, - // ); + let model = Account::with_values( + "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" + .try_into() + .unwrap(), + "Zaba 0".try_into().unwrap(), + 0.try_into().unwrap(), + ); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "securityState": { + "unsecuredEntityControl": { + "transactionSigning": { + "badge": { + "virtualSource": { + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/14H/525H/1460H/0H" + } + }, + "discriminator": "hierarchicalDeterministicPublicKey" + }, + "discriminator": "virtualSource" + }, + "factorSourceID": { + "fromHash": { + "kind": "device", + "body": "c9e67a9028fb3150304c77992710c35c8e479d4fa59f7c45a96ce17f6fdf1d2c" + }, + "discriminator": "fromHash" + } + } + }, + "discriminator": "unsecured" + }, + "networkID": 14, + "appearanceID": 0, + "flags": [], + "displayName": "Zaba 0", + "onLedgerSettings": { + "thirdPartyDeposits": { + "depositRule": "acceptAll", + "assetsExceptionList": [], + "depositorsAllowList": [] + } + }, + "flags": ["deletedByUser"], + "address": "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" + } + "#, + ); } } diff --git a/profile/src/v100/entity/account/appearance_id.rs b/profile/src/v100/entity/account/appearance_id.rs index f47f6974..0db767f1 100644 --- a/profile/src/v100/entity/account/appearance_id.rs +++ b/profile/src/v100/entity/account/appearance_id.rs @@ -1,4 +1,5 @@ use nutype::nutype; +use wallet_kit_common::error::Error; #[nutype( validate(less_or_equal = 11), @@ -23,10 +24,21 @@ impl Default for AppearanceID { } } +impl TryFrom for AppearanceID { + type Error = wallet_kit_common::error::Error; + + fn try_from(value: u8) -> Result { + AppearanceID::new(value).map_err(|_| Error::InvalidAppearanceID) + } +} + #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::json::{assert_json_value_eq_after_roundtrip, assert_json_value_fails}; + use wallet_kit_common::{ + error::Error, + json::{assert_json_value_eq_after_roundtrip, assert_json_value_fails}, + }; use crate::v100::entity::account::appearance_id::{AppearanceID, AppearanceIDError}; @@ -48,6 +60,12 @@ mod tests { ); } + #[test] + fn try_from() { + assert_eq!(AppearanceID::try_from(250), Err(Error::InvalidAppearanceID)); + assert_eq!(AppearanceID::try_from(1), Ok(AppearanceID::new(1).unwrap())); + } + #[test] fn json() { assert_json_value_eq_after_roundtrip(&AppearanceID::new(3).unwrap(), json!(3)); diff --git a/profile/src/v100/entity/display_name.rs b/profile/src/v100/entity/display_name.rs index 666a6c99..ae7da9fc 100644 --- a/profile/src/v100/entity/display_name.rs +++ b/profile/src/v100/entity/display_name.rs @@ -1,4 +1,5 @@ use nutype::nutype; +use wallet_kit_common::error::Error; #[nutype( sanitize(trim), @@ -23,3 +24,58 @@ impl Default for DisplayName { Self::new("Unnamed").expect("Default display name") } } + +impl TryFrom<&str> for DisplayName { + type Error = wallet_kit_common::error::Error; + + fn try_from(value: &str) -> Result { + DisplayName::new(value.to_string()).map_err(|_| Error::InvalidDisplayName) + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use wallet_kit_common::{ + error::Error, + json::{ + assert_json_roundtrip, assert_json_value_eq_after_roundtrip, + assert_json_value_ne_after_roundtrip, + }, + }; + + use super::DisplayName; + + #[test] + fn invalid() { + assert_eq!( + DisplayName::try_from("this is a much much too long display name"), + Err(Error::InvalidDisplayName) + ); + } + + #[test] + fn valid_try_from() { + assert_eq!( + DisplayName::try_from("Main"), + Ok(DisplayName::new("Main").unwrap()) + ); + } + + #[test] + fn inner() { + assert_eq!( + DisplayName::new("Main account").unwrap().into_inner(), + "Main account" + ); + } + + #[test] + fn json_roundtrip() { + let a: DisplayName = "Cool persona".try_into().unwrap(); + + assert_json_value_eq_after_roundtrip(&a, json!("Cool persona")); + assert_json_roundtrip(&a); + assert_json_value_ne_after_roundtrip(&a, json!("Main account")); + } +} diff --git a/profile/src/v100/entity_security_state/entity_security_state.rs b/profile/src/v100/entity_security_state/entity_security_state.rs index 8b95e3a9..9dd92207 100644 --- a/profile/src/v100/entity_security_state/entity_security_state.rs +++ b/profile/src/v100/entity_security_state/entity_security_state.rs @@ -1,8 +1,99 @@ -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use super::unsecured_entity_control::UnsecuredEntityControl; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(remote = "Self")] pub enum EntitySecurityState { + #[serde(rename = "unsecuredEntityControl")] Unsecured(UnsecuredEntityControl), } + +impl<'de> Deserialize<'de> for EntitySecurityState { + fn deserialize>(deserializer: D) -> Result { + // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 + #[derive(Deserialize, Serialize)] + struct Wrapper { + #[serde(rename = "discriminator")] + _ignore: String, + #[serde(flatten, with = "EntitySecurityState")] + inner: EntitySecurityState, + } + Wrapper::deserialize(deserializer).map(|w| w.inner) + } +} + +impl Serialize for EntitySecurityState { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("EntitySecurityState", 2)?; + match self { + EntitySecurityState::Unsecured(control) => { + let discriminant = "unsecuredEntityControl"; + state.serialize_field("discriminator", discriminant)?; + state.serialize_field(discriminant, control)?; + } + } + state.end() + } +} + +impl EntitySecurityState { + pub fn placeholder() -> Self { + Self::Unsecured(UnsecuredEntityControl::placeholder()) + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::EntitySecurityState; + // #[test] + // fn json_roundtrip() { + // // let model = Account::with_values( + // // "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" + // // .try_into() + // // .unwrap(), + // // "Zaba 0".try_into().unwrap(), + // // 0.try_into().unwrap(), + // // ); + // assert_eq_after_json_roundtrip( + // &model, + // r#" + // { + // "unsecuredEntityControl": { + // "transactionSigning": { + // "badge": { + // "virtualSource": { + // "hierarchicalDeterministicPublicKey": { + // "publicKey": { + // "curve": "curve25519", + // "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + // }, + // "derivationPath": { + // "scheme": "cap26", + // "path": "m/44H/1022H/14H/525H/1460H/0H" + // } + // }, + // "discriminator": "hierarchicalDeterministicPublicKey" + // }, + // "discriminator": "virtualSource" + // }, + // "factorSourceID": { + // "fromHash": { + // "kind": "device", + // "body": "c9e67a9028fb3150304c77992710c35c8e479d4fa59f7c45a96ce17f6fdf1d2c" + // }, + // "discriminator": "fromHash" + // } + // } + // }, + // "discriminator": "unsecured" + // } + // "#, + // ); + // } +} diff --git a/profile/src/v100/entity_security_state/unsecured_entity_control.rs b/profile/src/v100/entity_security_state/unsecured_entity_control.rs index 175e63f9..e6632534 100644 --- a/profile/src/v100/entity_security_state/unsecured_entity_control.rs +++ b/profile/src/v100/entity_security_state/unsecured_entity_control.rs @@ -1,4 +1,3 @@ -use hierarchical_deterministic::bip32::hd_path_component::HDPathValue; use serde::{Deserialize, Serialize}; use crate::v100::factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance; @@ -10,9 +9,6 @@ use crate::v100::factors::hierarchical_deterministic_factor_instance::Hierarchic #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct UnsecuredEntityControl { - /// The last path component of the SLIP10 derivation path. - pub entity_index: HDPathValue, - // /// The factor instance which was used to create this unsecured entity, which // /// also controls this entity and is used for signing transactions. pub transaction_signing: HierarchicalDeterministicFactorInstance, @@ -22,14 +18,70 @@ pub struct UnsecuredEntityControl { } impl UnsecuredEntityControl { - pub fn new( - entity_index: HDPathValue, + pub fn with_authentication_signing( transaction_signing: HierarchicalDeterministicFactorInstance, + authentication_signing: HierarchicalDeterministicFactorInstance, ) -> Self { Self { - entity_index, + transaction_signing, + authentication_signing: Some(authentication_signing), + } + } + pub fn new(transaction_signing: HierarchicalDeterministicFactorInstance) -> Self { + Self { transaction_signing, authentication_signing: Option::None, } } } + +impl UnsecuredEntityControl { + pub fn placeholder() -> Self { + Self::new(HierarchicalDeterministicFactorInstance::placeholder()) + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use crate::v100::factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance; + + use super::UnsecuredEntityControl; + #[test] + fn json_roundtrip() { + let model = UnsecuredEntityControl::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "transactionSigning": { + "badge": { + "virtualSource": { + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/14H/525H/1460H/0H" + } + }, + "discriminator": "hierarchicalDeterministicPublicKey" + }, + "discriminator": "virtualSource" + }, + "factorSourceID": { + "fromHash": { + "kind": "device", + "body": "c9e67a9028fb3150304c77992710c35c8e479d4fa59f7c45a96ce17f6fdf1d2c" + }, + "discriminator": "fromHash" + } + } + } + "#, + ); + } +} diff --git a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs new file mode 100644 index 00000000..55cb6c25 --- /dev/null +++ b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs @@ -0,0 +1,5 @@ +use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; + +pub enum FactorInstanceBadgeVirtualSource { + HierarchicalDeterministic(HierarchicalDeterministicPublicKey), +} diff --git a/profile/src/v100/factors/factor_instance/factor_instance.rs b/profile/src/v100/factors/factor_instance/factor_instance.rs new file mode 100644 index 00000000..664fdb38 --- /dev/null +++ b/profile/src/v100/factors/factor_instance/factor_instance.rs @@ -0,0 +1,16 @@ +use crate::v100::factors::factor_source_id::FactorSourceID; + +use super::factor_instance_badge::FactorInstanceBadge; + +pub struct FactorInstance { + /// The ID of the `FactorSource` that was used to produce this + /// factor instance. We will lookup the `FactorSource` in the + /// `Profile` and can present user with instruction to re-access + /// this factor source in order control the `badge`. + pub factor_source_id: FactorSourceID, + + /// Either a "physical" badge (NFT) or some source for recreation of a producer + /// of a virtual badge (signature), e.g. a HD derivation path, from which a private key + /// is derived which produces virtual badges (signatures). + pub badge: FactorInstanceBadge, +} diff --git a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs new file mode 100644 index 00000000..88775c49 --- /dev/null +++ b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs @@ -0,0 +1,8 @@ +use super::badge_virtual_source::FactorInstanceBadgeVirtualSource; + +/// Either a "physical" badge (NFT) or some source for recreation of a producer +/// of a virtual badge (signature), e.g. a HD derivation path, from which a private key +/// is derived which produces virtual badges (signatures). +pub enum FactorInstanceBadge { + Virtual(FactorInstanceBadgeVirtualSource), +} diff --git a/profile/src/v100/factors/factor_instance/mod.rs b/profile/src/v100/factors/factor_instance/mod.rs new file mode 100644 index 00000000..786029e1 --- /dev/null +++ b/profile/src/v100/factors/factor_instance/mod.rs @@ -0,0 +1,3 @@ +pub mod badge_virtual_source; +pub mod factor_instance; +pub mod factor_instance_badge; diff --git a/profile/src/v100/factors/factor_source.rs b/profile/src/v100/factors/factor_source.rs index d41e0670..dcfb70cf 100644 --- a/profile/src/v100/factors/factor_source.rs +++ b/profile/src/v100/factors/factor_source.rs @@ -28,7 +28,6 @@ impl Serialize for FactorSource { where S: Serializer, { - // 3 is the number of fields in the struct. let mut state = serializer.serialize_struct("FactorSource", 2)?; match self { FactorSource::Device(device) => { diff --git a/profile/src/v100/factors/factor_source_id.rs b/profile/src/v100/factors/factor_source_id.rs index 1a3abc32..dea759d0 100644 --- a/profile/src/v100/factors/factor_source_id.rs +++ b/profile/src/v100/factors/factor_source_id.rs @@ -37,7 +37,6 @@ impl Serialize for FactorSourceID { where S: Serializer, { - // 3 is the number of fields in the struct. let mut state = serializer.serialize_struct("FactorSourceID", 2)?; match self { FactorSourceID::Hash(from_hash) => { diff --git a/profile/src/v100/factors/factor_source_id_from_hash.rs b/profile/src/v100/factors/factor_source_id_from_hash.rs index fe43aab7..b4b42007 100644 --- a/profile/src/v100/factors/factor_source_id_from_hash.rs +++ b/profile/src/v100/factors/factor_source_id_from_hash.rs @@ -1,7 +1,6 @@ use hierarchical_deterministic::{ cap26::cap26_path::paths::getid_path::GetIDPath, derivation::mnemonic_with_passphrase::MnemonicWithPassphrase, - keys::key_extensions::public_key_bytes, }; use radix_engine_common::crypto::{blake2b_256_hash, Hash}; use serde::{Deserialize, Serialize}; @@ -32,7 +31,8 @@ impl FactorSourceIDFromHash { mnemonic_with_passphrase: MnemonicWithPassphrase, ) -> Self { let private_key = mnemonic_with_passphrase.derive_private_key(GetIDPath::default()); - let public_key_bytes = public_key_bytes(&private_key.public_key()); + // let public_key_bytes = public_key_bytes(&private_key.public_key()); + let public_key_bytes = private_key.public_key().to_bytes(); let hash: Hash = blake2b_256_hash(public_key_bytes); let body = Hex32Bytes::from(hash); Self::new(factor_source_kind, body) diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 06908df5..2e3390a0 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -1,12 +1,14 @@ use hierarchical_deterministic::derivation::derivation_path::DerivationPath; use radix_engine_common::crypto::PublicKey; use serde::{Deserialize, Serialize}; +use transaction::signing::ed25519::Ed25519PrivateKey; use super::factor_source_id_from_hash::FactorSourceIDFromHash; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct HierarchicalDeterministicFactorInstance { + #[serde(rename = "factorSourceID")] pub factor_source_id: FactorSourceIDFromHash, pub public_key: PublicKey, pub derivation_path: DerivationPath, @@ -25,3 +27,57 @@ impl HierarchicalDeterministicFactorInstance { } } } + +impl HierarchicalDeterministicFactorInstance { + pub fn placeholder() -> Self { + let private_key = Ed25519PrivateKey::from_u64(1337).unwrap(); + let public_key = private_key.public_key(); + Self::new( + FactorSourceIDFromHash::placeholder(), + PublicKey::Ed25519(public_key), + DerivationPath::placeholder(), + ) + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::HierarchicalDeterministicFactorInstance; + + #[test] + fn json_roundtrip() { + let model = HierarchicalDeterministicFactorInstance::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "badge": { + "virtualSource": { + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/14H/525H/1460H/0H" + } + }, + "discriminator": "hierarchicalDeterministicPublicKey" + }, + "discriminator": "virtualSource" + }, + "factorSourceID": { + "fromHash": { + "kind": "device", + "body": "c9e67a9028fb3150304c77992710c35c8e479d4fa59f7c45a96ce17f6fdf1d2c" + }, + "discriminator": "fromHash" + } + } + "#, + ); + } +} diff --git a/profile/src/v100/factors/mod.rs b/profile/src/v100/factors/mod.rs index 4d3baedd..245d3d71 100644 --- a/profile/src/v100/factors/mod.rs +++ b/profile/src/v100/factors/mod.rs @@ -1,3 +1,4 @@ +pub mod factor_instance; pub mod factor_source; pub mod factor_source_common; pub mod factor_source_crypto_parameters; diff --git a/wallet_kit_common/Cargo.toml b/wallet_kit_common/Cargo.toml index 396b804b..8afb5b00 100644 --- a/wallet_kit_common/Cargo.toml +++ b/wallet_kit_common/Cargo.toml @@ -19,3 +19,6 @@ radix-engine-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", re ] } enum-iterator = "1.4.1" hex = "0.4.3" +transaction = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "038ddee8b0f57aa90e36375c69946c4eb634efeb", features = [ + "serde", +] } diff --git a/wallet_kit_common/src/error.rs b/wallet_kit_common/src/error.rs index 99ed1798..2e49e047 100644 --- a/wallet_kit_common/src/error.rs +++ b/wallet_kit_common/src/error.rs @@ -2,6 +2,12 @@ use thiserror::Error; #[derive(Debug, Error, PartialEq)] pub enum Error { + #[error("Appearance id not recognized.")] + InvalidAppearanceID, + + #[error("String not not a valid display name, did not pass validation.")] + InvalidDisplayName, + #[error("String not hex")] StringNotHex, diff --git a/wallet_kit_common/src/types/keys/ed25519/mod.rs b/wallet_kit_common/src/types/keys/ed25519/mod.rs new file mode 100644 index 00000000..a37c90a7 --- /dev/null +++ b/wallet_kit_common/src/types/keys/ed25519/mod.rs @@ -0,0 +1,2 @@ +pub mod private_key; +pub mod public_key; diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs new file mode 100644 index 00000000..cec7df4e --- /dev/null +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -0,0 +1,73 @@ +use radix_engine_common::crypto::IsHash; +use transaction::signing::ed25519::{ + Ed25519PrivateKey as EngineEd25519PrivateKey, Ed25519Signature, +}; + +use super::public_key::Ed25519PublicKey; + +pub struct Ed25519PrivateKey(EngineEd25519PrivateKey); + +impl Ed25519PrivateKey { + pub const LENGTH: usize = 32; + + pub fn public_key(&self) -> Ed25519PublicKey { + // Ed25519PublicKey(PublicKey::from(&self.0).to_bytes()) + todo!() + } + + pub fn sign(&self, msg_hash: &impl IsHash) -> Ed25519Signature { + todo!(); + // let keypair = Keypair { + // secret: SecretKey::from_bytes(self.0.as_bytes()).expect("From a valid key bytes"), + // public: PublicKey::from(&self.0), + // }; + + // // SHA512 is used here + + // Ed25519Signature(keypair.sign(msg_hash.as_ref()).to_bytes()) + } + + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + pub fn from_bytes(slice: &[u8]) -> Result { + // if slice.len() != Ed25519PrivateKey::LENGTH { + // return Err(()); + // } + // Ok(Self(SecretKey::from_bytes(slice).map_err(|_| ())?))t + todo!(); + } + + pub fn from_u64(n: u64) -> Result { + todo!(); + // let mut bytes = [0u8; Ed25519PrivateKey::LENGTH]; + // (&mut bytes[Ed25519PrivateKey::LENGTH - 8..Ed25519PrivateKey::LENGTH]) + // .copy_from_slice(&n.to_be_bytes()); + + // Ok(Self(SecretKey::from_bytes(&bytes).map_err(|_| ())?)) + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::validation::verify_ed25519; +// use radix_engine_interface::crypto::hash; +// use sbor::rust::str::FromStr; + +// #[test] +// fn sign_and_verify() { +// let test_sk = "0000000000000000000000000000000000000000000000000000000000000001"; +// let test_pk = "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29"; +// let test_message_hash = hash("Test"); +// let test_signature = "cf0ca64435609b85ab170da339d415bbac87d678dfd505969be20adc6b5971f4ee4b4620c602bcbc34fd347596546675099d696265f4a42a16df343da1af980e"; +// let sk = Ed25519PrivateKey::from_bytes(&hex::decode(test_sk).unwrap()).unwrap(); +// let pk = Ed25519PublicKey::from_str(test_pk).unwrap(); +// let sig = Ed25519Signature::from_str(test_signature).unwrap(); + +// assert_eq!(sk.public_key(), pk); +// assert_eq!(sk.sign(&test_message_hash), sig); +// assert!(verify_ed25519(&test_message_hash, &pk, &sig)); +// } +// } diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs new file mode 100644 index 00000000..44920d56 --- /dev/null +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -0,0 +1,29 @@ +use crate::error::Error; +use radix_engine_common::crypto::Ed25519PublicKey as EngineEd25519PublicKey; + +pub struct Ed25519PublicKey(EngineEd25519PublicKey); + +impl Ed25519PublicKey { + pub const LENGTH: usize = 32; + + pub fn to_bytes(&self) -> Vec { + self.0.to_vec() + } + + // pub fn to_hash(&self) -> Ed25519PublicKeyHash { + // Ed25519PublicKeyHash::new_from_public_key(self) + // } +} + +impl TryFrom<&[u8]> for Ed25519PublicKey { + type Error = crate::error::Error; + + fn try_from(slice: &[u8]) -> Result { + // if slice.len() != Ed25519PublicKey::LENGTH { + // return Err(ParseEd25519PublicKeyError::InvalidLength(slice.len())); + // } + + // Ok(Ed25519PublicKey(copy_u8_array(slice))) + todo!(); + } +} diff --git a/wallet_kit_common/src/types/keys/mod.rs b/wallet_kit_common/src/types/keys/mod.rs new file mode 100644 index 00000000..de0c29c4 --- /dev/null +++ b/wallet_kit_common/src/types/keys/mod.rs @@ -0,0 +1,4 @@ +pub mod ed25519; +pub mod private_key; +pub mod public_key; +pub mod secp256k1; diff --git a/wallet_kit_common/src/types/keys/private_key.rs b/wallet_kit_common/src/types/keys/private_key.rs new file mode 100644 index 00000000..3833e01e --- /dev/null +++ b/wallet_kit_common/src/types/keys/private_key.rs @@ -0,0 +1,19 @@ +use super::{ + ed25519::private_key::Ed25519PrivateKey, public_key::PublicKey, + secp256k1::private_key::Secp256k1PrivateKey, +}; + +pub enum PrivateKey { + Ed25519(Ed25519PrivateKey), + Secp256k1(Secp256k1PrivateKey), +} + +impl PrivateKey { + pub fn public_key(&self) -> PublicKey { + todo!(); + } + + pub fn hex(&self) -> String { + todo!(); + } +} diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs new file mode 100644 index 00000000..652fc351 --- /dev/null +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -0,0 +1,18 @@ +use super::{ed25519::public_key::Ed25519PublicKey, secp256k1::public_key::Secp256k1PublicKey}; +pub enum PublicKey { + Ed25519(Ed25519PublicKey), + Secp256k1(Secp256k1PublicKey), +} + +impl PublicKey { + pub fn hex(&self) -> String { + todo!(); + } + + pub fn to_bytes(&self) -> Vec { + match self { + PublicKey::Ed25519(key) => key.to_bytes(), + PublicKey::Secp256k1(key) => key.to_bytes(), + } + } +} diff --git a/wallet_kit_common/src/types/keys/secp256k1/mod.rs b/wallet_kit_common/src/types/keys/secp256k1/mod.rs new file mode 100644 index 00000000..a37c90a7 --- /dev/null +++ b/wallet_kit_common/src/types/keys/secp256k1/mod.rs @@ -0,0 +1,2 @@ +pub mod private_key; +pub mod public_key; diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs new file mode 100644 index 00000000..b1a2cb4b --- /dev/null +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -0,0 +1,74 @@ +use radix_engine_common::crypto::IsHash; +use transaction::signing::secp256k1::{ + Secp256k1PrivateKey as EngineSecp256k1PrivateKey, Secp256k1Signature, +}; + +use super::public_key::Secp256k1PublicKey; + +pub struct Secp256k1PrivateKey(EngineSecp256k1PrivateKey); + +impl Secp256k1PrivateKey { + pub const LENGTH: usize = 32; + + pub fn public_key(&self) -> Secp256k1PublicKey { + // Secp256k1PublicKey(PublicKey::from_secret_key_global(&self.0).serialize()) + todo!(); + } + + pub fn sign(&self, msg_hash: &impl IsHash) -> Secp256k1Signature { + // let m = Message::from_slice(msg_hash.as_ref()).expect("Hash is always a valid message"); + // let signature = SECP256K1.sign_ecdsa_recoverable(&m, &self.0); + // let (recovery_id, signature_data) = signature.serialize_compact(); + + // let mut buf = [0u8; 65]; + // buf[0] = recovery_id.to_i32() as u8; + // buf[1..].copy_from_slice(&signature_data); + // Secp256k1Signature(buf) + todo!(); + } + + pub fn to_bytes(&self) -> Vec { + // self.0.secret_bytes().to_vec() + todo!(); + } + + pub fn from_bytes(slice: &[u8]) -> Result { + // if slice.len() != Secp256k1PrivateKey::LENGTH { + // return Err(()); + // } + // Ok(Self(SecretKey::from_slice(slice).map_err(|_| ())?)) + todo!(); + } + + pub fn from_u64(n: u64) -> Result { + // let mut bytes = [0u8; Secp256k1PrivateKey::LENGTH]; + // (&mut bytes[Secp256k1PrivateKey::LENGTH - 8..Secp256k1PrivateKey::LENGTH]) + // .copy_from_slice(&n.to_be_bytes()); + + // Ok(Self(SecretKey::from_slice(&bytes).map_err(|_| ())?)) + todo!(); + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::validation::verify_secp256k1; +// use radix_engine_interface::crypto::hash; +// use sbor::rust::str::FromStr; + +// #[test] +// fn sign_and_verify() { +// let test_sk = "0000000000000000000000000000000000000000000000000000000000000001"; +// let test_pk = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"; +// let test_message_hash = hash("Test"); +// let test_signature = "00eb8dcd5bb841430dd0a6f45565a1b8bdb4a204eb868832cd006f963a89a662813ab844a542fcdbfda4086a83fbbde516214113051b9c8e42a206c98d564d7122"; +// let sk = Secp256k1PrivateKey::from_bytes(&hex::decode(test_sk).unwrap()).unwrap(); +// let pk = Secp256k1PublicKey::from_str(test_pk).unwrap(); +// let sig = Secp256k1Signature::from_str(test_signature).unwrap(); + +// assert_eq!(sk.public_key(), pk); +// assert_eq!(sk.sign(&test_message_hash), sig); +// assert!(verify_secp256k1(&test_message_hash, &pk, &sig)); +// } +// } diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs new file mode 100644 index 00000000..27c2aa94 --- /dev/null +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -0,0 +1,28 @@ +use radix_engine_common::crypto::Secp256k1PublicKey as EngineSecp256k1PublicKey; + +pub struct Secp256k1PublicKey(EngineSecp256k1PublicKey); + +impl Secp256k1PublicKey { + pub const LENGTH: usize = 33; + + pub fn to_bytes(&self) -> Vec { + self.0.to_vec() + } + + // pub fn to_hash(&self) -> Secp256k1PublicKeyHash { + // Secp256k1PublicKeyHash::new_from_public_key(self) + // } +} + +impl TryFrom<&[u8]> for Secp256k1PublicKey { + type Error = crate::error::Error; + + fn try_from(slice: &[u8]) -> Result { + // if slice.len() != Secp256k1PublicKey::LENGTH { + // return Err(ParseSecp256k1PublicKeyError::InvalidLength(slice.len())); + // } + + // Ok(Secp256k1PublicKey(copy_u8_array(slice))) + todo!(); + } +} diff --git a/wallet_kit_common/src/types/mod.rs b/wallet_kit_common/src/types/mod.rs index ffb4b228..a672e5b0 100644 --- a/wallet_kit_common/src/types/mod.rs +++ b/wallet_kit_common/src/types/mod.rs @@ -1 +1,2 @@ pub mod hex_32bytes; +pub mod keys; From 32696fd68840821786624c906bc9d7491298cffc Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Tue, 28 Nov 2023 20:04:17 +0100 Subject: [PATCH 02/39] keys --- .../derivation/mnemonic_with_passphrase.rs | 8 ++--- .../tests/slip10_vectors_test.rs | 4 +-- profile/src/v100/entity/account/account.rs | 2 +- .../unsecured_entity_control.rs | 2 +- ...rarchical_deterministic_factor_instance.rs | 2 +- wallet_kit_common/src/error.rs | 18 +++++++++++ .../src/types/keys/ed25519/private_key.rs | 28 ++++++++--------- .../src/types/keys/ed25519/public_key.rs | 17 ++++++---- .../src/types/keys/private_key.rs | 12 +++++-- .../src/types/keys/public_key.rs | 7 +++-- .../src/types/keys/secp256k1/private_key.rs | 31 +++++++++---------- .../src/types/keys/secp256k1/public_key.rs | 19 ++++++++---- 12 files changed, 94 insertions(+), 56 deletions(-) diff --git a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs index dec56b9c..3b295834 100644 --- a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs +++ b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs @@ -155,11 +155,11 @@ mod tests { assert_eq!( "13e971fb16cb2c816d6b9f12176e9b8ab9af1831d006114d344d119ab2715506", - private_key.hex() + private_key.to_hex() ); assert_eq!( "451152a1cef7be603205086d4ebac0a0b78fda2ff4684b9dea5ca9ef003d4e7d", - private_key.public_key().hex() + private_key.public_key().to_hex() ); } @@ -179,12 +179,12 @@ mod tests { assert_eq!( "111323d507d9d690836798e3ef2e5292cfd31092b75b9b59fa584ff593a3d7e4", - private_key.hex() + private_key.to_hex() ); assert_eq!( "03e78cdb2e0b7ea6e55e121a58560ccf841a913d3a4a9b8349e0ef00c2102f48d8", - private_key.public_key().hex() + private_key.public_key().to_hex() ); } diff --git a/hierarchical_deterministic/tests/slip10_vectors_test.rs b/hierarchical_deterministic/tests/slip10_vectors_test.rs index efc8f18d..12796645 100644 --- a/hierarchical_deterministic/tests/slip10_vectors_test.rs +++ b/hierarchical_deterministic/tests/slip10_vectors_test.rs @@ -18,11 +18,11 @@ fn derive_a_secp256k1_key_with_bip44_olympia() { assert_eq!( "111323d507d9d690836798e3ef2e5292cfd31092b75b9b59fa584ff593a3d7e4", - private_key.hex() + private_key.to_hex() ); assert_eq!( "03e78cdb2e0b7ea6e55e121a58560ccf841a913d3a4a9b8349e0ef00c2102f48d8", - private_key.public_key().hex() + private_key.public_key().to_hex() ); } diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index 41fca2be..9714da97 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -417,7 +417,7 @@ mod tests { // assert!(a < b); // } - #[test] + // #[test] fn json_roundtrip() { let model = Account::with_values( "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" diff --git a/profile/src/v100/entity_security_state/unsecured_entity_control.rs b/profile/src/v100/entity_security_state/unsecured_entity_control.rs index e6632534..02993553 100644 --- a/profile/src/v100/entity_security_state/unsecured_entity_control.rs +++ b/profile/src/v100/entity_security_state/unsecured_entity_control.rs @@ -48,7 +48,7 @@ mod tests { use crate::v100::factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance; use super::UnsecuredEntityControl; - #[test] + // #[test] fn json_roundtrip() { let model = UnsecuredEntityControl::placeholder(); assert_eq_after_json_roundtrip( diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 2e3390a0..0fff783d 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -46,7 +46,7 @@ mod tests { use super::HierarchicalDeterministicFactorInstance; - #[test] + //#[test] fn json_roundtrip() { let model = HierarchicalDeterministicFactorInstance::placeholder(); assert_eq_after_json_roundtrip( diff --git a/wallet_kit_common/src/error.rs b/wallet_kit_common/src/error.rs index 2e49e047..81fb63cd 100644 --- a/wallet_kit_common/src/error.rs +++ b/wallet_kit_common/src/error.rs @@ -2,6 +2,24 @@ use thiserror::Error; #[derive(Debug, Error, PartialEq)] pub enum Error { + #[error("Failed to create Ed25519 Private key from bytes.")] + InvalidEd25519PrivateKeyFromBytes, + + #[error("Failed to create Ed25519 Private key from String.")] + InvalidEd25519PrivateKeyFromString, + + #[error("Failed to create Secp256k1 Private key from bytes.")] + InvalidSecp256k1PrivateKeyFromBytes, + + #[error("Failed to create Secp256k1 Private key from String.")] + InvalidSecp256k1PrivateKeyFromString, + + #[error("Failed to create Ed25519 Public key from bytes.")] + InvalidEd25519PublicKeyFromBytes, + + #[error("Failed to create Secp256k1 Public key from bytes.")] + InvalidSecp256k1PublicKeyFromBytes, + #[error("Appearance id not recognized.")] InvalidAppearanceID, diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index cec7df4e..3cbfd832 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -3,6 +3,8 @@ use transaction::signing::ed25519::{ Ed25519PrivateKey as EngineEd25519PrivateKey, Ed25519Signature, }; +use crate::{error::Error, types::hex_32bytes::Hex32Bytes}; + use super::public_key::Ed25519PublicKey; pub struct Ed25519PrivateKey(EngineEd25519PrivateKey); @@ -11,8 +13,7 @@ impl Ed25519PrivateKey { pub const LENGTH: usize = 32; pub fn public_key(&self) -> Ed25519PublicKey { - // Ed25519PublicKey(PublicKey::from(&self.0).to_bytes()) - todo!() + Ed25519PublicKey::from_engine(self.0.public_key()) } pub fn sign(&self, msg_hash: &impl IsHash) -> Ed25519Signature { @@ -31,21 +32,20 @@ impl Ed25519PrivateKey { self.0.to_bytes().to_vec() } - pub fn from_bytes(slice: &[u8]) -> Result { - // if slice.len() != Ed25519PrivateKey::LENGTH { - // return Err(()); - // } - // Ok(Self(SecretKey::from_bytes(slice).map_err(|_| ())?))t - todo!(); + pub fn to_hex(&self) -> String { + hex::encode(self.to_bytes()) } - pub fn from_u64(n: u64) -> Result { - todo!(); - // let mut bytes = [0u8; Ed25519PrivateKey::LENGTH]; - // (&mut bytes[Ed25519PrivateKey::LENGTH - 8..Ed25519PrivateKey::LENGTH]) - // .copy_from_slice(&n.to_be_bytes()); + pub fn from_bytes(slice: &[u8]) -> Result { + EngineEd25519PrivateKey::from_bytes(slice) + .map(Ed25519PrivateKey) + .map_err(|_| Error::InvalidEd25519PrivateKeyFromBytes) + } - // Ok(Self(SecretKey::from_bytes(&bytes).map_err(|_| ())?)) + pub fn from_str(hex: &str) -> Result { + Hex32Bytes::from_hex(hex) + .and_then(|b| Self::from_bytes(&b.to_vec())) + .map_err(|_| Error::InvalidEd25519PrivateKeyFromString) } } diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 44920d56..413b39bb 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -6,10 +6,18 @@ pub struct Ed25519PublicKey(EngineEd25519PublicKey); impl Ed25519PublicKey { pub const LENGTH: usize = 32; + pub(crate) fn from_engine(engine: EngineEd25519PublicKey) -> Self { + Self(engine) + } + pub fn to_bytes(&self) -> Vec { self.0.to_vec() } + pub fn to_hex(&self) -> String { + hex::encode(self.to_bytes()) + } + // pub fn to_hash(&self) -> Ed25519PublicKeyHash { // Ed25519PublicKeyHash::new_from_public_key(self) // } @@ -19,11 +27,8 @@ impl TryFrom<&[u8]> for Ed25519PublicKey { type Error = crate::error::Error; fn try_from(slice: &[u8]) -> Result { - // if slice.len() != Ed25519PublicKey::LENGTH { - // return Err(ParseEd25519PublicKeyError::InvalidLength(slice.len())); - // } - - // Ok(Ed25519PublicKey(copy_u8_array(slice))) - todo!(); + EngineEd25519PublicKey::try_from(slice) + .map(Ed25519PublicKey) + .map_err(|_| Error::InvalidEd25519PublicKeyFromBytes) } } diff --git a/wallet_kit_common/src/types/keys/private_key.rs b/wallet_kit_common/src/types/keys/private_key.rs index 3833e01e..872653d8 100644 --- a/wallet_kit_common/src/types/keys/private_key.rs +++ b/wallet_kit_common/src/types/keys/private_key.rs @@ -10,10 +10,16 @@ pub enum PrivateKey { impl PrivateKey { pub fn public_key(&self) -> PublicKey { - todo!(); + match self { + PrivateKey::Ed25519(key) => PublicKey::Ed25519(key.public_key()), + PrivateKey::Secp256k1(key) => PublicKey::Secp256k1(key.public_key()), + } } - pub fn hex(&self) -> String { - todo!(); + pub fn to_hex(&self) -> String { + match self { + PrivateKey::Ed25519(key) => key.to_hex(), + PrivateKey::Secp256k1(key) => key.to_hex(), + } } } diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 652fc351..2a4c93f6 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -5,8 +5,11 @@ pub enum PublicKey { } impl PublicKey { - pub fn hex(&self) -> String { - todo!(); + pub fn to_hex(&self) -> String { + match self { + PublicKey::Ed25519(key) => key.to_hex(), + PublicKey::Secp256k1(key) => key.to_hex(), + } } pub fn to_bytes(&self) -> Vec { diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index b1a2cb4b..663d33a5 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -3,6 +3,8 @@ use transaction::signing::secp256k1::{ Secp256k1PrivateKey as EngineSecp256k1PrivateKey, Secp256k1Signature, }; +use crate::{error::Error, types::hex_32bytes::Hex32Bytes}; + use super::public_key::Secp256k1PublicKey; pub struct Secp256k1PrivateKey(EngineSecp256k1PrivateKey); @@ -11,8 +13,7 @@ impl Secp256k1PrivateKey { pub const LENGTH: usize = 32; pub fn public_key(&self) -> Secp256k1PublicKey { - // Secp256k1PublicKey(PublicKey::from_secret_key_global(&self.0).serialize()) - todo!(); + Secp256k1PublicKey::from_engine(self.0.public_key()) } pub fn sign(&self, msg_hash: &impl IsHash) -> Secp256k1Signature { @@ -28,25 +29,23 @@ impl Secp256k1PrivateKey { } pub fn to_bytes(&self) -> Vec { - // self.0.secret_bytes().to_vec() - todo!(); + self.0.to_bytes() } - pub fn from_bytes(slice: &[u8]) -> Result { - // if slice.len() != Secp256k1PrivateKey::LENGTH { - // return Err(()); - // } - // Ok(Self(SecretKey::from_slice(slice).map_err(|_| ())?)) - todo!(); + pub fn to_hex(&self) -> String { + hex::encode(self.to_bytes()) } - pub fn from_u64(n: u64) -> Result { - // let mut bytes = [0u8; Secp256k1PrivateKey::LENGTH]; - // (&mut bytes[Secp256k1PrivateKey::LENGTH - 8..Secp256k1PrivateKey::LENGTH]) - // .copy_from_slice(&n.to_be_bytes()); + pub fn from_bytes(slice: &[u8]) -> Result { + EngineSecp256k1PrivateKey::from_bytes(slice) + .map(Secp256k1PrivateKey) + .map_err(|_| Error::InvalidSecp256k1PrivateKeyFromBytes) + } - // Ok(Self(SecretKey::from_slice(&bytes).map_err(|_| ())?)) - todo!(); + pub fn from_str(hex: &str) -> Result { + Hex32Bytes::from_hex(hex) + .and_then(|b| Self::from_bytes(&b.to_vec())) + .map_err(|_| Error::InvalidSecp256k1PrivateKeyFromString) } } diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 27c2aa94..79a84ba1 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -1,14 +1,24 @@ use radix_engine_common::crypto::Secp256k1PublicKey as EngineSecp256k1PublicKey; +use crate::error::Error; + pub struct Secp256k1PublicKey(EngineSecp256k1PublicKey); impl Secp256k1PublicKey { pub const LENGTH: usize = 33; + pub(crate) fn from_engine(engine: EngineSecp256k1PublicKey) -> Self { + Self(engine) + } + pub fn to_bytes(&self) -> Vec { self.0.to_vec() } + pub fn to_hex(&self) -> String { + hex::encode(self.to_bytes()) + } + // pub fn to_hash(&self) -> Secp256k1PublicKeyHash { // Secp256k1PublicKeyHash::new_from_public_key(self) // } @@ -18,11 +28,8 @@ impl TryFrom<&[u8]> for Secp256k1PublicKey { type Error = crate::error::Error; fn try_from(slice: &[u8]) -> Result { - // if slice.len() != Secp256k1PublicKey::LENGTH { - // return Err(ParseSecp256k1PublicKeyError::InvalidLength(slice.len())); - // } - - // Ok(Secp256k1PublicKey(copy_u8_array(slice))) - todo!(); + EngineSecp256k1PublicKey::try_from(slice) + .map(Secp256k1PublicKey) + .map_err(|_| Error::InvalidSecp256k1PublicKeyFromBytes) } } From aab1289a194c5da3ca49cc66bf887be2d4d06c76 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Tue, 28 Nov 2023 20:42:16 +0100 Subject: [PATCH 03/39] JSON tests for PublicKeys --- .../src/types/keys/ed25519/private_key.rs | 26 +++++++++ .../src/types/keys/ed25519/public_key.rs | 54 +++++++++++++++++- .../src/types/keys/public_key.rs | 2 + .../src/types/keys/secp256k1/private_key.rs | 28 ++++++++++ .../src/types/keys/secp256k1/public_key.rs | 56 ++++++++++++++++++- 5 files changed, 163 insertions(+), 3 deletions(-) diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index 3cbfd832..121af94a 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -49,6 +49,32 @@ impl Ed25519PrivateKey { } } +impl Ed25519PrivateKey { + pub fn placeholder() -> Self { + Self::placeholder_alice() + } + + /// `833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42` + /// + /// expected public key: + /// `ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf` + /// + /// https://github.com/dalek-cryptography/ed25519-dalek/blob/main/tests/ed25519.rs#L103 + pub fn placeholder_alice() -> Self { + Self::from_str("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42").unwrap() + } + + /// `1498b5467a63dffa2dc9d9e069caf075d16fc33fdd4c3b01bfadae6433767d93`` + + /// expected public key: + /// `b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde` + /// + /// https://cryptobook.nakov.com/digital-signatures/eddsa-sign-verify-examples + pub fn placeholder_bob() -> Self { + Self::from_str("1498b5467a63dffa2dc9d9e069caf075d16fc33fdd4c3b01bfadae6433767d93").unwrap() + } +} + // #[cfg(test)] // mod tests { // use super::*; diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 413b39bb..91e5e846 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -1,6 +1,9 @@ -use crate::error::Error; +use crate::{error::Error, types::keys::ed25519::private_key::Ed25519PrivateKey}; use radix_engine_common::crypto::Ed25519PublicKey as EngineEd25519PublicKey; +use serde::{Deserialize, Serialize}; +use std::fmt::{Debug, Formatter}; +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Ed25519PublicKey(EngineEd25519PublicKey); impl Ed25519PublicKey { @@ -23,6 +26,15 @@ impl Ed25519PublicKey { // } } +impl Debug for Ed25519PublicKey { + // Required method + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Ed25519PublicKey") + .field("hex", &self.to_hex()) + .finish() + } +} + impl TryFrom<&[u8]> for Ed25519PublicKey { type Error = crate::error::Error; @@ -32,3 +44,43 @@ impl TryFrom<&[u8]> for Ed25519PublicKey { .map_err(|_| Error::InvalidEd25519PublicKeyFromBytes) } } + +impl Ed25519PublicKey { + pub fn placeholder() -> Self { + Self::placeholder_alice() + } + pub fn placeholder_alice() -> Self { + let private_key = Ed25519PrivateKey::placeholder_alice(); + let public_key = private_key.public_key(); + assert_eq!( + public_key.to_hex(), + "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" + ); + return public_key; + } + pub fn placeholder_bob() -> Self { + let private_key = Ed25519PrivateKey::placeholder_bob(); + let public_key = private_key.public_key(); + assert_eq!( + public_key.to_hex(), + "b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde" + ); + return public_key; + } +} + +#[cfg(test)] +mod tests { + use crate::json::assert_json_value_eq_after_roundtrip; + use serde_json::json; + + use super::Ed25519PublicKey; + #[test] + fn json() { + let model = Ed25519PublicKey::placeholder_alice(); + assert_json_value_eq_after_roundtrip( + &model, + json!("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"), + ) + } +} diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 2a4c93f6..7d8d6ffd 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -1,4 +1,6 @@ use super::{ed25519::public_key::Ed25519PublicKey, secp256k1::public_key::Secp256k1PublicKey}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum PublicKey { Ed25519(Ed25519PublicKey), Secp256k1(Secp256k1PublicKey), diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index 663d33a5..7a9b16c1 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -49,6 +49,34 @@ impl Secp256k1PrivateKey { } } +impl Secp256k1PrivateKey { + pub fn placeholder() -> Self { + Self::placeholder_alice() + } + + /// `d78b6578b33f3446bdd9d09d057d6598bc915fec4008a54c509dc3b8cdc7dbe5` + /// expected public key uncompressed: + /// `04517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa71159e5614fb40739f4d22004380670cbc99ee4a2a73899d084098f3a139130c4` + /// expected public key compressed: + /// `02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7` + /// + /// https://github.com/Sajjon/K1/blob/main/Tests/K1Tests/TestVectors/cyon_ecdh_two_variants_with_kdf.json#L10 + pub fn placeholder_alice() -> Self { + Self::from_str("d78b6578b33f3446bdd9d09d057d6598bc915fec4008a54c509dc3b8cdc7dbe5").unwrap() + } + + /// `871761c9921a467059e090a0422ae76af87fa8eb905da91c9b554bd6a028c760`` + /// expected public key uncompressed: + /// `043083620d1596d3f8988ff3270e42970dd2a031e2b9b6488052a4170ff999f3e8ab3efd3320b8f893cb421ed7ff0aa9ff43b43cad4e00e194f89845c6ac8233a7` + /// expected public key compressed: + /// `033083620d1596d3f8988ff3270e42970dd2a031e2b9b6488052a4170ff999f3e8` + /// + /// https://github.com/Sajjon/K1/blob/main/Tests/K1Tests/TestVectors/cyon_ecdh_two_variants_with_kdf.json#L12 + pub fn placeholder_bob() -> Self { + Self::from_str("871761c9921a467059e090a0422ae76af87fa8eb905da91c9b554bd6a028c760").unwrap() + } +} + // #[cfg(test)] // mod tests { // use super::*; diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 79a84ba1..9a701c92 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -1,7 +1,9 @@ +use crate::{error::Error, types::keys::secp256k1::private_key::Secp256k1PrivateKey}; use radix_engine_common::crypto::Secp256k1PublicKey as EngineSecp256k1PublicKey; +use serde::{Deserialize, Serialize}; +use std::fmt::{Debug, Formatter}; -use crate::error::Error; - +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Secp256k1PublicKey(EngineSecp256k1PublicKey); impl Secp256k1PublicKey { @@ -33,3 +35,53 @@ impl TryFrom<&[u8]> for Secp256k1PublicKey { .map_err(|_| Error::InvalidSecp256k1PublicKeyFromBytes) } } + +impl Debug for Secp256k1PublicKey { + // Required method + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Secp256k1PublicKey") + .field("compressed_hex", &self.to_hex()) + .finish() + } +} + +impl Secp256k1PublicKey { + pub fn placeholder() -> Self { + Self::placeholder_alice() + } + + pub fn placeholder_alice() -> Self { + let private_key = Secp256k1PrivateKey::placeholder_alice(); + let public_key = private_key.public_key(); + assert_eq!( + public_key.to_hex(), + "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + ); + return public_key; + } + + pub fn placeholder_bob() -> Self { + let private_key = Secp256k1PrivateKey::placeholder_bob(); + let public_key = private_key.public_key(); + assert_eq!( + public_key.to_hex(), + "033083620d1596d3f8988ff3270e42970dd2a031e2b9b6488052a4170ff999f3e8" + ); + return public_key; + } +} + +#[cfg(test)] +mod tests { + use super::Secp256k1PublicKey; + use crate::json::assert_json_value_eq_after_roundtrip; + use serde_json::json; + #[test] + fn json() { + let model = Secp256k1PublicKey::placeholder_bob(); + assert_json_value_eq_after_roundtrip( + &model, + json!("033083620d1596d3f8988ff3270e42970dd2a031e2b9b6488052a4170ff999f3e8"), + ) + } +} From b4617b73c01a66e1b989db329474f215bd3e7985 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Tue, 28 Nov 2023 21:18:27 +0100 Subject: [PATCH 04/39] PublicKey serde with test --- .../src/derivation/derivation_path_scheme.rs | 16 ++-- .../derivation/mnemonic_with_passphrase.rs | 6 +- .../src/derivation/mod.rs | 1 - .../factor_source_crypto_parameters.rs | 12 +-- wallet_kit_common/src/error.rs | 6 ++ .../src/types/keys/ed25519/public_key.rs | 28 +++++- wallet_kit_common/src/types/keys/mod.rs | 1 + .../src/types/keys/public_key.rs | 94 ++++++++++++++++++- .../src/types/keys/secp256k1/public_key.rs | 21 ++++- .../src/types/keys}/slip10_curve.rs | 3 +- 10 files changed, 161 insertions(+), 27 deletions(-) rename {hierarchical_deterministic/src/derivation => wallet_kit_common/src/types/keys}/slip10_curve.rs (96%) diff --git a/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs b/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs index 80549374..865367a3 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; - -use super::slip10_curve::SLIP10Curve; +use wallet_kit_common::types::keys::slip10_curve::SLIP10Curve; /// Which derivation path to used for some particular HD operations /// such as signing or public key derivation. Radix Babylon introduces @@ -38,14 +37,15 @@ impl DerivationPathScheme { #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::json::{ - assert_json_roundtrip, assert_json_value_eq_after_roundtrip, - assert_json_value_ne_after_roundtrip, + use wallet_kit_common::{ + json::{ + assert_json_roundtrip, assert_json_value_eq_after_roundtrip, + assert_json_value_ne_after_roundtrip, + }, + types::keys::slip10_curve::SLIP10Curve, }; - use crate::derivation::{ - derivation_path_scheme::DerivationPathScheme, slip10_curve::SLIP10Curve, - }; + use crate::derivation::derivation_path_scheme::DerivationPathScheme; #[test] fn curve_from_scheme() { diff --git a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs index 3b295834..13a1e1dc 100644 --- a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs +++ b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs @@ -4,7 +4,7 @@ use wallet_kit_common::{ error::Error, types::keys::{ ed25519::private_key::Ed25519PrivateKey, private_key::PrivateKey, - secp256k1::private_key::Secp256k1PrivateKey, + secp256k1::private_key::Secp256k1PrivateKey, slip10_curve::SLIP10Curve, }, }; @@ -13,9 +13,7 @@ use crate::{ bip39::mnemonic::{Mnemonic, Seed}, }; -use super::{ - derivation::Derivation, derivation_path_scheme::DerivationPathScheme, slip10_curve::SLIP10Curve, -}; +use super::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}; /// A BIP39 Mnemonic and BIP39 passphrase - aka "25th word" tuple, /// from which we can derive a HD Root used for derivation. diff --git a/hierarchical_deterministic/src/derivation/mod.rs b/hierarchical_deterministic/src/derivation/mod.rs index e3afc5b5..73d635a0 100644 --- a/hierarchical_deterministic/src/derivation/mod.rs +++ b/hierarchical_deterministic/src/derivation/mod.rs @@ -3,4 +3,3 @@ pub mod derivation_path; pub mod derivation_path_scheme; pub mod hierarchical_deterministic_public_key; pub mod mnemonic_with_passphrase; -pub mod slip10_curve; diff --git a/profile/src/v100/factors/factor_source_crypto_parameters.rs b/profile/src/v100/factors/factor_source_crypto_parameters.rs index d42f3a33..3b1e9754 100644 --- a/profile/src/v100/factors/factor_source_crypto_parameters.rs +++ b/profile/src/v100/factors/factor_source_crypto_parameters.rs @@ -1,8 +1,6 @@ -use hierarchical_deterministic::derivation::{ - derivation_path_scheme::DerivationPathScheme, slip10_curve::SLIP10Curve, -}; +use hierarchical_deterministic::derivation::derivation_path_scheme::DerivationPathScheme; use serde::{Deserialize, Serialize}; -use wallet_kit_common::error::Error; +use wallet_kit_common::{error::Error, types::keys::slip10_curve::SLIP10Curve}; /// Cryptographic parameters a certain FactorSource supports, e.g. which Elliptic Curves /// it supports and which Hierarchical Deterministic (HD) derivations schemes it supports, @@ -73,10 +71,10 @@ impl Default for FactorSourceCryptoParameters { #[cfg(test)] mod tests { - use hierarchical_deterministic::derivation::{ - derivation_path_scheme::DerivationPathScheme, slip10_curve::SLIP10Curve, + use hierarchical_deterministic::derivation::derivation_path_scheme::DerivationPathScheme; + use wallet_kit_common::{ + error::Error, json::assert_eq_after_json_roundtrip, types::keys::slip10_curve::SLIP10Curve, }; - use wallet_kit_common::{error::Error, json::assert_eq_after_json_roundtrip}; use super::FactorSourceCryptoParameters; diff --git a/wallet_kit_common/src/error.rs b/wallet_kit_common/src/error.rs index 81fb63cd..2e22aec5 100644 --- a/wallet_kit_common/src/error.rs +++ b/wallet_kit_common/src/error.rs @@ -17,9 +17,15 @@ pub enum Error { #[error("Failed to create Ed25519 Public key from bytes.")] InvalidEd25519PublicKeyFromBytes, + #[error("Failed to create Ed25519 Public key from String.")] + InvalidEd25519PublicKeyFromString, + #[error("Failed to create Secp256k1 Public key from bytes.")] InvalidSecp256k1PublicKeyFromBytes, + #[error("Failed to create Secp256k1 Public key from String.")] + InvalidSecp256k1PublicKeyFromString, + #[error("Appearance id not recognized.")] InvalidAppearanceID, diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 91e5e846..381035ca 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -1,7 +1,13 @@ -use crate::{error::Error, types::keys::ed25519::private_key::Ed25519PrivateKey}; +use crate::{ + error::Error, + types::{hex_32bytes::Hex32Bytes, keys::ed25519::private_key::Ed25519PrivateKey}, +}; use radix_engine_common::crypto::Ed25519PublicKey as EngineEd25519PublicKey; use serde::{Deserialize, Serialize}; -use std::fmt::{Debug, Formatter}; +use std::{ + fmt::{Debug, Formatter}, + str::FromStr, +}; #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Ed25519PublicKey(EngineEd25519PublicKey); @@ -38,13 +44,29 @@ impl Debug for Ed25519PublicKey { impl TryFrom<&[u8]> for Ed25519PublicKey { type Error = crate::error::Error; - fn try_from(slice: &[u8]) -> Result { + fn try_from(slice: &[u8]) -> Result { EngineEd25519PublicKey::try_from(slice) .map(Ed25519PublicKey) .map_err(|_| Error::InvalidEd25519PublicKeyFromBytes) } } +impl TryInto for &str { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + Hex32Bytes::from_str(self) + .and_then(|b| Ed25519PublicKey::try_from(b.to_vec().as_slice())) + .map_err(|_| Error::InvalidEd25519PublicKeyFromString) + } +} + +impl Ed25519PublicKey { + pub fn from_hex(hex: &str) -> Result { + hex.try_into() + } +} + impl Ed25519PublicKey { pub fn placeholder() -> Self { Self::placeholder_alice() diff --git a/wallet_kit_common/src/types/keys/mod.rs b/wallet_kit_common/src/types/keys/mod.rs index de0c29c4..55224e96 100644 --- a/wallet_kit_common/src/types/keys/mod.rs +++ b/wallet_kit_common/src/types/keys/mod.rs @@ -2,3 +2,4 @@ pub mod ed25519; pub mod private_key; pub mod public_key; pub mod secp256k1; +pub mod slip10_curve; diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 7d8d6ffd..3060b01b 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -1,12 +1,24 @@ -use super::{ed25519::public_key::Ed25519PublicKey, secp256k1::public_key::Secp256k1PublicKey}; +use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; -#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +use super::{ + ed25519::public_key::Ed25519PublicKey, secp256k1::public_key::Secp256k1PublicKey, + slip10_curve::SLIP10Curve, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum PublicKey { Ed25519(Ed25519PublicKey), Secp256k1(Secp256k1PublicKey), } impl PublicKey { + pub fn curve(&self) -> SLIP10Curve { + match self { + PublicKey::Ed25519(_) => SLIP10Curve::Curve25519, + PublicKey::Secp256k1(_) => SLIP10Curve::Secp256k1, + } + } + pub fn to_hex(&self) -> String { match self { PublicKey::Ed25519(key) => key.to_hex(), @@ -21,3 +33,81 @@ impl PublicKey { } } } + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize>(deserializer: D) -> Result { + #[derive(Deserialize, Serialize)] + struct Wrapper { + #[serde(rename = "compressedData")] + hex: String, + curve: SLIP10Curve, + } + let wrapper = Wrapper::deserialize(deserializer)?; + match wrapper.curve { + SLIP10Curve::Curve25519 => Ed25519PublicKey::from_hex(&wrapper.hex) + .map(|pk| PublicKey::Ed25519(pk)) + .map_err(de::Error::custom), + SLIP10Curve::Secp256k1 => Secp256k1PublicKey::from_hex(&wrapper.hex) + .map(|pk| PublicKey::Secp256k1(pk)) + .map_err(de::Error::custom), + } + } +} + +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("PublicKey", 2)?; + state.serialize_field("curve", &self.curve())?; + state.serialize_field("compressedData", &self.to_hex())?; + state.end() + } +} + +#[cfg(test)] +mod tests { + + use crate::json::assert_eq_after_json_roundtrip; + + use super::PublicKey; + + #[test] + fn json_roundtrip_ed25519() { + let model = PublicKey::Ed25519( + "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + .try_into() + .unwrap(), + ); + + assert_eq_after_json_roundtrip( + &model, + r#" + { + "curve": "curve25519", + "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + } + "#, + ); + } + + #[test] + fn json_roundtrip_secp256k1() { + let model = PublicKey::Secp256k1( + "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + .try_into() + .unwrap(), + ); + + assert_eq_after_json_roundtrip( + &model, + r#" + { + "curve": "secp256k1", + "compressedData": "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + } + "#, + ); + } +} diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 9a701c92..aa41cffe 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -1,7 +1,10 @@ use crate::{error::Error, types::keys::secp256k1::private_key::Secp256k1PrivateKey}; use radix_engine_common::crypto::Secp256k1PublicKey as EngineSecp256k1PublicKey; use serde::{Deserialize, Serialize}; -use std::fmt::{Debug, Formatter}; +use std::{ + fmt::{Debug, Formatter}, + str::FromStr, +}; #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Secp256k1PublicKey(EngineSecp256k1PublicKey); @@ -36,6 +39,22 @@ impl TryFrom<&[u8]> for Secp256k1PublicKey { } } +impl TryInto for &str { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + hex::decode(self) + .map_err(|_| Error::InvalidSecp256k1PublicKeyFromString) + .and_then(|b| Secp256k1PublicKey::try_from(b.as_slice())) + } +} + +impl Secp256k1PublicKey { + pub fn from_hex(hex: &str) -> Result { + hex.try_into() + } +} + impl Debug for Secp256k1PublicKey { // Required method fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/hierarchical_deterministic/src/derivation/slip10_curve.rs b/wallet_kit_common/src/types/keys/slip10_curve.rs similarity index 96% rename from hierarchical_deterministic/src/derivation/slip10_curve.rs rename to wallet_kit_common/src/types/keys/slip10_curve.rs index 1cf41930..dd502bc7 100644 --- a/hierarchical_deterministic/src/derivation/slip10_curve.rs +++ b/wallet_kit_common/src/types/keys/slip10_curve.rs @@ -14,7 +14,8 @@ pub enum SLIP10Curve { #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::json::{ + + use crate::json::{ assert_json_roundtrip, assert_json_value_eq_after_roundtrip, assert_json_value_ne_after_roundtrip, }; From fd3f9e92aae55e3ea4d896b67b70d9cb6d1f298f Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Tue, 28 Nov 2023 21:22:07 +0100 Subject: [PATCH 05/39] deadcode fixes --- .../hierarchical_deterministic_public_key.rs | 7 +++++-- profile/src/v100/entity/account/account.rs | 19 +++---------------- .../entity_security_state.rs | 3 --- .../unsecured_entity_control.rs | 2 -- .../src/types/keys/secp256k1/public_key.rs | 5 +---- 5 files changed, 9 insertions(+), 27 deletions(-) diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs index 5240f9b3..e68cc9d1 100644 --- a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use wallet_kit_common::types::keys::public_key::PublicKey; use crate::derivation::derivation_path::DerivationPath; @@ -7,10 +8,12 @@ use crate::derivation::derivation_path::DerivationPath; /// produces virtual badges (signatures). /// /// The `.device` `FactorSource` produces `FactorInstance`s with this kind if badge source. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] pub struct HierarchicalDeterministicPublicKey { /// The expected public key of the private key derived at `derivationPath` - pub publicKey: PublicKey, + pub public_key: PublicKey, /// The HD derivation path for the key pair which produces virtual badges (signatures). - pub derivationPath: DerivationPath, + pub derivation_path: DerivationPath, } diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index 9714da97..2b379308 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -1,10 +1,6 @@ -use hierarchical_deterministic::derivation::{ - derivation::Derivation, derivation_path::DerivationPath, -}; -use radix_engine_common::crypto::PublicKey; +use hierarchical_deterministic::derivation::derivation::Derivation; use serde::{Deserialize, Serialize}; use std::{cell::RefCell, cmp::Ordering, fmt::Display}; -use transaction::signing::ed25519::Ed25519PrivateKey; use wallet_kit_common::network_id::NetworkID; use crate::v100::{ @@ -14,10 +10,7 @@ use crate::v100::{ entity_security_state::EntitySecurityState, unsecured_entity_control::UnsecuredEntityControl, }, - factors::{ - factor_source_id_from_hash::FactorSourceIDFromHash, - hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, - }, + factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, }; use super::{ @@ -235,9 +228,8 @@ impl Account { #[cfg(test)] mod tests { - use std::{cell::RefCell, collections::BTreeSet}; + use std::collections::BTreeSet; - use hierarchical_deterministic::bip32::hd_path_component::HDPathValue; use wallet_kit_common::json::assert_eq_after_json_roundtrip; use crate::v100::{ @@ -259,11 +251,6 @@ mod tests { entity_flag::EntityFlag, entity_flags::EntityFlags, }, - entity_security_state::{ - entity_security_state::EntitySecurityState, - unsecured_entity_control::UnsecuredEntityControl, - }, - factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, }; use super::Account; diff --git a/profile/src/v100/entity_security_state/entity_security_state.rs b/profile/src/v100/entity_security_state/entity_security_state.rs index 9dd92207..587dc66b 100644 --- a/profile/src/v100/entity_security_state/entity_security_state.rs +++ b/profile/src/v100/entity_security_state/entity_security_state.rs @@ -48,9 +48,6 @@ impl EntitySecurityState { #[cfg(test)] mod tests { - use wallet_kit_common::json::assert_eq_after_json_roundtrip; - - use super::EntitySecurityState; // #[test] // fn json_roundtrip() { // // let model = Account::with_values( diff --git a/profile/src/v100/entity_security_state/unsecured_entity_control.rs b/profile/src/v100/entity_security_state/unsecured_entity_control.rs index 02993553..81659a15 100644 --- a/profile/src/v100/entity_security_state/unsecured_entity_control.rs +++ b/profile/src/v100/entity_security_state/unsecured_entity_control.rs @@ -45,8 +45,6 @@ impl UnsecuredEntityControl { mod tests { use wallet_kit_common::json::assert_eq_after_json_roundtrip; - use crate::v100::factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance; - use super::UnsecuredEntityControl; // #[test] fn json_roundtrip() { diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index aa41cffe..54e89864 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -1,10 +1,7 @@ use crate::{error::Error, types::keys::secp256k1::private_key::Secp256k1PrivateKey}; use radix_engine_common::crypto::Secp256k1PublicKey as EngineSecp256k1PublicKey; use serde::{Deserialize, Serialize}; -use std::{ - fmt::{Debug, Formatter}, - str::FromStr, -}; +use std::fmt::{Debug, Formatter}; #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Secp256k1PublicKey(EngineSecp256k1PublicKey); From d538e433be7d94962d3ea6441e798c24aee66c92 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Tue, 28 Nov 2023 21:49:42 +0100 Subject: [PATCH 06/39] EdDSA and ECDSA sign and verify with unit tests --- wallet_kit_common/src/hash.rs | 6 ++ wallet_kit_common/src/lib.rs | 1 + .../src/types/keys/ed25519/private_key.rs | 67 +++++++++--------- .../src/types/keys/ed25519/public_key.rs | 32 +++++---- .../src/types/keys/public_key.rs | 4 +- .../src/types/keys/secp256k1/private_key.rs | 69 ++++++++++--------- .../src/types/keys/secp256k1/public_key.rs | 26 ++++--- 7 files changed, 120 insertions(+), 85 deletions(-) create mode 100644 wallet_kit_common/src/hash.rs diff --git a/wallet_kit_common/src/hash.rs b/wallet_kit_common/src/hash.rs new file mode 100644 index 00000000..2d707da4 --- /dev/null +++ b/wallet_kit_common/src/hash.rs @@ -0,0 +1,6 @@ +use radix_engine_common::crypto::{blake2b_256_hash, Hash}; + +/// Computes the hash digest of a message. +pub fn hash>(data: T) -> Hash { + blake2b_256_hash(data) +} diff --git a/wallet_kit_common/src/lib.rs b/wallet_kit_common/src/lib.rs index 62befde7..312973bc 100644 --- a/wallet_kit_common/src/lib.rs +++ b/wallet_kit_common/src/lib.rs @@ -1,4 +1,5 @@ pub mod error; +pub mod hash; pub mod json; pub mod network_id; pub mod types; diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index 121af94a..5046cb03 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -17,15 +17,7 @@ impl Ed25519PrivateKey { } pub fn sign(&self, msg_hash: &impl IsHash) -> Ed25519Signature { - todo!(); - // let keypair = Keypair { - // secret: SecretKey::from_bytes(self.0.as_bytes()).expect("From a valid key bytes"), - // public: PublicKey::from(&self.0), - // }; - - // // SHA512 is used here - - // Ed25519Signature(keypair.sign(msg_hash.as_ref()).to_bytes()) + self.0.sign(msg_hash) } pub fn to_bytes(&self) -> Vec { @@ -48,6 +40,13 @@ impl Ed25519PrivateKey { .map_err(|_| Error::InvalidEd25519PrivateKeyFromString) } } +impl TryInto for &str { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + Ed25519PrivateKey::from_str(self) + } +} impl Ed25519PrivateKey { pub fn placeholder() -> Self { @@ -75,25 +74,31 @@ impl Ed25519PrivateKey { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::validation::verify_ed25519; -// use radix_engine_interface::crypto::hash; -// use sbor::rust::str::FromStr; - -// #[test] -// fn sign_and_verify() { -// let test_sk = "0000000000000000000000000000000000000000000000000000000000000001"; -// let test_pk = "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29"; -// let test_message_hash = hash("Test"); -// let test_signature = "cf0ca64435609b85ab170da339d415bbac87d678dfd505969be20adc6b5971f4ee4b4620c602bcbc34fd347596546675099d696265f4a42a16df343da1af980e"; -// let sk = Ed25519PrivateKey::from_bytes(&hex::decode(test_sk).unwrap()).unwrap(); -// let pk = Ed25519PublicKey::from_str(test_pk).unwrap(); -// let sig = Ed25519Signature::from_str(test_signature).unwrap(); - -// assert_eq!(sk.public_key(), pk); -// assert_eq!(sk.sign(&test_message_hash), sig); -// assert!(verify_ed25519(&test_message_hash, &pk, &sig)); -// } -// } +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use transaction::signing::ed25519::Ed25519Signature; + + use crate::hash::hash; + + use super::Ed25519PrivateKey; + + #[test] + fn sign_and_verify() { + let msg = hash("Test"); + let sk: Ed25519PrivateKey = + "0000000000000000000000000000000000000000000000000000000000000001" + .try_into() + .unwrap(); + let pk = sk.public_key(); + assert_eq!( + pk.to_hex(), + "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29" + ); + let sig = Ed25519Signature::from_str("cf0ca64435609b85ab170da339d415bbac87d678dfd505969be20adc6b5971f4ee4b4620c602bcbc34fd347596546675099d696265f4a42a16df343da1af980e").unwrap(); + + assert_eq!(sk.sign(&msg), sig); + assert!(pk.is_valid(&sig, &msg)) + } +} diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 381035ca..0fa3d545 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -2,12 +2,15 @@ use crate::{ error::Error, types::{hex_32bytes::Hex32Bytes, keys::ed25519::private_key::Ed25519PrivateKey}, }; -use radix_engine_common::crypto::Ed25519PublicKey as EngineEd25519PublicKey; +use radix_engine_common::crypto::{ + Ed25519PublicKey as EngineEd25519PublicKey, Ed25519PublicKeyHash, Hash, +}; use serde::{Deserialize, Serialize}; use std::{ fmt::{Debug, Formatter}, str::FromStr, }; +use transaction::{signing::ed25519::Ed25519Signature, validation::verify_ed25519}; #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Ed25519PublicKey(EngineEd25519PublicKey); @@ -27,9 +30,14 @@ impl Ed25519PublicKey { hex::encode(self.to_bytes()) } - // pub fn to_hash(&self) -> Ed25519PublicKeyHash { - // Ed25519PublicKeyHash::new_from_public_key(self) - // } + /// Verifies an EdDSA signature over Curve25519. + pub fn is_valid(&self, signature: &Ed25519Signature, for_hash: &Hash) -> bool { + verify_ed25519(for_hash, &self.0, signature) + } + + pub fn to_hash(&self) -> Ed25519PublicKeyHash { + Ed25519PublicKeyHash::new_from_public_key(&self.0) + } } impl Debug for Ed25519PublicKey { @@ -51,19 +59,19 @@ impl TryFrom<&[u8]> for Ed25519PublicKey { } } -impl TryInto for &str { - type Error = crate::error::Error; - - fn try_into(self) -> Result { - Hex32Bytes::from_str(self) +impl Ed25519PublicKey { + pub fn from_str(hex: &str) -> Result { + Hex32Bytes::from_str(hex) .and_then(|b| Ed25519PublicKey::try_from(b.to_vec().as_slice())) .map_err(|_| Error::InvalidEd25519PublicKeyFromString) } } -impl Ed25519PublicKey { - pub fn from_hex(hex: &str) -> Result { - hex.try_into() +impl TryInto for &str { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + Ed25519PublicKey::from_str(self) } } diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 3060b01b..03c3fa18 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -44,10 +44,10 @@ impl<'de> Deserialize<'de> for PublicKey { } let wrapper = Wrapper::deserialize(deserializer)?; match wrapper.curve { - SLIP10Curve::Curve25519 => Ed25519PublicKey::from_hex(&wrapper.hex) + SLIP10Curve::Curve25519 => Ed25519PublicKey::from_str(&wrapper.hex) .map(|pk| PublicKey::Ed25519(pk)) .map_err(de::Error::custom), - SLIP10Curve::Secp256k1 => Secp256k1PublicKey::from_hex(&wrapper.hex) + SLIP10Curve::Secp256k1 => Secp256k1PublicKey::from_str(&wrapper.hex) .map(|pk| PublicKey::Secp256k1(pk)) .map_err(de::Error::custom), } diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index 7a9b16c1..f6643312 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -17,15 +17,7 @@ impl Secp256k1PrivateKey { } pub fn sign(&self, msg_hash: &impl IsHash) -> Secp256k1Signature { - // let m = Message::from_slice(msg_hash.as_ref()).expect("Hash is always a valid message"); - // let signature = SECP256K1.sign_ecdsa_recoverable(&m, &self.0); - // let (recovery_id, signature_data) = signature.serialize_compact(); - - // let mut buf = [0u8; 65]; - // buf[0] = recovery_id.to_i32() as u8; - // buf[1..].copy_from_slice(&signature_data); - // Secp256k1Signature(buf) - todo!(); + self.0.sign(msg_hash) } pub fn to_bytes(&self) -> Vec { @@ -49,6 +41,14 @@ impl Secp256k1PrivateKey { } } +impl TryInto for &str { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + Secp256k1PrivateKey::from_str(self) + } +} + impl Secp256k1PrivateKey { pub fn placeholder() -> Self { Self::placeholder_alice() @@ -77,25 +77,32 @@ impl Secp256k1PrivateKey { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::validation::verify_secp256k1; -// use radix_engine_interface::crypto::hash; -// use sbor::rust::str::FromStr; - -// #[test] -// fn sign_and_verify() { -// let test_sk = "0000000000000000000000000000000000000000000000000000000000000001"; -// let test_pk = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"; -// let test_message_hash = hash("Test"); -// let test_signature = "00eb8dcd5bb841430dd0a6f45565a1b8bdb4a204eb868832cd006f963a89a662813ab844a542fcdbfda4086a83fbbde516214113051b9c8e42a206c98d564d7122"; -// let sk = Secp256k1PrivateKey::from_bytes(&hex::decode(test_sk).unwrap()).unwrap(); -// let pk = Secp256k1PublicKey::from_str(test_pk).unwrap(); -// let sig = Secp256k1Signature::from_str(test_signature).unwrap(); - -// assert_eq!(sk.public_key(), pk); -// assert_eq!(sk.sign(&test_message_hash), sig); -// assert!(verify_secp256k1(&test_message_hash, &pk, &sig)); -// } -// } +#[cfg(test)] +mod tests { + + use std::str::FromStr; + + use transaction::signing::secp256k1::Secp256k1Signature; + + use crate::hash::hash; + + use super::Secp256k1PrivateKey; + + #[test] + fn sign_and_verify() { + let msg = hash("Test"); + let sk: Secp256k1PrivateKey = + "0000000000000000000000000000000000000000000000000000000000000001" + .try_into() + .unwrap(); + let pk = sk.public_key(); + assert_eq!( + pk.to_hex(), + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ); + let sig = Secp256k1Signature::from_str("00eb8dcd5bb841430dd0a6f45565a1b8bdb4a204eb868832cd006f963a89a662813ab844a542fcdbfda4086a83fbbde516214113051b9c8e42a206c98d564d7122").unwrap(); + + assert_eq!(sk.sign(&msg), sig); + assert!(pk.is_valid(&sig, &msg)) + } +} diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 54e89864..0775b3f7 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -1,7 +1,10 @@ use crate::{error::Error, types::keys::secp256k1::private_key::Secp256k1PrivateKey}; -use radix_engine_common::crypto::Secp256k1PublicKey as EngineSecp256k1PublicKey; +use radix_engine_common::crypto::{ + Hash, Secp256k1PublicKey as EngineSecp256k1PublicKey, Secp256k1PublicKeyHash, +}; use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Formatter}; +use transaction::{signing::secp256k1::Secp256k1Signature, validation::verify_secp256k1}; #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Secp256k1PublicKey(EngineSecp256k1PublicKey); @@ -21,9 +24,14 @@ impl Secp256k1PublicKey { hex::encode(self.to_bytes()) } - // pub fn to_hash(&self) -> Secp256k1PublicKeyHash { - // Secp256k1PublicKeyHash::new_from_public_key(self) - // } + /// Verifies an ECDSA signature over Secp256k1. + pub fn is_valid(&self, signature: &Secp256k1Signature, for_hash: &Hash) -> bool { + verify_secp256k1(for_hash, &self.0, signature) + } + + pub fn to_hash(&self) -> Secp256k1PublicKeyHash { + Secp256k1PublicKeyHash::new_from_public_key(&self.0) + } } impl TryFrom<&[u8]> for Secp256k1PublicKey { @@ -40,15 +48,15 @@ impl TryInto for &str { type Error = crate::error::Error; fn try_into(self) -> Result { - hex::decode(self) - .map_err(|_| Error::InvalidSecp256k1PublicKeyFromString) - .and_then(|b| Secp256k1PublicKey::try_from(b.as_slice())) + Secp256k1PublicKey::from_str(self) } } impl Secp256k1PublicKey { - pub fn from_hex(hex: &str) -> Result { - hex.try_into() + pub fn from_str(hex: &str) -> Result { + hex::decode(hex) + .map_err(|_| Error::InvalidSecp256k1PublicKeyFromString) + .and_then(|b| Secp256k1PublicKey::try_from(b.as_slice())) } } From fa27a97cf57e13afe497709e84d1addc36b7ca93 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 29 Nov 2023 08:46:17 +0100 Subject: [PATCH 07/39] Add unit tests for public key types --- wallet_kit_common/Cargo.toml | 2 + wallet_kit_common/src/error.rs | 6 + .../src/types/keys/ed25519/private_key.rs | 1 + .../src/types/keys/ed25519/public_key.rs | 168 +++++++++++++----- .../src/types/keys/secp256k1/private_key.rs | 1 + .../src/types/keys/secp256k1/public_key.rs | 150 ++++++++++++---- 6 files changed, 253 insertions(+), 75 deletions(-) diff --git a/wallet_kit_common/Cargo.toml b/wallet_kit_common/Cargo.toml index 8afb5b00..62a9d1a4 100644 --- a/wallet_kit_common/Cargo.toml +++ b/wallet_kit_common/Cargo.toml @@ -22,3 +22,5 @@ hex = "0.4.3" transaction = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "038ddee8b0f57aa90e36375c69946c4eb634efeb", features = [ "serde", ] } +bip32 = "0.5.1" # only need Secp256k1, to do validation of PublicKey +ed25519-dalek = "1.0.1" diff --git a/wallet_kit_common/src/error.rs b/wallet_kit_common/src/error.rs index 2e22aec5..9617723b 100644 --- a/wallet_kit_common/src/error.rs +++ b/wallet_kit_common/src/error.rs @@ -26,6 +26,12 @@ pub enum Error { #[error("Failed to create Secp256k1 Public key from String.")] InvalidSecp256k1PublicKeyFromString, + #[error("Failed to create Secp256k1 Public key, invalid point, not on curve.")] + InvalidSecp256k1PublicKeyPointNotOnCurve, + + #[error("Failed to create Ed25519 Public key, invalid point, not on curve.")] + InvalidEd25519PublicKeyPointNotOnCurve, + #[error("Appearance id not recognized.")] InvalidAppearanceID, diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index 5046cb03..012c857b 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -14,6 +14,7 @@ impl Ed25519PrivateKey { pub fn public_key(&self) -> Ed25519PublicKey { Ed25519PublicKey::from_engine(self.0.public_key()) + .expect("Public Key from EC scalar multiplication should always be valid.") } pub fn sign(&self, msg_hash: &impl IsHash) -> Ed25519Signature { diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 0fa3d545..d63c052a 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -2,9 +2,7 @@ use crate::{ error::Error, types::{hex_32bytes::Hex32Bytes, keys::ed25519::private_key::Ed25519PrivateKey}, }; -use radix_engine_common::crypto::{ - Ed25519PublicKey as EngineEd25519PublicKey, Ed25519PublicKeyHash, Hash, -}; +use radix_engine_common::crypto::{Ed25519PublicKey as EngineEd25519PublicKey, Hash}; use serde::{Deserialize, Serialize}; use std::{ fmt::{Debug, Formatter}, @@ -16,10 +14,10 @@ use transaction::{signing::ed25519::Ed25519Signature, validation::verify_ed25519 pub struct Ed25519PublicKey(EngineEd25519PublicKey); impl Ed25519PublicKey { - pub const LENGTH: usize = 32; - - pub(crate) fn from_engine(engine: EngineEd25519PublicKey) -> Self { - Self(engine) + pub(crate) fn from_engine(engine: EngineEd25519PublicKey) -> Result { + ed25519_dalek::PublicKey::from_bytes(engine.to_vec().as_slice()) + .map(|_| Self(engine)) + .map_err(|_| Error::InvalidEd25519PublicKeyPointNotOnCurve) } pub fn to_bytes(&self) -> Vec { @@ -34,19 +32,6 @@ impl Ed25519PublicKey { pub fn is_valid(&self, signature: &Ed25519Signature, for_hash: &Hash) -> bool { verify_ed25519(for_hash, &self.0, signature) } - - pub fn to_hash(&self) -> Ed25519PublicKeyHash { - Ed25519PublicKeyHash::new_from_public_key(&self.0) - } -} - -impl Debug for Ed25519PublicKey { - // Required method - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Ed25519PublicKey") - .field("hex", &self.to_hex()) - .finish() - } } impl TryFrom<&[u8]> for Ed25519PublicKey { @@ -54,24 +39,30 @@ impl TryFrom<&[u8]> for Ed25519PublicKey { fn try_from(slice: &[u8]) -> Result { EngineEd25519PublicKey::try_from(slice) - .map(Ed25519PublicKey) .map_err(|_| Error::InvalidEd25519PublicKeyFromBytes) + .and_then(|pk| Self::from_engine(pk)) + } +} + +impl TryInto for &str { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + Ed25519PublicKey::from_str(self) } } impl Ed25519PublicKey { pub fn from_str(hex: &str) -> Result { Hex32Bytes::from_str(hex) - .and_then(|b| Ed25519PublicKey::try_from(b.to_vec().as_slice())) .map_err(|_| Error::InvalidEd25519PublicKeyFromString) + .and_then(|b| Ed25519PublicKey::try_from(b.to_vec().as_slice())) } } -impl TryInto for &str { - type Error = crate::error::Error; - - fn try_into(self) -> Result { - Ed25519PublicKey::from_str(self) +impl Debug for Ed25519PublicKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_hex()) } } @@ -79,29 +70,21 @@ impl Ed25519PublicKey { pub fn placeholder() -> Self { Self::placeholder_alice() } + pub fn placeholder_alice() -> Self { - let private_key = Ed25519PrivateKey::placeholder_alice(); - let public_key = private_key.public_key(); - assert_eq!( - public_key.to_hex(), - "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" - ); - return public_key; + Ed25519PrivateKey::placeholder_alice().public_key() } + pub fn placeholder_bob() -> Self { - let private_key = Ed25519PrivateKey::placeholder_bob(); - let public_key = private_key.public_key(); - assert_eq!( - public_key.to_hex(), - "b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde" - ); - return public_key; + Ed25519PrivateKey::placeholder_bob().public_key() } } #[cfg(test)] mod tests { - use crate::json::assert_json_value_eq_after_roundtrip; + use std::collections::BTreeSet; + + use crate::{error::Error, json::assert_json_value_eq_after_roundtrip}; use serde_json::json; use super::Ed25519PublicKey; @@ -113,4 +96,105 @@ mod tests { json!("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"), ) } + + #[test] + fn from_str() { + assert!(Ed25519PublicKey::from_str( + "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" + ) + .is_ok()); + } + + #[test] + fn bytes_roundtrip() { + let bytes: &[u8] = &[ + 0xec, 0x17, 0x2b, 0x93, 0xad, 0x5e, 0x56, 0x3b, 0xf4, 0x93, 0x2c, 0x70, 0xe1, 0x24, + 0x50, 0x34, 0xc3, 0x54, 0x67, 0xef, 0x2e, 0xfd, 0x4d, 0x64, 0xeb, 0xf8, 0x19, 0x68, + 0x34, 0x67, 0xe2, 0xbf, + ]; + let key = Ed25519PublicKey::try_from(bytes).unwrap(); + assert_eq!( + key.to_hex(), + "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" + ); + assert_eq!(key.to_bytes(), bytes); + } + + #[test] + fn placeholder_alice() { + assert_eq!( + Ed25519PublicKey::placeholder_alice().to_hex(), + "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" + ); + } + + #[test] + fn placeholder_bob() { + assert_eq!( + Ed25519PublicKey::placeholder_bob().to_hex(), + "b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde" + ); + } + + #[test] + fn invalid_hex_str() { + assert_eq!( + Ed25519PublicKey::from_str("not a valid hex string"), + Err(Error::InvalidEd25519PublicKeyFromString) + ); + } + + #[test] + fn invalid_str_too_short() { + assert_eq!( + Ed25519PublicKey::from_str("dead"), + Err(Error::InvalidEd25519PublicKeyFromString) + ); + } + + #[test] + fn invalid_key_not_on_curve() { + assert_eq!( + Ed25519PublicKey::from_str( + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ), + Err(Error::InvalidEd25519PublicKeyPointNotOnCurve) + ); + } + + #[test] + fn debug() { + assert_eq!( + format!("{:?}", Ed25519PublicKey::placeholder_alice()), + "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" + ); + } + + #[test] + fn inequality() { + assert_ne!( + Ed25519PublicKey::placeholder_alice(), + Ed25519PublicKey::placeholder_bob() + ); + } + + #[test] + fn equality() { + assert_eq!( + Ed25519PublicKey::placeholder_alice(), + Ed25519PublicKey::placeholder_alice() + ); + } + + #[test] + fn hash() { + assert_eq!( + BTreeSet::from_iter([ + Ed25519PublicKey::placeholder_alice(), + Ed25519PublicKey::placeholder_alice() + ]) + .len(), + 1 + ); + } } diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index f6643312..fa3a79b0 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -14,6 +14,7 @@ impl Secp256k1PrivateKey { pub fn public_key(&self) -> Secp256k1PublicKey { Secp256k1PublicKey::from_engine(self.0.public_key()) + .expect("Public Key from EC scalar multiplication should always be valid.") } pub fn sign(&self, msg_hash: &impl IsHash) -> Secp256k1Signature { diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 0775b3f7..aa62afd1 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -1,7 +1,6 @@ use crate::{error::Error, types::keys::secp256k1::private_key::Secp256k1PrivateKey}; -use radix_engine_common::crypto::{ - Hash, Secp256k1PublicKey as EngineSecp256k1PublicKey, Secp256k1PublicKeyHash, -}; +use bip32::secp256k1::PublicKey as BIP32Secp256k1PublicKey; +use radix_engine_common::crypto::{Hash, Secp256k1PublicKey as EngineSecp256k1PublicKey}; use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Formatter}; use transaction::{signing::secp256k1::Secp256k1Signature, validation::verify_secp256k1}; @@ -10,10 +9,10 @@ use transaction::{signing::secp256k1::Secp256k1Signature, validation::verify_sec pub struct Secp256k1PublicKey(EngineSecp256k1PublicKey); impl Secp256k1PublicKey { - pub const LENGTH: usize = 33; - - pub(crate) fn from_engine(engine: EngineSecp256k1PublicKey) -> Self { - Self(engine) + pub(crate) fn from_engine(engine: EngineSecp256k1PublicKey) -> Result { + BIP32Secp256k1PublicKey::from_sec1_bytes(engine.to_vec().as_slice()) + .map(|_| Self(engine)) + .map_err(|_| Error::InvalidSecp256k1PublicKeyPointNotOnCurve) } pub fn to_bytes(&self) -> Vec { @@ -28,10 +27,6 @@ impl Secp256k1PublicKey { pub fn is_valid(&self, signature: &Secp256k1Signature, for_hash: &Hash) -> bool { verify_secp256k1(for_hash, &self.0, signature) } - - pub fn to_hash(&self) -> Secp256k1PublicKeyHash { - Secp256k1PublicKeyHash::new_from_public_key(&self.0) - } } impl TryFrom<&[u8]> for Secp256k1PublicKey { @@ -39,8 +34,8 @@ impl TryFrom<&[u8]> for Secp256k1PublicKey { fn try_from(slice: &[u8]) -> Result { EngineSecp256k1PublicKey::try_from(slice) - .map(Secp256k1PublicKey) .map_err(|_| Error::InvalidSecp256k1PublicKeyFromBytes) + .and_then(|pk| Self::from_engine(pk)) } } @@ -61,11 +56,8 @@ impl Secp256k1PublicKey { } impl Debug for Secp256k1PublicKey { - // Required method fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Secp256k1PublicKey") - .field("compressed_hex", &self.to_hex()) - .finish() + f.write_str(&self.to_hex()) } } @@ -75,37 +67,129 @@ impl Secp256k1PublicKey { } pub fn placeholder_alice() -> Self { - let private_key = Secp256k1PrivateKey::placeholder_alice(); - let public_key = private_key.public_key(); - assert_eq!( - public_key.to_hex(), - "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" - ); - return public_key; + Secp256k1PrivateKey::placeholder_alice().public_key() } pub fn placeholder_bob() -> Self { - let private_key = Secp256k1PrivateKey::placeholder_bob(); - let public_key = private_key.public_key(); - assert_eq!( - public_key.to_hex(), - "033083620d1596d3f8988ff3270e42970dd2a031e2b9b6488052a4170ff999f3e8" - ); - return public_key; + Secp256k1PrivateKey::placeholder_bob().public_key() } } #[cfg(test)] mod tests { + use std::collections::BTreeSet; + use super::Secp256k1PublicKey; - use crate::json::assert_json_value_eq_after_roundtrip; + use crate::{error::Error, json::assert_json_value_eq_after_roundtrip}; use serde_json::json; + + #[test] + fn from_str() { + assert!(Secp256k1PublicKey::from_str( + "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + ) + .is_ok()); + } + + #[test] + fn bytes_roundtrip() { + let bytes: &[u8] = &[ + 0x02, 0x51, 0x7b, 0x88, 0x91, 0x6e, 0x7f, 0x31, 0x5b, 0xb6, 0x82, 0xf9, 0x92, 0x6b, + 0x14, 0xbc, 0x67, 0xa0, 0xe4, 0x24, 0x6f, 0x8a, 0x41, 0x9b, 0x98, 0x62, 0x69, 0xe1, + 0xa7, 0xe6, 0x1f, 0xff, 0xa7, + ]; + let key = Secp256k1PublicKey::try_from(bytes).unwrap(); + assert_eq!( + key.to_hex(), + "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + ); + assert_eq!(key.to_bytes(), bytes); + } + + #[test] + fn placeholder_alice() { + assert_eq!( + Secp256k1PublicKey::placeholder_alice().to_hex(), + "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + ); + } + + #[test] + fn placeholder_bob() { + assert_eq!( + Secp256k1PublicKey::placeholder_bob().to_hex(), + "033083620d1596d3f8988ff3270e42970dd2a031e2b9b6488052a4170ff999f3e8" + ); + } + + #[test] + fn invalid_hex_str() { + assert_eq!( + Secp256k1PublicKey::from_str("not a valid hex string"), + Err(Error::InvalidSecp256k1PublicKeyFromString) + ); + } + + #[test] + fn invalid_str_too_short() { + assert_eq!( + Secp256k1PublicKey::from_str("dead"), + Err(Error::InvalidSecp256k1PublicKeyFromBytes) + ); + } + + #[test] + fn invalid_key_not_on_curve() { + assert_eq!( + Secp256k1PublicKey::from_str( + "99deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ), + Err(Error::InvalidSecp256k1PublicKeyPointNotOnCurve) + ); + } + + #[test] + fn debug() { + assert_eq!( + format!("{:?}", Secp256k1PublicKey::placeholder_alice()), + "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + ); + } + #[test] fn json() { - let model = Secp256k1PublicKey::placeholder_bob(); + let model = Secp256k1PublicKey::placeholder(); assert_json_value_eq_after_roundtrip( &model, - json!("033083620d1596d3f8988ff3270e42970dd2a031e2b9b6488052a4170ff999f3e8"), + json!("02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7"), ) } + + #[test] + fn inequality() { + assert_ne!( + Secp256k1PublicKey::placeholder_alice(), + Secp256k1PublicKey::placeholder_bob() + ); + } + + #[test] + fn equality() { + assert_eq!( + Secp256k1PublicKey::placeholder_alice(), + Secp256k1PublicKey::placeholder_alice() + ); + } + + #[test] + fn hash() { + assert_eq!( + BTreeSet::from_iter([ + Secp256k1PublicKey::placeholder_alice(), + Secp256k1PublicKey::placeholder_alice() + ]) + .len(), + 1 + ); + } } From a9bb892ba349361e979d705ea61968d7077030ee Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 29 Nov 2023 08:52:49 +0100 Subject: [PATCH 08/39] placeholder publickeys --- .../src/types/keys/public_key.rs | 106 ++++++++++++++++-- 1 file changed, 95 insertions(+), 11 deletions(-) diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 03c3fa18..17795649 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -34,6 +34,32 @@ impl PublicKey { } } +impl PublicKey { + pub fn placeholder_secp256k1() -> Self { + Self::placeholder_secp256k1_alice() + } + + pub fn placeholder_secp256k1_alice() -> Self { + Self::Secp256k1(Secp256k1PublicKey::placeholder_alice()) + } + + pub fn placeholder_secp256k1_bob() -> Self { + Self::Secp256k1(Secp256k1PublicKey::placeholder_bob()) + } + + pub fn placeholder_ed25519() -> Self { + Self::placeholder_ed25519_alice() + } + + pub fn placeholder_ed25519_alice() -> Self { + Self::Ed25519(Ed25519PublicKey::placeholder_alice()) + } + + pub fn placeholder_ed25519_bob() -> Self { + Self::Ed25519(Ed25519PublicKey::placeholder_bob()) + } +} + impl<'de> Deserialize<'de> for PublicKey { fn deserialize>(deserializer: D) -> Result { #[derive(Deserialize, Serialize)] @@ -69,24 +95,22 @@ impl Serialize for PublicKey { #[cfg(test)] mod tests { + use std::collections::BTreeSet; + use crate::json::assert_eq_after_json_roundtrip; use super::PublicKey; #[test] fn json_roundtrip_ed25519() { - let model = PublicKey::Ed25519( - "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" - .try_into() - .unwrap(), - ); + let model = PublicKey::placeholder_ed25519_alice(); assert_eq_after_json_roundtrip( &model, r#" { "curve": "curve25519", - "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + "compressedData": "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" } "#, ); @@ -94,11 +118,7 @@ mod tests { #[test] fn json_roundtrip_secp256k1() { - let model = PublicKey::Secp256k1( - "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" - .try_into() - .unwrap(), - ); + let model = PublicKey::placeholder_secp256k1_alice(); assert_eq_after_json_roundtrip( &model, @@ -110,4 +130,68 @@ mod tests { "#, ); } + + #[test] + fn inequality_secp256k1() { + assert_ne!( + PublicKey::placeholder_secp256k1_alice(), + PublicKey::placeholder_secp256k1_bob(), + ); + } + + #[test] + fn equality_secp256k1() { + assert_eq!( + PublicKey::placeholder_secp256k1_alice(), + PublicKey::placeholder_secp256k1_alice() + ); + } + + #[test] + fn hash_secp256k1() { + assert_eq!( + BTreeSet::from_iter([ + PublicKey::placeholder_secp256k1_alice(), + PublicKey::placeholder_secp256k1_alice() + ]) + .len(), + 1 + ); + } + + #[test] + fn inequality_ed25519() { + assert_ne!( + PublicKey::placeholder_ed25519_alice(), + PublicKey::placeholder_ed25519_bob(), + ); + } + + #[test] + fn equality_ed25519() { + assert_eq!( + PublicKey::placeholder_ed25519_alice(), + PublicKey::placeholder_ed25519_alice() + ); + } + + #[test] + fn hash_ed25519() { + assert_eq!( + BTreeSet::from_iter([ + PublicKey::placeholder_ed25519_alice(), + PublicKey::placeholder_ed25519_alice() + ]) + .len(), + 1 + ); + } + + #[test] + fn inequality_different_curves() { + assert_ne!( + PublicKey::placeholder_ed25519_alice(), + PublicKey::placeholder_secp256k1_alice(), + ); + } } From ef96c01132df60d75def8a5682dfa78760fa81f8 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 29 Nov 2023 09:45:48 +0100 Subject: [PATCH 09/39] convenience methods on PublicKey and more unit tests --- .../src/types/keys/public_key.rs | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 17795649..d03db5df 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -1,5 +1,7 @@ use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; +use crate::error::Error; + use super::{ ed25519::public_key::Ed25519PublicKey, secp256k1::public_key::Secp256k1PublicKey, slip10_curve::SLIP10Curve, @@ -11,6 +13,24 @@ pub enum PublicKey { Secp256k1(Secp256k1PublicKey), } +impl PublicKey { + pub fn secp256k1_from_bytes(slice: &[u8]) -> Result { + Secp256k1PublicKey::try_from(slice).map(Self::Secp256k1) + } + + pub fn ed25519_from_bytes(slice: &[u8]) -> Result { + Ed25519PublicKey::try_from(slice).map(Self::Ed25519) + } + + pub fn secp256k1_from_str(hex: &str) -> Result { + Secp256k1PublicKey::from_str(hex).map(Self::Secp256k1) + } + + pub fn ed25519_from_str(hex: &str) -> Result { + Ed25519PublicKey::from_str(hex).map(Self::Ed25519) + } +} + impl PublicKey { pub fn curve(&self) -> SLIP10Curve { match self { @@ -142,7 +162,7 @@ mod tests { #[test] fn equality_secp256k1() { assert_eq!( - PublicKey::placeholder_secp256k1_alice(), + PublicKey::placeholder_secp256k1(), PublicKey::placeholder_secp256k1_alice() ); } @@ -170,7 +190,7 @@ mod tests { #[test] fn equality_ed25519() { assert_eq!( - PublicKey::placeholder_ed25519_alice(), + PublicKey::placeholder_ed25519(), PublicKey::placeholder_ed25519_alice() ); } @@ -194,4 +214,48 @@ mod tests { PublicKey::placeholder_secp256k1_alice(), ); } + + #[test] + fn secp256k1_bytes_roundtrip() { + let bytes: &[u8] = &[ + 0x02, 0x51, 0x7b, 0x88, 0x91, 0x6e, 0x7f, 0x31, 0x5b, 0xb6, 0x82, 0xf9, 0x92, 0x6b, + 0x14, 0xbc, 0x67, 0xa0, 0xe4, 0x24, 0x6f, 0x8a, 0x41, 0x9b, 0x98, 0x62, 0x69, 0xe1, + 0xa7, 0xe6, 0x1f, 0xff, 0xa7, + ]; + let key = PublicKey::secp256k1_from_bytes(bytes).unwrap(); + assert_eq!( + key.to_hex(), + "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + ); + assert_eq!(key.to_bytes(), bytes); + } + + #[test] + fn secp256k1_hex_roundtrip() { + let hex = "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7"; + let key = PublicKey::secp256k1_from_str(hex).unwrap(); + assert_eq!(key.to_hex(), hex); + } + + #[test] + fn ed25519_bytes_roundtrip() { + let bytes: &[u8] = &[ + 0xec, 0x17, 0x2b, 0x93, 0xad, 0x5e, 0x56, 0x3b, 0xf4, 0x93, 0x2c, 0x70, 0xe1, 0x24, + 0x50, 0x34, 0xc3, 0x54, 0x67, 0xef, 0x2e, 0xfd, 0x4d, 0x64, 0xeb, 0xf8, 0x19, 0x68, + 0x34, 0x67, 0xe2, 0xbf, + ]; + let key = PublicKey::ed25519_from_bytes(bytes).unwrap(); + assert_eq!( + key.to_hex(), + "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" + ); + assert_eq!(key.to_bytes(), bytes); + } + + #[test] + fn ed25519_hex_roundtrip() { + let hex = "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"; + let key = PublicKey::ed25519_from_str(hex).unwrap(); + assert_eq!(key.to_hex(), hex); + } } From e03f51936021223c62aec00eeaa32934cc9e0fc2 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 29 Nov 2023 09:48:30 +0100 Subject: [PATCH 10/39] more public key tests --- wallet_kit_common/src/types/keys/ed25519/public_key.rs | 8 ++++++++ wallet_kit_common/src/types/keys/secp256k1/public_key.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index d63c052a..7a5989e7 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -136,6 +136,14 @@ mod tests { ); } + #[test] + fn invalid_bytes() { + assert_eq!( + Ed25519PublicKey::try_from(&[0u8] as &[u8]), + Err(Error::InvalidEd25519PublicKeyFromBytes) + ); + } + #[test] fn invalid_hex_str() { assert_eq!( diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index aa62afd1..98dbae55 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -138,6 +138,14 @@ mod tests { ); } + #[test] + fn invalid_bytes() { + assert_eq!( + Secp256k1PublicKey::try_from(&[0u8] as &[u8]), + Err(Error::InvalidSecp256k1PublicKeyFromBytes) + ); + } + #[test] fn invalid_key_not_on_curve() { assert_eq!( From d9208cdb7792aab41dc34884b197503e692379f7 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 29 Nov 2023 10:25:17 +0100 Subject: [PATCH 11/39] Private key ergonomics and tests --- .../src/types/keys/ed25519/private_key.rs | 79 ++++++++++++++-- .../src/types/keys/ed25519/public_key.rs | 1 + .../src/types/keys/secp256k1/private_key.rs | 92 ++++++++++++++++++- 3 files changed, 161 insertions(+), 11 deletions(-) diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index 012c857b..dfd526ae 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -3,14 +3,30 @@ use transaction::signing::ed25519::{ Ed25519PrivateKey as EngineEd25519PrivateKey, Ed25519Signature, }; -use crate::{error::Error, types::hex_32bytes::Hex32Bytes}; - use super::public_key::Ed25519PublicKey; +use crate::{error::Error, types::hex_32bytes::Hex32Bytes}; +use std::fmt::{Debug, Formatter}; +/// An Ed25519 private key used to create cryptographic signatures, using +/// EdDSA scheme. pub struct Ed25519PrivateKey(EngineEd25519PrivateKey); +impl PartialEq for Ed25519PrivateKey { + fn eq(&self, other: &Self) -> bool { + self.to_bytes() == other.to_bytes() + } +} + +impl Debug for Ed25519PrivateKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_hex()) + } +} + impl Ed25519PrivateKey { - pub const LENGTH: usize = 32; + pub fn from_engine(engine: EngineEd25519PrivateKey) -> Self { + Self(engine) + } pub fn public_key(&self) -> Ed25519PublicKey { Ed25519PublicKey::from_engine(self.0.public_key()) @@ -31,16 +47,25 @@ impl Ed25519PrivateKey { pub fn from_bytes(slice: &[u8]) -> Result { EngineEd25519PrivateKey::from_bytes(slice) - .map(Ed25519PrivateKey) .map_err(|_| Error::InvalidEd25519PrivateKeyFromBytes) + .map(Self::from_engine) } pub fn from_str(hex: &str) -> Result { Hex32Bytes::from_hex(hex) - .and_then(|b| Self::from_bytes(&b.to_vec())) .map_err(|_| Error::InvalidEd25519PrivateKeyFromString) + .and_then(|b| Self::from_bytes(&b.to_vec())) + } +} + +impl TryFrom<&[u8]> for Ed25519PrivateKey { + type Error = crate::error::Error; + + fn try_from(slice: &[u8]) -> Result { + Ed25519PrivateKey::from_bytes(slice) } } + impl TryInto for &str { type Error = crate::error::Error; @@ -81,7 +106,7 @@ mod tests { use transaction::signing::ed25519::Ed25519Signature; - use crate::hash::hash; + use crate::{error::Error, hash::hash}; use super::Ed25519PrivateKey; @@ -102,4 +127,46 @@ mod tests { assert_eq!(sk.sign(&msg), sig); assert!(pk.is_valid(&sig, &msg)) } + + #[test] + fn bytes_roundtrip() { + let bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + assert_eq!( + Ed25519PrivateKey::from_bytes(bytes.as_slice()) + .unwrap() + .to_bytes(), + bytes.as_slice() + ); + } + + #[test] + fn hex_roundtrip() { + let hex = "0000000000000000000000000000000000000000000000000000000000000001"; + assert_eq!(Ed25519PrivateKey::from_str(hex).unwrap().to_hex(), hex); + } + + #[test] + fn invalid_hex() { + assert_eq!( + Ed25519PrivateKey::from_str("not hex"), + Err(Error::InvalidEd25519PrivateKeyFromString) + ); + } + + #[test] + fn invalid_hex_too_short() { + assert_eq!( + Ed25519PrivateKey::from_str("dead"), + Err(Error::InvalidEd25519PrivateKeyFromString) + ); + } + + #[test] + fn invalid_bytes() { + assert_eq!( + Ed25519PrivateKey::from_bytes(&[0u8] as &[u8]), + Err(Error::InvalidEd25519PrivateKeyFromBytes) + ); + } } diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 7a5989e7..93931726 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -10,6 +10,7 @@ use std::{ }; use transaction::{signing::ed25519::Ed25519Signature, validation::verify_ed25519}; +/// An Ed25519 public key used to verify cryptographic signatures. #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Ed25519PublicKey(EngineEd25519PublicKey); diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index fa3a79b0..e9c7c1ee 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -1,16 +1,32 @@ +use crate::{error::Error, types::hex_32bytes::Hex32Bytes}; use radix_engine_common::crypto::IsHash; use transaction::signing::secp256k1::{ Secp256k1PrivateKey as EngineSecp256k1PrivateKey, Secp256k1Signature, }; -use crate::{error::Error, types::hex_32bytes::Hex32Bytes}; - use super::public_key::Secp256k1PublicKey; +use std::fmt::{Debug, Formatter}; +/// A secp256k1 private key used to create cryptographic signatures, more specifically +/// ECDSA signatures, that offer recovery of the public key. pub struct Secp256k1PrivateKey(EngineSecp256k1PrivateKey); +impl PartialEq for Secp256k1PrivateKey { + fn eq(&self, other: &Self) -> bool { + self.to_bytes() == other.to_bytes() + } +} + +impl Debug for Secp256k1PrivateKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_hex()) + } +} + impl Secp256k1PrivateKey { - pub const LENGTH: usize = 32; + pub fn from_engine(engine: EngineSecp256k1PrivateKey) -> Self { + Self(engine) + } pub fn public_key(&self) -> Secp256k1PublicKey { Secp256k1PublicKey::from_engine(self.0.public_key()) @@ -31,8 +47,8 @@ impl Secp256k1PrivateKey { pub fn from_bytes(slice: &[u8]) -> Result { EngineSecp256k1PrivateKey::from_bytes(slice) - .map(Secp256k1PrivateKey) .map_err(|_| Error::InvalidSecp256k1PrivateKeyFromBytes) + .map(Self::from_engine) } pub fn from_str(hex: &str) -> Result { @@ -50,6 +66,14 @@ impl TryInto for &str { } } +impl TryFrom<&[u8]> for Secp256k1PrivateKey { + type Error = crate::error::Error; + + fn try_from(slice: &[u8]) -> Result { + Secp256k1PrivateKey::from_bytes(slice) + } +} + impl Secp256k1PrivateKey { pub fn placeholder() -> Self { Self::placeholder_alice() @@ -85,7 +109,7 @@ mod tests { use transaction::signing::secp256k1::Secp256k1Signature; - use crate::hash::hash; + use crate::{error::Error, hash::hash}; use super::Secp256k1PrivateKey; @@ -106,4 +130,62 @@ mod tests { assert_eq!(sk.sign(&msg), sig); assert!(pk.is_valid(&sig, &msg)) } + + #[test] + fn bytes_roundtrip() { + let bytes = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + assert_eq!( + Secp256k1PrivateKey::from_bytes(bytes.as_slice()) + .unwrap() + .to_bytes(), + bytes.as_slice() + ); + } + + #[test] + fn hex_roundtrip() { + let hex = "0000000000000000000000000000000000000000000000000000000000000001"; + assert_eq!(Secp256k1PrivateKey::from_str(hex).unwrap().to_hex(), hex); + } + + #[test] + fn invalid_hex() { + assert_eq!( + Secp256k1PrivateKey::from_str("not hex"), + Err(Error::InvalidSecp256k1PrivateKeyFromString) + ); + } + + #[test] + fn invalid_hex_too_short() { + assert_eq!( + Secp256k1PrivateKey::from_str("dead"), + Err(Error::InvalidSecp256k1PrivateKeyFromString) + ); + } + + #[test] + fn invalid_bytes() { + assert_eq!( + Secp256k1PrivateKey::from_bytes(&[0u8] as &[u8]), + Err(Error::InvalidSecp256k1PrivateKeyFromBytes) + ); + } + + #[test] + fn invalid_too_large() { + assert_eq!( + Secp256k1PrivateKey::from_bytes(&[0xFFu8; 32]), + Err(Error::InvalidSecp256k1PrivateKeyFromBytes) + ); + } + + #[test] + fn invalid_zero() { + assert_eq!( + Secp256k1PrivateKey::from_bytes(&[0u8; 32]), + Err(Error::InvalidSecp256k1PrivateKeyFromBytes) + ); + } } From 698367ec57b77bbad9f232eec0b18455384ec913 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 29 Nov 2023 12:32:48 +0100 Subject: [PATCH 12/39] eq and debug tests --- .../src/types/keys/ed25519/private_key.rs | 23 +++++++++++++++++++ .../src/types/keys/secp256k1/private_key.rs | 23 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index dfd526ae..7e76107c 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -169,4 +169,27 @@ mod tests { Err(Error::InvalidEd25519PrivateKeyFromBytes) ); } + + #[test] + fn equality() { + assert_eq!( + Ed25519PrivateKey::from_str( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + .unwrap(), + Ed25519PrivateKey::from_str( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + .unwrap() + ); + } + + #[test] + fn debug() { + let hex = "0000000000000000000000000000000000000000000000000000000000000001"; + assert_eq!( + format!("{:?}", Ed25519PrivateKey::from_str(hex).unwrap()), + hex + ); + } } diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index e9c7c1ee..461c21bd 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -188,4 +188,27 @@ mod tests { Err(Error::InvalidSecp256k1PrivateKeyFromBytes) ); } + + #[test] + fn equality() { + assert_eq!( + Secp256k1PrivateKey::from_str( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + .unwrap(), + Secp256k1PrivateKey::from_str( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + .unwrap() + ); + } + + #[test] + fn debug() { + let hex = "0000000000000000000000000000000000000000000000000000000000000001"; + assert_eq!( + format!("{:?}", Secp256k1PrivateKey::from_str(hex).unwrap()), + hex + ); + } } From 1761b0b10b1b3fe65d712912eec69156f17a72fb Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 29 Nov 2023 13:00:53 +0100 Subject: [PATCH 13/39] placeholder for mnemonic and mnemonic with placeholder and also HDFactorInstance --- .../src/bip39/mnemonic.rs | 11 ++- .../hierarchical_deterministic_public_key.rs | 9 +++ .../derivation/mnemonic_with_passphrase.rs | 32 +++++++- .../factor_instance/factor_instance.rs | 16 +++- .../factors/factor_source_id_from_hash.rs | 21 ++++- ...rarchical_deterministic_factor_instance.rs | 76 ++++++++++++++++--- profile/src/v100/factors/mod.rs | 1 + .../src/v100/factors/to_factor_source_id.rs | 5 ++ 8 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 profile/src/v100/factors/to_factor_source_id.rs diff --git a/hierarchical_deterministic/src/bip39/mnemonic.rs b/hierarchical_deterministic/src/bip39/mnemonic.rs index dc66bac2..38e06a4a 100644 --- a/hierarchical_deterministic/src/bip39/mnemonic.rs +++ b/hierarchical_deterministic/src/bip39/mnemonic.rs @@ -74,6 +74,12 @@ impl TryInto for &str { } } +impl Mnemonic { + pub fn placeholder() -> Self { + Self::from_phrase("bright club bacon dinner achieve pull grid save ramp cereal blush woman humble limb repeat video sudden possible story mask neutral prize goose mandate").expect("Valid mnemonic") + } +} + #[cfg(test)] mod tests { use bip39::Language; @@ -111,10 +117,7 @@ mod tests { #[test] fn words() { - let mnemonic: Mnemonic = - "bright club bacon dinner achieve pull grid save ramp cereal blush woman humble limb repeat video sudden possible story mask neutral prize goose mandate" - .try_into() - .unwrap(); + let mnemonic = Mnemonic::placeholder(); assert_eq!(mnemonic.words[0].word, "bright"); assert_eq!(mnemonic.words[1].word, "club"); assert_eq!(mnemonic.words[2].word, "bacon"); diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs index e68cc9d1..23a6b09b 100644 --- a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs @@ -17,3 +17,12 @@ pub struct HierarchicalDeterministicPublicKey { /// The HD derivation path for the key pair which produces virtual badges (signatures). pub derivation_path: DerivationPath, } + +impl HierarchicalDeterministicPublicKey { + pub fn new(public_key: PublicKey, derivation_path: DerivationPath) -> Self { + Self { + public_key, + derivation_path, + } + } +} diff --git a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs index 13a1e1dc..b7c83c2f 100644 --- a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs +++ b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs @@ -47,6 +47,12 @@ impl MnemonicWithPassphrase { } } +impl MnemonicWithPassphrase { + pub fn placeholder() -> Self { + Self::with_passphrase(Mnemonic::placeholder(), "radix".to_string()) + } +} + pub type PrivateKeyBytes = [u8; 32]; impl MnemonicWithPassphrase { @@ -110,9 +116,13 @@ mod tests { use crate::{ bip39::mnemonic::Mnemonic, bip44::bip44_like_path::BIP44LikePath, - cap26::{cap26_path::paths::account_path::AccountPath, cap26_repr::CAP26Repr}, + cap26::{ + cap26_key_kind::CAP26KeyKind, cap26_path::paths::account_path::AccountPath, + cap26_repr::CAP26Repr, + }, + derivation::derivation::Derivation, }; - use wallet_kit_common::json::assert_eq_after_json_roundtrip; + use wallet_kit_common::{json::assert_eq_after_json_roundtrip, network_id::NetworkID}; use super::MnemonicWithPassphrase; @@ -206,4 +216,22 @@ mod tests { "#, ); } + + #[test] + fn keys_for_placeholder() { + let mwp = MnemonicWithPassphrase::placeholder(); + let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); + let private_key = mwp.derive_private_key(path.clone()); + + assert_eq!(path.to_string(), "m/44H/1022H/1H/525H/1460H/0H"); + + assert_eq!( + "cf52dbc7bb2663223e99fb31799281b813b939440a372d0aa92eb5f5b8516003", + private_key.to_hex() + ); + assert_eq!( + "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b", + private_key.public_key().to_hex() + ); + } } diff --git a/profile/src/v100/factors/factor_instance/factor_instance.rs b/profile/src/v100/factors/factor_instance/factor_instance.rs index 664fdb38..97e9497c 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance.rs @@ -1,4 +1,9 @@ -use crate::v100::factors::factor_source_id::FactorSourceID; +use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; + +use crate::v100::factors::{ + factor_source_id::FactorSourceID, + hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, +}; use super::factor_instance_badge::FactorInstanceBadge; @@ -14,3 +19,12 @@ pub struct FactorInstance { /// is derived which produces virtual badges (signatures). pub badge: FactorInstanceBadge, } + +impl FactorInstance { + pub fn new(factor_source_id: FactorSourceID, badge: FactorInstanceBadge) -> Self { + Self { + factor_source_id, + badge, + } + } +} diff --git a/profile/src/v100/factors/factor_source_id_from_hash.rs b/profile/src/v100/factors/factor_source_id_from_hash.rs index b4b42007..6834f4b0 100644 --- a/profile/src/v100/factors/factor_source_id_from_hash.rs +++ b/profile/src/v100/factors/factor_source_id_from_hash.rs @@ -6,7 +6,10 @@ use radix_engine_common::crypto::{blake2b_256_hash, Hash}; use serde::{Deserialize, Serialize}; use wallet_kit_common::types::hex_32bytes::Hex32Bytes; -use super::factor_source_kind::FactorSourceKind; +use super::{ + factor_source_id::FactorSourceID, factor_source_kind::FactorSourceKind, + to_factor_source_id::ToFactorSourceID, +}; /// FactorSourceID from a hash. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -15,6 +18,12 @@ pub struct FactorSourceIDFromHash { pub body: Hex32Bytes, } +impl ToFactorSourceID for FactorSourceIDFromHash { + fn embed(&self) -> FactorSourceID { + FactorSourceID::Hash(self.clone()) + } +} + impl ToString for FactorSourceIDFromHash { fn to_string(&self) -> String { format!("{}:{}", self.kind.discriminant(), self.body.to_string()) @@ -59,6 +68,10 @@ mod tests { }; use wallet_kit_common::json::assert_eq_after_json_roundtrip; + use crate::v100::factors::{ + factor_source_id::FactorSourceID, to_factor_source_id::ToFactorSourceID, + }; + use super::FactorSourceIDFromHash; #[test] @@ -76,6 +89,12 @@ mod tests { ); } + #[test] + fn embed() { + let from_hash = FactorSourceIDFromHash::placeholder(); + assert_eq!(from_hash.embed(), FactorSourceID::Hash(from_hash)); + } + struct Vector { /// Given input, bip49 mnemonic phrase phrase: String, diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 0fff783d..549b5e66 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -1,9 +1,28 @@ -use hierarchical_deterministic::derivation::derivation_path::DerivationPath; -use radix_engine_common::crypto::PublicKey; +use hierarchical_deterministic::{ + cap26::{ + cap26_key_kind::CAP26KeyKind, + cap26_path::{cap26_path::CAP26Path, paths::account_path::AccountPath}, + cap26_repr::CAP26Repr, + }, + derivation::{ + derivation::Derivation, derivation_path::DerivationPath, + hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey, + mnemonic_with_passphrase::MnemonicWithPassphrase, + }, +}; use serde::{Deserialize, Serialize}; -use transaction::signing::ed25519::Ed25519PrivateKey; +use wallet_kit_common::{network_id::NetworkID, types::keys::public_key::PublicKey}; -use super::factor_source_id_from_hash::FactorSourceIDFromHash; +use crate::v100::factors::factor_source_kind::FactorSourceKind; + +use super::{ + factor_instance::{ + badge_virtual_source::FactorInstanceBadgeVirtualSource, factor_instance::FactorInstance, + factor_instance_badge::FactorInstanceBadge, + }, + factor_source_id_from_hash::FactorSourceIDFromHash, + to_factor_source_id::ToFactorSourceID, +}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -26,16 +45,49 @@ impl HierarchicalDeterministicFactorInstance { derivation_path, } } + + pub fn factor_instance(&self) -> FactorInstance { + FactorInstance::new( + self.factor_source_id.embed(), + FactorInstanceBadge::Virtual( + FactorInstanceBadgeVirtualSource::HierarchicalDeterministic( + HierarchicalDeterministicPublicKey::new( + self.public_key, + self.derivation_path.clone(), + ), + ), + ), + ) + } } impl HierarchicalDeterministicFactorInstance { pub fn placeholder() -> Self { - let private_key = Ed25519PrivateKey::from_u64(1337).unwrap(); + let mwp = MnemonicWithPassphrase::placeholder(); + let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); + let private_key = mwp.derive_private_key(path.clone()); + + assert_eq!(path.to_string(), "m/44H/1022H/1H/525H/1460H/0H"); + + assert_eq!( + "cf52dbc7bb2663223e99fb31799281b813b939440a372d0aa92eb5f5b8516003", + private_key.to_hex() + ); let public_key = private_key.public_key(); + assert_eq!( + "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b", + public_key.to_hex() + ); + let id = + FactorSourceIDFromHash::from_mnemonic_with_passphrase(FactorSourceKind::Device, mwp); + assert_eq!( + id.to_string(), + "device:3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" + ); Self::new( - FactorSourceIDFromHash::placeholder(), - PublicKey::Ed25519(public_key), - DerivationPath::placeholder(), + id, + public_key, + DerivationPath::CAP26(CAP26Path::AccountPath(path)), ) } } @@ -46,7 +98,7 @@ mod tests { use super::HierarchicalDeterministicFactorInstance; - //#[test] + // #[test] fn json_roundtrip() { let model = HierarchicalDeterministicFactorInstance::placeholder(); assert_eq_after_json_roundtrip( @@ -58,11 +110,11 @@ mod tests { "hierarchicalDeterministicPublicKey": { "publicKey": { "curve": "curve25519", - "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + "compressedData": "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" }, "derivationPath": { "scheme": "cap26", - "path": "m/44H/1022H/14H/525H/1460H/0H" + "path": "m/44H/1022H/1H/525H/1460H/0H" } }, "discriminator": "hierarchicalDeterministicPublicKey" @@ -72,7 +124,7 @@ mod tests { "factorSourceID": { "fromHash": { "kind": "device", - "body": "c9e67a9028fb3150304c77992710c35c8e479d4fa59f7c45a96ce17f6fdf1d2c" + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" }, "discriminator": "fromHash" } diff --git a/profile/src/v100/factors/mod.rs b/profile/src/v100/factors/mod.rs index 245d3d71..df88afc0 100644 --- a/profile/src/v100/factors/mod.rs +++ b/profile/src/v100/factors/mod.rs @@ -9,3 +9,4 @@ pub mod factor_source_id_from_hash; pub mod factor_source_kind; pub mod factor_sources; pub mod hierarchical_deterministic_factor_instance; +pub mod to_factor_source_id; diff --git a/profile/src/v100/factors/to_factor_source_id.rs b/profile/src/v100/factors/to_factor_source_id.rs new file mode 100644 index 00000000..75c0a8f3 --- /dev/null +++ b/profile/src/v100/factors/to_factor_source_id.rs @@ -0,0 +1,5 @@ +use super::factor_source_id::FactorSourceID; + +pub trait ToFactorSourceID { + fn embed(&self) -> FactorSourceID; +} From bf00bb8ea79b38b264d2c47689451d022b8c4b44 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 30 Nov 2023 14:05:23 +0100 Subject: [PATCH 14/39] DerivationPath CAP26 json test pass --- .../src/bip44/bip44_like_path.rs | 14 +++ .../src/cap26/cap26_path/cap26_path.rs | 56 ++++++++- .../src/cap26/cap26_path/paths/getid_path.rs | 5 + .../src/cap26/cap26_repr.rs | 5 +- .../src/derivation/derivation.rs | 1 + .../src/derivation/derivation_path.rs | 111 +++++++++++++++++- .../src/derivation/derivation_path_scheme.rs | 2 +- .../hierarchical_deterministic_public_key.rs | 62 +++++++++- profile/Cargo.toml | 1 + .../factor_instance/badge_virtual_source.rs | 4 +- .../factor_instance/factor_instance.rs | 10 +- .../factor_instance/factor_instance_badge.rs | 5 +- ...rarchical_deterministic_factor_instance.rs | 50 +++++++- 13 files changed, 299 insertions(+), 27 deletions(-) diff --git a/hierarchical_deterministic/src/bip44/bip44_like_path.rs b/hierarchical_deterministic/src/bip44/bip44_like_path.rs index 22b4103c..80a0dadd 100644 --- a/hierarchical_deterministic/src/bip44/bip44_like_path.rs +++ b/hierarchical_deterministic/src/bip44/bip44_like_path.rs @@ -86,6 +86,12 @@ impl TryInto for &str { } } +impl BIP44LikePath { + pub fn placeholder() -> Self { + Self::from_str("m/44H/1022H/0H/0/0H").expect("Valid placeholder") + } +} + #[cfg(test)] mod tests { use serde_json::json; @@ -104,6 +110,14 @@ mod tests { assert_eq!(a.to_string(), str); } + #[test] + fn placeholder() { + assert_eq!( + BIP44LikePath::placeholder().to_string(), + "m/44H/1022H/0H/0/0H" + ); + } + #[test] fn invalid_depth_1() { assert_eq!( diff --git a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs index 39b11b67..839fa66e 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs @@ -1,20 +1,41 @@ -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use crate::{ bip32::hd_path::HDPath, + cap26::cap26_repr::CAP26Repr, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, }; use super::paths::{account_path::AccountPath, getid_path::GetIDPath}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "camelCase")] -#[serde(tag = "discriminator", content = "value")] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum CAP26Path { GetID(GetIDPath), AccountPath(AccountPath), } +impl Serialize for CAP26Path { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> serde::Deserialize<'de> for CAP26Path { + fn deserialize>(d: D) -> Result { + let s = String::deserialize(d)?; + if let Ok(getid) = GetIDPath::from_str(&s) { + return Ok(CAP26Path::GetID(getid)); + } else { + AccountPath::from_str(&s) + .map(Self::AccountPath) + .map_err(de::Error::custom) + } + } +} + impl Derivation for CAP26Path { fn hd_path(&self) -> &HDPath { match self { @@ -30,6 +51,17 @@ impl Derivation for CAP26Path { } } +impl From for CAP26Path { + fn from(value: AccountPath) -> Self { + Self::AccountPath(value) + } +} +impl From for CAP26Path { + fn from(value: GetIDPath) -> Self { + Self::GetID(value) + } +} + impl CAP26Path { pub fn placeholder_account() -> Self { Self::AccountPath(AccountPath::placeholder()) @@ -76,4 +108,20 @@ mod tests { GetIDPath::default().hd_path() ); } + + #[test] + fn into_from_account_path() { + assert_eq!( + CAP26Path::AccountPath(AccountPath::placeholder()), + AccountPath::placeholder().into() + ); + } + + #[test] + fn into_from_getid_path() { + assert_eq!( + CAP26Path::GetID(GetIDPath::default()), + GetIDPath::default().into() + ); + } } diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs index 24361f6c..2908978d 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs @@ -2,6 +2,7 @@ use serde::{de, Deserializer, Serialize, Serializer}; use crate::{ bip32::{hd_path::HDPath, hd_path_component::HDPathValue}, + cap26::cap26_path::cap26_path::CAP26Path, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, hdpath_error::HDPathError, }; @@ -28,6 +29,10 @@ impl Default for GetIDPath { } impl GetIDPath { + pub fn embed(&self) -> CAP26Path { + CAP26Path::GetID(self.clone()) + } + pub const LAST_COMPONENT_VALUE: HDPathValue = 365; pub fn from_str(s: &str) -> Result { diff --git a/hierarchical_deterministic/src/cap26/cap26_repr.rs b/hierarchical_deterministic/src/cap26/cap26_repr.rs index c8f23b74..893bbf7b 100644 --- a/hierarchical_deterministic/src/cap26/cap26_repr.rs +++ b/hierarchical_deterministic/src/cap26/cap26_repr.rs @@ -9,7 +9,10 @@ use crate::{ hdpath_error::HDPathError, }; -use super::{cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind}; +use super::{ + cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, + cap26_path::cap26_path::CAP26Path, +}; pub trait CAP26Repr: Derivation { fn entity_kind() -> Option { diff --git a/hierarchical_deterministic/src/derivation/derivation.rs b/hierarchical_deterministic/src/derivation/derivation.rs index 2f14ee3e..3471e7b3 100644 --- a/hierarchical_deterministic/src/derivation/derivation.rs +++ b/hierarchical_deterministic/src/derivation/derivation.rs @@ -8,6 +8,7 @@ pub trait Derivation: Sized { fn to_string(&self) -> String { self.hd_path().to_string() } + fn scheme(&self) -> DerivationPathScheme; fn last_component(&self) -> &HDPathComponent { diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 6ff5bfba..2c2a00fa 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -1,22 +1,58 @@ -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use crate::{ bip32::hd_path::HDPath, bip44::bip44_like_path::BIP44LikePath, - cap26::cap26_path::{cap26_path::CAP26Path, paths::account_path::AccountPath}, + cap26::cap26_path::{ + cap26_path::CAP26Path, + paths::{account_path::AccountPath, getid_path::GetIDPath}, + }, }; use super::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}; /// A derivation path on either supported schemes, either Babylon (CAP26) or Olympia (BIP44Like). -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "camelCase")] -#[serde(tag = "discriminator", content = "value")] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum DerivationPath { CAP26(CAP26Path), BIP44Like(BIP44LikePath), } +impl<'de> serde::Deserialize<'de> for DerivationPath { + /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + fn deserialize>(d: D) -> Result { + #[derive(Deserialize)] + pub struct Inner { + pub scheme: DerivationPathScheme, + pub path: serde_json::Value, + } + + let inner = Inner::deserialize(d)?; + + let derivation_path = match inner.scheme { + DerivationPathScheme::Cap26 => DerivationPath::CAP26( + CAP26Path::deserialize(inner.path).map_err(serde::de::Error::custom)?, + ), + DerivationPathScheme::Bip44Olympia => DerivationPath::BIP44Like( + BIP44LikePath::deserialize(inner.path).map_err(serde::de::Error::custom)?, + ), + }; + Ok(derivation_path) + } +} + +impl Serialize for DerivationPath { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("DerivationPath", 2)?; + state.serialize_field("scheme", &self.scheme())?; + state.serialize_field("path", &self.hd_path().to_string())?; + state.end() + } +} + impl DerivationPath { pub fn placeholder() -> Self { Self::CAP26(CAP26Path::AccountPath(AccountPath::placeholder())) @@ -44,11 +80,35 @@ impl DerivationPath { } } +impl From for DerivationPath { + fn from(value: AccountPath) -> Self { + Self::CAP26(value.into()) + } +} + +impl From for DerivationPath { + fn from(value: GetIDPath) -> Self { + Self::CAP26(value.into()) + } +} + +impl From for DerivationPath { + fn from(value: BIP44LikePath) -> Self { + Self::BIP44Like(value) + } +} + #[cfg(test)] mod tests { + use wallet_kit_common::{json::assert_eq_after_json_roundtrip, network_id::NetworkID}; + use crate::{ bip44::bip44_like_path::BIP44LikePath, - cap26::cap26_path::paths::account_path::AccountPath, + cap26::{ + cap26_key_kind::CAP26KeyKind, + cap26_path::paths::{account_path::AccountPath, getid_path::GetIDPath}, + cap26_repr::CAP26Repr, + }, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, }; @@ -84,4 +144,43 @@ mod tests { BIP44LikePath::new(0).hd_path() ); } + + #[test] + fn into_from_account_bip44_path() { + assert_eq!( + DerivationPath::BIP44Like(BIP44LikePath::placeholder()), + BIP44LikePath::placeholder().into() + ); + } + + #[test] + fn into_from_account_cap26_path() { + assert_eq!( + DerivationPath::CAP26(AccountPath::placeholder().into()), + AccountPath::placeholder().into() + ); + } + + #[test] + fn into_from_getid_path() { + assert_eq!( + DerivationPath::CAP26(GetIDPath::default().into()), + GetIDPath::default().into() + ); + } + + #[test] + fn json() { + let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); + let model: DerivationPath = path.into(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0H" + } + "#, + ); + } } diff --git a/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs b/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs index 865367a3..d1913c56 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs @@ -6,7 +6,7 @@ use wallet_kit_common::types::keys::slip10_curve::SLIP10Curve; /// a new scheme call Cap26 but we also need to support BIP44-like used /// by Olympia. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "lowercase")] pub enum DerivationPathScheme { /// A BIP32 based derivation path scheme, using SLIP10. Cap26, diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs index 23a6b09b..afd12cc5 100644 --- a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs @@ -1,7 +1,17 @@ +use bip39::Mnemonic; use serde::{Deserialize, Serialize}; -use wallet_kit_common::types::keys::public_key::PublicKey; +use wallet_kit_common::{network_id::NetworkID, types::keys::public_key::PublicKey}; -use crate::derivation::derivation_path::DerivationPath; +use crate::{ + cap26::{ + cap26_key_kind::CAP26KeyKind, + cap26_path::{cap26_path::CAP26Path, paths::account_path::AccountPath}, + cap26_repr::CAP26Repr, + }, + derivation::{derivation::Derivation, derivation_path::DerivationPath}, +}; + +use super::mnemonic_with_passphrase::MnemonicWithPassphrase; /// The **source** of a virtual hierarchical deterministic badge, contains a /// derivation path and public key, from which a private key is derived which @@ -26,3 +36,51 @@ impl HierarchicalDeterministicPublicKey { } } } + +impl HierarchicalDeterministicPublicKey { + pub fn placeholder() -> Self { + let mwp = MnemonicWithPassphrase::placeholder(); + let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); + let private_key = mwp.derive_private_key(path.clone()); + + assert_eq!(path.to_string(), "m/44H/1022H/1H/525H/1460H/0H"); + + assert_eq!( + "cf52dbc7bb2663223e99fb31799281b813b939440a372d0aa92eb5f5b8516003", + private_key.to_hex() + ); + let public_key = private_key.public_key(); + assert_eq!( + "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b", + public_key.to_hex() + ); + Self::new(public_key, path.into()) + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::HierarchicalDeterministicPublicKey; + + #[test] + fn json() { + let model = HierarchicalDeterministicPublicKey::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "publicKey": { + "curve": "curve25519", + "compressedData": "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0H" + } + } + "#, + ); + } +} diff --git a/profile/Cargo.toml b/profile/Cargo.toml index e50f5dfb..bc334740 100644 --- a/profile/Cargo.toml +++ b/profile/Cargo.toml @@ -23,3 +23,4 @@ nutype = { version = "0.4.0", features = ["serde"] } transaction = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "038ddee8b0f57aa90e36375c69946c4eb634efeb", features = [ "serde", ] } +enum-as-inner = "0.6.0" diff --git a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs index 55cb6c25..1ff5660f 100644 --- a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs +++ b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs @@ -1,5 +1,7 @@ +use enum_as_inner::EnumAsInner; use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; - +use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, EnumAsInner, Clone, Debug, PartialEq, Eq)] pub enum FactorInstanceBadgeVirtualSource { HierarchicalDeterministic(HierarchicalDeterministicPublicKey), } diff --git a/profile/src/v100/factors/factor_instance/factor_instance.rs b/profile/src/v100/factors/factor_instance/factor_instance.rs index 97e9497c..d002d1d5 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance.rs @@ -1,12 +1,10 @@ -use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; - -use crate::v100::factors::{ - factor_source_id::FactorSourceID, - hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, -}; +use serde::{Deserialize, Serialize}; use super::factor_instance_badge::FactorInstanceBadge; +use crate::v100::factors::factor_source_id::FactorSourceID; +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] pub struct FactorInstance { /// The ID of the `FactorSource` that was used to produce this /// factor instance. We will lookup the `FactorSource` in the diff --git a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs index 88775c49..73235a7b 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs @@ -1,8 +1,11 @@ -use super::badge_virtual_source::FactorInstanceBadgeVirtualSource; +use serde::{Deserialize, Serialize}; +use super::badge_virtual_source::FactorInstanceBadgeVirtualSource; +use enum_as_inner::EnumAsInner; /// Either a "physical" badge (NFT) or some source for recreation of a producer /// of a virtual badge (signature), e.g. a HD derivation path, from which a private key /// is derived which produces virtual badges (signatures). +#[derive(Serialize, Deserialize, EnumAsInner, Clone, Debug, PartialEq, Eq)] pub enum FactorInstanceBadge { Virtual(FactorInstanceBadgeVirtualSource), } diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 549b5e66..175b445f 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -10,8 +10,8 @@ use hierarchical_deterministic::{ mnemonic_with_passphrase::MnemonicWithPassphrase, }, }; -use serde::{Deserialize, Serialize}; -use wallet_kit_common::{network_id::NetworkID, types::keys::public_key::PublicKey}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use wallet_kit_common::{error::Error, network_id::NetworkID, types::keys::public_key::PublicKey}; use crate::v100::factors::factor_source_kind::FactorSourceKind; @@ -24,10 +24,8 @@ use super::{ to_factor_source_id::ToFactorSourceID, }; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct HierarchicalDeterministicFactorInstance { - #[serde(rename = "factorSourceID")] pub factor_source_id: FactorSourceIDFromHash, pub public_key: PublicKey, pub derivation_path: DerivationPath, @@ -46,6 +44,28 @@ impl HierarchicalDeterministicFactorInstance { } } + pub fn try_from_factor_instance(factor_instance: FactorInstance) -> Result { + // match factor_instance.badge { + // // pub enum FactorInstanceBadge { + // // Virtual(FactorInstanceBadgeVirtualSource), + // // } + // FactorInstanceBadge::Virtual(v) => match v { + // FactorInstanceBadgeVirtualSource::HierarchicalDeterministic(hd) => { + + // } + // }, + // } + todo!() + // guard case let .virtual(.hierarchicalDeterministic(badge)) = factorInstance.badge else { + // throw BadgeIsNotVirtualHierarchicalDeterministic() + // } + // try self.init( + // factorSourceID: factorInstance.factorSourceID, + // publicKey: badge.publicKey, + // derivationPath: badge.derivationPath + // ) + } + pub fn factor_instance(&self) -> FactorInstance { FactorInstance::new( self.factor_source_id.embed(), @@ -61,6 +81,26 @@ impl HierarchicalDeterministicFactorInstance { } } +impl Serialize for HierarchicalDeterministicFactorInstance { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + self.factor_instance().serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for HierarchicalDeterministicFactorInstance { + fn deserialize>( + d: D, + ) -> Result { + FactorInstance::deserialize(d).and_then(|fi| { + HierarchicalDeterministicFactorInstance::try_from_factor_instance(fi) + .map_err(de::Error::custom) + }) + } +} + impl HierarchicalDeterministicFactorInstance { pub fn placeholder() -> Self { let mwp = MnemonicWithPassphrase::placeholder(); From ae093fb5f797959985258c826da0e06b14a5b84d Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 30 Nov 2023 14:07:40 +0100 Subject: [PATCH 15/39] DerivationPath json test all --- .../src/derivation/derivation_path.rs | 32 ++++++++++++++++++- .../src/derivation/derivation_path_scheme.rs | 3 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 2c2a00fa..37013c7b 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -170,7 +170,7 @@ mod tests { } #[test] - fn json() { + fn json_cap26_account() { let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); let model: DerivationPath = path.into(); assert_eq_after_json_roundtrip( @@ -183,4 +183,34 @@ mod tests { "#, ); } + + #[test] + fn json_cap26_getid() { + let path = GetIDPath::default(); + let model: DerivationPath = path.into(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "scheme": "cap26", + "path": "m/44H/1022H/365H" + } + "#, + ); + } + + #[test] + fn json_bip44like_account() { + let path = BIP44LikePath::placeholder(); + let model: DerivationPath = path.into(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "scheme": "bip44Olympia", + "path": "m/44H/1022H/0H/0/0H" + } + "#, + ); + } } diff --git a/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs b/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs index d1913c56..e7a8c2bc 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path_scheme.rs @@ -6,14 +6,15 @@ use wallet_kit_common::types::keys::slip10_curve::SLIP10Curve; /// a new scheme call Cap26 but we also need to support BIP44-like used /// by Olympia. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "lowercase")] pub enum DerivationPathScheme { /// A BIP32 based derivation path scheme, using SLIP10. + #[serde(rename = "cap26")] Cap26, /// A BIP32 based similar to BIP44, but not strict BIP44 since the /// last path component is hardened (a mistake made during Olympia), /// used to support legacy accounts imported from Olympia wallet. + #[serde(rename = "bip44Olympia")] Bip44Olympia, } From f16c0ed8180765c7e4ecc3adb5a8df8590f70183 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 30 Nov 2023 15:07:20 +0100 Subject: [PATCH 16/39] JSON impl of many models and tests --- .../src/cap26/cap26_path/cap26_path.rs | 2 +- .../src/cap26/cap26_repr.rs | 5 +- .../hierarchical_deterministic_public_key.rs | 4 +- profile/src/v100/entity/account/account.rs | 8 +- .../entity_security_state.rs | 93 +++++++++---------- .../unsecured_entity_control.rs | 10 +- .../factor_instance/badge_virtual_source.rs | 71 +++++++++++++- .../factor_instance/factor_instance.rs | 51 +++++++++- .../factor_instance/factor_instance_badge.rs | 73 ++++++++++++++- profile/src/v100/factors/factor_source.rs | 2 +- profile/src/v100/factors/factor_source_id.rs | 21 +++-- .../factors/factor_source_id_from_hash.rs | 25 +++-- ...rarchical_deterministic_factor_instance.rs | 52 ++++++----- wallet_kit_common/src/error.rs | 6 ++ wallet_kit_common/src/json.rs | 7 +- wallet_kit_common/src/types/hex_32bytes.rs | 18 +++- 16 files changed, 339 insertions(+), 109 deletions(-) diff --git a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs index 839fa66e..f4dcef81 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs @@ -1,4 +1,4 @@ -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{de, Deserializer, Serialize, Serializer}; use crate::{ bip32::hd_path::HDPath, diff --git a/hierarchical_deterministic/src/cap26/cap26_repr.rs b/hierarchical_deterministic/src/cap26/cap26_repr.rs index 893bbf7b..c8f23b74 100644 --- a/hierarchical_deterministic/src/cap26/cap26_repr.rs +++ b/hierarchical_deterministic/src/cap26/cap26_repr.rs @@ -9,10 +9,7 @@ use crate::{ hdpath_error::HDPathError, }; -use super::{ - cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, - cap26_path::cap26_path::CAP26Path, -}; +use super::{cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind}; pub trait CAP26Repr: Derivation { fn entity_kind() -> Option { diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs index afd12cc5..2df8a0a8 100644 --- a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs @@ -1,11 +1,9 @@ -use bip39::Mnemonic; use serde::{Deserialize, Serialize}; use wallet_kit_common::{network_id::NetworkID, types::keys::public_key::PublicKey}; use crate::{ cap26::{ - cap26_key_kind::CAP26KeyKind, - cap26_path::{cap26_path::CAP26Path, paths::account_path::AccountPath}, + cap26_key_kind::CAP26KeyKind, cap26_path::paths::account_path::AccountPath, cap26_repr::CAP26Repr, }, derivation::{derivation::Derivation, derivation_path::DerivationPath}, diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index 2b379308..b764ce41 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -38,6 +38,7 @@ use super::{ #[serde(rename_all = "camelCase")] pub struct Account { /// The ID of the network this account can be used with. + #[serde(rename = "networkID")] pub network_id: NetworkID, /// A globally unique identifier of this account, being a human readable @@ -64,6 +65,7 @@ pub struct Account { /// The visual cue user learns to associated this account with, typically /// a beautiful colorful gradient. + #[serde(rename = "appearanceID")] appearance_id: RefCell, /// An order set of `EntityFlag`s used to describe certain Off-ledger @@ -425,11 +427,11 @@ mod tests { "hierarchicalDeterministicPublicKey": { "publicKey": { "curve": "curve25519", - "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + "compressedData": "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" }, "derivationPath": { "scheme": "cap26", - "path": "m/44H/1022H/14H/525H/1460H/0H" + "path": "m/44H/1022H/1H/525H/1460H/0H" } }, "discriminator": "hierarchicalDeterministicPublicKey" @@ -439,7 +441,7 @@ mod tests { "factorSourceID": { "fromHash": { "kind": "device", - "body": "c9e67a9028fb3150304c77992710c35c8e479d4fa59f7c45a96ce17f6fdf1d2c" + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" }, "discriminator": "fromHash" } diff --git a/profile/src/v100/entity_security_state/entity_security_state.rs b/profile/src/v100/entity_security_state/entity_security_state.rs index 587dc66b..a3198b98 100644 --- a/profile/src/v100/entity_security_state/entity_security_state.rs +++ b/profile/src/v100/entity_security_state/entity_security_state.rs @@ -31,9 +31,8 @@ impl Serialize for EntitySecurityState { let mut state = serializer.serialize_struct("EntitySecurityState", 2)?; match self { EntitySecurityState::Unsecured(control) => { - let discriminant = "unsecuredEntityControl"; - state.serialize_field("discriminator", discriminant)?; - state.serialize_field(discriminant, control)?; + state.serialize_field("discriminator", "unsecured")?; + state.serialize_field("unsecuredEntityControl", control)?; } } state.end() @@ -48,49 +47,47 @@ impl EntitySecurityState { #[cfg(test)] mod tests { - // #[test] - // fn json_roundtrip() { - // // let model = Account::with_values( - // // "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" - // // .try_into() - // // .unwrap(), - // // "Zaba 0".try_into().unwrap(), - // // 0.try_into().unwrap(), - // // ); - // assert_eq_after_json_roundtrip( - // &model, - // r#" - // { - // "unsecuredEntityControl": { - // "transactionSigning": { - // "badge": { - // "virtualSource": { - // "hierarchicalDeterministicPublicKey": { - // "publicKey": { - // "curve": "curve25519", - // "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" - // }, - // "derivationPath": { - // "scheme": "cap26", - // "path": "m/44H/1022H/14H/525H/1460H/0H" - // } - // }, - // "discriminator": "hierarchicalDeterministicPublicKey" - // }, - // "discriminator": "virtualSource" - // }, - // "factorSourceID": { - // "fromHash": { - // "kind": "device", - // "body": "c9e67a9028fb3150304c77992710c35c8e479d4fa59f7c45a96ce17f6fdf1d2c" - // }, - // "discriminator": "fromHash" - // } - // } - // }, - // "discriminator": "unsecured" - // } - // "#, - // ); - // } + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::EntitySecurityState; + + #[test] + fn json_roundtrip() { + let model = EntitySecurityState::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "unsecuredEntityControl": { + "transactionSigning": { + "badge": { + "virtualSource": { + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0H" + } + }, + "discriminator": "hierarchicalDeterministicPublicKey" + }, + "discriminator": "virtualSource" + }, + "factorSourceID": { + "fromHash": { + "kind": "device", + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" + }, + "discriminator": "fromHash" + } + } + }, + "discriminator": "unsecured" + } + "#, + ); + } } diff --git a/profile/src/v100/entity_security_state/unsecured_entity_control.rs b/profile/src/v100/entity_security_state/unsecured_entity_control.rs index 81659a15..4c4df979 100644 --- a/profile/src/v100/entity_security_state/unsecured_entity_control.rs +++ b/profile/src/v100/entity_security_state/unsecured_entity_control.rs @@ -14,6 +14,7 @@ pub struct UnsecuredEntityControl { pub transaction_signing: HierarchicalDeterministicFactorInstance, /// The factor instance which can be used for ROLA. + #[serde(skip_serializing_if = "Option::is_none")] pub authentication_signing: Option, } @@ -46,7 +47,8 @@ mod tests { use wallet_kit_common::json::assert_eq_after_json_roundtrip; use super::UnsecuredEntityControl; - // #[test] + + #[test] fn json_roundtrip() { let model = UnsecuredEntityControl::placeholder(); assert_eq_after_json_roundtrip( @@ -59,11 +61,11 @@ mod tests { "hierarchicalDeterministicPublicKey": { "publicKey": { "curve": "curve25519", - "compressedData": "3feb8194ead2e526fbcc4c1673a7a8b29d8cee0b32bb9393692f739821dd256b" + "compressedData": "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" }, "derivationPath": { "scheme": "cap26", - "path": "m/44H/1022H/14H/525H/1460H/0H" + "path": "m/44H/1022H/1H/525H/1460H/0H" } }, "discriminator": "hierarchicalDeterministicPublicKey" @@ -73,7 +75,7 @@ mod tests { "factorSourceID": { "fromHash": { "kind": "device", - "body": "c9e67a9028fb3150304c77992710c35c8e479d4fa59f7c45a96ce17f6fdf1d2c" + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" }, "discriminator": "fromHash" } diff --git a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs index 1ff5660f..f037d621 100644 --- a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs +++ b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs @@ -1,7 +1,76 @@ use enum_as_inner::EnumAsInner; use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; #[derive(Serialize, Deserialize, EnumAsInner, Clone, Debug, PartialEq, Eq)] +#[serde(remote = "Self")] pub enum FactorInstanceBadgeVirtualSource { + #[serde(rename = "hierarchicalDeterministicPublicKey")] HierarchicalDeterministic(HierarchicalDeterministicPublicKey), } + +impl<'de> Deserialize<'de> for FactorInstanceBadgeVirtualSource { + fn deserialize>(deserializer: D) -> Result { + // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 + #[derive(Deserialize, Serialize)] + struct Wrapper { + #[serde(rename = "discriminator")] + _ignore: String, + #[serde(flatten, with = "FactorInstanceBadgeVirtualSource")] + inner: FactorInstanceBadgeVirtualSource, + } + Wrapper::deserialize(deserializer).map(|w| w.inner) + } +} + +impl Serialize for FactorInstanceBadgeVirtualSource { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("FactorInstanceBadgeVirtualSource", 2)?; + match self { + FactorInstanceBadgeVirtualSource::HierarchicalDeterministic(hd) => { + let discriminant = "hierarchicalDeterministicPublicKey"; + state.serialize_field("discriminator", discriminant)?; + state.serialize_field(discriminant, hd)?; + } + } + state.end() + } +} + +impl FactorInstanceBadgeVirtualSource { + pub fn placeholder() -> Self { + Self::HierarchicalDeterministic(HierarchicalDeterministicPublicKey::placeholder()) + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::FactorInstanceBadgeVirtualSource; + #[test] + fn json_roundtrip() { + let model = FactorInstanceBadgeVirtualSource::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0H" + } + }, + "discriminator": "hierarchicalDeterministicPublicKey" + } + + "#, + ); + } +} diff --git a/profile/src/v100/factors/factor_instance/factor_instance.rs b/profile/src/v100/factors/factor_instance/factor_instance.rs index d002d1d5..cb89ecec 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance.rs @@ -4,12 +4,12 @@ use super::factor_instance_badge::FactorInstanceBadge; use crate::v100::factors::factor_source_id::FactorSourceID; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] pub struct FactorInstance { /// The ID of the `FactorSource` that was used to produce this /// factor instance. We will lookup the `FactorSource` in the /// `Profile` and can present user with instruction to re-access /// this factor source in order control the `badge`. + #[serde(rename = "factorSourceID")] pub factor_source_id: FactorSourceID, /// Either a "physical" badge (NFT) or some source for recreation of a producer @@ -25,4 +25,53 @@ impl FactorInstance { badge, } } + + pub fn placeholder() -> Self { + Self::new( + FactorSourceID::placeholder(), + FactorInstanceBadge::placeholder(), + ) + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::FactorInstance; + + #[test] + fn json_roundtrip() { + let model = FactorInstance::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "badge": { + "virtualSource": { + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0H" + } + }, + "discriminator": "hierarchicalDeterministicPublicKey" + }, + "discriminator": "virtualSource" + }, + "factorSourceID": { + "fromHash": { + "kind": "device", + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" + }, + "discriminator": "fromHash" + } + } + "#, + ); + } } diff --git a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs index 73235a7b..f99e4db7 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use super::badge_virtual_source::FactorInstanceBadgeVirtualSource; use enum_as_inner::EnumAsInner; @@ -6,6 +6,77 @@ use enum_as_inner::EnumAsInner; /// of a virtual badge (signature), e.g. a HD derivation path, from which a private key /// is derived which produces virtual badges (signatures). #[derive(Serialize, Deserialize, EnumAsInner, Clone, Debug, PartialEq, Eq)] +#[serde(remote = "Self")] pub enum FactorInstanceBadge { + #[serde(rename = "virtualSource")] Virtual(FactorInstanceBadgeVirtualSource), } + +impl FactorInstanceBadge { + pub fn placeholder() -> Self { + FactorInstanceBadge::Virtual(FactorInstanceBadgeVirtualSource::placeholder()) + } +} + +impl<'de> Deserialize<'de> for FactorInstanceBadge { + fn deserialize>(deserializer: D) -> Result { + // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 + #[derive(Deserialize, Serialize)] + struct Wrapper { + #[serde(rename = "discriminator")] + _ignore: String, + #[serde(flatten, with = "FactorInstanceBadge")] + inner: FactorInstanceBadge, + } + Wrapper::deserialize(deserializer).map(|w| w.inner) + } +} + +impl Serialize for FactorInstanceBadge { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("FactorInstanceBadge", 2)?; + match self { + FactorInstanceBadge::Virtual(virtual_source) => { + let discriminant = "virtualSource"; + state.serialize_field("discriminator", discriminant)?; + state.serialize_field(discriminant, virtual_source)?; + } + } + state.end() + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::FactorInstanceBadge; + #[test] + fn json_roundtrip() { + let model = FactorInstanceBadge::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "virtualSource": { + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0H" + } + }, + "discriminator": "hierarchicalDeterministicPublicKey" + }, + "discriminator": "virtualSource" + } + "#, + ); + } +} diff --git a/profile/src/v100/factors/factor_source.rs b/profile/src/v100/factors/factor_source.rs index dcfb70cf..9d654c84 100644 --- a/profile/src/v100/factors/factor_source.rs +++ b/profile/src/v100/factors/factor_source.rs @@ -58,7 +58,7 @@ mod tests { "device": { "id": { "kind": "device", - "body": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" }, "common": { "flags": ["main"], diff --git a/profile/src/v100/factors/factor_source_id.rs b/profile/src/v100/factors/factor_source_id.rs index dea759d0..f3670c84 100644 --- a/profile/src/v100/factors/factor_source_id.rs +++ b/profile/src/v100/factors/factor_source_id.rs @@ -1,15 +1,15 @@ -use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; - use super::{ factor_source_id_from_address::FactorSourceIDFromAddress, factor_source_id_from_hash::FactorSourceIDFromHash, }; +use enum_as_inner::EnumAsInner; +use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; /// A unique and stable identifier of a FactorSource, e.g. a /// DeviceFactorSource being a mnemonic securely stored in a /// device (phone), where the ID of it is the hash of a special /// key derived near the root of it. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Serialize, Deserialize, EnumAsInner, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[serde(remote = "Self")] pub enum FactorSourceID { #[serde(rename = "fromHash")] @@ -54,27 +54,30 @@ impl Serialize for FactorSourceID { } } +impl FactorSourceID { + pub fn placeholder() -> Self { + FactorSourceID::Hash(FactorSourceIDFromHash::placeholder()) + } +} + #[cfg(test)] mod tests { use wallet_kit_common::json::assert_eq_after_json_roundtrip; - use crate::v100::factors::{ - factor_source_id_from_address::FactorSourceIDFromAddress, - factor_source_id_from_hash::FactorSourceIDFromHash, - }; + use crate::v100::factors::factor_source_id_from_address::FactorSourceIDFromAddress; use super::FactorSourceID; #[test] fn json_roundtrip_from_hash() { - let model = FactorSourceID::Hash(FactorSourceIDFromHash::placeholder()); + let model = FactorSourceID::placeholder(); assert_eq_after_json_roundtrip( &model, r#" { "fromHash": { "kind": "device", - "body": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" }, "discriminator" : "fromHash" } diff --git a/profile/src/v100/factors/factor_source_id_from_hash.rs b/profile/src/v100/factors/factor_source_id_from_hash.rs index 6834f4b0..5a1f60ab 100644 --- a/profile/src/v100/factors/factor_source_id_from_hash.rs +++ b/profile/src/v100/factors/factor_source_id_from_hash.rs @@ -53,11 +53,9 @@ impl FactorSourceIDFromHash { } impl FactorSourceIDFromHash { + /// This is using the MnemonicWithPass pub fn placeholder() -> Self { - Self { - kind: FactorSourceKind::Device, - body: Hex32Bytes::placeholder(), - } + Self::new_for_device(MnemonicWithPassphrase::placeholder()) } } @@ -75,7 +73,7 @@ mod tests { use super::FactorSourceIDFromHash; #[test] - fn json_roundtrip() { + fn json_roundtrip_placeholder() { let model = FactorSourceIDFromHash::placeholder(); assert_eq_after_json_roundtrip( @@ -83,7 +81,22 @@ mod tests { r#" { "kind": "device", - "body": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" + } + "#, + ); + } + + #[test] + fn json_from_placeholder_mnemonic() { + let mwp = MnemonicWithPassphrase::placeholder(); + let model = FactorSourceIDFromHash::new_for_device(mwp); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "kind": "device", + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" } "#, ); diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 175b445f..2795d869 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -10,7 +10,7 @@ use hierarchical_deterministic::{ mnemonic_with_passphrase::MnemonicWithPassphrase, }, }; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{de, Deserializer, Serialize, Serializer}; use wallet_kit_common::{error::Error, network_id::NetworkID, types::keys::public_key::PublicKey}; use crate::v100::factors::factor_source_kind::FactorSourceKind; @@ -20,6 +20,7 @@ use super::{ badge_virtual_source::FactorInstanceBadgeVirtualSource, factor_instance::FactorInstance, factor_instance_badge::FactorInstanceBadge, }, + factor_source_id::FactorSourceID, factor_source_id_from_hash::FactorSourceIDFromHash, to_factor_source_id::ToFactorSourceID, }; @@ -44,26 +45,35 @@ impl HierarchicalDeterministicFactorInstance { } } + pub fn try_from( + factor_source_id: FactorSourceID, + public_key: PublicKey, + derivation_path: DerivationPath, + ) -> Result { + let factor_source_id = factor_source_id + .as_hash() + .ok_or(Error::FactorSourceIDNotFromHash)?; + Ok(Self::new( + factor_source_id.clone(), + public_key, + derivation_path, + )) + } + pub fn try_from_factor_instance(factor_instance: FactorInstance) -> Result { - // match factor_instance.badge { - // // pub enum FactorInstanceBadge { - // // Virtual(FactorInstanceBadgeVirtualSource), - // // } - // FactorInstanceBadge::Virtual(v) => match v { - // FactorInstanceBadgeVirtualSource::HierarchicalDeterministic(hd) => { - - // } - // }, - // } - todo!() - // guard case let .virtual(.hierarchicalDeterministic(badge)) = factorInstance.badge else { - // throw BadgeIsNotVirtualHierarchicalDeterministic() - // } - // try self.init( - // factorSourceID: factorInstance.factorSourceID, - // publicKey: badge.publicKey, - // derivationPath: badge.derivationPath - // ) + let virtual_source = factor_instance + .badge + .as_virtual() + .ok_or(Error::BadgeIsNotVirtualHierarchicalDeterministic)?; + let badge = virtual_source + .as_hierarchical_deterministic() + .ok_or(Error::BadgeIsNotVirtualHierarchicalDeterministic)?; + + Self::try_from( + factor_instance.factor_source_id, + badge.public_key, + badge.derivation_path.clone(), + ) } pub fn factor_instance(&self) -> FactorInstance { @@ -138,7 +148,7 @@ mod tests { use super::HierarchicalDeterministicFactorInstance; - // #[test] + #[test] fn json_roundtrip() { let model = HierarchicalDeterministicFactorInstance::placeholder(); assert_eq_after_json_roundtrip( diff --git a/wallet_kit_common/src/error.rs b/wallet_kit_common/src/error.rs index 9617723b..b2ac6266 100644 --- a/wallet_kit_common/src/error.rs +++ b/wallet_kit_common/src/error.rs @@ -76,4 +76,10 @@ pub enum Error { #[error("Invalid bip39 word count : '{0}'")] InvalidBIP39WordCount(usize), + + #[error("Failed to convert FactorInstance into HierarchicalDeterministicFactorInstance, badge is not virtual HD.")] + BadgeIsNotVirtualHierarchicalDeterministic, + + #[error("Failed to create FactorSourceIDFromHash from FactorSourceID")] + FactorSourceIDNotFromHash, } diff --git a/wallet_kit_common/src/json.rs b/wallet_kit_common/src/json.rs index 497babc6..ee10f408 100644 --- a/wallet_kit_common/src/json.rs +++ b/wallet_kit_common/src/json.rs @@ -1,5 +1,8 @@ use core::fmt::Debug; -use serde::{de::DeserializeOwned, ser::Serialize}; +use serde::{ + de::{self, DeserializeOwned}, + ser::Serialize, +}; use serde_json::Value; fn base_assert_equality_after_json_roundtrip(model: &T, json: Value, expect_eq: bool) @@ -9,9 +12,11 @@ where let serialized = serde_json::to_value(&model).unwrap(); let deserialized: T = serde_json::from_value(json.clone()).unwrap(); if expect_eq { + assert_eq!(model, &deserialized); assert_eq!(&deserialized, model, "Expected `model: T` and `T` deserialized from `json_string`, to be equal, but they were not."); assert_eq!(serialized, json, "Expected `json` (string) and json serialized from `model to be equal`, but they were not."); } else { + assert_ne!(model, &deserialized); assert_ne!(&deserialized, model, "Expected difference between `model: T` and `T` deserialized from `json_string`, but they were unexpectedly equal."); assert_ne!(serialized, json, "Expected difference between `json` (string) and json serialized from `model`, but they were unexpectedly equal."); } diff --git a/wallet_kit_common/src/types/hex_32bytes.rs b/wallet_kit_common/src/types/hex_32bytes.rs index d10f03e8..8f40e0ca 100644 --- a/wallet_kit_common/src/types/hex_32bytes.rs +++ b/wallet_kit_common/src/types/hex_32bytes.rs @@ -1,16 +1,24 @@ -use std::str::FromStr; +use std::{ + fmt::{Debug, Display, Formatter}, + str::FromStr, +}; use radix_engine_common::crypto::{Hash, IsHash}; use serde::{de, Deserializer, Serialize, Serializer}; use crate::error::Error; -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Hex32Bytes([u8; 32]); -impl ToString for Hex32Bytes { - fn to_string(&self) -> String { - hex::encode(self.0) +impl Display for Hex32Bytes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} +impl Debug for Hex32Bytes { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&hex::encode(self.0)) } } From 70efbb33faf1d7866c141dd20ac91abbb0ccc8ea Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 30 Nov 2023 19:11:50 +0100 Subject: [PATCH 17/39] json test for account --- .../src/derivation/derivation_path.rs | 12 +++++-- profile/src/v100/entity/account/account.rs | 33 +++---------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 37013c7b..b845c82d 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -1,5 +1,6 @@ use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; +use super::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}; use crate::{ bip32::hd_path::HDPath, bip44::bip44_like_path::BIP44LikePath, @@ -8,16 +9,21 @@ use crate::{ paths::{account_path::AccountPath, getid_path::GetIDPath}, }, }; - -use super::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}; +use std::fmt::{Debug, Formatter}; /// A derivation path on either supported schemes, either Babylon (CAP26) or Olympia (BIP44Like). -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum DerivationPath { CAP26(CAP26Path), BIP44Like(BIP44LikePath), } +impl Debug for DerivationPath { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string()) + } +} + impl<'de> serde::Deserialize<'de> for DerivationPath { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. fn deserialize>(d: D) -> Result { diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index b764ce41..f98cecc9 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -379,34 +379,7 @@ mod tests { ); } - // #[test] - // fn compare() { - // let make = |index: HDPathValue| -> Account { - // let address: AccountAddress = - // "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" - // .try_into() - // .unwrap(); - - // let account = Account { - // address: address.clone(), - // network_id: address.network_id, - // display_name: RefCell::new(DisplayName::new("Test").unwrap()), - // appearance_id: RefCell::new(AppearanceID::default()), - // flags: RefCell::new(EntityFlags::default()), - // on_ledger_settings: RefCell::new(OnLedgerSettings::default()), - // security_state: EntitySecurityState::Unsecured(UnsecuredEntityControl::new( - // index, - // HierarchicalDeterministicFactorInstance::placeholder(), - // )), - // }; - // account - // }; - // let a = make(0); - // let b = make(1); - // assert!(a < b); - // } - - // #[test] + #[test] fn json_roundtrip() { let model = Account::with_values( "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" @@ -415,6 +388,10 @@ mod tests { "Zaba 0".try_into().unwrap(), 0.try_into().unwrap(), ); + model + .flags + .borrow_mut() + .insert_flag(EntityFlag::DeletedByUser); assert_eq_after_json_roundtrip( &model, r#" From 68bf25040e99c321641daf411358061174a999c9 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 1 Dec 2023 15:39:31 +0100 Subject: [PATCH 18/39] docs, tests, and convenience methods on keys --- profile/src/v100/entity/account/account.rs | 3 +- wallet_kit_common/Cargo.toml | 2 + wallet_kit_common/src/lib.rs | 1 + wallet_kit_common/src/secure_random_bytes.rs | 37 ++++++++ wallet_kit_common/src/types/hex_32bytes.rs | 45 +++++++++- .../src/types/keys/ed25519/private_key.rs | 48 ++++++++++- .../src/types/keys/ed25519/public_key.rs | 2 +- .../src/types/keys/private_key.rs | 85 +++++++++++++++++++ .../src/types/keys/public_key.rs | 19 ++++- .../src/types/keys/secp256k1/private_key.rs | 24 ++++++ .../src/types/keys/secp256k1/public_key.rs | 1 + 11 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 wallet_kit_common/src/secure_random_bytes.rs diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index f98cecc9..2ca47b5a 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -73,7 +73,8 @@ pub struct Account { /// marked as hidden or not. flags: RefCell, - /// The on ledger synced settings for this account + /// The on ledger synced settings for this account, contains e.g. + /// ThirdPartyDeposit settings, with deposit rules for assets. on_ledger_settings: RefCell, } diff --git a/wallet_kit_common/Cargo.toml b/wallet_kit_common/Cargo.toml index 62a9d1a4..20192f3d 100644 --- a/wallet_kit_common/Cargo.toml +++ b/wallet_kit_common/Cargo.toml @@ -24,3 +24,5 @@ transaction = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "038 ] } bip32 = "0.5.1" # only need Secp256k1, to do validation of PublicKey ed25519-dalek = "1.0.1" +rand = "0.8.5" +enum-as-inner = "0.6.0" diff --git a/wallet_kit_common/src/lib.rs b/wallet_kit_common/src/lib.rs index 312973bc..06cded68 100644 --- a/wallet_kit_common/src/lib.rs +++ b/wallet_kit_common/src/lib.rs @@ -2,5 +2,6 @@ pub mod error; pub mod hash; pub mod json; pub mod network_id; +pub mod secure_random_bytes; pub mod types; pub mod utils; diff --git a/wallet_kit_common/src/secure_random_bytes.rs b/wallet_kit_common/src/secure_random_bytes.rs new file mode 100644 index 00000000..d4ec3c8f --- /dev/null +++ b/wallet_kit_common/src/secure_random_bytes.rs @@ -0,0 +1,37 @@ +use rand::{rngs::OsRng, RngCore}; + +/// Generates `N` random bytes using a cryptographically +/// secure random generator and returns these bytes as +/// a Vec. +pub fn generate_bytes() -> Vec { + let mut csprng = OsRng; + let mut bytes: [u8; N] = [0u8; N]; + csprng.fill_bytes(&mut bytes); + Vec::from(bytes) +} + +/// Generates `32` random bytes using a cryptographically +/// secure random generator and returns these bytes as +/// a Vec. +pub fn generate_32_bytes() -> Vec { + generate_bytes::<32>() +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use super::generate_32_bytes; + + #[test] + fn random() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let bytes = generate_32_bytes(); + assert_eq!(bytes.len(), 32); + set.insert(bytes); + } + assert_eq!(set.len(), n); + } +} diff --git a/wallet_kit_common/src/types/hex_32bytes.rs b/wallet_kit_common/src/types/hex_32bytes.rs index 8f40e0ca..d317c15b 100644 --- a/wallet_kit_common/src/types/hex_32bytes.rs +++ b/wallet_kit_common/src/types/hex_32bytes.rs @@ -6,23 +6,42 @@ use std::{ use radix_engine_common::crypto::{Hash, IsHash}; use serde::{de, Deserializer, Serialize, Serializer}; -use crate::error::Error; +use crate::{error::Error, secure_random_bytes::generate_32_bytes}; +/// Serializable 32 bytes which **always** serializes as a **hex** string, this is useful +/// since in Radix Wallet Kit we almost always want to serialize bytes into hex and this +/// allows us to skip using #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Hex32Bytes([u8; 32]); +impl Hex32Bytes { + /// Instantiates a new `Hex32Bytes` from bytes generated by + /// a CSPRNG. + pub fn generate() -> Self { + Hex32Bytes::from_vec(generate_32_bytes()).expect("Should be able to generate 32 bytes.") + } + + /// Just an alias for `Self::generate()` + pub fn new() -> Self { + Self::generate() + } +} + impl Display for Hex32Bytes { + /// Formats the `Hex32Bytes` as a hex string. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", hex::encode(self.0)) } } impl Debug for Hex32Bytes { + /// Formats the `Hex32Bytes` as a hex string. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&hex::encode(self.0)) } } impl From for Hex32Bytes { + /// Instantiates a new `Hex32Bytes` from the `Hash` (32 bytes). fn from(value: Hash) -> Self { Self::from_bytes(&value.into_bytes()) } @@ -31,6 +50,9 @@ impl From for Hex32Bytes { impl FromStr for Hex32Bytes { type Err = Error; + /// Tries to decode the string `s` into a `Hex32Bytes`. Will fail + /// if the string is not valid hex or if the decoded bytes does + /// not have length 32. fn from_str(s: &str) -> Result { hex::decode(s) .map_err(|_| Error::StringNotHex) @@ -39,23 +61,33 @@ impl FromStr for Hex32Bytes { } impl Hex32Bytes { + /// Just some placeholder Hex32Bytes pub fn placeholder() -> Self { Self::from_str("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") .expect("Deadbeef") } +} +impl Hex32Bytes { + /// Returns a clone of the inner bytes as a `Vec`. pub fn to_vec(&self) -> Vec { Vec::from(self.bytes().clone()) } + /// Returns a references to the inner array slice. pub fn bytes(&self) -> &[u8; 32] { &self.0 } +} +impl Hex32Bytes { + /// Instantiates a new `Hex32Bytes` from the 32 bytes, by cloning them. pub fn from_bytes(bytes: &[u8; 32]) -> Self { Self(bytes.clone()) } + /// Tries to turn the `Vec` into a `Hex32Bytes`. Will fail + /// if `bytes` does not have length 32. pub fn from_vec(bytes: Vec) -> Result { bytes .try_into() @@ -63,6 +95,9 @@ impl Hex32Bytes { .map_err(|_| Error::InvalidByteCountExpected32) } + /// Tries to decode the string `s` into a `Hex32Bytes`. Will fail + /// if the string is not valid hex or if the decoded bytes does + /// not have length 32. pub fn from_hex(s: &str) -> Result { Self::from_str(s) } @@ -100,6 +135,14 @@ mod tests { assert_eq!(Hex32Bytes::from_hex(str).unwrap().to_string(), str); } + #[test] + fn generate_32() { + assert_ne!( + Hex32Bytes::generate().to_string(), + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ); + } + #[test] fn from_bytes_roundtrip() { let bytes = [0u8; 32]; diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index 7e76107c..14d6ad2b 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -11,6 +11,22 @@ use std::fmt::{Debug, Formatter}; /// EdDSA scheme. pub struct Ed25519PrivateKey(EngineEd25519PrivateKey); +impl Ed25519PrivateKey { + /// Generates a new `Ed25519PrivateKey` from random bytes + /// generated by a CSRNG, note that this is typically never + /// used by wallets, which tend to rather use a Mnemonic and + /// derive hierarchical deterministic keys. + pub fn generate() -> Self { + Self::from_hex32_bytes(Hex32Bytes::generate()).expect("Should be able to generate 32 bytes") + } + + /// Just an alias for `Self::generate()`, generating a new + /// key from random bytes. + pub fn new() -> Self { + Self::generate() + } +} + impl PartialEq for Ed25519PrivateKey { fn eq(&self, other: &Self) -> bool { self.to_bytes() == other.to_bytes() @@ -56,6 +72,14 @@ impl Ed25519PrivateKey { .map_err(|_| Error::InvalidEd25519PrivateKeyFromString) .and_then(|b| Self::from_bytes(&b.to_vec())) } + + pub fn from_vec(bytes: Vec) -> Result { + Self::from_bytes(bytes.as_slice()) + } + + pub fn from_hex32_bytes(bytes: Hex32Bytes) -> Result { + Self::from_vec(bytes.to_vec()) + } } impl TryFrom<&[u8]> for Ed25519PrivateKey { @@ -106,7 +130,7 @@ mod tests { use transaction::signing::ed25519::Ed25519Signature; - use crate::{error::Error, hash::hash}; + use crate::{error::Error, hash::hash, types::hex_32bytes::Hex32Bytes}; use super::Ed25519PrivateKey; @@ -192,4 +216,26 @@ mod tests { hex ); } + + #[test] + fn from_vec() { + let hex = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + assert_eq!( + Ed25519PrivateKey::from_vec(Vec::from(hex::decode(hex).unwrap())) + .unwrap() + .to_hex(), + hex + ); + } + + #[test] + fn from_hex32() { + let hex = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + assert_eq!( + Ed25519PrivateKey::from_hex32_bytes(Hex32Bytes::from_hex(hex).unwrap()) + .unwrap() + .to_hex(), + hex + ); + } } diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 93931726..6886b33f 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -10,7 +10,7 @@ use std::{ }; use transaction::{signing::ed25519::Ed25519Signature, validation::verify_ed25519}; -/// An Ed25519 public key used to verify cryptographic signatures. +/// An Ed25519 public key used to verify cryptographic signatures (EdDSA signatures). #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Ed25519PublicKey(EngineEd25519PublicKey); diff --git a/wallet_kit_common/src/types/keys/private_key.rs b/wallet_kit_common/src/types/keys/private_key.rs index 872653d8..3556a272 100644 --- a/wallet_kit_common/src/types/keys/private_key.rs +++ b/wallet_kit_common/src/types/keys/private_key.rs @@ -2,13 +2,42 @@ use super::{ ed25519::private_key::Ed25519PrivateKey, public_key::PublicKey, secp256k1::private_key::Secp256k1PrivateKey, }; +use enum_as_inner::EnumAsInner; +#[derive(EnumAsInner)] +/// A tagged union of supported private keys on different curves, supported +/// curves are `secp256k1` and `Curve25519` pub enum PrivateKey { + /// An Ed25519 private key used to create cryptographic signatures, using EdDSA scheme. Ed25519(Ed25519PrivateKey), + + /// A secp256k1 private key used to create cryptographic signatures, + /// more specifically ECDSA signatures, that offer recovery of the public key Secp256k1(Secp256k1PrivateKey), } +impl From for PrivateKey { + /// Enables `let private_key: PrivateKey = Ed25519PrivateKey::new().into()` + fn from(value: Ed25519PrivateKey) -> Self { + Self::Ed25519(value) + } +} + +impl From for PrivateKey { + /// Enables `let private_key: PrivateKey = Secp256k1PrivateKey::new().into()` + fn from(value: Secp256k1PrivateKey) -> Self { + Self::Secp256k1(value) + } +} + impl PrivateKey { + /// Generates a new `PrivateKey` over Curve25519. + pub fn new() -> Self { + Ed25519PrivateKey::generate().into() + } + + /// Calculates the public key of the inner `PrivateKey` and wraps it + /// in the `PublicKey` tagged union. pub fn public_key(&self) -> PublicKey { match self { PrivateKey::Ed25519(key) => PublicKey::Ed25519(key.public_key()), @@ -16,6 +45,7 @@ impl PrivateKey { } } + /// Returns the hex representation of the inner public key as a `String`. pub fn to_hex(&self) -> String { match self { PrivateKey::Ed25519(key) => key.to_hex(), @@ -23,3 +53,58 @@ impl PrivateKey { } } } + +#[cfg(test)] +mod tests { + + use crate::{ + secure_random_bytes::generate_32_bytes, + types::keys::{ + ed25519::private_key::Ed25519PrivateKey, secp256k1::private_key::Secp256k1PrivateKey, + }, + }; + + use super::PrivateKey; + + #[test] + fn private_key_ed25519_into_as_roundtrip() { + let bytes = generate_32_bytes(); + // test `into`` + let private_key: PrivateKey = Ed25519PrivateKey::from_vec(bytes.clone()).unwrap().into(); + // test `as` + assert_eq!( + private_key.as_ed25519().unwrap(), + &Ed25519PrivateKey::from_vec(bytes).unwrap() + ); + } + + #[test] + fn private_key_ed25519_into_as_wrong_fails() { + let bytes = generate_32_bytes(); + // test `into`` + let private_key: PrivateKey = Ed25519PrivateKey::from_vec(bytes.clone()).unwrap().into(); + // test `as` + assert!(private_key.as_secp256k1().is_none()); + } + + #[test] + fn private_key_secp256k1_into_as_roundtrip() { + let bytes = generate_32_bytes(); + // test `into`` + let private_key: PrivateKey = Secp256k1PrivateKey::from_vec(bytes.clone()).unwrap().into(); + // test `as` + assert_eq!( + private_key.as_secp256k1().unwrap(), + &Secp256k1PrivateKey::from_vec(bytes).unwrap() + ); + } + + #[test] + fn private_key_secp256k1_into_as_wrong_fails() { + let bytes = generate_32_bytes(); + // test `into`` + let private_key: PrivateKey = Secp256k1PrivateKey::from_vec(bytes.clone()).unwrap().into(); + // test `as` + assert!(private_key.as_ed25519().is_none()); + } +} diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index d03db5df..3065bc3c 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -6,13 +6,28 @@ use super::{ ed25519::public_key::Ed25519PublicKey, secp256k1::public_key::Secp256k1PublicKey, slip10_curve::SLIP10Curve, }; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +use enum_as_inner::EnumAsInner; +#[derive(Clone, Copy, Debug, PartialEq, EnumAsInner, Eq, Hash, PartialOrd, Ord)] +/// A tagged union of supported public keys on different curves, supported +/// curves are `secp256k1` and `Curve25519` pub enum PublicKey { + /// An Ed25519 public key used to verify cryptographic signatures. Ed25519(Ed25519PublicKey), Secp256k1(Secp256k1PublicKey), } +impl From for PublicKey { + fn from(value: Ed25519PublicKey) -> Self { + Self::Ed25519(value) + } +} + +impl From for PublicKey { + fn from(value: Secp256k1PublicKey) -> Self { + Self::Secp256k1(value) + } +} + impl PublicKey { pub fn secp256k1_from_bytes(slice: &[u8]) -> Result { Secp256k1PublicKey::try_from(slice).map(Self::Secp256k1) diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index 461c21bd..8621eda6 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -11,6 +11,22 @@ use std::fmt::{Debug, Formatter}; /// ECDSA signatures, that offer recovery of the public key. pub struct Secp256k1PrivateKey(EngineSecp256k1PrivateKey); +impl Secp256k1PrivateKey { + /// Generates a new `Secp256k1PrivateKey` from random bytes + /// generated by a CSRNG, note that this is typically never + /// used by wallets, which tend to rather use a Mnemonic and + /// derive hierarchical deterministic keys. + pub fn generate() -> Self { + Self::from_hex32_bytes(Hex32Bytes::generate()).expect("Should be able to generate 32 bytes") + } + + /// Just an alias for `Self::generate()`, generating a new + /// key from random bytes. + pub fn new() -> Self { + Self::generate() + } +} + impl PartialEq for Secp256k1PrivateKey { fn eq(&self, other: &Self) -> bool { self.to_bytes() == other.to_bytes() @@ -51,6 +67,14 @@ impl Secp256k1PrivateKey { .map(Self::from_engine) } + pub fn from_vec(bytes: Vec) -> Result { + Self::from_bytes(bytes.as_slice()) + } + + pub fn from_hex32_bytes(bytes: Hex32Bytes) -> Result { + Self::from_vec(bytes.to_vec()) + } + pub fn from_str(hex: &str) -> Result { Hex32Bytes::from_hex(hex) .and_then(|b| Self::from_bytes(&b.to_vec())) diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 98dbae55..6997af04 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Formatter}; use transaction::{signing::secp256k1::Secp256k1Signature, validation::verify_secp256k1}; +/// A `secp256k1` public key used to verify cryptographic signatures (ECDSA signatures). #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Secp256k1PublicKey(EngineSecp256k1PublicKey); From 9c582eddf73b912c393f6d6eee619d3f15d4092d Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 1 Dec 2023 15:47:55 +0100 Subject: [PATCH 19/39] doc --- wallet_kit_common/src/json.rs | 5 +---- wallet_kit_common/src/types/keys/private_key.rs | 2 +- wallet_kit_common/src/types/keys/public_key.rs | 3 ++- wallet_kit_common/src/types/keys/slip10_curve.rs | 6 ++++++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/wallet_kit_common/src/json.rs b/wallet_kit_common/src/json.rs index ee10f408..f4a43957 100644 --- a/wallet_kit_common/src/json.rs +++ b/wallet_kit_common/src/json.rs @@ -1,8 +1,5 @@ use core::fmt::Debug; -use serde::{ - de::{self, DeserializeOwned}, - ser::Serialize, -}; +use serde::{de::DeserializeOwned, ser::Serialize}; use serde_json::Value; fn base_assert_equality_after_json_roundtrip(model: &T, json: Value, expect_eq: bool) diff --git a/wallet_kit_common/src/types/keys/private_key.rs b/wallet_kit_common/src/types/keys/private_key.rs index 3556a272..d34d661c 100644 --- a/wallet_kit_common/src/types/keys/private_key.rs +++ b/wallet_kit_common/src/types/keys/private_key.rs @@ -4,9 +4,9 @@ use super::{ }; use enum_as_inner::EnumAsInner; -#[derive(EnumAsInner)] /// A tagged union of supported private keys on different curves, supported /// curves are `secp256k1` and `Curve25519` +#[derive(EnumAsInner)] pub enum PrivateKey { /// An Ed25519 private key used to create cryptographic signatures, using EdDSA scheme. Ed25519(Ed25519PrivateKey), diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 3065bc3c..e6786ed1 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -7,9 +7,10 @@ use super::{ slip10_curve::SLIP10Curve, }; use enum_as_inner::EnumAsInner; -#[derive(Clone, Copy, Debug, PartialEq, EnumAsInner, Eq, Hash, PartialOrd, Ord)] + /// A tagged union of supported public keys on different curves, supported /// curves are `secp256k1` and `Curve25519` +#[derive(Clone, Copy, Debug, PartialEq, EnumAsInner, Eq, Hash, PartialOrd, Ord)] pub enum PublicKey { /// An Ed25519 public key used to verify cryptographic signatures. Ed25519(Ed25519PublicKey), diff --git a/wallet_kit_common/src/types/keys/slip10_curve.rs b/wallet_kit_common/src/types/keys/slip10_curve.rs index dd502bc7..6bbba32f 100644 --- a/wallet_kit_common/src/types/keys/slip10_curve.rs +++ b/wallet_kit_common/src/types/keys/slip10_curve.rs @@ -1,5 +1,11 @@ use serde::{Deserialize, Serialize}; +/// Elliptic Curves which the SLIP10 derivation algorithm supports. +/// +/// We use SLIP10 for hierarchical deterministic derivation since we +/// prefer using Curve25519 - which is incompatible with BIP32 (BIP44). +/// +/// For for information see [SLIP10 reference](https://github.com/satoshilabs/slips/blob/master/slip-0010.md) #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] pub enum SLIP10Curve { From ef3154070e8b8210391ecb05864cb5702d4e316f Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 1 Dec 2023 15:53:30 +0100 Subject: [PATCH 20/39] doc --- hierarchical_deterministic/src/bip39/mnemonic.rs | 1 + hierarchical_deterministic/src/bip44/bip44_like_path.rs | 1 + .../src/cap26/cap26_path/paths/account_path.rs | 1 + hierarchical_deterministic/src/derivation/derivation_path.rs | 1 + .../src/derivation/hierarchical_deterministic_public_key.rs | 1 + .../src/derivation/mnemonic_with_passphrase.rs | 1 + profile/src/v100/address/account_address.rs | 1 + profile/src/v100/entity/account/account.rs | 1 + profile/src/v100/entity_security_state/entity_security_state.rs | 1 + .../src/v100/entity_security_state/unsecured_entity_control.rs | 1 + .../src/v100/factors/factor_instance/badge_virtual_source.rs | 1 + profile/src/v100/factors/factor_instance/factor_instance.rs | 1 + .../src/v100/factors/factor_instance/factor_instance_badge.rs | 1 + profile/src/v100/factors/factor_source_common.rs | 1 + profile/src/v100/factors/factor_source_id.rs | 1 + profile/src/v100/factors/factor_source_id_from_address.rs | 1 + profile/src/v100/factors/factor_source_id_from_hash.rs | 2 +- .../factor_sources/device_factor_source/device_factor_source.rs | 1 + .../device_factor_source/device_factor_source_hint.rs | 1 + .../v100/factors/hierarchical_deterministic_factor_instance.rs | 1 + wallet_kit_common/src/types/hex_32bytes.rs | 1 + wallet_kit_common/src/types/keys/ed25519/private_key.rs | 1 + wallet_kit_common/src/types/keys/ed25519/public_key.rs | 1 + wallet_kit_common/src/types/keys/secp256k1/private_key.rs | 1 + wallet_kit_common/src/types/keys/secp256k1/public_key.rs | 1 + 25 files changed, 25 insertions(+), 1 deletion(-) diff --git a/hierarchical_deterministic/src/bip39/mnemonic.rs b/hierarchical_deterministic/src/bip39/mnemonic.rs index 38e06a4a..b0c8ad80 100644 --- a/hierarchical_deterministic/src/bip39/mnemonic.rs +++ b/hierarchical_deterministic/src/bip39/mnemonic.rs @@ -75,6 +75,7 @@ impl TryInto for &str { } impl Mnemonic { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::from_phrase("bright club bacon dinner achieve pull grid save ramp cereal blush woman humble limb repeat video sudden possible story mask neutral prize goose mandate").expect("Valid mnemonic") } diff --git a/hierarchical_deterministic/src/bip44/bip44_like_path.rs b/hierarchical_deterministic/src/bip44/bip44_like_path.rs index 80a0dadd..affbf874 100644 --- a/hierarchical_deterministic/src/bip44/bip44_like_path.rs +++ b/hierarchical_deterministic/src/bip44/bip44_like_path.rs @@ -87,6 +87,7 @@ impl TryInto for &str { } impl BIP44LikePath { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::from_str("m/44H/1022H/0H/0/0H").expect("Valid placeholder") } diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs index 3042c79d..a510f876 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs @@ -42,6 +42,7 @@ impl CAP26Repr for AccountPath { } impl AccountPath { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::from_str("m/44H/1022H/1H/525H/1460H/0H").unwrap() } diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index b845c82d..372135a6 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -60,6 +60,7 @@ impl Serialize for DerivationPath { } impl DerivationPath { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::CAP26(CAP26Path::AccountPath(AccountPath::placeholder())) } diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs index 2df8a0a8..40637c9e 100644 --- a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs @@ -36,6 +36,7 @@ impl HierarchicalDeterministicPublicKey { } impl HierarchicalDeterministicPublicKey { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { let mwp = MnemonicWithPassphrase::placeholder(); let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); diff --git a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs index b7c83c2f..e00563df 100644 --- a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs +++ b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs @@ -48,6 +48,7 @@ impl MnemonicWithPassphrase { } impl MnemonicWithPassphrase { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::with_passphrase(Mnemonic::placeholder(), "radix".to_string()) } diff --git a/profile/src/v100/address/account_address.rs b/profile/src/v100/address/account_address.rs index a198d698..55e471d6 100644 --- a/profile/src/v100/address/account_address.rs +++ b/profile/src/v100/address/account_address.rs @@ -104,6 +104,7 @@ impl Display for AccountAddress { } impl AccountAddress { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { AccountAddress::try_from_bech32( "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease", diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index 2ca47b5a..3532bbf3 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -174,6 +174,7 @@ impl Display for Account { // CFG test #[cfg(test)] impl Account { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::placeholder_mainnet() } diff --git a/profile/src/v100/entity_security_state/entity_security_state.rs b/profile/src/v100/entity_security_state/entity_security_state.rs index a3198b98..6e5778d5 100644 --- a/profile/src/v100/entity_security_state/entity_security_state.rs +++ b/profile/src/v100/entity_security_state/entity_security_state.rs @@ -40,6 +40,7 @@ impl Serialize for EntitySecurityState { } impl EntitySecurityState { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::Unsecured(UnsecuredEntityControl::placeholder()) } diff --git a/profile/src/v100/entity_security_state/unsecured_entity_control.rs b/profile/src/v100/entity_security_state/unsecured_entity_control.rs index 4c4df979..d87ef0aa 100644 --- a/profile/src/v100/entity_security_state/unsecured_entity_control.rs +++ b/profile/src/v100/entity_security_state/unsecured_entity_control.rs @@ -37,6 +37,7 @@ impl UnsecuredEntityControl { } impl UnsecuredEntityControl { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::new(HierarchicalDeterministicFactorInstance::placeholder()) } diff --git a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs index f037d621..27846824 100644 --- a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs +++ b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs @@ -40,6 +40,7 @@ impl Serialize for FactorInstanceBadgeVirtualSource { } impl FactorInstanceBadgeVirtualSource { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::HierarchicalDeterministic(HierarchicalDeterministicPublicKey::placeholder()) } diff --git a/profile/src/v100/factors/factor_instance/factor_instance.rs b/profile/src/v100/factors/factor_instance/factor_instance.rs index cb89ecec..6cd78010 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance.rs @@ -26,6 +26,7 @@ impl FactorInstance { } } + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::new( FactorSourceID::placeholder(), diff --git a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs index f99e4db7..1eccc59a 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs @@ -13,6 +13,7 @@ pub enum FactorInstanceBadge { } impl FactorInstanceBadge { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { FactorInstanceBadge::Virtual(FactorInstanceBadgeVirtualSource::placeholder()) } diff --git a/profile/src/v100/factors/factor_source_common.rs b/profile/src/v100/factors/factor_source_common.rs index e017b302..e4db4b9e 100644 --- a/profile/src/v100/factors/factor_source_common.rs +++ b/profile/src/v100/factors/factor_source_common.rs @@ -74,6 +74,7 @@ impl Default for FactorSourceCommon { } impl FactorSourceCommon { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { let date = NaiveDateTime::parse_from_str("2023-09-11T16:05:56", "%Y-%m-%dT%H:%M:%S").unwrap(); diff --git a/profile/src/v100/factors/factor_source_id.rs b/profile/src/v100/factors/factor_source_id.rs index f3670c84..e1b98f7f 100644 --- a/profile/src/v100/factors/factor_source_id.rs +++ b/profile/src/v100/factors/factor_source_id.rs @@ -55,6 +55,7 @@ impl Serialize for FactorSourceID { } impl FactorSourceID { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { FactorSourceID::Hash(FactorSourceIDFromHash::placeholder()) } diff --git a/profile/src/v100/factors/factor_source_id_from_address.rs b/profile/src/v100/factors/factor_source_id_from_address.rs index 610fa5d0..13251ff3 100644 --- a/profile/src/v100/factors/factor_source_id_from_address.rs +++ b/profile/src/v100/factors/factor_source_id_from_address.rs @@ -19,6 +19,7 @@ impl FactorSourceIDFromAddress { } impl FactorSourceIDFromAddress { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::new( FactorSourceKind::TrustedContact, diff --git a/profile/src/v100/factors/factor_source_id_from_hash.rs b/profile/src/v100/factors/factor_source_id_from_hash.rs index 5a1f60ab..a1871ee6 100644 --- a/profile/src/v100/factors/factor_source_id_from_hash.rs +++ b/profile/src/v100/factors/factor_source_id_from_hash.rs @@ -53,7 +53,7 @@ impl FactorSourceIDFromHash { } impl FactorSourceIDFromHash { - /// This is using the MnemonicWithPass + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::new_for_device(MnemonicWithPassphrase::placeholder()) } diff --git a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs index b281a313..39ba57de 100644 --- a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs +++ b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs @@ -27,6 +27,7 @@ pub struct DeviceFactorSource { } impl DeviceFactorSource { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self { id: FactorSourceIDFromHash::placeholder(), diff --git a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs index 4b5de737..9db87551 100644 --- a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs +++ b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs @@ -45,6 +45,7 @@ impl Default for DeviceFactorSourceHint { } impl DeviceFactorSourceHint { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::iphone_unknown() } diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 2795d869..85fc4694 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -112,6 +112,7 @@ impl<'de> serde::Deserialize<'de> for HierarchicalDeterministicFactorInstance { } impl HierarchicalDeterministicFactorInstance { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { let mwp = MnemonicWithPassphrase::placeholder(); let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); diff --git a/wallet_kit_common/src/types/hex_32bytes.rs b/wallet_kit_common/src/types/hex_32bytes.rs index d317c15b..c8d7a808 100644 --- a/wallet_kit_common/src/types/hex_32bytes.rs +++ b/wallet_kit_common/src/types/hex_32bytes.rs @@ -62,6 +62,7 @@ impl FromStr for Hex32Bytes { impl Hex32Bytes { /// Just some placeholder Hex32Bytes + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::from_str("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") .expect("Deadbeef") diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index 14d6ad2b..c3caaddf 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -99,6 +99,7 @@ impl TryInto for &str { } impl Ed25519PrivateKey { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::placeholder_alice() } diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 6886b33f..82c6d5e2 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -68,6 +68,7 @@ impl Debug for Ed25519PublicKey { } impl Ed25519PublicKey { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::placeholder_alice() } diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index 8621eda6..fcbcc3c9 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -99,6 +99,7 @@ impl TryFrom<&[u8]> for Secp256k1PrivateKey { } impl Secp256k1PrivateKey { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::placeholder_alice() } diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 6997af04..087fbe07 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -63,6 +63,7 @@ impl Debug for Secp256k1PublicKey { } impl Secp256k1PublicKey { + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::placeholder_alice() } From 862c9fbbbffc95f8ab79eb22e620636e9619a70a Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 1 Dec 2023 16:06:40 +0100 Subject: [PATCH 21/39] doc and tests --- profile/src/v100/factors/factor_source_id.rs | 49 ++++++++++++++++++- .../factors/factor_source_id_from_address.rs | 5 +- .../factors/factor_source_id_from_hash.rs | 28 +++-------- ...rarchical_deterministic_factor_instance.rs | 3 +- profile/src/v100/factors/mod.rs | 1 - .../src/v100/factors/to_factor_source_id.rs | 5 -- 6 files changed, 60 insertions(+), 31 deletions(-) delete mode 100644 profile/src/v100/factors/to_factor_source_id.rs diff --git a/profile/src/v100/factors/factor_source_id.rs b/profile/src/v100/factors/factor_source_id.rs index e1b98f7f..3a03cda2 100644 --- a/profile/src/v100/factors/factor_source_id.rs +++ b/profile/src/v100/factors/factor_source_id.rs @@ -12,12 +12,28 @@ use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializ #[derive(Serialize, Deserialize, EnumAsInner, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[serde(remote = "Self")] pub enum FactorSourceID { + /// FactorSourceID from the blake2b hash of the special HD public key derived at `CAP26::GetID`, + /// for a certain `FactorSourceKind` #[serde(rename = "fromHash")] Hash(FactorSourceIDFromHash), + + /// FactorSourceID from an AccountAddress, typically used by `trustedContact` FactorSource. #[serde(rename = "fromAddress")] Address(FactorSourceIDFromAddress), } +impl From for FactorSourceID { + fn from(value: FactorSourceIDFromHash) -> Self { + Self::Hash(value) + } +} + +impl From for FactorSourceID { + fn from(value: FactorSourceIDFromAddress) -> Self { + Self::Address(value) + } +} + impl<'de> Deserialize<'de> for FactorSourceID { fn deserialize>(deserializer: D) -> Result { // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 @@ -65,7 +81,10 @@ impl FactorSourceID { mod tests { use wallet_kit_common::json::assert_eq_after_json_roundtrip; - use crate::v100::factors::factor_source_id_from_address::FactorSourceIDFromAddress; + use crate::v100::factors::{ + factor_source_id_from_address::FactorSourceIDFromAddress, + factor_source_id_from_hash::FactorSourceIDFromHash, + }; use super::FactorSourceID; @@ -102,4 +121,32 @@ mod tests { "#, ) } + + #[test] + fn hash_into_as_roundtrip() { + let from_hash = FactorSourceIDFromHash::placeholder(); + let id: FactorSourceID = from_hash.clone().into(); // test `into()` + assert_eq!(id.as_hash().unwrap(), &from_hash); + } + + #[test] + fn hash_into_as_wrong_fails() { + let from_hash = FactorSourceIDFromHash::placeholder(); + let id: FactorSourceID = from_hash.into(); // test `into()` + assert!(id.as_address().is_none()); + } + + #[test] + fn address_into_as_roundtrip() { + let from_address = FactorSourceIDFromAddress::placeholder(); + let id: FactorSourceID = from_address.clone().into(); // test `into()` + assert_eq!(id.as_address().unwrap(), &from_address); + } + + #[test] + fn address_into_as_wrong_fails() { + let from_address = FactorSourceIDFromAddress::placeholder(); + let id: FactorSourceID = from_address.into(); // test `into()` + assert!(id.as_hash().is_none()); + } } diff --git a/profile/src/v100/factors/factor_source_id_from_address.rs b/profile/src/v100/factors/factor_source_id_from_address.rs index 13251ff3..2075d72f 100644 --- a/profile/src/v100/factors/factor_source_id_from_address.rs +++ b/profile/src/v100/factors/factor_source_id_from_address.rs @@ -4,10 +4,13 @@ use crate::v100::address::account_address::AccountAddress; use super::factor_source_kind::FactorSourceKind; -/// FactorSourceID from a hash. +/// FactorSourceID from an AccountAddress, typically used by `trustedContact` FactorSource. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct FactorSourceIDFromAddress { + /// The kind of the FactorSource this ID refers to, typically `trustedContact`. pub kind: FactorSourceKind, + + /// An account address which the FactorSource this ID refers uses/needs. pub body: AccountAddress, } diff --git a/profile/src/v100/factors/factor_source_id_from_hash.rs b/profile/src/v100/factors/factor_source_id_from_hash.rs index a1871ee6..a5ac5d92 100644 --- a/profile/src/v100/factors/factor_source_id_from_hash.rs +++ b/profile/src/v100/factors/factor_source_id_from_hash.rs @@ -6,22 +6,17 @@ use radix_engine_common::crypto::{blake2b_256_hash, Hash}; use serde::{Deserialize, Serialize}; use wallet_kit_common::types::hex_32bytes::Hex32Bytes; -use super::{ - factor_source_id::FactorSourceID, factor_source_kind::FactorSourceKind, - to_factor_source_id::ToFactorSourceID, -}; +use super::factor_source_kind::FactorSourceKind; -/// FactorSourceID from a hash. +/// FactorSourceID from the blake2b hash of the special HD public key derived at `CAP26::GetID`, +/// for a certain `FactorSourceKind` #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct FactorSourceIDFromHash { + /// The kind of the FactorSource this ID refers to, typically `device` or `ledger`. pub kind: FactorSourceKind, - pub body: Hex32Bytes, -} -impl ToFactorSourceID for FactorSourceIDFromHash { - fn embed(&self) -> FactorSourceID { - FactorSourceID::Hash(self.clone()) - } + /// The blake2b hash of the special HD public key derived at `CAP26::GetID`. + pub body: Hex32Bytes, } impl ToString for FactorSourceIDFromHash { @@ -31,6 +26,7 @@ impl ToString for FactorSourceIDFromHash { } impl FactorSourceIDFromHash { + /// Instantiates a new `FactorSourceIDFromHash` from the `kind` and `body`. pub fn new(kind: FactorSourceKind, body: Hex32Bytes) -> Self { Self { kind, body } } @@ -66,10 +62,6 @@ mod tests { }; use wallet_kit_common::json::assert_eq_after_json_roundtrip; - use crate::v100::factors::{ - factor_source_id::FactorSourceID, to_factor_source_id::ToFactorSourceID, - }; - use super::FactorSourceIDFromHash; #[test] @@ -102,12 +94,6 @@ mod tests { ); } - #[test] - fn embed() { - let from_hash = FactorSourceIDFromHash::placeholder(); - assert_eq!(from_hash.embed(), FactorSourceID::Hash(from_hash)); - } - struct Vector { /// Given input, bip49 mnemonic phrase phrase: String, diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 85fc4694..ad2cbeaa 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -22,7 +22,6 @@ use super::{ }, factor_source_id::FactorSourceID, factor_source_id_from_hash::FactorSourceIDFromHash, - to_factor_source_id::ToFactorSourceID, }; #[derive(Clone, Debug, PartialEq, Eq)] @@ -78,7 +77,7 @@ impl HierarchicalDeterministicFactorInstance { pub fn factor_instance(&self) -> FactorInstance { FactorInstance::new( - self.factor_source_id.embed(), + self.factor_source_id.clone().into(), FactorInstanceBadge::Virtual( FactorInstanceBadgeVirtualSource::HierarchicalDeterministic( HierarchicalDeterministicPublicKey::new( diff --git a/profile/src/v100/factors/mod.rs b/profile/src/v100/factors/mod.rs index df88afc0..245d3d71 100644 --- a/profile/src/v100/factors/mod.rs +++ b/profile/src/v100/factors/mod.rs @@ -9,4 +9,3 @@ pub mod factor_source_id_from_hash; pub mod factor_source_kind; pub mod factor_sources; pub mod hierarchical_deterministic_factor_instance; -pub mod to_factor_source_id; diff --git a/profile/src/v100/factors/to_factor_source_id.rs b/profile/src/v100/factors/to_factor_source_id.rs deleted file mode 100644 index 75c0a8f3..00000000 --- a/profile/src/v100/factors/to_factor_source_id.rs +++ /dev/null @@ -1,5 +0,0 @@ -use super::factor_source_id::FactorSourceID; - -pub trait ToFactorSourceID { - fn embed(&self) -> FactorSourceID; -} From 5b57f50a9cabab31db6d4abf5cb7f7ceae718cd1 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 1 Dec 2023 18:58:29 +0100 Subject: [PATCH 22/39] moar tests --- hierarchical_deterministic/Cargo.toml | 3 - .../src/bip32/hd_path.rs | 4 +- .../factor_instance/badge_virtual_source.rs | 11 ++- .../src/v100/factors/factor_source_common.rs | 6 +- .../src/v100/factors/factor_source_kind.rs | 19 +++++ ...rarchical_deterministic_factor_instance.rs | 5 +- wallet_kit_common/Cargo.toml | 3 - wallet_kit_common/src/types/hex_32bytes.rs | 33 ++++++-- .../src/types/keys/ed25519/private_key.rs | 39 +++++++++- .../src/types/keys/ed25519/public_key.rs | 7 ++ .../src/types/keys/private_key.rs | 35 ++++++++- .../src/types/keys/public_key.rs | 78 ++++++++++++++++++- .../src/types/keys/secp256k1/private_key.rs | 41 +++++++++- .../src/types/keys/secp256k1/public_key.rs | 7 ++ .../src/types/keys/slip10_curve.rs | 2 +- 15 files changed, 267 insertions(+), 26 deletions(-) diff --git a/hierarchical_deterministic/Cargo.toml b/hierarchical_deterministic/Cargo.toml index 6212e6f1..93e75ea2 100644 --- a/hierarchical_deterministic/Cargo.toml +++ b/hierarchical_deterministic/Cargo.toml @@ -3,9 +3,6 @@ name = "hierarchical_deterministic" version = "0.1.0" edition = "2021" -[lib] -doctest = false - [dependencies] serde = { version = "1.0.192", features = ["derive"] } serde_json = { version = "1.0.108", features = ["preserve_order"] } diff --git a/hierarchical_deterministic/src/bip32/hd_path.rs b/hierarchical_deterministic/src/bip32/hd_path.rs index e79aed21..55e81dbc 100644 --- a/hierarchical_deterministic/src/bip32/hd_path.rs +++ b/hierarchical_deterministic/src/bip32/hd_path.rs @@ -140,7 +140,8 @@ impl<'de> serde::Deserialize<'de> for HDPath { mod tests { use serde_json::json; use wallet_kit_common::json::{ - assert_json_value_eq_after_roundtrip, assert_json_value_ne_after_roundtrip, + assert_json_value_eq_after_roundtrip, assert_json_value_fails, + assert_json_value_ne_after_roundtrip, }; use super::HDPath; @@ -151,5 +152,6 @@ mod tests { let parsed = HDPath::from_str(str).unwrap(); assert_json_value_eq_after_roundtrip(&parsed, json!(str)); assert_json_value_ne_after_roundtrip(&parsed, json!("m/44H/33H")); + assert_json_value_fails::(json!("super invalid path")); } } diff --git a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs index 27846824..84e42c6a 100644 --- a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs +++ b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs @@ -1,13 +1,20 @@ -use enum_as_inner::EnumAsInner; use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; -#[derive(Serialize, Deserialize, EnumAsInner, Clone, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(remote = "Self")] pub enum FactorInstanceBadgeVirtualSource { #[serde(rename = "hierarchicalDeterministicPublicKey")] HierarchicalDeterministic(HierarchicalDeterministicPublicKey), } +impl FactorInstanceBadgeVirtualSource { + pub fn as_hierarchical_deterministic(&self) -> &HierarchicalDeterministicPublicKey { + match self { + FactorInstanceBadgeVirtualSource::HierarchicalDeterministic(hd) => hd, + } + } +} + impl<'de> Deserialize<'de> for FactorInstanceBadgeVirtualSource { fn deserialize>(deserializer: D) -> Result { // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 diff --git a/profile/src/v100/factors/factor_source_common.rs b/profile/src/v100/factors/factor_source_common.rs index e4db4b9e..8b0b91d7 100644 --- a/profile/src/v100/factors/factor_source_common.rs +++ b/profile/src/v100/factors/factor_source_common.rs @@ -112,7 +112,11 @@ mod tests { fn new_uses_now_as_date() { let date0 = now(); let model = FactorSourceCommon::new(FactorSourceCryptoParameters::default(), []); - let date1 = now(); + let mut date1 = now(); + for _ in 0..10 { + // rust is too fast... lol. + date1 = now(); + } let do_test = |d: NaiveDateTime| { assert!(d > date0); assert!(d < date1); diff --git a/profile/src/v100/factors/factor_source_kind.rs b/profile/src/v100/factors/factor_source_kind.rs index 73ee7bfb..6400eb07 100644 --- a/profile/src/v100/factors/factor_source_kind.rs +++ b/profile/src/v100/factors/factor_source_kind.rs @@ -147,6 +147,25 @@ mod tests { format!("{}", FactorSourceKind::Device.discriminant()), "device" ); + assert_eq!( + format!( + "{}", + FactorSourceKind::LedgerHQHardwareWallet.discriminant() + ), + "ledgerHQHardwareWallet" + ); + assert_eq!( + format!("{}", FactorSourceKind::SecurityQuestions.discriminant()), + "securityQuestions" + ); + assert_eq!( + format!("{}", FactorSourceKind::OffDeviceMnemonic.discriminant()), + "offDeviceMnemonic" + ); + assert_eq!( + format!("{}", FactorSourceKind::TrustedContact.discriminant()), + "trustedContact" + ); } #[test] diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index ad2cbeaa..7df6a7f6 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -64,9 +64,8 @@ impl HierarchicalDeterministicFactorInstance { .badge .as_virtual() .ok_or(Error::BadgeIsNotVirtualHierarchicalDeterministic)?; - let badge = virtual_source - .as_hierarchical_deterministic() - .ok_or(Error::BadgeIsNotVirtualHierarchicalDeterministic)?; + + let badge = virtual_source.as_hierarchical_deterministic(); Self::try_from( factor_instance.factor_source_id, diff --git a/wallet_kit_common/Cargo.toml b/wallet_kit_common/Cargo.toml index 20192f3d..356cfb43 100644 --- a/wallet_kit_common/Cargo.toml +++ b/wallet_kit_common/Cargo.toml @@ -3,9 +3,6 @@ name = "wallet_kit_common" version = "0.1.0" edition = "2021" -[lib] -doctest = false - [dependencies] serde = { version = "1.0.192", features = ["derive"] } serde_json = { version = "1.0.108", features = ["preserve_order"] } diff --git a/wallet_kit_common/src/types/hex_32bytes.rs b/wallet_kit_common/src/types/hex_32bytes.rs index c8d7a808..6a9f50ae 100644 --- a/wallet_kit_common/src/types/hex_32bytes.rs +++ b/wallet_kit_common/src/types/hex_32bytes.rs @@ -124,9 +124,11 @@ impl<'de> serde::Deserialize<'de> for Hex32Bytes { #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{collections::HashSet, str::FromStr}; - use crate::error::Error; + use serde_json::json; + + use crate::{error::Error, json::assert_json_value_eq_after_roundtrip}; use super::Hex32Bytes; @@ -137,10 +139,18 @@ mod tests { } #[test] - fn generate_32() { - assert_ne!( - Hex32Bytes::generate().to_string(), - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + fn debug() { + let str = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let hex_bytes = Hex32Bytes::placeholder(); + assert_eq!(format!("{:?}", hex_bytes), str); + } + + #[test] + fn json_roundtrip() { + let model = Hex32Bytes::placeholder(); + assert_json_value_eq_after_roundtrip( + &model, + json!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), ); } @@ -171,4 +181,15 @@ mod tests { Err(Error::InvalidByteCountExpected32) ) } + + #[test] + fn random() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let bytes = Hex32Bytes::new(); + set.insert(bytes.to_vec()); + } + assert_eq!(set.len(), n); + } } diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index c3caaddf..1596cfe1 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -127,7 +127,7 @@ impl Ed25519PrivateKey { #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{collections::HashSet, str::FromStr}; use transaction::signing::ed25519::Ed25519Signature; @@ -239,4 +239,41 @@ mod tests { hex ); } + + #[test] + fn generate_new() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let key = Ed25519PrivateKey::new(); + let bytes = key.to_bytes(); + assert_eq!(bytes.len(), 32); + set.insert(bytes); + } + assert_eq!(set.len(), n); + } + + #[test] + fn from_hex32_bytes() { + let str = "0000000000000000000000000000000000000000000000000000000000000001"; + let hex32 = Hex32Bytes::from_hex(str).unwrap(); + let key = Ed25519PrivateKey::from_hex32_bytes(hex32).unwrap(); + assert_eq!(key.to_hex(), str); + } + + #[test] + fn try_from_bytes() { + let str = "0000000000000000000000000000000000000000000000000000000000000001"; + let vec = hex::decode(str).unwrap(); + let key = Ed25519PrivateKey::try_from(vec.as_slice()).unwrap(); + assert_eq!(key.to_hex(), str); + } + + #[test] + fn placeholder() { + assert_eq!( + Ed25519PrivateKey::placeholder().public_key().to_hex(), + "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" + ); + } } diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 82c6d5e2..11afed48 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -162,6 +162,13 @@ mod tests { ); } + #[test] + fn try_into_from_str() { + let str = "b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde"; + let key: Ed25519PublicKey = str.try_into().unwrap(); + assert_eq!(key.to_hex(), str); + } + #[test] fn invalid_key_not_on_curve() { assert_eq!( diff --git a/wallet_kit_common/src/types/keys/private_key.rs b/wallet_kit_common/src/types/keys/private_key.rs index d34d661c..3c7e79c5 100644 --- a/wallet_kit_common/src/types/keys/private_key.rs +++ b/wallet_kit_common/src/types/keys/private_key.rs @@ -17,7 +17,15 @@ pub enum PrivateKey { } impl From for PrivateKey { - /// Enables `let private_key: PrivateKey = Ed25519PrivateKey::new().into()` + /// Enables: + /// + /// ``` + /// extern crate wallet_kit_common; + /// use wallet_kit_common::types::keys::ed25519::private_key::Ed25519PrivateKey; + /// use wallet_kit_common::types::keys::private_key::PrivateKey; + /// + /// let key: PublicKey = Ed25519PrivateKey::new().public_key().into(); + /// ``` fn from(value: Ed25519PrivateKey) -> Self { Self::Ed25519(value) } @@ -45,7 +53,15 @@ impl PrivateKey { } } - /// Returns the hex representation of the inner public key as a `String`. + /// Returns the hex representation of the inner private key's bytes as a `Vec`. + pub fn to_bytes(&self) -> Vec { + match self { + PrivateKey::Ed25519(key) => key.to_bytes(), + PrivateKey::Secp256k1(key) => key.to_bytes(), + } + } + + /// Returns the hex representation of the inner private key as a `String`. pub fn to_hex(&self) -> String { match self { PrivateKey::Ed25519(key) => key.to_hex(), @@ -57,6 +73,8 @@ impl PrivateKey { #[cfg(test)] mod tests { + use std::collections::HashSet; + use crate::{ secure_random_bytes::generate_32_bytes, types::keys::{ @@ -107,4 +125,17 @@ mod tests { // test `as` assert!(private_key.as_ed25519().is_none()); } + + #[test] + fn generate_new() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let key = PrivateKey::new(); + let bytes = key.to_bytes(); + assert_eq!(bytes.len(), 32); + set.insert(bytes); + } + assert_eq!(set.len(), n); + } } diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index e6786ed1..1659bf8b 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -14,40 +14,65 @@ use enum_as_inner::EnumAsInner; pub enum PublicKey { /// An Ed25519 public key used to verify cryptographic signatures. Ed25519(Ed25519PublicKey), + + /// A secp256k1 public key used to verify cryptographic signatures (ECDSA signatures). Secp256k1(Secp256k1PublicKey), } impl From for PublicKey { + /// Enables: + /// + /// ``` + /// extern crate wallet_kit_common; + /// use wallet_kit_common::types::keys::ed25519::private_key::Ed25519PrivateKey; + /// use wallet_kit_common::types::keys::public_key::PublicKey; + /// + /// let key: PublicKey = Ed25519PrivateKey::new().public_key().into(); + /// ``` fn from(value: Ed25519PublicKey) -> Self { Self::Ed25519(value) } } impl From for PublicKey { + /// Enables: + /// + /// ``` + /// extern crate wallet_kit_common; + /// use wallet_kit_common::types::keys::secp256k1::private_key::Secp256k1PrivateKey; + /// use wallet_kit_common::types::keys::public_key::PublicKey; + /// + /// let key: PublicKey = Secp256k1PrivateKey::new().public_key().into(); + /// ``` fn from(value: Secp256k1PublicKey) -> Self { Self::Secp256k1(value) } } impl PublicKey { + /// Try to instantiate a `PublicKey` from bytes as a `Secp256k1PublicKey`. pub fn secp256k1_from_bytes(slice: &[u8]) -> Result { Secp256k1PublicKey::try_from(slice).map(Self::Secp256k1) } + /// Try to instantiate a `PublicKey` from bytes as a `Ed25519PublicKey`. pub fn ed25519_from_bytes(slice: &[u8]) -> Result { Ed25519PublicKey::try_from(slice).map(Self::Ed25519) } + /// Try to instantiate a `PublicKey` from hex string as a `Secp256k1PublicKey`. pub fn secp256k1_from_str(hex: &str) -> Result { Secp256k1PublicKey::from_str(hex).map(Self::Secp256k1) } + /// Try to instantiate a `PublicKey` from hex string as a `Ed25519PublicKey`. pub fn ed25519_from_str(hex: &str) -> Result { Ed25519PublicKey::from_str(hex).map(Self::Ed25519) } } impl PublicKey { + /// Returns a `SLIP10Curve`, being the curve of the `PublicKey`. pub fn curve(&self) -> SLIP10Curve { match self { PublicKey::Ed25519(_) => SLIP10Curve::Curve25519, @@ -55,6 +80,7 @@ impl PublicKey { } } + /// Returns a hex encoding of the inner public key. pub fn to_hex(&self) -> String { match self { PublicKey::Ed25519(key) => key.to_hex(), @@ -62,6 +88,7 @@ impl PublicKey { } } + /// Returns a clone of the bytes of the inner public key as a `Vec`. pub fn to_bytes(&self) -> Vec { match self { PublicKey::Ed25519(key) => key.to_bytes(), @@ -71,26 +98,32 @@ impl PublicKey { } impl PublicKey { + /// A placeholder used to facilitate unit tests. pub fn placeholder_secp256k1() -> Self { Self::placeholder_secp256k1_alice() } + /// A placeholder used to facilitate unit tests. pub fn placeholder_secp256k1_alice() -> Self { Self::Secp256k1(Secp256k1PublicKey::placeholder_alice()) } + /// A placeholder used to facilitate unit tests. pub fn placeholder_secp256k1_bob() -> Self { Self::Secp256k1(Secp256k1PublicKey::placeholder_bob()) } + /// A placeholder used to facilitate unit tests. pub fn placeholder_ed25519() -> Self { Self::placeholder_ed25519_alice() } + /// A placeholder used to facilitate unit tests. pub fn placeholder_ed25519_alice() -> Self { Self::Ed25519(Ed25519PublicKey::placeholder_alice()) } + /// A placeholder used to facilitate unit tests. pub fn placeholder_ed25519_bob() -> Self { Self::Ed25519(Ed25519PublicKey::placeholder_bob()) } @@ -133,7 +166,12 @@ mod tests { use std::collections::BTreeSet; - use crate::json::assert_eq_after_json_roundtrip; + use crate::{ + json::{assert_eq_after_json_roundtrip, assert_json_fails}, + types::keys::{ + ed25519::public_key::Ed25519PublicKey, secp256k1::public_key::Secp256k1PublicKey, + }, + }; use super::PublicKey; @@ -152,6 +190,30 @@ mod tests { ); } + #[test] + fn json_invalid_curve() { + assert_json_fails::( + r#" + { + "curve": "invalid curve", + "compressedData": "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" + } + "#, + ); + } + + #[test] + fn json_invalid_public_key_not_on_curve() { + assert_json_fails::( + r#" + { + "curve": "curve25519", + "compressedData": "abbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba" + } + "#, + ); + } + #[test] fn json_roundtrip_secp256k1() { let model = PublicKey::placeholder_secp256k1_alice(); @@ -274,4 +336,18 @@ mod tests { let key = PublicKey::ed25519_from_str(hex).unwrap(); assert_eq!(key.to_hex(), hex); } + + #[test] + fn ed25519_into_as_roundtrip() { + let ed25519 = Ed25519PublicKey::placeholder(); + let key: PublicKey = ed25519.clone().into(); + assert_eq!(key.as_ed25519().unwrap(), &ed25519); + } + + #[test] + fn secp256k1_into_as_roundtrip() { + let secp256k1 = Secp256k1PublicKey::placeholder(); + let key: PublicKey = secp256k1.clone().into(); + assert_eq!(key.as_secp256k1().unwrap(), &secp256k1); + } } diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index fcbcc3c9..244fda74 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -130,11 +130,11 @@ impl Secp256k1PrivateKey { #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{collections::HashSet, str::FromStr}; use transaction::signing::secp256k1::Secp256k1Signature; - use crate::{error::Error, hash::hash}; + use crate::{error::Error, hash::hash, types::hex_32bytes::Hex32Bytes}; use super::Secp256k1PrivateKey; @@ -236,4 +236,41 @@ mod tests { hex ); } + + #[test] + fn from_hex32_bytes() { + let str = "0000000000000000000000000000000000000000000000000000000000000001"; + let hex32 = Hex32Bytes::from_hex(str).unwrap(); + let key = Secp256k1PrivateKey::from_hex32_bytes(hex32).unwrap(); + assert_eq!(key.to_hex(), str); + } + + #[test] + fn try_from_bytes() { + let str = "0000000000000000000000000000000000000000000000000000000000000001"; + let vec = hex::decode(str).unwrap(); + let key = Secp256k1PrivateKey::try_from(vec.as_slice()).unwrap(); + assert_eq!(key.to_hex(), str); + } + + #[test] + fn generate_new() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let key = Secp256k1PrivateKey::new(); + let bytes = key.to_bytes(); + assert_eq!(bytes.len(), 32); + set.insert(bytes); + } + assert_eq!(set.len(), n); + } + + #[test] + fn placeholder() { + assert_eq!( + Secp256k1PrivateKey::placeholder().public_key().to_hex(), + "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7" + ); + } } diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 087fbe07..56e4546c 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -175,6 +175,13 @@ mod tests { ) } + #[test] + fn try_into_from_str() { + let str = "02517b88916e7f315bb682f9926b14bc67a0e4246f8a419b986269e1a7e61fffa7"; + let key: Secp256k1PublicKey = str.try_into().unwrap(); + assert_eq!(key.to_hex(), str); + } + #[test] fn inequality() { assert_ne!( diff --git a/wallet_kit_common/src/types/keys/slip10_curve.rs b/wallet_kit_common/src/types/keys/slip10_curve.rs index 6bbba32f..0c7f9f37 100644 --- a/wallet_kit_common/src/types/keys/slip10_curve.rs +++ b/wallet_kit_common/src/types/keys/slip10_curve.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] pub enum SLIP10Curve { - /// Curve25519 or Ed25519 + /// Curve25519 which we use for Ed25519 for EdDSA signatures. Curve25519, /// The bitcoin curve, used by Radix Olympia and still valid From 871a41d3f76d798a43b214d1776a2402aaac7a5d Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 1 Dec 2023 19:17:18 +0100 Subject: [PATCH 23/39] mooore tests --- hierarchical_deterministic/src/bip32/hd_path.rs | 1 + .../src/bip39/mnemonic.rs | 1 + .../src/bip44/bip44_like_path.rs | 1 + .../src/cap26/cap26_path/cap26_path.rs | 17 +++++++++++++++++ .../src/cap26/cap26_path/paths/account_path.rs | 1 + .../src/derivation/derivation_path.rs | 2 ++ .../entity_security_state.rs | 2 ++ .../factor_instance/badge_virtual_source.rs | 2 ++ .../factor_instance/factor_instance_badge.rs | 2 ++ profile/src/v100/factors/factor_source.rs | 2 ++ profile/src/v100/factors/factor_source_id.rs | 2 ++ wallet_kit_common/src/types/keys/public_key.rs | 2 ++ 12 files changed, 35 insertions(+) diff --git a/hierarchical_deterministic/src/bip32/hd_path.rs b/hierarchical_deterministic/src/bip32/hd_path.rs index 55e81dbc..63d36192 100644 --- a/hierarchical_deterministic/src/bip32/hd_path.rs +++ b/hierarchical_deterministic/src/bip32/hd_path.rs @@ -130,6 +130,7 @@ impl Serialize for HDPath { impl<'de> serde::Deserialize<'de> for HDPath { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; HDPath::from_str(&s).map_err(de::Error::custom) diff --git a/hierarchical_deterministic/src/bip39/mnemonic.rs b/hierarchical_deterministic/src/bip39/mnemonic.rs index b0c8ad80..565a0089 100644 --- a/hierarchical_deterministic/src/bip39/mnemonic.rs +++ b/hierarchical_deterministic/src/bip39/mnemonic.rs @@ -59,6 +59,7 @@ impl Serialize for Mnemonic { impl<'de> serde::Deserialize<'de> for Mnemonic { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; Mnemonic::from_phrase(&s).map_err(de::Error::custom) diff --git a/hierarchical_deterministic/src/bip44/bip44_like_path.rs b/hierarchical_deterministic/src/bip44/bip44_like_path.rs index affbf874..8cce8498 100644 --- a/hierarchical_deterministic/src/bip44/bip44_like_path.rs +++ b/hierarchical_deterministic/src/bip44/bip44_like_path.rs @@ -72,6 +72,7 @@ impl Serialize for BIP44LikePath { impl<'de> serde::Deserialize<'de> for BIP44LikePath { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; BIP44LikePath::from_str(&s).map_err(de::Error::custom) diff --git a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs index f4dcef81..b2fbb36a 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs @@ -70,6 +70,11 @@ impl CAP26Path { #[cfg(test)] mod tests { + use serde_json::json; + use wallet_kit_common::json::{ + assert_eq_after_json_roundtrip, assert_json_roundtrip, assert_json_value_eq_after_roundtrip, + }; + use crate::{ cap26::cap26_path::paths::{account_path::AccountPath, getid_path::GetIDPath}, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, @@ -124,4 +129,16 @@ mod tests { GetIDPath::default().into() ); } + + #[test] + fn json_roundtrip_getid() { + let model: CAP26Path = GetIDPath::default().into(); + assert_json_value_eq_after_roundtrip(&model, json!("m/44H/1022H/365H")); + } + + #[test] + fn json_roundtrip_account() { + let model: CAP26Path = AccountPath::placeholder().into(); + assert_json_value_eq_after_roundtrip(&model, json!("m/44H/1022H/1H/525H/1460H/0H")); + } } diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs index a510f876..7f948d11 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs @@ -60,6 +60,7 @@ impl Serialize for AccountPath { impl<'de> serde::Deserialize<'de> for AccountPath { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; AccountPath::from_str(&s).map_err(de::Error::custom) diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 372135a6..640c6a42 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -26,6 +26,7 @@ impl Debug for DerivationPath { impl<'de> serde::Deserialize<'de> for DerivationPath { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { #[derive(Deserialize)] pub struct Inner { @@ -48,6 +49,7 @@ impl<'de> serde::Deserialize<'de> for DerivationPath { } impl Serialize for DerivationPath { + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/profile/src/v100/entity_security_state/entity_security_state.rs b/profile/src/v100/entity_security_state/entity_security_state.rs index 6e5778d5..89471e4f 100644 --- a/profile/src/v100/entity_security_state/entity_security_state.rs +++ b/profile/src/v100/entity_security_state/entity_security_state.rs @@ -10,6 +10,7 @@ pub enum EntitySecurityState { } impl<'de> Deserialize<'de> for EntitySecurityState { + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 #[derive(Deserialize, Serialize)] @@ -24,6 +25,7 @@ impl<'de> Deserialize<'de> for EntitySecurityState { } impl Serialize for EntitySecurityState { + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs index 84e42c6a..3182c422 100644 --- a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs +++ b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs @@ -16,6 +16,7 @@ impl FactorInstanceBadgeVirtualSource { } impl<'de> Deserialize<'de> for FactorInstanceBadgeVirtualSource { + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 #[derive(Deserialize, Serialize)] @@ -30,6 +31,7 @@ impl<'de> Deserialize<'de> for FactorInstanceBadgeVirtualSource { } impl Serialize for FactorInstanceBadgeVirtualSource { + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs index 1eccc59a..88c7f4f6 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs @@ -20,6 +20,7 @@ impl FactorInstanceBadge { } impl<'de> Deserialize<'de> for FactorInstanceBadge { + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 #[derive(Deserialize, Serialize)] @@ -34,6 +35,7 @@ impl<'de> Deserialize<'de> for FactorInstanceBadge { } impl Serialize for FactorInstanceBadge { + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/profile/src/v100/factors/factor_source.rs b/profile/src/v100/factors/factor_source.rs index 9d654c84..46f6167e 100644 --- a/profile/src/v100/factors/factor_source.rs +++ b/profile/src/v100/factors/factor_source.rs @@ -10,6 +10,7 @@ pub enum FactorSource { } impl<'de> Deserialize<'de> for FactorSource { + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 #[derive(Deserialize, Serialize)] @@ -24,6 +25,7 @@ impl<'de> Deserialize<'de> for FactorSource { } impl Serialize for FactorSource { + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/profile/src/v100/factors/factor_source_id.rs b/profile/src/v100/factors/factor_source_id.rs index 3a03cda2..b3ff8779 100644 --- a/profile/src/v100/factors/factor_source_id.rs +++ b/profile/src/v100/factors/factor_source_id.rs @@ -35,6 +35,7 @@ impl From for FactorSourceID { } impl<'de> Deserialize<'de> for FactorSourceID { + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { // https://github.com/serde-rs/serde/issues/1343#issuecomment-409698470 #[derive(Deserialize, Serialize)] @@ -49,6 +50,7 @@ impl<'de> Deserialize<'de> for FactorSourceID { } impl Serialize for FactorSourceID { + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 1659bf8b..58c14c39 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -130,6 +130,7 @@ impl PublicKey { } impl<'de> Deserialize<'de> for PublicKey { + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { #[derive(Deserialize, Serialize)] struct Wrapper { @@ -150,6 +151,7 @@ impl<'de> Deserialize<'de> for PublicKey { } impl Serialize for PublicKey { + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result where S: Serializer, From 06ac9ec8ed93abe412f55158e1c4e1a5641d6ab1 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 1 Dec 2023 21:06:11 +0100 Subject: [PATCH 24/39] docs and tests --- .../src/cap26/cap26_path/cap26_path.rs | 4 +- .../src/cap26/cap26_path/paths/getid_path.rs | 13 ++++--- .../src/cap26/cap26_repr.rs | 1 + .../src/derivation/derivation.rs | 1 + .../src/derivation/derivation_path.rs | 17 +++++---- .../derivation/mnemonic_with_passphrase.rs | 1 + profile/src/v100/address/account_address.rs | 11 ++++++ profile/src/v100/address/entity_address.rs | 1 + profile/src/v100/address/identity_address.rs | 1 + profile/src/v100/address/resource_address.rs | 1 + profile/src/v100/entity/account/account.rs | 6 +++ .../device_factor_source.rs | 37 +++++++++++++++++++ wallet_kit_common/Cargo.toml | 1 + wallet_kit_common/src/json.rs | 1 + wallet_kit_common/src/network_id.rs | 23 ++++++++++++ .../src/types/keys/private_key.rs | 12 +++++- 16 files changed, 112 insertions(+), 19 deletions(-) diff --git a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs index b2fbb36a..df185c30 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs @@ -71,9 +71,7 @@ impl CAP26Path { #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::json::{ - assert_eq_after_json_roundtrip, assert_json_roundtrip, assert_json_value_eq_after_roundtrip, - }; + use wallet_kit_common::json::assert_json_value_eq_after_roundtrip; use crate::{ cap26::cap26_path::paths::{account_path::AccountPath, getid_path::GetIDPath}, diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs index 2908978d..0d2fb208 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs @@ -2,7 +2,6 @@ use serde::{de, Deserializer, Serialize, Serializer}; use crate::{ bip32::{hd_path::HDPath, hd_path_component::HDPathValue}, - cap26::cap26_path::cap26_path::CAP26Path, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, hdpath_error::HDPathError, }; @@ -29,10 +28,6 @@ impl Default for GetIDPath { } impl GetIDPath { - pub fn embed(&self) -> CAP26Path { - CAP26Path::GetID(self.clone()) - } - pub const LAST_COMPONENT_VALUE: HDPathValue = 365; pub fn from_str(s: &str) -> Result { @@ -63,6 +58,7 @@ impl Serialize for GetIDPath { impl<'de> serde::Deserialize<'de> for GetIDPath { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; GetIDPath::from_str(&s).map_err(de::Error::custom) @@ -80,7 +76,7 @@ impl TryInto for &str { #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::json::assert_json_value_eq_after_roundtrip; + use wallet_kit_common::json::{assert_json_value_eq_after_roundtrip, assert_json_value_fails}; use crate::{derivation::derivation::Derivation, hdpath_error::HDPathError}; @@ -119,4 +115,9 @@ mod tests { let parsed: GetIDPath = str.try_into().unwrap(); assert_json_value_eq_after_roundtrip(&parsed, json!(str)); } + + #[test] + fn json_roundtrip_invalid() { + assert_json_value_fails::(json!("m/44H/1022H/99H")); + } } diff --git a/hierarchical_deterministic/src/cap26/cap26_repr.rs b/hierarchical_deterministic/src/cap26/cap26_repr.rs index c8f23b74..205097ad 100644 --- a/hierarchical_deterministic/src/cap26/cap26_repr.rs +++ b/hierarchical_deterministic/src/cap26/cap26_repr.rs @@ -24,6 +24,7 @@ pub trait CAP26Repr: Derivation { index: HDPathValue, ) -> Self; + #[cfg(not(tarpaulin_include))] // false negative, this is in fact heavily tested. fn from_str(s: &str) -> Result { use HDPathError::*; let (path, components) = HDPath::try_parse_base(s, HDPathError::InvalidDepthOfCAP26Path)?; diff --git a/hierarchical_deterministic/src/derivation/derivation.rs b/hierarchical_deterministic/src/derivation/derivation.rs index 3471e7b3..f979f38a 100644 --- a/hierarchical_deterministic/src/derivation/derivation.rs +++ b/hierarchical_deterministic/src/derivation/derivation.rs @@ -11,6 +11,7 @@ pub trait Derivation: Sized { fn scheme(&self) -> DerivationPathScheme; + #[cfg(not(tarpaulin_include))] // false negative fn last_component(&self) -> &HDPathComponent { self.hd_path().components().last().unwrap() } diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 640c6a42..1fdf8740 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -109,15 +109,11 @@ impl From for DerivationPath { #[cfg(test)] mod tests { - use wallet_kit_common::{json::assert_eq_after_json_roundtrip, network_id::NetworkID}; + use wallet_kit_common::json::assert_eq_after_json_roundtrip; use crate::{ bip44::bip44_like_path::BIP44LikePath, - cap26::{ - cap26_key_kind::CAP26KeyKind, - cap26_path::paths::{account_path::AccountPath, getid_path::GetIDPath}, - cap26_repr::CAP26Repr, - }, + cap26::cap26_path::paths::{account_path::AccountPath, getid_path::GetIDPath}, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, }; @@ -180,8 +176,7 @@ mod tests { #[test] fn json_cap26_account() { - let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); - let model: DerivationPath = path.into(); + let model = DerivationPath::placeholder(); assert_eq_after_json_roundtrip( &model, r#" @@ -193,6 +188,12 @@ mod tests { ); } + #[test] + fn debug() { + let model = DerivationPath::placeholder(); + assert_eq!(format!("{:?}", model), "m/44H/1022H/1H/525H/1460H/0H") + } + #[test] fn json_cap26_getid() { let path = GetIDPath::default(); diff --git a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs index e00563df..9c08144f 100644 --- a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs +++ b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs @@ -90,6 +90,7 @@ impl MnemonicWithPassphrase { .expect("Valid Secp256k1PrivateKey bytes") } + #[cfg(not(tarpaulin_include))] // false negative pub fn derive_private_key(&self, derivation: D) -> PrivateKey where D: Derivation, diff --git a/profile/src/v100/address/account_address.rs b/profile/src/v100/address/account_address.rs index 55e471d6..5ae7fd5a 100644 --- a/profile/src/v100/address/account_address.rs +++ b/profile/src/v100/address/account_address.rs @@ -42,6 +42,7 @@ impl Serialize for AccountAddress { impl<'de> serde::Deserialize<'de> for AccountAddress { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; AccountAddress::try_from_bech32(&s).map_err(de::Error::custom) @@ -138,6 +139,16 @@ mod tests { .is_ok()); } + #[test] + fn from_bech32_invalid_entity_type() { + assert_eq!( + AccountAddress::try_from_bech32( + "identity_tdx_21_12tljxea3s0mse52jmpvsphr0haqs86sung8d3qlhr763nxttj59650", + ), + Err(Error::MismatchingEntityTypeWhileDecodingAddress) + ); + } + #[test] fn format() { let a = AccountAddress::try_from_bech32( diff --git a/profile/src/v100/address/entity_address.rs b/profile/src/v100/address/entity_address.rs index f8bbee6c..1612dcf6 100644 --- a/profile/src/v100/address/entity_address.rs +++ b/profile/src/v100/address/entity_address.rs @@ -21,6 +21,7 @@ pub trait EntityAddress: Sized { /// Creates a new address from `public_key` and `network_id` by bech32 encoding /// it. + #[cfg(not(tarpaulin_include))] // false negative fn from_public_key(public_key: PublicKey, network_id: NetworkID) -> Self { let component = match Self::entity_type() { AbstractEntityType::Account => virtual_account_address_from_public_key(&public_key), diff --git a/profile/src/v100/address/identity_address.rs b/profile/src/v100/address/identity_address.rs index ad6e775a..d371767c 100644 --- a/profile/src/v100/address/identity_address.rs +++ b/profile/src/v100/address/identity_address.rs @@ -59,6 +59,7 @@ impl Serialize for IdentityAddress { impl<'de> serde::Deserialize<'de> for IdentityAddress { /// Tries to deserializes a JSON string as a bech32 address into an `IdentityAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; IdentityAddress::try_from_bech32(&s).map_err(de::Error::custom) diff --git a/profile/src/v100/address/resource_address.rs b/profile/src/v100/address/resource_address.rs index 0b549814..14e8e70a 100644 --- a/profile/src/v100/address/resource_address.rs +++ b/profile/src/v100/address/resource_address.rs @@ -27,6 +27,7 @@ impl Serialize for ResourceAddress { impl<'de> serde::Deserialize<'de> for ResourceAddress { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; ResourceAddress::try_from_bech32(&s).map_err(de::Error::custom) diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index 3532bbf3..b34b4c8a 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -179,6 +179,12 @@ impl Account { Self::placeholder_mainnet() } + // /// A `Mainnet` account, a placeholder used to facilitate unit tests. + // pub fn placeholder_alice() -> Self { + // let mwp = MnemonicWithPassphrase::placeholder(); + // let bdfs = DeviceFactorSource::placeholder() + // } + pub fn placeholder_mainnet() -> Self { Self::with_values( "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" diff --git a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs index 39ba57de..9940946d 100644 --- a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs +++ b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs @@ -36,3 +36,40 @@ impl DeviceFactorSource { } } } + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::DeviceFactorSource; + + #[test] + fn json() { + let model = DeviceFactorSource::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "common": { + "addedOn": "2023-09-11T16:05:56", + "cryptoParameters": { + "supportedCurves": ["curve25519"], + "supportedDerivationPathSchemes": ["cap26"] + }, + "flags": ["main"], + "lastUsedOn": "2023-09-11T16:05:56" + }, + "hint": { + "mnemonicWordCount": 24, + "model": "iPhone", + "name": "Unknown Name" + }, + "id": { + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240", + "kind": "device" + } + } + "#, + ); + } +} diff --git a/wallet_kit_common/Cargo.toml b/wallet_kit_common/Cargo.toml index 356cfb43..1b4634ea 100644 --- a/wallet_kit_common/Cargo.toml +++ b/wallet_kit_common/Cargo.toml @@ -23,3 +23,4 @@ bip32 = "0.5.1" # only need Secp256k1, to do validation of PublicKey ed25519-dalek = "1.0.1" rand = "0.8.5" enum-as-inner = "0.6.0" +pretty_assertions = "1.4.0" diff --git a/wallet_kit_common/src/json.rs b/wallet_kit_common/src/json.rs index f4a43957..32f56af2 100644 --- a/wallet_kit_common/src/json.rs +++ b/wallet_kit_common/src/json.rs @@ -1,4 +1,5 @@ use core::fmt::Debug; +use pretty_assertions::{assert_eq, assert_ne}; use serde::{de::DeserializeOwned, ser::Serialize}; use serde_json::Value; diff --git a/wallet_kit_common/src/network_id.rs b/wallet_kit_common/src/network_id.rs index e9d8f5fe..6c48e303 100644 --- a/wallet_kit_common/src/network_id.rs +++ b/wallet_kit_common/src/network_id.rs @@ -58,6 +58,11 @@ pub enum NetworkID { /// The third release candidate of Babylon (RCnet v3) Zabanet = 0x0e, + /// Enkinet (0x21 / 0d33) + /// + /// https://github.com/radixdlt/babylon-node/blob/main/common/src/main/java/com/radixdlt/networks/Network.java#L94 + Enkinet = 0x21, + /// Simulator (0xf2 / 0d242) Simulator = 242, } @@ -108,6 +113,11 @@ impl NetworkID { NetworkID::Kisharnet => NetworkDefinition::kisharnet(), NetworkID::Ansharnet => NetworkDefinition::ansharnet(), NetworkID::Zabanet => NetworkDefinition::zabanet(), + NetworkID::Enkinet => NetworkDefinition { + id: NetworkID::Enkinet.discriminant(), + logical_name: String::from("enkinet"), + hrp_suffix: String::from("tdx_21_"), + }, NetworkID::Simulator => NetworkDefinition::simulator(), } } @@ -170,6 +180,11 @@ mod tests { assert_eq!(NetworkID::Adapanet.discriminant(), 0x0a); } + #[test] + fn discriminant_enkinet() { + assert_eq!(NetworkID::Enkinet.discriminant(), 0x21); + } + #[test] fn discriminant_nebunet() { assert_eq!(NetworkID::Nebunet.discriminant(), 0x0b); @@ -207,6 +222,14 @@ mod tests { ) } + #[test] + fn lookup_network_definition_enkinet() { + assert_eq!( + NetworkID::Enkinet.network_definition().id, + NetworkID::Enkinet.discriminant() + ) + } + #[test] fn logical_name() { assert_eq!(NetworkID::Mainnet.logical_name(), "mainnet") diff --git a/wallet_kit_common/src/types/keys/private_key.rs b/wallet_kit_common/src/types/keys/private_key.rs index 3c7e79c5..227a9dac 100644 --- a/wallet_kit_common/src/types/keys/private_key.rs +++ b/wallet_kit_common/src/types/keys/private_key.rs @@ -22,7 +22,7 @@ impl From for PrivateKey { /// ``` /// extern crate wallet_kit_common; /// use wallet_kit_common::types::keys::ed25519::private_key::Ed25519PrivateKey; - /// use wallet_kit_common::types::keys::private_key::PrivateKey; + /// use wallet_kit_common::types::keys::public_key::PublicKey; /// /// let key: PublicKey = Ed25519PrivateKey::new().public_key().into(); /// ``` @@ -32,7 +32,15 @@ impl From for PrivateKey { } impl From for PrivateKey { - /// Enables `let private_key: PrivateKey = Secp256k1PrivateKey::new().into()` + /// Enables: + /// + /// ``` + /// extern crate wallet_kit_common; + /// use wallet_kit_common::types::keys::secp256k1::private_key::Secp256k1PrivateKey; + /// use wallet_kit_common::types::keys::public_key::PublicKey; + /// + /// let key: PublicKey = Secp256k1PrivateKey::new().public_key().into(); + /// ``` fn from(value: Secp256k1PrivateKey) -> Self { Self::Secp256k1(value) } From ef9cd39a9c141e49b752c7bbe9a0351b0b463bf9 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 2 Dec 2023 09:27:03 +0100 Subject: [PATCH 25/39] more tests --- hierarchical_deterministic/Cargo.toml | 1 + .../src/derivation/derivation_path.rs | 23 +++++- profile/src/v100/entity/account/account.rs | 4 +- .../unsecured_entity_control.rs | 48 +++++++++--- ...rarchical_deterministic_factor_instance.rs | 76 ++++++++++++++++--- wallet_kit_common/src/error.rs | 6 ++ 6 files changed, 129 insertions(+), 29 deletions(-) diff --git a/hierarchical_deterministic/Cargo.toml b/hierarchical_deterministic/Cargo.toml index 93e75ea2..627a1d08 100644 --- a/hierarchical_deterministic/Cargo.toml +++ b/hierarchical_deterministic/Cargo.toml @@ -20,3 +20,4 @@ radix-engine-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", re ] } bip32 = "0.5.1" # slip10 crate does not support `secp256k1`. hex = "0.4.3" +enum-as-inner = "0.6.0" diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 1fdf8740..49056ae6 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -9,10 +9,11 @@ use crate::{ paths::{account_path::AccountPath, getid_path::GetIDPath}, }, }; +use enum_as_inner::EnumAsInner; use std::fmt::{Debug, Formatter}; /// A derivation path on either supported schemes, either Babylon (CAP26) or Olympia (BIP44Like). -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, EnumAsInner, PartialOrd, Ord)] pub enum DerivationPath { CAP26(CAP26Path), BIP44Like(BIP44LikePath), @@ -113,7 +114,10 @@ mod tests { use crate::{ bip44::bip44_like_path::BIP44LikePath, - cap26::cap26_path::paths::{account_path::AccountPath, getid_path::GetIDPath}, + cap26::cap26_path::{ + cap26_path::CAP26Path, + paths::{account_path::AccountPath, getid_path::GetIDPath}, + }, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, }; @@ -158,6 +162,12 @@ mod tests { ); } + #[test] + fn as_bip44_path() { + let path: DerivationPath = BIP44LikePath::placeholder().into(); + assert_eq!(path.as_bip44_like().unwrap(), &BIP44LikePath::placeholder()); + } + #[test] fn into_from_account_cap26_path() { assert_eq!( @@ -166,6 +176,15 @@ mod tests { ); } + #[test] + fn as_cap26_path() { + let path: DerivationPath = AccountPath::placeholder().into(); + assert_eq!( + path.as_cap26().unwrap(), + &CAP26Path::AccountPath(AccountPath::placeholder()) + ); + } + #[test] fn into_from_getid_path() { assert_eq!( diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index b34b4c8a..0c8f9a07 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -92,9 +92,7 @@ impl Account { appearance_id: RefCell::new(appearance_id), flags: RefCell::new(EntityFlags::default()), on_ledger_settings: RefCell::new(OnLedgerSettings::default()), - security_state: EntitySecurityState::Unsecured(UnsecuredEntityControl::new( - HierarchicalDeterministicFactorInstance::placeholder(), - )), + security_state: EntitySecurityState::placeholder(), } } } diff --git a/profile/src/v100/entity_security_state/unsecured_entity_control.rs b/profile/src/v100/entity_security_state/unsecured_entity_control.rs index d87ef0aa..5ab18548 100644 --- a/profile/src/v100/entity_security_state/unsecured_entity_control.rs +++ b/profile/src/v100/entity_security_state/unsecured_entity_control.rs @@ -1,4 +1,6 @@ +use hierarchical_deterministic::cap26::cap26_key_kind::CAP26KeyKind; use serde::{Deserialize, Serialize}; +use wallet_kit_common::error::Error; use crate::v100::factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance; @@ -19,27 +21,39 @@ pub struct UnsecuredEntityControl { } impl UnsecuredEntityControl { - pub fn with_authentication_signing( + pub fn new( transaction_signing: HierarchicalDeterministicFactorInstance, - authentication_signing: HierarchicalDeterministicFactorInstance, - ) -> Self { - Self { - transaction_signing, - authentication_signing: Some(authentication_signing), + authentication_signing: Option, + ) -> Result { + if let Some(auth) = &authentication_signing { + if let Some(key_kind) = auth.key_kind() { + if key_kind != CAP26KeyKind::AuthenticationSigning { + return Err(Error::WrongKeyKindOfAuthenticationSigningFactorInstance); + } + } } - } - pub fn new(transaction_signing: HierarchicalDeterministicFactorInstance) -> Self { - Self { - transaction_signing, - authentication_signing: Option::None, + if let Some(key_kind) = transaction_signing.key_kind() { + if key_kind != CAP26KeyKind::TransactionSigning { + return Err(Error::WrongKeyKindOfTransactionSigningFactorInstance); + } } + Ok(Self { + transaction_signing, + authentication_signing, + }) + } + pub fn with_transaction_signing_only( + transaction_signing: HierarchicalDeterministicFactorInstance, + ) -> Result { + Self::new(transaction_signing, None) } } impl UnsecuredEntityControl { /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { - Self::new(HierarchicalDeterministicFactorInstance::placeholder()) + Self::with_transaction_signing_only(HierarchicalDeterministicFactorInstance::placeholder()) + .expect("Valid placeholder") } } @@ -47,8 +61,18 @@ impl UnsecuredEntityControl { mod tests { use wallet_kit_common::json::assert_eq_after_json_roundtrip; + use crate::v100::factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance; + use super::UnsecuredEntityControl; + #[test] + fn with_auth_signing() { + let tx_sign = HierarchicalDeterministicFactorInstance::placeholder_transaction_signing(); + let auth_sign = HierarchicalDeterministicFactorInstance::placeholder_auth_signing(); + let control = UnsecuredEntityControl::new(tx_sign, Some(auth_sign.clone())).unwrap(); + assert_eq!(control.authentication_signing, Some(auth_sign)); + } + #[test] fn json_roundtrip() { let model = UnsecuredEntityControl::placeholder(); diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 7df6a7f6..8b74b248 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -1,4 +1,5 @@ use hierarchical_deterministic::{ + bip32::hd_path_component::HDPathValue, cap26::{ cap26_key_kind::CAP26KeyKind, cap26_path::{cap26_path::CAP26Path, paths::account_path::AccountPath}, @@ -87,6 +88,16 @@ impl HierarchicalDeterministicFactorInstance { ), ) } + + pub fn key_kind(&self) -> Option { + match &self.derivation_path { + DerivationPath::CAP26(cap26) => match cap26 { + CAP26Path::GetID(_) => None, + CAP26Path::AccountPath(account_path) => Some(account_path.key_kind), + }, + DerivationPath::BIP44Like(_) => None, + } + } } impl Serialize for HierarchicalDeterministicFactorInstance { @@ -112,27 +123,57 @@ impl<'de> serde::Deserialize<'de> for HierarchicalDeterministicFactorInstance { impl HierarchicalDeterministicFactorInstance { /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { - let mwp = MnemonicWithPassphrase::placeholder(); - let path = AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); - let private_key = mwp.derive_private_key(path.clone()); - - assert_eq!(path.to_string(), "m/44H/1022H/1H/525H/1460H/0H"); + Self::placeholder_transaction_signing() + } + /// A placeholder used to facilitate unit tests. + pub fn placeholder_transaction_signing() -> Self { + let placeholder = Self::placeholder_with_key_kind(CAP26KeyKind::TransactionSigning, 0); assert_eq!( - "cf52dbc7bb2663223e99fb31799281b813b939440a372d0aa92eb5f5b8516003", - private_key.to_hex() + placeholder.derivation_path.to_string(), + "m/44H/1022H/1H/525H/1460H/0H" ); - let public_key = private_key.public_key(); + assert_eq!( "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b", - public_key.to_hex() + placeholder.public_key.to_hex() ); - let id = - FactorSourceIDFromHash::from_mnemonic_with_passphrase(FactorSourceKind::Device, mwp); + + assert_eq!( + placeholder.factor_source_id.to_string(), + "device:3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" + ); + return placeholder; + } + + /// A placeholder used to facilitate unit tests. + pub fn placeholder_auth_signing() -> Self { + let placeholder = Self::placeholder_with_key_kind(CAP26KeyKind::AuthenticationSigning, 0); assert_eq!( - id.to_string(), + placeholder.derivation_path.to_string(), + "m/44H/1022H/1H/525H/1678H/0H" + ); + + assert_eq!( + "564d6ca366bb24cbe8b512324c93809a32039d74f133bfa82d1e80d93225989f", + placeholder.public_key.to_hex() + ); + + assert_eq!( + placeholder.factor_source_id.to_string(), "device:3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" ); + return placeholder; + } + + /// A placeholder used to facilitate unit tests. + fn placeholder_with_key_kind(key_kind: CAP26KeyKind, index: HDPathValue) -> Self { + let mwp = MnemonicWithPassphrase::placeholder(); + let path = AccountPath::new(NetworkID::Mainnet, key_kind, index); + let private_key = mwp.derive_private_key(path.clone()); + let public_key = private_key.public_key(); + let id = + FactorSourceIDFromHash::from_mnemonic_with_passphrase(FactorSourceKind::Device, mwp); Self::new( id, public_key, @@ -143,6 +184,7 @@ impl HierarchicalDeterministicFactorInstance { #[cfg(test)] mod tests { + use hierarchical_deterministic::derivation::derivation::Derivation; use wallet_kit_common::json::assert_eq_after_json_roundtrip; use super::HierarchicalDeterministicFactorInstance; @@ -181,4 +223,14 @@ mod tests { "#, ); } + + #[test] + fn placeholder_auth() { + assert_eq!( + HierarchicalDeterministicFactorInstance::placeholder_auth_signing() + .derivation_path + .to_string(), + "m/44H/1022H/1H/525H/1678H/0H" + ); + } } diff --git a/wallet_kit_common/src/error.rs b/wallet_kit_common/src/error.rs index b2ac6266..288df079 100644 --- a/wallet_kit_common/src/error.rs +++ b/wallet_kit_common/src/error.rs @@ -82,4 +82,10 @@ pub enum Error { #[error("Failed to create FactorSourceIDFromHash from FactorSourceID")] FactorSourceIDNotFromHash, + + #[error("Failed to create UnsecuredEntityControl, transactionSigning factor instance path has wrong key kind")] + WrongKeyKindOfTransactionSigningFactorInstance, + + #[error("Failed to create UnsecuredEntityControl, authenticationSigning factor instance path has wrong key kind")] + WrongKeyKindOfAuthenticationSigningFactorInstance, } From ada7f7a9ecdd566eaacfba5c9da89046587dfada Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 2 Dec 2023 21:37:15 +0100 Subject: [PATCH 26/39] big commit... restructure errors, add Private HD FactorInstance, HD Private Key, Private HD Factor Source and various conversion methods. Also add two placeholder Accounts, Alice and Bob, not dummy impl, but using a Mnemonic. --- .../src/bip32/hd_path.rs | 16 ++- .../src/bip39/bip39_word/bip39_word.rs | 7 +- .../src/bip39/bip39_word_count.rs | 2 +- .../src/bip39/mnemonic.rs | 4 +- .../src/bip44/bip44_like_path.rs | 33 ++++-- .../src/cap26/cap26_entity_kind.rs | 2 + .../src/cap26/cap26_key_kind.rs | 2 + .../src/cap26/cap26_path/cap26_path.rs | 24 +++- .../cap26/cap26_path/paths/account_path.rs | 57 +++++++-- .../src/cap26/cap26_path/paths/getid_path.rs | 35 ++++-- .../cap26/cap26_path/paths/is_entity_path.rs | 26 +++++ .../src/cap26/cap26_path/paths/mod.rs | 1 + .../src/cap26/cap26_repr.rs | 24 +++- .../src/derivation/derivation.rs | 3 +- .../src/derivation/derivation_path.rs | 22 ++++ .../hierarchical_deterministic_private_key.rs | 33 ++++++ .../hierarchical_deterministic_public_key.rs | 21 +++- .../derivation/mnemonic_with_passphrase.rs | 22 ++-- .../src/derivation/mod.rs | 1 + hierarchical_deterministic/src/lib.rs | 1 - profile/src/v100/address/account_address.rs | 7 +- .../src/v100/address/decode_address_helper.rs | 5 +- profile/src/v100/address/entity_address.rs | 27 ++++- profile/src/v100/address/identity_address.rs | 18 +-- .../v100/address/non_fungible_global_id.rs | 8 +- profile/src/v100/address/resource_address.rs | 2 +- .../src/v100/entity/abstract_entity_type.rs | 2 +- profile/src/v100/entity/account/account.rs | 109 ++++++++++++++---- .../src/v100/entity/account/appearance_id.rs | 11 +- profile/src/v100/entity/display_name.rs | 15 ++- .../entity_security_state.rs | 6 + .../unsecured_entity_control.rs | 16 ++- .../factor_instance/badge_virtual_source.rs | 6 + .../factor_instance/factor_instance.rs | 22 +++- .../factor_instance/factor_instance_badge.rs | 13 +++ .../src/v100/factors/factor_instance/mod.rs | 1 + ...rarchical_deterministic_factor_instance.rs | 37 ++++++ profile/src/v100/factors/factor_source.rs | 10 +- .../src/v100/factors/factor_source_common.rs | 12 ++ .../factor_source_crypto_parameters.rs | 6 +- .../factors/factor_source_id_from_hash.rs | 1 - .../device_factor_source.rs | 67 ++++++++++- .../device_factor_source_hint.rs | 20 ++-- .../src/v100/factors/factor_sources/mod.rs | 1 + ...ierarchical_deterministic_factor_source.rs | 59 ++++++++++ .../hd_transaction_signing_factor_instance.rs | 84 ++++++++++++++ ...rarchical_deterministic_factor_instance.rs | 61 +++++----- profile/src/v100/factors/is_factor_source.rs | 9 ++ profile/src/v100/factors/mod.rs | 2 + profile/src/v100/networks/network/accounts.rs | 2 +- wallet_kit_common/src/error.rs | 91 --------------- wallet_kit_common/src/error/bytes_error.rs | 10 ++ wallet_kit_common/src/error/common_error.rs | 70 +++++++++++ .../src/error}/hdpath_error.rs | 25 ++-- wallet_kit_common/src/error/key_error.rs | 34 ++++++ wallet_kit_common/src/error/mod.rs | 4 + wallet_kit_common/src/network_id.rs | 2 +- wallet_kit_common/src/types/hex_32bytes.rs | 6 +- .../src/types/keys/ed25519/private_key.rs | 8 +- .../src/types/keys/ed25519/public_key.rs | 8 +- .../src/types/keys/public_key.rs | 32 ++++- .../src/types/keys/secp256k1/private_key.rs | 10 +- .../src/types/keys/secp256k1/public_key.rs | 10 +- 63 files changed, 991 insertions(+), 294 deletions(-) create mode 100644 hierarchical_deterministic/src/cap26/cap26_path/paths/is_entity_path.rs create mode 100644 hierarchical_deterministic/src/derivation/hierarchical_deterministic_private_key.rs create mode 100644 profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs create mode 100644 profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs create mode 100644 profile/src/v100/factors/hd_transaction_signing_factor_instance.rs create mode 100644 profile/src/v100/factors/is_factor_source.rs delete mode 100644 wallet_kit_common/src/error.rs create mode 100644 wallet_kit_common/src/error/bytes_error.rs create mode 100644 wallet_kit_common/src/error/common_error.rs rename {hierarchical_deterministic/src => wallet_kit_common/src/error}/hdpath_error.rs (79%) create mode 100644 wallet_kit_common/src/error/key_error.rs create mode 100644 wallet_kit_common/src/error/mod.rs diff --git a/hierarchical_deterministic/src/bip32/hd_path.rs b/hierarchical_deterministic/src/bip32/hd_path.rs index 63d36192..19d816ac 100644 --- a/hierarchical_deterministic/src/bip32/hd_path.rs +++ b/hierarchical_deterministic/src/bip32/hd_path.rs @@ -3,8 +3,7 @@ use std::str::FromStr; use itertools::Itertools; use serde::{de, Deserializer, Serialize, Serializer}; use slip10::path::BIP32Path; - -use crate::hdpath_error::HDPathError; +use wallet_kit_common::error::hdpath_error::HDPathError; use super::hd_path_component::{HDPathComponent, HDPathValue}; @@ -79,12 +78,11 @@ impl HDPath { Ok(got) } - pub(crate) fn try_parse_base( - s: &str, + pub(crate) fn try_parse_base_hdpath( + path: &HDPath, depth_error: HDPathError, ) -> Result<(HDPath, Vec), HDPathError> { use HDPathError::*; - let path = HDPath::from_str(s).map_err(|_| HDPathError::InvalidBIP32Path(s.to_string()))?; if path.depth() < 2 { return Err(depth_error); } @@ -105,6 +103,14 @@ impl HDPath { )?; return Ok((path.clone(), components.clone())); } + + pub(crate) fn try_parse_base( + s: &str, + depth_error: HDPathError, + ) -> Result<(HDPath, Vec), HDPathError> { + let path = HDPath::from_str(s).map_err(|_| HDPathError::InvalidBIP32Path(s.to_string()))?; + return Self::try_parse_base_hdpath(&path, depth_error); + } } impl ToString for HDPath { diff --git a/hierarchical_deterministic/src/bip39/bip39_word/bip39_word.rs b/hierarchical_deterministic/src/bip39/bip39_word/bip39_word.rs index b7568efa..9c31e208 100644 --- a/hierarchical_deterministic/src/bip39/bip39_word/bip39_word.rs +++ b/hierarchical_deterministic/src/bip39/bip39_word/bip39_word.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use bip39::Language; use memoize::memoize; -use wallet_kit_common::error::Error; +use wallet_kit_common::error::hdpath_error::HDPathError as Error; use super::u11::U11; @@ -53,10 +53,9 @@ fn index_of_word_in_bip39_wordlist_of_language( #[cfg(test)] mod tests { - use bip39::Language; - use wallet_kit_common::error::Error; - use super::BIP39Word; + use bip39::Language; + use wallet_kit_common::error::hdpath_error::HDPathError as Error; #[test] fn equality() { diff --git a/hierarchical_deterministic/src/bip39/bip39_word_count.rs b/hierarchical_deterministic/src/bip39/bip39_word_count.rs index a81e90dc..900c4622 100644 --- a/hierarchical_deterministic/src/bip39/bip39_word_count.rs +++ b/hierarchical_deterministic/src/bip39/bip39_word_count.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use serde_repr::{Deserialize_repr, Serialize_repr}; use strum::FromRepr; -use wallet_kit_common::error::Error; +use wallet_kit_common::error::hdpath_error::HDPathError as Error; /// The number of words in the mnemonic of a DeviceFactorSource, according to the BIP39 /// standard, a multiple of 3, from 12 to 24 words. All "Babylon" `DeviceFactorSource`s diff --git a/hierarchical_deterministic/src/bip39/mnemonic.rs b/hierarchical_deterministic/src/bip39/mnemonic.rs index 565a0089..9ff6a49f 100644 --- a/hierarchical_deterministic/src/bip39/mnemonic.rs +++ b/hierarchical_deterministic/src/bip39/mnemonic.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use bip39::Language; use itertools::Itertools; use serde::{de, Deserializer, Serialize, Serializer}; -use wallet_kit_common::error::Error; +use wallet_kit_common::error::hdpath_error::HDPathError as Error; use super::{bip39_word::bip39_word::BIP39Word, bip39_word_count::BIP39WordCount}; @@ -67,7 +67,7 @@ impl<'de> serde::Deserialize<'de> for Mnemonic { } impl TryInto for &str { - type Error = wallet_kit_common::error::Error; + type Error = wallet_kit_common::error::hdpath_error::HDPathError; /// Tries to deserializes a bech32 address into an `AccountAddress`. fn try_into(self) -> Result { diff --git a/hierarchical_deterministic/src/bip44/bip44_like_path.rs b/hierarchical_deterministic/src/bip44/bip44_like_path.rs index 8cce8498..9bccccf1 100644 --- a/hierarchical_deterministic/src/bip44/bip44_like_path.rs +++ b/hierarchical_deterministic/src/bip44/bip44_like_path.rs @@ -1,20 +1,26 @@ use serde::{de, Deserializer, Serialize, Serializer}; +use wallet_kit_common::error::hdpath_error::HDPathError; use crate::{ bip32::{ hd_path::HDPath, hd_path_component::{HDPathComponent, HDPathValue}, }, - derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, - hdpath_error::HDPathError, + derivation::{ + derivation::Derivation, derivation_path::DerivationPath, + derivation_path_scheme::DerivationPathScheme, + }, }; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct BIP44LikePath(HDPath); -impl BIP44LikePath { - pub fn from_str(s: &str) -> Result { - let (path, components) = HDPath::try_parse_base(s, HDPathError::InvalidDepthOfBIP44Path)?; +impl TryFrom<&HDPath> for BIP44LikePath { + type Error = HDPathError; + + fn try_from(value: &HDPath) -> Result { + let (path, components) = + HDPath::try_parse_base_hdpath(value, HDPathError::InvalidDepthOfBIP44Path)?; if path.depth() != 5 { return Err(HDPathError::InvalidDepthOfBIP44Path); } @@ -33,6 +39,13 @@ impl BIP44LikePath { } return Ok(Self(path)); } +} + +impl BIP44LikePath { + pub fn from_str(s: &str) -> Result { + let (path, _) = HDPath::try_parse_base(s, HDPathError::InvalidDepthOfBIP44Path)?; + return Self::try_from(&path); + } fn with_account_and_index(account: HDPathValue, index: HDPathValue) -> Self { let c0 = HDPathComponent::bip44_purpose(); // purpose @@ -51,6 +64,9 @@ impl BIP44LikePath { } impl Derivation for BIP44LikePath { + fn derivation_path(&self) -> DerivationPath { + DerivationPath::BIP44Like(self.clone()) + } fn hd_path(&self) -> &HDPath { &self.0 } @@ -97,11 +113,12 @@ impl BIP44LikePath { #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::json::{ - assert_json_value_eq_after_roundtrip, assert_json_value_ne_after_roundtrip, + use wallet_kit_common::{ + error::hdpath_error::HDPathError, + json::{assert_json_value_eq_after_roundtrip, assert_json_value_ne_after_roundtrip}, }; - use crate::{derivation::derivation::Derivation, hdpath_error::HDPathError}; + use crate::derivation::derivation::Derivation; use super::BIP44LikePath; diff --git a/hierarchical_deterministic/src/cap26/cap26_entity_kind.rs b/hierarchical_deterministic/src/cap26/cap26_entity_kind.rs index 9215a212..3aa78af3 100644 --- a/hierarchical_deterministic/src/cap26/cap26_entity_kind.rs +++ b/hierarchical_deterministic/src/cap26/cap26_entity_kind.rs @@ -4,6 +4,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use strum::FromRepr; use crate::bip32::hd_path_component::HDPathValue; +use enum_as_inner::EnumAsInner; /// Account or Identity (used by Personas) part of a CAP26 derivation /// path. @@ -14,6 +15,7 @@ use crate::bip32::hd_path_component::HDPathValue; Clone, Copy, Debug, + EnumAsInner, PartialEq, Eq, Hash, diff --git a/hierarchical_deterministic/src/cap26/cap26_key_kind.rs b/hierarchical_deterministic/src/cap26/cap26_key_kind.rs index 85eba04b..3abb07e9 100644 --- a/hierarchical_deterministic/src/cap26/cap26_key_kind.rs +++ b/hierarchical_deterministic/src/cap26/cap26_key_kind.rs @@ -4,6 +4,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use strum::FromRepr; use crate::bip32::hd_path_component::HDPathValue; +use enum_as_inner::EnumAsInner; #[derive( Serialize_repr, @@ -13,6 +14,7 @@ use crate::bip32::hd_path_component::HDPathValue; Copy, Debug, PartialEq, + EnumAsInner, Eq, Hash, PartialOrd, diff --git a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs index df185c30..95581a0d 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs @@ -1,19 +1,32 @@ -use serde::{de, Deserializer, Serialize, Serializer}; - use crate::{ bip32::hd_path::HDPath, cap26::cap26_repr::CAP26Repr, + derivation::derivation_path::DerivationPath, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, }; +use enum_as_inner::EnumAsInner; +use serde::{de, Deserializer, Serialize, Serializer}; +use wallet_kit_common::error::hdpath_error::HDPathError; use super::paths::{account_path::AccountPath, getid_path::GetIDPath}; -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, EnumAsInner, Eq, PartialOrd, Ord)] pub enum CAP26Path { GetID(GetIDPath), AccountPath(AccountPath), } +impl TryFrom<&HDPath> for CAP26Path { + type Error = HDPathError; + + fn try_from(value: &HDPath) -> Result { + if let Ok(get_id) = GetIDPath::try_from(value) { + return Ok(get_id.into()); + } + return AccountPath::try_from(value).map(|p| p.into()); + } +} + impl Serialize for CAP26Path { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where @@ -43,6 +56,11 @@ impl Derivation for CAP26Path { CAP26Path::GetID(path) => path.hd_path(), } } + + fn derivation_path(&self) -> DerivationPath { + DerivationPath::CAP26(self.clone()) + } + fn scheme(&self) -> DerivationPathScheme { match self { CAP26Path::AccountPath(p) => p.scheme(), diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs index 7f948d11..38434e49 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs @@ -1,22 +1,47 @@ use serde::{de, Deserializer, Serialize, Serializer}; -use wallet_kit_common::network_id::NetworkID; +use wallet_kit_common::{error::hdpath_error::HDPathError, network_id::NetworkID}; use crate::{ bip32::{hd_path::HDPath, hd_path_component::HDPathValue}, cap26::{ - cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, cap26_repr::CAP26Repr, + cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, + cap26_path::cap26_path::CAP26Path, cap26_repr::CAP26Repr, + }, + derivation::{ + derivation::Derivation, derivation_path::DerivationPath, + derivation_path_scheme::DerivationPathScheme, }, - derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, - hdpath_error::HDPathError, }; +use super::is_entity_path::IsEntityPath; + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct AccountPath { pub path: HDPath, pub network_id: NetworkID, - pub entity_kind: CAP26EntityKind, - pub key_kind: CAP26KeyKind, - pub index: HDPathValue, + entity_kind: CAP26EntityKind, + key_kind: CAP26KeyKind, + index: HDPathValue, +} + +impl IsEntityPath for AccountPath { + fn network_id(&self) -> NetworkID { + self.network_id + } + fn key_kind(&self) -> CAP26KeyKind { + self.key_kind + } + fn index(&self) -> HDPathValue { + self.index + } +} + +impl TryFrom<&HDPath> for AccountPath { + type Error = HDPathError; + + fn try_from(value: &HDPath) -> Result { + Self::try_from_hdpath(value) + } } impl CAP26Repr for AccountPath { @@ -79,7 +104,9 @@ impl Derivation for AccountPath { fn hd_path(&self) -> &HDPath { &self.path } - + fn derivation_path(&self) -> DerivationPath { + DerivationPath::CAP26(CAP26Path::AccountPath(self.clone())) + } fn scheme(&self) -> DerivationPathScheme { DerivationPathScheme::Cap26 } @@ -89,6 +116,7 @@ impl Derivation for AccountPath { mod tests { use serde_json::json; use wallet_kit_common::{ + error::hdpath_error::HDPathError, json::{assert_json_value_eq_after_roundtrip, assert_json_value_ne_after_roundtrip}, network_id::NetworkID, }; @@ -98,7 +126,6 @@ mod tests { cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, cap26_repr::CAP26Repr, }, derivation::derivation::Derivation, - hdpath_error::HDPathError, }; use super::AccountPath; @@ -128,6 +155,14 @@ mod tests { assert_eq!(built, parsed) } + #[test] + fn new_tx_sign() { + assert_eq!( + AccountPath::new_mainnet_transaction_signing(77).to_string(), + "m/44H/1022H/1H/525H/1460H/77H" + ); + } + #[test] fn invalid_depth() { assert_eq!( @@ -157,8 +192,8 @@ mod tests { assert_eq!( AccountPath::from_str("m/44H/1022H/1H/618H/1460H/0H"), Err(HDPathError::WrongEntityKind( - CAP26EntityKind::Identity, - CAP26EntityKind::Account + CAP26EntityKind::Identity.discriminant(), + CAP26EntityKind::Account.discriminant() )) ) } diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs index 0d2fb208..cf7f23f2 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs @@ -1,9 +1,12 @@ use serde::{de, Deserializer, Serialize, Serializer}; +use wallet_kit_common::error::hdpath_error::HDPathError; use crate::{ bip32::{hd_path::HDPath, hd_path_component::HDPathValue}, - derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, - hdpath_error::HDPathError, + derivation::{ + derivation::Derivation, derivation_path::DerivationPath, + derivation_path_scheme::DerivationPathScheme, + }, }; /// Use it with `GetIDPath::default()` to create the path `m/44'/1022'/365'` @@ -13,6 +16,9 @@ use crate::{ pub struct GetIDPath(HDPath); impl Derivation for GetIDPath { + fn derivation_path(&self) -> DerivationPath { + DerivationPath::CAP26(self.clone().into()) + } fn hd_path(&self) -> &HDPath { &self.0 } @@ -27,12 +33,13 @@ impl Default for GetIDPath { } } -impl GetIDPath { - pub const LAST_COMPONENT_VALUE: HDPathValue = 365; +impl TryFrom<&HDPath> for GetIDPath { + type Error = HDPathError; - pub fn from_str(s: &str) -> Result { + fn try_from(value: &HDPath) -> Result { use HDPathError::*; - let (path, components) = HDPath::try_parse_base(s, HDPathError::InvalidDepthOfCAP26Path)?; + let (path, components) = + HDPath::try_parse_base_hdpath(value, HDPathError::InvalidDepthOfCAP26Path)?; if path.depth() != 3 { return Err(InvalidDepthOfCAP26Path); } @@ -46,6 +53,15 @@ impl GetIDPath { } } +impl GetIDPath { + pub const LAST_COMPONENT_VALUE: HDPathValue = 365; + + pub fn from_str(s: &str) -> Result { + let (path, _) = HDPath::try_parse_base(s, HDPathError::InvalidDepthOfCAP26Path)?; + return Self::try_from(&path); + } +} + impl Serialize for GetIDPath { /// Serializes this `AccountAddress` into its bech32 address string as JSON. fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> @@ -76,9 +92,12 @@ impl TryInto for &str { #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::json::{assert_json_value_eq_after_roundtrip, assert_json_value_fails}; + use wallet_kit_common::{ + error::hdpath_error::HDPathError, + json::{assert_json_value_eq_after_roundtrip, assert_json_value_fails}, + }; - use crate::{derivation::derivation::Derivation, hdpath_error::HDPathError}; + use crate::derivation::derivation::Derivation; use super::GetIDPath; diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/is_entity_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/is_entity_path.rs new file mode 100644 index 00000000..955674b3 --- /dev/null +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/is_entity_path.rs @@ -0,0 +1,26 @@ +use wallet_kit_common::network_id::NetworkID; + +use crate::{ + bip32::hd_path_component::HDPathValue, + cap26::{cap26_key_kind::CAP26KeyKind, cap26_repr::CAP26Repr}, +}; + +pub trait IsEntityPath: CAP26Repr { + fn network_id(&self) -> NetworkID; + fn key_kind(&self) -> CAP26KeyKind; + fn index(&self) -> HDPathValue; +} + +pub trait HasEntityPath { + fn path(&self) -> Path; + + fn network_id(&self) -> NetworkID { + self.path().network_id() + } + fn key_kind(&self) -> CAP26KeyKind { + self.path().key_kind() + } + fn index(&self) -> HDPathValue { + self.path().index() + } +} diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/mod.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/mod.rs index 3dc6db78..04ba05d6 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/mod.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/mod.rs @@ -1,2 +1,3 @@ pub mod account_path; pub mod getid_path; +pub mod is_entity_path; diff --git a/hierarchical_deterministic/src/cap26/cap26_repr.rs b/hierarchical_deterministic/src/cap26/cap26_repr.rs index 205097ad..5ac7b2c3 100644 --- a/hierarchical_deterministic/src/cap26/cap26_repr.rs +++ b/hierarchical_deterministic/src/cap26/cap26_repr.rs @@ -1,4 +1,4 @@ -use wallet_kit_common::network_id::NetworkID; +use wallet_kit_common::{error::hdpath_error::HDPathError, network_id::NetworkID}; use crate::{ bip32::{ @@ -6,7 +6,6 @@ use crate::{ hd_path_component::{HDPathComponent, HDPathValue}, }, derivation::derivation::Derivation, - hdpath_error::HDPathError, }; use super::{cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind}; @@ -24,10 +23,10 @@ pub trait CAP26Repr: Derivation { index: HDPathValue, ) -> Self; - #[cfg(not(tarpaulin_include))] // false negative, this is in fact heavily tested. - fn from_str(s: &str) -> Result { + fn try_from_hdpath(hdpath: &HDPath) -> Result { use HDPathError::*; - let (path, components) = HDPath::try_parse_base(s, HDPathError::InvalidDepthOfCAP26Path)?; + let (path, components) = + HDPath::try_parse_base_hdpath(hdpath, HDPathError::InvalidDepthOfCAP26Path)?; if !components.clone().iter().all(|c| c.is_hardened()) { return Err(NotAllComponentsAreHardened); } @@ -54,7 +53,10 @@ pub trait CAP26Repr: Derivation { if let Some(expected_entity_kind) = Self::entity_kind() { if entity_kind != expected_entity_kind { - return Err(WrongEntityKind(entity_kind, expected_entity_kind)); + return Err(WrongEntityKind( + entity_kind.discriminant(), + expected_entity_kind.discriminant(), + )); } } @@ -75,6 +77,12 @@ pub trait CAP26Repr: Derivation { )); } + #[cfg(not(tarpaulin_include))] // false negative, this is in fact heavily tested. + fn from_str(s: &str) -> Result { + let (path, _) = HDPath::try_parse_base(s, HDPathError::InvalidDepthOfCAP26Path)?; + Self::try_from_hdpath(&path) + } + fn new(network_id: NetworkID, key_kind: CAP26KeyKind, index: HDPathValue) -> Self { let entity_kind = Self::entity_kind().expect("GetID cannot be used with this constructor"); let c0 = HDPathComponent::bip44_purpose(); @@ -88,4 +96,8 @@ pub trait CAP26Repr: Derivation { let path = HDPath::from_components(components); return Self::__with_path_and_components(path, network_id, entity_kind, key_kind, index); } + + fn new_mainnet_transaction_signing(index: HDPathValue) -> Self { + Self::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, index) + } } diff --git a/hierarchical_deterministic/src/derivation/derivation.rs b/hierarchical_deterministic/src/derivation/derivation.rs index f979f38a..b0595007 100644 --- a/hierarchical_deterministic/src/derivation/derivation.rs +++ b/hierarchical_deterministic/src/derivation/derivation.rs @@ -1,8 +1,9 @@ use crate::bip32::{hd_path::HDPath, hd_path_component::HDPathComponent}; -use super::derivation_path_scheme::DerivationPathScheme; +use super::{derivation_path::DerivationPath, derivation_path_scheme::DerivationPathScheme}; pub trait Derivation: Sized { + fn derivation_path(&self) -> DerivationPath; fn hd_path(&self) -> &HDPath; fn to_string(&self) -> String { diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 49056ae6..ef965b84 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -19,6 +19,19 @@ pub enum DerivationPath { BIP44Like(BIP44LikePath), } +impl TryFrom<&HDPath> for DerivationPath { + type Error = wallet_kit_common::error::common_error::CommonError; + + fn try_from(value: &HDPath) -> Result { + if let Ok(bip44) = BIP44LikePath::try_from(value) { + return Ok(bip44.into()); + }; + return CAP26Path::try_from(value) + .map(|p| p.derivation_path()) + .map_err(|e| e.into()); + } +} + impl Debug for DerivationPath { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&self.to_string()) @@ -70,6 +83,9 @@ impl DerivationPath { } impl Derivation for DerivationPath { + fn derivation_path(&self) -> DerivationPath { + self.clone() + } fn hd_path(&self) -> &HDPath { match self { DerivationPath::CAP26(path) => path.hd_path(), @@ -108,6 +124,12 @@ impl From for DerivationPath { } } +impl From for DerivationPath { + fn from(value: CAP26Path) -> Self { + Self::CAP26(value) + } +} + #[cfg(test)] mod tests { use wallet_kit_common::json::assert_eq_after_json_roundtrip; diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_private_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_private_key.rs new file mode 100644 index 00000000..250ca29c --- /dev/null +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_private_key.rs @@ -0,0 +1,33 @@ +use wallet_kit_common::types::keys::private_key::PrivateKey; + +use super::{ + derivation_path::DerivationPath, + hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey, +}; + +pub struct HierarchicalDeterministicPrivateKey { + pub private_key: PrivateKey, + pub derivation_path: DerivationPath, +} + +impl HierarchicalDeterministicPrivateKey { + pub fn new(private_key: PrivateKey, derivation_path: DerivationPath) -> Self { + Self { + private_key, + derivation_path, + } + } +} + +impl HierarchicalDeterministicPrivateKey { + pub fn public_key(&self) -> HierarchicalDeterministicPublicKey { + HierarchicalDeterministicPublicKey::new( + self.private_key.public_key(), + self.derivation_path.clone(), + ) + } + + pub fn to_hex(&self) -> String { + self.private_key.to_hex() + } +} diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs index 40637c9e..2b109d80 100644 --- a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_public_key.rs @@ -35,6 +35,16 @@ impl HierarchicalDeterministicPublicKey { } } +impl HierarchicalDeterministicPublicKey { + pub fn to_hex(&self) -> String { + self.public_key.to_hex() + } + + pub fn to_bytes(&self) -> Vec { + self.public_key.to_bytes() + } +} + impl HierarchicalDeterministicPublicKey { /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { @@ -53,7 +63,8 @@ impl HierarchicalDeterministicPublicKey { "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b", public_key.to_hex() ); - Self::new(public_key, path.into()) + // Self::new(public_key, path.into()) + return public_key; } } @@ -63,6 +74,14 @@ mod tests { use super::HierarchicalDeterministicPublicKey; + #[test] + fn to_hex() { + assert_eq!( + HierarchicalDeterministicPublicKey::placeholder().to_hex(), + "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" + ); + } + #[test] fn json() { let model = HierarchicalDeterministicPublicKey::placeholder(); diff --git a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs index 9c08144f..7b7bc2f8 100644 --- a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs +++ b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs @@ -1,19 +1,21 @@ +use super::hierarchical_deterministic_private_key::HierarchicalDeterministicPrivateKey; use itertools::Itertools; use serde::{Deserialize, Serialize}; use wallet_kit_common::{ - error::Error, + network_id::NetworkID, types::keys::{ ed25519::private_key::Ed25519PrivateKey, private_key::PrivateKey, secp256k1::private_key::Secp256k1PrivateKey, slip10_curve::SLIP10Curve, }, }; +use super::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}; use crate::{ - bip32::hd_path::HDPath, + bip32::{hd_path::HDPath, hd_path_component::HDPathValue}, bip39::mnemonic::{Mnemonic, Seed}, + cap26::{cap26_path::paths::account_path::AccountPath, cap26_repr::CAP26Repr}, }; - -use super::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}; +use wallet_kit_common::error::hdpath_error::HDPathError as Error; /// A BIP39 Mnemonic and BIP39 passphrase - aka "25th word" tuple, /// from which we can derive a HD Root used for derivation. @@ -91,22 +93,22 @@ impl MnemonicWithPassphrase { } #[cfg(not(tarpaulin_include))] // false negative - pub fn derive_private_key(&self, derivation: D) -> PrivateKey + pub fn derive_private_key(&self, derivation: D) -> HierarchicalDeterministicPrivateKey where D: Derivation, { let seed = self.to_seed(); - let path = derivation.hd_path(); + let path = derivation.derivation_path(); match derivation.scheme() { DerivationPathScheme::Cap26 => { assert_eq!(derivation.scheme().curve(), SLIP10Curve::Curve25519); - let key = Self::derive_ed25519_private_key(&seed, path); - PrivateKey::Ed25519(key) + let key = Self::derive_ed25519_private_key(&seed, path.hd_path()); + HierarchicalDeterministicPrivateKey::new(key.into(), path) } DerivationPathScheme::Bip44Olympia => { assert_eq!(derivation.scheme().curve(), SLIP10Curve::Secp256k1); - let key = Self::derive_secp256k1_private_key(&seed, path); - PrivateKey::Secp256k1(key) + let key = Self::derive_secp256k1_private_key(&seed, path.hd_path()); + HierarchicalDeterministicPrivateKey::new(key.into(), path) } } } diff --git a/hierarchical_deterministic/src/derivation/mod.rs b/hierarchical_deterministic/src/derivation/mod.rs index 73d635a0..334e57ae 100644 --- a/hierarchical_deterministic/src/derivation/mod.rs +++ b/hierarchical_deterministic/src/derivation/mod.rs @@ -1,5 +1,6 @@ pub mod derivation; pub mod derivation_path; pub mod derivation_path_scheme; +pub mod hierarchical_deterministic_private_key; pub mod hierarchical_deterministic_public_key; pub mod mnemonic_with_passphrase; diff --git a/hierarchical_deterministic/src/lib.rs b/hierarchical_deterministic/src/lib.rs index 835e91a5..0a3a6d08 100644 --- a/hierarchical_deterministic/src/lib.rs +++ b/hierarchical_deterministic/src/lib.rs @@ -3,4 +3,3 @@ pub mod bip39; pub mod bip44; pub mod cap26; pub mod derivation; -pub mod hdpath_error; diff --git a/profile/src/v100/address/account_address.rs b/profile/src/v100/address/account_address.rs index 5ae7fd5a..88a6d22e 100644 --- a/profile/src/v100/address/account_address.rs +++ b/profile/src/v100/address/account_address.rs @@ -89,7 +89,7 @@ impl EntityAddress for AccountAddress { } impl TryInto for &str { - type Error = wallet_kit_common::error::Error; + type Error = wallet_kit_common::error::common_error::CommonError; /// Tries to deserializes a bech32 address into an `AccountAddress`. fn try_into(self) -> Result { @@ -116,12 +116,11 @@ impl AccountAddress { #[cfg(test)] mod tests { - use std::str::FromStr; - use radix_engine_common::crypto::{Ed25519PublicKey, PublicKey}; use serde_json::json; + use std::str::FromStr; + use wallet_kit_common::error::common_error::CommonError as Error; use wallet_kit_common::{ - error::Error, json::{ assert_json_roundtrip, assert_json_value_eq_after_roundtrip, assert_json_value_ne_after_roundtrip, diff --git a/profile/src/v100/address/decode_address_helper.rs b/profile/src/v100/address/decode_address_helper.rs index e492ac97..429074dc 100644 --- a/profile/src/v100/address/decode_address_helper.rs +++ b/profile/src/v100/address/decode_address_helper.rs @@ -1,8 +1,9 @@ use radix_engine_common::types::EntityType as EngineEntityType; use radix_engine_toolkit::functions::address::decode; -use wallet_kit_common::{error::Error, network_id::NetworkID}; +use wallet_kit_common::network_id::NetworkID; use crate::v100::entity::abstract_entity_type::AbstractEntityType; +use wallet_kit_common::error::common_error::CommonError as Error; type EngineDecodeAddressOutput = (u8, EngineEntityType, String, [u8; 30]); pub type DecodeAddressOutput = (NetworkID, AbstractEntityType, String, [u8; 30]); @@ -23,9 +24,9 @@ pub fn decode_address(s: &str) -> Result { #[cfg(test)] mod tests { - use wallet_kit_common::error::Error; use super::decode_address; + use wallet_kit_common::error::common_error::CommonError as Error; #[test] fn decode_unsupported_entity() { diff --git a/profile/src/v100/address/entity_address.rs b/profile/src/v100/address/entity_address.rs index 1612dcf6..3423751f 100644 --- a/profile/src/v100/address/entity_address.rs +++ b/profile/src/v100/address/entity_address.rs @@ -1,11 +1,16 @@ -use radix_engine_common::crypto::PublicKey; +use hierarchical_deterministic::cap26::cap26_path::paths::is_entity_path::IsEntityPath; +use radix_engine_common::crypto::PublicKey as EnginePublicKey; use radix_engine_toolkit::functions::derive::{ virtual_account_address_from_public_key, virtual_identity_address_from_public_key, }; use radix_engine_toolkit_json::models::scrypto::node_id::SerializableNodeIdInternal; -use wallet_kit_common::{error::Error, network_id::NetworkID}; +use wallet_kit_common::network_id::NetworkID; -use crate::v100::entity::abstract_entity_type::AbstractEntityType; +use crate::v100::{ + entity::abstract_entity_type::AbstractEntityType, + factors::hd_transaction_signing_factor_instance::HDFactorInstanceTransactionSigning, +}; +use wallet_kit_common::error::common_error::CommonError as Error; use super::decode_address_helper::decode_address; @@ -22,7 +27,10 @@ pub trait EntityAddress: Sized { /// Creates a new address from `public_key` and `network_id` by bech32 encoding /// it. #[cfg(not(tarpaulin_include))] // false negative - fn from_public_key(public_key: PublicKey, network_id: NetworkID) -> Self { + fn from_public_key

(public_key: P, network_id: NetworkID) -> Self + where + P: Into + Clone, + { let component = match Self::entity_type() { AbstractEntityType::Account => virtual_account_address_from_public_key(&public_key), AbstractEntityType::Identity => virtual_identity_address_from_public_key(&public_key), @@ -38,6 +46,17 @@ pub trait EntityAddress: Sized { return Self::__with_address_and_network_id(&address, network_id); } + fn from_hd_factor_instance_virtual_entity_creation( + hd_factor_instance_virtual_entity_creation: HDFactorInstanceTransactionSigning, + ) -> Self { + Self::from_public_key( + hd_factor_instance_virtual_entity_creation + .public_key() + .public_key, + hd_factor_instance_virtual_entity_creation.path.network_id(), + ) + } + fn try_from_bech32(s: &str) -> Result { let (network_id, entity_type, hrp, _) = decode_address(s)?; if entity_type != Self::entity_type() { diff --git a/profile/src/v100/address/identity_address.rs b/profile/src/v100/address/identity_address.rs index d371767c..f3485343 100644 --- a/profile/src/v100/address/identity_address.rs +++ b/profile/src/v100/address/identity_address.rs @@ -67,7 +67,7 @@ impl<'de> serde::Deserialize<'de> for IdentityAddress { } impl TryInto for &str { - type Error = wallet_kit_common::error::Error; + type Error = wallet_kit_common::error::common_error::CommonError; /// Tries to deserializes a bech32 address into an `IdentityAddress`. fn try_into(self) -> Result { @@ -86,16 +86,17 @@ impl Display for IdentityAddress { mod tests { use std::str::FromStr; - use radix_engine_common::crypto::{Ed25519PublicKey, PublicKey}; + use radix_engine_common::crypto::{ + Ed25519PublicKey as EngineEd25519PublicKey, PublicKey as EnginePublicKey, + }; use serde_json::json; use wallet_kit_common::json::{ assert_json_roundtrip, assert_json_value_eq_after_roundtrip, assert_json_value_ne_after_roundtrip, }; - use wallet_kit_common::error::Error; - use super::*; + use wallet_kit_common::error::common_error::CommonError as Error; #[test] fn from_bech32() { @@ -119,13 +120,16 @@ mod tests { #[test] fn from_public_key_bytes_and_network_id() { - let public_key = Ed25519PublicKey::from_str( + let public_key = EngineEd25519PublicKey::from_str( "6c28952be5cdade99c7dd5d003b6b692714b6b74c5fdb5fdc9a8e4ee1d297838", ) .unwrap(); assert_eq!( - IdentityAddress::from_public_key(PublicKey::Ed25519(public_key), NetworkID::Mainnet) - .address, + IdentityAddress::from_public_key( + EnginePublicKey::Ed25519(public_key), + NetworkID::Mainnet + ) + .address, "identity_rdx12tgzjrz9u0xz4l28vf04hz87eguclmfaq4d2p8f8lv7zg9ssnzku8j" ) } diff --git a/profile/src/v100/address/non_fungible_global_id.rs b/profile/src/v100/address/non_fungible_global_id.rs index 4c71c5d3..aabeff42 100644 --- a/profile/src/v100/address/non_fungible_global_id.rs +++ b/profile/src/v100/address/non_fungible_global_id.rs @@ -11,7 +11,9 @@ use std::{ hash::{Hash, Hasher}, str::FromStr, }; -use wallet_kit_common::{error::Error, network_id::NetworkID}; + +use wallet_kit_common::error::common_error::CommonError as Error; +use wallet_kit_common::network_id::NetworkID; use super::resource_address::ResourceAddress; @@ -52,12 +54,12 @@ impl NonFungibleGlobalId { pub fn try_from_str(s: &str) -> Result { EngineSerializableNonFungibleGlobalIdInternal::from_str(s) .map(|i| Self(EngineSerializableNonFungibleGlobalId(i))) - .map_err(|_| wallet_kit_common::error::Error::InvalidNonFungibleGlobalID) + .map_err(|_| Error::InvalidNonFungibleGlobalID) } } impl TryInto for &str { - type Error = wallet_kit_common::error::Error; + type Error = wallet_kit_common::error::common_error::CommonError; /// Tries to deserializes a bech32 address into an `AccountAddress`. fn try_into(self) -> Result { diff --git a/profile/src/v100/address/resource_address.rs b/profile/src/v100/address/resource_address.rs index 14e8e70a..6b9bdee4 100644 --- a/profile/src/v100/address/resource_address.rs +++ b/profile/src/v100/address/resource_address.rs @@ -52,7 +52,7 @@ impl EntityAddress for ResourceAddress { } impl TryInto for &str { - type Error = wallet_kit_common::error::Error; + type Error = wallet_kit_common::error::common_error::CommonError; fn try_into(self) -> Result { ResourceAddress::try_from_bech32(self) diff --git a/profile/src/v100/entity/abstract_entity_type.rs b/profile/src/v100/entity/abstract_entity_type.rs index 84267be7..b8f7feae 100644 --- a/profile/src/v100/entity/abstract_entity_type.rs +++ b/profile/src/v100/entity/abstract_entity_type.rs @@ -2,7 +2,7 @@ use radix_engine_common::types::EntityType as EngineEntityType; use serde::{Deserialize, Serialize}; use strum::FromRepr; -use wallet_kit_common::error::Error; +use wallet_kit_common::error::common_error::CommonError as Error; /// Type of a wallet Radix Entity - Account or Identity (used by Personas). /// diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index 0c8f9a07..21ba8564 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -1,16 +1,26 @@ -use hierarchical_deterministic::derivation::derivation::Derivation; +use hierarchical_deterministic::{ + bip32::hd_path_component::HDPathValue, + cap26::cap26_path::paths::is_entity_path::HasEntityPath, + derivation::{derivation::Derivation, mnemonic_with_passphrase::MnemonicWithPassphrase}, +}; use serde::{Deserialize, Serialize}; use std::{cell::RefCell, cmp::Ordering, fmt::Display}; use wallet_kit_common::network_id::NetworkID; use crate::v100::{ - address::account_address::AccountAddress, + address::{account_address::AccountAddress, entity_address::EntityAddress}, entity::{display_name::DisplayName, entity_flags::EntityFlags}, entity_security_state::{ entity_security_state::EntitySecurityState, unsecured_entity_control::UnsecuredEntityControl, }, - factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, + factors::{ + factor_sources::{ + device_factor_source::device_factor_source::DeviceFactorSource, + private_hierarchical_deterministic_factor_source::PrivateHierarchicalDeterministicFactorSource, + }, + hd_transaction_signing_factor_instance::HDFactorInstanceAccountCreation, + }, }; use super::{ @@ -79,20 +89,25 @@ pub struct Account { } impl Account { - /// Instantiates an account with a display name, address and appearance id. - pub fn with_values( - address: AccountAddress, + pub fn new( + account_creating_factor_instance: HDFactorInstanceAccountCreation, display_name: DisplayName, appearance_id: AppearanceID, ) -> Self { + let address = AccountAddress::from_hd_factor_instance_virtual_entity_creation( + account_creating_factor_instance.clone(), + ); Self { - network_id: address.network_id, + network_id: account_creating_factor_instance.network_id(), address, display_name: RefCell::new(display_name), + security_state: UnsecuredEntityControl::with_account_creating_factor_instance( + account_creating_factor_instance, + ) + .into(), appearance_id: RefCell::new(appearance_id), flags: RefCell::new(EntityFlags::default()), on_ledger_settings: RefCell::new(OnLedgerSettings::default()), - security_state: EntitySecurityState::placeholder(), } } } @@ -150,9 +165,9 @@ impl Ord for Account { match (&self.security_state, &other.security_state) { (EntitySecurityState::Unsecured(l), EntitySecurityState::Unsecured(r)) => l .transaction_signing - .derivation_path + .derivation_path() .last_component() - .cmp(r.transaction_signing.derivation_path.last_component()), + .cmp(r.transaction_signing.derivation_path().last_component()), } } } @@ -169,6 +184,25 @@ impl Display for Account { } } +impl Account { + /// Instantiates an account with a display name, address and appearance id. + pub fn placeholder_with_values( + address: AccountAddress, + display_name: DisplayName, + appearance_id: AppearanceID, + ) -> Self { + Self { + network_id: address.network_id, + address, + display_name: RefCell::new(display_name), + appearance_id: RefCell::new(appearance_id), + flags: RefCell::new(EntityFlags::default()), + on_ledger_settings: RefCell::new(OnLedgerSettings::default()), + security_state: EntitySecurityState::placeholder(), + } + } +} + // CFG test #[cfg(test)] impl Account { @@ -177,14 +211,34 @@ impl Account { Self::placeholder_mainnet() } - // /// A `Mainnet` account, a placeholder used to facilitate unit tests. - // pub fn placeholder_alice() -> Self { - // let mwp = MnemonicWithPassphrase::placeholder(); - // let bdfs = DeviceFactorSource::placeholder() - // } + fn placeholder_at_index_name(index: HDPathValue, name: &str) -> Self { + let mwp = MnemonicWithPassphrase::placeholder(); + let bdfs = DeviceFactorSource::babylon(true, mwp.clone(), "iPhone"); + let private_hd_factor_source = PrivateHierarchicalDeterministicFactorSource::new(mwp, bdfs); + let account_creating_factor_instance = private_hd_factor_source + .derive_account_creation_factor_instance(NetworkID::Mainnet, index); + + Self::new( + account_creating_factor_instance, + DisplayName::new(name).unwrap(), + AppearanceID::try_from(index as u8).unwrap(), + ) + } + + /// A `Mainnet` account named "Alice", a placeholder used to facilitate unit tests, with + /// derivation index 0, + pub fn placeholder_alice() -> Self { + Self::placeholder_at_index_name(0, "Alice") + } + + /// A `Mainnet` account named "Bob", a placeholder used to facilitate unit tests, with + /// derivation index 1. + pub fn placeholder_bob() -> Self { + Self::placeholder_at_index_name(1, "Bob") + } pub fn placeholder_mainnet() -> Self { - Self::with_values( + Self::placeholder_with_values( "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" .try_into() .unwrap(), @@ -194,7 +248,7 @@ impl Account { } pub fn placeholder_stokenet() -> Self { - Self::with_values( + Self::placeholder_with_values( "account_tdx_2_12ygsf87pma439ezvdyervjfq2nhqme6reau6kcxf6jtaysaxl7sqvd" .try_into() .unwrap(), @@ -204,7 +258,7 @@ impl Account { } pub fn placeholder_nebunet() -> Self { - Self::with_values( + Self::placeholder_with_values( "account_tdx_b_1p8ahenyznrqy2w0tyg00r82rwuxys6z8kmrhh37c7maqpydx7p" .try_into() .unwrap(), @@ -214,7 +268,7 @@ impl Account { } pub fn placeholder_kisharnet() -> Self { - Self::with_values( + Self::placeholder_with_values( "account_tdx_c_1px26p5tyqq65809em2h4yjczxcxj776kaun6sv3dw66sc3wrm6" .try_into() .unwrap(), @@ -224,7 +278,7 @@ impl Account { } pub fn placeholder_adapanet() -> Self { - Self::with_values( + Self::placeholder_with_values( "account_tdx_a_1qwv0unmwmxschqj8sntg6n9eejkrr6yr6fa4ekxazdzqhm6wy5" .try_into() .unwrap(), @@ -269,7 +323,7 @@ mod tests { "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" .try_into() .unwrap(); - let account = Account::with_values( + let account = Account::placeholder_with_values( address.clone(), DisplayName::default(), AppearanceID::default(), @@ -295,9 +349,14 @@ mod tests { ); } + #[test] + fn compare() { + assert!(Account::placeholder_alice() < Account::placeholder_bob()); + } + #[test] fn display_name_get_set() { - let account = Account::with_values( + let account = Account::placeholder_with_values( "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" .try_into() .unwrap(), @@ -312,7 +371,7 @@ mod tests { #[test] fn flags_get_set() { - let account = Account::with_values( + let account = Account::placeholder_with_values( "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" .try_into() .unwrap(), @@ -327,7 +386,7 @@ mod tests { #[test] fn on_ledger_settings_get_set() { - let account = Account::with_values( + let account = Account::placeholder_with_values( "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" .try_into() .unwrap(), @@ -387,7 +446,7 @@ mod tests { #[test] fn json_roundtrip() { - let model = Account::with_values( + let model = Account::placeholder_with_values( "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" .try_into() .unwrap(), diff --git a/profile/src/v100/entity/account/appearance_id.rs b/profile/src/v100/entity/account/appearance_id.rs index 0db767f1..a5ac4f6e 100644 --- a/profile/src/v100/entity/account/appearance_id.rs +++ b/profile/src/v100/entity/account/appearance_id.rs @@ -1,5 +1,6 @@ use nutype::nutype; -use wallet_kit_common::error::Error; + +use wallet_kit_common::error::common_error::CommonError as Error; #[nutype( validate(less_or_equal = 11), @@ -25,7 +26,7 @@ impl Default for AppearanceID { } impl TryFrom for AppearanceID { - type Error = wallet_kit_common::error::Error; + type Error = wallet_kit_common::error::common_error::CommonError; fn try_from(value: u8) -> Result { AppearanceID::new(value).map_err(|_| Error::InvalidAppearanceID) @@ -35,12 +36,10 @@ impl TryFrom for AppearanceID { #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::{ - error::Error, - json::{assert_json_value_eq_after_roundtrip, assert_json_value_fails}, - }; + use wallet_kit_common::json::{assert_json_value_eq_after_roundtrip, assert_json_value_fails}; use crate::v100::entity::account::appearance_id::{AppearanceID, AppearanceIDError}; + use wallet_kit_common::error::common_error::CommonError as Error; #[test] fn lowest() { diff --git a/profile/src/v100/entity/display_name.rs b/profile/src/v100/entity/display_name.rs index ae7da9fc..ae15ff8e 100644 --- a/profile/src/v100/entity/display_name.rs +++ b/profile/src/v100/entity/display_name.rs @@ -1,5 +1,6 @@ use nutype::nutype; -use wallet_kit_common::error::Error; + +use wallet_kit_common::error::common_error::CommonError as Error; #[nutype( sanitize(trim), @@ -26,7 +27,7 @@ impl Default for DisplayName { } impl TryFrom<&str> for DisplayName { - type Error = wallet_kit_common::error::Error; + type Error = wallet_kit_common::error::common_error::CommonError; fn try_from(value: &str) -> Result { DisplayName::new(value.to_string()).map_err(|_| Error::InvalidDisplayName) @@ -36,15 +37,13 @@ impl TryFrom<&str> for DisplayName { #[cfg(test)] mod tests { use serde_json::json; - use wallet_kit_common::{ - error::Error, - json::{ - assert_json_roundtrip, assert_json_value_eq_after_roundtrip, - assert_json_value_ne_after_roundtrip, - }, + use wallet_kit_common::json::{ + assert_json_roundtrip, assert_json_value_eq_after_roundtrip, + assert_json_value_ne_after_roundtrip, }; use super::DisplayName; + use wallet_kit_common::error::common_error::CommonError as Error; #[test] fn invalid() { diff --git a/profile/src/v100/entity_security_state/entity_security_state.rs b/profile/src/v100/entity_security_state/entity_security_state.rs index 89471e4f..d9364997 100644 --- a/profile/src/v100/entity_security_state/entity_security_state.rs +++ b/profile/src/v100/entity_security_state/entity_security_state.rs @@ -41,6 +41,12 @@ impl Serialize for EntitySecurityState { } } +impl From for EntitySecurityState { + fn from(value: UnsecuredEntityControl) -> Self { + Self::Unsecured(value) + } +} + impl EntitySecurityState { /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { diff --git a/profile/src/v100/entity_security_state/unsecured_entity_control.rs b/profile/src/v100/entity_security_state/unsecured_entity_control.rs index 5ab18548..4a2fb868 100644 --- a/profile/src/v100/entity_security_state/unsecured_entity_control.rs +++ b/profile/src/v100/entity_security_state/unsecured_entity_control.rs @@ -1,8 +1,11 @@ use hierarchical_deterministic::cap26::cap26_key_kind::CAP26KeyKind; use serde::{Deserialize, Serialize}; -use wallet_kit_common::error::Error; -use crate::v100::factors::hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance; +use crate::v100::factors::{ + hd_transaction_signing_factor_instance::HDFactorInstanceAccountCreation, + hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, +}; +use wallet_kit_common::error::common_error::CommonError as Error; /// Basic security control of an unsecured entity. When said entity /// is "securified" it will no longer be controlled by this `UnsecuredEntityControl` @@ -21,6 +24,15 @@ pub struct UnsecuredEntityControl { } impl UnsecuredEntityControl { + pub fn with_account_creating_factor_instance( + account_creating_factor_instance: HDFactorInstanceAccountCreation, + ) -> Self { + Self { + transaction_signing: account_creating_factor_instance.into(), + authentication_signing: None, + } + } + pub fn new( transaction_signing: HierarchicalDeterministicFactorInstance, authentication_signing: Option, diff --git a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs index 3182c422..3ab0061a 100644 --- a/profile/src/v100/factors/factor_instance/badge_virtual_source.rs +++ b/profile/src/v100/factors/factor_instance/badge_virtual_source.rs @@ -15,6 +15,12 @@ impl FactorInstanceBadgeVirtualSource { } } +impl From for FactorInstanceBadgeVirtualSource { + fn from(value: HierarchicalDeterministicPublicKey) -> Self { + Self::HierarchicalDeterministic(value) + } +} + impl<'de> Deserialize<'de> for FactorInstanceBadgeVirtualSource { #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { diff --git a/profile/src/v100/factors/factor_instance/factor_instance.rs b/profile/src/v100/factors/factor_instance/factor_instance.rs index 6cd78010..0009b578 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance.rs @@ -1,6 +1,12 @@ +use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::{ + self, HierarchicalDeterministicPublicKey, +}; use serde::{Deserialize, Serialize}; -use super::factor_instance_badge::FactorInstanceBadge; +use super::{ + badge_virtual_source::FactorInstanceBadgeVirtualSource, + factor_instance_badge::FactorInstanceBadge, +}; use crate::v100::factors::factor_source_id::FactorSourceID; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -26,6 +32,20 @@ impl FactorInstance { } } + pub fn with_hierarchical_deterministic_public_key( + factor_source_id: FactorSourceID, + hierarchical_deterministic_public_key: HierarchicalDeterministicPublicKey, + ) -> Self { + Self::new( + factor_source_id, + FactorInstanceBadge::Virtual( + FactorInstanceBadgeVirtualSource::HierarchicalDeterministic( + hierarchical_deterministic_public_key, + ), + ), + ) + } + /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { Self::new( diff --git a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs index 88c7f4f6..6d9f6412 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs @@ -1,3 +1,4 @@ +use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use super::badge_virtual_source::FactorInstanceBadgeVirtualSource; @@ -19,6 +20,18 @@ impl FactorInstanceBadge { } } +impl From for FactorInstanceBadge { + fn from(value: FactorInstanceBadgeVirtualSource) -> Self { + Self::Virtual(value) + } +} + +impl From for FactorInstanceBadge { + fn from(value: HierarchicalDeterministicPublicKey) -> Self { + Self::Virtual(value.into()) + } +} + impl<'de> Deserialize<'de> for FactorInstanceBadge { #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { diff --git a/profile/src/v100/factors/factor_instance/mod.rs b/profile/src/v100/factors/factor_instance/mod.rs index 786029e1..fcbe9df9 100644 --- a/profile/src/v100/factors/factor_instance/mod.rs +++ b/profile/src/v100/factors/factor_instance/mod.rs @@ -1,3 +1,4 @@ pub mod badge_virtual_source; pub mod factor_instance; pub mod factor_instance_badge; +pub mod private_hierarchical_deterministic_factor_instance; diff --git a/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs new file mode 100644 index 00000000..2c6ca699 --- /dev/null +++ b/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs @@ -0,0 +1,37 @@ +use hierarchical_deterministic::derivation::hierarchical_deterministic_private_key::HierarchicalDeterministicPrivateKey; + +use crate::v100::factors::factor_source_id::FactorSourceID; + +use super::factor_instance::FactorInstance; + +pub struct PrivateHierarchicalDeterministicFactorInstance { + pub private_key: HierarchicalDeterministicPrivateKey, + pub factor_source_id: FactorSourceID, +} + +impl From for HierarchicalDeterministicPrivateKey { + fn from(value: PrivateHierarchicalDeterministicFactorInstance) -> Self { + value.private_key + } +} + +impl From for FactorInstance { + fn from(value: PrivateHierarchicalDeterministicFactorInstance) -> Self { + FactorInstance::with_hierarchical_deterministic_public_key( + value.factor_source_id, + value.private_key.public_key(), + ) + } +} + +impl PrivateHierarchicalDeterministicFactorInstance { + pub fn new( + private_key: HierarchicalDeterministicPrivateKey, + factor_source_id: FactorSourceID, + ) -> Self { + Self { + private_key, + factor_source_id, + } + } +} diff --git a/profile/src/v100/factors/factor_source.rs b/profile/src/v100/factors/factor_source.rs index 46f6167e..f550b0d5 100644 --- a/profile/src/v100/factors/factor_source.rs +++ b/profile/src/v100/factors/factor_source.rs @@ -1,14 +1,20 @@ use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use super::factor_sources::device_factor_source::device_factor_source::DeviceFactorSource; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +use enum_as_inner::EnumAsInner; +#[derive(Serialize, Deserialize, Clone, EnumAsInner, Debug, PartialEq, Eq)] #[serde(remote = "Self")] pub enum FactorSource { #[serde(rename = "device")] Device(DeviceFactorSource), } +impl From for FactorSource { + fn from(value: DeviceFactorSource) -> Self { + FactorSource::Device(value) + } +} + impl<'de> Deserialize<'de> for FactorSource { #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { diff --git a/profile/src/v100/factors/factor_source_common.rs b/profile/src/v100/factors/factor_source_common.rs index 8b0b91d7..3d16a7ec 100644 --- a/profile/src/v100/factors/factor_source_common.rs +++ b/profile/src/v100/factors/factor_source_common.rs @@ -65,6 +65,18 @@ impl FactorSourceCommon { let date: NaiveDateTime = now(); Self::with_values(crypto_parameters, date, date, flags) } + + pub fn new_bdfs(is_main: bool) -> Self { + Self::new( + FactorSourceCryptoParameters::babylon(), + if is_main { + vec![FactorSourceFlag::Main] + } else { + Vec::new() + } + .into_iter(), + ) + } } impl Default for FactorSourceCommon { diff --git a/profile/src/v100/factors/factor_source_crypto_parameters.rs b/profile/src/v100/factors/factor_source_crypto_parameters.rs index 3b1e9754..3e19292d 100644 --- a/profile/src/v100/factors/factor_source_crypto_parameters.rs +++ b/profile/src/v100/factors/factor_source_crypto_parameters.rs @@ -1,6 +1,7 @@ use hierarchical_deterministic::derivation::derivation_path_scheme::DerivationPathScheme; use serde::{Deserialize, Serialize}; -use wallet_kit_common::{error::Error, types::keys::slip10_curve::SLIP10Curve}; +use wallet_kit_common::error::common_error::CommonError as Error; +use wallet_kit_common::types::keys::slip10_curve::SLIP10Curve; /// Cryptographic parameters a certain FactorSource supports, e.g. which Elliptic Curves /// it supports and which Hierarchical Deterministic (HD) derivations schemes it supports, @@ -73,10 +74,11 @@ impl Default for FactorSourceCryptoParameters { mod tests { use hierarchical_deterministic::derivation::derivation_path_scheme::DerivationPathScheme; use wallet_kit_common::{ - error::Error, json::assert_eq_after_json_roundtrip, types::keys::slip10_curve::SLIP10Curve, + json::assert_eq_after_json_roundtrip, types::keys::slip10_curve::SLIP10Curve, }; use super::FactorSourceCryptoParameters; + use wallet_kit_common::error::common_error::CommonError as Error; #[test] fn babylon_has_curve25519_as_first_curve() { diff --git a/profile/src/v100/factors/factor_source_id_from_hash.rs b/profile/src/v100/factors/factor_source_id_from_hash.rs index a5ac5d92..da421bda 100644 --- a/profile/src/v100/factors/factor_source_id_from_hash.rs +++ b/profile/src/v100/factors/factor_source_id_from_hash.rs @@ -36,7 +36,6 @@ impl FactorSourceIDFromHash { mnemonic_with_passphrase: MnemonicWithPassphrase, ) -> Self { let private_key = mnemonic_with_passphrase.derive_private_key(GetIDPath::default()); - // let public_key_bytes = public_key_bytes(&private_key.public_key()); let public_key_bytes = private_key.public_key().to_bytes(); let hash: Hash = blake2b_256_hash(public_key_bytes); let body = Hex32Bytes::from(hash); diff --git a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs index 9940946d..86b8b06b 100644 --- a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs +++ b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs @@ -1,7 +1,12 @@ +use hierarchical_deterministic::derivation::mnemonic_with_passphrase::{ + self, MnemonicWithPassphrase, +}; use serde::{Deserialize, Serialize}; use crate::v100::factors::{ - factor_source_common::FactorSourceCommon, factor_source_id_from_hash::FactorSourceIDFromHash, + factor_source::FactorSource, factor_source_common::FactorSourceCommon, + factor_source_id::FactorSourceID, factor_source_id_from_hash::FactorSourceIDFromHash, + factor_source_kind::FactorSourceKind, is_factor_source::IsFactorSource, }; use super::device_factor_source_hint::DeviceFactorSourceHint; @@ -26,14 +31,64 @@ pub struct DeviceFactorSource { pub hint: DeviceFactorSourceHint, } +impl TryFrom for DeviceFactorSource { + type Error = wallet_kit_common::error::common_error::CommonError; + + fn try_from(value: FactorSource) -> Result { + value + .into_device() + .map_err(|e| Self::Error::ExpectedDeviceFactorSourceGotSomethingElse) + } +} + +impl IsFactorSource for DeviceFactorSource { + fn factor_source_kind(&self) -> FactorSourceKind { + self.id.kind + } + + fn factor_source_id(&self) -> FactorSourceID { + self.clone().id.into() + } +} + +impl DeviceFactorSource { + pub fn new( + id: FactorSourceIDFromHash, + common: FactorSourceCommon, + hint: DeviceFactorSourceHint, + ) -> Self { + Self { id, common, hint } + } + + pub fn babylon( + is_main: bool, + mnemonic_with_passphrase: MnemonicWithPassphrase, + device_model: &str, + ) -> Self { + let id = FactorSourceIDFromHash::from_mnemonic_with_passphrase( + FactorSourceKind::Device, + mnemonic_with_passphrase.clone(), + ); + + Self::new( + id, + FactorSourceCommon::new_bdfs(is_main), + DeviceFactorSourceHint::unknown_model_and_name_with_word_count( + mnemonic_with_passphrase.mnemonic.word_count, + device_model, + ), + ) + } +} + impl DeviceFactorSource { /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { - Self { - id: FactorSourceIDFromHash::placeholder(), - common: FactorSourceCommon::placeholder(), - hint: DeviceFactorSourceHint::placeholder(), - } + Self::new( + FactorSourceIDFromHash::placeholder(), + FactorSourceCommon::placeholder(), + DeviceFactorSourceHint::placeholder(), + ) } } diff --git a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs index 9db87551..0790ddc7 100644 --- a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs +++ b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source_hint.rs @@ -29,25 +29,29 @@ impl DeviceFactorSourceHint { } } - pub fn iphone_unknown_model_and_name_with_word_count(word_count: BIP39WordCount) -> Self { - Self::new("Unknown Name".to_string(), "iPhone".to_string(), word_count) + pub fn unknown_model_and_name_with_word_count(word_count: BIP39WordCount, model: &str) -> Self { + Self::new("Unknown Name".to_string(), model.to_string(), word_count) } - - pub fn iphone_unknown() -> Self { - Self::iphone_unknown_model_and_name_with_word_count(BIP39WordCount::TwentyFour) + pub fn iphone_unknown_model_and_name_with_word_count(word_count: BIP39WordCount) -> Self { + Self::unknown_model_and_name_with_word_count(word_count, "iPhone") } } impl Default for DeviceFactorSourceHint { fn default() -> Self { - Self::iphone_unknown() + Self::placeholder_iphone_unknown() } } impl DeviceFactorSourceHint { /// A placeholder used to facilitate unit tests. pub fn placeholder() -> Self { - Self::iphone_unknown() + Self::placeholder_iphone_unknown() + } + + /// A placeholder used to facilitate unit tests. + pub fn placeholder_iphone_unknown() -> Self { + Self::iphone_unknown_model_and_name_with_word_count(BIP39WordCount::TwentyFour) } } @@ -61,7 +65,7 @@ mod tests { fn default_is_iphone_unknown() { assert_eq!( DeviceFactorSourceHint::default(), - DeviceFactorSourceHint::iphone_unknown() + DeviceFactorSourceHint::placeholder_iphone_unknown() ); } diff --git a/profile/src/v100/factors/factor_sources/mod.rs b/profile/src/v100/factors/factor_sources/mod.rs index 8ca920a2..b7001019 100644 --- a/profile/src/v100/factors/factor_sources/mod.rs +++ b/profile/src/v100/factors/factor_sources/mod.rs @@ -1 +1,2 @@ pub mod device_factor_source; +pub mod private_hierarchical_deterministic_factor_source; diff --git a/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs b/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs new file mode 100644 index 00000000..c0581257 --- /dev/null +++ b/profile/src/v100/factors/factor_sources/private_hierarchical_deterministic_factor_source.rs @@ -0,0 +1,59 @@ +use hierarchical_deterministic::{ + bip32::hd_path_component::HDPathValue, + cap26::{ + cap26_key_kind::CAP26KeyKind, cap26_path::paths::account_path::AccountPath, + cap26_repr::CAP26Repr, + }, + derivation::mnemonic_with_passphrase::MnemonicWithPassphrase, +}; +use wallet_kit_common::network_id::NetworkID; + +use crate::v100::factors::{ + factor_source_id_from_hash::FactorSourceIDFromHash, + hd_transaction_signing_factor_instance::HDFactorInstanceAccountCreation, + hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, + is_factor_source::IsFactorSource, +}; + +use super::device_factor_source::device_factor_source::DeviceFactorSource; + +pub struct PrivateHierarchicalDeterministicFactorSource { + pub mnemonic_with_passphrase: MnemonicWithPassphrase, + pub factor_source: DeviceFactorSource, +} + +impl PrivateHierarchicalDeterministicFactorSource { + pub fn new( + mnemonic_with_passphrase: MnemonicWithPassphrase, + factor_source: DeviceFactorSource, + ) -> Self { + assert_eq!( + factor_source.id, + FactorSourceIDFromHash::from_mnemonic_with_passphrase( + factor_source.factor_source_kind(), + mnemonic_with_passphrase.clone() + ) + .into() + ); + Self { + mnemonic_with_passphrase, + factor_source, + } + } +} + +impl PrivateHierarchicalDeterministicFactorSource { + pub fn derive_account_creation_factor_instance( + &self, + network_id: NetworkID, + index: HDPathValue, + ) -> HDFactorInstanceAccountCreation { + let path = AccountPath::new(network_id, CAP26KeyKind::TransactionSigning, index); + let hd_private_key = self.mnemonic_with_passphrase.derive_private_key(path); + let hd_factor_instance = HierarchicalDeterministicFactorInstance::new( + self.factor_source.id.clone(), + hd_private_key.public_key(), + ); + HDFactorInstanceAccountCreation::new(hd_factor_instance).unwrap() + } +} diff --git a/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs b/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs new file mode 100644 index 00000000..3e11af4b --- /dev/null +++ b/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs @@ -0,0 +1,84 @@ +use super::{ + factor_source_id_from_hash::FactorSourceIDFromHash, + hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, +}; +use hierarchical_deterministic::{ + cap26::cap26_path::{ + cap26_path::CAP26Path, + paths::{ + account_path::AccountPath, + is_entity_path::{HasEntityPath, IsEntityPath}, + }, + }, + derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey, +}; +use wallet_kit_common::{ + error::common_error::CommonError as Error, types::keys::public_key::PublicKey, +}; + +/// A specialized Hierarchical Deterministic FactorInstance used for transaction signing +/// and creation of virtual Accounts and Identities (Personas). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HDFactorInstanceTransactionSigning { + pub factor_source_id: FactorSourceIDFromHash, + public_key: PublicKey, + pub path: E, +} +impl HDFactorInstanceTransactionSigning { + pub fn try_from( + hd_factor_instance: HierarchicalDeterministicFactorInstance, + extract: F, + ) -> Result + where + F: Fn(&CAP26Path) -> Option<&E>, + { + if let Some(path) = hd_factor_instance + .derivation_path() + .as_cap26() + .and_then(|p| extract(p)) + { + if !path.key_kind().is_transaction_signing() { + return Err(Error::WrongKeyKindOfTransactionSigningFactorInstance); + } + + Ok(Self { + factor_source_id: hd_factor_instance.factor_source_id, + public_key: hd_factor_instance.public_key.public_key, + path: path.clone(), + }) + } else { + return Err(Error::WrongKeyKindOfTransactionSigningFactorInstance); + } + } +} + +impl HasEntityPath for HDFactorInstanceTransactionSigning { + fn path(&self) -> E { + self.path.clone() + } +} + +impl HDFactorInstanceTransactionSigning { + pub fn public_key(&self) -> HierarchicalDeterministicPublicKey { + HierarchicalDeterministicPublicKey::new(self.public_key, self.path.derivation_path()) + } +} + +/// Just an alias for when `HDFactorInstanceTransactionSigning` is used in the purpose of +/// creation of a virtual entity - i.e. derivation of entity address. +pub type HDFactorInstanceAccountCreation = HDFactorInstanceTransactionSigning; + +impl HDFactorInstanceAccountCreation { + pub fn new(hd_factor_instance: HierarchicalDeterministicFactorInstance) -> Result { + Self::try_from(hd_factor_instance, |p| p.as_account_path()) + } +} + +impl From for HierarchicalDeterministicFactorInstance { + fn from(value: HDFactorInstanceAccountCreation) -> Self { + HierarchicalDeterministicFactorInstance::new( + value.clone().factor_source_id, + value.public_key(), + ) + } +} diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 8b74b248..11b973c4 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -2,7 +2,10 @@ use hierarchical_deterministic::{ bip32::hd_path_component::HDPathValue, cap26::{ cap26_key_kind::CAP26KeyKind, - cap26_path::{cap26_path::CAP26Path, paths::account_path::AccountPath}, + cap26_path::{ + cap26_path::CAP26Path, + paths::{account_path::AccountPath, is_entity_path::IsEntityPath}, + }, cap26_repr::CAP26Repr, }, derivation::{ @@ -12,39 +15,52 @@ use hierarchical_deterministic::{ }, }; use serde::{de, Deserializer, Serialize, Serializer}; -use wallet_kit_common::{error::Error, network_id::NetworkID, types::keys::public_key::PublicKey}; +use wallet_kit_common::{network_id::NetworkID, types::keys::public_key::PublicKey}; use crate::v100::factors::factor_source_kind::FactorSourceKind; +use wallet_kit_common::error::common_error::CommonError as Error; use super::{ factor_instance::{ - badge_virtual_source::FactorInstanceBadgeVirtualSource, factor_instance::FactorInstance, - factor_instance_badge::FactorInstanceBadge, + factor_instance::FactorInstance, factor_instance_badge::FactorInstanceBadge, }, factor_source_id::FactorSourceID, factor_source_id_from_hash::FactorSourceIDFromHash, }; +/// A virtual hierarchical deterministic `FactorInstance` #[derive(Clone, Debug, PartialEq, Eq)] pub struct HierarchicalDeterministicFactorInstance { pub factor_source_id: FactorSourceIDFromHash, - pub public_key: PublicKey, - pub derivation_path: DerivationPath, + pub public_key: HierarchicalDeterministicPublicKey, } impl HierarchicalDeterministicFactorInstance { + pub fn derivation_path(&self) -> DerivationPath { + self.public_key.derivation_path.clone() + } + pub fn new( factor_source_id: FactorSourceIDFromHash, - public_key: PublicKey, - derivation_path: DerivationPath, + public_key: HierarchicalDeterministicPublicKey, ) -> Self { Self { factor_source_id, public_key, - derivation_path, } } + pub fn with_key_and_path( + factor_source_id: FactorSourceIDFromHash, + public_key: PublicKey, + derivation_path: DerivationPath, + ) -> Self { + Self::new( + factor_source_id, + HierarchicalDeterministicPublicKey::new(public_key, derivation_path), + ) + } + pub fn try_from( factor_source_id: FactorSourceID, public_key: PublicKey, @@ -53,7 +69,7 @@ impl HierarchicalDeterministicFactorInstance { let factor_source_id = factor_source_id .as_hash() .ok_or(Error::FactorSourceIDNotFromHash)?; - Ok(Self::new( + Ok(Self::with_key_and_path( factor_source_id.clone(), public_key, derivation_path, @@ -78,22 +94,15 @@ impl HierarchicalDeterministicFactorInstance { pub fn factor_instance(&self) -> FactorInstance { FactorInstance::new( self.factor_source_id.clone().into(), - FactorInstanceBadge::Virtual( - FactorInstanceBadgeVirtualSource::HierarchicalDeterministic( - HierarchicalDeterministicPublicKey::new( - self.public_key, - self.derivation_path.clone(), - ), - ), - ), + FactorInstanceBadge::Virtual(self.public_key.clone().into()), ) } pub fn key_kind(&self) -> Option { - match &self.derivation_path { + match &self.derivation_path() { DerivationPath::CAP26(cap26) => match cap26 { CAP26Path::GetID(_) => None, - CAP26Path::AccountPath(account_path) => Some(account_path.key_kind), + CAP26Path::AccountPath(account_path) => Some(account_path.key_kind()), }, DerivationPath::BIP44Like(_) => None, } @@ -130,7 +139,7 @@ impl HierarchicalDeterministicFactorInstance { pub fn placeholder_transaction_signing() -> Self { let placeholder = Self::placeholder_with_key_kind(CAP26KeyKind::TransactionSigning, 0); assert_eq!( - placeholder.derivation_path.to_string(), + placeholder.derivation_path().to_string(), "m/44H/1022H/1H/525H/1460H/0H" ); @@ -150,7 +159,7 @@ impl HierarchicalDeterministicFactorInstance { pub fn placeholder_auth_signing() -> Self { let placeholder = Self::placeholder_with_key_kind(CAP26KeyKind::AuthenticationSigning, 0); assert_eq!( - placeholder.derivation_path.to_string(), + placeholder.derivation_path().to_string(), "m/44H/1022H/1H/525H/1678H/0H" ); @@ -174,11 +183,7 @@ impl HierarchicalDeterministicFactorInstance { let public_key = private_key.public_key(); let id = FactorSourceIDFromHash::from_mnemonic_with_passphrase(FactorSourceKind::Device, mwp); - Self::new( - id, - public_key, - DerivationPath::CAP26(CAP26Path::AccountPath(path)), - ) + Self::new(id.into(), public_key) } } @@ -228,7 +233,7 @@ mod tests { fn placeholder_auth() { assert_eq!( HierarchicalDeterministicFactorInstance::placeholder_auth_signing() - .derivation_path + .derivation_path() .to_string(), "m/44H/1022H/1H/525H/1678H/0H" ); diff --git a/profile/src/v100/factors/is_factor_source.rs b/profile/src/v100/factors/is_factor_source.rs new file mode 100644 index 00000000..1d53194e --- /dev/null +++ b/profile/src/v100/factors/is_factor_source.rs @@ -0,0 +1,9 @@ +use super::{ + factor_source::FactorSource, factor_source_id::FactorSourceID, + factor_source_kind::FactorSourceKind, +}; + +pub trait IsFactorSource: Into + TryFrom { + fn factor_source_kind(&self) -> FactorSourceKind; + fn factor_source_id(&self) -> FactorSourceID; +} diff --git a/profile/src/v100/factors/mod.rs b/profile/src/v100/factors/mod.rs index 245d3d71..2d349458 100644 --- a/profile/src/v100/factors/mod.rs +++ b/profile/src/v100/factors/mod.rs @@ -8,4 +8,6 @@ pub mod factor_source_id_from_address; pub mod factor_source_id_from_hash; pub mod factor_source_kind; pub mod factor_sources; +pub mod hd_transaction_signing_factor_instance; pub mod hierarchical_deterministic_factor_instance; +pub mod is_factor_source; diff --git a/profile/src/v100/networks/network/accounts.rs b/profile/src/v100/networks/network/accounts.rs index 22f678d3..09540fda 100644 --- a/profile/src/v100/networks/network/accounts.rs +++ b/profile/src/v100/networks/network/accounts.rs @@ -86,7 +86,7 @@ mod tests { "account_rdx16xlfcpp0vf7e3gqnswv8j9k58n6rjccu58vvspmdva22kf3aplease" .try_into() .unwrap(); - let account = Account::with_values( + let account = Account::placeholder_with_values( address.clone(), DisplayName::default(), AppearanceID::default(), diff --git a/wallet_kit_common/src/error.rs b/wallet_kit_common/src/error.rs deleted file mode 100644 index 288df079..00000000 --- a/wallet_kit_common/src/error.rs +++ /dev/null @@ -1,91 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error, PartialEq)] -pub enum Error { - #[error("Failed to create Ed25519 Private key from bytes.")] - InvalidEd25519PrivateKeyFromBytes, - - #[error("Failed to create Ed25519 Private key from String.")] - InvalidEd25519PrivateKeyFromString, - - #[error("Failed to create Secp256k1 Private key from bytes.")] - InvalidSecp256k1PrivateKeyFromBytes, - - #[error("Failed to create Secp256k1 Private key from String.")] - InvalidSecp256k1PrivateKeyFromString, - - #[error("Failed to create Ed25519 Public key from bytes.")] - InvalidEd25519PublicKeyFromBytes, - - #[error("Failed to create Ed25519 Public key from String.")] - InvalidEd25519PublicKeyFromString, - - #[error("Failed to create Secp256k1 Public key from bytes.")] - InvalidSecp256k1PublicKeyFromBytes, - - #[error("Failed to create Secp256k1 Public key from String.")] - InvalidSecp256k1PublicKeyFromString, - - #[error("Failed to create Secp256k1 Public key, invalid point, not on curve.")] - InvalidSecp256k1PublicKeyPointNotOnCurve, - - #[error("Failed to create Ed25519 Public key, invalid point, not on curve.")] - InvalidEd25519PublicKeyPointNotOnCurve, - - #[error("Appearance id not recognized.")] - InvalidAppearanceID, - - #[error("String not not a valid display name, did not pass validation.")] - InvalidDisplayName, - - #[error("String not hex")] - StringNotHex, - - #[error("Invalid byte count, expected 32.")] - InvalidByteCountExpected32, - - #[error("Invalid Account Address '{0}'.")] - InvalidAccountAddress(String), - - #[error("Unsupported engine entity type.")] - UnsupportedEntityType, - - #[error("Failed to decode address from bech32.")] - FailedToDecodeAddressFromBech32, - - #[error("Failed to decode address mismatching entity type")] - MismatchingEntityTypeWhileDecodingAddress, - - #[error("Failed to decode address mismatching HRP")] - MismatchingHRPWhileDecodingAddress, - - #[error("Unknown network ID '{0}'")] - UnknownNetworkID(u8), - - #[error("Failed to parse InvalidNonFungibleGlobalID from str.")] - InvalidNonFungibleGlobalID, - - #[error("Supported SLIP10 curves in FactorSource crypto parameters is either empty or contains more elements than allowed.")] - FactorSourceCryptoParametersSupportedCurvesInvalidSize, - - #[error("Unknown BIP39 word.")] - UnknownBIP39Word, - - #[error("Invalid mnemonic phrase.")] - InvalidMnemonicPhrase, - - #[error("Invalid bip39 word count : '{0}'")] - InvalidBIP39WordCount(usize), - - #[error("Failed to convert FactorInstance into HierarchicalDeterministicFactorInstance, badge is not virtual HD.")] - BadgeIsNotVirtualHierarchicalDeterministic, - - #[error("Failed to create FactorSourceIDFromHash from FactorSourceID")] - FactorSourceIDNotFromHash, - - #[error("Failed to create UnsecuredEntityControl, transactionSigning factor instance path has wrong key kind")] - WrongKeyKindOfTransactionSigningFactorInstance, - - #[error("Failed to create UnsecuredEntityControl, authenticationSigning factor instance path has wrong key kind")] - WrongKeyKindOfAuthenticationSigningFactorInstance, -} diff --git a/wallet_kit_common/src/error/bytes_error.rs b/wallet_kit_common/src/error/bytes_error.rs new file mode 100644 index 00000000..be18bc78 --- /dev/null +++ b/wallet_kit_common/src/error/bytes_error.rs @@ -0,0 +1,10 @@ +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum BytesError { + #[error("String not hex")] + StringNotHex, + + #[error("Invalid byte count, expected 32.")] + InvalidByteCountExpected32, +} diff --git a/wallet_kit_common/src/error/common_error.rs b/wallet_kit_common/src/error/common_error.rs new file mode 100644 index 00000000..fd959da2 --- /dev/null +++ b/wallet_kit_common/src/error/common_error.rs @@ -0,0 +1,70 @@ +use thiserror::Error; + +use super::{bytes_error::BytesError, hdpath_error::HDPathError, key_error::KeyError}; + +#[derive(Debug, Error, PartialEq)] +pub enum CommonError { + /// + /// NESTED + /// + #[error("Hierarchical Deterministic Path error")] + HDPath(#[from] HDPathError), + + #[error("EC key error")] + Key(#[from] KeyError), + + #[error("Bytes error")] + Bytes(#[from] BytesError), + + /// + /// UN-NESTED + /// + + #[error("Appearance id not recognized.")] + InvalidAppearanceID, + + #[error("String not not a valid display name, did not pass validation.")] + InvalidDisplayName, + + #[error("Invalid Account Address '{0}'.")] + InvalidAccountAddress(String), + + #[error("Unsupported engine entity type.")] + UnsupportedEntityType, + + #[error("Failed to decode address from bech32.")] + FailedToDecodeAddressFromBech32, + + #[error("Failed to decode address mismatching entity type")] + MismatchingEntityTypeWhileDecodingAddress, + + #[error("Failed to decode address mismatching HRP")] + MismatchingHRPWhileDecodingAddress, + + #[error("Unknown network ID '{0}'")] + UnknownNetworkID(u8), + + #[error("Failed to parse InvalidNonFungibleGlobalID from str.")] + InvalidNonFungibleGlobalID, + + #[error("Supported SLIP10 curves in FactorSource crypto parameters is either empty or contains more elements than allowed.")] + FactorSourceCryptoParametersSupportedCurvesInvalidSize, + + #[error("Failed to convert FactorInstance into HierarchicalDeterministicFactorInstance, badge is not virtual HD.")] + BadgeIsNotVirtualHierarchicalDeterministic, + + #[error("Failed to create FactorSourceIDFromHash from FactorSourceID")] + FactorSourceIDNotFromHash, + + #[error("Expected AccountPath but got something else.")] + ExpectedAccountPathButGotSomethingElse, + + #[error("Wrong key kind of FactorInstance - expected transaction signing")] + WrongKeyKindOfTransactionSigningFactorInstance, + + #[error("Wrong key kind of FactorInstance - expected authentication signing")] + WrongKeyKindOfAuthenticationSigningFactorInstance, + + #[error("Expected DeviceFactorSource")] + ExpectedDeviceFactorSourceGotSomethingElse, +} diff --git a/hierarchical_deterministic/src/hdpath_error.rs b/wallet_kit_common/src/error/hdpath_error.rs similarity index 79% rename from hierarchical_deterministic/src/hdpath_error.rs rename to wallet_kit_common/src/error/hdpath_error.rs index 084b2b54..90976fae 100644 --- a/hierarchical_deterministic/src/hdpath_error.rs +++ b/wallet_kit_common/src/error/hdpath_error.rs @@ -1,7 +1,5 @@ use thiserror::Error; -use crate::{bip32::hd_path_component::HDPathValue, cap26::cap26_entity_kind::CAP26EntityKind}; - #[derive(Debug, Error, PartialEq)] pub enum HDPathError { #[error("Invalid BIP32 path '{0}'.")] @@ -28,26 +26,35 @@ pub enum HDPathError { NotAllComponentsAreHardened, #[error("Did not find 44H, found value: '{0}'")] - BIP44PurposeNotFound(HDPathValue), + BIP44PurposeNotFound(u32), #[error("Did not find cointype 1022H, found value: '{0}'")] - CoinTypeNotFound(HDPathValue), + CoinTypeNotFound(u32), #[error("Network ID exceeds limit of 255, will never be valid, at index 3, found value: '{0}', known network IDs: [1 (mainnet), 2 (stokenet)]")] - InvalidNetworkIDExceedsLimit(HDPathValue), + InvalidNetworkIDExceedsLimit(u32), #[error("InvalidEntityKind, got: '{0}', expected any of: [525H, 618H].")] - InvalidEntityKind(HDPathValue), + InvalidEntityKind(u32), #[error("Wrong entity kind, got: '{0}', but expected: '{1}'")] - WrongEntityKind(CAP26EntityKind, CAP26EntityKind), + WrongEntityKind(u32, u32), #[error("InvalidKeyKind, got: '{0}', expected any of: [1460H, 1678H, 1391H].")] - InvalidKeyKind(HDPathValue), + InvalidKeyKind(u32), #[error("Unsupported NetworkID, got: '{0}', found value: '{0}', known network IDs: [1 (mainnet), 2 (stokenet)]")] UnsupportedNetworkID(u8), #[error("Invalid GetID path, last component was not 365' but {0}'")] - InvalidGetIDPath(HDPathValue), + InvalidGetIDPath(u32), + + #[error("Unknown BIP39 word.")] + UnknownBIP39Word, + + #[error("Invalid mnemonic phrase.")] + InvalidMnemonicPhrase, + + #[error("Invalid bip39 word count : '{0}'")] + InvalidBIP39WordCount(usize), } diff --git a/wallet_kit_common/src/error/key_error.rs b/wallet_kit_common/src/error/key_error.rs new file mode 100644 index 00000000..6e9c7545 --- /dev/null +++ b/wallet_kit_common/src/error/key_error.rs @@ -0,0 +1,34 @@ +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum KeyError { + #[error("Failed to create Ed25519 Private key from bytes.")] + InvalidEd25519PrivateKeyFromBytes, + + #[error("Failed to create Ed25519 Private key from String.")] + InvalidEd25519PrivateKeyFromString, + + #[error("Failed to create Secp256k1 Private key from bytes.")] + InvalidSecp256k1PrivateKeyFromBytes, + + #[error("Failed to create Secp256k1 Private key from String.")] + InvalidSecp256k1PrivateKeyFromString, + + #[error("Failed to create Ed25519 Public key from bytes.")] + InvalidEd25519PublicKeyFromBytes, + + #[error("Failed to create Ed25519 Public key from String.")] + InvalidEd25519PublicKeyFromString, + + #[error("Failed to create Secp256k1 Public key from bytes.")] + InvalidSecp256k1PublicKeyFromBytes, + + #[error("Failed to create Secp256k1 Public key from String.")] + InvalidSecp256k1PublicKeyFromString, + + #[error("Failed to create Secp256k1 Public key, invalid point, not on curve.")] + InvalidSecp256k1PublicKeyPointNotOnCurve, + + #[error("Failed to create Ed25519 Public key, invalid point, not on curve.")] + InvalidEd25519PublicKeyPointNotOnCurve, +} diff --git a/wallet_kit_common/src/error/mod.rs b/wallet_kit_common/src/error/mod.rs new file mode 100644 index 00000000..66925710 --- /dev/null +++ b/wallet_kit_common/src/error/mod.rs @@ -0,0 +1,4 @@ +pub mod bytes_error; +pub mod common_error; +pub mod hdpath_error; +pub mod key_error; diff --git a/wallet_kit_common/src/network_id.rs b/wallet_kit_common/src/network_id.rs index 6c48e303..b631f569 100644 --- a/wallet_kit_common/src/network_id.rs +++ b/wallet_kit_common/src/network_id.rs @@ -87,7 +87,7 @@ impl NetworkID { } impl TryFrom for NetworkID { - type Error = crate::error::Error; + type Error = crate::error::common_error::CommonError; /// Tries to instantiate a NetworkID from its raw representation `u8`. fn try_from(value: u8) -> Result { diff --git a/wallet_kit_common/src/types/hex_32bytes.rs b/wallet_kit_common/src/types/hex_32bytes.rs index 6a9f50ae..253e73b6 100644 --- a/wallet_kit_common/src/types/hex_32bytes.rs +++ b/wallet_kit_common/src/types/hex_32bytes.rs @@ -6,7 +6,7 @@ use std::{ use radix_engine_common::crypto::{Hash, IsHash}; use serde::{de, Deserializer, Serialize, Serializer}; -use crate::{error::Error, secure_random_bytes::generate_32_bytes}; +use crate::{error::bytes_error::BytesError as Error, secure_random_bytes::generate_32_bytes}; /// Serializable 32 bytes which **always** serializes as a **hex** string, this is useful /// since in Radix Wallet Kit we almost always want to serialize bytes into hex and this @@ -128,7 +128,9 @@ mod tests { use serde_json::json; - use crate::{error::Error, json::assert_json_value_eq_after_roundtrip}; + use crate::{ + error::bytes_error::BytesError as Error, json::assert_json_value_eq_after_roundtrip, + }; use super::Hex32Bytes; diff --git a/wallet_kit_common/src/types/keys/ed25519/private_key.rs b/wallet_kit_common/src/types/keys/ed25519/private_key.rs index 1596cfe1..c5625215 100644 --- a/wallet_kit_common/src/types/keys/ed25519/private_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/private_key.rs @@ -4,7 +4,7 @@ use transaction::signing::ed25519::{ }; use super::public_key::Ed25519PublicKey; -use crate::{error::Error, types::hex_32bytes::Hex32Bytes}; +use crate::{error::key_error::KeyError as Error, types::hex_32bytes::Hex32Bytes}; use std::fmt::{Debug, Formatter}; /// An Ed25519 private key used to create cryptographic signatures, using @@ -83,7 +83,7 @@ impl Ed25519PrivateKey { } impl TryFrom<&[u8]> for Ed25519PrivateKey { - type Error = crate::error::Error; + type Error = crate::error::key_error::KeyError; fn try_from(slice: &[u8]) -> Result { Ed25519PrivateKey::from_bytes(slice) @@ -91,7 +91,7 @@ impl TryFrom<&[u8]> for Ed25519PrivateKey { } impl TryInto for &str { - type Error = crate::error::Error; + type Error = crate::error::key_error::KeyError; fn try_into(self) -> Result { Ed25519PrivateKey::from_str(self) @@ -131,7 +131,7 @@ mod tests { use transaction::signing::ed25519::Ed25519Signature; - use crate::{error::Error, hash::hash, types::hex_32bytes::Hex32Bytes}; + use crate::{error::key_error::KeyError as Error, hash::hash, types::hex_32bytes::Hex32Bytes}; use super::Ed25519PrivateKey; diff --git a/wallet_kit_common/src/types/keys/ed25519/public_key.rs b/wallet_kit_common/src/types/keys/ed25519/public_key.rs index 11afed48..5c63637e 100644 --- a/wallet_kit_common/src/types/keys/ed25519/public_key.rs +++ b/wallet_kit_common/src/types/keys/ed25519/public_key.rs @@ -1,5 +1,5 @@ use crate::{ - error::Error, + error::key_error::KeyError as Error, types::{hex_32bytes::Hex32Bytes, keys::ed25519::private_key::Ed25519PrivateKey}, }; use radix_engine_common::crypto::{Ed25519PublicKey as EngineEd25519PublicKey, Hash}; @@ -36,7 +36,7 @@ impl Ed25519PublicKey { } impl TryFrom<&[u8]> for Ed25519PublicKey { - type Error = crate::error::Error; + type Error = crate::error::key_error::KeyError; fn try_from(slice: &[u8]) -> Result { EngineEd25519PublicKey::try_from(slice) @@ -46,7 +46,7 @@ impl TryFrom<&[u8]> for Ed25519PublicKey { } impl TryInto for &str { - type Error = crate::error::Error; + type Error = crate::error::key_error::KeyError; fn try_into(self) -> Result { Ed25519PublicKey::from_str(self) @@ -86,7 +86,7 @@ impl Ed25519PublicKey { mod tests { use std::collections::BTreeSet; - use crate::{error::Error, json::assert_json_value_eq_after_roundtrip}; + use crate::{error::key_error::KeyError as Error, json::assert_json_value_eq_after_roundtrip}; use serde_json::json; use super::Ed25519PublicKey; diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 58c14c39..850f3fec 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -1,6 +1,11 @@ use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; -use crate::error::Error; +use crate::error::key_error::KeyError as Error; + +use radix_engine_common::crypto::{ + Ed25519PublicKey as EngineEd25519PublicKey, PublicKey as EnginePublicKey, + Secp256k1PublicKey as EngineSecp256k1PublicKey, +}; use super::{ ed25519::public_key::Ed25519PublicKey, secp256k1::public_key::Secp256k1PublicKey, @@ -163,6 +168,31 @@ impl Serialize for PublicKey { } } +impl Into for PublicKey { + fn into(self) -> EnginePublicKey { + match self { + PublicKey::Ed25519(key) => EnginePublicKey::Ed25519(key.into()), + PublicKey::Secp256k1(key) => EnginePublicKey::Secp256k1(key.into()), + } + } +} + +impl Into for Secp256k1PublicKey { + fn into(self) -> EngineSecp256k1PublicKey { + EngineSecp256k1PublicKey::try_from(self.to_bytes().as_slice()).unwrap() + } +} + +impl Into for Ed25519PublicKey { + fn into(self) -> EngineEd25519PublicKey { + EngineEd25519PublicKey::try_from(self.to_bytes().as_slice()).unwrap() + } +} + +// impl Into +// Secp256k1(Secp256k1PublicKey), +// Ed25519(Ed25519PublicKey), + #[cfg(test)] mod tests { diff --git a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs index 244fda74..85c8d9fb 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/private_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/private_key.rs @@ -1,4 +1,4 @@ -use crate::{error::Error, types::hex_32bytes::Hex32Bytes}; +use crate::{error::key_error::KeyError as Error, types::hex_32bytes::Hex32Bytes}; use radix_engine_common::crypto::IsHash; use transaction::signing::secp256k1::{ Secp256k1PrivateKey as EngineSecp256k1PrivateKey, Secp256k1Signature, @@ -77,13 +77,13 @@ impl Secp256k1PrivateKey { pub fn from_str(hex: &str) -> Result { Hex32Bytes::from_hex(hex) - .and_then(|b| Self::from_bytes(&b.to_vec())) .map_err(|_| Error::InvalidSecp256k1PrivateKeyFromString) + .and_then(|b| Self::from_bytes(&b.to_vec())) } } impl TryInto for &str { - type Error = crate::error::Error; + type Error = crate::error::key_error::KeyError; fn try_into(self) -> Result { Secp256k1PrivateKey::from_str(self) @@ -91,7 +91,7 @@ impl TryInto for &str { } impl TryFrom<&[u8]> for Secp256k1PrivateKey { - type Error = crate::error::Error; + type Error = crate::error::key_error::KeyError; fn try_from(slice: &[u8]) -> Result { Secp256k1PrivateKey::from_bytes(slice) @@ -134,7 +134,7 @@ mod tests { use transaction::signing::secp256k1::Secp256k1Signature; - use crate::{error::Error, hash::hash, types::hex_32bytes::Hex32Bytes}; + use crate::{error::key_error::KeyError as Error, hash::hash, types::hex_32bytes::Hex32Bytes}; use super::Secp256k1PrivateKey; diff --git a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs index 56e4546c..ceaa281b 100644 --- a/wallet_kit_common/src/types/keys/secp256k1/public_key.rs +++ b/wallet_kit_common/src/types/keys/secp256k1/public_key.rs @@ -1,4 +1,6 @@ -use crate::{error::Error, types::keys::secp256k1::private_key::Secp256k1PrivateKey}; +use crate::{ + error::key_error::KeyError as Error, types::keys::secp256k1::private_key::Secp256k1PrivateKey, +}; use bip32::secp256k1::PublicKey as BIP32Secp256k1PublicKey; use radix_engine_common::crypto::{Hash, Secp256k1PublicKey as EngineSecp256k1PublicKey}; use serde::{Deserialize, Serialize}; @@ -31,7 +33,7 @@ impl Secp256k1PublicKey { } impl TryFrom<&[u8]> for Secp256k1PublicKey { - type Error = crate::error::Error; + type Error = crate::error::key_error::KeyError; fn try_from(slice: &[u8]) -> Result { EngineSecp256k1PublicKey::try_from(slice) @@ -41,7 +43,7 @@ impl TryFrom<&[u8]> for Secp256k1PublicKey { } impl TryInto for &str { - type Error = crate::error::Error; + type Error = crate::error::key_error::KeyError; fn try_into(self) -> Result { Secp256k1PublicKey::from_str(self) @@ -82,7 +84,7 @@ mod tests { use std::collections::BTreeSet; use super::Secp256k1PublicKey; - use crate::{error::Error, json::assert_json_value_eq_after_roundtrip}; + use crate::{error::key_error::KeyError as Error, json::assert_json_value_eq_after_roundtrip}; use serde_json::json; #[test] From c99121c27abc8cc2d60043036471fdeffd5a3f2e Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 2 Dec 2023 21:52:17 +0100 Subject: [PATCH 27/39] account json tests --- profile/src/v100/entity/account/account.rs | 95 ++++++++++++++++------ 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/profile/src/v100/entity/account/account.rs b/profile/src/v100/entity/account/account.rs index 21ba8564..6e2a6f10 100644 --- a/profile/src/v100/entity/account/account.rs +++ b/profile/src/v100/entity/account/account.rs @@ -201,15 +201,6 @@ impl Account { security_state: EntitySecurityState::placeholder(), } } -} - -// CFG test -#[cfg(test)] -impl Account { - /// A placeholder used to facilitate unit tests. - pub fn placeholder() -> Self { - Self::placeholder_mainnet() - } fn placeholder_at_index_name(index: HDPathValue, name: &str) -> Self { let mwp = MnemonicWithPassphrase::placeholder(); @@ -236,6 +227,15 @@ impl Account { pub fn placeholder_bob() -> Self { Self::placeholder_at_index_name(1, "Bob") } +} + +// CFG test +#[cfg(test)] +impl Account { + /// A placeholder used to facilitate unit tests. + pub fn placeholder() -> Self { + Self::placeholder_mainnet() + } pub fn placeholder_mainnet() -> Self { Self::placeholder_with_values( @@ -445,18 +445,8 @@ mod tests { } #[test] - fn json_roundtrip() { - let model = Account::placeholder_with_values( - "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" - .try_into() - .unwrap(), - "Zaba 0".try_into().unwrap(), - 0.try_into().unwrap(), - ); - model - .flags - .borrow_mut() - .insert_flag(EntityFlag::DeletedByUser); + fn json_roundtrip_alice() { + let model = Account::placeholder_alice(); assert_eq_after_json_roundtrip( &model, r#" @@ -491,10 +481,10 @@ mod tests { }, "discriminator": "unsecured" }, - "networkID": 14, + "networkID": 1, "appearanceID": 0, "flags": [], - "displayName": "Zaba 0", + "displayName": "Alice", "onLedgerSettings": { "thirdPartyDeposits": { "depositRule": "acceptAll", @@ -502,8 +492,63 @@ mod tests { "depositorsAllowList": [] } }, - "flags": ["deletedByUser"], - "address": "account_tdx_e_128vkt2fur65p4hqhulfv3h0cknrppwtjsstlttkfamj4jnnpm82gsw" + "flags": [], + "address": "account_rdx12yy8n09a0w907vrjyj4hws2yptrm3rdjv84l9sr24e3w7pk7nuxst8" + } + "#, + ); + } + + #[test] + fn json_roundtrip_bob() { + let model = Account::placeholder_bob(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "securityState": { + "unsecuredEntityControl": { + "transactionSigning": { + "badge": { + "virtualSource": { + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "08740a2fd178c40ce71966a6537f780978f7f00548cfb59196344b5d7d67e9cf" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/1H" + } + }, + "discriminator": "hierarchicalDeterministicPublicKey" + }, + "discriminator": "virtualSource" + }, + "factorSourceID": { + "fromHash": { + "kind": "device", + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" + }, + "discriminator": "fromHash" + } + } + }, + "discriminator": "unsecured" + }, + "networkID": 1, + "appearanceID": 1, + "flags": [], + "displayName": "Bob", + "onLedgerSettings": { + "thirdPartyDeposits": { + "depositRule": "acceptAll", + "assetsExceptionList": [], + "depositorsAllowList": [] + } + }, + "flags": [], + "address": "account_rdx129a9wuey40lducsf6yu232zmzk5kscpvnl6fv472r0ja39f3hced69" } "#, ); From 1630c246aac8a5124895ff5223f66153a47f2b7f Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 2 Dec 2023 22:09:29 +0100 Subject: [PATCH 28/39] fix warnings --- .../src/types/keys/public_key.rs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/wallet_kit_common/src/types/keys/public_key.rs b/wallet_kit_common/src/types/keys/public_key.rs index 850f3fec..a1b49373 100644 --- a/wallet_kit_common/src/types/keys/public_key.rs +++ b/wallet_kit_common/src/types/keys/public_key.rs @@ -189,10 +189,6 @@ impl Into for Ed25519PublicKey { } } -// impl Into -// Secp256k1(Secp256k1PublicKey), -// Ed25519(Ed25519PublicKey), - #[cfg(test)] mod tests { @@ -207,6 +203,32 @@ mod tests { use super::PublicKey; + use radix_engine_common::crypto::PublicKey as EnginePublicKey; + + #[test] + fn engine_roundtrip_secp256k1() { + let public_key_secp256k1: PublicKey = Secp256k1PublicKey::placeholder().into(); + let engine_key_secp256k1: EnginePublicKey = public_key_secp256k1.clone().into(); + match engine_key_secp256k1 { + EnginePublicKey::Secp256k1(k) => { + assert_eq!(k.to_vec(), public_key_secp256k1.to_bytes()) + } + EnginePublicKey::Ed25519(_) => panic!("wrong kind"), + } + } + + #[test] + fn engine_roundtrip_ed25519() { + let public_key_ed25519: PublicKey = Ed25519PublicKey::placeholder().into(); + let engine_key_ed25519: EnginePublicKey = public_key_ed25519.clone().into(); + match engine_key_ed25519 { + EnginePublicKey::Ed25519(k) => { + assert_eq!(k.to_vec(), public_key_ed25519.to_bytes()) + } + EnginePublicKey::Secp256k1(_) => panic!("wrong kind"), + } + } + #[test] fn json_roundtrip_ed25519() { let model = PublicKey::placeholder_ed25519_alice(); From c06f22d236b54485735fb2601421ede3bd5dd989 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 2 Dec 2023 22:09:36 +0100 Subject: [PATCH 29/39] fix warnings --- .../src/derivation/mnemonic_with_passphrase.rs | 12 ++++-------- .../v100/factors/factor_instance/factor_instance.rs | 4 +--- .../device_factor_source/device_factor_source.rs | 6 ++---- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs index 7b7bc2f8..df962417 100644 --- a/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs +++ b/hierarchical_deterministic/src/derivation/mnemonic_with_passphrase.rs @@ -1,19 +1,15 @@ use super::hierarchical_deterministic_private_key::HierarchicalDeterministicPrivateKey; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use wallet_kit_common::{ - network_id::NetworkID, - types::keys::{ - ed25519::private_key::Ed25519PrivateKey, private_key::PrivateKey, - secp256k1::private_key::Secp256k1PrivateKey, slip10_curve::SLIP10Curve, - }, +use wallet_kit_common::types::keys::{ + ed25519::private_key::Ed25519PrivateKey, secp256k1::private_key::Secp256k1PrivateKey, + slip10_curve::SLIP10Curve, }; use super::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}; use crate::{ - bip32::{hd_path::HDPath, hd_path_component::HDPathValue}, + bip32::hd_path::HDPath, bip39::mnemonic::{Mnemonic, Seed}, - cap26::{cap26_path::paths::account_path::AccountPath, cap26_repr::CAP26Repr}, }; use wallet_kit_common::error::hdpath_error::HDPathError as Error; diff --git a/profile/src/v100/factors/factor_instance/factor_instance.rs b/profile/src/v100/factors/factor_instance/factor_instance.rs index 0009b578..852839c8 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance.rs @@ -1,6 +1,4 @@ -use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::{ - self, HierarchicalDeterministicPublicKey, -}; +use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; use serde::{Deserialize, Serialize}; use super::{ diff --git a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs index 86b8b06b..3b8465c8 100644 --- a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs +++ b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs @@ -1,6 +1,4 @@ -use hierarchical_deterministic::derivation::mnemonic_with_passphrase::{ - self, MnemonicWithPassphrase, -}; +use hierarchical_deterministic::derivation::mnemonic_with_passphrase::MnemonicWithPassphrase; use serde::{Deserialize, Serialize}; use crate::v100::factors::{ @@ -37,7 +35,7 @@ impl TryFrom for DeviceFactorSource { fn try_from(value: FactorSource) -> Result { value .into_device() - .map_err(|e| Self::Error::ExpectedDeviceFactorSourceGotSomethingElse) + .map_err(|_| Self::Error::ExpectedDeviceFactorSourceGotSomethingElse) } } From 3f1b860c463e5b7b30253f711fca9311cd2c8e6b Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 10:48:40 +0100 Subject: [PATCH 30/39] tests of derivation hd_factor_instance --- ...rarchical_deterministic_factor_instance.rs | 41 ++++++++++++++++++- wallet_kit_common/src/types/hex_32bytes.rs | 1 + .../src/types/keys/private_key.rs | 7 ++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 11b973c4..cb3673a7 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -189,8 +189,19 @@ impl HierarchicalDeterministicFactorInstance { #[cfg(test)] mod tests { - use hierarchical_deterministic::derivation::derivation::Derivation; - use wallet_kit_common::json::assert_eq_after_json_roundtrip; + use hierarchical_deterministic::{ + bip44::bip44_like_path::BIP44LikePath, + cap26::cap26_path::paths::getid_path::GetIDPath, + derivation::{ + derivation::Derivation, derivation_path::DerivationPath, + hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey, + }, + }; + use wallet_kit_common::{ + json::assert_eq_after_json_roundtrip, types::keys::public_key::PublicKey, + }; + + use crate::v100::factors::factor_source_id_from_hash::FactorSourceIDFromHash; use super::HierarchicalDeterministicFactorInstance; @@ -229,6 +240,32 @@ mod tests { ); } + #[test] + fn key_kind_bip44_is_none() { + let derivation_path: DerivationPath = BIP44LikePath::placeholder().into(); + let sut = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + derivation_path, + ), + ); + assert_eq!(sut.key_kind(), None); + } + + #[test] + fn key_kind_cap26_getid_is_none() { + let derivation_path: DerivationPath = GetIDPath::default().into(); + let sut = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + derivation_path, + ), + ); + assert_eq!(sut.key_kind(), None); + } + #[test] fn placeholder_auth() { assert_eq!( diff --git a/wallet_kit_common/src/types/hex_32bytes.rs b/wallet_kit_common/src/types/hex_32bytes.rs index 253e73b6..aa0160f6 100644 --- a/wallet_kit_common/src/types/hex_32bytes.rs +++ b/wallet_kit_common/src/types/hex_32bytes.rs @@ -116,6 +116,7 @@ impl Serialize for Hex32Bytes { impl<'de> serde::Deserialize<'de> for Hex32Bytes { /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; Hex32Bytes::from_hex(&s).map_err(de::Error::custom) diff --git a/wallet_kit_common/src/types/keys/private_key.rs b/wallet_kit_common/src/types/keys/private_key.rs index 227a9dac..e9178fd2 100644 --- a/wallet_kit_common/src/types/keys/private_key.rs +++ b/wallet_kit_common/src/types/keys/private_key.rs @@ -146,4 +146,11 @@ mod tests { } assert_eq!(set.len(), n); } + + #[test] + fn secp256k1_to_bytes() { + let bytes = generate_32_bytes(); + let key = Secp256k1PrivateKey::from_bytes(&bytes).unwrap(); + assert_eq!(key.to_bytes(), bytes); + } } From 6fb1b1ce384e5d3ec5e8c326d82f117c0e20613c Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 11:12:23 +0100 Subject: [PATCH 31/39] Add IdentityPath, add HDFactorInstanceIdentityCreation and add tests of HDFactorInstanceIdentityCreation and HDFactorInstanceAccountCreation --- .../src/cap26/cap26_path/cap26_path.rs | 16 +- .../cap26/cap26_path/paths/identity_path.rs | 275 ++++++++++++++++++ .../src/cap26/cap26_path/paths/mod.rs | 1 + .../src/derivation/derivation_path.rs | 8 +- .../hd_transaction_signing_factor_instance.rs | 150 +++++++++- ...rarchical_deterministic_factor_instance.rs | 1 + wallet_kit_common/src/error/common_error.rs | 3 + 7 files changed, 447 insertions(+), 7 deletions(-) create mode 100644 hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs diff --git a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs index 95581a0d..af941a49 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/cap26_path.rs @@ -8,12 +8,16 @@ use enum_as_inner::EnumAsInner; use serde::{de, Deserializer, Serialize, Serializer}; use wallet_kit_common::error::hdpath_error::HDPathError; -use super::paths::{account_path::AccountPath, getid_path::GetIDPath}; +use super::paths::{account_path::AccountPath, getid_path::GetIDPath, identity_path::IdentityPath}; +/// A derivation path design specifically for Radix Babylon wallets used by Accounts and Personas +/// to be unique per network with separate key spaces for Accounts/Identities (Personas) and key +/// kind: sign transaction or sign auth. #[derive(Clone, Debug, PartialEq, EnumAsInner, Eq, PartialOrd, Ord)] pub enum CAP26Path { GetID(GetIDPath), AccountPath(AccountPath), + IdentityPath(IdentityPath), } impl TryFrom<&HDPath> for CAP26Path { @@ -23,6 +27,9 @@ impl TryFrom<&HDPath> for CAP26Path { if let Ok(get_id) = GetIDPath::try_from(value) { return Ok(get_id.into()); } + if let Ok(identity_path) = IdentityPath::try_from(value) { + return Ok(identity_path.into()); + } return AccountPath::try_from(value).map(|p| p.into()); } } @@ -53,6 +60,7 @@ impl Derivation for CAP26Path { fn hd_path(&self) -> &HDPath { match self { CAP26Path::AccountPath(path) => path.hd_path(), + CAP26Path::IdentityPath(path) => path.hd_path(), CAP26Path::GetID(path) => path.hd_path(), } } @@ -64,6 +72,7 @@ impl Derivation for CAP26Path { fn scheme(&self) -> DerivationPathScheme { match self { CAP26Path::AccountPath(p) => p.scheme(), + CAP26Path::IdentityPath(p) => p.scheme(), CAP26Path::GetID(p) => p.scheme(), } } @@ -74,6 +83,11 @@ impl From for CAP26Path { Self::AccountPath(value) } } +impl From for CAP26Path { + fn from(value: IdentityPath) -> Self { + Self::IdentityPath(value) + } +} impl From for CAP26Path { fn from(value: GetIDPath) -> Self { Self::GetID(value) diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs new file mode 100644 index 00000000..f3a1facc --- /dev/null +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs @@ -0,0 +1,275 @@ +use serde::{de, Deserializer, Serialize, Serializer}; +use wallet_kit_common::{error::hdpath_error::HDPathError, network_id::NetworkID}; + +use crate::{ + bip32::{hd_path::HDPath, hd_path_component::HDPathValue}, + cap26::{ + cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, + cap26_path::cap26_path::CAP26Path, cap26_repr::CAP26Repr, + }, + derivation::{ + derivation::Derivation, derivation_path::DerivationPath, + derivation_path_scheme::DerivationPathScheme, + }, +}; + +use super::is_entity_path::IsEntityPath; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct IdentityPath { + pub path: HDPath, + pub network_id: NetworkID, + entity_kind: CAP26EntityKind, + key_kind: CAP26KeyKind, + index: HDPathValue, +} + +impl IsEntityPath for IdentityPath { + fn network_id(&self) -> NetworkID { + self.network_id + } + fn key_kind(&self) -> CAP26KeyKind { + self.key_kind + } + fn index(&self) -> HDPathValue { + self.index + } +} + +impl TryFrom<&HDPath> for IdentityPath { + type Error = HDPathError; + + fn try_from(value: &HDPath) -> Result { + Self::try_from_hdpath(value) + } +} + +impl CAP26Repr for IdentityPath { + fn entity_kind() -> Option { + Some(CAP26EntityKind::Identity) + } + + fn __with_path_and_components( + path: HDPath, + network_id: NetworkID, + entity_kind: CAP26EntityKind, + key_kind: CAP26KeyKind, + index: HDPathValue, + ) -> Self { + Self { + path, + network_id, + entity_kind, + key_kind, + index, + } + } +} + +impl IdentityPath { + /// A placeholder used to facilitate unit tests. + pub fn placeholder() -> Self { + Self::from_str("m/44H/1022H/1H/618H/1460H/0H").unwrap() + } +} + +impl Serialize for IdentityPath { + /// Serializes this `IdentityPath` into its bech32 address string as JSON. + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> serde::Deserialize<'de> for IdentityPath { + /// Tries to deserializes a JSON string as a bech32 address into an `IdentityPath`. + #[cfg(not(tarpaulin_include))] // false negative + fn deserialize>(d: D) -> Result { + let s = String::deserialize(d)?; + IdentityPath::from_str(&s).map_err(de::Error::custom) + } +} + +impl TryInto for &str { + type Error = HDPathError; + + fn try_into(self) -> Result { + IdentityPath::from_str(self) + } +} + +impl Derivation for IdentityPath { + fn hd_path(&self) -> &HDPath { + &self.path + } + fn derivation_path(&self) -> DerivationPath { + DerivationPath::CAP26(CAP26Path::IdentityPath(self.clone())) + } + fn scheme(&self) -> DerivationPathScheme { + DerivationPathScheme::Cap26 + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use wallet_kit_common::{ + error::hdpath_error::HDPathError, + json::{assert_json_value_eq_after_roundtrip, assert_json_value_ne_after_roundtrip}, + network_id::NetworkID, + }; + + use crate::{ + cap26::{ + cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, cap26_repr::CAP26Repr, + }, + derivation::derivation::Derivation, + }; + + use super::IdentityPath; + + #[test] + fn entity_kind() { + assert_eq!(IdentityPath::entity_kind(), Some(CAP26EntityKind::Identity)); + } + + #[test] + fn hd_path() { + let str = "m/44H/1022H/1H/618H/1460H/0H"; + let parsed: IdentityPath = str.try_into().unwrap(); + assert_eq!(parsed.hd_path().depth(), 6); + } + + #[test] + fn string_roundtrip() { + let str = "m/44H/1022H/1H/618H/1460H/0H"; + let parsed: IdentityPath = str.try_into().unwrap(); + assert_eq!(parsed.network_id, NetworkID::Mainnet); + assert_eq!(parsed.entity_kind, CAP26EntityKind::Identity); + assert_eq!(parsed.key_kind, CAP26KeyKind::TransactionSigning); + assert_eq!(parsed.index, 0); + assert_eq!(parsed.to_string(), str); + let built = IdentityPath::new(NetworkID::Mainnet, CAP26KeyKind::TransactionSigning, 0); + assert_eq!(built, parsed) + } + + #[test] + fn new_tx_sign() { + assert_eq!( + IdentityPath::new_mainnet_transaction_signing(77).to_string(), + "m/44H/1022H/1H/618H/1460H/77H" + ); + } + + #[test] + fn invalid_depth() { + assert_eq!( + IdentityPath::from_str("m/44H/1022H"), + Err(HDPathError::InvalidDepthOfCAP26Path) + ) + } + + #[test] + fn not_all_hardened() { + assert_eq!( + IdentityPath::from_str("m/44H/1022H/1H/618H/1460H/0"), // last not hardened + Err(HDPathError::NotAllComponentsAreHardened) + ) + } + + #[test] + fn cointype_not_found() { + assert_eq!( + IdentityPath::from_str("m/44H/33H/1H/618H/1460H/0"), // `33` instead of 1022 + Err(HDPathError::CoinTypeNotFound(33)) + ) + } + + #[test] + fn fails_when_entity_type_identity() { + assert_eq!( + IdentityPath::from_str("m/44H/1022H/1H/525H/1460H/0H"), + Err(HDPathError::WrongEntityKind( + CAP26EntityKind::Account.discriminant(), + CAP26EntityKind::Identity.discriminant() + )) + ) + } + + #[test] + fn fails_when_entity_type_does_not_exist() { + assert_eq!( + IdentityPath::from_str("m/44H/1022H/1H/99999H/1460H/0H"), + Err(HDPathError::InvalidEntityKind(99999)) + ) + } + + #[test] + fn fails_when_key_kind_does_not_exist() { + assert_eq!( + IdentityPath::from_str("m/44H/1022H/1H/618H/22222H/0H"), + Err(HDPathError::InvalidKeyKind(22222)) + ) + } + + #[test] + fn fails_when_network_id_is_out_of_bounds() { + assert_eq!( + IdentityPath::from_str("m/44H/1022H/4444H/618H/1460H/0H"), + Err(HDPathError::InvalidNetworkIDExceedsLimit(4444)) + ) + } + + #[test] + fn fails_when_not_bip44() { + assert_eq!( + IdentityPath::from_str("m/777H/1022H/1H/618H/1460H/0H"), + Err(HDPathError::BIP44PurposeNotFound(777)) + ) + } + + #[test] + fn missing_leading_m_is_ok() { + assert!(IdentityPath::from_str("44H/1022H/1H/618H/1460H/0H").is_ok()) + } + + #[test] + fn fails_when_index_is_too_large() { + assert_eq!( + IdentityPath::from_str("m/44H/1022H/1H/618H/1460H/4294967296H"), + Err(HDPathError::InvalidBIP32Path( + "m/44H/1022H/1H/618H/1460H/4294967296H".to_string() + )) + ) + } + + #[test] + fn inequality_different_index() { + let a: IdentityPath = "m/44H/1022H/1H/618H/1460H/0H".try_into().unwrap(); + let b: IdentityPath = "m/44H/1022H/1H/618H/1460H/1H".try_into().unwrap(); + assert!(a != b); + } + #[test] + fn inequality_different_network_id() { + let a: IdentityPath = "m/44H/1022H/1H/618H/1460H/0H".try_into().unwrap(); + let b: IdentityPath = "m/44H/1022H/2H/618H/1460H/0H".try_into().unwrap(); + assert!(a != b); + } + + #[test] + fn inequality_different_key_kind() { + let a: IdentityPath = "m/44H/1022H/1H/618H/1460H/0H".try_into().unwrap(); + let b: IdentityPath = "m/44H/1022H/1H/618H/1678H/0H".try_into().unwrap(); + assert!(a != b); + } + + #[test] + fn json_roundtrip() { + let str = "m/44H/1022H/1H/618H/1460H/0H"; + let parsed: IdentityPath = str.try_into().unwrap(); + assert_json_value_eq_after_roundtrip(&parsed, json!(str)); + assert_json_value_ne_after_roundtrip(&parsed, json!("m/44H/1022H/1H/618H/1460H/1H")); + } +} diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/mod.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/mod.rs index 04ba05d6..d95d20cc 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/mod.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/mod.rs @@ -1,3 +1,4 @@ pub mod account_path; pub mod getid_path; +pub mod identity_path; pub mod is_entity_path; diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index ef965b84..8f5743df 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -6,7 +6,7 @@ use crate::{ bip44::bip44_like_path::BIP44LikePath, cap26::cap26_path::{ cap26_path::CAP26Path, - paths::{account_path::AccountPath, getid_path::GetIDPath}, + paths::{account_path::AccountPath, getid_path::GetIDPath, identity_path::IdentityPath}, }, }; use enum_as_inner::EnumAsInner; @@ -112,6 +112,12 @@ impl From for DerivationPath { } } +impl From for DerivationPath { + fn from(value: IdentityPath) -> Self { + Self::CAP26(value.into()) + } +} + impl From for DerivationPath { fn from(value: GetIDPath) -> Self { Self::CAP26(value.into()) diff --git a/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs b/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs index 3e11af4b..c0492fe6 100644 --- a/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs +++ b/profile/src/v100/factors/hd_transaction_signing_factor_instance.rs @@ -7,6 +7,7 @@ use hierarchical_deterministic::{ cap26_path::CAP26Path, paths::{ account_path::AccountPath, + identity_path::IdentityPath, is_entity_path::{HasEntityPath, IsEntityPath}, }, }, @@ -47,7 +48,7 @@ impl HDFactorInstanceTransactionSigning { path: path.clone(), }) } else { - return Err(Error::WrongKeyKindOfTransactionSigningFactorInstance); + return Err(Error::WrongEntityKindOfInFactorInstancesPath); } } } @@ -64,21 +65,160 @@ impl HDFactorInstanceTransactionSigning { } } -/// Just an alias for when `HDFactorInstanceTransactionSigning` is used in the purpose of -/// creation of a virtual entity - i.e. derivation of entity address. +/// Just an alias for when `HDFactorInstanceTransactionSigning` is used to create a new Account. pub type HDFactorInstanceAccountCreation = HDFactorInstanceTransactionSigning; +/// Just an alias for when `HDFactorInstanceTransactionSigning` is used to create a new Account. +pub type HDFactorInstanceIdentityCreation = HDFactorInstanceTransactionSigning; + impl HDFactorInstanceAccountCreation { pub fn new(hd_factor_instance: HierarchicalDeterministicFactorInstance) -> Result { Self::try_from(hd_factor_instance, |p| p.as_account_path()) } } -impl From for HierarchicalDeterministicFactorInstance { - fn from(value: HDFactorInstanceAccountCreation) -> Self { +impl From> + for HierarchicalDeterministicFactorInstance +{ + fn from(value: HDFactorInstanceTransactionSigning) -> Self { HierarchicalDeterministicFactorInstance::new( value.clone().factor_source_id, value.public_key(), ) } } + +impl HDFactorInstanceIdentityCreation { + pub fn new(hd_factor_instance: HierarchicalDeterministicFactorInstance) -> Result { + Self::try_from(hd_factor_instance, |p| p.as_identity_path()) + } +} + +#[cfg(test)] +mod tests { + use hierarchical_deterministic::{ + cap26::{ + cap26_key_kind::CAP26KeyKind, + cap26_path::paths::{ + account_path::AccountPath, identity_path::IdentityPath, + is_entity_path::IsEntityPath, + }, + cap26_repr::CAP26Repr, + }, + derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey, + }; + use wallet_kit_common::{ + error::common_error::CommonError as Error, network_id::NetworkID, + types::keys::public_key::PublicKey, + }; + + use crate::v100::factors::{ + factor_source_id_from_hash::FactorSourceIDFromHash, + hd_transaction_signing_factor_instance::{ + HDFactorInstanceAccountCreation, HDFactorInstanceIdentityCreation, + }, + hierarchical_deterministic_factor_instance::HierarchicalDeterministicFactorInstance, + }; + + #[test] + fn account_creation_valid() { + let hd_key = HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + AccountPath::placeholder().into(), + ); + let hd_fi = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + hd_key, + ); + assert_eq!( + HDFactorInstanceAccountCreation::new(hd_fi) + .unwrap() + .path + .key_kind(), + CAP26KeyKind::TransactionSigning + ); + } + + #[test] + fn account_creation_wrong_entity_kind() { + let hd_key = HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + IdentityPath::placeholder().into(), + ); + let hd_fi = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + hd_key, + ); + assert_eq!( + HDFactorInstanceAccountCreation::new(hd_fi), + Err(Error::WrongEntityKindOfInFactorInstancesPath) + ); + } + + #[test] + fn account_creation_wrong_key_kind() { + let hd_key = HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + AccountPath::new(NetworkID::Mainnet, CAP26KeyKind::AuthenticationSigning, 0).into(), + ); + let hd_fi = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + hd_key, + ); + assert_eq!( + HDFactorInstanceAccountCreation::new(hd_fi), + Err(Error::WrongKeyKindOfTransactionSigningFactorInstance) + ); + } + + #[test] + fn identity_creation_valid() { + let hd_key = HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + IdentityPath::placeholder().into(), + ); + let hd_fi = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + hd_key, + ); + assert_eq!( + HDFactorInstanceIdentityCreation::new(hd_fi) + .unwrap() + .path + .key_kind(), + CAP26KeyKind::TransactionSigning + ); + } + + #[test] + fn identity_creation_wrong_entity_kind() { + let hd_key = HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + AccountPath::placeholder().into(), + ); + let hd_fi = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + hd_key, + ); + assert_eq!( + HDFactorInstanceIdentityCreation::new(hd_fi), + Err(Error::WrongEntityKindOfInFactorInstancesPath) + ); + } + + #[test] + fn identity_creation_wrong_key_kind() { + let hd_key = HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + IdentityPath::new(NetworkID::Mainnet, CAP26KeyKind::AuthenticationSigning, 0).into(), + ); + let hd_fi = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + hd_key, + ); + assert_eq!( + HDFactorInstanceIdentityCreation::new(hd_fi), + Err(Error::WrongKeyKindOfTransactionSigningFactorInstance) + ); + } +} diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index cb3673a7..97743a4c 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -102,6 +102,7 @@ impl HierarchicalDeterministicFactorInstance { match &self.derivation_path() { DerivationPath::CAP26(cap26) => match cap26 { CAP26Path::GetID(_) => None, + CAP26Path::IdentityPath(identity_path) => Some(identity_path.key_kind()), CAP26Path::AccountPath(account_path) => Some(account_path.key_kind()), }, DerivationPath::BIP44Like(_) => None, diff --git a/wallet_kit_common/src/error/common_error.rs b/wallet_kit_common/src/error/common_error.rs index fd959da2..653ff9ab 100644 --- a/wallet_kit_common/src/error/common_error.rs +++ b/wallet_kit_common/src/error/common_error.rs @@ -59,6 +59,9 @@ pub enum CommonError { #[error("Expected AccountPath but got something else.")] ExpectedAccountPathButGotSomethingElse, + #[error("Wrong entity kind in path of FactorInstance")] + WrongEntityKindOfInFactorInstancesPath, + #[error("Wrong key kind of FactorInstance - expected transaction signing")] WrongKeyKindOfTransactionSigningFactorInstance, From b36ef2133836c7d0457ce62e4edbaa3d2797ad05 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 19:00:42 +0100 Subject: [PATCH 32/39] Add LedgerHardwareWalletFactorSource --- profile/src/v100/factors/factor_source.rs | 17 +++- .../factors/factor_source_id_from_hash.rs | 16 +++- .../device_factor_source.rs | 25 +++++- .../ledger_hardware_wallet_factor_source.rs | 89 +++++++++++++++++++ .../ledger_hardware_wallet_hint.rs | 47 ++++++++++ .../ledger_hardware_wallet_model.rs | 72 +++++++++++++++ .../mod.rs | 3 + .../src/v100/factors/factor_sources/mod.rs | 1 + 8 files changed, 265 insertions(+), 5 deletions(-) create mode 100644 profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs create mode 100644 profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_hint.rs create mode 100644 profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model.rs create mode 100644 profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/mod.rs diff --git a/profile/src/v100/factors/factor_source.rs b/profile/src/v100/factors/factor_source.rs index f550b0d5..5b1dad47 100644 --- a/profile/src/v100/factors/factor_source.rs +++ b/profile/src/v100/factors/factor_source.rs @@ -1,12 +1,19 @@ use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; -use super::factor_sources::device_factor_source::device_factor_source::DeviceFactorSource; use enum_as_inner::EnumAsInner; + +use super::factor_sources::{ + device_factor_source::device_factor_source::DeviceFactorSource, + ledger_hardware_wallet_factor_source::ledger_hardware_wallet_factor_source::LedgerHardwareWalletFactorSource, +}; #[derive(Serialize, Deserialize, Clone, EnumAsInner, Debug, PartialEq, Eq)] #[serde(remote = "Self")] pub enum FactorSource { #[serde(rename = "device")] Device(DeviceFactorSource), + + #[serde(rename = "ledgerHQHardwareWallet")] + Ledger(LedgerHardwareWalletFactorSource), } impl From for FactorSource { @@ -37,12 +44,18 @@ impl Serialize for FactorSource { S: Serializer, { let mut state = serializer.serialize_struct("FactorSource", 2)?; + let discriminator_key = "discriminator"; match self { FactorSource::Device(device) => { let discriminant = "device"; - state.serialize_field("discriminator", discriminant)?; + state.serialize_field(discriminator_key, discriminant)?; state.serialize_field(discriminant, device)?; } + FactorSource::Ledger(ledger) => { + let discriminant = "ledgerHQHardwareWallet"; + state.serialize_field(discriminator_key, discriminant)?; + state.serialize_field(discriminant, ledger)?; + } } state.end() } diff --git a/profile/src/v100/factors/factor_source_id_from_hash.rs b/profile/src/v100/factors/factor_source_id_from_hash.rs index da421bda..d679f7b7 100644 --- a/profile/src/v100/factors/factor_source_id_from_hash.rs +++ b/profile/src/v100/factors/factor_source_id_from_hash.rs @@ -48,10 +48,24 @@ impl FactorSourceIDFromHash { } impl FactorSourceIDFromHash { - /// A placeholder used to facilitate unit tests. + /// A placeholder used to facilitate unit tests, just an alias + /// for `placeholder_device` pub fn placeholder() -> Self { + Self::placeholder_device() + } + + /// A placeholder used to facilitate unit tests. + pub fn placeholder_device() -> Self { Self::new_for_device(MnemonicWithPassphrase::placeholder()) } + + /// A placeholder used to facilitate unit tests. + pub fn placeholder_ledger() -> Self { + Self::from_mnemonic_with_passphrase( + FactorSourceKind::LedgerHQHardwareWallet, + MnemonicWithPassphrase::placeholder(), + ) + } } #[cfg(test)] diff --git a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs index 3b8465c8..0a6284ba 100644 --- a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs +++ b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs @@ -1,3 +1,5 @@ +use std::cell::RefCell; + use hierarchical_deterministic::derivation::mnemonic_with_passphrase::MnemonicWithPassphrase; use serde::{Deserialize, Serialize}; @@ -23,7 +25,10 @@ pub struct DeviceFactorSource { /// Common properties shared between FactorSources of different kinds, /// describing its state, when added, and supported cryptographic parameters. - pub common: FactorSourceCommon, + /// + /// Has interior mutability since we must be able to update the + /// last used date. + pub common: RefCell, /// Properties describing a DeviceFactorSource to help user disambiguate between it and another one. pub hint: DeviceFactorSourceHint, @@ -50,12 +55,17 @@ impl IsFactorSource for DeviceFactorSource { } impl DeviceFactorSource { + /// Instantiates a new `DeviceFactorSource` pub fn new( id: FactorSourceIDFromHash, common: FactorSourceCommon, hint: DeviceFactorSourceHint, ) -> Self { - Self { id, common, hint } + Self { + id, + common: RefCell::new(common), + hint, + } } pub fn babylon( @@ -94,6 +104,10 @@ impl DeviceFactorSource { mod tests { use wallet_kit_common::json::assert_eq_after_json_roundtrip; + use crate::v100::factors::{ + factor_source_id::FactorSourceID, is_factor_source::IsFactorSource, + }; + use super::DeviceFactorSource; #[test] @@ -125,4 +139,11 @@ mod tests { "#, ); } + + #[test] + fn factor_source_id() { + let sut = DeviceFactorSource::placeholder(); + let factor_source_id: FactorSourceID = sut.clone().id.into(); + assert_eq!(factor_source_id, sut.factor_source_id()); + } } diff --git a/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs new file mode 100644 index 00000000..513c3879 --- /dev/null +++ b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs @@ -0,0 +1,89 @@ +use std::cell::RefCell; + +use serde::{Deserialize, Serialize}; + +use crate::v100::factors::{ + factor_source_common::FactorSourceCommon, factor_source_id_from_hash::FactorSourceIDFromHash, +}; + +use super::ledger_hardware_wallet_hint::LedgerHardwareWalletHint; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct LedgerHardwareWalletFactorSource { + /// Unique and stable identifier of this factor source, stemming from the + /// hash of a special child key of the HD root of the mnemonic, + /// that is secured by the Ledger Hardware Wallet device. + pub id: FactorSourceIDFromHash, + + /// Common properties shared between FactorSources of different kinds, + /// describing its state, when added, and supported cryptographic parameters. + /// + /// Has interior mutability since we must be able to update the + /// last used date. + pub common: RefCell, + + /// Properties describing a LedgerHardwareWalletFactorSource to help user disambiguate between it and another one. + pub hint: LedgerHardwareWalletHint, +} + +impl LedgerHardwareWalletFactorSource { + /// Instantiates a new `LedgerHardwareWalletFactorSource` + pub fn new( + id: FactorSourceIDFromHash, + common: FactorSourceCommon, + hint: LedgerHardwareWalletHint, + ) -> Self { + Self { + id, + common: RefCell::new(common), + hint, + } + } +} + +impl LedgerHardwareWalletFactorSource { + pub fn placeholder() -> Self { + Self::new( + FactorSourceIDFromHash::placeholder_ledger(), + FactorSourceCommon::placeholder(), + LedgerHardwareWalletHint::placeholder(), + ) + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::LedgerHardwareWalletFactorSource; + + #[test] + fn json_roundtrip() { + let model: LedgerHardwareWalletFactorSource = + LedgerHardwareWalletFactorSource::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "id": { + "kind": "ledgerHQHardwareWallet", + "body": "3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" + }, + "common": { + "addedOn": "2023-09-11T16:05:56", + "cryptoParameters": { + "supportedCurves": ["curve25519"], + "supportedDerivationPathSchemes": ["cap26"] + }, + "flags": ["main"], + "lastUsedOn": "2023-09-11T16:05:56" + }, + "hint": { + "name": "Orange, scratched", + "model": "nanoS+" + } + }"#, + ); + } +} diff --git a/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_hint.rs b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_hint.rs new file mode 100644 index 00000000..b3f2cf05 --- /dev/null +++ b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_hint.rs @@ -0,0 +1,47 @@ +use serde::{Deserialize, Serialize}; + +use super::ledger_hardware_wallet_model::LedgerHardwareWalletModel; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct LedgerHardwareWalletHint { + /// "Orange, scratched" + pub name: String, + + /// E.g. `nanoS+` + pub model: LedgerHardwareWalletModel, +} + +impl LedgerHardwareWalletHint { + pub fn new(name: &str, model: LedgerHardwareWalletModel) -> Self { + Self { + name: name.to_string(), + model, + } + } +} + +impl LedgerHardwareWalletHint { + pub fn placeholder() -> Self { + Self::new("Orange, scratched", LedgerHardwareWalletModel::NanoSPlus) + } +} + +#[cfg(test)] +mod tests { + use wallet_kit_common::json::assert_eq_after_json_roundtrip; + + use super::LedgerHardwareWalletHint; + #[test] + fn json_roundtrip() { + let model = LedgerHardwareWalletHint::placeholder(); + assert_eq_after_json_roundtrip( + &model, + r#" + { + "name": "Orange, scratched", + "model": "nanoS+" + } + "#, + ); + } +} diff --git a/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model.rs b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model.rs new file mode 100644 index 00000000..02661344 --- /dev/null +++ b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_model.rs @@ -0,0 +1,72 @@ +use serde::{Deserialize, Serialize}; + +/// The model of a Ledger HQ hardware wallet NanoS, e.g. +/// *Ledger Nano S+*. +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub enum LedgerHardwareWalletModel { + NanoS, + + #[serde(rename = "nanoS+")] + NanoSPlus, + NanoX, +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use serde_json::json; + use wallet_kit_common::json::assert_json_value_eq_after_roundtrip; + + use crate::v100::factors::factor_sources::ledger_hardware_wallet_factor_source::ledger_hardware_wallet_model::LedgerHardwareWalletModel; + + #[test] + fn equality() { + assert_eq!( + LedgerHardwareWalletModel::NanoS, + LedgerHardwareWalletModel::NanoS + ); + assert_eq!( + LedgerHardwareWalletModel::NanoX, + LedgerHardwareWalletModel::NanoX + ); + } + #[test] + fn inequality() { + assert_ne!( + LedgerHardwareWalletModel::NanoS, + LedgerHardwareWalletModel::NanoX + ); + } + + #[test] + fn hash() { + assert_eq!( + BTreeSet::from_iter( + [ + LedgerHardwareWalletModel::NanoS, + LedgerHardwareWalletModel::NanoS + ] + .into_iter() + ) + .len(), + 1 + ); + } + + #[test] + fn ord() { + assert!(LedgerHardwareWalletModel::NanoS < LedgerHardwareWalletModel::NanoX); + } + + #[test] + fn json_roundtrip() { + assert_json_value_eq_after_roundtrip(&LedgerHardwareWalletModel::NanoS, json!("nanoS")); + assert_json_value_eq_after_roundtrip( + &LedgerHardwareWalletModel::NanoSPlus, + json!("nanoS+"), + ); + assert_json_value_eq_after_roundtrip(&LedgerHardwareWalletModel::NanoX, json!("nanoX")); + } +} diff --git a/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/mod.rs b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/mod.rs new file mode 100644 index 00000000..3c155b24 --- /dev/null +++ b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/mod.rs @@ -0,0 +1,3 @@ +pub mod ledger_hardware_wallet_factor_source; +pub mod ledger_hardware_wallet_hint; +pub mod ledger_hardware_wallet_model; diff --git a/profile/src/v100/factors/factor_sources/mod.rs b/profile/src/v100/factors/factor_sources/mod.rs index b7001019..689964e8 100644 --- a/profile/src/v100/factors/factor_sources/mod.rs +++ b/profile/src/v100/factors/factor_sources/mod.rs @@ -1,2 +1,3 @@ pub mod device_factor_source; +pub mod ledger_hardware_wallet_factor_source; pub mod private_hierarchical_deterministic_factor_source; From ed1004438de0c10702e5a065af68670464219162 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 19:28:24 +0100 Subject: [PATCH 33/39] more tests --- profile/src/v100/factors/factor_source.rs | 6 +++ .../device_factor_source.rs | 21 +++++++- .../ledger_hardware_wallet_factor_source.rs | 53 ++++++++++++++++++- ...rarchical_deterministic_factor_instance.rs | 18 ++++++- wallet_kit_common/src/error/common_error.rs | 3 ++ .../src/types/keys/private_key.rs | 3 +- 6 files changed, 98 insertions(+), 6 deletions(-) diff --git a/profile/src/v100/factors/factor_source.rs b/profile/src/v100/factors/factor_source.rs index 5b1dad47..b3c47eb8 100644 --- a/profile/src/v100/factors/factor_source.rs +++ b/profile/src/v100/factors/factor_source.rs @@ -22,6 +22,12 @@ impl From for FactorSource { } } +impl From for FactorSource { + fn from(value: LedgerHardwareWalletFactorSource) -> Self { + FactorSource::Ledger(value) + } +} + impl<'de> Deserialize<'de> for FactorSource { #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(deserializer: D) -> Result { diff --git a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs index 0a6284ba..8020cf1c 100644 --- a/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs +++ b/profile/src/v100/factors/factor_sources/device_factor_source/device_factor_source.rs @@ -105,9 +105,9 @@ mod tests { use wallet_kit_common::json::assert_eq_after_json_roundtrip; use crate::v100::factors::{ - factor_source_id::FactorSourceID, is_factor_source::IsFactorSource, + factor_source_id::FactorSourceID, is_factor_source::IsFactorSource, factor_source::FactorSource, factor_sources::ledger_hardware_wallet_factor_source::ledger_hardware_wallet_factor_source::LedgerHardwareWalletFactorSource, }; - +use wallet_kit_common::error::common_error::CommonError as Error; use super::DeviceFactorSource; #[test] @@ -146,4 +146,21 @@ mod tests { let factor_source_id: FactorSourceID = sut.clone().id.into(); assert_eq!(factor_source_id, sut.factor_source_id()); } + + #[test] + fn from_factor_source() { + let sut = DeviceFactorSource::placeholder(); + let factor_source: FactorSource = sut.clone().into(); + assert_eq!(DeviceFactorSource::try_from(factor_source), Ok(sut)); + } + + #[test] + fn from_factor_source_invalid_got_ledger() { + let ledger = LedgerHardwareWalletFactorSource::placeholder(); + let factor_source: FactorSource = ledger.clone().into(); + assert_eq!( + DeviceFactorSource::try_from(factor_source), + Err(Error::ExpectedDeviceFactorSourceGotSomethingElse) + ); + } } diff --git a/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs index 513c3879..3844521e 100644 --- a/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs +++ b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs @@ -3,7 +3,9 @@ use std::cell::RefCell; use serde::{Deserialize, Serialize}; use crate::v100::factors::{ - factor_source_common::FactorSourceCommon, factor_source_id_from_hash::FactorSourceIDFromHash, + factor_source::FactorSource, factor_source_common::FactorSourceCommon, + factor_source_id::FactorSourceID, factor_source_id_from_hash::FactorSourceIDFromHash, + factor_source_kind::FactorSourceKind, is_factor_source::IsFactorSource, }; use super::ledger_hardware_wallet_hint::LedgerHardwareWalletHint; @@ -52,9 +54,36 @@ impl LedgerHardwareWalletFactorSource { } } +impl TryFrom for LedgerHardwareWalletFactorSource { + type Error = wallet_kit_common::error::common_error::CommonError; + + fn try_from(value: FactorSource) -> Result { + value + .into_ledger() + .map_err(|_| Self::Error::ExpectedLedgerHardwareWalletFactorSourceGotSomethingElse) + } +} + +impl IsFactorSource for LedgerHardwareWalletFactorSource { + fn factor_source_kind(&self) -> FactorSourceKind { + self.id.kind + } + + fn factor_source_id(&self) -> FactorSourceID { + self.clone().id.into() + } +} + #[cfg(test)] mod tests { - use wallet_kit_common::json::assert_eq_after_json_roundtrip; + use wallet_kit_common::{ + error::common_error::CommonError as Error, json::assert_eq_after_json_roundtrip, + }; + + use crate::v100::factors::{ + factor_source::FactorSource, + factor_sources::device_factor_source::device_factor_source::DeviceFactorSource, + }; use super::LedgerHardwareWalletFactorSource; @@ -86,4 +115,24 @@ mod tests { }"#, ); } + + #[test] + fn from_factor_source() { + let sut = LedgerHardwareWalletFactorSource::placeholder(); + let factor_source: FactorSource = sut.clone().into(); + assert_eq!( + LedgerHardwareWalletFactorSource::try_from(factor_source), + Ok(sut) + ); + } + + #[test] + fn from_factor_source_invalid_got_device() { + let wrong = DeviceFactorSource::placeholder(); + let factor_source: FactorSource = wrong.clone().into(); + assert_eq!( + LedgerHardwareWalletFactorSource::try_from(factor_source), + Err(Error::ExpectedLedgerHardwareWalletFactorSourceGotSomethingElse) + ); + } } diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 97743a4c..8b0619fc 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -192,7 +192,10 @@ impl HierarchicalDeterministicFactorInstance { mod tests { use hierarchical_deterministic::{ bip44::bip44_like_path::BIP44LikePath, - cap26::cap26_path::paths::getid_path::GetIDPath, + cap26::{ + cap26_key_kind::CAP26KeyKind, + cap26_path::paths::{getid_path::GetIDPath, identity_path::IdentityPath}, + }, derivation::{ derivation::Derivation, derivation_path::DerivationPath, hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey, @@ -254,6 +257,19 @@ mod tests { assert_eq!(sut.key_kind(), None); } + #[test] + fn key_kind_identity() { + let derivation_path: DerivationPath = IdentityPath::placeholder().into(); + let sut = HierarchicalDeterministicFactorInstance::new( + FactorSourceIDFromHash::placeholder(), + HierarchicalDeterministicPublicKey::new( + PublicKey::placeholder_ed25519(), + derivation_path, + ), + ); + assert_eq!(sut.key_kind(), Some(CAP26KeyKind::TransactionSigning)); + } + #[test] fn key_kind_cap26_getid_is_none() { let derivation_path: DerivationPath = GetIDPath::default().into(); diff --git a/wallet_kit_common/src/error/common_error.rs b/wallet_kit_common/src/error/common_error.rs index 653ff9ab..97836415 100644 --- a/wallet_kit_common/src/error/common_error.rs +++ b/wallet_kit_common/src/error/common_error.rs @@ -70,4 +70,7 @@ pub enum CommonError { #[error("Expected DeviceFactorSource")] ExpectedDeviceFactorSourceGotSomethingElse, + + #[error("Expected LedgerHardwareWalletFactorSource")] + ExpectedLedgerHardwareWalletFactorSourceGotSomethingElse, } diff --git a/wallet_kit_common/src/types/keys/private_key.rs b/wallet_kit_common/src/types/keys/private_key.rs index e9178fd2..5eb2d5b7 100644 --- a/wallet_kit_common/src/types/keys/private_key.rs +++ b/wallet_kit_common/src/types/keys/private_key.rs @@ -151,6 +151,7 @@ mod tests { fn secp256k1_to_bytes() { let bytes = generate_32_bytes(); let key = Secp256k1PrivateKey::from_bytes(&bytes).unwrap(); - assert_eq!(key.to_bytes(), bytes); + let private_key: PrivateKey = key.into(); + assert_eq!(private_key.to_bytes(), bytes); } } From 03a7596e230fe79d73f24f0ffd8d4370ded9dca1 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 20:04:37 +0100 Subject: [PATCH 34/39] docs --- hierarchical_deterministic/src/bip32/hd_path.rs | 4 ++-- hierarchical_deterministic/src/bip39/mnemonic.rs | 4 ++-- .../src/bip44/bip44_like_path.rs | 4 ++-- .../src/cap26/cap26_path/paths/account_path.rs | 4 ++-- .../src/cap26/cap26_path/paths/getid_path.rs | 4 ++-- profile/src/v100/address/resource_address.rs | 4 ++-- .../entity_security_state.rs | 4 ++++ profile/src/v100/factors/factor_source_common.rs | 16 ++++++++++++++++ profile/src/v100/factors/factor_source_kind.rs | 1 + wallet_kit_common/src/types/hex_32bytes.rs | 4 ++-- 10 files changed, 35 insertions(+), 14 deletions(-) diff --git a/hierarchical_deterministic/src/bip32/hd_path.rs b/hierarchical_deterministic/src/bip32/hd_path.rs index 19d816ac..76a85f97 100644 --- a/hierarchical_deterministic/src/bip32/hd_path.rs +++ b/hierarchical_deterministic/src/bip32/hd_path.rs @@ -125,7 +125,7 @@ impl ToString for HDPath { } impl Serialize for HDPath { - /// Serializes this `AccountAddress` into its bech32 address string as JSON. + /// Serializes this `HDPath` into its bech32 address string as JSON. fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -135,7 +135,7 @@ impl Serialize for HDPath { } impl<'de> serde::Deserialize<'de> for HDPath { - /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + /// Tries to deserializes a JSON string as a bech32 address into an `HDPath`. #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; diff --git a/hierarchical_deterministic/src/bip39/mnemonic.rs b/hierarchical_deterministic/src/bip39/mnemonic.rs index 9ff6a49f..95104bce 100644 --- a/hierarchical_deterministic/src/bip39/mnemonic.rs +++ b/hierarchical_deterministic/src/bip39/mnemonic.rs @@ -48,7 +48,7 @@ impl Mnemonic { pub type Seed = [u8; 64]; impl Serialize for Mnemonic { - /// Serializes this `AccountAddress` into its bech32 address string as JSON. + /// Serializes this `Mnemonic` into a phrase, all words separated by a space. fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -58,7 +58,7 @@ impl Serialize for Mnemonic { } impl<'de> serde::Deserialize<'de> for Mnemonic { - /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + /// Tries to deserializes a JSON string as a Mnemonic #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; diff --git a/hierarchical_deterministic/src/bip44/bip44_like_path.rs b/hierarchical_deterministic/src/bip44/bip44_like_path.rs index 9bccccf1..2abc9fbc 100644 --- a/hierarchical_deterministic/src/bip44/bip44_like_path.rs +++ b/hierarchical_deterministic/src/bip44/bip44_like_path.rs @@ -77,7 +77,7 @@ impl Derivation for BIP44LikePath { } impl Serialize for BIP44LikePath { - /// Serializes this `AccountAddress` into its bech32 address string as JSON. + /// Serializes this `BIP44LikePath` into JSON as a string on: "m/44H/1022H/0H/0/0H" format fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -87,7 +87,7 @@ impl Serialize for BIP44LikePath { } impl<'de> serde::Deserialize<'de> for BIP44LikePath { - /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + /// Tries to deserializes a JSON string as a derivation path string into a `BIP44LikePath` #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs index 38434e49..eeefcfcd 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs @@ -74,7 +74,7 @@ impl AccountPath { } impl Serialize for AccountPath { - /// Serializes this `AccountAddress` into its bech32 address string as JSON. + /// Serializes this `AccountPath` into JSON as a string on: "m/44H/1022H/1H/525H/1460H/0H" format fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -84,7 +84,7 @@ impl Serialize for AccountPath { } impl<'de> serde::Deserialize<'de> for AccountPath { - /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + /// Tries to deserializes a JSON string as a derivation path string into a `AccountPath` #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs index cf7f23f2..3de24eb0 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/getid_path.rs @@ -63,7 +63,7 @@ impl GetIDPath { } impl Serialize for GetIDPath { - /// Serializes this `AccountAddress` into its bech32 address string as JSON. + /// Serializes this `GetIDPath` into JSON as a derivation path string on format `m/1022H/365H` fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -73,7 +73,7 @@ impl Serialize for GetIDPath { } impl<'de> serde::Deserialize<'de> for GetIDPath { - /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + /// Tries to deserializes a JSON string as derivation path string into a `GetIDPath` #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; diff --git a/profile/src/v100/address/resource_address.rs b/profile/src/v100/address/resource_address.rs index 6b9bdee4..37706d99 100644 --- a/profile/src/v100/address/resource_address.rs +++ b/profile/src/v100/address/resource_address.rs @@ -16,7 +16,7 @@ pub struct ResourceAddress { } impl Serialize for ResourceAddress { - /// Serializes this `AccountAddress` into its bech32 address string as JSON. + /// Serializes this `ResourceAddress` into its bech32 address string as JSON. fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -26,7 +26,7 @@ impl Serialize for ResourceAddress { } impl<'de> serde::Deserialize<'de> for ResourceAddress { - /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + /// Tries to deserializes a JSON string as a bech32 address into an `ResourceAddress`. #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; diff --git a/profile/src/v100/entity_security_state/entity_security_state.rs b/profile/src/v100/entity_security_state/entity_security_state.rs index d9364997..1f55e14a 100644 --- a/profile/src/v100/entity_security_state/entity_security_state.rs +++ b/profile/src/v100/entity_security_state/entity_security_state.rs @@ -2,9 +2,13 @@ use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializ use super::unsecured_entity_control::UnsecuredEntityControl; +/// Describes the state an entity - Account or Persona - is in in regards to how +/// the user controls it, i.e. if it is controlled by a single factor (private key) +/// or an `AccessController` with a potential Multi-Factor setup. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(remote = "Self")] pub enum EntitySecurityState { + /// The account is controlled by a single factor (private key) #[serde(rename = "unsecuredEntityControl")] Unsecured(UnsecuredEntityControl), } diff --git a/profile/src/v100/factors/factor_source_common.rs b/profile/src/v100/factors/factor_source_common.rs index 3d16a7ec..664b97e2 100644 --- a/profile/src/v100/factors/factor_source_common.rs +++ b/profile/src/v100/factors/factor_source_common.rs @@ -163,4 +163,20 @@ mod tests { "#, ); } + + #[test] + fn main_flag_present_if_main() { + assert!(FactorSourceCommon::new_bdfs(true) + .flags + .get_mut() + .contains(&FactorSourceFlag::Main)); + } + + #[test] + fn main_flag_not_present_if_not_main() { + assert!(FactorSourceCommon::new_bdfs(false) + .flags + .get_mut() + .is_empty()); + } } diff --git a/profile/src/v100/factors/factor_source_kind.rs b/profile/src/v100/factors/factor_source_kind.rs index 6400eb07..292b5670 100644 --- a/profile/src/v100/factors/factor_source_kind.rs +++ b/profile/src/v100/factors/factor_source_kind.rs @@ -75,6 +75,7 @@ impl FactorSourceKind { } impl Display for FactorSourceKind { + #[cfg(not(tarpaulin_include))] // false negative fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.discriminant()) } diff --git a/wallet_kit_common/src/types/hex_32bytes.rs b/wallet_kit_common/src/types/hex_32bytes.rs index aa0160f6..914acf14 100644 --- a/wallet_kit_common/src/types/hex_32bytes.rs +++ b/wallet_kit_common/src/types/hex_32bytes.rs @@ -105,7 +105,7 @@ impl Hex32Bytes { } impl Serialize for Hex32Bytes { - /// Serializes this `AccountAddress` into its bech32 address string as JSON. + /// Serializes this `Hex32Bytes` into a hex string as JSON. fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -115,7 +115,7 @@ impl Serialize for Hex32Bytes { } impl<'de> serde::Deserialize<'de> for Hex32Bytes { - /// Tries to deserializes a JSON string as a bech32 address into an `AccountAddress`. + /// Tries to deserializes a JSON string as a hex string into an `Hex32Bytes`. #[cfg(not(tarpaulin_include))] // false negative fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; From 8f64c343ab114b93aa4cda6c0f09d50360e99524 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 20:33:18 +0100 Subject: [PATCH 35/39] more tests --- .../src/derivation/derivation_path.rs | 2 + .../hierarchical_deterministic_private_key.rs | 44 ++++++++++++- .../factor_instance/factor_instance_badge.rs | 30 +++++++++ ...rarchical_deterministic_factor_instance.rs | 61 +++++++++++++++++++ 4 files changed, 136 insertions(+), 1 deletion(-) diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 8f5743df..bfc66140 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -86,12 +86,14 @@ impl Derivation for DerivationPath { fn derivation_path(&self) -> DerivationPath { self.clone() } + fn hd_path(&self) -> &HDPath { match self { DerivationPath::CAP26(path) => path.hd_path(), DerivationPath::BIP44Like(path) => path.hd_path(), } } + fn scheme(&self) -> DerivationPathScheme { match self { DerivationPath::CAP26(p) => p.scheme(), diff --git a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_private_key.rs b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_private_key.rs index 250ca29c..d3390704 100644 --- a/hierarchical_deterministic/src/derivation/hierarchical_deterministic_private_key.rs +++ b/hierarchical_deterministic/src/derivation/hierarchical_deterministic_private_key.rs @@ -1,16 +1,26 @@ -use wallet_kit_common::types::keys::private_key::PrivateKey; +use wallet_kit_common::types::keys::{ + ed25519::private_key::Ed25519PrivateKey, private_key::PrivateKey, +}; + +use crate::cap26::{cap26_path::paths::account_path::AccountPath, cap26_repr::CAP26Repr}; use super::{ derivation_path::DerivationPath, hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey, }; +/// An ephemeral (never persisted) HD PrivateKey which contains +/// the derivation path used to derive it. pub struct HierarchicalDeterministicPrivateKey { + /// The PrivateKey derived from some HD FactorSource using `derivation_path`. pub private_key: PrivateKey, + /// Derivation path used to derive the `PrivateKey` from some HD FactorSource. pub derivation_path: DerivationPath, } impl HierarchicalDeterministicPrivateKey { + /// Instantiates a new `HierarchicalDeterministicPrivateKey` from a PrivateKey and + /// the derivation path used to derive it. pub fn new(private_key: PrivateKey, derivation_path: DerivationPath) -> Self { Self { private_key, @@ -20,6 +30,7 @@ impl HierarchicalDeterministicPrivateKey { } impl HierarchicalDeterministicPrivateKey { + /// Returns the public key of the private key with the derivation path intact. pub fn public_key(&self) -> HierarchicalDeterministicPublicKey { HierarchicalDeterministicPublicKey::new( self.private_key.public_key(), @@ -27,7 +38,38 @@ impl HierarchicalDeterministicPrivateKey { ) } + /// The PrivateKey as hex string. pub fn to_hex(&self) -> String { self.private_key.to_hex() } } + +impl HierarchicalDeterministicPrivateKey { + /// A placeholder used to facilitate unit tests. + pub fn placeholder() -> Self { + Self::new( + Ed25519PrivateKey::from_str( + "cf52dbc7bb2663223e99fb31799281b813b939440a372d0aa92eb5f5b8516003", + ) + .unwrap() + .into(), + AccountPath::from_str("m/44H/1022H/1H/525H/1460H/0H") + .unwrap() + .into(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::HierarchicalDeterministicPrivateKey; + + #[test] + fn publickey_of_placeholder() { + let sut = HierarchicalDeterministicPrivateKey::placeholder(); + assert_eq!( + sut.public_key().to_hex(), + "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b" + ); + } +} diff --git a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs index 6d9f6412..d47c4a23 100644 --- a/profile/src/v100/factors/factor_instance/factor_instance_badge.rs +++ b/profile/src/v100/factors/factor_instance/factor_instance_badge.rs @@ -3,6 +3,7 @@ use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializ use super::badge_virtual_source::FactorInstanceBadgeVirtualSource; use enum_as_inner::EnumAsInner; + /// Either a "physical" badge (NFT) or some source for recreation of a producer /// of a virtual badge (signature), e.g. a HD derivation path, from which a private key /// is derived which produces virtual badges (signatures). @@ -67,8 +68,11 @@ impl Serialize for FactorInstanceBadge { #[cfg(test)] mod tests { + use hierarchical_deterministic::derivation::hierarchical_deterministic_public_key::HierarchicalDeterministicPublicKey; use wallet_kit_common::json::assert_eq_after_json_roundtrip; + use crate::v100::factors::factor_instance::badge_virtual_source::FactorInstanceBadgeVirtualSource; + use super::FactorInstanceBadge; #[test] fn json_roundtrip() { @@ -95,4 +99,30 @@ mod tests { "#, ); } + + #[test] + fn into_from_hd_pubkey() { + let sut: FactorInstanceBadge = HierarchicalDeterministicPublicKey::placeholder().into(); + assert_eq!( + sut, + FactorInstanceBadge::Virtual( + FactorInstanceBadgeVirtualSource::HierarchicalDeterministic( + HierarchicalDeterministicPublicKey::placeholder() + ) + ) + ) + } + + #[test] + fn into_from_virtual_source() { + let sut: FactorInstanceBadge = FactorInstanceBadgeVirtualSource::placeholder().into(); + assert_eq!( + sut, + FactorInstanceBadge::Virtual( + FactorInstanceBadgeVirtualSource::HierarchicalDeterministic( + HierarchicalDeterministicPublicKey::placeholder() + ) + ) + ) + } } diff --git a/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs index 2c6ca699..07908c2a 100644 --- a/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/factor_instance/private_hierarchical_deterministic_factor_instance.rs @@ -4,8 +4,12 @@ use crate::v100::factors::factor_source_id::FactorSourceID; use super::factor_instance::FactorInstance; +/// An ephemeral (never persisted) HD FactorInstance which contains +/// the private key, with the ID of its creating FactorSource. pub struct PrivateHierarchicalDeterministicFactorInstance { + /// The HD Private Key. pub private_key: HierarchicalDeterministicPrivateKey, + /// The ID of the FactorSource creating the `PrivateKey`. pub factor_source_id: FactorSourceID, } @@ -25,6 +29,8 @@ impl From for FactorInstance { } impl PrivateHierarchicalDeterministicFactorInstance { + /// Instantiates a new `PrivateHierarchicalDeterministicFactorInstance` from the HD PrivateKey + /// with a FactorSourceID. pub fn new( private_key: HierarchicalDeterministicPrivateKey, factor_source_id: FactorSourceID, @@ -35,3 +41,58 @@ impl PrivateHierarchicalDeterministicFactorInstance { } } } + +impl PrivateHierarchicalDeterministicFactorInstance { + /// A placeholder used to facilitate unit tests. + pub fn placeholder() -> Self { + Self::new( + HierarchicalDeterministicPrivateKey::placeholder(), + FactorSourceID::placeholder(), + ) + } +} + +#[cfg(test)] +mod tests { + use hierarchical_deterministic::derivation::{ + derivation::Derivation, + hierarchical_deterministic_private_key::HierarchicalDeterministicPrivateKey, + }; + + use crate::v100::factors::factor_instance::factor_instance::FactorInstance; + + use super::PrivateHierarchicalDeterministicFactorInstance; + + #[test] + fn new() { + let sut = PrivateHierarchicalDeterministicFactorInstance::placeholder(); + assert_eq!( + sut.private_key.derivation_path.to_string(), + "m/44H/1022H/1H/525H/1460H/0H" + ); + assert_eq!( + sut.private_key.private_key.to_hex(), + "cf52dbc7bb2663223e99fb31799281b813b939440a372d0aa92eb5f5b8516003" + ); + } + + #[test] + fn into_hierarchical_deterministic_private_key() { + let sut = PrivateHierarchicalDeterministicFactorInstance::placeholder(); + let key: HierarchicalDeterministicPrivateKey = sut.into(); + assert_eq!( + key.public_key(), + HierarchicalDeterministicPrivateKey::placeholder().public_key() + ); + } + + #[test] + fn into_factor_instance() { + let sut = PrivateHierarchicalDeterministicFactorInstance::placeholder(); + let key: FactorInstance = sut.into(); + assert_eq!( + key.factor_source_id, + PrivateHierarchicalDeterministicFactorInstance::placeholder().factor_source_id + ); + } +} From 2ecc675edfc28b3bc1d5b8574299730a65a55f42 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 20:57:41 +0100 Subject: [PATCH 36/39] moooar tests --- .../cap26/cap26_path/paths/account_path.rs | 30 ++++++++++- .../cap26/cap26_path/paths/identity_path.rs | 50 ++++++++++++++++--- .../src/cap26/cap26_repr.rs | 1 + .../src/derivation/derivation_path.rs | 46 ++++++++++++++++- 4 files changed, 118 insertions(+), 9 deletions(-) diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs index eeefcfcd..9f54fce6 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/account_path.rs @@ -28,9 +28,11 @@ impl IsEntityPath for AccountPath { fn network_id(&self) -> NetworkID { self.network_id } + fn key_kind(&self) -> CAP26KeyKind { self.key_kind } + fn index(&self) -> HDPathValue { self.index } @@ -122,8 +124,10 @@ mod tests { }; use crate::{ + bip32::hd_path::HDPath, cap26::{ - cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, cap26_repr::CAP26Repr, + cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, + cap26_path::paths::is_entity_path::IsEntityPath, cap26_repr::CAP26Repr, }, derivation::derivation::Derivation, }; @@ -272,4 +276,28 @@ mod tests { assert_json_value_eq_after_roundtrip(&parsed, json!(str)); assert_json_value_ne_after_roundtrip(&parsed, json!("m/44H/1022H/1H/525H/1460H/1H")); } + + #[test] + fn is_entity_path_index() { + let sut = AccountPath::placeholder(); + assert_eq!(sut.index(), 0); + assert_eq!(sut.network_id(), NetworkID::Mainnet); + assert_eq!(sut.key_kind(), CAP26KeyKind::TransactionSigning); + } + + #[test] + fn try_from_hdpath_valid() { + let hdpath = HDPath::from_str("m/44H/1022H/1H/525H/1460H/0H").unwrap(); + let account_path = AccountPath::try_from(&hdpath); + assert!(account_path.is_ok()); + } + + #[test] + fn try_from_hdpath_invalid() { + let hdpath = HDPath::from_str("m/44H/1022H/1H/618H/1460H/0H").unwrap(); + assert_eq!( + AccountPath::try_from(&hdpath), + Err(HDPathError::WrongEntityKind(618, 525)) + ); + } } diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs index f3a1facc..a1a8975e 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs @@ -104,9 +104,11 @@ impl Derivation for IdentityPath { fn hd_path(&self) -> &HDPath { &self.path } + fn derivation_path(&self) -> DerivationPath { DerivationPath::CAP26(CAP26Path::IdentityPath(self.clone())) } + fn scheme(&self) -> DerivationPathScheme { DerivationPathScheme::Cap26 } @@ -114,6 +116,13 @@ impl Derivation for IdentityPath { #[cfg(test)] mod tests { + use crate::{ + bip32::hd_path::HDPath, + cap26::{ + cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, cap26_repr::CAP26Repr, + }, + derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, + }; use serde_json::json; use wallet_kit_common::{ error::hdpath_error::HDPathError, @@ -121,13 +130,6 @@ mod tests { network_id::NetworkID, }; - use crate::{ - cap26::{ - cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, cap26_repr::CAP26Repr, - }, - derivation::derivation::Derivation, - }; - use super::IdentityPath; #[test] @@ -272,4 +274,38 @@ mod tests { assert_json_value_eq_after_roundtrip(&parsed, json!(str)); assert_json_value_ne_after_roundtrip(&parsed, json!("m/44H/1022H/1H/618H/1460H/1H")); } + + #[test] + fn identity_path_scheme() { + assert_eq!( + IdentityPath::placeholder().scheme(), + DerivationPathScheme::Cap26 + ); + } + + #[test] + fn identity_path_derivation_path() { + assert_eq!( + IdentityPath::placeholder() + .derivation_path() + .hd_path() + .to_string(), + "m/44H/1022H/1H/618H/1460H/0H" + ); + } + + #[test] + fn try_from_hdpath_valid() { + let hdpath = HDPath::from_str("m/44H/1022H/1H/618H/1460H/0H").unwrap(); + assert!(IdentityPath::try_from(&hdpath).is_ok()); + } + + #[test] + fn try_from_hdpath_invalid() { + let hdpath = HDPath::from_str("m/44H/1022H/1H/525H/1460H/0H").unwrap(); + assert_eq!( + IdentityPath::try_from(&hdpath), + Err(HDPathError::WrongEntityKind(525, 618)) + ); + } } diff --git a/hierarchical_deterministic/src/cap26/cap26_repr.rs b/hierarchical_deterministic/src/cap26/cap26_repr.rs index 5ac7b2c3..7fdc7909 100644 --- a/hierarchical_deterministic/src/cap26/cap26_repr.rs +++ b/hierarchical_deterministic/src/cap26/cap26_repr.rs @@ -23,6 +23,7 @@ pub trait CAP26Repr: Derivation { index: HDPathValue, ) -> Self; + #[cfg(not(tarpaulin_include))] // false negative, this is in fact heavily tested. fn try_from_hdpath(hdpath: &HDPath) -> Result { use HDPathError::*; let (path, components) = diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index bfc66140..7b315376 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -146,7 +146,9 @@ mod tests { bip44::bip44_like_path::BIP44LikePath, cap26::cap26_path::{ cap26_path::CAP26Path, - paths::{account_path::AccountPath, getid_path::GetIDPath}, + paths::{ + account_path::AccountPath, getid_path::GetIDPath, identity_path::IdentityPath, + }, }, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, }; @@ -206,6 +208,48 @@ mod tests { ); } + #[test] + fn into_from_identity_cap26_path() { + assert_eq!( + DerivationPath::CAP26(IdentityPath::placeholder().into()), + IdentityPath::placeholder().into() + ); + } + + #[test] + fn derivation_path_identity() { + let derivation_path: DerivationPath = IdentityPath::placeholder().into(); + assert_eq!(derivation_path, derivation_path.derivation_path()); + } + + #[test] + fn try_from_hdpath_account() { + let derivation_path: DerivationPath = AccountPath::placeholder().into(); + let hd_path = derivation_path.hd_path(); + assert_eq!(DerivationPath::try_from(hd_path), Ok(derivation_path)); + } + + #[test] + fn try_from_hdpath_identity() { + let derivation_path: DerivationPath = IdentityPath::placeholder().into(); + let hd_path = derivation_path.hd_path(); + assert_eq!(DerivationPath::try_from(hd_path), Ok(derivation_path)); + } + + #[test] + fn try_from_hdpath_bip44() { + let derivation_path: DerivationPath = BIP44LikePath::placeholder().into(); + let hd_path = derivation_path.hd_path(); + assert_eq!(DerivationPath::try_from(hd_path), Ok(derivation_path)); + } + + #[test] + fn try_from_hdpath_getid() { + let derivation_path: DerivationPath = GetIDPath::default().into(); + let hd_path = derivation_path.hd_path(); + assert_eq!(DerivationPath::try_from(hd_path), Ok(derivation_path)); + } + #[test] fn as_cap26_path() { let path: DerivationPath = AccountPath::placeholder().into(); From 9a82c58589724cd67f606c54576479f73ae2c6e8 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 21:07:15 +0100 Subject: [PATCH 37/39] moare tests --- .../src/cap26/cap26_path/paths/identity_path.rs | 11 ++++++++++- .../src/derivation/derivation_path.rs | 10 ++++++++++ .../ledger_hardware_wallet_factor_source.rs | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs index a1a8975e..75a05c06 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/identity_path.rs @@ -119,7 +119,8 @@ mod tests { use crate::{ bip32::hd_path::HDPath, cap26::{ - cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, cap26_repr::CAP26Repr, + cap26_entity_kind::CAP26EntityKind, cap26_key_kind::CAP26KeyKind, + cap26_path::paths::is_entity_path::IsEntityPath, cap26_repr::CAP26Repr, }, derivation::{derivation::Derivation, derivation_path_scheme::DerivationPathScheme}, }; @@ -308,4 +309,12 @@ mod tests { Err(HDPathError::WrongEntityKind(525, 618)) ); } + + #[test] + fn is_entity_path_index() { + let sut = IdentityPath::placeholder(); + assert_eq!(sut.index(), 0); + assert_eq!(sut.network_id(), NetworkID::Mainnet); + assert_eq!(sut.key_kind(), CAP26KeyKind::TransactionSigning); + } } diff --git a/hierarchical_deterministic/src/derivation/derivation_path.rs b/hierarchical_deterministic/src/derivation/derivation_path.rs index 7b315376..e9d8c38c 100644 --- a/hierarchical_deterministic/src/derivation/derivation_path.rs +++ b/hierarchical_deterministic/src/derivation/derivation_path.rs @@ -250,6 +250,16 @@ mod tests { assert_eq!(DerivationPath::try_from(hd_path), Ok(derivation_path)); } + #[test] + fn from_cap26() { + let derivation_path: DerivationPath = + CAP26Path::AccountPath(AccountPath::placeholder()).into(); + assert_eq!( + derivation_path.derivation_path(), + AccountPath::placeholder().derivation_path() + ) + } + #[test] fn as_cap26_path() { let path: DerivationPath = AccountPath::placeholder().into(); diff --git a/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs index 3844521e..a2a8218b 100644 --- a/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs +++ b/profile/src/v100/factors/factor_sources/ledger_hardware_wallet_factor_source/ledger_hardware_wallet_factor_source.rs @@ -83,6 +83,7 @@ mod tests { use crate::v100::factors::{ factor_source::FactorSource, factor_sources::device_factor_source::device_factor_source::DeviceFactorSource, + is_factor_source::IsFactorSource, }; use super::LedgerHardwareWalletFactorSource; @@ -135,4 +136,20 @@ mod tests { Err(Error::ExpectedLedgerHardwareWalletFactorSourceGotSomethingElse) ); } + + #[test] + fn factor_source_id() { + assert_eq!( + LedgerHardwareWalletFactorSource::placeholder().factor_source_id(), + LedgerHardwareWalletFactorSource::placeholder().id.into() + ); + } + + #[test] + fn factor_source_kind() { + assert_eq!( + LedgerHardwareWalletFactorSource::placeholder().factor_source_kind(), + LedgerHardwareWalletFactorSource::placeholder().id.kind + ); + } } From 3ba563a4999b2bda0c4127c3f0f1484209074f71 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 21:21:19 +0100 Subject: [PATCH 38/39] false negatives --- .../src/cap26/cap26_path/paths/is_entity_path.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hierarchical_deterministic/src/cap26/cap26_path/paths/is_entity_path.rs b/hierarchical_deterministic/src/cap26/cap26_path/paths/is_entity_path.rs index 955674b3..3359c280 100644 --- a/hierarchical_deterministic/src/cap26/cap26_path/paths/is_entity_path.rs +++ b/hierarchical_deterministic/src/cap26/cap26_path/paths/is_entity_path.rs @@ -14,12 +14,17 @@ pub trait IsEntityPath: CAP26Repr { pub trait HasEntityPath { fn path(&self) -> Path; + #[cfg(not(tarpaulin_include))] // false negative fn network_id(&self) -> NetworkID { self.path().network_id() } + + #[cfg(not(tarpaulin_include))] // false negative fn key_kind(&self) -> CAP26KeyKind { self.path().key_kind() } + + #[cfg(not(tarpaulin_include))] // false negative fn index(&self) -> HDPathValue { self.path().index() } From 519d4bfdd8f2a125d60d2bbfe6606109340e638b Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sun, 3 Dec 2023 23:08:39 +0100 Subject: [PATCH 39/39] remove false negatives --- .../src/bip32/hd_path.rs | 1 + .../unsecured_entity_control.rs | 2 ++ ...rarchical_deterministic_factor_instance.rs | 36 +++---------------- 3 files changed, 7 insertions(+), 32 deletions(-) diff --git a/hierarchical_deterministic/src/bip32/hd_path.rs b/hierarchical_deterministic/src/bip32/hd_path.rs index 76a85f97..50ac86b7 100644 --- a/hierarchical_deterministic/src/bip32/hd_path.rs +++ b/hierarchical_deterministic/src/bip32/hd_path.rs @@ -126,6 +126,7 @@ impl ToString for HDPath { impl Serialize for HDPath { /// Serializes this `HDPath` into its bech32 address string as JSON. + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, diff --git a/profile/src/v100/entity_security_state/unsecured_entity_control.rs b/profile/src/v100/entity_security_state/unsecured_entity_control.rs index 4a2fb868..a2133bd8 100644 --- a/profile/src/v100/entity_security_state/unsecured_entity_control.rs +++ b/profile/src/v100/entity_security_state/unsecured_entity_control.rs @@ -33,6 +33,7 @@ impl UnsecuredEntityControl { } } + #[cfg(not(tarpaulin_include))] // false negative pub fn new( transaction_signing: HierarchicalDeterministicFactorInstance, authentication_signing: Option, @@ -54,6 +55,7 @@ impl UnsecuredEntityControl { authentication_signing, }) } + pub fn with_transaction_signing_only( transaction_signing: HierarchicalDeterministicFactorInstance, ) -> Result { diff --git a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs index 8b0619fc..53e5a020 100644 --- a/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs +++ b/profile/src/v100/factors/hierarchical_deterministic_factor_instance.rs @@ -111,6 +111,7 @@ impl HierarchicalDeterministicFactorInstance { } impl Serialize for HierarchicalDeterministicFactorInstance { + #[cfg(not(tarpaulin_include))] // false negative fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -120,6 +121,7 @@ impl Serialize for HierarchicalDeterministicFactorInstance { } impl<'de> serde::Deserialize<'de> for HierarchicalDeterministicFactorInstance { + #[cfg(not(tarpaulin_include))] // false negative fn deserialize>( d: D, ) -> Result { @@ -138,42 +140,12 @@ impl HierarchicalDeterministicFactorInstance { /// A placeholder used to facilitate unit tests. pub fn placeholder_transaction_signing() -> Self { - let placeholder = Self::placeholder_with_key_kind(CAP26KeyKind::TransactionSigning, 0); - assert_eq!( - placeholder.derivation_path().to_string(), - "m/44H/1022H/1H/525H/1460H/0H" - ); - - assert_eq!( - "d24cc6af91c3f103d7f46e5691ce2af9fea7d90cfb89a89d5bba4b513b34be3b", - placeholder.public_key.to_hex() - ); - - assert_eq!( - placeholder.factor_source_id.to_string(), - "device:3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" - ); - return placeholder; + Self::placeholder_with_key_kind(CAP26KeyKind::TransactionSigning, 0) } /// A placeholder used to facilitate unit tests. pub fn placeholder_auth_signing() -> Self { - let placeholder = Self::placeholder_with_key_kind(CAP26KeyKind::AuthenticationSigning, 0); - assert_eq!( - placeholder.derivation_path().to_string(), - "m/44H/1022H/1H/525H/1678H/0H" - ); - - assert_eq!( - "564d6ca366bb24cbe8b512324c93809a32039d74f133bfa82d1e80d93225989f", - placeholder.public_key.to_hex() - ); - - assert_eq!( - placeholder.factor_source_id.to_string(), - "device:3c986ebf9dcd9167a97036d3b2c997433e85e6cc4e4422ad89269dac7bfea240" - ); - return placeholder; + Self::placeholder_with_key_kind(CAP26KeyKind::AuthenticationSigning, 0) } /// A placeholder used to facilitate unit tests.