diff --git a/Cargo.toml b/Cargo.toml index e26892ebb..e5354baf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ bs58 = "0.5.0" base64 = "0.22.1" copypasta = "0.10.1" dash-sdk = { git = "https://github.com/dashpay/platform", branch = "chore/DocumentQueryPartialEq" } -simple-signer = { git = "https://github.com/dashpay/platform", branch = "chore/DocumentQueryPartialEq" } thiserror = "1" serde = "1.0.197" serde_json = "1.0.120" diff --git a/src/model/qualified_identity.rs b/src/model/qualified_identity.rs index 52ab6846a..210883b01 100644 --- a/src/model/qualified_identity.rs +++ b/src/model/qualified_identity.rs @@ -3,6 +3,8 @@ use dash_sdk::dashcore_rpc::dashcore::{signer, PubkeyHash}; use dash_sdk::dpp::dashcore::address::Payload; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::dashcore::{Address, Network, ScriptHash}; +use dash_sdk::dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dash_sdk::dpp::data_contract::document_type::DocumentTypeRef; use dash_sdk::dpp::ed25519_dalek::Signer as EDDSASigner; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::hash::IdentityPublicKeyHashMethodsV0; @@ -14,7 +16,7 @@ use dash_sdk::dpp::platform_value::BinaryData; use dash_sdk::dpp::state_transition::errors::InvalidIdentityPublicKeyTypeError; use dash_sdk::dpp::{bls_signatures, ed25519_dalek, ProtocolError}; use dash_sdk::platform::IdentityPublicKey; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; use std::fmt::{Display, Formatter}; #[derive(Debug, Encode, Decode, PartialEq, Clone, Copy)] @@ -176,6 +178,18 @@ impl QualifiedIdentity { None } + pub fn document_signing_key( + &self, + document_type: &DocumentTypeRef, + ) -> Option<&IdentityPublicKey> { + self.identity.get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([document_type.security_level_requirement()]), + HashSet::from(KeyType::all_key_types()), + false, + ) + } + pub fn available_withdrawal_keys(&self) -> Vec<&IdentityPublicKey> { let mut keys = vec![]; diff --git a/src/platform/identity/mod.rs b/src/platform/identity/mod.rs index ce35e4ebe..310f9519c 100644 --- a/src/platform/identity/mod.rs +++ b/src/platform/identity/mod.rs @@ -38,7 +38,7 @@ pub struct IdentityRegistrationInfo { #[derive(Debug, Clone, PartialEq)] pub struct RegisterDpnsNameInput { - pub identity_id_input: Identifier, + pub qualified_identity: QualifiedIdentity, pub name_input: String, } diff --git a/src/platform/identity/register_dpns_name.rs b/src/platform/identity/register_dpns_name.rs index 1baa664fa..abd1f8fdc 100644 --- a/src/platform/identity/register_dpns_name.rs +++ b/src/platform/identity/register_dpns_name.rs @@ -1,31 +1,29 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::BTreeMap; -use crate::{context::AppContext, model::qualified_identity::EncryptedPrivateKeyTarget}; +use crate::{context::AppContext, model::qualified_identity::QualifiedIdentity}; use dash_sdk::{ dpp::{ data_contract::{ accessors::v0::DataContractV0Getters, document_type::accessors::DocumentTypeV0Getters, }, - document::{DocumentV0, DocumentV0Getters, DocumentV0Setters}, - identity::{accessors::IdentityGettersV0, KeyType, Purpose, SecurityLevel}, + document::DocumentV0, + identity::accessors::IdentityGettersV0, platform_value::Bytes32, state_transition::documents_batch_transition::{ methods::v0::DocumentsBatchTransitionMethodsV0, DocumentsBatchTransition, }, util::{hash::hash_double, strings::convert_to_homograph_safe_chars}, - version::PlatformVersion, }, platform::{ transition::{ broadcast::BroadcastStateTransition, put_document::PutDocument, put_settings::PutSettings, }, - Document, Identity, + Document, }, RequestSettings, Sdk, }; use rand::{rngs::StdRng, Rng, SeedableRng}; -use simple_signer::signer::SimpleSigner; use super::RegisterDpnsNameInput; impl AppContext { @@ -35,14 +33,9 @@ impl AppContext { input: RegisterDpnsNameInput, ) -> Result<(), String> { let mut rng = StdRng::from_entropy(); - let platform_version = PlatformVersion::latest(); let dpns_contract = self.dpns_contract.clone(); - let qualified_identities = self.load_local_qualified_identities().unwrap_or_default(); - let qualified_identity = qualified_identities - .iter() - .find(|identity| identity.identity.id() == input.identity_id_input) - .expect("Expected to find the identity in qualified identities vec"); + let qualified_identity = input.qualified_identity; let entropy = Bytes32::random_with_rng(&mut rng); let preorder_document_type = dpns_contract @@ -65,10 +58,20 @@ impl AppContext { entropy.as_slice(), ); - let mut preorder_document = Document::V0(DocumentV0 { + let salt: [u8; 32] = rng.gen(); + let mut salted_domain_buffer: Vec = vec![]; + salted_domain_buffer.extend(salt); + salted_domain_buffer + .extend((convert_to_homograph_safe_chars(&input.name_input) + ".dash").as_bytes()); + let salted_domain_hash = hash_double(salted_domain_buffer); + + let preorder_document = Document::V0(DocumentV0 { id: preorder_id, - owner_id: input.identity_id_input, - properties: BTreeMap::new(), + owner_id: qualified_identity.identity.id(), + properties: BTreeMap::from([( + "saltedDomainHash".to_string(), + salted_domain_hash.into(), + )]), revision: None, created_at: None, updated_at: None, @@ -80,10 +83,37 @@ impl AppContext { updated_at_core_block_height: None, transferred_at_core_block_height: None, }); - let mut domain_document = Document::V0(DocumentV0 { + let domain_document = Document::V0(DocumentV0 { id: domain_id, - owner_id: input.identity_id_input, - properties: BTreeMap::new(), + owner_id: qualified_identity.identity.id(), + properties: BTreeMap::from([ + ("parentDomainName".to_string(), "dash".into()), + ("normalizedParentDomainName".to_string(), "dash".into()), + ("label".to_string(), input.name_input.clone().into()), + ( + "normalizedLabel".to_string(), + convert_to_homograph_safe_chars(&input.name_input).into(), + ), + ("preorderSalt".to_string(), salt.into()), + ( + "records".to_string(), + BTreeMap::from([( + "identity".to_string(), + Into::::into( + qualified_identity.identity.id(), + ), + )]) + .into(), + ), + ( + "subdomainRules".to_string(), + BTreeMap::from([( + "allowSubdomains".to_string(), + Into::::into(false), + )]) + .into(), + ), + ]), revision: None, created_at: None, updated_at: None, @@ -96,28 +126,16 @@ impl AppContext { transferred_at_core_block_height: None, }); - let salt: [u8; 32] = rng.gen(); - let mut salted_domain_buffer: Vec = vec![]; - salted_domain_buffer.extend(salt); - salted_domain_buffer - .extend((convert_to_homograph_safe_chars(&input.name_input) + ".dash").as_bytes()); - let salted_domain_hash = hash_double(salted_domain_buffer); - - preorder_document.set("saltedDomainHash", salted_domain_hash.into()); - domain_document.set("parentDomainName", "dash".into()); - domain_document.set("normalizedParentDomainName", "dash".into()); - domain_document.set("label", input.name_input.clone().into()); - domain_document.set( - "normalizedLabel", - convert_to_homograph_safe_chars(&input.name_input).into(), - ); - domain_document.set("records.identity", domain_document.owner_id().into()); - domain_document.set("subdomainRules.allowSubdomains", false.into()); - domain_document.set("preorderSalt", salt.into()); + let public_key = qualified_identity + .document_signing_key(&preorder_document_type) + .ok_or( + "Identity doesn't have an authentication key for signing document transitions" + .to_string(), + )?; let identity_contract_nonce = match sdk .get_identity_contract_nonce( - input.identity_id_input, + qualified_identity.identity.id(), dpns_contract.id(), true, Some(PutSettings { @@ -132,40 +150,6 @@ impl AppContext { Err(e) => return Err(e.to_string()), }; - // Get signer from loaded_identity - // Convert loaded_identity to SimpleSigner - let signer = { - let mut new_signer = SimpleSigner::default(); - let Identity::V0(identity_v0) = &qualified_identity.identity; - for (key_id, public_key) in &identity_v0.public_keys { - let identity_key_tuple = - (EncryptedPrivateKeyTarget::PrivateKeyOnMainIdentity, *key_id); - if let Some(private_key_bytes) = qualified_identity - .encrypted_private_keys - .get(&identity_key_tuple) - { - new_signer - .private_keys - .insert(public_key.clone(), private_key_bytes.1.clone()); - } - } - new_signer - }; - - let public_key = - match qualified_identity.identity.get_first_public_key_matching( - Purpose::AUTHENTICATION, - HashSet::from([SecurityLevel::CRITICAL]), - HashSet::from([KeyType::ECDSA_SECP256K1, KeyType::BLS12_381]), - false, - ) { - Some(key) => key, - None => return Err( - "Identity doesn't have an authentication key for signing document transitions" - .to_string(), - ), - }; - let preorder_transition = DocumentsBatchTransition::new_document_creation_transition_from_document( preorder_document.clone(), @@ -174,8 +158,8 @@ impl AppContext { public_key, identity_contract_nonce, 0, - &signer, - &platform_version, + &qualified_identity, + &sdk.version(), None, None, None, @@ -187,19 +171,11 @@ impl AppContext { domain_document.clone(), domain_document_type, entropy.0, - qualified_identity - .identity - .get_first_public_key_matching( - Purpose::AUTHENTICATION, - HashSet::from([SecurityLevel::CRITICAL]), - HashSet::from([KeyType::ECDSA_SECP256K1, KeyType::BLS12_381]), - false, - ) - .expect("expected to get a signing key"), + public_key, identity_contract_nonce + 1, 0, - &signer, - &platform_version, + &qualified_identity, + &sdk.version(), None, None, None, @@ -211,48 +187,42 @@ impl AppContext { .await .map_err(|e| e.to_string())?; - let _preorder_document = - match >::wait_for_response::< - '_, - '_, - '_, - >( - &preorder_document, - sdk, - preorder_transition, - dpns_contract.clone().into(), - ) - .await - { - Ok(document) => document, - Err(e) => { - return Err(format!("Preorder document failed to process: {e}")); - } - }; + let _preorder_document = match >::wait_for_response::<'_, '_, '_>( + &preorder_document, + sdk, + preorder_transition, + dpns_contract.clone().into(), + ) + .await + { + Ok(document) => document, + Err(e) => { + return Err(format!("Preorder document failed to process: {e}")); + } + }; domain_transition .broadcast(sdk) .await .map_err(|e| e.to_string())?; - let _domain_document = - match >::wait_for_response::< - '_, - '_, - '_, - >( - &domain_document, - sdk, - domain_transition, - dpns_contract.into(), - ) - .await - { - Ok(document) => document, - Err(e) => { - return Err(format!("Domain document failed to process: {e}")); - } - }; + let _domain_document = match >::wait_for_response::<'_, '_, '_>( + &domain_document, + sdk, + domain_transition, + dpns_contract.into(), + ) + .await + { + Ok(document) => document, + Err(e) => { + return Err(format!("Domain document failed to process: {e}")); + } + }; Ok(()) } diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index fe2a88e66..7a861180f 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -20,9 +20,9 @@ struct MasternodeInfo { #[serde(rename = "pro-tx-hash")] pro_tx_hash: String, owner: KeyInfo, - _collateral: KeyInfo, + collateral: KeyInfo, voter: KeyInfo, - _operator: OperatorInfo, + operator: OperatorInfo, } #[derive(Debug, Clone, Deserialize)] @@ -30,17 +30,17 @@ struct HPMasternodeInfo { #[serde(rename = "protx-tx-hash")] protx_tx_hash: String, owner: KeyInfo, - _collateral: KeyInfo, + collateral: KeyInfo, voter: KeyInfo, payout: KeyInfo, - _operator: OperatorInfo, + operator: OperatorInfo, #[serde(rename = "node_key")] - _node_key: Option, + node_key: Option, } #[derive(Debug, Clone, Deserialize)] struct KeyInfo { - _address: String, + address: String, #[serde(rename = "private_key")] private_key: String, } @@ -48,16 +48,16 @@ struct KeyInfo { #[derive(Debug, Clone, Deserialize)] struct OperatorInfo { #[serde(rename = "public_key")] - _public_key: String, + public_key: String, #[serde(rename = "private_key")] - _private_key: String, + private_key: String, } #[derive(Debug, Clone, Deserialize)] struct NodeKeyInfo { - _id: String, + id: String, #[serde(rename = "private_key")] - _private_key: String, + private_key: String, } #[derive(Debug, Clone, Deserialize)] @@ -68,7 +68,7 @@ struct TestnetNodes { fn load_testnet_nodes_from_yml(file_path: &str) -> Option { let file_content = fs::read_to_string(file_path).ok()?; - serde_yaml::from_str(&file_content).ok() + serde_yaml::from_str(&file_content).expect("expected proper yaml") } pub enum AddIdentityStatus { diff --git a/src/ui/identities/register_dpns_name_screen.rs b/src/ui/identities/register_dpns_name_screen.rs index 57b9f0b7a..0a4f8ec3d 100644 --- a/src/ui/identities/register_dpns_name_screen.rs +++ b/src/ui/identities/register_dpns_name_screen.rs @@ -1,5 +1,6 @@ use crate::app::AppAction; use crate::context::AppContext; +use crate::model::qualified_identity::QualifiedIdentity; use crate::platform::identity::{IdentityTask, RegisterDpnsNameInput}; use crate::platform::BackendTask; use crate::ui::components::top_panel::add_top_panel; @@ -7,9 +8,7 @@ use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::TimestampMillis; use dash_sdk::dpp::platform_value::string_encoding::Encoding; -use dash_sdk::platform::Identifier; use eframe::egui::Context; -use itertools::Itertools; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; @@ -21,8 +20,8 @@ pub enum RegisterDpnsNameStatus { } pub struct RegisterDpnsNameScreen { - qualified_identity_ids: Vec, - identity_id_input: Identifier, + qualified_identities: Vec, + selected_qualified_identity: Option, name_input: String, register_dpns_name_status: RegisterDpnsNameStatus, pub app_context: Arc, @@ -30,15 +29,13 @@ pub struct RegisterDpnsNameScreen { impl RegisterDpnsNameScreen { pub fn new(app_context: &Arc) -> Self { - let qualified_identity_ids = app_context + let qualified_identities = app_context .load_local_qualified_identities() - .unwrap_or_default() - .iter() - .map(|identity| identity.identity.id()) - .collect_vec(); + .unwrap_or_default(); + let selected_qualified_identity = qualified_identities.first().cloned(); Self { - qualified_identity_ids, - identity_id_input: Identifier::random(), + qualified_identities, + selected_qualified_identity, name_input: String::new(), register_dpns_name_status: RegisterDpnsNameStatus::NotStarted, app_context: app_context.clone(), @@ -49,32 +46,46 @@ impl RegisterDpnsNameScreen { ui.horizontal(|ui| { ui.label("Identity ID:"); - // Create a ComboBox for selecting Identity IDs + // Create a ComboBox for selecting a Qualified Identity egui::ComboBox::from_label("") - .selected_text(format!( - "{:?}", - self.qualified_identity_ids - .first() - .expect("Expected to have at least one qualified identity") - .to_string(Encoding::Base58) - )) + .selected_text( + self.selected_qualified_identity + .as_ref() + .map(|qi| { + qi.alias + .as_ref() + .unwrap_or(&qi.identity.id().to_string(Encoding::Base58)) + .clone() + }) + .unwrap_or_else(|| "Select an identity".to_string()), + ) .show_ui(ui, |ui| { - // Loop through the qualified_identity_ids and display each as selectable - for id in &self.qualified_identity_ids { - // Display each Identifier as a selectable item - ui.selectable_value( - &mut self.identity_id_input, - *id, - id.to_string(Encoding::Base58), - ); + // Loop through the qualified identities and display each as selectable + for qualified_identity in &self.qualified_identities { + let id = qualified_identity.identity.id(); // Extract the Identifier + + // Display each QualifiedIdentity as a selectable item + if ui + .selectable_value( + &mut self.selected_qualified_identity, + Some(qualified_identity.clone()), + id.to_string(Encoding::Base58), + ) + .clicked() + { + self.selected_qualified_identity = Some(qualified_identity.clone()); + } } }); }); } fn register_dpns_name_clicked(&mut self) -> AppAction { + let Some(qualified_identity) = self.selected_qualified_identity.as_ref() else { + return AppAction::None; + }; let dpns_name_input = RegisterDpnsNameInput { - identity_id_input: self.identity_id_input, + qualified_identity: qualified_identity.clone(), name_input: self.name_input.trim().to_string(), }; @@ -108,7 +119,7 @@ impl ScreenLike for RegisterDpnsNameScreen { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Register DPNS Name"); - if self.qualified_identity_ids.is_empty() { + if self.qualified_identities.is_empty() { ui.label("No qualified identities available to register a DPNS name."); return; }