diff --git a/Cargo.lock b/Cargo.lock index 7010b06375..4790ae8de1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5976,6 +5976,7 @@ name = "tari_common_types" version = "1.0.0-pre.13" dependencies = [ "base64 0.21.5", + "bitflags 2.4.1", "blake2", "borsh", "chacha20poly1305", diff --git a/applications/minotari_app_utilities/src/utilities.rs b/applications/minotari_app_utilities/src/utilities.rs index 9201a17a5e..0142b8c93c 100644 --- a/applications/minotari_app_utilities/src/utilities.rs +++ b/applications/minotari_app_utilities/src/utilities.rs @@ -83,8 +83,6 @@ impl FromStr for UniPublicKey { Ok(Self(PublicKey::from(&emoji_id))) } else if let Ok(public_key) = PublicKey::from_hex(key) { Ok(Self(public_key)) - } else if let Ok(tari_address) = TariAddress::from_hex(key) { - Ok(Self(tari_address.public_key().clone())) } else { Err(UniIdError::UnknownIdType) } @@ -136,7 +134,7 @@ impl TryFrom for PublicKey { fn try_from(id: UniNodeId) -> Result { match id { UniNodeId::PublicKey(public_key) => Ok(public_key), - UniNodeId::TariAddress(tari_address) => Ok(tari_address.public_key().clone()), + UniNodeId::TariAddress(tari_address) => Ok(tari_address.public_spend_key().clone()), UniNodeId::NodeId(_) => Err(UniIdError::Nonconvertible), } } @@ -147,7 +145,7 @@ impl From for NodeId { match id { UniNodeId::PublicKey(public_key) => NodeId::from_public_key(&public_key), UniNodeId::NodeId(node_id) => node_id, - UniNodeId::TariAddress(tari_address) => NodeId::from_public_key(tari_address.public_key()), + UniNodeId::TariAddress(tari_address) => NodeId::from_public_key(tari_address.public_spend_key()), } } } diff --git a/applications/minotari_console_wallet/src/grpc/mod.rs b/applications/minotari_console_wallet/src/grpc/mod.rs index ecffc7ce38..b56951b623 100644 --- a/applications/minotari_console_wallet/src/grpc/mod.rs +++ b/applications/minotari_console_wallet/src/grpc/mod.rs @@ -23,8 +23,8 @@ pub fn convert_to_transaction_event(event: String, source: TransactionWrapper) - TransactionWrapper::Completed(completed) => TransactionEvent { event, tx_id: completed.tx_id.to_string(), - source_address: completed.source_address.to_bytes().to_vec(), - dest_address: completed.destination_address.to_bytes().to_vec(), + source_address: completed.source_address.to_vec(), + dest_address: completed.destination_address.to_vec(), status: completed.status.to_string(), direction: completed.direction.to_string(), amount: completed.amount.as_u64(), @@ -34,7 +34,7 @@ pub fn convert_to_transaction_event(event: String, source: TransactionWrapper) - event, tx_id: outbound.tx_id.to_string(), source_address: vec![], - dest_address: outbound.destination_address.to_bytes().to_vec(), + dest_address: outbound.destination_address.to_vec(), status: outbound.status.to_string(), direction: "outbound".to_string(), amount: outbound.amount.as_u64(), @@ -43,7 +43,7 @@ pub fn convert_to_transaction_event(event: String, source: TransactionWrapper) - TransactionWrapper::Inbound(inbound) => TransactionEvent { event, tx_id: inbound.tx_id.to_string(), - source_address: inbound.source_address.to_bytes().to_vec(), + source_address: inbound.source_address.to_vec(), dest_address: vec![], status: inbound.status.to_string(), direction: "inbound".to_string(), diff --git a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs index 563bb004e7..367417a591 100644 --- a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -226,11 +226,13 @@ impl wallet_server::Wallet for WalletGrpcServer { } async fn get_address(&self, _: Request) -> Result, Status> { - let network = self.wallet.network.as_network(); - let pk = self.wallet.comms.node_identity().public_key().clone(); - let address = TariAddress::new(pk, network); + let address = self + .wallet + .get_wallet_address() + .await + .map_err(|e| Status::internal(format!("{:?}", e)))?; Ok(Response::new(GetAddressResponse { - address: address.to_bytes().to_vec(), + address: address.to_vec(), })) } @@ -650,10 +652,11 @@ impl wallet_server::Wallet for WalletGrpcServer { .await .map(|tx| tx.into_iter()) .map_err(|err| Status::unknown(err.to_string()))?; - - let wallet_pk = self.wallet.comms.node_identity_ref().public_key(); - let wallet_network = self.wallet.network.as_network(); - let wallet_address = TariAddress::new(wallet_pk.clone(), wallet_network); + let wallet_address = self + .wallet + .get_wallet_address() + .await + .map_err(|e| Status::internal(format!("{:?}", e)))?; let transactions = transactions .map(|(tx_id, tx)| match tx { Some(tx) => convert_wallet_transaction_into_transaction_info(tx, &wallet_address), @@ -755,8 +758,8 @@ impl wallet_server::Wallet for WalletGrpcServer { let response = GetCompletedTransactionsResponse { transaction: Some(TransactionInfo { tx_id: txn.tx_id.into(), - source_address: txn.source_address.to_bytes().to_vec(), - dest_address: txn.destination_address.to_bytes().to_vec(), + source_address: txn.source_address.to_vec(), + dest_address: txn.destination_address.to_vec(), status: TransactionStatus::from(txn.status.clone()) as i32, amount: txn.amount.into(), is_cancelled: txn.cancelled.is_some(), @@ -1097,8 +1100,8 @@ fn convert_wallet_transaction_into_transaction_info( match tx { PendingInbound(tx) => TransactionInfo { tx_id: tx.tx_id.into(), - source_address: tx.source_address.to_bytes().to_vec(), - dest_address: wallet_address.to_bytes().to_vec(), + source_address: tx.source_address.to_vec(), + dest_address: wallet_address.to_vec(), status: TransactionStatus::from(tx.status) as i32, amount: tx.amount.into(), is_cancelled: tx.cancelled, @@ -1110,8 +1113,8 @@ fn convert_wallet_transaction_into_transaction_info( }, PendingOutbound(tx) => TransactionInfo { tx_id: tx.tx_id.into(), - source_address: wallet_address.to_bytes().to_vec(), - dest_address: tx.destination_address.to_bytes().to_vec(), + source_address: wallet_address.to_vec(), + dest_address: tx.destination_address.to_vec(), status: TransactionStatus::from(tx.status) as i32, amount: tx.amount.into(), is_cancelled: tx.cancelled, @@ -1123,8 +1126,8 @@ fn convert_wallet_transaction_into_transaction_info( }, Completed(tx) => TransactionInfo { tx_id: tx.tx_id.into(), - source_address: tx.source_address.to_bytes().to_vec(), - dest_address: tx.destination_address.to_bytes().to_vec(), + source_address: tx.source_address.to_vec(), + dest_address: tx.destination_address.to_vec(), status: TransactionStatus::from(tx.status) as i32, amount: tx.amount.into(), is_cancelled: tx.cancelled.is_some(), diff --git a/applications/minotari_console_wallet/src/recovery.rs b/applications/minotari_console_wallet/src/recovery.rs index 9ca3969a23..7938009a7c 100644 --- a/applications/minotari_console_wallet/src/recovery.rs +++ b/applications/minotari_console_wallet/src/recovery.rs @@ -22,11 +22,14 @@ #![allow(dead_code, unused)] +use std::ptr; + use chrono::offset::Local; use futures::FutureExt; use log::*; use minotari_wallet::{ connectivity_service::WalletConnectivityHandle, + error::WalletError, storage::sqlite_db::wallet::WalletSqliteDatabase, utxo_scanner_service::{handle::UtxoScannerEvent, service::UtxoScannerService}, WalletSqlite, @@ -37,7 +40,7 @@ use tari_crypto::tari_utilities::Hidden; use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic, SeedWords}; use tari_shutdown::Shutdown; use tari_utilities::hex::Hex; -use tokio::sync::broadcast; +use tokio::{runtime::Runtime, sync::broadcast}; use zeroize::{Zeroize, Zeroizing}; use crate::wallet_modes::PeerConfig; @@ -122,7 +125,7 @@ pub async fn wallet_recovery( .with_peers(peer_public_keys) // Do not make this a small number as wallet recovery needs to be resilient .with_retry_limit(retry_limit) - .build_with_wallet(wallet, shutdown_signal); + .build_with_wallet(wallet, shutdown_signal).await.map_err(|e| ExitError::new(ExitCode::RecoveryError, e))?; let mut event_stream = recovery_task.get_event_receiver(); diff --git a/applications/minotari_console_wallet/src/ui/app.rs b/applications/minotari_console_wallet/src/ui/app.rs index 7cd1c19e74..9c124e5137 100644 --- a/applications/minotari_console_wallet/src/ui/app.rs +++ b/applications/minotari_console_wallet/src/ui/app.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use minotari_wallet::{util::wallet_identity::WalletIdentity, WalletConfig, WalletSqlite}; +use tari_common::exit_codes::ExitError; use tari_comms::peer_manager::Peer; use tokio::runtime::Handle; use tui::{ @@ -69,15 +70,16 @@ pub struct App { } impl App { - pub fn new( + pub async fn new( title: String, wallet: WalletSqlite, wallet_config: WalletConfig, base_node_selected: Peer, base_node_config: PeerConfig, notifier: Notifier, - ) -> Self { - let wallet_id = WalletIdentity::new(wallet.comms.node_identity(), wallet.network.as_network()); + ) -> Result { + let wallet_address = wallet.get_wallet_address().await?; + let wallet_id = WalletIdentity::new(wallet.comms.node_identity(), wallet_address); let app_state = AppState::new( &wallet_id, wallet, @@ -101,7 +103,7 @@ impl App { let base_node_status = BaseNode::new(); let menu = Menu::new(); - Self { + Ok(Self { title, should_quit: false, app_state, @@ -109,7 +111,7 @@ impl App { base_node_status, menu, notifier, - } + }) } pub fn on_control_key(&mut self, c: char) { diff --git a/applications/minotari_console_wallet/src/ui/mod.rs b/applications/minotari_console_wallet/src/ui/mod.rs index 5413c7ee22..176685226f 100644 --- a/applications/minotari_console_wallet/src/ui/mod.rs +++ b/applications/minotari_console_wallet/src/ui/mod.rs @@ -49,7 +49,7 @@ use ui_error::UiError; use crate::utils::events::{Event, EventStream}; -pub const MAX_WIDTH: u16 = 133; +pub const MAX_WIDTH: u16 = 157; pub fn run(app: App>) -> Result<(), ExitError> { let mut app = app; diff --git a/applications/minotari_console_wallet/src/ui/state/app_state.rs b/applications/minotari_console_wallet/src/ui/state/app_state.rs index efc0ff3145..1bc194bc20 100644 --- a/applications/minotari_console_wallet/src/ui/state/app_state.rs +++ b/applications/minotari_console_wallet/src/ui/state/app_state.rs @@ -939,11 +939,11 @@ impl AppStateInner { } pub async fn refresh_network_id(&mut self) -> Result<(), UiError> { - let wallet_id = WalletIdentity::new(self.wallet.comms.node_identity(), self.wallet.network.as_network()); + let wallet_id = self.wallet.get_wallet_id().await?; let eid = wallet_id.address.to_emoji_string(); let qr_link = format!( "tari://{}/transactions/send?tariAddress={}", - wallet_id.network, + wallet_id.network(), wallet_id.address.to_hex() ); let code = QrCode::new(qr_link).unwrap(); @@ -1278,7 +1278,7 @@ impl AppStateData { let eid = wallet_identity.address.to_emoji_string(); let qr_link = format!( "tari://{}/transactions/send?tariAddress={}", - wallet_identity.network, + wallet_identity.network(), wallet_identity.address.to_hex() ); let code = QrCode::new(qr_link).unwrap(); diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index 70fe202652..f4aed9ecf2 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -322,14 +322,14 @@ pub fn tui_mode( return Err(ExitError::new(ExitCode::WalletError, "Could not select a base node")); } - let app = App::>::new( + let app = handle.block_on(App::>::new( "Minotari Wallet".into(), wallet, config.clone(), base_node_selected, base_node_config.clone(), notifier, - ); + ))?; info!(target: LOG_TARGET, "Starting app"); @@ -490,7 +490,8 @@ mod test { discover-peer f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 - send-minotari --message Our_secret! 125T 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d615e + send-minotari --message Our_secret! 125T \ + 2603fed9cf87097105913096da423ae4e3096e44a172185742ce5bc00d27016cd81118 burn-minotari --message Ups_these_funds_will_be_burned! 100T @@ -498,7 +499,7 @@ mod test { make-it-rain --duration 100 --transactions-per-second 10 --start-amount 0.009200T --increase-amount 0T \ --start-time now --message Stressing_it_a_bit...!_(from_Feeling-a-bit-Generous) \ - 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d615e + 2603fed9cf87097105913096da423ae4e3096e44a172185742ce5bc00d27016cd81118 export-tx 123456789 --output-file pie.txt diff --git a/base_layer/chat_ffi/chat.h b/base_layer/chat_ffi/chat.h index a98692ed7d..fdf383ee28 100644 --- a/base_layer/chat_ffi/chat.h +++ b/base_layer/chat_ffi/chat.h @@ -67,11 +67,12 @@ extern "C" { * The ```destroy_chat_client``` method must be called when finished with a ClientFFI to prevent a memory leak */ struct ChatClient *create_chat_client(struct ApplicationConfig *config, - int *error_out, CallbackContactStatusChange callback_contact_status_change, CallbackMessageReceived callback_message_received, CallbackDeliveryConfirmationReceived callback_delivery_confirmation_received, - CallbackReadConfirmationReceived callback_read_confirmation_received); + CallbackReadConfirmationReceived callback_read_confirmation_received, + struct TariAddress *tari_address, + int *error_out); /** * Side loads a chat client @@ -98,11 +99,12 @@ struct ChatClient *create_chat_client(struct ApplicationConfig *config, */ struct ChatClient *sideload_chat_client(struct ApplicationConfig *config, struct ContactsServiceHandle *contacts_handle, - int *error_out, CallbackContactStatusChange callback_contact_status_change, CallbackMessageReceived callback_message_received, CallbackDeliveryConfirmationReceived callback_delivery_confirmation_received, - CallbackReadConfirmationReceived callback_read_confirmation_received); + CallbackReadConfirmationReceived callback_read_confirmation_received, + struct TariAddress *tari_address, + int *error_out); /** * Frees memory for a ChatClient diff --git a/base_layer/chat_ffi/src/contacts_liveness_data.rs b/base_layer/chat_ffi/src/contacts_liveness_data.rs index 45f98e87c9..18e0c55397 100644 --- a/base_layer/chat_ffi/src/contacts_liveness_data.rs +++ b/base_layer/chat_ffi/src/contacts_liveness_data.rs @@ -157,7 +157,7 @@ mod test { #[test] fn test_reading_address() { let address = - TariAddress::from_hex("0c017c5cd01385f34ac065e3b05948326dc55d2494f120c6f459a07389011b4ec1").unwrap(); + TariAddress::from_hex("2603fed9cf87097105913096da423ae4e3096e44a172185742ce5bc00d27016cd81118").unwrap(); let liveness = ContactsLivenessData::new( address.clone(), Default::default(), @@ -172,7 +172,7 @@ mod test { unsafe { let address_ptr = read_liveness_data_address(liveness_ptr, error_out); - assert_eq!(address.to_bytes(), (*address_ptr).to_bytes()); + assert_eq!(address.to_vec(), (*address_ptr).to_vec()); destroy_contacts_liveness_data(liveness_ptr); destroy_tari_address(address_ptr); diff --git a/base_layer/chat_ffi/src/conversationalists.rs b/base_layer/chat_ffi/src/conversationalists.rs index c012956abb..30d02b18e4 100644 --- a/base_layer/chat_ffi/src/conversationalists.rs +++ b/base_layer/chat_ffi/src/conversationalists.rs @@ -177,7 +177,7 @@ mod test { #[test] fn test_retrieving_conversationalists_from_vector() { let (_, pk) = PublicKey::random_keypair(&mut OsRng); - let a = TariAddress::from_public_key(&pk, Network::LocalNet); + let a = TariAddress::new_single_address_with_interactive_only(pk, Network::LocalNet); let conversationalists = ConversationalistsVector(vec![TariAddress::default(), TariAddress::default(), a.clone()]); diff --git a/base_layer/chat_ffi/src/lib.rs b/base_layer/chat_ffi/src/lib.rs index ba113f2026..346d6e125a 100644 --- a/base_layer/chat_ffi/src/lib.rs +++ b/base_layer/chat_ffi/src/lib.rs @@ -29,6 +29,7 @@ use libc::c_int; use log::info; use minotari_app_utilities::identity_management::setup_node_identity; use tari_chat_client::{config::ApplicationConfig, networking::PeerFeatures, ChatClient as ChatClientTrait, Client}; +use tari_common_types::tari_address::TariAddress; use tari_contacts::contacts_service::handle::ContactsServiceHandle; use tokio::runtime::Runtime; @@ -93,11 +94,12 @@ pub struct ChatClient { #[no_mangle] pub unsafe extern "C" fn create_chat_client( config: *mut ApplicationConfig, - error_out: *mut c_int, callback_contact_status_change: CallbackContactStatusChange, callback_message_received: CallbackMessageReceived, callback_delivery_confirmation_received: CallbackDeliveryConfirmationReceived, callback_read_confirmation_received: CallbackReadConfirmationReceived, + tari_address: *mut TariAddress, + error_out: *mut c_int, ) -> *mut ChatClient { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -148,7 +150,8 @@ pub unsafe extern "C" fn create_chat_client( }, }; let user_agent = format!("tari/chat_ffi/{}", env!("CARGO_PKG_VERSION")); - let mut client = Client::new(identity, (*config).clone(), user_agent); + let tari_address = (*tari_address).clone(); + let mut client = Client::new(identity, tari_address, (*config).clone(), user_agent); if let Ok(()) = runtime.block_on(client.initialize()) { let contacts_handler = match client.contacts.clone() { @@ -205,11 +208,12 @@ pub unsafe extern "C" fn create_chat_client( pub unsafe extern "C" fn sideload_chat_client( config: *mut ApplicationConfig, contacts_handle: *mut ContactsServiceHandle, - error_out: *mut c_int, callback_contact_status_change: CallbackContactStatusChange, callback_message_received: CallbackMessageReceived, callback_delivery_confirmation_received: CallbackDeliveryConfirmationReceived, callback_read_confirmation_received: CallbackReadConfirmationReceived, + tari_address: *mut TariAddress, + error_out: *mut c_int, ) -> *mut ChatClient { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -248,7 +252,8 @@ pub unsafe extern "C" fn sideload_chat_client( return ptr::null_mut(); } let user_agent = format!("tari/chat_ffi/{}", env!("CARGO_PKG_VERSION")); - let mut client = Client::sideload((*config).clone(), (*contacts_handle).clone(), user_agent); + let tari_address = (*tari_address).clone(); + let mut client = Client::sideload((*config).clone(), (*contacts_handle).clone(), user_agent, tari_address); if let Ok(()) = runtime.block_on(client.initialize()) { let mut callback_handler = CallbackHandler::new( (*contacts_handle).clone(), diff --git a/base_layer/chat_ffi/src/message.rs b/base_layer/chat_ffi/src/message.rs index ed2f9fad8e..a8bb1993e3 100644 --- a/base_layer/chat_ffi/src/message.rs +++ b/base_layer/chat_ffi/src/message.rs @@ -600,9 +600,9 @@ mod test { #[test] fn test_reading_message_address() { let receiver_address = - TariAddress::from_hex("0c017c5cd01385f34ac065e3b05948326dc55d2494f120c6f459a07389011b4ec1").unwrap(); + TariAddress::from_hex("2602742c39084e62565e1416f9f97ff34bd91fc3ccd35bb7e6cf916cb757066c816966").unwrap(); let sender_address = - TariAddress::from_hex("3e596f98f6904f0fc1c8685e2274bd8b2c445d5dac284a9398d09a0e9a760436d0").unwrap(); + TariAddress::from_hex("2602764460f2fff434446cab6e03a5ea2a4c1dc4984c1749a4af8371ceecd8da1d0c01").unwrap(); let message = MessageBuilder::new() .receiver_address(receiver_address.clone()) .sender_address(sender_address.clone()) @@ -614,7 +614,7 @@ mod test { unsafe { let address_ptr = read_chat_message_sender_address(message_ptr, error_out); - assert_eq!(sender_address.to_bytes(), (*address_ptr).to_bytes()); + assert_eq!(sender_address.to_vec(), (*address_ptr).to_vec()); destroy_chat_message(message_ptr); destroy_tari_address(address_ptr); diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index b77e1114c6..c1dab78146 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -13,6 +13,7 @@ tari_utilities = { version = "0.7" } tari_common = { path = "../../common", version = "1.0.0-pre.13" } chacha20poly1305 = "0.10.1" +bitflags = { version = "2.4", features = ["serde"] } borsh = "1.2" digest = "0.10" newtype-ops = "0.1" diff --git a/base_layer/common_types/src/tari_address.rs b/base_layer/common_types/src/tari_address.rs deleted file mode 100644 index 93b81b0cf2..0000000000 --- a/base_layer/common_types/src/tari_address.rs +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright 2020. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use std::{ - convert::TryFrom, - fmt::{Display, Error, Formatter}, - str::FromStr, -}; - -use serde::{Deserialize, Serialize}; -use tari_common::configuration::Network; -use tari_crypto::tari_utilities::ByteArray; -use tari_utilities::hex::{from_hex, Hex}; -use thiserror::Error; - -use crate::{ - dammsum::{compute_checksum, validate_checksum}, - emoji::{EMOJI, REVERSE_EMOJI}, - types::PublicKey, -}; - -const INTERNAL_SIZE: usize = 33; // number of bytes used for the internal representation - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct TariAddress { - network: Network, - public_key: PublicKey, -} - -#[derive(Debug, Error, PartialEq)] -pub enum TariAddressError { - #[error("Invalid size")] - InvalidSize, - #[error("Invalid network or checksum")] - InvalidNetworkOrChecksum, - #[error("Invalid emoji character")] - InvalidEmoji, - #[error("Cannot recover public key")] - CannotRecoverPublicKey, -} - -impl TariAddress { - /// Creates a new Tari Address from the provided public key and network while using the current version - pub fn new(public_key: PublicKey, network: Network) -> Self { - TariAddress { network, public_key } - } - - /// helper function to convert emojis to u8 - fn emoji_to_bytes(emoji: &str) -> Result, TariAddressError> { - // The string must be the correct size, including the checksum - if emoji.chars().count() != INTERNAL_SIZE { - return Err(TariAddressError::InvalidSize); - } - - // Convert the emoji string to a byte array - let mut bytes = Vec::::with_capacity(INTERNAL_SIZE); - for c in emoji.chars() { - if let Some(i) = REVERSE_EMOJI.get(&c) { - bytes.push(*i); - } else { - return Err(TariAddressError::InvalidEmoji); - } - } - Ok(bytes) - } - - /// Construct an TariAddress from an emoji string with checksum and network - pub fn from_emoji_string_with_network(emoji: &str, network: Network) -> Result { - let bytes = TariAddress::emoji_to_bytes(emoji)?; - - TariAddress::from_bytes_with_network(&bytes, network) - } - - /// Construct an TariAddress from an emoji string with checksum trying to calculate the network - pub fn from_emoji_string(emoji: &str) -> Result { - let bytes = TariAddress::emoji_to_bytes(emoji)?; - - TariAddress::from_bytes(&bytes) - } - - /// Construct an Tari Address from a public key - pub fn from_public_key(public_key: &PublicKey, network: Network) -> Self { - Self { - network, - public_key: public_key.clone(), - } - } - - /// Gets the network from the Tari Address - pub fn network(&self) -> Network { - self.network - } - - /// Convert Tari Address to an emoji string with checksum - pub fn to_emoji_string(&self) -> String { - // Convert the public key to bytes and compute the checksum - let bytes = self.to_bytes(); - bytes.iter().map(|b| EMOJI[*b as usize]).collect::() - } - - /// Return the public key of an Tari Address - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } - - /// Construct Tari Address from bytes with network - pub fn from_bytes_with_network(bytes: &[u8], network: Network) -> Result - where Self: Sized { - if bytes.len() != INTERNAL_SIZE { - return Err(TariAddressError::InvalidSize); - } - let mut fixed_data = bytes.to_vec(); - fixed_data[32] ^= network.as_byte(); - // Assert the checksum is valid - if validate_checksum(&fixed_data).is_err() { - return Err(TariAddressError::InvalidNetworkOrChecksum); - } - let key = - PublicKey::from_canonical_bytes(&bytes[0..32]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; - Ok(TariAddress { - public_key: key, - network, - }) - } - - /// Construct Tari Address from bytes and try to calculate the network - pub fn from_bytes(bytes: &[u8]) -> Result - where Self: Sized { - if bytes.len() != INTERNAL_SIZE { - return Err(TariAddressError::InvalidSize); - } - let checksum = compute_checksum(&bytes[0..32]); - // if the network is a valid network number, we can assume that the checksum as valid - let network = - Network::try_from(checksum ^ bytes[32]).map_err(|_| TariAddressError::InvalidNetworkOrChecksum)?; - let key = - PublicKey::from_canonical_bytes(&bytes[0..32]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; - Ok(TariAddress { - public_key: key, - network, - }) - } - - /// Convert Tari Address to bytes - pub fn to_bytes(&self) -> [u8; INTERNAL_SIZE] { - let mut buf = [0u8; INTERNAL_SIZE]; - buf[0..32].copy_from_slice(self.public_key.as_bytes()); - let checksum = compute_checksum(&buf[0..32]); - buf[32] = self.network.as_byte() ^ checksum; - buf - } - - /// Construct Tari Address from hex with network - pub fn from_hex_with_network(hex_str: &str, network: Network) -> Result { - let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; - TariAddress::from_bytes_with_network(buf.as_slice(), network) - } - - /// Construct Tari Address from hex and try to calculate the network - pub fn from_hex(hex_str: &str) -> Result { - let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; - TariAddress::from_bytes(buf.as_slice()) - } - - /// Convert Tari Address to bytes - pub fn to_hex(&self) -> String { - let buf = self.to_bytes(); - buf.to_hex() - } -} - -impl FromStr for TariAddress { - type Err = TariAddressError; - - fn from_str(key: &str) -> Result { - if let Ok(address) = TariAddress::from_emoji_string(&key.trim().replace('|', "")) { - Ok(address) - } else if let Ok(address) = TariAddress::from_hex(key) { - Ok(address) - } else { - Err(TariAddressError::CannotRecoverPublicKey) - } - } -} - -impl Display for TariAddress { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> { - fmt.write_str(&self.to_emoji_string()) - } -} - -#[cfg(test)] -mod test { - use tari_crypto::keys::{PublicKey, SecretKey}; - - use super::*; - use crate::types::PrivateKey; - - #[test] - /// Test valid tari address - fn valid_emoji_id() { - // Generate random public key - let mut rng = rand::thread_rng(); - let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); - - // Generate an emoji ID from the public key and ensure we recover it - let emoji_id_from_public_key = TariAddress::from_public_key(&public_key, Network::Esmeralda); - assert_eq!(emoji_id_from_public_key.public_key(), &public_key); - - // Check the size of the corresponding emoji string - let emoji_string = emoji_id_from_public_key.to_emoji_string(); - assert_eq!(emoji_string.chars().count(), INTERNAL_SIZE); - - // Generate an emoji ID from the emoji string and ensure we recover it - let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); - assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); - - // Return to the original public key for good measure - assert_eq!(emoji_id_from_emoji_string.public_key(), &public_key); - } - - #[test] - /// Test encoding for tari address - fn encoding() { - // Generate random public key - let mut rng = rand::thread_rng(); - let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); - - // Generate an emoji ID from the public key and ensure we recover it - let address = TariAddress::from_public_key(&public_key, Network::Esmeralda); - - let buff = address.to_bytes(); - let hex = address.to_hex(); - - let address_buff = TariAddress::from_bytes(&buff); - assert_eq!(address_buff, Ok(address.clone())); - - let address_buff = TariAddress::from_bytes_with_network(&buff, Network::Esmeralda); - assert_eq!(address_buff, Ok(address.clone())); - - let address_hex = TariAddress::from_hex(&hex); - assert_eq!(address_hex, Ok(address.clone())); - - let address_hex = TariAddress::from_hex_with_network(&hex, Network::Esmeralda); - assert_eq!(address_hex, Ok(address)); - } - - #[test] - /// Test invalid size - fn invalid_size() { - // This emoji string is too short to be a valid emoji ID - let emoji_string = "๐ŸŒด๐Ÿฆ€๐Ÿ”Œ๐Ÿ“Œ๐Ÿš‘๐ŸŒฐ๐ŸŽ“๐ŸŒด๐ŸŠ๐ŸŒ๐Ÿ”’๐Ÿ’ก๐Ÿœ๐Ÿ“œ๐Ÿ‘›๐Ÿต๐Ÿ‘›๐Ÿฝ๐ŸŽ‚๐Ÿป๐Ÿฆ‹๐Ÿ“๐Ÿ‘ถ๐Ÿญ๐Ÿผ๐Ÿ€๐ŸŽช๐Ÿ’”๐Ÿ’ต๐Ÿฅ‘๐Ÿ”‹๐ŸŽ’"; - assert_eq!( - TariAddress::from_emoji_string(emoji_string), - Err(TariAddressError::InvalidSize) - ); - // This emoji string is too long to be a valid emoji ID - let emoji_string = "๐ŸŒด๐Ÿฆ€๐Ÿ”Œ๐Ÿ“Œ๐Ÿš‘๐ŸŒฐ๐ŸŽ“๐ŸŒด๐ŸŠ๐ŸŒ๐Ÿ”’๐Ÿ’ก๐Ÿœ๐Ÿ“œ๐Ÿ‘›๐Ÿต๐Ÿ‘›๐Ÿฝ๐ŸŽ‚๐Ÿป๐Ÿฆ‹๐Ÿ“๐Ÿ‘ถ๐Ÿญ๐Ÿผ๐Ÿ€๐ŸŽช๐Ÿ’”๐Ÿ’ต๐Ÿฅ‘๐Ÿ”‹๐ŸŽ’๐ŸŽ’๐ŸŽ’๐ŸŽ’๐ŸŽ’"; - assert_eq!( - TariAddress::from_emoji_string(emoji_string), - Err(TariAddressError::InvalidSize) - ); - } - - #[test] - /// Test invalid emoji - fn invalid_emoji() { - // This emoji string contains an invalid emoji character - let emoji_string = "๐ŸŒด๐Ÿฆ€๐Ÿ”Œ๐Ÿ“Œ๐Ÿš‘๐ŸŒฐ๐ŸŽ“๐ŸŒด๐ŸŠ๐ŸŒ๐Ÿ”’๐Ÿ’ก๐Ÿœ๐Ÿ“œ๐Ÿ‘›๐Ÿต๐Ÿ‘›๐Ÿฝ๐ŸŽ‚๐Ÿป๐Ÿฆ‹๐Ÿ“๐Ÿ‘ถ๐Ÿญ๐Ÿผ๐Ÿ€๐ŸŽช๐Ÿ’”๐Ÿ’ต๐Ÿฅ‘๐Ÿ”‹๐ŸŽ’๐ŸŽ…"; - assert_eq!( - TariAddress::from_emoji_string(emoji_string), - Err(TariAddressError::InvalidEmoji) - ); - } - - #[test] - /// Test invalid checksum - fn invalid_checksum() { - // This emoji string contains an invalid checksum - let emoji_string = "๐ŸŒด๐Ÿฆ€๐Ÿ”Œ๐Ÿ“Œ๐Ÿš‘๐ŸŒฐ๐ŸŽ“๐ŸŒด๐ŸŠ๐ŸŒ๐Ÿ”’๐Ÿ’ก๐Ÿœ๐Ÿ“œ๐Ÿ‘›๐Ÿต๐Ÿ‘›๐Ÿฝ๐ŸŽ‚๐Ÿป๐Ÿฆ‹๐Ÿ“๐Ÿ‘ถ๐Ÿญ๐Ÿผ๐Ÿ€๐ŸŽช๐Ÿ’”๐Ÿ’ต๐Ÿฅ‘๐Ÿ”‹๐ŸŽ’๐ŸŽ’"; - assert_eq!( - TariAddress::from_emoji_string(emoji_string), - Err(TariAddressError::InvalidNetworkOrChecksum) - ); - } - - #[test] - /// Test invalid network - fn invalid_network() { - let mut rng = rand::thread_rng(); - let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); - - // Generate an address using a valid network and ensure it's not valid on another network - let address = TariAddress::from_public_key(&public_key, Network::Esmeralda); - assert_eq!( - TariAddress::from_bytes_with_network(&address.to_bytes(), Network::Igor), - Err(TariAddressError::InvalidNetworkOrChecksum) - ); - - // Generate an address using a valid network, mutate it, and ensure it's not valid on the same network - let mut address_bytes = address.to_bytes(); - address_bytes[32] ^= 0xFF; - assert_eq!( - TariAddress::from_bytes_with_network(&address_bytes, Network::Esmeralda), - Err(TariAddressError::InvalidNetworkOrChecksum) - ); - } - - #[test] - /// Test invalid public key - fn invalid_public_key() { - let mut bytes = [0; 33].to_vec(); - bytes[0] = 1; - let checksum = compute_checksum(&bytes[0..32]); - bytes[32] = Network::Esmeralda.as_byte() ^ checksum; - let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); - - // This emoji string contains an invalid checksum - assert_eq!( - TariAddress::from_emoji_string(&emoji_string), - Err(TariAddressError::CannotRecoverPublicKey) - ); - } -} diff --git a/base_layer/common_types/src/tari_address/dual_address.rs b/base_layer/common_types/src/tari_address/dual_address.rs new file mode 100644 index 0000000000..9b6ac9602b --- /dev/null +++ b/base_layer/common_types/src/tari_address/dual_address.rs @@ -0,0 +1,435 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::convert::TryFrom; + +use serde::{Deserialize, Serialize}; +use tari_common::configuration::Network; +use tari_crypto::tari_utilities::ByteArray; +use tari_utilities::hex::{from_hex, Hex}; + +use crate::{ + dammsum::{compute_checksum, validate_checksum}, + emoji::{EMOJI, REVERSE_EMOJI}, + tari_address::{TariAddressError, TariAddressFeatures, INTERNAL_DUAL_SIZE}, + types::PublicKey, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct DualAddress { + network: Network, + features: TariAddressFeatures, + public_view_key: PublicKey, + public_spend_key: PublicKey, +} + +impl DualAddress { + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new( + view_key: PublicKey, + spend_key: PublicKey, + network: Network, + features: TariAddressFeatures, + ) -> DualAddress { + Self { + network, + features, + public_view_key: view_key, + public_spend_key: spend_key, + } + } + + /// Creates a new Tari Address from the provided public keys and network while using the default features + pub fn new_with_default_features(view_key: PublicKey, spend_key: PublicKey, network: Network) -> DualAddress { + Self { + network, + features: TariAddressFeatures::default(), + public_view_key: view_key, + public_spend_key: spend_key, + } + } + + /// helper function to convert emojis to u8 + pub fn emoji_to_bytes(emoji: &str) -> Result, TariAddressError> { + // The string must be the correct size, including the checksum + if emoji.chars().count() != INTERNAL_DUAL_SIZE { + return Err(TariAddressError::InvalidSize); + } + + // Convert the emoji string to a byte array + let mut bytes = Vec::::with_capacity(INTERNAL_DUAL_SIZE); + for c in emoji.chars() { + if let Some(i) = REVERSE_EMOJI.get(&c) { + bytes.push(*i); + } else { + return Err(TariAddressError::InvalidEmoji); + } + } + Ok(bytes) + } + + /// Construct an TariAddress from an emoji string + pub fn from_emoji_string(emoji: &str) -> Result { + let bytes = Self::emoji_to_bytes(emoji)?; + + Self::from_bytes(&bytes) + } + + /// Gets the network from the Tari Address + pub fn network(&self) -> Network { + self.network + } + + /// Gets the features from the Tari Address + pub fn features(&self) -> TariAddressFeatures { + self.features + } + + /// Convert Tari Address to an emoji string + pub fn to_emoji_string(&self) -> String { + // Convert the public key to bytes and compute the checksum + let bytes = self.to_bytes(); + bytes.iter().map(|b| EMOJI[*b as usize]).collect::() + } + + /// Return the public view key of a Tari Address + pub fn public_view_key(&self) -> &PublicKey { + &self.public_view_key + } + + /// Return the public spend key of a Tari Address + pub fn public_spend_key(&self) -> &PublicKey { + &self.public_spend_key + } + + /// Construct Tari Address from bytes + pub fn from_bytes(bytes: &[u8]) -> Result + where Self: Sized { + if bytes.len() != INTERNAL_DUAL_SIZE { + return Err(TariAddressError::InvalidSize); + } + if validate_checksum(bytes).is_err() { + return Err(TariAddressError::InvalidChecksum); + } + let network = Network::try_from(bytes[0]).map_err(|_| TariAddressError::InvalidNetwork)?; + let features = TariAddressFeatures::from_bits(bytes[1]).ok_or(TariAddressError::InvalidFeatures)?; + let public_view_key = + PublicKey::from_canonical_bytes(&bytes[2..34]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + let public_spend_key = + PublicKey::from_canonical_bytes(&bytes[34..66]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + Ok(Self { + network, + features, + public_view_key, + public_spend_key, + }) + } + + /// Convert Tari Address to bytes + pub fn to_bytes(&self) -> [u8; INTERNAL_DUAL_SIZE] { + let mut buf = [0u8; INTERNAL_DUAL_SIZE]; + buf[0] = self.network.as_byte(); + buf[1] = self.features.0; + buf[2..34].copy_from_slice(self.public_view_key.as_bytes()); + buf[34..66].copy_from_slice(self.public_spend_key.as_bytes()); + let checksum = compute_checksum(&buf[0..66]); + buf[66] = checksum; + buf + } + + /// Construct Tari Address from hex + pub fn from_hex(hex_str: &str) -> Result { + let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + Self::from_bytes(buf.as_slice()) + } + + /// Convert Tari Address to hex string + pub fn to_hex(&self) -> String { + let buf = self.to_bytes(); + buf.to_hex() + } +} + +#[cfg(test)] +mod test { + use tari_crypto::keys::{PublicKey as pk, SecretKey}; + + use super::*; + use crate::types::PrivateKey; + + #[test] + /// Test valid dual tari address + fn valid_emoji_id() { + // Generate random public key + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = + DualAddress::new_with_default_features(view_key.clone(), spend_key.clone(), Network::Esmeralda); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_and_one_sided()); + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = DualAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Generate random public key + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = DualAddress::new( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = DualAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Generate random public key + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = DualAddress::new( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_one_sided_only()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = DualAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + } + + #[test] + /// Test encoding for dual tari address + fn encoding() { + // Generate random public key + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = DualAddress::new_with_default_features(view_key.clone(), spend_key.clone(), Network::Esmeralda); + + let buff = address.to_bytes(); + let hex = address.to_hex(); + + let address_buff = DualAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = DualAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = DualAddress::new( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + + let buff = address.to_bytes(); + let hex = address.to_hex(); + + let address_buff = DualAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = DualAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = DualAddress::new( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + + let buff = address.to_bytes(); + let hex = address.to_hex(); + + let address_buff = DualAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = DualAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + } + + #[test] + /// Test invalid size + fn invalid_size() { + // This emoji string is too short to be a valid emoji ID + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿฆ‚๐ŸŽ๐Ÿ›๐Ÿ”ฑ๐ŸŸ๐Ÿšฆ๐Ÿฆ†๐Ÿ‘ƒ๐Ÿ›๐ŸŽผ๐Ÿ›ต๐Ÿ”ฎ๐Ÿ’‹๐Ÿ‘™๐Ÿ’ฆ๐Ÿท๐Ÿ‘ ๐Ÿฆ€๐Ÿบ๐Ÿช๐Ÿš€๐ŸŽฎ๐ŸŽฉ๐Ÿ‘…๐Ÿ”๐Ÿ‰๐Ÿ๐Ÿฅ‘๐Ÿ’”๐Ÿ“Œ๐Ÿšง๐ŸŠ๐Ÿ’„๐ŸŽฅ๐ŸŽ“๐Ÿš—๐ŸŽณ๐Ÿ›๐Ÿšฟ๐Ÿ’‰๐ŸŒด๐Ÿงข๐Ÿต๐ŸŽฉ๐Ÿ‘พ๐Ÿ‘ฝ๐ŸŽƒ๐Ÿคก๐Ÿ‘๐Ÿ”ฎ๐Ÿ‘’๐Ÿ‘ฝ๐ŸŽต๐Ÿ‘€๐Ÿšจ๐Ÿ˜ท๐ŸŽ’๐Ÿ‘‚๐Ÿ‘ถ๐Ÿ„๐Ÿฐ๐Ÿš‘๐ŸŒธ๐Ÿ"; + assert_eq!( + DualAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + // This emoji string is too long to be a valid emoji ID + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿฆ‚๐ŸŽ๐Ÿ›๐Ÿ”ฑ๐ŸŸ๐Ÿšฆ๐Ÿฆ†๐Ÿ‘ƒ๐Ÿ›๐ŸŽผ๐Ÿ›ต๐Ÿ”ฎ๐Ÿ’‹๐Ÿ‘™๐Ÿ’ฆ๐Ÿท๐Ÿ‘ ๐Ÿฆ€๐Ÿบ๐Ÿช๐Ÿš€๐ŸŽฎ๐ŸŽฉ๐Ÿ‘…๐Ÿ”๐Ÿ‰๐Ÿ๐Ÿฅ‘๐Ÿ’”๐Ÿ“Œ๐Ÿšง๐ŸŠ๐Ÿ’„๐ŸŽฅ๐ŸŽ“๐Ÿš—๐ŸŽณ๐Ÿ›๐Ÿšฟ๐Ÿ’‰๐ŸŒด๐Ÿงข๐Ÿต๐ŸŽฉ๐Ÿ‘พ๐Ÿ‘ฝ๐ŸŽƒ๐Ÿคก๐Ÿ‘๐Ÿ”ฎ๐Ÿ‘’๐Ÿ‘ฝ๐ŸŽต๐Ÿ‘€๐Ÿšจ๐Ÿ˜ท๐ŸŽ’๐Ÿ‘‚๐Ÿ‘ถ๐Ÿ„๐Ÿฐ๐Ÿš‘๐ŸŒธ๐Ÿ๐Ÿ‘‚๐ŸŽ’"; + assert_eq!( + DualAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + } + + #[test] + /// Test invalid emoji + fn invalid_emoji() { + // This emoji string contains an invalid emoji character + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿฆ‚๐ŸŽ๐Ÿ›๐Ÿ”ฑ๐ŸŸ๐Ÿšฆ๐Ÿฆ†๐Ÿ‘ƒ๐Ÿ›๐ŸŽผ๐Ÿ›ต๐Ÿ”ฎ๐Ÿ’‹๐Ÿ‘™๐Ÿ’ฆ๐Ÿท๐Ÿ‘ ๐Ÿฆ€๐Ÿบ๐Ÿช๐Ÿš€๐ŸŽฎ๐ŸŽฉ๐Ÿ‘…๐Ÿ”๐Ÿ‰๐Ÿ๐Ÿฅ‘๐Ÿ’”๐Ÿ“Œ๐Ÿšง๐ŸŠ๐Ÿ’„๐ŸŽฅ๐ŸŽ“๐Ÿš—๐ŸŽณ๐Ÿ›๐Ÿšฟ๐Ÿ’‰๐ŸŒด๐Ÿงข๐Ÿต๐ŸŽฉ๐Ÿ‘พ๐Ÿ‘ฝ๐ŸŽƒ๐Ÿคก๐Ÿ‘๐Ÿ”ฎ๐Ÿ‘’๐Ÿ‘ฝ๐ŸŽต๐Ÿ‘€๐Ÿšจ๐Ÿ˜ท๐ŸŽ’๐Ÿ‘‚๐Ÿ‘ถ๐Ÿ„๐Ÿฐ๐Ÿš‘๐ŸŒธ๐Ÿ๐ŸŽ…"; + assert_eq!( + DualAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidEmoji) + ); + } + + #[test] + /// Test invalid checksum + fn invalid_checksum() { + // This emoji string contains an invalid checksum + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿฆ‚๐ŸŽ๐Ÿ›๐Ÿ”ฑ๐ŸŸ๐Ÿšฆ๐Ÿฆ†๐Ÿ‘ƒ๐Ÿ›๐ŸŽผ๐Ÿ›ต๐Ÿ”ฎ๐Ÿ’‹๐Ÿ‘™๐Ÿ’ฆ๐Ÿท๐Ÿ‘ ๐Ÿฆ€๐Ÿบ๐Ÿช๐Ÿš€๐ŸŽฎ๐ŸŽฉ๐Ÿ‘…๐Ÿ”๐Ÿ‰๐Ÿ๐Ÿฅ‘๐Ÿ’”๐Ÿ“Œ๐Ÿšง๐ŸŠ๐Ÿ’„๐ŸŽฅ๐ŸŽ“๐Ÿš—๐ŸŽณ๐Ÿ›๐Ÿšฟ๐Ÿ’‰๐ŸŒด๐Ÿงข๐Ÿต๐ŸŽฉ๐Ÿ‘พ๐Ÿ‘ฝ๐ŸŽƒ๐Ÿคก๐Ÿ‘๐Ÿ”ฎ๐Ÿ‘’๐Ÿ‘ฝ๐ŸŽต๐Ÿ‘€๐Ÿšจ๐Ÿ˜ท๐ŸŽ’๐Ÿ‘‚๐Ÿ‘ถ๐Ÿ„๐Ÿฐ๐Ÿš‘๐ŸŒธ๐Ÿ๐ŸŽ’"; + assert_eq!( + DualAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidChecksum) + ); + } + + #[test] + /// Test invalid features + fn invalid_features() { + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let mut address = + DualAddress::new_with_default_features(view_key.clone(), spend_key.clone(), Network::Esmeralda); + address.features = TariAddressFeatures(5); + + let emoji_string = address.to_emoji_string(); + assert_eq!( + DualAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::InvalidFeatures) + ); + } + + #[test] + /// Test invalid network + fn invalid_network() { + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an address using a valid network and ensure it's not valid on another network + let address = DualAddress::new_with_default_features(view_key, spend_key, Network::Esmeralda); + let mut bytes = address.to_bytes(); + // this is an invalid network + bytes[0] = 123; + let checksum = compute_checksum(&bytes[0..66]); + bytes[66] = checksum; + assert_eq!(DualAddress::from_bytes(&bytes), Err(TariAddressError::InvalidNetwork)); + } + + #[test] + /// Test invalid public key + fn invalid_public_key() { + let mut bytes = [0; 67].to_vec(); + bytes[0] = Network::Esmeralda.as_byte(); + bytes[1] = TariAddressFeatures::create_interactive_and_one_sided().0; + bytes[2] = 1; + let checksum = compute_checksum(&bytes[0..66]); + bytes[66] = checksum; + let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + + // This emoji string contains an invalid checksum + assert_eq!( + DualAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::CannotRecoverPublicKey) + ); + } +} diff --git a/base_layer/common_types/src/tari_address/mod.rs b/base_layer/common_types/src/tari_address/mod.rs new file mode 100644 index 0000000000..3e133e6603 --- /dev/null +++ b/base_layer/common_types/src/tari_address/mod.rs @@ -0,0 +1,709 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod dual_address; +mod single_address; + +use std::{ + fmt::{Display, Error, Formatter}, + str::FromStr, +}; + +use bitflags::bitflags; +use serde::{Deserialize, Serialize}; +use tari_common::configuration::Network; +use tari_crypto::tari_utilities::ByteArray; +use tari_utilities::hex::{from_hex, Hex}; +use thiserror::Error; + +use crate::{ + emoji::EMOJI, + tari_address::{dual_address::DualAddress, single_address::SingleAddress}, + types::PublicKey, +}; + +const INTERNAL_DUAL_SIZE: usize = 67; // number of bytes used for the internal representation +const INTERNAL_SINGLE_SIZE: usize = 35; // number of bytes used for the internal representation + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TariAddressFeatures(u8); + +bitflags! { + impl TariAddressFeatures: u8 { + const INTERACTIVE = 2u8; + ///one sided payment + const ONE_SIDED = 1u8; + } +} + +impl TariAddressFeatures { + pub fn create_interactive_only() -> TariAddressFeatures { + TariAddressFeatures::INTERACTIVE + } + + pub fn create_one_sided_only() -> TariAddressFeatures { + TariAddressFeatures::ONE_SIDED + } + + pub fn create_interactive_and_one_sided() -> TariAddressFeatures { + TariAddressFeatures::INTERACTIVE | TariAddressFeatures::ONE_SIDED + } +} + +impl Default for TariAddressFeatures { + fn default() -> TariAddressFeatures { + TariAddressFeatures::INTERACTIVE | TariAddressFeatures::ONE_SIDED + } +} + +#[derive(Debug, Error, PartialEq)] +pub enum TariAddressError { + #[error("Invalid size")] + InvalidSize, + #[error("Invalid network")] + InvalidNetwork, + #[error("Invalid features")] + InvalidFeatures, + #[error("Invalid checksum")] + InvalidChecksum, + #[error("Invalid emoji character")] + InvalidEmoji, + #[error("Cannot recover public key")] + CannotRecoverPublicKey, + #[error("Could not recover TariAddress from string")] + InvalidAddressString, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum TariAddress { + Dual(DualAddress), + Single(SingleAddress), +} + +impl TariAddress { + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new_dual_address( + view_key: PublicKey, + spend_key: PublicKey, + network: Network, + features: TariAddressFeatures, + ) -> Self { + TariAddress::Dual(DualAddress::new(view_key, spend_key, network, features)) + } + + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new_single_address(spend_key: PublicKey, network: Network, features: TariAddressFeatures) -> Self { + TariAddress::Single(SingleAddress::new(spend_key, network, features)) + } + + /// Creates a new Tari Address from the provided public keys and network while using the default features + pub fn new_dual_address_with_default_features(view_key: PublicKey, spend_key: PublicKey, network: Network) -> Self { + TariAddress::Dual(DualAddress::new_with_default_features(view_key, spend_key, network)) + } + + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new_single_address_with_interactive_only(spend_key: PublicKey, network: Network) -> Self { + TariAddress::Single(SingleAddress::new_with_interactive_only(spend_key, network)) + } + + /// helper function to convert emojis to u8 + fn emoji_to_bytes(emoji: &str) -> Result, TariAddressError> { + // The string must be the correct size, including the checksum + if !(emoji.chars().count() == INTERNAL_SINGLE_SIZE || emoji.chars().count() == INTERNAL_DUAL_SIZE) { + return Err(TariAddressError::InvalidSize); + } + if emoji.chars().count() == INTERNAL_SINGLE_SIZE { + SingleAddress::emoji_to_bytes(emoji) + } else { + DualAddress::emoji_to_bytes(emoji) + } + } + + /// Construct an TariAddress from an emoji string + pub fn from_emoji_string(emoji: &str) -> Result { + let bytes = TariAddress::emoji_to_bytes(emoji)?; + + TariAddress::from_bytes(&bytes) + } + + /// Gets the network from the Tari Address + pub fn network(&self) -> Network { + match self { + TariAddress::Dual(v) => v.network(), + TariAddress::Single(v) => v.network(), + } + } + + /// Gets the features from the Tari Address + pub fn features(&self) -> TariAddressFeatures { + match self { + TariAddress::Dual(v) => v.features(), + TariAddress::Single(v) => v.features(), + } + } + + /// Convert Tari Address to an emoji string + pub fn to_emoji_string(&self) -> String { + // Convert the public key to bytes and compute the checksum + let bytes = self.to_vec(); + bytes.iter().map(|b| EMOJI[*b as usize]).collect::() + } + + /// Return the public view key of an Tari Address + pub fn public_view_key(&self) -> Option<&PublicKey> { + match self { + TariAddress::Dual(v) => Some(v.public_view_key()), + TariAddress::Single(_) => None, + } + } + + /// Return the public spend key of an Tari Address + pub fn public_spend_key(&self) -> &PublicKey { + match self { + TariAddress::Dual(v) => v.public_spend_key(), + TariAddress::Single(v) => v.public_spend_key(), + } + } + + /// Return the public comms key of an Tari Address, which is the public spend key + pub fn comms_public_key(&self) -> &PublicKey { + match self { + TariAddress::Dual(v) => v.public_spend_key(), + TariAddress::Single(v) => v.public_spend_key(), + } + } + + /// Construct Tari Address from bytes + pub fn from_bytes(bytes: &[u8]) -> Result + where Self: Sized { + if !(bytes.len() == INTERNAL_SINGLE_SIZE || bytes.len() == INTERNAL_DUAL_SIZE) { + return Err(TariAddressError::InvalidSize); + } + if bytes.len() == INTERNAL_SINGLE_SIZE { + Ok(TariAddress::Single(SingleAddress::from_bytes(bytes)?)) + } else { + Ok(TariAddress::Dual(DualAddress::from_bytes(bytes)?)) + } + } + + /// Convert Tari Address to bytes + pub fn to_vec(&self) -> Vec { + match self { + TariAddress::Dual(v) => v.to_bytes().to_vec(), + TariAddress::Single(v) => v.to_bytes().to_vec(), + } + } + + /// Construct Tari Address from hex + pub fn from_hex(hex_str: &str) -> Result { + let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + TariAddress::from_bytes(buf.as_slice()) + } + + /// Convert Tari Address to bytes + pub fn to_hex(&self) -> String { + let buf = self.to_vec(); + buf.to_hex() + } +} + +impl FromStr for TariAddress { + type Err = TariAddressError; + + fn from_str(key: &str) -> Result { + if let Ok(address) = TariAddress::from_emoji_string(&key.trim().replace('|', "")) { + Ok(address) + } else if let Ok(address) = TariAddress::from_hex(key) { + Ok(address) + } else { + Err(TariAddressError::InvalidAddressString) + } + } +} + +impl Display for TariAddress { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> { + fmt.write_str(&self.to_emoji_string()) + } +} + +impl Default for TariAddress { + fn default() -> Self { + Self::Dual(DualAddress::default()) + } +} + +#[cfg(test)] +mod test { + use tari_crypto::keys::{PublicKey as pk, SecretKey}; + + use super::*; + use crate::{dammsum::compute_checksum, types::PrivateKey}; + + #[test] + /// Test valid single tari address + fn valid_emoji_id_single() { + // Generate random public key + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = + TariAddress::new_single_address_with_interactive_only(public_key.clone(), Network::Esmeralda); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_single_address( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_single_address( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_one_sided_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + } + + #[test] + /// Test valid dual tari address + fn valid_emoji_id_dual() { + // Generate random public key + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_dual_address_with_default_features( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_and_one_sided()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Generate random public key + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Generate random public key + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_one_sided_only()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + } + + #[test] + /// Test encoding for single tari address + fn encoding_single() { + // Generate random public key + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_single_address_with_interactive_only(public_key.clone(), Network::Esmeralda); + + let buff = address.to_vec(); + let hex = address.to_hex(); + + let address_buff = TariAddress::from_bytes(&buff); + assert_eq!(address_buff, Ok(address.clone())); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_single_address( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + + let buff = address.to_vec(); + let hex = address.to_hex(); + + let address_buff = TariAddress::from_bytes(&buff); + assert_eq!(address_buff, Ok(address.clone())); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_single_address( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + + let buff = address.to_vec(); + let hex = address.to_hex(); + + let address_buff = TariAddress::from_bytes(&buff); + assert_eq!(address_buff, Ok(address.clone())); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + } + + #[test] + /// Test encoding for dual tari address + fn encoding_dual() { + // Generate random public key + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_dual_address_with_default_features( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + ); + + let buff = address.to_vec(); + let hex = address.to_hex(); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + + let buff = address.to_vec(); + let hex = address.to_hex(); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + + let buff = address.to_vec(); + let hex = address.to_hex(); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + } + + #[test] + /// Test invalid size + fn invalid_size() { + // This emoji string is too short to be a valid emoji ID + let emoji_string = "๐ŸŒด๐Ÿฆ€๐Ÿ”Œ๐Ÿ“Œ๐Ÿš‘๐ŸŒฐ๐ŸŽ“๐ŸŒด๐ŸŠ๐ŸŒ๐Ÿ”’๐Ÿ’ก๐Ÿœ๐Ÿ“œ๐Ÿ‘›๐Ÿต๐Ÿ‘›๐Ÿฝ๐ŸŽ‚๐Ÿป๐Ÿฆ‹๐Ÿ“๐Ÿ‘ถ๐Ÿญ๐Ÿผ๐Ÿ€๐ŸŽช๐Ÿ’”๐Ÿ’ต๐Ÿฅ‘๐Ÿ”‹๐ŸŽ’๐ŸŽ’๐ŸŽ’"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + // This emoji string is too long to be a valid emoji ID + let emoji_string = "๐ŸŒด๐Ÿฆ€๐Ÿ”Œ๐Ÿ“Œ๐Ÿš‘๐ŸŒฐ๐ŸŽ“๐ŸŒด๐ŸŠ๐ŸŒ๐Ÿ”’๐Ÿ’ก๐Ÿœ๐Ÿ“œ๐Ÿ‘›๐Ÿต๐Ÿ‘›๐Ÿฝ๐ŸŽ‚๐Ÿป๐Ÿฆ‹๐Ÿ“๐Ÿ‘ถ๐Ÿญ๐Ÿผ๐Ÿ€๐ŸŽช๐Ÿ’”๐Ÿ’ต๐Ÿฅ‘๐Ÿ”‹๐ŸŽ’๐ŸŽ’๐ŸŽ’๐ŸŽ’๐ŸŽ’"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + + // This emoji string is too short to be a valid emoji ID + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿฆ‚๐ŸŽ๐Ÿ›๐Ÿ”ฑ๐ŸŸ๐Ÿšฆ๐Ÿฆ†๐Ÿ‘ƒ๐Ÿ›๐ŸŽผ๐Ÿ›ต๐Ÿ”ฎ๐Ÿ’‹๐Ÿ‘™๐Ÿ’ฆ๐Ÿท๐Ÿ‘ ๐Ÿฆ€๐Ÿบ๐Ÿช๐Ÿš€๐ŸŽฎ๐ŸŽฉ๐Ÿ‘…๐Ÿ”๐Ÿ‰๐Ÿ๐Ÿฅ‘๐Ÿ’”๐Ÿ“Œ๐Ÿšง๐ŸŠ๐Ÿ’„๐ŸŽฅ๐ŸŽ“๐Ÿš—๐ŸŽณ๐Ÿ›๐Ÿšฟ๐Ÿ’‰๐ŸŒด๐Ÿงข๐Ÿต๐ŸŽฉ๐Ÿ‘พ๐Ÿ‘ฝ๐ŸŽƒ๐Ÿคก๐Ÿ‘๐Ÿ”ฎ๐Ÿ‘’๐Ÿ‘ฝ๐ŸŽต๐Ÿ‘€๐Ÿšจ๐Ÿ˜ท๐ŸŽ’๐Ÿ‘‚๐Ÿ‘ถ๐Ÿ„๐Ÿฐ๐Ÿš‘๐ŸŒธ๐Ÿ"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + // This emoji string is too long to be a valid emoji ID + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿฆ‚๐ŸŽ๐Ÿ›๐Ÿ”ฑ๐ŸŸ๐Ÿšฆ๐Ÿฆ†๐Ÿ‘ƒ๐Ÿ›๐ŸŽผ๐Ÿ›ต๐Ÿ”ฎ๐Ÿ’‹๐Ÿ‘™๐Ÿ’ฆ๐Ÿท๐Ÿ‘ ๐Ÿฆ€๐Ÿบ๐Ÿช๐Ÿš€๐ŸŽฎ๐ŸŽฉ๐Ÿ‘…๐Ÿ”๐Ÿ‰๐Ÿ๐Ÿฅ‘๐Ÿ’”๐Ÿ“Œ๐Ÿšง๐ŸŠ๐Ÿ’„๐ŸŽฅ๐ŸŽ“๐Ÿš—๐ŸŽณ๐Ÿ›๐Ÿšฟ๐Ÿ’‰๐ŸŒด๐Ÿงข๐Ÿต๐ŸŽฉ๐Ÿ‘พ๐Ÿ‘ฝ๐ŸŽƒ๐Ÿคก๐Ÿ‘๐Ÿ”ฎ๐Ÿ‘’๐Ÿ‘ฝ๐ŸŽต๐Ÿ‘€๐Ÿšจ๐Ÿ˜ท๐ŸŽ’๐Ÿ‘‚๐Ÿ‘ถ๐Ÿ„๐Ÿฐ๐Ÿš‘๐ŸŒธ๐Ÿ๐Ÿ‘‚๐ŸŽ’"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + } + + #[test] + /// Test invalid emoji + fn invalid_emoji() { + // This emoji string contains an invalid emoji character + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿ‰๐Ÿฆ‹๐ŸŽช๐Ÿ‘›๐ŸŒฒ๐Ÿญ๐Ÿฆ‚๐Ÿ”จ๐Ÿ’บ๐ŸŽบ๐ŸŒ•๐Ÿ’ฆ๐Ÿšจ๐ŸŽผ๐Ÿชโฐ๐Ÿฌ๐Ÿš๐ŸŽฑ๐Ÿ’ณ๐Ÿ”ฑ๐Ÿต๐Ÿ›ต๐Ÿ’ก๐Ÿ“ฑ๐ŸŒป๐Ÿ“Ž๐ŸŽป๐ŸŒ๐Ÿ˜Ž๐Ÿ‘™๐ŸŽน๐ŸŽ…"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidEmoji) + ); + } + + #[test] + /// Test invalid checksum + fn invalid_checksum() { + // This emoji string contains an invalid checksum + let emoji_string = "๐Ÿ—๐ŸŒˆ๐Ÿš“๐Ÿงฒ๐Ÿ“Œ๐Ÿบ๐Ÿฃ๐Ÿ™ˆ๐Ÿ’ฐ๐Ÿ‡๐ŸŽ“๐Ÿ‘‚๐Ÿ“ˆโšฝ๐Ÿšง๐Ÿšง๐Ÿšข๐Ÿซ๐Ÿ’‹๐Ÿ‘ฝ๐ŸŒˆ๐ŸŽช๐Ÿšฝ๐Ÿช๐ŸŽณ๐Ÿ’ผ๐Ÿ™ˆ๐ŸŽช๐Ÿ˜Ž๐Ÿ ๐ŸŽณ๐Ÿ‘๐Ÿ“ท๐ŸŽฒ๐ŸŽ’"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidChecksum) + ); + + // This emoji string contains an invalid checksum + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿฆ‚๐ŸŽ๐Ÿ›๐Ÿ”ฑ๐ŸŸ๐Ÿšฆ๐Ÿฆ†๐Ÿ‘ƒ๐Ÿ›๐ŸŽผ๐Ÿ›ต๐Ÿ”ฎ๐Ÿ’‹๐Ÿ‘™๐Ÿ’ฆ๐Ÿท๐Ÿ‘ ๐Ÿฆ€๐Ÿบ๐Ÿช๐Ÿš€๐ŸŽฎ๐ŸŽฉ๐Ÿ‘…๐Ÿ”๐Ÿ‰๐Ÿ๐Ÿฅ‘๐Ÿ’”๐Ÿ“Œ๐Ÿšง๐ŸŠ๐Ÿ’„๐ŸŽฅ๐ŸŽ“๐Ÿš—๐ŸŽณ๐Ÿ›๐Ÿšฟ๐Ÿ’‰๐ŸŒด๐Ÿงข๐Ÿต๐ŸŽฉ๐Ÿ‘พ๐Ÿ‘ฝ๐ŸŽƒ๐Ÿคก๐Ÿ‘๐Ÿ”ฎ๐Ÿ‘’๐Ÿ‘ฝ๐ŸŽต๐Ÿ‘€๐Ÿšจ๐Ÿ˜ท๐ŸŽ’๐Ÿ‘‚๐Ÿ‘ถ๐Ÿ„๐Ÿฐ๐Ÿš‘๐ŸŒธ๐Ÿ๐ŸŽ’"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidChecksum) + ); + } + + #[test] + /// Test invalid network + fn invalid_network() { + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an address using a valid network and ensure it's not valid on another network + let address = SingleAddress::new_with_interactive_only(public_key, Network::Esmeralda); + let mut bytes = address.to_bytes(); + // this is an invalid network + bytes[0] = 123; + let checksum = compute_checksum(&bytes[0..34]); + bytes[34] = checksum; + assert_eq!(TariAddress::from_bytes(&bytes), Err(TariAddressError::InvalidNetwork)); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an address using a valid network and ensure it's not valid on another network + let address = TariAddress::new_dual_address_with_default_features(view_key, spend_key, Network::Esmeralda); + let mut bytes = address.to_vec(); + // this is an invalid network + bytes[0] = 123; + let checksum = compute_checksum(&bytes[0..66]); + bytes[66] = checksum; + assert_eq!(TariAddress::from_bytes(&bytes), Err(TariAddressError::InvalidNetwork)); + } + + #[test] + /// Test invalid public key + fn invalid_public_key() { + let mut bytes = [0; 35].to_vec(); + bytes[0] = Network::Esmeralda.as_byte(); + bytes[1] = TariAddressFeatures::create_interactive_and_one_sided().0; + bytes[2] = 1; + let checksum = compute_checksum(&bytes[0..34]); + bytes[34] = checksum; + let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + + // This emoji string contains an invalid checksum + assert_eq!( + SingleAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::CannotRecoverPublicKey) + ); + + let mut bytes = [0; 67].to_vec(); + bytes[0] = Network::Esmeralda.as_byte(); + bytes[1] = TariAddressFeatures::create_interactive_and_one_sided().0; + bytes[2] = 1; + let checksum = compute_checksum(&bytes[0..66]); + bytes[66] = checksum; + let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + + // This emoji string contains an invalid checksum + assert_eq!( + DualAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::CannotRecoverPublicKey) + ); + } +} diff --git a/base_layer/common_types/src/tari_address/single_address.rs b/base_layer/common_types/src/tari_address/single_address.rs new file mode 100644 index 0000000000..9eca6e4807 --- /dev/null +++ b/base_layer/common_types/src/tari_address/single_address.rs @@ -0,0 +1,393 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::convert::TryFrom; + +use serde::{Deserialize, Serialize}; +use tari_common::configuration::Network; +use tari_crypto::tari_utilities::ByteArray; +use tari_utilities::hex::{from_hex, Hex}; + +use crate::{ + dammsum::{compute_checksum, validate_checksum}, + emoji::{EMOJI, REVERSE_EMOJI}, + tari_address::{TariAddressError, TariAddressFeatures, INTERNAL_SINGLE_SIZE}, + types::PublicKey, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct SingleAddress { + network: Network, + features: TariAddressFeatures, + public_spend_key: PublicKey, +} + +impl SingleAddress { + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new(spend_key: PublicKey, network: Network, features: TariAddressFeatures) -> SingleAddress { + Self { + network, + features, + public_spend_key: spend_key, + } + } + + /// Creates a new Tari Address from the provided public keys and network while using the default features + pub fn new_with_interactive_only(spend_key: PublicKey, network: Network) -> SingleAddress { + Self { + network, + features: TariAddressFeatures::create_interactive_only(), + public_spend_key: spend_key, + } + } + + /// helper function to convert emojis to u8 + pub fn emoji_to_bytes(emoji: &str) -> Result, TariAddressError> { + // The string must be the correct size, including the checksum + if emoji.chars().count() != INTERNAL_SINGLE_SIZE { + return Err(TariAddressError::InvalidSize); + } + + // Convert the emoji string to a byte array + let mut bytes = Vec::::with_capacity(INTERNAL_SINGLE_SIZE); + for c in emoji.chars() { + if let Some(i) = REVERSE_EMOJI.get(&c) { + bytes.push(*i); + } else { + return Err(TariAddressError::InvalidEmoji); + } + } + Ok(bytes) + } + + /// Construct an TariAddress from an emoji string + pub fn from_emoji_string(emoji: &str) -> Result { + let bytes = Self::emoji_to_bytes(emoji)?; + + Self::from_bytes(&bytes) + } + + /// Gets the network from the Tari Address + pub fn network(&self) -> Network { + self.network + } + + /// Gets the features from the Tari Address + pub fn features(&self) -> TariAddressFeatures { + self.features + } + + /// Convert Tari Address to an emoji string + pub fn to_emoji_string(&self) -> String { + // Convert the public key to bytes and compute the checksum + let bytes = self.to_bytes(); + bytes.iter().map(|b| EMOJI[*b as usize]).collect::() + } + + /// Return the public spend key of a Tari Address + pub fn public_spend_key(&self) -> &PublicKey { + &self.public_spend_key + } + + /// Construct Tari Address from bytes + pub fn from_bytes(bytes: &[u8]) -> Result + where Self: Sized { + if bytes.len() != INTERNAL_SINGLE_SIZE { + return Err(TariAddressError::InvalidSize); + } + if validate_checksum(bytes).is_err() { + return Err(TariAddressError::InvalidChecksum); + } + let network = Network::try_from(bytes[0]).map_err(|_| TariAddressError::InvalidNetwork)?; + let features = TariAddressFeatures::from_bits(bytes[1]).ok_or(TariAddressError::InvalidFeatures)?; + let public_spend_key = + PublicKey::from_canonical_bytes(&bytes[2..34]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + Ok(Self { + network, + features, + public_spend_key, + }) + } + + /// Convert Tari Address to bytes + pub fn to_bytes(&self) -> [u8; INTERNAL_SINGLE_SIZE] { + let mut buf = [0u8; INTERNAL_SINGLE_SIZE]; + buf[0] = self.network.as_byte(); + buf[1] = self.features.0; + buf[2..34].copy_from_slice(self.public_spend_key.as_bytes()); + let checksum = compute_checksum(&buf[0..34]); + buf[34] = checksum; + buf + } + + /// Construct Tari Address from hex + pub fn from_hex(hex_str: &str) -> Result { + let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + Self::from_bytes(buf.as_slice()) + } + + /// Convert Tari Address to bytes + pub fn to_hex(&self) -> String { + let buf = self.to_bytes(); + buf.to_hex() + } +} +#[cfg(test)] +mod test { + use tari_crypto::keys::{PublicKey as pk, SecretKey}; + + use super::*; + use crate::types::PrivateKey; + + #[test] + /// Test valid single tari address + fn valid_emoji_id() { + // Generate random public key + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = SingleAddress::new_with_interactive_only(public_key.clone(), Network::Esmeralda); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = SingleAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = SingleAddress::new( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = SingleAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = SingleAddress::new( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_one_sided_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = SingleAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + } + + #[test] + /// Test encoding for single tari address + fn encoding() { + // Generate random public key + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = SingleAddress::new_with_interactive_only(public_key.clone(), Network::Esmeralda); + + let buff = address.to_bytes(); + let hex = address.to_hex(); + + let address_buff = SingleAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = SingleAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = SingleAddress::new( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + + let buff = address.to_bytes(); + let hex = address.to_hex(); + + let address_buff = SingleAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = SingleAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = SingleAddress::new( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + + let buff = address.to_bytes(); + let hex = address.to_hex(); + + let address_buff = SingleAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_hex = SingleAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + } + + #[test] + /// Test invalid size + fn invalid_size() { + // This emoji string is too short to be a valid emoji ID + let emoji_string = "๐ŸŒด๐Ÿฆ€๐Ÿ”Œ๐Ÿ“Œ๐Ÿš‘๐ŸŒฐ๐ŸŽ“๐ŸŒด๐ŸŠ๐ŸŒ๐Ÿ”’๐Ÿ’ก๐Ÿœ๐Ÿ“œ๐Ÿ‘›๐Ÿต๐Ÿ‘›๐Ÿฝ๐ŸŽ‚๐Ÿป๐Ÿฆ‹๐Ÿ“๐Ÿ‘ถ๐Ÿญ๐Ÿผ๐Ÿ€๐ŸŽช๐Ÿ’”๐Ÿ’ต๐Ÿฅ‘๐Ÿ”‹๐ŸŽ’๐ŸŽ’๐ŸŽ’"; + assert_eq!( + SingleAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + // This emoji string is too long to be a valid emoji ID + let emoji_string = "๐ŸŒด๐Ÿฆ€๐Ÿ”Œ๐Ÿ“Œ๐Ÿš‘๐ŸŒฐ๐ŸŽ“๐ŸŒด๐ŸŠ๐ŸŒ๐Ÿ”’๐Ÿ’ก๐Ÿœ๐Ÿ“œ๐Ÿ‘›๐Ÿต๐Ÿ‘›๐Ÿฝ๐ŸŽ‚๐Ÿป๐Ÿฆ‹๐Ÿ“๐Ÿ‘ถ๐Ÿญ๐Ÿผ๐Ÿ€๐ŸŽช๐Ÿ’”๐Ÿ’ต๐Ÿฅ‘๐Ÿ”‹๐ŸŽ’๐ŸŽ’๐ŸŽ’๐ŸŽ’๐ŸŽ’"; + assert_eq!( + SingleAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + } + + #[test] + /// Test invalid emoji + fn invalid_emoji() { + // This emoji string contains an invalid emoji character + let emoji_string = "๐Ÿ—๐ŸŒŠ๐Ÿ‰๐Ÿฆ‹๐ŸŽช๐Ÿ‘›๐ŸŒฒ๐Ÿญ๐Ÿฆ‚๐Ÿ”จ๐Ÿ’บ๐ŸŽบ๐ŸŒ•๐Ÿ’ฆ๐Ÿšจ๐ŸŽผ๐Ÿชโฐ๐Ÿฌ๐Ÿš๐ŸŽฑ๐Ÿ’ณ๐Ÿ”ฑ๐Ÿต๐Ÿ›ต๐Ÿ’ก๐Ÿ“ฑ๐ŸŒป๐Ÿ“Ž๐ŸŽป๐ŸŒ๐Ÿ˜Ž๐Ÿ‘™๐ŸŽน๐ŸŽ…"; + assert_eq!( + SingleAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidEmoji) + ); + } + + #[test] + /// Test invalid features + fn invalid_features() { + let mut rng = rand::thread_rng(); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let mut address = SingleAddress::new_with_interactive_only(spend_key.clone(), Network::Esmeralda); + address.features = TariAddressFeatures(5); + + let emoji_string = address.to_emoji_string(); + assert_eq!( + SingleAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::InvalidFeatures) + ); + } + + #[test] + /// Test invalid checksum + fn invalid_checksum() { + // This emoji string contains an invalid checksum + let emoji_string = "๐Ÿ—๐ŸŒˆ๐Ÿš“๐Ÿงฒ๐Ÿ“Œ๐Ÿบ๐Ÿฃ๐Ÿ™ˆ๐Ÿ’ฐ๐Ÿ‡๐ŸŽ“๐Ÿ‘‚๐Ÿ“ˆโšฝ๐Ÿšง๐Ÿšง๐Ÿšข๐Ÿซ๐Ÿ’‹๐Ÿ‘ฝ๐ŸŒˆ๐ŸŽช๐Ÿšฝ๐Ÿช๐ŸŽณ๐Ÿ’ผ๐Ÿ™ˆ๐ŸŽช๐Ÿ˜Ž๐Ÿ ๐ŸŽณ๐Ÿ‘๐Ÿ“ท๐ŸŽฒ๐ŸŽ’"; + assert_eq!( + SingleAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidChecksum) + ); + } + + #[test] + /// Test invalid network + fn invalid_network() { + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an address using a valid network and ensure it's not valid on another network + let address = SingleAddress::new_with_interactive_only(public_key, Network::Esmeralda); + let mut bytes = address.to_bytes(); + // this is an invalid network + bytes[0] = 123; + let checksum = compute_checksum(&bytes[0..34]); + bytes[34] = checksum; + assert_eq!(SingleAddress::from_bytes(&bytes), Err(TariAddressError::InvalidNetwork)); + } + + #[test] + /// Test invalid public key + fn invalid_public_key() { + let mut bytes = [0; 35].to_vec(); + bytes[0] = Network::Esmeralda.as_byte(); + bytes[1] = TariAddressFeatures::create_interactive_and_one_sided().0; + bytes[2] = 1; + let checksum = compute_checksum(&bytes[0..34]); + bytes[34] = checksum; + let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + + // This emoji string contains an invalid checksum + assert_eq!( + SingleAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::CannotRecoverPublicKey) + ); + } +} diff --git a/base_layer/contacts/src/chat_client/src/client.rs b/base_layer/contacts/src/chat_client/src/client.rs index 6ac47d6a8f..e04ecaf078 100644 --- a/base_layer/contacts/src/chat_client/src/client.rs +++ b/base_layer/contacts/src/chat_client/src/client.rs @@ -63,6 +63,7 @@ pub struct Client { pub contacts: Option, pub identity: Arc, pub shutdown: Shutdown, + pub address: TariAddress, } impl Debug for Client { @@ -82,17 +83,28 @@ impl Drop for Client { } impl Client { - pub fn new(identity: Arc, config: ApplicationConfig, user_agent: String) -> Self { + pub fn new( + identity: Arc, + address: TariAddress, + config: ApplicationConfig, + user_agent: String, + ) -> Self { Self { config, user_agent, contacts: None, identity, shutdown: Shutdown::new(), + address, } } - pub fn sideload(config: ApplicationConfig, contacts: ContactsServiceHandle, user_agent: String) -> Self { + pub fn sideload( + config: ApplicationConfig, + contacts: ContactsServiceHandle, + user_agent: String, + address: TariAddress, + ) -> Self { // Create a placeholder ID. It won't be written or used when sideloaded. let identity = Arc::new(NodeIdentity::random( &mut OsRng, @@ -106,6 +118,7 @@ impl Client { contacts: Some(contacts), identity, shutdown: Shutdown::new(), + address, } } @@ -232,7 +245,7 @@ impl ChatClient for Client { } fn address(&self) -> TariAddress { - TariAddress::from_public_key(self.identity.public_key(), self.config.chat_client.network) + self.address.clone() } fn shutdown(&mut self) { diff --git a/base_layer/contacts/src/contacts_service/service.rs b/base_layer/contacts/src/contacts_service/service.rs index 1d294df4a6..2e1ab81f0a 100644 --- a/base_layer/contacts/src/contacts_service/service.rs +++ b/base_layer/contacts/src/contacts_service/service.rs @@ -586,7 +586,7 @@ where T: ContactsBackend + 'static message: Message, source_public_key: CommsPublicKey, ) -> Result<(), ContactsServiceError> { - if source_public_key != *message.sender_address.public_key() { + if source_public_key != *message.sender_address.comms_public_key() { return Err(ContactsServiceError::MessageSourceDoesNotMatchOrigin); } let our_message = Message { @@ -667,7 +667,7 @@ where T: ContactsBackend + 'static Ok(contact) => contact, Err(_) => Contact::from(&address), }; - let encryption = OutboundEncryption::EncryptFor(Box::new(address.public_key().clone())); + let encryption = OutboundEncryption::EncryptFor(Box::new(address.public_spend_key().clone())); match self.get_online_status(&contact).await { Ok(ContactOnlineStatus::Online) => { @@ -676,7 +676,7 @@ where T: ContactsBackend + 'static comms_outbound .send_direct_encrypted( - address.public_key().clone(), + address.public_spend_key().clone(), message, encryption, "contact service messaging".to_string(), @@ -688,7 +688,7 @@ where T: ContactsBackend + 'static info!(target: LOG_TARGET, "Chat message being sent via closest broadcast"); let mut comms_outbound = self.dht.outbound_requester(); comms_outbound - .closest_broadcast(address.public_key().clone(), encryption, vec![], message) + .closest_broadcast(address.public_spend_key().clone(), encryption, vec![], message) .await?; }, }; diff --git a/base_layer/contacts/src/contacts_service/storage/sqlite_db.rs b/base_layer/contacts/src/contacts_service/storage/sqlite_db.rs index e3c4b1ad67..d783e74a49 100644 --- a/base_layer/contacts/src/contacts_service/storage/sqlite_db.rs +++ b/base_layer/contacts/src/contacts_service/storage/sqlite_db.rs @@ -95,7 +95,7 @@ where TContactServiceDbConnection: PooledDbConnection match ContactSql::find_by_address(&address.to_bytes(), &mut conn) { + DbKey::Contact(address) => match ContactSql::find_by_address(&address.to_vec(), &mut conn) { Ok(c) => Some(DbValue::Contact(Box::new(Contact::try_from(c)?))), Err(ContactsServiceStorageError::DieselError(DieselError::NotFound)) => None, Err(e) => return Err(e), @@ -112,7 +112,7 @@ where TContactServiceDbConnection: PooledDbConnection, _>>()?, )), DbKey::Messages(address, limit, page) => { - match MessagesSql::find_by_address(&address.to_bytes(), *limit, *page, &mut conn) { + match MessagesSql::find_by_address(&address.to_vec(), *limit, *page, &mut conn) { Ok(messages_sql) => { let mut messages = vec![]; for m in &messages_sql { @@ -156,7 +156,7 @@ where TContactServiceDbConnection: PooledDbConnection { - if ContactSql::find_by_address_and_update(&mut conn, &k.to_bytes(), UpdateContact { + if ContactSql::find_by_address_and_update(&mut conn, &k.to_vec(), UpdateContact { alias: Some(c.clone().alias), last_seen: None, latency: None, @@ -189,7 +189,7 @@ where TContactServiceDbConnection: PooledDbConnection match k { - DbKey::Contact(k) => match ContactSql::find_by_address_and_delete(&mut conn, &k.to_bytes()) { + DbKey::Contact(k) => match ContactSql::find_by_address_and_delete(&mut conn, &k.to_vec()) { Ok(c) => { return Ok(Some(DbValue::Contact(Box::new(Contact::try_from(c)?)))); }, @@ -248,7 +248,7 @@ mod test { let mut contacts = Vec::new(); for i in 0..names.len() { let pub_key = PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)); - let address = TariAddress::new(pub_key, Network::default()); + let address = TariAddress::new_single_address_with_interactive_only(pub_key, Network::default()); contacts.push(Contact::new(names[i].clone(), address, None, None, false)); ContactSql::from(contacts[i].clone()).commit(&mut conn).unwrap(); } @@ -261,11 +261,11 @@ mod test { assert_eq!( contacts[1], - Contact::try_from(ContactSql::find_by_address(&contacts[1].address.to_bytes(), &mut conn).unwrap()) + Contact::try_from(ContactSql::find_by_address(&contacts[1].address.to_vec(), &mut conn).unwrap()) .unwrap() ); - ContactSql::find_by_address_and_delete(&mut conn, &contacts[0].address.clone().to_bytes()).unwrap(); + ContactSql::find_by_address_and_delete(&mut conn, &contacts[0].address.clone().to_vec()).unwrap(); let retrieved_contacts = ContactSql::index(&mut conn).unwrap(); assert_eq!(retrieved_contacts.len(), 2); @@ -274,16 +274,15 @@ mod test { .iter() .any(|v| v == &ContactSql::from(contacts[0].clone()))); - let _c = - ContactSql::find_by_address_and_update(&mut conn, &contacts[1].address.to_bytes(), UpdateContact { - alias: Some("Fred".to_string()), - last_seen: None, - latency: None, - favourite: Some(i32::from(true)), - }) - .unwrap(); + let _c = ContactSql::find_by_address_and_update(&mut conn, &contacts[1].address.to_vec(), UpdateContact { + alias: Some("Fred".to_string()), + last_seen: None, + latency: None, + favourite: Some(i32::from(true)), + }) + .unwrap(); - let c_updated = ContactSql::find_by_address(&contacts[1].address.to_bytes(), &mut conn).unwrap(); + let c_updated = ContactSql::find_by_address(&contacts[1].address.to_vec(), &mut conn).unwrap(); assert_eq!(c_updated.alias, "Fred".to_string()); assert_eq!(c_updated.favourite, i32::from(true)); }); diff --git a/base_layer/contacts/src/contacts_service/storage/types/contacts.rs b/base_layer/contacts/src/contacts_service/storage/types/contacts.rs index f2666643d0..7ef4e15ddf 100644 --- a/base_layer/contacts/src/contacts_service/storage/types/contacts.rs +++ b/base_layer/contacts/src/contacts_service/storage/types/contacts.rs @@ -144,7 +144,7 @@ impl TryFrom for Contact { let address = TariAddress::from_bytes(&o.address).map_err(|_| ContactsServiceStorageError::ConversionError)?; Ok(Self { // Public key must always be the master data source for node ID here - node_id: NodeId::from_key(address.public_key()), + node_id: NodeId::from_key(address.public_spend_key()), address, alias: o.alias, last_seen: o.last_seen, @@ -164,8 +164,8 @@ impl From for ContactSql { fn from(o: Contact) -> Self { Self { // Public key must always be the master data source for node ID here - node_id: NodeId::from_key(o.address.public_key()).to_vec(), - address: o.address.to_bytes().to_vec(), + node_id: NodeId::from_key(o.address.public_spend_key()).to_vec(), + address: o.address.to_vec(), alias: o.alias, last_seen: o.last_seen, latency: o.latency.map(|val| val as i32), diff --git a/base_layer/contacts/src/contacts_service/storage/types/messages.rs b/base_layer/contacts/src/contacts_service/storage/types/messages.rs index 47ad07624d..1f7669de3a 100644 --- a/base_layer/contacts/src/contacts_service/storage/types/messages.rs +++ b/base_layer/contacts/src/contacts_service/storage/types/messages.rs @@ -207,8 +207,8 @@ impl TryFrom for MessagesSqlInsert { fn try_from(o: Message) -> Result { let metadata = serde_json::to_string(&o.metadata).map_err(|_| ContactsServiceStorageError::ConversionError)?; Ok(Self { - receiver_address: o.receiver_address.to_bytes().to_vec(), - sender_address: o.sender_address.to_bytes().to_vec(), + receiver_address: o.receiver_address.to_vec(), + sender_address: o.sender_address.to_vec(), message_id: o.message_id, body: o.body, metadata: metadata.into_bytes().to_vec(), diff --git a/base_layer/contacts/src/contacts_service/types/contact.rs b/base_layer/contacts/src/contacts_service/types/contact.rs index 19153a786c..c75aeb3806 100644 --- a/base_layer/contacts/src/contacts_service/types/contact.rs +++ b/base_layer/contacts/src/contacts_service/types/contact.rs @@ -44,7 +44,7 @@ impl Contact { ) -> Self { Self { alias, - node_id: NodeId::from_key(address.public_key()), + node_id: NodeId::from_key(address.public_spend_key()), address, last_seen, latency, @@ -58,7 +58,7 @@ impl From<&TariAddress> for Contact { Self { alias: address.to_emoji_string(), address: address.clone(), - node_id: NodeId::from_key(address.public_key()), + node_id: NodeId::from_key(address.public_spend_key()), last_seen: None, latency: None, favourite: false, diff --git a/base_layer/contacts/src/contacts_service/types/message.rs b/base_layer/contacts/src/contacts_service/types/message.rs index f130c29dd8..1238e714ad 100644 --- a/base_layer/contacts/src/contacts_service/types/message.rs +++ b/base_layer/contacts/src/contacts_service/types/message.rs @@ -28,7 +28,6 @@ use serde::{Deserialize, Serialize}; use tari_common_types::tari_address::TariAddress; use tari_comms_dht::domain_message::OutboundDomainMessage; use tari_p2p::tari_message::TariMessageType; -use tari_utilities::ByteArray; use crate::contacts_service::proto; @@ -107,8 +106,8 @@ impl From for proto::Message { .iter() .map(|m| proto::MessageMetadata::from(m.clone())) .collect(), - receiver_address: message.receiver_address.to_bytes().to_vec(), - sender_address: message.sender_address.to_bytes().to_vec(), + receiver_address: message.receiver_address.to_vec(), + sender_address: message.sender_address.to_vec(), direction: i32::from(message.direction.as_byte()), message_id: message.message_id, } diff --git a/base_layer/contacts/tests/contacts_service.rs b/base_layer/contacts/tests/contacts_service.rs index a876dd2849..98a8eb1a99 100644 --- a/base_layer/contacts/tests/contacts_service.rs +++ b/base_layer/contacts/tests/contacts_service.rs @@ -151,7 +151,7 @@ pub fn test_contacts_service() { let mut contacts = Vec::new(); for i in 0..5 { let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - let address = TariAddress::new(public_key, Network::default()); + let address = TariAddress::new_single_address_with_interactive_only(public_key, Network::default()); contacts.push(Contact::new(random::string(8), address, None, None, false)); @@ -169,7 +169,7 @@ pub fn test_contacts_service() { assert_eq!(contact, contacts[0]); let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - let address = TariAddress::new(public_key, Network::default()); + let address = TariAddress::new_single_address_with_interactive_only(public_key, Network::default()); let contact = runtime.block_on(contacts_service.get_contact(address.clone())); match contact { @@ -234,7 +234,7 @@ pub fn test_message_pagination() { let (mut contacts_service, _node_identity, _shutdown) = setup_contacts_service(&mut runtime, backend); let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - let address = TariAddress::new(public_key, Network::default()); + let address = TariAddress::new_single_address_with_interactive_only(public_key, Network::default()); let contact = Contact::new(random::string(8), address.clone(), None, None, false); runtime.block_on(contacts_service.upsert_contact(contact)).unwrap(); diff --git a/base_layer/core/src/common/one_sided.rs b/base_layer/core/src/common/one_sided.rs index cccbf37036..34958668ac 100644 --- a/base_layer/core/src/common/one_sided.rs +++ b/base_layer/core/src/common/one_sided.rs @@ -80,10 +80,10 @@ pub fn diffie_hellman_stealth_domain_hasher( /// Stealth payment script spending key pub fn stealth_address_script_spending_key( dh_domain_hasher: &DomainSeparatedHash>, - destination_public_key: &PublicKey, + spend_key: &PublicKey, ) -> PublicKey { PublicKey::from_secret_key( &PrivateKey::from_uniform_bytes(dh_domain_hasher.as_ref()) .expect("'DomainSeparatedHash>' has correct size"), - ) + destination_public_key + ) + spend_key } diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index d5c54e5135..8d41bc764f 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -73,9 +73,15 @@ pub fn create_orphan_block(block_height: u64, transactions: Vec, co } pub async fn default_coinbase_entities(key_manager: &MemoryDbKeyManager) -> (TariKeyId, TariAddress) { - let wallet_private_key = PrivateKey::random(&mut OsRng); - let script_key_id = key_manager.import_key(wallet_private_key.clone()).await.unwrap(); - let wallet_payment_address = TariAddress::new(PublicKey::from_secret_key(&wallet_private_key), Network::LocalNet); + let wallet_private_spend_key = PrivateKey::random(&mut OsRng); + let wallet_private_view_key = PrivateKey::random(&mut OsRng); + let _key = key_manager.import_key(wallet_private_view_key.clone()).await.unwrap(); + let script_key_id = key_manager.import_key(wallet_private_spend_key.clone()).await.unwrap(); + let wallet_payment_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&wallet_private_view_key), + PublicKey::from_secret_key(&wallet_private_spend_key), + Network::LocalNet, + ); (script_key_id, wallet_payment_address) } diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index ead4800baa..603f780b0f 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -91,8 +91,8 @@ pub enum CoinbaseBuildError { MissingScript, #[error("The range proof type for this coinbase transaction wasn't provided")] MissingRangeProofType, - #[error("The wallet public key for this coinbase transaction wasn't provided")] - MissingWalletPublicKey, + #[error("The wallet public view key for this coinbase transaction wasn't provided")] + MissingWalletPublicViewKey, #[error("The encryption key for this coinbase transaction wasn't provided")] MissingEncryptionKey, #[error("The sender offset key for this coinbase transaction wasn't provided")] @@ -436,7 +436,12 @@ pub async fn generate_coinbase_with_wallet_output( .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await?; let shared_secret = key_manager - .get_diffie_hellman_shared_secret(&sender_offset_key_id, wallet_payment_address.public_key()) + .get_diffie_hellman_shared_secret( + &sender_offset_key_id, + wallet_payment_address + .public_view_key() + .ok_or(CoinbaseBuildError::MissingWalletPublicViewKey)?, + ) .await?; let spending_key = shared_secret_to_output_spending_key(&shared_secret)?; @@ -447,11 +452,16 @@ pub async fn generate_coinbase_with_wallet_output( let script = if stealth_payment { let (nonce_private_key, nonce_public_key) = PublicKey::random_keypair(&mut OsRng); - let c = diffie_hellman_stealth_domain_hasher(&nonce_private_key, wallet_payment_address.public_key()); - let script_spending_key = stealth_address_script_spending_key(&c, wallet_payment_address.public_key()); + let c = diffie_hellman_stealth_domain_hasher( + &nonce_private_key, + wallet_payment_address + .public_view_key() + .ok_or(CoinbaseBuildError::MissingWalletPublicViewKey)?, + ); + let script_spending_key = stealth_address_script_spending_key(&c, wallet_payment_address.public_spend_key()); stealth_payment_script(&nonce_public_key, &script_spending_key) } else { - one_sided_payment_script(wallet_payment_address.public_key()) + one_sided_payment_script(wallet_payment_address.public_spend_key()) }; let (transaction, wallet_output) = CoinbaseBuilder::new(key_manager.clone()) @@ -567,7 +577,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx, _unblinded_output) = builder .build(rules.consensus_constants(42), rules.emission_schedule()) @@ -618,7 +628,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (mut tx, _) = builder .build(rules.consensus_constants(42), rules.emission_schedule()) @@ -653,7 +663,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id.clone()) .with_script_key_id(p.script_key_id.clone()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (mut tx, _) = builder .build(rules.consensus_constants(0), rules.emission_schedule()) @@ -668,7 +678,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id.clone()) .with_script_key_id(p.script_key_id.clone()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (tx2, _) = builder .build(rules.consensus_constants(0), rules.emission_schedule()) @@ -700,7 +710,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (tx3, _) = builder .build(rules.consensus_constants(0), rules.emission_schedule()) @@ -750,7 +760,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id.clone()) .with_script_key_id(p.script_key_id.clone()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (mut tx, _) = builder .build(rules.consensus_constants(0), rules.emission_schedule()) @@ -767,7 +777,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx2, output) = builder .build(rules.consensus_constants(0), rules.emission_schedule()) @@ -882,7 +892,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id.clone()) .with_script_key_id(p.script_key_id.clone()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx1, wo1) = builder .build(rules.consensus_constants(0), rules.emission_schedule()) @@ -899,7 +909,7 @@ mod test { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx2, wo2) = builder .build(rules.consensus_constants(0), rules.emission_schedule()) diff --git a/base_layer/core/src/transactions/key_manager/inner.rs b/base_layer/core/src/transactions/key_manager/inner.rs index f7cdcd3400..b5cd171ac7 100644 --- a/base_layer/core/src/transactions/key_manager/inner.rs +++ b/base_layer/core/src/transactions/key_manager/inner.rs @@ -1175,12 +1175,12 @@ where TBackend: KeyManagerBackend + 'static // Encrypted data section (transactions > transaction_components > encrypted_data) // ----------------------------------------------------------------------------------------------------------------- - async fn get_recovery_key(&self) -> Result { - let recovery_id = KeyId::Managed { + async fn get_view_key(&self) -> Result { + let view_id = KeyId::Managed { branch: TransactionKeyManagerBranch::DataEncryption.get_branch_key(), index: 0, }; - self.get_private_key(&recovery_id).await + self.get_private_key(&view_id).await } pub async fn encrypt_data_for_recovery( @@ -1192,7 +1192,7 @@ where TBackend: KeyManagerBackend + 'static let recovery_key = if let Some(key_id) = custom_recovery_key_id { self.get_private_key(key_id).await? } else { - self.get_recovery_key().await? + self.get_view_key().await? }; let value_key = value.into(); let commitment = self.get_commitment(spend_key_id, &value_key).await?; @@ -1209,7 +1209,7 @@ where TBackend: KeyManagerBackend + 'static let recovery_key = if let Some(key_id) = custom_recovery_key_id { self.get_private_key(key_id).await? } else { - self.get_recovery_key().await? + self.get_view_key().await? }; let (value, private_key) = EncryptedData::decrypt_data(&recovery_key, output.commitment(), output.encrypted_data())?; diff --git a/base_layer/core/src/transactions/key_manager/interface.rs b/base_layer/core/src/transactions/key_manager/interface.rs index e3eae15ecb..b162009608 100644 --- a/base_layer/core/src/transactions/key_manager/interface.rs +++ b/base_layer/core/src/transactions/key_manager/interface.rs @@ -139,7 +139,7 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { value: u64, ) -> Result; - async fn get_recovery_key_id(&self) -> Result; + async fn get_view_key_id(&self) -> Result; async fn get_next_spend_and_script_key_ids( &self, diff --git a/base_layer/core/src/transactions/key_manager/wrapper.rs b/base_layer/core/src/transactions/key_manager/wrapper.rs index 9e316892e0..0ad6025f75 100644 --- a/base_layer/core/src/transactions/key_manager/wrapper.rs +++ b/base_layer/core/src/transactions/key_manager/wrapper.rs @@ -196,7 +196,7 @@ where TBackend: KeyManagerBackend + 'static .await } - async fn get_recovery_key_id(&self) -> Result { + async fn get_view_key_id(&self) -> Result { self.get_static_key(TransactionKeyManagerBranch::DataEncryption.get_branch_key()) .await } diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index 5c30c7a7ba..d8e47b7e44 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -193,7 +193,7 @@ where KM: TransactionKeyManagerInterface Ok(self) } - /// As the Sender adds an output to the transaction. + /// As the Sender add an output to the transaction. pub async fn with_output( &mut self, output: WalletOutput, diff --git a/base_layer/core/src/validation/block_body/test.rs b/base_layer/core/src/validation/block_body/test.rs index 2a689f350e..cc582c1385 100644 --- a/base_layer/core/src/validation/block_body/test.rs +++ b/base_layer/core/src/validation/block_body/test.rs @@ -246,7 +246,7 @@ async fn it_allows_multiple_coinbases() { .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(TariKeyId::default()) .with_script_key_id(TariKeyId::default()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(one_sided_payment_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue) .build_with_reward(blockchain.rules().consensus_constants(1), coinbase.value) .await diff --git a/base_layer/wallet/src/output_manager_service/mod.rs b/base_layer/wallet/src/output_manager_service/mod.rs index ef26e0d5a2..c13ba230b0 100644 --- a/base_layer/wallet/src/output_manager_service/mod.rs +++ b/base_layer/wallet/src/output_manager_service/mod.rs @@ -33,10 +33,11 @@ pub mod service; pub mod storage; mod tasks; -use std::marker::PhantomData; +use std::{marker::PhantomData, sync::Arc}; use futures::future; use log::*; +use tari_comms::NodeIdentity; use tari_core::{ consensus::NetworkConsensus, transactions::{key_manager::TransactionKeyManagerInterface, CryptoFactories}, @@ -59,7 +60,6 @@ use crate::{ service::OutputManagerService, storage::database::{OutputManagerBackend, OutputManagerDatabase}, }, - util::wallet_identity::WalletIdentity, }; /// The maximum number of transaction inputs that can be created in a single transaction, slightly less than the maximum @@ -74,7 +74,7 @@ where T: OutputManagerBackend backend: Option, factories: CryptoFactories, network: NetworkConsensus, - wallet_identity: WalletIdentity, + node_identity: Arc, phantom: PhantomData, } @@ -86,14 +86,14 @@ where T: OutputManagerBackend + 'static backend: T, factories: CryptoFactories, network: NetworkConsensus, - wallet_identity: WalletIdentity, + node_identity: Arc, ) -> Self { Self { config, backend: Some(backend), factories, network, - wallet_identity, + node_identity, phantom: PhantomData, } } @@ -120,7 +120,8 @@ where let factories = self.factories.clone(); let config = self.config.clone(); let constants = self.network.create_consensus_constants().pop().unwrap(); - let wallet_identity = self.wallet_identity.clone(); + let node_identity = self.node_identity.clone(); + let network = self.network.as_network(); context.spawn_when_ready(move |handles| async move { let base_node_service_handle = handles.expect_handle::(); let connectivity = handles.expect_handle::(); @@ -136,7 +137,8 @@ where handles.get_shutdown_signal(), base_node_service_handle, connectivity, - wallet_identity, + node_identity, + network, key_manager, ) .await diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 185eb1cfc1..ad61d1a269 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -26,11 +26,13 @@ use diesel::result::{DatabaseErrorKind, Error as DieselError}; use futures::{pin_mut, StreamExt}; use log::*; use rand::{rngs::OsRng, RngCore}; +use tari_common::configuration::Network; use tari_common_types::{ + tari_address::TariAddress, transaction::TxId, types::{BlockHash, Commitment, HashOutput, PrivateKey, PublicKey}, }; -use tari_comms::types::CommsDHKE; +use tari_comms::{types::CommsDHKE, NodeIdentity}; use tari_core::{ borsh::SerializedSize, consensus::ConsensusConstants, @@ -128,9 +130,15 @@ where shutdown_signal: ShutdownSignal, base_node_service: BaseNodeServiceHandle, connectivity: TWalletConnectivity, - wallet_identity: WalletIdentity, + node_identity: Arc, + network: Network, key_manager: TKeyManagerInterface, ) -> Result { + let view_key = key_manager.get_view_key_id().await?; + let view_key = key_manager.get_public_key_at_key_id(&view_key).await?; + let tari_address = + TariAddress::new_dual_address_with_default_features(view_key, node_identity.public_key().clone(), network); + let wallet_identity = WalletIdentity::new(node_identity.clone(), tari_address); let resources = OutputManagerResources { config, db, @@ -2086,7 +2094,7 @@ where .resources .key_manager .get_diffie_hellman_shared_secret( - &self.resources.wallet_identity.wallet_node_key_id, + &self.resources.key_manager.get_view_key_id().await?, &output.sender_offset_public_key, ) .await?; @@ -2299,6 +2307,7 @@ where let wallet_sk = self.resources.wallet_identity.wallet_node_key_id.clone(); let wallet_pk = self.resources.key_manager.get_public_key_at_key_id(&wallet_sk).await?; + let wallet_view_key = self.resources.key_manager.get_view_key_id().await?; let mut scanned_outputs = vec![]; @@ -2316,7 +2325,7 @@ where let shared_secret = self .resources .key_manager - .get_diffie_hellman_shared_secret(&matched_key.1, &output.sender_offset_public_key) + .get_diffie_hellman_shared_secret(&wallet_view_key, &output.sender_offset_public_key) .await?; scanned_outputs.push(( output.clone(), @@ -2337,7 +2346,7 @@ where let stealth_address_hasher = self .resources .key_manager - .get_diffie_hellman_stealth_domain_hasher(&wallet_sk, nonce.as_ref()) + .get_diffie_hellman_stealth_domain_hasher(&wallet_view_key, nonce.as_ref()) .await?; let script_spending_key = stealth_address_script_spending_key(&stealth_address_hasher, &wallet_pk); if &script_spending_key != scanned_pk.as_ref() { @@ -2356,7 +2365,7 @@ where let shared_secret = self .resources .key_manager - .get_diffie_hellman_shared_secret(&wallet_sk, &output.sender_offset_public_key) + .get_diffie_hellman_shared_secret(&wallet_view_key, &output.sender_offset_public_key) .await?; scanned_outputs.push(( output.clone(), diff --git a/base_layer/wallet/src/transaction_service/error.rs b/base_layer/wallet/src/transaction_service/error.rs index 780430e501..455364cdca 100644 --- a/base_layer/wallet/src/transaction_service/error.rs +++ b/base_layer/wallet/src/transaction_service/error.rs @@ -191,6 +191,8 @@ pub enum TransactionServiceError { TransactionTooLarge { got: usize, expected: usize }, #[error("Pending Transaction was oversized")] Oversized, + #[error("Transaction has invalid address: `{0}`")] + InvalidAddress(String), } impl From for TransactionServiceError { diff --git a/base_layer/wallet/src/transaction_service/mod.rs b/base_layer/wallet/src/transaction_service/mod.rs index c02c48630d..2822a3472a 100644 --- a/base_layer/wallet/src/transaction_service/mod.rs +++ b/base_layer/wallet/src/transaction_service/mod.rs @@ -24,6 +24,8 @@ use std::{marker::PhantomData, sync::Arc}; use futures::{Stream, StreamExt}; use log::*; +use tari_common::configuration::Network; +use tari_comms::NodeIdentity; use tari_comms_dht::Dht; use tari_core::{ consensus::ConsensusManager, @@ -60,7 +62,6 @@ use crate::{ service::TransactionService, storage::database::{TransactionBackend, TransactionDatabase}, }, - util::wallet_identity::WalletIdentity, }; pub mod config; @@ -84,7 +85,8 @@ where config: TransactionServiceConfig, subscription_factory: Arc, tx_backend: Option, - wallet_identity: WalletIdentity, + node_identity: Arc, + network: Network, consensus_manager: ConsensusManager, factories: CryptoFactories, wallet_database: Option>, @@ -101,7 +103,8 @@ where config: TransactionServiceConfig, subscription_factory: Arc, backend: T, - wallet_identity: WalletIdentity, + node_identity: Arc, + network: Network, consensus_manager: ConsensusManager, factories: CryptoFactories, wallet_database: WalletDatabase, @@ -110,7 +113,8 @@ where config, subscription_factory, tx_backend: Some(backend), - wallet_identity, + node_identity, + network, consensus_manager, factories, wallet_database: Some(wallet_database), @@ -222,10 +226,11 @@ where .take() .expect("Cannot start Transaction Service without providing a wallet database"); - let wallet_identity = self.wallet_identity.clone(); + let node_identity = self.node_identity.clone(); let consensus_manager = self.consensus_manager.clone(); let factories = self.factories.clone(); let config = self.config.clone(); + let network = self.network; context.spawn_when_ready(move |handles| async move { let outbound_message_service = handles.expect_handle::().outbound_requester(); @@ -249,12 +254,15 @@ where outbound_message_service, connectivity, publisher, - wallet_identity, + node_identity, + network, consensus_manager, factories, handles.get_shutdown_signal(), base_node_service_handle, ) + .await + .expect("Could not initialize Transaction Manager Service") .start() .await; diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs index c0a16a38aa..c1f4d88de5 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs @@ -315,7 +315,7 @@ where tokio::select! { Some((address, tx_id, tx)) = receiver.recv() => { incoming_finalized_transaction = Some(tx); - if inbound_tx.source_address != address { + if inbound_tx.source_address.public_spend_key() != address.public_spend_key() { warn!( target: LOG_TARGET, "Finalized Transaction did not come from the expected Public Key" diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs index ab12db4f47..ba4f1fca39 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs @@ -495,7 +495,7 @@ where let rr_tx_id = rr.tx_id; reply = Some(rr); - if outbound_tx.destination_address.public_key() != &spk { + if outbound_tx.destination_address.comms_public_key() != &spk { warn!( target: LOG_TARGET, "Transaction Reply did not come from the expected Public Key" @@ -510,7 +510,7 @@ where if result.is_ok() { info!(target: LOG_TARGET, "Cancelling Transaction Send Protocol (TxId: {})", self.id); let _ = send_transaction_cancelled_message( - self.id,self.dest_address.public_key().clone(), + self.id,self.dest_address.comms_public_key().clone(), self.resources.outbound_message_service.clone(), ) .await.map_err(|e| { warn!( @@ -621,7 +621,7 @@ where send_finalized_transaction_message( tx_id, tx.clone(), - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), self.resources.outbound_message_service.clone(), self.resources.config.direct_send_timeout, self.resources.config.transaction_routing_mechanism, @@ -704,7 +704,7 @@ where .resources .outbound_message_service .send_direct_unencrypted( - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), OutboundDomainMessage::new(&TariMessageType::SenderPartialTransaction, proto_message.clone()), "transaction send".to_string(), ) @@ -715,7 +715,7 @@ where if wait_on_dial( send_states, self.id, - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), "Transaction", self.resources.config.direct_send_timeout, ) @@ -793,7 +793,7 @@ where direct_send_result = wait_on_dial( send_states, self.id, - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), "Transaction", self.resources.config.direct_send_timeout, ) @@ -849,8 +849,8 @@ where .resources .outbound_message_service .closest_broadcast( - self.dest_address.public_key().clone(), - OutboundEncryption::encrypt_for(self.dest_address.public_key().clone()), + self.dest_address.comms_public_key().clone(), + OutboundEncryption::encrypt_for(self.dest_address.comms_public_key().clone()), vec![], OutboundDomainMessage::new(&TariMessageType::SenderPartialTransaction, proto_message), ) @@ -940,7 +940,7 @@ where ) -> Result<(), TransactionServiceProtocolError> { let _ = send_transaction_cancelled_message( self.id, - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), self.resources.outbound_message_service.clone(), ) .await diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 3054de0136..d74b6a3414 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -33,13 +33,14 @@ use futures::{pin_mut, stream::FuturesUnordered, Stream, StreamExt}; use log::*; use rand::rngs::OsRng; use sha2::Sha256; +use tari_common::configuration::Network; use tari_common_types::{ burnt_proof::BurntProof, - tari_address::TariAddress, + tari_address::{TariAddress, TariAddressFeatures}, transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, types::{PrivateKey, PublicKey, Signature}, }; -use tari_comms::types::CommsPublicKey; +use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_comms_dht::outbound::OutboundMessageRequester; use tari_core::{ consensus::ConsensusManager, @@ -79,7 +80,7 @@ use tari_crypto::{ }; use tari_key_manager::key_manager_service::KeyId; use tari_p2p::domain_message::DomainMessage; -use tari_script::{inputs, one_sided_payment_script, script, stealth_payment_script, TariScript}; +use tari_script::{inputs, one_sided_payment_script, script, stealth_payment_script, ExecutionStack, TariScript}; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::ShutdownSignal; use tokio::{ @@ -220,7 +221,7 @@ where TWalletConnectivity: WalletConnectivityInterface, TKeyManagerInterface: TransactionKeyManagerInterface, { - pub fn new( + pub async fn new( config: TransactionServiceConfig, db: TransactionDatabase, wallet_db: WalletDatabase, @@ -238,14 +239,20 @@ where outbound_message_service: OutboundMessageRequester, connectivity: TWalletConnectivity, event_publisher: TransactionEventSender, - wallet_identity: WalletIdentity, + node_identity: Arc, + network: Network, consensus_manager: ConsensusManager, factories: CryptoFactories, shutdown_signal: ShutdownSignal, base_node_service: BaseNodeServiceHandle, - ) -> Self { + ) -> Result { // Collect the resources that all protocols will need so that they can be neatly cloned as the protocols are // spawned. + let view_key = core_key_manager_service.get_view_key_id().await?; + let view_key = core_key_manager_service.get_public_key_at_key_id(&view_key).await?; + let tari_address = + TariAddress::new_dual_address_with_default_features(view_key, node_identity.public_key().clone(), network); + let wallet_identity = WalletIdentity::new(node_identity.clone(), tari_address); let resources = TransactionServiceResources { db: db.clone(), output_manager_service, @@ -266,7 +273,7 @@ where }; let timeout_update_watch = Watch::new(timeout); - Self { + Ok(Self { config, db, transaction_stream: Some(transaction_stream), @@ -289,7 +296,7 @@ where last_seen_tip_height: None, validation_in_progress: Arc::new(Mutex::new(())), consensus_manager, - } + }) } #[allow(clippy::too_many_lines)] @@ -989,7 +996,7 @@ where reply_channel: oneshot::Sender>, ) -> Result<(), TransactionServiceError> { let tx_id = TxId::new_random(); - if destination.network() != self.resources.wallet_identity.network { + if destination.network() != self.resources.wallet_identity.address.network() { let _result = reply_channel .send(Err(TransactionServiceError::InvalidNetwork)) .map_err(|e| { @@ -998,9 +1005,8 @@ where }); return Err(TransactionServiceError::InvalidNetwork); } - let dest_pubkey = destination.public_key(); // If we're paying ourselves, let's complete and submit the transaction immediately - if self.resources.wallet_identity.address.public_key() == dest_pubkey { + if self.resources.wallet_identity.address.comms_public_key() == destination.comms_public_key() { debug!( target: LOG_TARGET, "Received transaction with spend-to-self transaction" @@ -1089,7 +1095,6 @@ where JoinHandle>>, >, ) -> Result, TransactionServiceError> { - let dest_pubkey = destination.public_key(); let tx_id = TxId::new_random(); // this can be anything, so lets generate a random private key let pre_image = PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)); @@ -1102,7 +1107,7 @@ where // lets create the HTLC script let script = script!( HashSha256 PushHash(Box::new(hash)) Equal IfThen - PushPubKey(Box::new(dest_pubkey.clone())) + PushPubKey(Box::new(destination.public_spend_key().clone())) Else CheckHeightVerify(height) PushPubKey(Box::new(self.resources.wallet_identity.node_identity.public_key().clone())) EndIf @@ -1160,7 +1165,15 @@ where let shared_secret = self .resources .transaction_key_manager_service - .get_diffie_hellman_shared_secret(&sender_offset_private_key, destination.public_key()) + .get_diffie_hellman_shared_secret( + &sender_offset_private_key, + destination + .public_view_key() + .ok_or(TransactionServiceProtocolError::new( + tx_id, + TransactionServiceError::InvalidAddress("Missing public view key".to_string()), + ))?, + ) .await?; let spending_key = shared_secret_to_output_spending_key(&shared_secret) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; @@ -1203,9 +1216,7 @@ where .with_script(script) .encrypt_data_for_recovery(&self.resources.transaction_key_manager_service, Some(&encryption_key)) .await? - .with_input_data(inputs!(PublicKey::from_secret_key( - self.resources.wallet_identity.node_identity.secret_key() - ))) + .with_input_data(ExecutionStack::default()) .with_covenant(covenant) .with_sender_offset_public_key(sender_offset_public_key) .with_script_key(self.resources.wallet_identity.wallet_node_key_id.clone()) @@ -1355,7 +1366,15 @@ where let shared_secret = self .resources .transaction_key_manager_service - .get_diffie_hellman_shared_secret(&sender_offset_private_key, dest_address.public_key()) + .get_diffie_hellman_shared_secret( + &sender_offset_private_key, + dest_address + .public_view_key() + .ok_or(TransactionServiceProtocolError::new( + tx_id, + TransactionServiceError::OneSidedTransactionError("Missing public view key".to_string()), + ))?, + ) .await?; let spending_key = shared_secret_to_output_spending_key(&shared_secret) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; @@ -1495,16 +1514,16 @@ where JoinHandle>>, >, ) -> Result { - if destination.network() != self.resources.wallet_identity.network { + if destination.network() != self.resources.wallet_identity.address.network() { return Err(TransactionServiceError::InvalidNetwork); } - if self.resources.wallet_identity.node_identity.public_key() == destination.public_key() { + if self.resources.wallet_identity.node_identity.public_key() == destination.comms_public_key() { warn!(target: LOG_TARGET, "One-sided spend-to-self transactions not supported"); return Err(TransactionServiceError::OneSidedTransactionError( "One-sided spend-to-self transactions not supported".to_string(), )); } - let dest_pubkey = destination.public_key().clone(); + let dest_pubkey = destination.public_spend_key().clone(); self.send_one_sided_or_stealth( destination, amount, @@ -1585,11 +1604,7 @@ where .get_next_spend_and_script_key_ids() .await?; - let recovery_key_id = self - .resources - .transaction_key_manager_service - .get_recovery_key_id() - .await?; + let recovery_key_id = self.resources.transaction_key_manager_service.get_view_key_id().await?; let recovery_key_id = match claim_public_key { Some(ref claim_public_key) => { @@ -1826,10 +1841,10 @@ where JoinHandle>>, >, ) -> Result { - if destination.network() != self.resources.wallet_identity.network { + if destination.network() != self.resources.wallet_identity.address.network() { return Err(TransactionServiceError::InvalidNetwork); } - if self.resources.wallet_identity.node_identity.public_key() == destination.public_key() { + if self.resources.wallet_identity.node_identity.public_key() == destination.comms_public_key() { warn!(target: LOG_TARGET, "One-sided spend-to-self transactions not supported"); return Err(TransactionServiceError::OneSidedTransactionError( "One-sided-to-stealth-address spend-to-self transactions not supported".to_string(), @@ -1838,10 +1853,16 @@ where let (nonce_private_key, nonce_public_key) = PublicKey::random_keypair(&mut OsRng); - let dest_pubkey = destination.public_key().clone(); - let c = diffie_hellman_stealth_domain_hasher(&nonce_private_key, &dest_pubkey); + let c = diffie_hellman_stealth_domain_hasher( + &nonce_private_key, + destination + .public_view_key() + .ok_or(TransactionServiceError::OneSidedTransactionError( + "Missing public view key".to_string(), + ))?, + ); - let script_spending_key = stealth_address_script_spending_key(&c, &dest_pubkey); + let script_spending_key = stealth_address_script_spending_key(&c, destination.public_spend_key()); self.send_one_sided_or_stealth( destination, @@ -1907,7 +1928,7 @@ where if let Ok(ctx) = completed_tx { // Check that it is from the same person - if ctx.destination_address.public_key() != &source_pubkey { + if ctx.destination_address.comms_public_key() != &source_pubkey { return Err(TransactionServiceError::InvalidSourcePublicKey); } if !check_cooldown(ctx.last_send_timestamp) { @@ -1954,7 +1975,7 @@ where if let Ok(otx) = cancelled_outbound_tx { // Check that it is from the same person - if otx.destination_address.public_key() != &source_pubkey { + if otx.destination_address.comms_public_key() != &source_pubkey { return Err(TransactionServiceError::InvalidSourcePublicKey); } if !check_cooldown(otx.last_send_timestamp) { @@ -2120,7 +2141,7 @@ where // Check that an inbound transaction exists to be cancelled and that the Source Public key for that transaction // is the same as the cancellation message if let Ok(inbound_tx) = self.db.get_pending_inbound_transaction(tx_id) { - if inbound_tx.source_address.public_key() == &source_pubkey { + if inbound_tx.source_address.comms_public_key() == &source_pubkey { self.cancel_pending_transaction(tx_id).await?; } else { trace!( @@ -2239,7 +2260,7 @@ where if let Ok(Some(any_tx)) = self.db.get_any_cancelled_transaction(data.tx_id) { let tx = CompletedTransaction::from(any_tx); - if tx.source_address.public_key() != &source_pubkey { + if tx.source_address.comms_public_key() != &source_pubkey { return Err(TransactionServiceError::InvalidSourcePublicKey); } trace!( @@ -2259,7 +2280,7 @@ where // Check if this transaction has already been received. if let Ok(inbound_tx) = self.db.get_pending_inbound_transaction(data.tx_id) { // Check that it is from the same person - if inbound_tx.source_address.public_key() != &source_pubkey { + if inbound_tx.source_address.comms_public_key() != &source_pubkey { return Err(TransactionServiceError::InvalidSourcePublicKey); } // Check if the last reply is beyond the resend cooldown @@ -2316,8 +2337,13 @@ where .insert(data.tx_id, tx_finalized_sender); self.receiver_transaction_cancellation_senders .insert(data.tx_id, cancellation_sender); - // we are making the assumption that because we received this transaction, its on the same network as us. - let source_address = TariAddress::new(source_pubkey, self.resources.wallet_identity.network); + // We are recieving an interactive transaction from someone on our network, so we assume its features are + // interactive and its the same network + let source_address = TariAddress::new_single_address( + source_pubkey, + self.resources.wallet_identity.network(), + TariAddressFeatures::INTERACTIVE, + ); let protocol = TransactionReceiveProtocol::new( data.tx_id, source_address, @@ -2371,8 +2397,13 @@ where ) })?; - // assuming since we talked to the node, it has the same identity than - let source_address = TariAddress::new(source_pubkey, self.resources.wallet_identity.network); + // assuming since we talked to the node, that it has an interactive address, we dont know what the view key is + // but we know its interactive, so make the view key 0, and the spend key the source public key. + let source_address = TariAddress::new_single_address( + source_pubkey, + self.resources.wallet_identity.address.network(), + TariAddressFeatures::INTERACTIVE, + ); let sender = match self.finalized_transaction_senders.get_mut(&tx_id) { None => { // First check if perhaps we know about this inbound transaction but it was cancelled diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 74e18145ed..1288ff6037 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -1320,7 +1320,7 @@ impl InboundTransactionSql { .map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?; let i = Self { tx_id: i.tx_id.as_u64() as i64, - source_address: i.source_address.to_bytes().to_vec(), + source_address: i.source_address.to_vec(), amount: u64::from(i.amount) as i64, receiver_protocol: receiver_protocol_bytes.to_vec(), message: i.message, @@ -1568,7 +1568,7 @@ impl OutboundTransactionSql { .map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?; let outbound_tx = Self { tx_id: o.tx_id.as_u64() as i64, - destination_address: o.destination_address.to_bytes().to_vec(), + destination_address: o.destination_address.to_vec(), amount: u64::from(o.amount) as i64, fee: u64::from(o.fee) as i64, sender_protocol: sender_protocol_bytes.to_vec(), @@ -1939,8 +1939,8 @@ impl CompletedTransactionSql { let output = Self { tx_id: c.tx_id.as_u64() as i64, - source_address: c.source_address.to_bytes().to_vec(), - destination_address: c.destination_address.to_bytes().to_vec(), + source_address: c.source_address.to_vec(), + destination_address: c.destination_address.to_vec(), amount: u64::from(c.amount) as i64, fee: u64::from(c.fee) as i64, transaction_protocol: transaction_bytes.to_vec(), @@ -2276,7 +2276,7 @@ mod test { ); let mut stp = builder.build().await.unwrap(); - let address = TariAddress::new( + let address = TariAddress::new_single_address_with_interactive_only( PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2294,7 +2294,7 @@ mod test { send_count: 0, last_send_timestamp: None, }; - let address = TariAddress::new( + let address = TariAddress::new_single_address_with_interactive_only( PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2361,7 +2361,8 @@ mod test { &consensus_constants, ) .await; - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2378,7 +2379,8 @@ mod test { send_count: 0, last_send_timestamp: None, }; - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2431,11 +2433,13 @@ mod test { PrivateKey::random(&mut OsRng), PrivateKey::random(&mut OsRng), ); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2459,11 +2463,13 @@ mod test { mined_in_block: None, mined_timestamp: None, }; - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2634,7 +2640,8 @@ mod test { let key_ga = Key::from_slice(&key); let cipher = XChaCha20Poly1305::new(key_ga); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2660,7 +2667,8 @@ mod test { let decrypted_inbound_tx = InboundTransaction::try_from(db_inbound_tx, &cipher).unwrap(); assert_eq!(inbound_tx, decrypted_inbound_tx); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2688,11 +2696,13 @@ mod test { let decrypted_outbound_tx = OutboundTransaction::try_from(db_outbound_tx, &cipher).unwrap(); assert_eq!(outbound_tx, decrypted_outbound_tx); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2773,7 +2783,8 @@ mod test { }) .expect("Migrations failed"); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2794,7 +2805,8 @@ mod test { inbound_tx_sql.commit(&mut conn).unwrap(); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2816,11 +2828,13 @@ mod test { outbound_tx_sql.commit(&mut conn).unwrap(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2956,11 +2970,13 @@ mod test { 10 => (None, TransactionStatus::MinedConfirmed), _ => (None, TransactionStatus::Completed), }; - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); diff --git a/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs b/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs index 462ba452e1..6cf5b7a34c 100644 --- a/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs +++ b/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs @@ -67,7 +67,7 @@ pub async fn send_transaction_reply( TransactionRoutingMechanism::StoreAndForwardOnly => { send_transaction_reply_store_and_forward( inbound_transaction.tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.public_spend_key().clone(), proto_message.clone(), &mut outbound_message_service, ) @@ -97,7 +97,7 @@ pub async fn send_transaction_reply_direct( .map_err(TransactionServiceError::ServiceError)?; match outbound_message_service .send_direct_unencrypted( - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), OutboundDomainMessage::new(&TariMessageType::ReceiverPartialTransactionReply, proto_message.clone()), "wallet transaction reply".to_string(), ) @@ -108,7 +108,7 @@ pub async fn send_transaction_reply_direct( if wait_on_dial( send_states, tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), "Transaction Reply", direct_send_timeout, ) @@ -127,7 +127,7 @@ pub async fn send_transaction_reply_direct( if transaction_routing_mechanism == TransactionRoutingMechanism::DirectAndStoreAndForward { store_and_forward_send_result = send_transaction_reply_store_and_forward( tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), proto_message.clone(), &mut outbound_message_service, ) @@ -142,7 +142,7 @@ pub async fn send_transaction_reply_direct( if transaction_routing_mechanism == TransactionRoutingMechanism::DirectAndStoreAndForward { store_and_forward_send_result = send_transaction_reply_store_and_forward( tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), proto_message.clone(), &mut outbound_message_service, ) @@ -153,7 +153,7 @@ pub async fn send_transaction_reply_direct( if transaction_routing_mechanism == TransactionRoutingMechanism::DirectAndStoreAndForward { store_and_forward_send_result = send_transaction_reply_store_and_forward( tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), proto_message.clone(), &mut outbound_message_service, ) @@ -169,7 +169,7 @@ pub async fn send_transaction_reply_direct( direct_send_result = wait_on_dial( send_states, tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), "Transaction Reply", direct_send_timeout, ) diff --git a/base_layer/wallet/src/util/wallet_identity.rs b/base_layer/wallet/src/util/wallet_identity.rs index 37340c417c..f545a9caaf 100644 --- a/base_layer/wallet/src/util/wallet_identity.rs +++ b/base_layer/wallet/src/util/wallet_identity.rs @@ -30,30 +30,32 @@ use tari_core::transactions::key_manager::TariKeyId; #[derive(Clone, Debug)] pub struct WalletIdentity { pub node_identity: Arc, - pub network: Network, pub address: TariAddress, pub wallet_node_key_id: TariKeyId, } impl WalletIdentity { - pub fn new(node_identity: Arc, network: Network) -> Self { - let address = TariAddress::new(node_identity.public_key().clone(), network); + pub fn new(node_identity: Arc, address: TariAddress) -> Self { let wallet_node_key_id = TariKeyId::Imported { key: node_identity.public_key().clone(), }; WalletIdentity { node_identity, - network, address, wallet_node_key_id, } } + + pub fn network(&self) -> Network { + self.address.network() + } } impl Display for WalletIdentity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "{}", self.node_identity)?; - writeln!(f, "Network: {:?}", self.network)?; + writeln!(f, "Tari Address: {}", self.address)?; + writeln!(f, "Network: {:?}", self.address.network())?; Ok(()) } } diff --git a/base_layer/wallet/src/utxo_scanner_service/initializer.rs b/base_layer/wallet/src/utxo_scanner_service/initializer.rs index 14df2190aa..5eb1f76de4 100644 --- a/base_layer/wallet/src/utxo_scanner_service/initializer.rs +++ b/base_layer/wallet/src/utxo_scanner_service/initializer.rs @@ -20,10 +20,13 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::{marker::PhantomData, sync::Arc}; + use futures::future; use log::*; -use tari_comms::connectivity::ConnectivityRequester; -use tari_core::transactions::CryptoFactories; +use tari_common::configuration::Network; +use tari_comms::{connectivity::ConnectivityRequester, NodeIdentity}; +use tari_core::transactions::{key_manager::TransactionKeyManagerInterface, CryptoFactories}; use tari_service_framework::{async_trait, ServiceInitializationError, ServiceInitializer, ServiceInitializerContext}; use tokio::sync::broadcast; @@ -33,7 +36,7 @@ use crate::{ output_manager_service::handle::OutputManagerHandle, storage::database::{WalletBackend, WalletDatabase}, transaction_service::handle::TransactionServiceHandle, - util::{wallet_identity::WalletIdentity, watch::Watch}, + util::watch::Watch, utxo_scanner_service::{ handle::UtxoScannerHandle, service::UtxoScannerService, @@ -43,27 +46,38 @@ use crate::{ const LOG_TARGET: &str = "wallet::utxo_scanner_service::initializer"; -pub struct UtxoScannerServiceInitializer { +pub struct UtxoScannerServiceInitializer { backend: Option>, factories: CryptoFactories, - wallet_identity: WalletIdentity, + node_identity: Arc, + network: Network, + phantom: PhantomData, } -impl UtxoScannerServiceInitializer +impl UtxoScannerServiceInitializer where T: WalletBackend + 'static { - pub fn new(backend: WalletDatabase, factories: CryptoFactories, wallet_identity: WalletIdentity) -> Self { + pub fn new( + backend: WalletDatabase, + factories: CryptoFactories, + node_identity: Arc, + network: Network, + ) -> Self { Self { backend: Some(backend), factories, - wallet_identity, + node_identity, + network, + phantom: PhantomData, } } } #[async_trait] -impl ServiceInitializer for UtxoScannerServiceInitializer -where T: WalletBackend + 'static +impl ServiceInitializer for UtxoScannerServiceInitializer +where + T: WalletBackend + 'static, + TKeyManagerInterface: TransactionKeyManagerInterface, { async fn initialize(&mut self, context: ServiceInitializerContext) -> Result<(), ServiceInitializationError> { trace!(target: LOG_TARGET, "Utxo scanner initialization"); @@ -86,7 +100,8 @@ where T: WalletBackend + 'static .take() .expect("Cannot start Utxo scanner service without setting a storage backend"); let factories = self.factories.clone(); - let wallet_identity = self.wallet_identity.clone(); + let node_identity = self.node_identity.clone(); + let network = self.network; context.spawn_when_ready(move |handles| async move { let transaction_service = handles.expect_handle::(); @@ -94,6 +109,7 @@ where T: WalletBackend + 'static let comms_connectivity = handles.expect_handle::(); let wallet_connectivity = handles.expect_handle::(); let base_node_service_handle = handles.expect_handle::(); + let key_manager = handles.expect_handle::(); let scanning_service = UtxoScannerService::::builder() .with_peers(vec![]) @@ -105,7 +121,9 @@ where T: WalletBackend + 'static wallet_connectivity.clone(), output_manager_service, transaction_service, - wallet_identity, + key_manager, + node_identity, + network, factories, handles.get_shutdown_signal(), event_sender, @@ -113,6 +131,8 @@ where T: WalletBackend + 'static one_sided_message_watch_receiver, recovery_message_watch_receiver, ) + .await + .expect("Could not initialize UTXO scanner Service") .run(); futures::pin_mut!(scanning_service); diff --git a/base_layer/wallet/src/utxo_scanner_service/uxto_scanner_service_builder.rs b/base_layer/wallet/src/utxo_scanner_service/uxto_scanner_service_builder.rs index 13a8509421..7ee066b428 100644 --- a/base_layer/wallet/src/utxo_scanner_service/uxto_scanner_service_builder.rs +++ b/base_layer/wallet/src/utxo_scanner_service/uxto_scanner_service_builder.rs @@ -20,8 +20,13 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use tari_comms::{connectivity::ConnectivityRequester, types::CommsPublicKey}; -use tari_core::transactions::CryptoFactories; +use std::sync::Arc; + +use tari_common::configuration::Network; +use tari_common_types::tari_address::TariAddress; +use tari_comms::{connectivity::ConnectivityRequester, types::CommsPublicKey, NodeIdentity}; +use tari_core::transactions::{key_manager::TransactionKeyManagerInterface, CryptoFactories}; +use tari_key_manager::key_manager_service::{KeyManagerInterface, KeyManagerServiceError}; use tari_shutdown::ShutdownSignal; use tokio::sync::{broadcast, watch}; @@ -98,12 +103,22 @@ impl UtxoScannerServiceBuilder { self } - pub fn build_with_wallet( + pub async fn build_with_wallet( &mut self, wallet: &WalletSqlite, shutdown_signal: ShutdownSignal, - ) -> UtxoScannerService { - let wallet_identity = WalletIdentity::new(wallet.comms.node_identity(), wallet.network.as_network()); + ) -> Result, KeyManagerServiceError> { + let view_key_id = wallet.key_manager_service.get_view_key_id().await?; + let view_key = wallet + .key_manager_service + .get_public_key_at_key_id(&view_key_id) + .await?; + let tari_address = TariAddress::new_dual_address_with_default_features( + view_key, + wallet.comms.node_identity().public_key().clone(), + wallet.network.as_network(), + ); + let wallet_identity = WalletIdentity::new(wallet.comms.node_identity(), tari_address); let resources = UtxoScannerResources { db: wallet.db.clone(), comms_connectivity: wallet.comms.connectivity(), @@ -119,7 +134,7 @@ impl UtxoScannerServiceBuilder { let (event_sender, _) = broadcast::channel(200); - UtxoScannerService::new( + Ok(UtxoScannerService::new( self.peers.drain(..).collect(), self.retry_limit, self.mode.clone().unwrap_or_default(), @@ -129,24 +144,35 @@ impl UtxoScannerServiceBuilder { wallet.base_node_service.clone(), wallet.utxo_scanner_service.get_one_sided_payment_message_watcher(), wallet.utxo_scanner_service.get_recovery_message_watcher(), - ) + )) } - pub fn build_with_resources( + pub async fn build_with_resources< + TBackend: WalletBackend + 'static, + TWalletConnectivity: WalletConnectivityInterface, + TKeyManagerInterface: TransactionKeyManagerInterface, + >( &mut self, db: WalletDatabase, comms_connectivity: ConnectivityRequester, wallet_connectivity: TWalletConnectivity, output_manager_service: OutputManagerHandle, transaction_service: TransactionServiceHandle, - wallet_identity: WalletIdentity, + key_manager: TKeyManagerInterface, + node_identity: Arc, + network: Network, factories: CryptoFactories, shutdown_signal: ShutdownSignal, event_sender: broadcast::Sender, base_node_service: BaseNodeServiceHandle, one_sided_message_watch: watch::Receiver, recovery_message_watch: watch::Receiver, - ) -> UtxoScannerService { + ) -> Result, KeyManagerServiceError> { + let view_key_id = key_manager.get_view_key_id().await?; + let view_key = key_manager.get_public_key_at_key_id(&view_key_id).await?; + let tari_address = + TariAddress::new_dual_address_with_default_features(view_key, node_identity.public_key().clone(), network); + let wallet_identity = WalletIdentity::new(node_identity.clone(), tari_address); let resources = UtxoScannerResources { db, comms_connectivity, @@ -160,7 +186,7 @@ impl UtxoScannerServiceBuilder { one_sided_payment_message: self.one_sided_message.clone(), }; - UtxoScannerService::new( + Ok(UtxoScannerService::new( self.peers.drain(..).collect(), self.retry_limit, self.mode.clone().unwrap_or_default(), @@ -170,6 +196,6 @@ impl UtxoScannerServiceBuilder { base_node_service, one_sided_message_watch, recovery_message_watch, - ) + )) } } diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 726f5ddb42..990d315873 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -54,7 +54,7 @@ use tari_core::{ consensus::{ConsensusManager, NetworkConsensus}, covenants::Covenant, transactions::{ - key_manager::{SecretTransactionKeyManagerInterface, TransactionKeyManagerInitializer}, + key_manager::{SecretTransactionKeyManagerInterface, TariKeyId, TransactionKeyManagerInitializer}, tari_amount::MicroMinotari, transaction_components::{EncryptedData, OutputFeatures, UnblindedOutput}, CryptoFactories, @@ -186,7 +186,6 @@ where config.transaction_service_config, config.buffer_size, ); - let wallet_identity = WalletIdentity::new(node_identity.clone(), config.network); let stack = StackBuilder::new(shutdown_signal) .add_initializer(P2pInitializer::new( config.p2p.clone(), @@ -201,7 +200,7 @@ where output_manager_backend.clone(), factories.clone(), config.network.into(), - wallet_identity.clone(), + node_identity.clone(), )) .add_initializer(TransactionKeyManagerInitializer::new( key_manager_backend, @@ -213,7 +212,8 @@ where config.transaction_service_config, peer_message_subscription_factory.clone(), transaction_backend, - wallet_identity.clone(), + node_identity.clone(), + config.network, consensus_manager, factories.clone(), wallet_database.clone(), @@ -238,10 +238,11 @@ where wallet_database.clone(), )) .add_initializer(WalletConnectivityInitializer::new(config.base_node_service_config)) - .add_initializer(UtxoScannerServiceInitializer::new( + .add_initializer(UtxoScannerServiceInitializer::::new( wallet_database.clone(), factories.clone(), - wallet_identity.clone(), + node_identity.clone(), + config.network, )); // Check if we have update config. FFI wallets don't do this, the update on mobile is done differently. @@ -319,7 +320,7 @@ where None }; - persist_one_sided_payment_script_for_node_identity(&mut output_manager_handle, wallet_identity.clone()) + persist_one_sided_payment_script_for_node_identity(&mut output_manager_handle, &node_identity) .await .map_err(|e| { error!(target: LOG_TARGET, "{:?}", e); @@ -476,6 +477,27 @@ where } } + pub async fn get_wallet_address(&self) -> Result { + let view_key_id = self.key_manager_service.get_view_key_id().await?; + let view_key = self.key_manager_service.get_public_key_at_key_id(&view_key_id).await?; + Ok(TariAddress::new_dual_address_with_default_features( + view_key.clone(), + self.comms.node_identity().public_key().clone(), + self.network.as_network(), + )) + } + + pub async fn get_wallet_id(&self) -> Result { + let view_key_id = self.key_manager_service.get_view_key_id().await?; + let view_key = self.key_manager_service.get_public_key_at_key_id(&view_key_id).await?; + let address = TariAddress::new_dual_address_with_default_features( + view_key.clone(), + self.comms.node_identity().public_key().clone(), + self.network.as_network(), + ); + Ok(WalletIdentity::new(self.comms.node_identity(), address)) + } + pub fn get_software_updater(&self) -> Option { self.updater_service.as_ref().cloned() } @@ -801,15 +823,18 @@ pub fn derive_comms_secret_key(master_seed: &CipherSeed) -> Result, ) -> Result<(), WalletError> { - let script = one_sided_payment_script(wallet_identity.node_identity.public_key()); + let script = one_sided_payment_script(node_identity.public_key()); + let wallet_node_key_id = TariKeyId::Imported { + key: node_identity.public_key().clone(), + }; let known_script = KnownOneSidedPaymentScript { script_hash: script .as_hash::>() .map_err(|e| WalletError::OutputManagerError(OutputManagerError::ScriptError(e)))? .to_vec(), - script_key_id: wallet_identity.wallet_node_key_id.clone(), + script_key_id: wallet_node_key_id.clone(), script, input: ExecutionStack::default(), script_lock_height: 0, diff --git a/base_layer/wallet/tests/other/mod.rs b/base_layer/wallet/tests/other/mod.rs index 592af4c345..1ce90e1003 100644 --- a/base_layer/wallet/tests/other/mod.rs +++ b/base_layer/wallet/tests/other/mod.rs @@ -250,7 +250,7 @@ async fn test_wallet() { .await .unwrap(); let bob_identity = (*bob_wallet.comms.node_identity()).clone(); - let bob_address = TariAddress::new(bob_identity.public_key().clone(), network); + let bob_address = TariAddress::new_single_address_with_interactive_only(bob_identity.public_key().clone(), network); alice_wallet .comms @@ -320,7 +320,7 @@ async fn test_wallet() { let mut contacts = Vec::new(); for i in 0..2 { let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - let address = TariAddress::new(public_key, Network::LocalNet); + let address = TariAddress::new_single_address_with_interactive_only(public_key, Network::LocalNet); contacts.push(Contact::new(random::string(8), address, None, None, false)); @@ -571,7 +571,7 @@ async fn test_store_and_forward_send_tx() { .unwrap(); let carol_identity = carol_wallet.comms.node_identity(); - let carol_address = TariAddress::new(carol_identity.public_key().clone(), network); + let carol_address = TariAddress::new_single_address_with_interactive_only(carol_identity.public_key().clone(), network); let mut carol_event_stream = carol_wallet.transaction_service.get_event_stream(); alice_wallet @@ -738,7 +738,7 @@ async fn test_import_utxo() { let utxo = create_wallet_output_with_data(script.clone(), temp_features, &p, 20000 * uT, &key_manager).await.unwrap(); let output = utxo.as_transaction_output(&key_manager).unwrap(); let expected_output_hash = output.hash(); - let node_address = TariAddress::new(node_identity.public_key().clone(), network); + let node_address = TariAddress::new_single_address_with_interactive_only(node_identity.public_key().clone(), network); alice_wallet .set_base_node_peer( node_identity.public_key().clone(), @@ -860,7 +860,7 @@ async fn test_contacts_service_liveness() { .await .unwrap(); let alice_identity = alice_wallet.comms.node_identity(); - let alice_address = TariAddress::new(alice_identity.public_key().clone(), network); + let alice_address = TariAddress::new_single_address_with_interactive_only(alice_identity.public_key().clone(), network); let mut bob_wallet = create_wallet( bob_db_tempdir.path(), @@ -874,7 +874,7 @@ async fn test_contacts_service_liveness() { .await .unwrap(); let bob_identity = (*bob_wallet.comms.node_identity()).clone(); - let bob_address = TariAddress::new(bob_identity.public_key().clone(), network); + let bob_address = TariAddress::new_single_address_with_interactive_only(bob_identity.public_key().clone(), network); alice_wallet .comms diff --git a/base_layer/wallet/tests/output_manager_service_tests/service.rs b/base_layer/wallet/tests/output_manager_service_tests/service.rs index 91b61ba84e..d9e9ccf564 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -40,7 +40,6 @@ use minotari_wallet::{ }, test_utils::create_consensus_constants, transaction_service::handle::TransactionServiceHandle, - util::wallet_identity::WalletIdentity, }; use rand::{rngs::OsRng, RngCore}; use tari_common::configuration::Network; @@ -162,7 +161,6 @@ async fn setup_output_manager_service( let key_manager = create_memory_db_key_manager(); - let wallet_identity = WalletIdentity::new(server_node_identity.clone(), Network::LocalNet); let output_manager_service = OutputManagerService::new( OutputManagerServiceConfig { ..Default::default() }, oms_request_receiver, @@ -173,7 +171,8 @@ async fn setup_output_manager_service( shutdown.to_signal(), basenode_service_handle, wallet_connectivity_mock.clone(), - wallet_identity, + server_node_identity.clone(), + Network::LocalNet, key_manager.clone(), ) .await @@ -228,7 +227,6 @@ pub async fn setup_oms_with_bn_state( task::spawn(mock_base_node_service.run()); let connectivity = create_wallet_connectivity_mock(); let key_manager = create_memory_db_key_manager(); - let wallet_identity = WalletIdentity::new(node_identity.clone(), Network::LocalNet); let output_manager_service = OutputManagerService::new( OutputManagerServiceConfig { ..Default::default() }, oms_request_receiver, @@ -239,7 +237,8 @@ pub async fn setup_oms_with_bn_state( shutdown.to_signal(), base_node_service_handle.clone(), connectivity, - wallet_identity, + node_identity.clone(), + Network::LocalNet, key_manager.clone(), ) .await diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index 7a2e8d32bf..91adf8f732 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -81,7 +81,6 @@ use minotari_wallet::{ }, TransactionServiceInitializer, }, - util::wallet_identity::WalletIdentity, }; use prost::Message; use rand::{rngs::OsRng, RngCore}; @@ -118,7 +117,11 @@ use tari_core::{ }, consensus::{ConsensusConstantsBuilder, ConsensusManager}, covenants::Covenant, - one_sided::shared_secret_to_output_encryption_key, + one_sided::{ + diffie_hellman_stealth_domain_hasher, + shared_secret_to_output_encryption_key, + stealth_address_script_spending_key, + }, proto::base_node as base_node_proto, transactions::{ fee::Fee, @@ -154,7 +157,7 @@ use tari_key_manager::{ key_manager_service::{storage::sqlite_db::KeyManagerSqliteDatabase, KeyId, KeyManagerInterface}, }; use tari_p2p::{comms_connector::pubsub_connector, domain_message::DomainMessage, Network}; -use tari_script::{inputs, one_sided_payment_script, script, ExecutionStack}; +use tari_script::{inputs, one_sided_payment_script, script, stealth_payment_script, ExecutionStack}; use tari_service_framework::{reply_channel, RegisterHandle, StackBuilder}; use tari_shutdown::{Shutdown, ShutdownSignal}; use tari_test_utils::{comms_and_services::get_next_memory_address, random}; @@ -215,7 +218,6 @@ async fn setup_transaction_service>( let ts_backend = TransactionServiceSqliteDatabase::new(db_connection.clone(), cipher.clone()); let oms_backend = OutputManagerSqliteDatabase::new(db_connection.clone()); - let wallet_identity = WalletIdentity::new(node_identity, Network::LocalNet); let connection = DbConnection::connect_url(&DbConnectionUrl::MemoryShared(random_string(8))).unwrap(); let cipher = CipherSeed::new(); @@ -236,7 +238,7 @@ async fn setup_transaction_service>( oms_backend.clone(), factories.clone(), Network::LocalNet.into(), - wallet_identity.clone(), + node_identity.clone(), )) .add_initializer(TransactionKeyManagerInitializer::>::new( kms_backend, @@ -254,7 +256,8 @@ async fn setup_transaction_service>( }, subscription_factory, ts_backend, - wallet_identity, + node_identity.clone(), + Network::LocalNet, consensus_manager, factories, db.clone(), @@ -378,7 +381,6 @@ async fn setup_transaction_service_no_comms( let ts_db = TransactionDatabase::new(ts_service_db.clone()); let key_manager = create_memory_db_key_manager(); let oms_db = OutputManagerDatabase::new(OutputManagerSqliteDatabase::new(db_connection)); - let wallet_identity = WalletIdentity::new(node_identity.clone(), Network::LocalNet); let output_manager_service = OutputManagerService::new( OutputManagerServiceConfig::default(), oms_request_receiver, @@ -389,7 +391,8 @@ async fn setup_transaction_service_no_comms( shutdown.to_signal(), base_node_service_handle.clone(), wallet_connectivity_service_mock.clone(), - wallet_identity, + node_identity.clone(), + Network::LocalNet, key_manager.clone(), ) .await @@ -411,7 +414,6 @@ async fn setup_transaction_service_no_comms( max_tx_query_batch_size: 2, ..Default::default() }); - let wallet_identity = WalletIdentity::new(node_identity.clone(), Network::LocalNet); let ts_service = TransactionService::new( test_config, ts_db.clone(), @@ -427,12 +429,15 @@ async fn setup_transaction_service_no_comms( outbound_message_requester, wallet_connectivity_service_mock.clone(), event_publisher, - wallet_identity, + node_identity.clone(), + Network::LocalNet, consensus_manager, factories, shutdown.to_signal(), base_node_service_handle, - ); + ) + .await + .unwrap(); task::spawn(async move { output_manager_service.start().await.unwrap() }); task::spawn(async move { ts_service.start().await.unwrap() }); TransactionServiceNoCommsInterface { @@ -590,7 +595,8 @@ async fn manage_single_transaction() { &alice_key_manager_handle, ) .await; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); assert!(alice_ts .send_transaction( bob_address.clone(), @@ -763,7 +769,8 @@ async fn large_interactive_transaction() { } alice_db.mark_outputs_as_unspent(unspent).unwrap(); let transaction_value = output_value * (outputs_count as u64 - 1); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); let message = "TAKE MAH MONEYS!".to_string(); alice_ts @@ -926,7 +933,8 @@ async fn test_spend_dust_to_self_in_oversized_transaction() { let fee_per_gram = MicroMinotari::from(1); let message = "TAKE MAH _OWN_ MONEYS!".to_string(); let value = balance.available_balance - amount_per_output * 10; - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), network); + let alice_address = + TariAddress::new_single_address_with_interactive_only(alice_node_identity.public_key().clone(), network); assert!(alice_ts .send_transaction( alice_address, @@ -1023,7 +1031,8 @@ async fn test_spend_dust_to_other_in_oversized_transaction() { let fee_per_gram = MicroMinotari::from(1); let message = "GIVE MAH _OWN_ MONEYS AWAY!".to_string(); let value = balance.available_balance - amount_per_output * 10; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( bob_address, @@ -1138,7 +1147,8 @@ async fn test_spend_dust_happy_path() { let message = "TAKE MAH _OWN_ MONEYS!".to_string(); let value_self = (number_of_outputs / 3) * amount_per_output; - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), network); + let alice_address = + TariAddress::new_single_address_with_interactive_only(alice_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( alice_address, @@ -1182,7 +1192,8 @@ async fn test_spend_dust_happy_path() { let message = "GIVE MAH _OWN_ MONEYS AWAY!".to_string(); let value_bob = (number_of_outputs / 3) * amount_per_output; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( bob_address, @@ -1282,7 +1293,8 @@ async fn single_transaction_to_self() { .unwrap(); let message = "TAKE MAH _OWN_ MONEYS!".to_string(); let value = 10000.into(); - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), network); + let alice_address = + TariAddress::new_single_address_with_interactive_only(alice_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( alice_address, @@ -1603,7 +1615,13 @@ async fn send_one_sided_transaction_to_other() { let message = "SEE IF YOU CAN CATCH THIS ONE..... SIDED TX!".to_string(); let value = 10000.into(); let mut alice_ts_clone = alice_ts.clone(); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let random_pvt_key = PrivateKey::random(&mut OsRng); + let bob_view_key = PublicKey::from_secret_key(&random_pvt_key); + let bob_address = TariAddress::new_dual_address_with_default_features( + bob_view_key, + bob_node_identity.public_key().clone(), + network, + ); let tx_id = alice_ts_clone .send_one_sided_transaction( bob_address, @@ -1746,7 +1764,160 @@ async fn recover_one_sided_transaction() { let message = "".to_string(); let value = 10000.into(); let mut alice_ts_clone = alice_ts.clone(); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_view_key_id = bob_key_manager_handle.get_view_key_id().await.unwrap(); + let bob_view_key = bob_key_manager_handle + .get_public_key_at_key_id(&bob_view_key_id) + .await + .unwrap(); + let bob_address = TariAddress::new_dual_address_with_default_features( + bob_view_key, + bob_node_identity.public_key().clone(), + network, + ); + let tx_id = alice_ts_clone + .send_one_sided_transaction( + bob_address, + value, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), + 20.into(), + message.clone(), + ) + .await + .expect("Alice sending one-sided tx to Bob"); + + let completed_tx = alice_ts + .get_completed_transaction(tx_id) + .await + .expect("Could not find completed one-sided tx"); + let outputs = completed_tx.transaction.body.outputs().clone(); + + let recovered_outputs_1 = bob_oms + .scan_outputs_for_one_sided_payments(outputs.clone()) + .await + .unwrap(); + // Bob should be able to claim 1 output. + assert_eq!(1, recovered_outputs_1.len()); + assert_eq!(value, recovered_outputs_1[0].output.value); + + // Should ignore already existing outputs + let recovered_outputs_2 = bob_oms.scan_outputs_for_one_sided_payments(outputs).await.unwrap(); + assert!(recovered_outputs_2.is_empty()); +} + +#[tokio::test] +async fn recover_stealth_one_sided_transaction() { + let network = Network::LocalNet; + let consensus_manager = ConsensusManager::builder(network).build().unwrap(); + let factories = CryptoFactories::default(); + // Alice's parameters + let alice_node_identity = Arc::new(NodeIdentity::random( + &mut OsRng, + get_next_memory_address(), + PeerFeatures::COMMUNICATION_NODE, + )); + + // Bob's parameters + let bob_node_identity = Arc::new(NodeIdentity::random( + &mut OsRng, + get_next_memory_address(), + PeerFeatures::COMMUNICATION_NODE, + )); + + let base_node_identity = Arc::new(NodeIdentity::random( + &mut OsRng, + get_next_memory_address(), + PeerFeatures::COMMUNICATION_NODE, + )); + + log::info!( + "manage_single_transaction: Alice: '{}', Bob: '{}', Base: '{}'", + alice_node_identity.node_id().short_str(), + bob_node_identity.node_id().short_str(), + base_node_identity.node_id().short_str() + ); + + let temp_dir = tempdir().unwrap(); + let temp_dir2 = tempdir().unwrap(); + let database_path = temp_dir.path().to_str().unwrap().to_string(); + let database_path2 = temp_dir2.path().to_str().unwrap().to_string(); + + let alice_connection = make_wallet_database_memory_connection(); + let bob_connection = make_wallet_database_memory_connection(); + + let shutdown = Shutdown::new(); + let (mut alice_ts, alice_oms, _alice_comms, _alice_connectivity, alice_key_manager_handle, alice_db) = + setup_transaction_service( + alice_node_identity, + vec![], + consensus_manager.clone(), + factories.clone(), + alice_connection, + database_path, + Duration::from_secs(0), + shutdown.to_signal(), + ) + .await; + + let (_bob_ts, mut bob_oms, _bob_comms, _bob_connectivity, bob_key_manager_handle, _bob_db) = + setup_transaction_service( + bob_node_identity.clone(), + vec![], + consensus_manager, + factories.clone(), + bob_connection, + database_path2, + Duration::from_secs(0), + shutdown.to_signal(), + ) + .await; + + let bob_view_key_id = bob_key_manager_handle.get_view_key_id().await.unwrap(); + let bob_view_key = bob_key_manager_handle + .get_public_key_at_key_id(&bob_view_key_id) + .await + .unwrap(); + + let (nonce_private_key, nonce_public_key) = PublicKey::random_keypair(&mut OsRng); + let c = diffie_hellman_stealth_domain_hasher(&nonce_private_key, &bob_view_key); + let script_spending_key = stealth_address_script_spending_key(&c, bob_node_identity.public_key()); + let script = stealth_payment_script(&nonce_public_key, &script_spending_key); + let known_script = KnownOneSidedPaymentScript { + script_hash: script.as_hash::>().unwrap().to_vec(), + script_key_id: bob_key_manager_handle + .import_key(bob_node_identity.secret_key().clone()) + .await + .unwrap(), + script, + input: ExecutionStack::default(), + script_lock_height: 0, + }; + let mut cloned_bob_oms = bob_oms.clone(); + cloned_bob_oms.add_known_script(known_script).await.unwrap(); + + let initial_wallet_value = 25000.into(); + let uo1 = make_input( + &mut OsRng, + initial_wallet_value, + &OutputFeatures::default(), + &alice_key_manager_handle, + ) + .await; + let mut alice_oms_clone = alice_oms; + alice_oms_clone.add_output(uo1.clone(), None).await.unwrap(); + alice_db + .mark_outputs_as_unspent(vec![(uo1.hash(&alice_key_manager_handle).await.unwrap(), true)]) + .unwrap(); + + let message = "".to_string(); + let value = 10000.into(); + let mut alice_ts_clone = alice_ts.clone(); + + let bob_address = TariAddress::new_dual_address_with_default_features( + bob_view_key, + bob_node_identity.public_key().clone(), + network, + ); let tx_id = alice_ts_clone .send_one_sided_transaction( bob_address, @@ -1851,7 +2022,13 @@ async fn test_htlc_send_and_claim() { let message = "".to_string(); let value = 10000.into(); let bob_pubkey = bob_ts_interface.base_node_identity.public_key().clone(); - let bob_address = TariAddress::new(bob_pubkey.clone(), Network::LocalNet); + let bob_view_key_id = bob_ts_interface.key_manager_handle.get_view_key_id().await.unwrap(); + let bob_view_key = bob_ts_interface + .key_manager_handle + .get_public_key_at_key_id(&bob_view_key_id) + .await + .unwrap(); + let bob_address = TariAddress::new_dual_address_with_default_features(bob_view_key, bob_pubkey.clone(), network); let (tx_id, pre_image, output) = alice_ts .send_sha_atomic_swap_transaction( bob_address, @@ -1975,7 +2152,8 @@ async fn send_one_sided_transaction_to_self() { let message = "SEE IF YOU CAN CATCH THIS ONE..... SIDED TX!".to_string(); let value = 1000.into(); let mut alice_ts_clone = alice_ts; - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), network); + let alice_address = + TariAddress::new_single_address_with_interactive_only(alice_node_identity.public_key().clone(), network); match alice_ts_clone .send_one_sided_transaction( alice_address, @@ -2168,7 +2346,10 @@ async fn manage_multiple_transactions() { let value_b_to_a_1 = MicroMinotari::from(11000); let value_a_to_c_1 = MicroMinotari::from(14000); log::trace!("Sending A to B 1"); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id_a_to_b_1 = alice_ts .send_transaction( bob_address.clone(), @@ -2182,7 +2363,10 @@ async fn manage_multiple_transactions() { .unwrap(); log::trace!("A to B 1 TxID: {}", tx_id_a_to_b_1); log::trace!("Sending A to C 1"); - let carol_address = TariAddress::new(carol_node_identity.public_key().clone(), Network::LocalNet); + let carol_address = TariAddress::new_single_address_with_interactive_only( + carol_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id_a_to_c_1 = alice_ts .send_transaction( carol_address, @@ -2198,7 +2382,10 @@ async fn manage_multiple_transactions() { assert_eq!(alice_completed_tx.len(), 0); log::trace!("A to C 1 TxID: {}", tx_id_a_to_c_1); - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), Network::LocalNet); + let alice_address = TariAddress::new_single_address_with_interactive_only( + alice_node_identity.public_key().clone(), + Network::LocalNet, + ); bob_ts .send_transaction( alice_address, @@ -2350,7 +2537,10 @@ async fn test_accepting_unknown_tx_id_and_malformed_reply() { )]) .unwrap(); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); alice_ts_interface .transaction_service_handle .send_transaction( @@ -2781,7 +2971,8 @@ async fn discovery_async_return_test() { let initial_balance = alice_oms.get_balance().await.unwrap(); let value_a_to_c_1 = MicroMinotari::from(14000); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( bob_address, @@ -2819,7 +3010,8 @@ async fn discovery_async_return_test() { assert_eq!(found_txid, tx_id); assert!(!is_direct_send); - let carol_address = TariAddress::new(carol_node_identity.public_key().clone(), network); + let carol_address = + TariAddress::new_single_address_with_interactive_only(carol_node_identity.public_key().clone(), network); let tx_id2 = alice_ts .send_transaction( carol_address, @@ -2900,11 +3092,13 @@ async fn test_power_mode_updates() { PrivateKey::random(&mut OsRng), PrivateKey::random(&mut OsRng), ); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2929,11 +3123,13 @@ async fn test_power_mode_updates() { mined_timestamp: None, }; - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -3115,7 +3311,10 @@ async fn test_transaction_cancellation() { .unwrap(); let amount_sent = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -3458,7 +3657,10 @@ async fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { .unwrap(); let amount_sent = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -3657,7 +3859,10 @@ async fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { let amount_sent = 20000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let _tx_id2 = alice_ts_interface .transaction_service_handle .send_transaction( @@ -3843,7 +4048,10 @@ async fn test_tx_direct_send_behaviour() { }) .await; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let _tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -4121,7 +4329,14 @@ async fn test_restarting_transaction_protocols() { }; let tx = bob_stp.get_transaction().unwrap().clone(); - let bob_address = TariAddress::new(bob_identity.public_key().clone(), network); + let bob_view_key_id = bob_ts_interface.key_manager_handle.get_view_key_id().await.unwrap(); + let bob_view_key = bob_ts_interface + .key_manager_handle + .get_public_key_at_key_id(&bob_view_key_id) + .await + .unwrap(); + let bob_address = + TariAddress::new_dual_address_with_default_features(bob_view_key, bob_identity.public_key().clone(), network); let inbound_tx = InboundTransaction { tx_id, source_address: bob_address, @@ -4142,8 +4357,17 @@ async fn test_restarting_transaction_protocols() { Box::new(inbound_tx), ))) .unwrap(); - - let alice_address = TariAddress::new(alice_identity.public_key().clone(), network); + let alice_view_key_id = alice_ts_interface.key_manager_handle.get_view_key_id().await.unwrap(); + let alice_view_key = alice_ts_interface + .key_manager_handle + .get_public_key_at_key_id(&alice_view_key_id) + .await + .unwrap(); + let alice_address = TariAddress::new_dual_address_with_default_features( + alice_view_key, + alice_identity.public_key().clone(), + network, + ); let outbound_tx = OutboundTransaction { tx_id, destination_address: alice_address, @@ -4298,7 +4522,10 @@ async fn test_transaction_resending() { let amount_sent = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -4521,7 +4748,8 @@ async fn test_resend_on_startup() { let tx_sender_msg = TransactionSenderMessage::Single(Box::new(stp_msg)); let tx_id = stp.get_tx_id().unwrap(); - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -4654,7 +4882,8 @@ async fn test_resend_on_startup() { &constants, ) .await; - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -4814,7 +5043,10 @@ async fn test_replying_to_cancelled_tx() { )]) .unwrap(); let amount_sent = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -4951,7 +5183,10 @@ async fn test_transaction_timeout_cancellation() { let amount_sent = 10000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -5041,7 +5276,8 @@ async fn test_transaction_timeout_cancellation() { let tx_sender_msg = TransactionSenderMessage::Single(Box::new(stp_msg)); let tx_id = stp.get_tx_id().unwrap(); - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -5242,7 +5478,10 @@ async fn transaction_service_tx_broadcast() { let amount_sent1 = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); // Send Tx1 let tx_id1 = alice_ts_interface .transaction_service_handle @@ -5576,11 +5815,13 @@ async fn broadcast_all_completed_transactions_on_startup() { PrivateKey::random(&mut OsRng), PrivateKey::random(&mut OsRng), ); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -5701,7 +5942,7 @@ async fn test_update_faux_tx_on_oms_validation() { let connection = make_wallet_database_memory_connection(); let mut alice_ts_interface = setup_transaction_service_no_comms(factories.clone(), connection, None).await; - let alice_address = TariAddress::new( + let alice_address = TariAddress::new_single_address_with_interactive_only( alice_ts_interface.base_node_identity.public_key().clone(), Network::LocalNet, ); @@ -5876,7 +6117,7 @@ async fn test_update_coinbase_tx_on_oms_validation() { let connection = make_wallet_database_memory_connection(); let mut alice_ts_interface = setup_transaction_service_no_comms(factories.clone(), connection, None).await; - let alice_address = TariAddress::new( + let alice_address = TariAddress::new_single_address_with_interactive_only( alice_ts_interface.base_node_identity.public_key().clone(), Network::LocalNet, ); diff --git a/base_layer/wallet/tests/transaction_service_tests/storage.rs b/base_layer/wallet/tests/transaction_service_tests/storage.rs index 69f749ffda..83bee46de7 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -130,7 +130,8 @@ pub async fn test_db_backend(backend: T) { for i in 0..messages.len() { let tx_id = TxId::from(i + 10); - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -237,7 +238,8 @@ pub async fn test_db_backend(backend: T) { let mut inbound_txs = Vec::new(); for i in 0..messages.len() { - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -306,11 +308,13 @@ pub async fn test_db_backend(backend: T) { ); for i in 0..messages.len() { - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let dest_address = TariAddress::new( + let dest_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -424,7 +428,8 @@ pub async fn test_db_backend(backend: T) { } else { panic!("Should have found cancelled completed tx"); } - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -475,7 +480,8 @@ pub async fn test_db_backend(backend: T) { let mut cancelled_txs = db.get_cancelled_pending_inbound_transactions().unwrap(); assert_eq!(cancelled_txs.len(), 1); assert!(cancelled_txs.remove(&999u64.into()).is_some()); - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); diff --git a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs index 95ed908ad6..8416f6c798 100644 --- a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs @@ -78,7 +78,7 @@ use tari_core::{ types::Signature as SignatureProto, }, transactions::{ - key_manager::{create_memory_db_key_manager, MemoryDbKeyManager}, + key_manager::{create_memory_db_key_manager, MemoryDbKeyManager, TransactionKeyManagerInterface}, tari_amount::{uT, MicroMinotari, T}, test_helpers::schema_to_transaction, transaction_components::OutputFeatures, @@ -86,6 +86,7 @@ use tari_core::{ }, txn_schema, }; +use tari_key_manager::key_manager_service::KeyManagerInterface; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::Shutdown; use tari_test_utils::random; @@ -158,7 +159,17 @@ pub async fn setup() -> ( let shutdown = Shutdown::new(); let network = Network::LocalNet; let consensus_manager = ConsensusManager::builder(network).build().unwrap(); - let wallet_identity = WalletIdentity::new(client_node_identity, network); + let view_key = core_key_manager_service_handle.get_view_key_id().await.unwrap(); + let view_key = core_key_manager_service_handle + .get_public_key_at_key_id(&view_key) + .await + .unwrap(); + let tari_address = TariAddress::new_dual_address_with_default_features( + view_key, + client_node_identity.public_key().clone(), + network, + ); + let wallet_identity = WalletIdentity::new(client_node_identity.clone(), tari_address); let resources = TransactionServiceResources { db, output_manager_service: output_manager_service_handle, diff --git a/base_layer/wallet/tests/utxo_scanner/mod.rs b/base_layer/wallet/tests/utxo_scanner/mod.rs index ba46a85ec7..d6080e1a96 100644 --- a/base_layer/wallet/tests/utxo_scanner/mod.rs +++ b/base_layer/wallet/tests/utxo_scanner/mod.rs @@ -38,7 +38,7 @@ use minotari_wallet::{ sqlite_utilities::run_migration_and_create_sqlite_connection, }, transaction_service::handle::TransactionServiceRequest, - util::{wallet_identity::WalletIdentity, watch::Watch}, + util::watch::Watch, utxo_scanner_service::{ handle::{UtxoScannerEvent, UtxoScannerHandle}, service::{ScannedBlock, UtxoScannerService}, @@ -102,6 +102,7 @@ pub struct UtxoScannerTestInterface { } async fn setup( + key_manager: MemoryDbKeyManager, mode: UtxoScannerMode, previous_db: Option>, recovery_message: Option, @@ -149,7 +150,6 @@ async fn setup( task::spawn(oms_mock.run()); let node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); - let wallet_identity = WalletIdentity::new(node_identity, Network::default()); let (event_sender, _) = broadcast::channel(200); let temp_dir = tempdir().unwrap(); @@ -193,20 +193,25 @@ async fn setup( scanner_service_builder.with_recovery_message(message); } - let scanner_service = scanner_service_builder.build_with_resources( - wallet_db.clone(), - comms_connectivity, - wallet_connectivity_mock, - oms_handle, - ts_handle, - wallet_identity, - factories, - shutdown.to_signal(), - event_sender, - base_node_service_handle, - one_sided_message_watch_receiver, - recovery_message_watch_receiver, - ); + let scanner_service = scanner_service_builder + .build_with_resources( + wallet_db.clone(), + comms_connectivity, + wallet_connectivity_mock, + oms_handle, + ts_handle, + key_manager, + node_identity.clone(), + Network::default(), + factories, + shutdown.to_signal(), + event_sender, + base_node_service_handle, + one_sided_message_watch_receiver, + recovery_message_watch_receiver, + ) + .await + .unwrap(); UtxoScannerTestInterface { scanner_service: Some(scanner_service), @@ -289,7 +294,8 @@ async fn generate_block_headers_and_utxos( #[tokio::test] async fn test_utxo_scanner_recovery() { - let mut test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager(); + let mut test_interface = setup(key_manager.clone(), UtxoScannerMode::Recovery, None, None, None).await; let cipher_seed = CipherSeed::new(); // get birthday duration, in seconds, from unix epoch @@ -299,7 +305,6 @@ async fn test_utxo_scanner_recovery() { const NUM_BLOCKS: u64 = 11; const BIRTHDAY_OFFSET: u64 = 5; - let key_manager = create_memory_db_key_manager(); let TestBlockData { block_headers, wallet_outputs, @@ -380,7 +385,8 @@ async fn test_utxo_scanner_recovery() { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_utxo_scanner_recovery_with_restart() { - let mut test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager(); + let mut test_interface = setup(key_manager.clone(), UtxoScannerMode::Recovery, None, None, None).await; let cipher_seed = CipherSeed::new(); // get birthday duration, in seconds, from unix epoch @@ -398,7 +404,6 @@ async fn test_utxo_scanner_recovery_with_restart() { const BIRTHDAY_OFFSET: u64 = 5; const SYNC_INTERRUPT: u64 = 6; - let key_manager = create_memory_db_key_manager(); let TestBlockData { block_headers, wallet_outputs, @@ -487,6 +492,7 @@ async fn test_utxo_scanner_recovery_with_restart() { test_interface.shutdown_signal.trigger(); let mut test_interface2 = setup( + key_manager.clone(), UtxoScannerMode::Recovery, Some(test_interface.wallet_db), Some("recovery".to_string()), @@ -554,7 +560,8 @@ async fn test_utxo_scanner_recovery_with_restart() { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_utxo_scanner_recovery_with_restart_and_reorg() { - let mut test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager(); + let mut test_interface = setup(key_manager.clone(), UtxoScannerMode::Recovery, None, None, None).await; let cipher_seed = CipherSeed::new(); // get birthday duration, in seconds, from unix epoch @@ -564,7 +571,6 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { const NUM_BLOCKS: u64 = 11; const BIRTHDAY_OFFSET: u64 = 5; const SYNC_INTERRUPT: u64 = 6; - let key_manager = create_memory_db_key_manager(); let TestBlockData { mut block_headers, mut wallet_outputs, @@ -645,7 +651,14 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { utxos_by_block.append(&mut new_utxos_by_block); wallet_outputs.extend(new_wallet_outputs); - let mut test_interface2 = setup(UtxoScannerMode::Recovery, Some(test_interface.wallet_db), None, None).await; + let mut test_interface2 = setup( + key_manager.clone(), + UtxoScannerMode::Recovery, + Some(test_interface.wallet_db), + None, + None, + ) + .await; test_interface2 .rpc_service_state .set_utxos_by_block(utxos_by_block.clone()); @@ -725,7 +738,8 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_utxo_scanner_scanned_block_cache_clearing() { - let mut test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager(); + let mut test_interface = setup(key_manager.clone(), UtxoScannerMode::Recovery, None, None, None).await; for h in 0u64..800u64 { let num_outputs = if h % 2 == 1 { Some(1) } else { None }; @@ -755,7 +769,6 @@ async fn test_utxo_scanner_scanned_block_cache_clearing() { const NUM_BLOCKS: u64 = 11; const BIRTHDAY_OFFSET: u64 = 5; - let key_manager = create_memory_db_key_manager(); let TestBlockData { block_headers, wallet_outputs: _wallet_outputs, @@ -848,7 +861,9 @@ async fn test_utxo_scanner_scanned_block_cache_clearing() { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_utxo_scanner_one_sided_payments() { + let key_manager = create_memory_db_key_manager(); let mut test_interface = setup( + key_manager.clone(), UtxoScannerMode::Scanning, None, None, @@ -864,7 +879,6 @@ async fn test_utxo_scanner_one_sided_payments() { const NUM_BLOCKS: u64 = 11; const BIRTHDAY_OFFSET: u64 = 5; - let key_manager = create_memory_db_key_manager(); let TestBlockData { mut block_headers, wallet_outputs, @@ -1063,7 +1077,8 @@ async fn test_utxo_scanner_one_sided_payments() { #[tokio::test] async fn test_birthday_timestamp_over_chain() { - let test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager(); + let test_interface = setup(key_manager, UtxoScannerMode::Recovery, None, None, None).await; let cipher_seed = CipherSeed::new(); // get birthday duration, in seconds, from unix epoch diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index 7ff55a7bc8..cd5f7fe5b7 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -279,7 +279,8 @@ mod test { let db = TransactionDatabase::new(TransactionServiceSqliteDatabase::new(connection, cipher)); let rtp = ReceiverTransactionProtocol::new_placeholder(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -295,11 +296,13 @@ mod test { db.add_pending_inbound_transaction(1u64.into(), inbound_tx.clone()) .unwrap(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -328,7 +331,8 @@ mod test { .unwrap(); let stp = SenderTransactionProtocol::new_placeholder(); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -363,11 +367,13 @@ mod test { .unwrap(); db.reject_completed_transaction(5u64.into(), TxCancellationReason::Unknown) .unwrap(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -395,11 +401,13 @@ mod test { db.insert_completed_transaction(6u64.into(), faux_unconfirmed_tx.clone()) .unwrap(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -455,7 +463,8 @@ mod test { let (connectivity_tx, connectivity_rx) = watch::channel(OnlineStatus::Offline); let (contacts_liveness_events_sender, _) = broadcast::channel(250); let contacts_liveness_events = contacts_liveness_events_sender.subscribe(); - let comms_address = TariAddress::new( + let comms_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); diff --git a/base_layer/wallet_ffi/src/error.rs b/base_layer/wallet_ffi/src/error.rs index 8b4926a40b..2453fbea59 100644 --- a/base_layer/wallet_ffi/src/error.rs +++ b/base_layer/wallet_ffi/src/error.rs @@ -398,7 +398,7 @@ impl From for LibWalletError { fn from(e: TariAddressError) -> Self { error!(target: LOG_TARGET, "{}", format!("{:?}", e)); match e { - TariAddressError::InvalidNetworkOrChecksum => Self { + TariAddressError::InvalidNetwork => Self { code: 701, message: format!("{:?}", e), }, @@ -414,6 +414,19 @@ impl From for LibWalletError { code: 704, message: format!("{:?}", e), }, + + TariAddressError::InvalidFeatures => Self { + code: 705, + message: format!("{:?}", e), + }, + TariAddressError::InvalidChecksum => Self { + code: 706, + message: format!("{:?}", e), + }, + TariAddressError::InvalidAddressString => Self { + code: 706, + message: format!("{:?}", e), + }, } } } diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 93644e7737..f917e581e7 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -1125,52 +1125,11 @@ pub unsafe extern "C" fn tari_address_get_bytes( ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } else { - bytes.0 = (*address).to_bytes().to_vec(); + bytes.0 = (*address).to_vec(); } Box::into_raw(Box::new(bytes)) } -/// Creates a TariWalletAddress from a TariPrivateKey -/// -/// ## Arguments -/// `secret_key` - The pointer to a TariPrivateKey -/// `network` - an u8 indicating the network -/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions -/// as an out parameter. -/// -/// ## Returns -/// `*mut TariWalletAddress` - Returns a pointer to a TariWalletAddress -/// -/// # Safety -/// The ```private_key_destroy``` method must be called when finished with a private key to prevent a memory leak -// casting here is network is a u8 -#[allow(clippy::cast_possible_truncation)] -#[no_mangle] -pub unsafe extern "C" fn tari_address_from_private_key( - secret_key: *mut TariPrivateKey, - network: c_uint, - error_out: *mut c_int, -) -> *mut TariWalletAddress { - let mut error = 0; - ptr::swap(error_out, &mut error as *mut c_int); - if secret_key.is_null() { - error = LibWalletError::from(InterfaceError::NullError("secret_key".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return ptr::null_mut(); - } - let key = PublicKey::from_secret_key(&(*secret_key)); - let network = match (network as u8).try_into() { - Ok(network) => network, - Err(_) => { - error = LibWalletError::from(InterfaceError::InvalidArgument("network".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return ptr::null_mut(); - }, - }; - let address = TariWalletAddress::new(key, network); - Box::into_raw(Box::new(address)) -} - /// Creates a TariWalletAddress from a char array /// /// ## Arguments @@ -5556,7 +5515,14 @@ pub unsafe extern "C" fn wallet_create( match w { Ok(w) => { - let wallet_address = TariAddress::new(w.comms.node_identity().public_key().clone(), w.network.as_network()); + let wallet_address = match runtime.block_on(async { w.get_wallet_address().await }) { + Ok(address) => address, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; // Start Callback Handler let callback_handler = CallbackHandler::new( @@ -7235,10 +7201,22 @@ pub unsafe extern "C" fn wallet_get_cancelled_transactions( for tx in completed_transactions.values() { completed.push(tx.clone()); } - let wallet_address = TariAddress::new( - (*wallet).wallet.comms.node_identity().public_key().clone(), - (*wallet).wallet.network.as_network(), - ); + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = LibWalletError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + let wallet_address = match runtime.block_on(async { (*wallet).wallet.get_wallet_address().await }) { + Ok(address) => address, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; for tx in inbound_transactions.values() { let mut inbound_tx = CompletedTransaction::from(tx.clone()); inbound_tx.destination_address = wallet_address.clone(); @@ -7517,8 +7495,22 @@ pub unsafe extern "C" fn wallet_get_cancelled_transaction_by_id( return ptr::null_mut(); }, }; - let network = (*wallet).wallet.network.as_network(); - let address = TariWalletAddress::new((*wallet).wallet.comms.node_identity().public_key().clone(), network); + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = LibWalletError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + let address = match runtime.block_on(async { (*wallet).wallet.get_wallet_address().await }) { + Ok(address) => address, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; if let Some(tx) = outbound_transactions.remove(&transaction_id) { let mut outbound_tx = CompletedTransaction::from(tx); outbound_tx.source_address = address; @@ -7586,9 +7578,22 @@ pub unsafe extern "C" fn wallet_get_tari_address( ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } - let network = (*wallet).wallet.network.as_network(); - let pk = (*wallet).wallet.comms.node_identity().public_key().clone(); - let address = TariWalletAddress::new(pk, network); + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = LibWalletError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + let address = match runtime.block_on(async { (*wallet).wallet.get_wallet_address().await }) { + Ok(address) => address, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; Box::into_raw(Box::new(address)) } @@ -8190,11 +8195,28 @@ pub unsafe extern "C" fn wallet_start_recovery( }; recovery_task_builder.with_recovery_message(message_str); } - - let mut recovery_task = recovery_task_builder - .with_peers(peer_public_keys) - .with_retry_limit(10) - .build_with_wallet(&(*wallet).wallet, shutdown_signal); + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = LibWalletError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + }; + let mut recovery_task = match runtime.block_on(async { + recovery_task_builder + .with_peers(peer_public_keys) + .with_retry_limit(10) + .build_with_wallet(&(*wallet).wallet, shutdown_signal) + .await + }) { + Ok(v) => v, + Err(e) => { + error = LibWalletError::from(WalletError::KeyManagerServiceError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + }; let event_stream = recovery_task.get_event_receiver(); let recovery_join_handle = (*wallet).runtime.spawn(recovery_task.run()); @@ -9234,36 +9256,21 @@ mod test { let private_key = private_key_generate(); let public_key = public_key_from_private_key(private_key, error_ptr); assert_eq!(error, 0); - let address = tari_address_from_private_key(private_key, 0x26, error_ptr); - assert_eq!(error, 0); let private_bytes = private_key_get_bytes(private_key, error_ptr); assert_eq!(error, 0); let public_bytes = public_key_get_bytes(public_key, error_ptr); assert_eq!(error, 0); - let address_bytes = tari_address_get_bytes(address, error_ptr); - assert_eq!(error, 0); let private_key_length = byte_vector_get_length(private_bytes, error_ptr); assert_eq!(error, 0); let public_key_length = byte_vector_get_length(public_bytes, error_ptr); assert_eq!(error, 0); - let tari_address_length = byte_vector_get_length(address_bytes, error_ptr); - assert_eq!(error, 0); assert_eq!(private_key_length, 32); assert_eq!(public_key_length, 32); - assert_eq!(tari_address_length, 33); assert_ne!((*private_bytes), (*public_bytes)); - let emoji = tari_address_to_emoji_id(address, error_ptr) as *mut c_char; - let emoji_str = CStr::from_ptr(emoji).to_str().unwrap(); - assert!(TariAddress::from_emoji_string(emoji_str).is_ok()); - let address_emoji = emoji_id_to_tari_address(emoji, error_ptr); - assert_eq!((*address), (*address_emoji)); private_key_destroy(private_key); public_key_destroy(public_key); - tari_address_destroy(address_emoji); - tari_address_destroy(address); byte_vector_destroy(public_bytes); byte_vector_destroy(private_bytes); - byte_vector_destroy(address_bytes); } } @@ -9477,7 +9484,11 @@ mod test { let mut error = 0; let error_ptr = &mut error as *mut c_int; let test_contact_private_key = private_key_generate(); - let test_address = tari_address_from_private_key(test_contact_private_key, 0x10, error_ptr); + let key = PublicKey::from_secret_key(&(*test_contact_private_key)); + let test_address = Box::into_raw(Box::new(TariWalletAddress::new_single_address_with_interactive_only( + key, + Network::default(), + ))); let test_str = "Test Contact"; let test_contact_str = CString::new(test_str).unwrap(); let test_contact_alias: *const c_char = CString::into_raw(test_contact_str) as *const c_char; @@ -9490,7 +9501,7 @@ mod test { let contact_address = contact_get_tari_address(test_contact, error_ptr); let contact_key_bytes = tari_address_get_bytes(contact_address, error_ptr); let contact_bytes_len = byte_vector_get_length(contact_key_bytes, error_ptr); - assert_eq!(contact_bytes_len, 33); + assert_eq!(contact_bytes_len, 35); contact_destroy(test_contact); tari_address_destroy(test_address); private_key_destroy(test_contact_private_key); @@ -9505,7 +9516,10 @@ mod test { let mut error = 0; let error_ptr = &mut error as *mut c_int; let test_contact_private_key = private_key_generate(); - let test_contact_address = tari_address_from_private_key(test_contact_private_key, 0x00, error_ptr); + let key = PublicKey::from_secret_key(&(*test_contact_private_key)); + let test_contact_address = Box::into_raw(Box::new( + TariWalletAddress::new_single_address_with_interactive_only(key, Network::default()), + )); let test_str = "Test Contact"; let test_contact_str = CString::new(test_str).unwrap(); let test_contact_alias: *const c_char = CString::into_raw(test_contact_str) as *const c_char; @@ -11518,7 +11532,10 @@ mod test { // Add some contacts // - Contact for Alice - let bob_wallet_address = TariWalletAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_wallet_address = TariWalletAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let alice_contact_alias_ptr: *const c_char = CString::into_raw(CString::new("bob").unwrap()) as *const c_char; let alice_contact_address_ptr = Box::into_raw(Box::new(bob_wallet_address.clone())); @@ -11527,8 +11544,10 @@ mod test { assert!(wallet_upsert_contact(alice_wallet_ptr, alice_contact_ptr, error_ptr)); contact_destroy(alice_contact_ptr); // - Contact for Bob - let alice_wallet_address = - TariWalletAddress::new(alice_node_identity.public_key().clone(), Network::LocalNet); + let alice_wallet_address = TariWalletAddress::new_single_address_with_interactive_only( + alice_node_identity.public_key().clone(), + Network::LocalNet, + ); let bob_contact_alias_ptr: *const c_char = CString::into_raw(CString::new("alice").unwrap()) as *const c_char; let bob_contact_address_ptr = Box::into_raw(Box::new(alice_wallet_address.clone())); diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 9ef96d45c9..172e51a14e 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -715,25 +715,6 @@ void tari_address_destroy(TariWalletAddress *address); struct ByteVector *tari_address_get_bytes(TariWalletAddress *address, int *error_out); -/** - * Creates a TariWalletAddress from a TariPrivateKey - * - * ## Arguments - * `secret_key` - The pointer to a TariPrivateKey - * `network` - an u8 indicating the network - * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions - * as an out parameter. - * - * ## Returns - * `*mut TariWalletAddress` - Returns a pointer to a TariWalletAddress - * - * # Safety - * The ```private_key_destroy``` method must be called when finished with a private key to prevent a memory leak - */ -TariWalletAddress *tari_address_from_private_key(TariPrivateKey *secret_key, - unsigned int network, - int *error_out); - /** * Creates a TariWalletAddress from a char array * diff --git a/clients/nodejs/wallet_grpc_client/index.js b/clients/nodejs/wallet_grpc_client/index.js index e88f42b22d..ba24f4c996 100644 --- a/clients/nodejs/wallet_grpc_client/index.js +++ b/clients/nodejs/wallet_grpc_client/index.js @@ -37,7 +37,7 @@ function Client(address) { "getCompletedTransactions", "getTransactionInfo", "getVersion", - "identify", + "getAddress", "transfer", "importUtxos", "listConnectedPeers", diff --git a/integration_tests/src/chat_client.rs b/integration_tests/src/chat_client.rs index e14f1cea50..7014748c1e 100644 --- a/integration_tests/src/chat_client.rs +++ b/integration_tests/src/chat_client.rs @@ -29,6 +29,7 @@ use tari_chat_client::{ Client, }; use tari_common::configuration::MultiaddrList; +use tari_common_types::tari_address::TariAddress; use tari_comms::{ multiaddr::Multiaddr, peer_manager::{Peer, PeerFeatures}, @@ -62,7 +63,9 @@ pub async fn spawn_chat_client(name: &str, seed_peers: Vec, base_dir: Path .collect::>() .into(); let user_agent = format!("tari/integration_tests/{}", env!("CARGO_PKG_VERSION")); - let mut client = Client::new(identity, config, user_agent); + let address = + TariAddress::new_single_address_with_interactive_only(identity.public_key().clone(), config.network()); + let mut client = Client::new(identity, address, config, user_agent); client.initialize().await.expect("the chat client to spawn"); client diff --git a/integration_tests/src/chat_ffi.rs b/integration_tests/src/chat_ffi.rs index 42bcef33fd..da0ceb127a 100644 --- a/integration_tests/src/chat_ffi.rs +++ b/integration_tests/src/chat_ffi.rs @@ -70,20 +70,22 @@ extern "C" fn callback_read_confirmation_received(_state: *mut c_void) { extern "C" { pub fn create_chat_client( config: *mut c_void, - error_out: *const c_int, callback_contact_status_change: unsafe extern "C" fn(*mut c_void), callback_message_received: unsafe extern "C" fn(*mut c_void), callback_delivery_confirmation_received: unsafe extern "C" fn(*mut c_void), callback_read_confirmation_received: unsafe extern "C" fn(*mut c_void), + tari_address: *mut c_void, + error_out: *const c_int, ) -> *mut ClientFFI; pub fn sideload_chat_client( config: *mut c_void, contact_handle: *mut c_void, - error_out: *const c_int, callback_contact_status_change: unsafe extern "C" fn(*mut c_void), callback_message_received: unsafe extern "C" fn(*mut c_void), callback_delivery_confirmation_received: unsafe extern "C" fn(*mut c_void), callback_read_confirmation_received: unsafe extern "C" fn(*mut c_void), + tari_address: *mut c_void, + error_out: *const c_int, ) -> *mut ClientFFI; pub fn create_chat_message( receiver: *mut c_void, @@ -310,23 +312,26 @@ pub async fn spawn_ffi_chat_client(name: &str, seed_peers: Vec, base_dir: let client_ptr; let error_out = Box::into_raw(Box::new(0)); - + let address = + TariAddress::new_single_address_with_interactive_only(identity.public_key().clone(), Network::LocalNet); + let address_ptr = Box::into_raw(Box::new(address.clone())) as *mut c_void; unsafe { *ChatCallback::instance().contact_status_change.lock().unwrap() = 0; client_ptr = create_chat_client( config_ptr, - error_out, callback_contact_status_change, callback_message_received, callback_delivery_confirmation_received, callback_read_confirmation_received, + address_ptr, + error_out, ); } ChatFFI { ptr: Arc::new(Mutex::new(PtrWrapper(client_ptr))), - address: TariAddress::from_public_key(identity.public_key(), Network::LocalNet), + address, } } @@ -339,6 +344,7 @@ pub async fn sideload_ffi_chat_client( config.chat_client.set_base_path(base_dir); let config_ptr = Box::into_raw(Box::new(config)) as *mut c_void; + let adress_ptr = Box::into_raw(Box::new(address.clone())) as *mut c_void; let client_ptr; let error_out = Box::into_raw(Box::new(0)); @@ -348,11 +354,12 @@ pub async fn sideload_ffi_chat_client( client_ptr = sideload_chat_client( config_ptr, contacts_handle_ptr, - error_out, callback_contact_status_change, callback_message_received, callback_delivery_confirmation_received, callback_read_confirmation_received, + adress_ptr, + error_out, ); } diff --git a/integration_tests/src/ffi/wallet_address.rs b/integration_tests/src/ffi/wallet_address.rs index af865c9eea..dbfc252636 100644 --- a/integration_tests/src/ffi/wallet_address.rs +++ b/integration_tests/src/ffi/wallet_address.rs @@ -24,7 +24,7 @@ use std::{ffi::CString, ptr::null_mut}; use libc::c_void; -use super::{ffi_bytes::FFIBytes, ffi_import, FFIString, PrivateKey}; +use super::{ffi_bytes::FFIBytes, ffi_import, FFIString}; pub struct WalletAddress { ptr: *mut c_void, @@ -42,19 +42,6 @@ impl WalletAddress { Self { ptr } } - #[allow(dead_code)] - pub fn from_private_key(private_key: PrivateKey, network: u32) -> Self { - let mut error = 0; - let ptr; - unsafe { - ptr = ffi_import::tari_address_from_private_key(private_key.get_ptr(), network, &mut error); - if error > 0 { - println!("wallet_get_tari_address error {}", error); - } - } - Self { ptr } - } - pub fn from_hex(address: String) -> Self { let mut error = 0; let ptr; diff --git a/integration_tests/src/merge_mining_proxy.rs b/integration_tests/src/merge_mining_proxy.rs index 26663bab8a..3a2f6e60c7 100644 --- a/integration_tests/src/merge_mining_proxy.rs +++ b/integration_tests/src/merge_mining_proxy.rs @@ -22,14 +22,12 @@ use std::{convert::TryInto, thread}; -use minotari_app_grpc::tari_rpc::GetIdentityRequest; use minotari_app_utilities::common_cli_args::CommonCliArgs; use minotari_merge_mining_proxy::{merge_miner, Cli}; -use minotari_wallet_grpc_client::WalletGrpcClient; +use minotari_wallet_grpc_client::{grpc, WalletGrpcClient}; use serde_json::{json, Value}; use tari_common::{configuration::Network, network_check::set_network_if_choice_valid}; -use tari_common_types::{tari_address::TariAddress, types::PublicKey}; -use tari_utilities::ByteArray; +use tari_common_types::tari_address::TariAddress; use tempfile::tempdir; use tokio::runtime; use tonic::transport::Channel; @@ -88,16 +86,13 @@ impl MergeMiningProxyProcess { let mut wallet_client = create_wallet_client(world, self.wallet_name.clone()) .await .expect("wallet grpc client"); - let wallet_public_key = PublicKey::from_vec( - &wallet_client - .identify(GetIdentityRequest {}) - .await - .unwrap() - .into_inner() - .public_key, - ) - .unwrap(); - let wallet_payment_address = TariAddress::new(wallet_public_key, Network::LocalNet); + let wallet_public_key = &wallet_client + .get_address(grpc::Empty {}) + .await + .unwrap() + .into_inner() + .address; + let wallet_payment_address = TariAddress::from_bytes(wallet_public_key).unwrap(); let stealth = self.stealth; thread::spawn(move || { let cli = Cli { diff --git a/integration_tests/src/miner.rs b/integration_tests/src/miner.rs index 840107b3a2..68573c8aa5 100644 --- a/integration_tests/src/miner.rs +++ b/integration_tests/src/miner.rs @@ -25,7 +25,6 @@ use std::{convert::TryFrom, time::Duration}; use minotari_app_grpc::tari_rpc::{ pow_algo::PowAlgos, Block, - GetIdentityRequest, NewBlockTemplate, NewBlockTemplateRequest, PowAlgo, @@ -34,9 +33,9 @@ use minotari_app_grpc::tari_rpc::{ use minotari_app_utilities::common_cli_args::CommonCliArgs; use minotari_miner::{run_miner, Cli}; use minotari_node_grpc_client::BaseNodeGrpcClient; -use minotari_wallet_grpc_client::WalletGrpcClient; +use minotari_wallet_grpc_client::{grpc, WalletGrpcClient}; use tari_common::{configuration::Network, network_check::set_network_if_choice_valid}; -use tari_common_types::{tari_address::TariAddress, types::PublicKey}; +use tari_common_types::tari_address::TariAddress; use tari_core::{ consensus::ConsensusManager, transactions::{ @@ -46,7 +45,6 @@ use tari_core::{ transaction_components::{RangeProofType, WalletOutput}, }, }; -use tari_utilities::ByteArray; use tonic::transport::Channel; use crate::TariWorld; @@ -94,16 +92,14 @@ impl MinerProcess { let mut wallet_client = create_wallet_client(world, self.wallet_name.clone()) .await .expect("wallet grpc client"); - let wallet_public_key = PublicKey::from_vec( - &wallet_client - .identify(GetIdentityRequest {}) - .await - .unwrap() - .into_inner() - .public_key, - ) - .unwrap(); - let wallet_payment_address = TariAddress::new(wallet_public_key, Network::LocalNet); + + let wallet_public_key = &wallet_client + .get_address(grpc::Empty {}) + .await + .unwrap() + .into_inner() + .address; + let wallet_payment_address = TariAddress::from_bytes(wallet_public_key).unwrap(); let node = world.get_node(&self.base_node_name).unwrap().grpc_port; let temp_dir = world diff --git a/integration_tests/src/world.rs b/integration_tests/src/world.rs index 18aeadd894..f477d07be1 100644 --- a/integration_tests/src/world.rs +++ b/integration_tests/src/world.rs @@ -112,8 +112,11 @@ impl Default for TariWorld { fn default() -> Self { println!("\nWorld initialized - remove this line when called!\n"); let wallet_private_key = PrivateKey::random(&mut OsRng); - let default_payment_address = - TariAddress::new(PublicKey::from_secret_key(&wallet_private_key), Network::LocalNet); + let default_payment_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&wallet_private_key), + PublicKey::from_secret_key(&wallet_private_key), + Network::LocalNet, + ); Self { current_scenario_name: None, current_feature_name: None, diff --git a/integration_tests/tests/steps/chat_ffi_steps.rs b/integration_tests/tests/steps/chat_ffi_steps.rs index 35acd32845..7461ebdf2a 100644 --- a/integration_tests/tests/steps/chat_ffi_steps.rs +++ b/integration_tests/tests/steps/chat_ffi_steps.rs @@ -47,9 +47,8 @@ async fn chat_ffi_client_connected_to_base_node(world: &mut TariWorld, name: Str #[when(expr = "I have a sideloaded chat FFI client {word} from {word}")] async fn sideloaded_chat_ffi_client_connected_to_wallet(world: &mut TariWorld, chat_name: String, wallet_name: String) { let wallet = world.get_ffi_wallet(&wallet_name).unwrap(); - let pubkey = world.get_wallet_address(&wallet_name).await.unwrap(); - let address = TariAddress::from_hex(&pubkey).unwrap(); - + let address = world.get_wallet_address(&wallet_name).await.unwrap(); + let address = TariAddress::from_hex(&address).unwrap(); let client = sideload_ffi_chat_client(address, wallet.base_dir.clone(), wallet.contacts_handle()).await; world.chat_clients.insert(chat_name, Box::new(client)); }