From b6a0e161d151cfc5266121d0dfb699b20003624a Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Mon, 2 Dec 2024 18:49:15 +0200 Subject: [PATCH] Add change metadata to encrypted data Added receiver address. amount and payment id to encrypted data so a view only wallet will be able to identify the receiver when importing a change output. --- Cargo.lock | 1 + .../minotari_app_grpc/proto/wallet.proto | 1 + .../src/automation/commands.rs | 29 +- .../minotari_console_wallet/src/cli.rs | 6 + .../src/grpc/wallet_grpc_server.rs | 10 +- .../src/ui/components/burn_tab.rs | 53 +- .../src/ui/components/send_tab.rs | 15 +- .../src/ui/components/transactions_tab.rs | 11 +- .../src/ui/state/app_state.rs | 12 +- .../src/ui/state/tasks.rs | 12 +- base_layer/core/Cargo.toml | 1 + .../core/src/transactions/aggregated_body.rs | 7 +- .../transaction_components/encrypted_data.rs | 552 +++++++++++++++--- .../transaction_protocol/sender.rs | 6 + .../transaction_initializer.rs | 70 ++- .../src/output_manager_service/handle.rs | 41 +- .../src/output_manager_service/service.rs | 119 ++-- .../wallet/src/transaction_service/handle.rs | 18 +- .../protocols/transaction_send_protocol.rs | 9 +- .../wallet/src/transaction_service/service.rs | 36 +- .../src/transaction_service/storage/models.rs | 4 + .../transaction_service/storage/sqlite_db.rs | 7 + .../utxo_scanner_service/utxo_scanner_task.rs | 6 +- .../output_manager_service_tests/service.rs | 29 + .../transaction_service_tests/service.rs | 42 +- .../transaction_service_tests/storage.rs | 3 + .../wallet_ffi/src/callback_handler_tests.rs | 3 +- base_layer/wallet_ffi/src/lib.rs | 34 +- .../tests/steps/wallet_cli_steps.rs | 2 + integration_tests/tests/steps/wallet_steps.rs | 1 + 30 files changed, 954 insertions(+), 186 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index feaa1927c52..219af9a9517 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6673,6 +6673,7 @@ dependencies = [ "serial_test", "sha2 0.10.8", "sha3", + "static_assertions", "strum", "strum_macros", "tari-tiny-keccak", diff --git a/applications/minotari_app_grpc/proto/wallet.proto b/applications/minotari_app_grpc/proto/wallet.proto index 23498e4aaca..d60b83c2e24 100644 --- a/applications/minotari_app_grpc/proto/wallet.proto +++ b/applications/minotari_app_grpc/proto/wallet.proto @@ -107,6 +107,7 @@ message CreateBurnTransactionRequest{ uint64 fee_per_gram = 2; string message = 3; bytes claim_public_key = 4; + string payment_id = 5; } diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 60cd0fafdf0..a898b14d501 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -158,6 +158,7 @@ pub(crate) const SPEND_STEP_2_SELF: &str = "step_2_for_self"; pub(crate) const SPEND_STEP_3_SELF: &str = "step_3_for_self"; pub(crate) const SPEND_STEP_3_PARTIES: &str = "step_3_for_parties"; pub(crate) const SPEND_STEP_4_LEADER: &str = "step_4_for_leader_from_"; +pub(crate) const NO_PAYMENT_ID: &str = ""; #[derive(Debug)] pub struct SentTransaction {} @@ -169,6 +170,7 @@ pub async fn send_tari( amount: MicroMinotari, destination: TariAddress, message: String, + payment_id: PaymentId, ) -> Result { wallet_transaction_service .send_transaction( @@ -178,6 +180,7 @@ pub async fn send_tari( OutputFeatures::default(), fee_per_gram * uT, message, + payment_id, ) .await .map_err(CommandError::TransactionServiceError) @@ -188,6 +191,7 @@ pub async fn burn_tari( fee_per_gram: u64, amount: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result<(TxId, BurntProof), CommandError> { wallet_transaction_service .burn_tari( @@ -195,6 +199,7 @@ pub async fn burn_tari( UtxoSelectionCriteria::default(), fee_per_gram * uT, message, + payment_id, None, ) .await @@ -544,7 +549,7 @@ pub async fn make_it_rain( // Send transaction let tx_id = match transaction_type { MakeItRainTransactionType::Interactive => { - send_tari(tx_service, fee, amount, address.clone(), msg.clone()).await + send_tari(tx_service, fee, amount, address.clone(), msg.clone(), PaymentId::Empty).await }, MakeItRainTransactionType::StealthOneSided => { send_one_sided_to_stealth_address( @@ -558,9 +563,11 @@ pub async fn make_it_rain( ) .await }, - MakeItRainTransactionType::BurnTari => burn_tari(tx_service, fee, amount, msg.clone()) - .await - .map(|(tx_id, _)| tx_id), + MakeItRainTransactionType::BurnTari => { + burn_tari(tx_service, fee, amount, msg.clone(), PaymentId::Empty) + .await + .map(|(tx_id, _)| tx_id) + }, }; let submit_time = Instant::now(); @@ -784,6 +791,7 @@ pub async fn command_runner( config.fee_per_gram, args.amount, args.message, + get_payment_id(&args.payment_id), ) .await { @@ -1977,6 +1985,7 @@ pub async fn command_runner( args.amount, args.destination, args.message, + get_payment_id(&args.payment_id), ) .await { @@ -1995,7 +2004,7 @@ pub async fn command_runner( UtxoSelectionCriteria::default(), args.destination, args.message, - PaymentId::Empty, + get_payment_id(&args.payment_id), ) .await { @@ -2692,6 +2701,16 @@ pub async fn command_runner( Ok(unban_peer_manager_peers) } +fn get_payment_id(payment_id: &str) -> PaymentId { + match payment_id { + NO_PAYMENT_ID => PaymentId::Empty, + _ => { + let payment_id_arg = payment_id.as_bytes().to_vec(); + PaymentId::Open(payment_id_arg) + }, + } +} + async fn temp_ban_peers(wallet: &WalletSqlite, peer_list: &mut Vec) { for peer in peer_list { let _unused = wallet diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index b2abd7d513f..b3dff422338 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -41,6 +41,8 @@ use tari_utilities::{ }; use thiserror::Error; +use crate::automation::commands::NO_PAYMENT_ID; + #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] @@ -177,6 +179,8 @@ pub struct SendMinotariArgs { pub destination: TariAddress, #[clap(short, long, default_value = "")] pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } #[derive(Debug, Args, Clone)] @@ -184,6 +188,8 @@ pub struct BurnMinotariArgs { pub amount: MicroMinotari, #[clap(short, long, default_value = "Burn funds")] pub message: String, + #[clap(short, long, default_value = NO_PAYMENT_ID)] + pub payment_id: String, } #[derive(Debug, Args, Clone)] 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 59707a0c2f5..db90e46fb8e 100644 --- a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -538,6 +538,7 @@ impl wallet_server::Wallet for WalletGrpcServer { OutputFeatures::default(), fee_per_gram.into(), message, + payment_id, ) .await } else if payment_type == PaymentType::OneSided as i32 { @@ -612,6 +613,7 @@ impl wallet_server::Wallet for WalletGrpcServer { UtxoSelectionCriteria::default(), message.fee_per_gram.into(), message.message, + PaymentId::Open(message.payment_id.as_bytes().to_vec()), if message.claim_public_key.is_empty() { None } else { @@ -985,7 +987,13 @@ impl wallet_server::Wallet for WalletGrpcServer { output = output.with_script(script![Nop].map_err(|e| Status::invalid_argument(e.to_string()))?); let (tx_id, transaction) = output_manager - .create_send_to_self_with_output(vec![output], fee_per_gram.into(), UtxoSelectionCriteria::default()) + .create_send_to_self_with_output( + vec![output], + fee_per_gram.into(), + UtxoSelectionCriteria::default(), + message.clone(), + PaymentId::Empty, + ) .await .map_err(|e| Status::internal(e.to_string()))?; diff --git a/applications/minotari_console_wallet/src/ui/components/burn_tab.rs b/applications/minotari_console_wallet/src/ui/components/burn_tab.rs index e90d97c87b2..f9c21bdf400 100644 --- a/applications/minotari_console_wallet/src/ui/components/burn_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/burn_tab.rs @@ -5,7 +5,7 @@ use std::fs; use log::*; use minotari_wallet::output_manager_service::UtxoSelectionCriteria; -use tari_core::transactions::tari_amount::MicroMinotari; +use tari_core::transactions::{tari_amount::MicroMinotari, transaction_components::encrypted_data::PaymentId}; use tokio::{runtime::Handle, sync::watch}; use tui::{ backend::Backend, @@ -35,6 +35,7 @@ pub struct BurnTab { amount_field: String, fee_field: String, message_field: String, + payment_id_field: String, error_message: Option, success_message: Option, offline_message: Option, @@ -54,6 +55,7 @@ impl BurnTab { amount_field: String::new(), fee_field: app_state.get_default_fee_per_gram().as_u64().to_string(), message_field: String::new(), + payment_id_field: String::new(), error_message: None, success_message: None, offline_message: None, @@ -92,22 +94,22 @@ impl BurnTab { let instructions = Paragraph::new(vec![ Spans::from(vec![ Span::raw("Press "), - Span::styled("P", Style::default().add_modifier(Modifier::BOLD)), + Span::styled("V", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Burn Proof Filepath", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field, "), + Span::raw(", "), Span::styled("C", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Claim Public Key", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field, "), + Span::raw(", "), Span::styled("A", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Amount", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" and "), + Span::raw(", "), Span::styled("F", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Fee-Per-Gram", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field,"), + Span::raw(" and "), Span::styled("B", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to view burnt proofs."), ]), @@ -129,7 +131,7 @@ impl BurnTab { .block( Block::default() .borders(Borders::ALL) - .title("Save burn proof to file(p)ath:"), + .title("Sa(v)e burn proof to file path:"), ); f.render_widget(burnt_proof_filepath_input, vert_chunks[1]); @@ -162,13 +164,26 @@ impl BurnTab { .block(Block::default().borders(Borders::ALL).title("(F)ee-per-gram (uT):")); f.render_widget(fee_input, amount_fee_layout[1]); + let message_payment_id_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(vert_chunks[4]); + let message_input = Paragraph::new(self.message_field.as_ref()) .style(match self.burn_input_mode { BurnInputMode::Message => Style::default().fg(Color::Magenta), _ => Style::default(), }) .block(Block::default().borders(Borders::ALL).title("(M)essage:")); - f.render_widget(message_input, vert_chunks[4]); + f.render_widget(message_input, message_payment_id_layout[0]); + + let payment_id_input = Paragraph::new(self.payment_id_field.as_ref()) + .style(match self.burn_input_mode { + BurnInputMode::PaymentId => Style::default().fg(Color::Magenta), + _ => Style::default(), + }) + .block(Block::default().borders(Borders::ALL).title("(P)ayment-id:")); + f.render_widget(payment_id_input, message_payment_id_layout[1]); match self.burn_input_mode { BurnInputMode::None => (), @@ -204,6 +219,12 @@ impl BurnTab { // Move one line down, from the border to the input line vert_chunks[4].y + 1, ), + BurnInputMode::PaymentId => f.set_cursor( + // Put cursor past the end of the input text + vert_chunks[5].x + self.payment_id_field.width() as u16 + 1, + // Move one line down, from the border to the input line + vert_chunks[5].y + 1, + ), } } @@ -312,6 +333,7 @@ impl BurnTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), + PaymentId::Open(self.payment_id_field.as_bytes().to_vec()), tx, )) { Err(e) => { @@ -356,6 +378,7 @@ impl BurnTab { self.amount_field = "".to_string(); self.fee_field = app_state.get_default_fee_per_gram().as_u64().to_string(); self.message_field = "".to_string(); + self.payment_id_field = "".to_string(); self.burn_input_mode = BurnInputMode::None; self.burn_result_watch = Some(rx); } @@ -414,6 +437,13 @@ impl BurnTab { return KeyHandled::Handled; }, }, + BurnInputMode::PaymentId => match c { + '\n' => self.burn_input_mode = BurnInputMode::None, + c => { + self.payment_id_field.push(c); + return KeyHandled::Handled; + }, + }, } } @@ -608,13 +638,14 @@ impl Component for BurnTab { } match c { - 'p' => self.burn_input_mode = BurnInputMode::BurntProofPath, + 'v' => self.burn_input_mode = BurnInputMode::BurntProofPath, 'c' => self.burn_input_mode = BurnInputMode::ClaimPublicKey, 'a' => { self.burn_input_mode = BurnInputMode::Amount; }, 'f' => self.burn_input_mode = BurnInputMode::Fee, 'm' => self.burn_input_mode = BurnInputMode::Message, + 'p' => self.burn_input_mode = BurnInputMode::PaymentId, 'b' => { self.show_proofs = !self.show_proofs; }, @@ -672,6 +703,9 @@ impl Component for BurnTab { BurnInputMode::Message => { let _ = self.message_field.pop(); }, + BurnInputMode::PaymentId => { + let _ = self.payment_id_field.pop(); + }, BurnInputMode::None => {}, } } @@ -684,6 +718,7 @@ pub enum BurnInputMode { ClaimPublicKey, Amount, Message, + PaymentId, Fee, } diff --git a/applications/minotari_console_wallet/src/ui/components/send_tab.rs b/applications/minotari_console_wallet/src/ui/components/send_tab.rs index 9f634120e35..166b2381536 100644 --- a/applications/minotari_console_wallet/src/ui/components/send_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/send_tab.rs @@ -4,7 +4,7 @@ use log::*; use minotari_wallet::output_manager_service::UtxoSelectionCriteria; use tari_common_types::wallet_types::WalletType; -use tari_core::transactions::tari_amount::MicroMinotari; +use tari_core::transactions::{tari_amount::MicroMinotari, transaction_components::encrypted_data::PaymentId}; use tari_utilities::hex::Hex; use tokio::{runtime::Handle, sync::watch}; use tui::{ @@ -300,7 +300,12 @@ impl SendTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), - self.payment_id_field.clone(), + if self.payment_id_field.is_empty() { + PaymentId::Empty + } else { + let bytes = self.payment_id_field.as_bytes().to_vec(); + PaymentId::Open(bytes) + }, tx, ), ) { @@ -321,6 +326,12 @@ impl SendTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), + if self.payment_id_field.is_empty() { + PaymentId::Empty + } else { + let bytes = self.payment_id_field.as_bytes().to_vec(); + PaymentId::Open(bytes) + }, tx, )) { Err(e) => { diff --git a/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs b/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs index c7ea9d74ff2..eb116661614 100644 --- a/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs @@ -440,16 +440,7 @@ impl TransactionsTab { let maturity = Span::styled(maturity, Style::default().fg(Color::White)); let payment_id = match tx.payment_id.clone() { - Some(v) => { - let bytes = v.get_data(); - if bytes.is_empty() { - format!("#{}", v) - } else { - String::from_utf8(bytes) - .unwrap_or_else(|_| "Invalid string".to_string()) - .to_string() - } - }, + Some(v) => format!("#{}", v.compact_display()), None => "None".to_string(), }; let payment_id = Span::styled(payment_id, Style::default().fg(Color::White)); 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 9fe1cfb3d51..8d3f40be928 100644 --- a/applications/minotari_console_wallet/src/ui/state/app_state.rs +++ b/applications/minotari_console_wallet/src/ui/state/app_state.rs @@ -295,6 +295,7 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, + payment_id: PaymentId, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; @@ -310,6 +311,7 @@ impl AppState { selection_criteria, output_features, message, + payment_id, fee_per_gram, tx_service_handle, result_tx, @@ -325,17 +327,11 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, - payment_id_str: String, + payment_id: PaymentId, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; let address = TariAddress::from_str(&address).map_err(|_| UiError::PublicKeyParseError)?; - let payment_id = if payment_id_str.is_empty() { - PaymentId::Empty - } else { - let bytes = payment_id_str.as_bytes().to_vec(); - PaymentId::Open(bytes) - }; let output_features = OutputFeatures { ..Default::default() }; @@ -364,6 +360,7 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, + payment_id: PaymentId, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; @@ -397,6 +394,7 @@ impl AppState { MicroMinotari::from(amount), selection_criteria, message, + payment_id, fee_per_gram, tx_service_handle, inner.wallet.db.clone(), diff --git a/applications/minotari_console_wallet/src/ui/state/tasks.rs b/applications/minotari_console_wallet/src/ui/state/tasks.rs index b0fb66f47f2..47ce2439777 100644 --- a/applications/minotari_console_wallet/src/ui/state/tasks.rs +++ b/applications/minotari_console_wallet/src/ui/state/tasks.rs @@ -62,6 +62,7 @@ pub async fn send_transaction_task( selection_criteria: UtxoSelectionCriteria, output_features: OutputFeatures, message: String, + payment_id: PaymentId, fee_per_gram: MicroMinotari, mut transaction_service_handle: TransactionServiceHandle, result_tx: watch::Sender, @@ -77,6 +78,7 @@ pub async fn send_transaction_task( output_features, fee_per_gram, message, + payment_id, ) .await { @@ -194,6 +196,7 @@ pub async fn send_burn_transaction_task( amount: MicroMinotari, selection_criteria: UtxoSelectionCriteria, message: String, + payment_id: PaymentId, fee_per_gram: MicroMinotari, mut transaction_service_handle: TransactionServiceHandle, db: WalletDatabase, @@ -207,7 +210,14 @@ pub async fn send_burn_transaction_task( // ---------------------------------------------------------------------------- let (burn_tx_id, original_proof) = match transaction_service_handle - .burn_tari(amount, selection_criteria, fee_per_gram, message, claim_public_key) + .burn_tari( + amount, + selection_criteria, + fee_per_gram, + message, + payment_id, + claim_public_key, + ) .await { Ok((burn_tx_id, original_proof)) => (burn_tx_id, original_proof), diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index 0b7a7b11bb1..9aa83b929ba 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -109,6 +109,7 @@ tempfile = "3.1.0" toml = { version = "0.5" } quickcheck = "1.0" serial_test = "0.5" +static_assertions = "1.1.0" [build-dependencies] tari_common = { path = "../../common", features = [ diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index c6c31498b06..7fa873e5788 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -19,16 +19,19 @@ // 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. +#[cfg(feature = "base_node")] +use std::convert::TryFrom; use std::{ cmp::max, - convert::TryFrom, fmt::{Display, Error, Formatter}, }; use borsh::{BorshDeserialize, BorshSerialize}; use log::*; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{ComAndPubSignature, Commitment, FixedHash, PrivateKey}; +#[cfg(feature = "base_node")] +use tari_common_types::types::FixedHash; +use tari_common_types::types::{ComAndPubSignature, Commitment, PrivateKey}; use tari_crypto::commitment::HomomorphicCommitmentFactory; #[cfg(feature = "base_node")] use tari_mmr::pruned_hashset::PrunedHashSet; diff --git a/base_layer/core/src/transactions/transaction_components/encrypted_data.rs b/base_layer/core/src/transactions/transaction_components/encrypted_data.rs index 070d2c45aa9..854c33e0b1f 100644 --- a/base_layer/core/src/transactions/transaction_components/encrypted_data.rs +++ b/base_layer/core/src/transactions/transaction_components/encrypted_data.rs @@ -42,6 +42,7 @@ use chacha20poly1305::{ XNonce, }; use digest::{consts::U32, generic_array::GenericArray, FixedOutput}; +use num_traits::ToBytes; use primitive_types::U256; use serde::{Deserialize, Serialize}; use tari_common_types::{ @@ -67,6 +68,7 @@ const SIZE_NONCE: usize = size_of::(); const SIZE_VALUE: usize = size_of::(); const SIZE_MASK: usize = PrivateKey::KEY_LEN; const SIZE_TAG: usize = size_of::(); +const SIZE_U256: usize = size_of::(); pub const STATIC_ENCRYPTED_DATA_SIZE_TOTAL: usize = SIZE_NONCE + SIZE_VALUE + SIZE_MASK + SIZE_TAG; const MAX_ENCRYPTED_DATA_SIZE: usize = 256 + STATIC_ENCRYPTED_DATA_SIZE_TOTAL; @@ -81,31 +83,62 @@ pub struct EncryptedData { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub enum PaymentId { + /// No payment ID. Empty, + /// A u64 number. U64(u64), + /// A u256 number. U256(U256), + /// A Tari address, usually the sender address. Address(TariAddress), + /// Open - the user optionally specifies a human-readable string. Open(Vec), - AddressAndData(TariAddress, Vec), + /// This payment ID is automatically generated by the system for output UTXOs. The optional user specified + /// `Open(Vec)` payment ID will be assigned to `id`; the system adds in the sender address. + AddressAndData { sender_address: TariAddress, data: Vec }, + /// This payment ID is automatically generated by the system for change outputs. The optional user specified + /// `Open(Vec)` payment ID will be assigned to `id`; the system adds in the transaction amount and recipient + /// address. + ChangeInfo { + recipient_address: TariAddress, + amount: MicroMinotari, + data: Vec, + }, } impl PaymentId { pub fn get_size(&self) -> usize { match self { PaymentId::Empty => 0, - PaymentId::U64(_) => size_of::(), - PaymentId::U256(_) => size_of::(), + PaymentId::U64(_) => SIZE_VALUE, + PaymentId::U256(_) => SIZE_U256, PaymentId::Address(a) => a.get_size(), PaymentId::Open(v) => v.len(), - PaymentId::AddressAndData(a, v) => a.get_size() + v.len(), + PaymentId::AddressAndData { + sender_address, + data: id, + } => sender_address.get_size() + id.len(), + PaymentId::ChangeInfo { + recipient_address, + amount: _amount, + data: msg_and_id, + } => recipient_address.get_size() + SIZE_VALUE + msg_and_id.len(), } } pub fn get_data(&self) -> Vec { match &self { - PaymentId::Empty | PaymentId::U64(_) | PaymentId::U256(_) | PaymentId::Address(_) => Vec::new(), + PaymentId::Empty => vec![], + PaymentId::U64(v) => v.to_le_bytes().to_vec(), + PaymentId::U256(v) => { + let bytes: &mut [u8] = &mut [0; SIZE_U256]; + v.to_little_endian(bytes); + bytes.to_vec() + }, + PaymentId::Address(v) => v.to_vec(), PaymentId::Open(v) => v.clone(), - PaymentId::AddressAndData(_v, d) => d.clone(), + PaymentId::AddressAndData { data: id, .. } => id.clone(), + PaymentId::ChangeInfo { data: msg_and_id, .. } => msg_and_id.clone(), } } @@ -120,9 +153,22 @@ impl PaymentId { }, PaymentId::Address(v) => v.to_vec(), PaymentId::Open(v) => v.clone(), - PaymentId::AddressAndData(v, d) => { - let mut bytes = v.to_vec(); - bytes.extend_from_slice(d); + PaymentId::AddressAndData { + sender_address, + data: id, + } => { + let mut bytes = sender_address.to_vec(); + bytes.extend_from_slice(id); + bytes + }, + PaymentId::ChangeInfo { + recipient_address, + amount, + data: msg_and_id, + } => { + let mut bytes = amount.as_u64().to_le_bytes().to_vec(); + bytes.extend_from_slice(&recipient_address.to_vec()); + bytes.extend_from_slice(msg_and_id); bytes }, } @@ -131,51 +177,133 @@ impl PaymentId { pub fn from_bytes(bytes: &[u8]) -> Result { match bytes.len() { 0 => Ok(PaymentId::Empty), - 8 => { - let bytes: [u8; 8] = bytes.try_into().expect("Cannot fail, as we already test the length"); + SIZE_VALUE => { + let bytes: [u8; SIZE_VALUE] = bytes.try_into().expect("Cannot fail, as we already test the length"); let v = u64::from_le_bytes(bytes); Ok(PaymentId::U64(v)) }, - 32 => { + SIZE_U256 => { let v = U256::from_little_endian(bytes); Ok(PaymentId::U256(v)) }, - TARI_ADDRESS_INTERNAL_DUAL_SIZE => { - let v = - TariAddress::from_bytes(bytes).map_err(|e| EncryptedDataError::ByteArrayError(e.to_string()))?; - Ok(PaymentId::Address(v)) - }, - TARI_ADDRESS_INTERNAL_SINGLE_SIZE => { - let v = - TariAddress::from_bytes(bytes).map_err(|e| EncryptedDataError::ByteArrayError(e.to_string()))?; - Ok(PaymentId::Address(v)) - }, - len if len < TARI_ADDRESS_INTERNAL_SINGLE_SIZE => Ok(PaymentId::Open(bytes.to_vec())), - len if len < TARI_ADDRESS_INTERNAL_DUAL_SIZE => { - if let Ok(address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_SINGLE_SIZE]) { - Ok(PaymentId::AddressAndData( - address, - bytes[TARI_ADDRESS_INTERNAL_SINGLE_SIZE..].to_vec(), - )) + len if len <= TARI_ADDRESS_INTERNAL_SINGLE_SIZE => { + if let Ok(v) = TariAddress::from_bytes(bytes) { + // Single + Ok(PaymentId::Address(v)) } else { + // data Ok(PaymentId::Open(bytes.to_vec())) } }, _ => { - if let Ok(address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_SINGLE_SIZE]) { - Ok(PaymentId::AddressAndData( - address, - bytes[TARI_ADDRESS_INTERNAL_SINGLE_SIZE..].to_vec(), - )) - } else if let Ok(address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_DUAL_SIZE]) { - Ok(PaymentId::AddressAndData( - address, - bytes[TARI_ADDRESS_INTERNAL_DUAL_SIZE..].to_vec(), - )) - } else { - Ok(PaymentId::Open(bytes.to_vec())) + // PaymentId::Address + if let Ok(v) = TariAddress::from_bytes(bytes) { + // Dual + return Ok(PaymentId::Address(v)); + } + // PaymentId::AddressAndData + if bytes.len() > TARI_ADDRESS_INTERNAL_DUAL_SIZE { + // Dual + data + if let Ok(sender_address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_DUAL_SIZE]) { + return Ok(PaymentId::AddressAndData { + sender_address, + data: bytes[TARI_ADDRESS_INTERNAL_DUAL_SIZE..].to_vec(), + }); + } + } + if bytes.len() > TARI_ADDRESS_INTERNAL_SINGLE_SIZE { + // Single + data + if let Ok(sender_address) = TariAddress::from_bytes(&bytes[0..TARI_ADDRESS_INTERNAL_SINGLE_SIZE]) { + return Ok(PaymentId::AddressAndData { + sender_address, + data: bytes[TARI_ADDRESS_INTERNAL_SINGLE_SIZE..].to_vec(), + }); + } + } + // PaymentId::ChangeData + let mut le_bytes = [0u8; SIZE_VALUE]; + le_bytes.copy_from_slice(&bytes[0..SIZE_VALUE]); + let amount = u64::from_le_bytes(le_bytes); + // Amount + Single/Dual + if let Ok(recipient_address) = TariAddress::from_bytes(&bytes[SIZE_VALUE..]) { + return Ok(PaymentId::ChangeInfo { + recipient_address, + amount: MicroMinotari::from(amount), + data: Vec::new(), + }); } + if bytes.len() > SIZE_VALUE + TARI_ADDRESS_INTERNAL_DUAL_SIZE { + if let Ok(recipient_address) = + TariAddress::from_bytes(&bytes[SIZE_VALUE..SIZE_VALUE + TARI_ADDRESS_INTERNAL_DUAL_SIZE]) + { + // Amount + Dual + data + return Ok(PaymentId::ChangeInfo { + recipient_address, + amount: MicroMinotari::from(amount), + data: bytes[SIZE_VALUE + TARI_ADDRESS_INTERNAL_DUAL_SIZE..].to_vec(), + }); + } + } + if bytes.len() > SIZE_VALUE + TARI_ADDRESS_INTERNAL_SINGLE_SIZE { + if let Ok(recipient_address) = + TariAddress::from_bytes(&bytes[SIZE_VALUE..SIZE_VALUE + TARI_ADDRESS_INTERNAL_SINGLE_SIZE]) + { + // Amount + Single + data + return Ok(PaymentId::ChangeInfo { + recipient_address, + amount: MicroMinotari::from(amount), + data: bytes[SIZE_VALUE + TARI_ADDRESS_INTERNAL_SINGLE_SIZE..].to_vec(), + }); + } + } + // Single + Ok(PaymentId::Open(bytes.to_vec())) + }, + } + } + + /// Helper function to convert a byte slice to a string for the open and data variants + pub fn stringify_bytes(bytes: &[u8]) -> String { + String::from_utf8_lossy(bytes).to_string() + } + + /// Helper function to display the payment id in a compact form + pub fn compact_display(&self) -> String { + fn short_address(s: &str, start_end_display: usize) -> String { + if s.len() > 2 * start_end_display { + format!( + "{}..{}", + &s[0..start_end_display], + &s[s.len() - start_end_display..s.len()] + ) + } else { + s.to_string() + } + } + match self { + PaymentId::Empty | PaymentId::U64(_) | PaymentId::U256(_) | PaymentId::Address(_) | PaymentId::Open(_) => { + self.to_string() }, + PaymentId::AddressAndData { + sender_address, + data: id, + } => { + format!( + "address({}), data({})", + short_address(&sender_address.to_base58(), 8), + PaymentId::stringify_bytes(id) + ) + }, + PaymentId::ChangeInfo { + recipient_address, + amount, + data: msg_and_id, + } => format!( + "address({}), amount({}), data({})", + short_address(&recipient_address.to_base58(), 8), + amount, + PaymentId::stringify_bytes(msg_and_id), + ), } } } @@ -187,8 +315,27 @@ impl Display for PaymentId { PaymentId::U64(v) => write!(f, "u64({v})"), PaymentId::U256(v) => write!(f, "u256({v})"), PaymentId::Address(v) => write!(f, "address({})", v.to_base58()), - PaymentId::Open(v) => write!(f, "data({})", v.to_hex()), - PaymentId::AddressAndData(v, d) => write!(f, "address_and_data({},{})", v.to_base58(), d.to_hex()), + PaymentId::Open(v) => write!(f, "data({})", PaymentId::stringify_bytes(v)), + PaymentId::AddressAndData { + sender_address, + data: id, + } => write!( + f, + "address({}), data({})", + sender_address.to_base58(), + PaymentId::stringify_bytes(id) + ), + PaymentId::ChangeInfo { + recipient_address, + amount, + data: msg_and_id, + } => write!( + f, + "address({}), amount({}), data({})", + recipient_address.to_base58(), + amount, + PaymentId::stringify_bytes(msg_and_id), + ), } } } @@ -388,11 +535,118 @@ fn kdf_aead(encryption_key: &PrivateKey, commitment: &Commitment) -> EncryptedDa #[cfg(test)] mod test { + use static_assertions::const_assert; use tari_common_types::types::CommitmentFactory; use tari_crypto::commitment::HomomorphicCommitmentFactory; use super::*; + #[test] + fn address_sizes_increase_as_expected() { + const_assert!(SIZE_VALUE < SIZE_U256); + const_assert!(SIZE_U256 < TARI_ADDRESS_INTERNAL_SINGLE_SIZE); + const_assert!(TARI_ADDRESS_INTERNAL_SINGLE_SIZE < TARI_ADDRESS_INTERNAL_DUAL_SIZE); + } + + fn encrypt_decrypt_payment_id(payment_id: PaymentId) -> PaymentId { + let (value, mask) = (u64::MAX, PrivateKey::random(&mut OsRng)); + let commitment = CommitmentFactory::default().commit(&mask, &PrivateKey::from(value)); + let encryption_key = PrivateKey::random(&mut OsRng); + let amount = MicroMinotari::from(value); + let encrypted_data = + EncryptedData::encrypt_data(&encryption_key, &commitment, amount, &mask, payment_id.clone()).unwrap(); + let (_decrypted_value, _decrypted_mask, decrypted_payment_id) = + EncryptedData::decrypt_data(&encryption_key, &commitment, &encrypted_data).unwrap(); + decrypted_payment_id + } + + #[test] + fn it_encrypts_and_decrypts_equivalent_structures() { + // Address vs. AddressAndData with zero data + + // Single + let payment_id_1 = + PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()); + let payment_id_2 = PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![], + }; + + let decrypted_payment_id_1 = encrypt_decrypt_payment_id(payment_id_1.clone()); + let decrypted_payment_id_2 = encrypt_decrypt_payment_id(payment_id_2); + // Decrypted bytes are the same + assert_eq!(decrypted_payment_id_1.to_bytes(), decrypted_payment_id_2.to_bytes()); + // But the types are different + assert_eq!(payment_id_1, decrypted_payment_id_1); + assert_eq!(payment_id_1, decrypted_payment_id_2); + + // Dual + let payment_id_1 = PaymentId::Address( + TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + ); + let payment_id_2 = PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + data: vec![], + }; + + let decrypted_payment_id_1 = encrypt_decrypt_payment_id(payment_id_1.clone()); + let decrypted_payment_id_2 = encrypt_decrypt_payment_id(payment_id_2); + // Decrypted bytes are the same + assert_eq!(decrypted_payment_id_1.to_bytes(), decrypted_payment_id_2.to_bytes()); + // But the types are different + assert_eq!(payment_id_1, decrypted_payment_id_1); + assert_eq!(payment_id_1, decrypted_payment_id_2); + } + + fn test_edge_case_address(payment_id: PaymentId) { + if let PaymentId::Address(address) = payment_id.clone() { + let payment_id_bytes = payment_id.to_bytes(); + // Manipulate the last byte to invalidate the address + let mut payment_id_2_bytes = payment_id_bytes.clone(); + let payment_id_2_bytes_len = payment_id_2_bytes.len(); + payment_id_2_bytes[payment_id_2_bytes_len - 1] += 1; + + // The original payment_id deserializes to 'PaymentId::Address' + let payment_id_from_bytes = PaymentId::from_bytes(&payment_id_bytes).unwrap(); + assert_eq!(payment_id_from_bytes, payment_id); + + // The manipulated payment_id deserializes to 'PaymentId::Open' + let payment_id_2_from_bytes = PaymentId::from_bytes(&payment_id_2_bytes).unwrap(); + assert_eq!(payment_id_2_from_bytes, PaymentId::Open(payment_id_2_bytes)); + + // All the bytes except for the last byte of the two payment ids should be the same + let address_vec = address.to_vec(); + let payment_id_2_vec = payment_id_2_from_bytes.get_data(); + assert_eq!( + address_vec[..address_vec.len() - 1], + payment_id_2_vec[..payment_id_2_vec.len() - 1] + ); + } else { + panic!("payment_id_1 should be an address"); + } + } + + #[test] + fn it_encrypts_and_decrypts_edge_cases() { + let payment_id = + PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()); + test_edge_case_address(payment_id); + + let payment_id = PaymentId::Address( + TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + ); + test_edge_case_address(payment_id); + } + #[test] fn it_encrypts_and_decrypts_correctly() { for payment_id in [ @@ -411,28 +665,58 @@ mod test { PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()), PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), PaymentId::Open(vec![1; 256]), - PaymentId::AddressAndData( - TariAddress::from_base58( + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", ) .unwrap(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ), - PaymentId::AddressAndData( - TariAddress::from_base58( + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", ) .unwrap(), - vec![1; 189], - ), - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ), - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![1; 189], - ), + data: vec![1; 189], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![1; 189], + }, + // Single + amount + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![], + }, + // Single + amount + data + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + // Dual + amount + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + amount: MicroMinotari::from(123456), + data: vec![], + }, + // Dual + amount + data + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + amount: MicroMinotari::from(123456), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, ] { for (value, mask) in [ (0, PrivateKey::default()), @@ -481,28 +765,58 @@ mod test { PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()), PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), PaymentId::Open(vec![1; 256]), - PaymentId::AddressAndData( - TariAddress::from_base58( + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", ) .unwrap(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ), - PaymentId::AddressAndData( - TariAddress::from_base58( + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58( "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", ) .unwrap(), - vec![1; 189], - ), - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - ), - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![1; 189], - ), + data: vec![1; 189], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![1; 189], + }, + // Single + amount + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![], + }, + // Single + amount + data + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, + // Dual + amount + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + amount: MicroMinotari::from(123456), + data: vec![], + }, + // Dual + amount + data + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + amount: MicroMinotari::from(123456), + data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + }, ] { for (value, mask) in [ (0, PrivateKey::default()), @@ -541,16 +855,90 @@ mod test { "address(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk)" ); assert_eq!( - PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).to_string(), - "data(0102030405060708090a)" + PaymentId::Open(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64]).to_string(), + "data(Hello World)" ); assert_eq!( - PaymentId::AddressAndData( - TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), - vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64] - ) + PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64] + } + .to_string(), + "address(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk), data(Hello World)" + ); + assert_eq!( + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(123456), + data: vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64] + } .to_string(), - "address_and_data(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk,48656c6c6f20576f726c64)" + "address(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk), amount(123456 µT), data(Hello World)" + ); + assert_eq!( + PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(1234), + data: { + let mut msg_and_id = "Hello World!!!".as_bytes().to_vec(); + let mut separator = " | ".as_bytes().to_vec(); + msg_and_id.append(&mut separator); + let mut id = "11-22-33".as_bytes().to_vec(); + msg_and_id.append(&mut id); + msg_and_id + } + } + .to_string(), + "address(f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk), amount(1234 µT), data(Hello World!!! | \ + 11-22-33)" + ); + } + + #[test] + fn it_gets_useable_data() { + let payment_id = PaymentId::Empty; + assert_eq!("", PaymentId::stringify_bytes(&payment_id.get_data())); + + let payment_id = PaymentId::U64(12345); + assert_eq!( + "12345", + u64::from_le_bytes(payment_id.get_data().try_into().unwrap()).to_string() + ); + + let payment_id = PaymentId::U256(U256::from_dec_str("123456789").unwrap()); + assert_eq!( + "123456789", + U256::from_little_endian(&payment_id.get_data()).to_string() + ); + + let payment_id = + PaymentId::Address(TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap()); + assert_eq!( + "f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk", + TariAddress::from_bytes(&payment_id.get_data()).unwrap().to_base58() + ); + + let payment_id = PaymentId::AddressAndData { + sender_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + data: "Hello World!!!".as_bytes().to_vec(), + }; + assert_eq!("Hello World!!!", PaymentId::stringify_bytes(&payment_id.get_data())); + + let payment_id = PaymentId::ChangeInfo { + recipient_address: TariAddress::from_base58("f3S7XTiyKQauZpDUjdR8NbcQ33MYJigiWiS44ccZCxwAAjk").unwrap(), + amount: MicroMinotari::from(1234), + data: { + let mut msg_and_id = "Hello World!!!".as_bytes().to_vec(); + let mut separator = " | ".as_bytes().to_vec(); + msg_and_id.append(&mut separator); + let mut id = "11-22-33".as_bytes().to_vec(); + msg_and_id.append(&mut id); + msg_and_id + }, + }; + assert_eq!( + "Hello World!!! | 11-22-33", + PaymentId::stringify_bytes(&payment_id.get_data()) ); } } diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index ef9ef19256c..f6f24d700b3 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -1130,6 +1130,7 @@ mod test { Covenant::default(), 0.into(), MicroMinotari(1200) - fee - MicroMinotari(10), + TariAddress::default(), ) .await .unwrap() @@ -1255,6 +1256,7 @@ mod test { Covenant::default(), 0.into(), MicroMinotari(5000), + TariAddress::default(), ) .await .unwrap(); @@ -1367,6 +1369,7 @@ mod test { Covenant::default(), 0.into(), MicroMinotari(5000), + TariAddress::default(), ) .await .unwrap(); @@ -1467,6 +1470,7 @@ mod test { Covenant::default(), 0.into(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -1507,6 +1511,7 @@ mod test { Covenant::default(), 0.into(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -1552,6 +1557,7 @@ mod test { Covenant::default(), 0.into(), MicroMinotari(5000), + TariAddress::default(), ) .await .unwrap(); 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 ff0a54f0028..161ac2747fe 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -79,6 +79,7 @@ pub(super) struct RecipientDetails { pub recipient_covenant: Covenant, pub recipient_minimum_value_promise: MicroMinotari, pub recipient_ephemeral_public_key_nonce: TariKeyId, + pub recipient_address: TariAddress, } /// The SenderTransactionProtocolBuilder is a Builder that helps set up the initial state for the Sender party of a new @@ -98,6 +99,7 @@ pub struct SenderTransactionInitializer { change: Option, recipient: Option, recipient_text_message: Option, + payment_id: Option, prevent_fee_gt_amount: bool, tx_id: Option, kernel_features: KernelFeatures, @@ -130,6 +132,7 @@ where KM: TransactionKeyManagerInterface sender_custom_outputs: Vec::new(), change: None, recipient_text_message: None, + payment_id: None, prevent_fee_gt_amount: true, recipient: None, kernel_features: KernelFeatures::empty(), @@ -162,6 +165,7 @@ where KM: TransactionKeyManagerInterface recipient_covenant: Covenant, recipient_minimum_value_promise: MicroMinotari, amount: MicroMinotari, + recipient_address: TariAddress, ) -> Result<&mut Self, KeyManagerServiceError> { let recipient_ephemeral_public_key_nonce = self .key_manager @@ -179,6 +183,7 @@ where KM: TransactionKeyManagerInterface recipient_minimum_value_promise, recipient_ephemeral_public_key_nonce: recipient_ephemeral_public_key_nonce.key_id, amount, + recipient_address, }; self.recipient = Some(recipient_details); Ok(self) @@ -253,6 +258,54 @@ where KM: TransactionKeyManagerInterface self } + /// Provide a text message for receiver + pub fn with_payment_id(&mut self, payment_id: PaymentId) -> &mut Self { + self.payment_id = Some(payment_id); + self + } + + /// Append message and payment id as bytes + pub fn append_message_and_id(&self) -> Option> { + if let Some(mut message) = self.recipient_text_message.clone() { + if let Some(payment_id) = self.payment_id.clone() { + message.push_str(" | "); + let mut msg_and_id_bytes = message.into_bytes(); + match payment_id { + PaymentId::Empty => { + let mut empty = "*no payment id*".to_string().as_bytes().to_vec(); + msg_and_id_bytes.append(&mut empty); + }, + PaymentId::U64(val) => { + let mut data = val.to_string().as_bytes().to_vec(); + msg_and_id_bytes.append(&mut data); + }, + PaymentId::U256(val) => { + let mut data = val.to_string().as_bytes().to_vec(); + msg_and_id_bytes.append(&mut data); + }, + PaymentId::Address(val) => { + let mut data = val.to_base58().as_bytes().to_vec(); + msg_and_id_bytes.append(&mut data); + }, + PaymentId::Open(mut val) => { + msg_and_id_bytes.append(&mut val); + }, + PaymentId::AddressAndData { data: mut id, .. } => { + msg_and_id_bytes.append(&mut id); + }, + PaymentId::ChangeInfo { + data: mut msg_and_id, .. + } => { + msg_and_id_bytes.append(&mut msg_and_id); + }, + } + return Some(msg_and_id_bytes); + } + return Some(message.into_bytes()); + } + None + } + /// This will select the desired kernel features to be signed by the receiver pub fn with_kernel_features(&mut self, features: KernelFeatures) -> &mut Self { self.kernel_features = features; @@ -402,7 +455,19 @@ where KM: TransactionKeyManagerInterface .own_address .clone(); - let payment_id = PaymentId::Address(address); + let payment_id = if let Some(recipient) = self.recipient.clone() { + PaymentId::ChangeInfo { + recipient_address: recipient.recipient_address, + amount: recipient.amount, + data: self.append_message_and_id().unwrap_or_default(), + } + } else { + PaymentId::ChangeInfo { + recipient_address: address, + amount: total_to_self, + data: self.append_message_and_id().unwrap_or_default(), + } + }; let encrypted_data = self .key_manager @@ -878,6 +943,7 @@ mod test { Default::default(), 0.into(), MicroMinotari(500), + TariAddress::default(), ) .await .unwrap(); @@ -927,6 +993,7 @@ mod test { Default::default(), 0.into(), MicroMinotari::zero(), + TariAddress::default(), ) .await .unwrap(); @@ -994,6 +1061,7 @@ mod test { Default::default(), 0.into(), MicroMinotari(2500), + TariAddress::default(), ) .await .unwrap(); diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 7f32a2f147d..e5334ad96ab 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -31,7 +31,14 @@ use tari_core::{ covenants::Covenant, transactions::{ tari_amount::MicroMinotari, - transaction_components::{OutputFeatures, Transaction, TransactionOutput, WalletOutput, WalletOutputBuilder}, + transaction_components::{ + encrypted_data::PaymentId, + OutputFeatures, + Transaction, + TransactionOutput, + WalletOutput, + WalletOutputBuilder, + }, transaction_protocol::{sender::TransactionSenderMessage, TransactionMetadata}, ReceiverTransactionProtocol, SenderTransactionProtocol, @@ -92,6 +99,8 @@ pub enum OutputManagerRequest { script: TariScript, covenant: Covenant, minimum_value_promise: MicroMinotari, + recipient_address: TariAddress, + payment_id: PaymentId, }, CreatePayToSelfTransaction { tx_id: TxId, @@ -105,6 +114,8 @@ pub enum OutputManagerRequest { outputs: Vec, fee_per_gram: MicroMinotari, selection_criteria: UtxoSelectionCriteria, + message: String, + payment_id: PaymentId, }, CancelTransaction(TxId), GetSpentOutputs, @@ -119,6 +130,7 @@ pub enum OutputManagerRequest { ScrapeWallet { tx_id: TxId, fee_per_gram: MicroMinotari, + recipient_address: TariAddress, }, CreateCoinJoin { commitments: Vec, @@ -167,8 +179,16 @@ impl fmt::Display for OutputManagerRequest { v.metadata_signature.u_y().to_hex(), v.metadata_signature.u_a().to_hex(), ), - ScrapeWallet { tx_id, fee_per_gram } => { - write!(f, "ScrapeWallet (tx_id: {}, fee_per_gram: {})", tx_id, fee_per_gram) + ScrapeWallet { + tx_id, + fee_per_gram, + recipient_address, + } => { + write!( + f, + "ScrapeWallet (tx_id: {}, fee_per_gram: {}, recipient_address {})", + tx_id, fee_per_gram, recipient_address + ) }, EncumberAggregateUtxo { tx_id, @@ -509,6 +529,8 @@ impl OutputManagerHandle { script: TariScript, covenant: Covenant, minimum_value_promise: MicroMinotari, + recipient_address: TariAddress, + payment_id: PaymentId, ) -> Result { match self .handle @@ -523,6 +545,8 @@ impl OutputManagerHandle { script, covenant, minimum_value_promise, + recipient_address, + payment_id, }) .await?? { @@ -535,10 +559,15 @@ impl OutputManagerHandle { &mut self, tx_id: TxId, fee_per_gram: MicroMinotari, + recipient_address: TariAddress, ) -> Result { match self .handle - .call(OutputManagerRequest::ScrapeWallet { tx_id, fee_per_gram }) + .call(OutputManagerRequest::ScrapeWallet { + tx_id, + fee_per_gram, + recipient_address, + }) .await?? { OutputManagerResponse::TransactionToSend(stp) => Ok(stp), @@ -795,6 +824,8 @@ impl OutputManagerHandle { outputs: Vec, fee_per_gram: MicroMinotari, input_selection: UtxoSelectionCriteria, + message: String, + payment_id: PaymentId, ) -> Result<(TxId, Transaction), OutputManagerError> { match self .handle @@ -802,6 +833,8 @@ impl OutputManagerHandle { outputs, fee_per_gram, selection_criteria: input_selection, + message, + payment_id, }) .await?? { diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 3b14cb59a13..9eb6da7ade5 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -269,7 +269,6 @@ where metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, - PaymentId::Empty, original_maturity, RangeProofType::BulletProofPlus, 0.into(), @@ -290,7 +289,7 @@ where output_hash, expected_commitment, recipient_address, - PaymentId::Empty, + PaymentId::Open(output_hash.to_vec()), 0, RangeProofType::BulletProofPlus, 0.into(), @@ -327,6 +326,8 @@ where script, covenant, minimum_value_promise, + recipient_address, + payment_id, } => self .prepare_transaction_to_send( tx_id, @@ -339,6 +340,8 @@ where script, covenant, minimum_value_promise, + recipient_address, + payment_id, ) .await .map(OutputManagerResponse::TransactionToSend), @@ -400,8 +403,12 @@ where .await?, )) }, - OutputManagerRequest::ScrapeWallet { tx_id, fee_per_gram } => self - .scrape_wallet(tx_id, fee_per_gram) + OutputManagerRequest::ScrapeWallet { + tx_id, + fee_per_gram, + recipient_address, + } => self + .scrape_wallet(tx_id, fee_per_gram, recipient_address) .await .map(OutputManagerResponse::TransactionToSend), @@ -472,9 +479,17 @@ where outputs, fee_per_gram, selection_criteria, + message, + payment_id, } => { let (tx_id, transaction) = self - .create_pay_to_self_containing_outputs(outputs, selection_criteria, fee_per_gram) + .create_pay_to_self_containing_outputs( + outputs, + selection_criteria, + fee_per_gram, + message, + payment_id, + ) .await?; Ok(OutputManagerResponse::CreatePayToSelfWithOutputs { transaction: Box::new(transaction), @@ -965,6 +980,8 @@ where recipient_script: TariScript, recipient_covenant: Covenant, recipient_minimum_value_promise: MicroMinotari, + recipient_address: TariAddress, + payment_id: PaymentId, ) -> Result { debug!( target: LOG_TARGET, @@ -1011,10 +1028,12 @@ where recipient_covenant, recipient_minimum_value_promise, amount, + recipient_address, ) .await? .with_sender_address(self.resources.interactive_tari_address.clone()) .with_message(message) + .with_payment_id(payment_id) .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) .with_lock_height(tx_meta.lock_height) .with_kernel_features(tx_meta.kernel_features) @@ -1087,6 +1106,8 @@ where outputs: Vec, selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, + message: String, + payment_id: PaymentId, ) -> Result<(TxId, Transaction), OutputManagerError> { let total_value = outputs.iter().map(|o| o.value()).sum(); let nop_script = script![Nop]?; @@ -1131,7 +1152,9 @@ where .with_lock_height(0) .with_fee_per_gram(fee_per_gram) .with_prevent_fee_gt_amount(false) - .with_kernel_features(KernelFeatures::empty()); + .with_kernel_features(KernelFeatures::empty()) + .with_message(message) + .with_payment_id(payment_id); for uo in input_selection.iter() { builder.with_input(uo.wallet_output.clone()).await?; @@ -1249,7 +1272,6 @@ where metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, - payment_id: PaymentId, original_maturity: u64, range_proof_type: RangeProofType, minimum_value_promise: MicroMinotari, @@ -1299,7 +1321,7 @@ where let mut aggregated_script_public_key_shares = PublicKey::default(); trace!(target: LOG_TARGET, "encumber_aggregate_utxo: created deterministic encryption key"); // Decrypt the output secrets and create a new input as WalletOutput (unblinded) - let input = if let Ok((amount, commitment_mask, payment_id)) = + let (input, payment_id) = if let Ok((amount, commitment_mask, payment_id)) = EncryptedData::decrypt_data(&encryption_private_key, &output.commitment, &output.encrypted_data) { if output.verify_mask(&self.resources.factories.range_proof, &commitment_mask, amount.as_u64())? { @@ -1335,21 +1357,24 @@ where ))); } let commitment_mask_key_id = self.resources.key_manager.import_key(commitment_mask).await?; - WalletOutput::new_with_rangeproof( - output.version, - amount, - commitment_mask_key_id, - output.features, - output.script, - ExecutionStack::new(script_signatures), - script_key.key_id.clone(), // Only of the master wallet - output.sender_offset_public_key, - output.metadata_signature, - 0, - output.covenant, - output.encrypted_data, - output.minimum_value_promise, - output.proof, + ( + WalletOutput::new_with_rangeproof( + output.version, + amount, + commitment_mask_key_id, + output.features, + output.script, + ExecutionStack::new(script_signatures), + script_key.key_id.clone(), // Only of the master wallet + output.sender_offset_public_key, + output.metadata_signature, + 0, + output.covenant, + output.encrypted_data, + output.minimum_value_promise, + output.proof, + payment_id.clone(), + ), payment_id, ) } else { @@ -1408,6 +1433,7 @@ where Covenant::default(), minimum_value_promise, amount, + recipient_address.clone(), ) .await? .with_change_data( @@ -1417,7 +1443,9 @@ where TariKeyId::default(), Covenant::default(), self.resources.interactive_tari_address.clone(), - ); + ) + .with_message("Encumber aggregate UTXO".to_string()) + .with_payment_id(payment_id.clone()); let mut stp = builder .build() .await @@ -1740,6 +1768,7 @@ where Covenant::default(), minimum_value_promise, amount, + recipient_address.clone(), ) .await? .with_change_data( @@ -1749,7 +1778,9 @@ where TariKeyId::default(), Covenant::default(), self.resources.one_sided_tari_address.clone(), - ); + ) + .with_message("Spend backup pre-mine UTXO".to_string()) + .with_payment_id(payment_id.clone()); let mut stp = builder .build() .await @@ -1818,7 +1849,10 @@ where .await?; let script = push_pubkey_script(&script_spending_key); let payment_id = match payment_id { - PaymentId::Open(v) => PaymentId::AddressAndData(self.resources.interactive_tari_address.clone(), v), + PaymentId::Open(v) => PaymentId::AddressAndData { + sender_address: self.resources.interactive_tari_address.clone(), + data: v, + }, PaymentId::Empty => PaymentId::Address(self.resources.one_sided_tari_address.clone()), _ => payment_id, }; @@ -1944,14 +1978,16 @@ where .key_manager .get_next_commitment_mask_and_script_key() .await?; - builder.with_change_data( - script!(PushPubKey(Box::new(change_script_public_key.pub_key.clone())))?, - ExecutionStack::default(), - change_script_public_key.key_id.clone(), - change_commitment_mask_key_id.key_id, - Covenant::default(), - self.resources.interactive_tari_address.clone(), - ); + builder + .with_change_data( + script!(PushPubKey(Box::new(change_script_public_key.pub_key.clone())))?, + ExecutionStack::default(), + change_script_public_key.key_id.clone(), + change_commitment_mask_key_id.key_id, + Covenant::default(), + self.resources.interactive_tari_address.clone(), + ) + .with_message("Pay to self transaction".to_string()); let mut stp = builder .build() @@ -2381,6 +2417,10 @@ where self.resources.key_manager.clone(), ); tx_builder + .with_message(format!( + "Coin split transaction, {} into {} outputs", + accumulated_amount, number_of_splits + )) .with_lock_height(0) .with_fee_per_gram(fee_per_gram) .with_kernel_features(KernelFeatures::empty()); @@ -2540,6 +2580,10 @@ where self.resources.key_manager.clone(), ); tx_builder + .with_message(format!( + "Coin split transaction, {} into {} outputs", + accumulated_amount, number_of_splits + )) .with_lock_height(0) .with_fee_per_gram(fee_per_gram) .with_kernel_features(KernelFeatures::empty()); @@ -2772,6 +2816,11 @@ where self.resources.key_manager.clone(), ); tx_builder + .with_message(format!( + "Coin join transaction, {} outputs{} into", + src_outputs.len(), + accumulated_amount + )) .with_lock_height(0) .with_fee_per_gram(fee_per_gram) .with_kernel_features(KernelFeatures::empty()); @@ -2831,6 +2880,7 @@ where &mut self, tx_id: TxId, fee_per_gram: MicroMinotari, + recipient_address: TariAddress, ) -> Result { let default_features_and_scripts_size = self .default_features_and_scripts_size() @@ -2862,6 +2912,7 @@ where Default::default(), MicroMinotari::zero(), accumulated_amount, + recipient_address, ) .await? .with_sender_address(self.resources.interactive_tari_address.clone()) diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index c270e18435a..332fc723417 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -94,12 +94,14 @@ pub enum TransactionServiceRequest { output_features: Box, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, }, BurnTari { amount: MicroMinotari, selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, claim_public_key: Option, }, EncumberAggregateUtxo { @@ -224,13 +226,19 @@ impl fmt::Display for TransactionServiceRequest { destination, amount, message, + payment_id, .. } => write!( f, - "SendTransaction (amount: {}, to: {}, message: {})", - amount, destination, message + "SendTransaction (amount: {}, to: {}, message: {}, payment_id: {})", + amount, destination, message, payment_id ), - Self::BurnTari { amount, message, .. } => write!(f, "Burning Tari ({}, {})", amount, message), + Self::BurnTari { + amount, + message, + payment_id, + .. + } => write!(f, "Burning Tari ({}, {}, {})", amount, message, payment_id), Self::SpendBackupPreMineUtxo { fee_per_gram, output_hash, @@ -609,6 +617,7 @@ impl TransactionServiceHandle { output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { match self .handle @@ -619,6 +628,7 @@ impl TransactionServiceHandle { output_features: Box::new(output_features), fee_per_gram, message, + payment_id, }) .await?? { @@ -738,6 +748,7 @@ impl TransactionServiceHandle { selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, claim_public_key: Option, ) -> Result<(TxId, BurntProof), TransactionServiceError> { match self @@ -747,6 +758,7 @@ impl TransactionServiceHandle { selection_criteria, fee_per_gram, message, + payment_id, claim_public_key, }) .await?? 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 e6c0a95f23b..0fb85cbb477 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 @@ -39,7 +39,7 @@ use tari_core::{ transactions::{ key_manager::TransactionKeyManagerInterface, tari_amount::MicroMinotari, - transaction_components::OutputFeatures, + transaction_components::{encrypted_data::PaymentId, OutputFeatures}, transaction_protocol::{ proto::protocol as proto, recipient::RecipientSignedMessage, @@ -93,6 +93,7 @@ pub struct TransactionSendProtocol>>, stage: TransactionSendProtocolStage, resources: TransactionServiceResources, @@ -118,6 +119,7 @@ where amount: MicroMinotari, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, tx_meta: TransactionMetadata, service_request_reply_channel: Option< oneshot::Sender>, @@ -134,6 +136,7 @@ where amount, fee_per_gram, message, + payment_id, service_request_reply_channel, stage, tx_meta, @@ -228,6 +231,8 @@ where TariScript::default(), Covenant::default(), MicroMinotari::zero(), + self.dest_address.clone(), + self.payment_id.clone(), ) .await { @@ -290,6 +295,7 @@ where sender_protocol.clone(), TransactionStatus::Pending, // This does not matter for the check self.message.clone(), + self.payment_id.clone(), Utc::now().naive_utc(), true, // This does not matter for the check ); @@ -344,6 +350,7 @@ where sender_protocol.clone(), initial_send.transaction_status.clone(), self.message.clone(), + self.payment_id.clone(), Utc::now().naive_utc(), initial_send.direct_send_result, ); diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 9988c774ff2..2b1a81ff40f 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -614,6 +614,7 @@ where output_features, fee_per_gram, message, + payment_id, } => { let rp = reply_channel.take().expect("Cannot be missing"); self.send_transaction( @@ -623,6 +624,7 @@ where *output_features, fee_per_gram, message, + payment_id, TransactionMetadata::default(), send_transaction_join_handles, transaction_broadcast_join_handles, @@ -686,6 +688,7 @@ where selection_criteria, fee_per_gram, message, + payment_id, claim_public_key, } => self .burn_tari( @@ -693,6 +696,7 @@ where selection_criteria, fee_per_gram, message, + payment_id, claim_public_key, transaction_broadcast_join_handles, ) @@ -1077,6 +1081,7 @@ where output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, tx_meta: TransactionMetadata, join_handles: &mut FuturesUnordered< JoinHandle>>, @@ -1135,7 +1140,7 @@ where TransactionDirection::Inbound, None, None, - None, + Some(payment_id), )?, ) .await?; @@ -1164,6 +1169,7 @@ where amount, fee_per_gram, message, + payment_id, tx_meta, Some(reply_channel), TransactionSendProtocolStage::Initial, @@ -1474,6 +1480,8 @@ where script.clone(), covenant.clone(), minimum_value_promise, + destination.clone(), + PaymentId::Empty, ) .await?; @@ -1672,8 +1680,14 @@ where ) -> Result { let tx_id = TxId::new_random(); let payment_id = match payment_id { - PaymentId::Open(v) => PaymentId::AddressAndData(self.resources.interactive_tari_address.clone(), v), - PaymentId::Empty => PaymentId::Address(self.resources.interactive_tari_address.clone()), + PaymentId::Open(v) => PaymentId::AddressAndData { + sender_address: self.resources.interactive_tari_address.clone(), + data: v, + }, + PaymentId::Empty => PaymentId::AddressAndData { + sender_address: self.resources.interactive_tari_address.clone(), + data: vec![], + }, _ => payment_id, }; self.verify_send(&dest_address, TariAddressFeatures::create_one_sided_only())?; @@ -1700,6 +1714,8 @@ where script.clone(), Covenant::default(), MicroMinotari::zero(), + dest_address.clone(), + payment_id.clone(), ) .await?; @@ -1914,7 +1930,7 @@ where let mut stp = self .resources .output_manager_service - .scrape_wallet(tx_id, fee_per_gram) + .scrape_wallet(tx_id, fee_per_gram, dest_address.clone()) .await?; // This call is needed to advance the state from `SingleRoundMessageReady` to `SingleRoundMessageReady`, @@ -2147,6 +2163,7 @@ where selection_criteria: UtxoSelectionCriteria, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, claim_public_key: Option, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, @@ -2176,6 +2193,8 @@ where script!(Nop)?, Covenant::default(), MicroMinotari::zero(), + self.resources.interactive_tari_address.clone(), + payment_id, ) .await?; @@ -2393,6 +2412,7 @@ where output_features, fee_per_gram, message, + PaymentId::Empty, TransactionMetadata::default(), join_handles, transaction_broadcast_join_handles, @@ -2422,6 +2442,7 @@ where OutputFeatures::for_template_registration(template_registration), fee_per_gram, message, + PaymentId::Empty, TransactionMetadata::default(), join_handles, transaction_broadcast_join_handles, @@ -2790,6 +2811,7 @@ where tx.amount, tx.fee, tx.message, + tx.payment_id, TransactionMetadata::default(), None, stage, @@ -3045,7 +3067,11 @@ where let mut amount = None; for ro in recovered { match &ro.output.payment_id { - PaymentId::AddressAndData(address, _) | PaymentId::Address(address) => { + PaymentId::AddressAndData { + sender_address: address, + data: _, + } | + PaymentId::Address(address) => { if source_address.is_none() { source_address = Some(address.clone()); payment_id = Some(ro.output.payment_id.clone()); diff --git a/base_layer/wallet/src/transaction_service/storage/models.rs b/base_layer/wallet/src/transaction_service/storage/models.rs index fa81435234e..fa0c564fda7 100644 --- a/base_layer/wallet/src/transaction_service/storage/models.rs +++ b/base_layer/wallet/src/transaction_service/storage/models.rs @@ -91,6 +91,7 @@ pub struct OutboundTransaction { pub sender_protocol: SenderTransactionProtocol, pub status: TransactionStatus, pub message: String, + pub payment_id: PaymentId, pub timestamp: NaiveDateTime, pub cancelled: bool, pub direct_send_success: bool, @@ -107,6 +108,7 @@ impl OutboundTransaction { sender_protocol: SenderTransactionProtocol, status: TransactionStatus, message: String, + payment_id: PaymentId, timestamp: NaiveDateTime, direct_send_success: bool, ) -> Self { @@ -118,6 +120,7 @@ impl OutboundTransaction { sender_protocol, status, message, + payment_id, timestamp, cancelled: false, direct_send_success, @@ -226,6 +229,7 @@ impl From for OutboundTransaction { sender_protocol: SenderTransactionProtocol::new_placeholder(), status: ct.status, message: ct.message, + payment_id: ct.payment_id.unwrap_or(PaymentId::Empty), timestamp: ct.timestamp, cancelled: ct.cancelled.is_some(), direct_send_success: false, 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 14cc8ad353e..38ec278a97e 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -1652,6 +1652,8 @@ impl OutboundTransaction { .map_err(|e| TransactionStorageError::BincodeDeserialize(e.to_string()))?, status: TransactionStatus::Pending, message: o.message, + // TODO: Hansie, fix this in the db + payment_id: PaymentId::Empty, timestamp: o.timestamp, cancelled: o.cancelled != 0, direct_send_success: o.direct_send_success != 0, @@ -2309,6 +2311,7 @@ mod test { Default::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap() @@ -2334,6 +2337,7 @@ mod test { sender_protocol: stp.clone(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2353,6 +2357,7 @@ mod test { sender_protocol: stp.clone(), status: TransactionStatus::Pending, message: "Hey!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2728,6 +2733,7 @@ mod test { sender_protocol: SenderTransactionProtocol::new_placeholder(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -2867,6 +2873,7 @@ mod test { sender_protocol: SenderTransactionProtocol::new_placeholder(), status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, diff --git a/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs b/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs index 41fb837de38..a096673de57 100644 --- a/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs +++ b/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs @@ -651,7 +651,11 @@ where self.resources.one_sided_tari_address.clone() } else { match &wo.payment_id { - PaymentId::AddressAndData(address, _) | PaymentId::Address(address) => address.clone(), + PaymentId::AddressAndData { + sender_address: address, + data: _, + } | + PaymentId::Address(address) => address.clone(), _ => TariAddress::default(), } }; 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 8d821462570..a3f852a46e7 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -269,6 +269,7 @@ async fn generate_sender_transaction_message( Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -404,6 +405,8 @@ async fn test_utxo_selection_no_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap_err(); @@ -440,6 +443,8 @@ async fn test_utxo_selection_no_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -534,6 +539,8 @@ async fn test_utxo_selection_with_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap_err(); @@ -616,6 +623,8 @@ async fn test_utxo_selection_with_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -644,6 +653,8 @@ async fn test_utxo_selection_with_chain_metadata() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -739,6 +750,8 @@ async fn test_utxo_selection_with_tx_priority() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -785,6 +798,8 @@ async fn send_not_enough_funds() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await { @@ -852,6 +867,8 @@ async fn send_no_change() { TariScript::default(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -918,6 +935,8 @@ async fn send_not_enough_for_change() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await { @@ -959,6 +978,8 @@ async fn cancel_transaction() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -1064,6 +1085,8 @@ async fn test_get_balance() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -1135,6 +1158,8 @@ async fn sending_transaction_persisted_while_offline() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -1168,6 +1193,8 @@ async fn sending_transaction_persisted_while_offline() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -1479,6 +1506,8 @@ async fn test_txo_validation() { TariScript::default(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index 15fea49f586..9db080d5d1d 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -609,7 +609,8 @@ async fn manage_single_transaction() { UtxoSelectionCriteria::default(), OutputFeatures::default(), MicroMinotari::from(4), - "".to_string() + "".to_string(), + PaymentId::Empty, ) .await .is_err()); @@ -628,6 +629,7 @@ async fn manage_single_transaction() { OutputFeatures::default(), MicroMinotari::from(4), message, + PaymentId::Empty, ) .await .expect("Alice sending tx"); @@ -786,6 +788,7 @@ async fn large_interactive_transaction() { OutputFeatures::default(), MicroMinotari::from(1), message, + PaymentId::Empty, ) .await .expect("Alice sending large tx"); @@ -948,6 +951,7 @@ async fn test_spend_dust_to_self_in_oversized_transaction() { OutputFeatures::default(), fee_per_gram, message.clone(), + PaymentId::Empty, ) .await .is_err()); @@ -1046,6 +1050,7 @@ async fn test_spend_dust_to_other_in_oversized_transaction() { OutputFeatures::default(), fee_per_gram, message.clone(), + PaymentId::Empty, ) .await .unwrap(); @@ -1162,6 +1167,7 @@ async fn test_spend_dust_happy_path() { OutputFeatures::default(), fee_per_gram, message.clone(), + PaymentId::Empty, ) .await .unwrap(); @@ -1207,6 +1213,7 @@ async fn test_spend_dust_happy_path() { OutputFeatures::default(), fee_per_gram, message.clone(), + PaymentId::Empty, ) .await .unwrap(); @@ -1308,6 +1315,7 @@ async fn single_transaction_to_self() { OutputFeatures::default(), 20.into(), message.clone(), + PaymentId::Empty, ) .await .expect("Alice sending tx"); @@ -1477,6 +1485,7 @@ async fn single_transaction_burn_tari() { UtxoSelectionCriteria::default(), 20.into(), message.clone(), + PaymentId::Empty, Some(claim_public_key.clone()), ) .await @@ -2260,6 +2269,7 @@ async fn manage_multiple_transactions() { OutputFeatures::default(), MicroMinotari::from(20), "a to b 1".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2277,6 +2287,7 @@ async fn manage_multiple_transactions() { OutputFeatures::default(), MicroMinotari::from(20), "a to c 1".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2296,6 +2307,7 @@ async fn manage_multiple_transactions() { OutputFeatures::default(), MicroMinotari::from(20), "b to a 1".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2307,6 +2319,7 @@ async fn manage_multiple_transactions() { OutputFeatures::default(), MicroMinotari::from(20), "a to b 2".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2452,6 +2465,7 @@ async fn test_accepting_unknown_tx_id_and_malformed_reply() { OutputFeatures::default(), MicroMinotari::from(20), "".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2556,6 +2570,8 @@ async fn finalize_tx_with_incorrect_pubkey() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -2685,6 +2701,8 @@ async fn finalize_tx_with_missing_output() { script!(Nop).unwrap(), Covenant::default(), MicroMinotari::zero(), + TariAddress::default(), + PaymentId::Empty, ) .await .unwrap(); @@ -2883,6 +2901,7 @@ async fn discovery_async_return_test() { OutputFeatures::default(), MicroMinotari::from(20), "Discovery Tx!".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -2922,6 +2941,7 @@ async fn discovery_async_return_test() { OutputFeatures::default(), MicroMinotari::from(20), "Discovery Tx2!".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -3230,6 +3250,7 @@ async fn test_transaction_cancellation() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -3353,6 +3374,7 @@ async fn test_transaction_cancellation() { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -3439,6 +3461,7 @@ async fn test_transaction_cancellation() { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -3578,6 +3601,7 @@ async fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -3780,6 +3804,7 @@ async fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -3969,6 +3994,7 @@ async fn test_tx_direct_send_behaviour() { OutputFeatures::default(), 100 * uT, "Testing Message1".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4013,6 +4039,7 @@ async fn test_tx_direct_send_behaviour() { OutputFeatures::default(), 100 * uT, "Testing Message2".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4062,6 +4089,7 @@ async fn test_tx_direct_send_behaviour() { OutputFeatures::default(), 100 * uT, "Testing Message3".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4111,6 +4139,7 @@ async fn test_tx_direct_send_behaviour() { OutputFeatures::default(), 100 * uT, "Testing Message4".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4203,6 +4232,7 @@ async fn test_restarting_transaction_protocols() { Covenant::default(), MicroMinotari::zero(), MicroMinotari(2000) - fee - MicroMinotari(10), + TariAddress::default(), ) .await .unwrap() @@ -4278,6 +4308,7 @@ async fn test_restarting_transaction_protocols() { sender_protocol: bob_pre_finalize, status: TransactionStatus::Pending, message: msg.message, + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -4437,6 +4468,7 @@ async fn test_transaction_resending() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -4642,6 +4674,7 @@ async fn test_resend_on_startup() { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -4664,6 +4697,7 @@ async fn test_resend_on_startup() { sender_protocol: stp, status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -4959,6 +4993,7 @@ async fn test_replying_to_cancelled_tx() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -5099,6 +5134,7 @@ async fn test_transaction_timeout_cancellation() { OutputFeatures::default(), 20 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -5171,6 +5207,7 @@ async fn test_transaction_timeout_cancellation() { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -5193,6 +5230,7 @@ async fn test_transaction_timeout_cancellation() { sender_protocol: stp, status: TransactionStatus::Pending, message: "Yo!".to_string(), + payment_id: PaymentId::Empty, timestamp: Utc::now() .naive_utc() .checked_sub_signed(ChronoDuration::seconds(20)) @@ -5396,6 +5434,7 @@ async fn transaction_service_tx_broadcast() { OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), + PaymentId::Empty, ) .await .unwrap(); @@ -5457,6 +5496,7 @@ async fn transaction_service_tx_broadcast() { OutputFeatures::default(), 20 * uT, "Testing Message2".to_string(), + PaymentId::Empty, ) .await .unwrap(); diff --git a/base_layer/wallet/tests/transaction_service_tests/storage.rs b/base_layer/wallet/tests/transaction_service_tests/storage.rs index 41e34016913..ddd55e9511e 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -102,6 +102,7 @@ pub async fn test_db_backend(backend: T) { Covenant::default(), MicroMinotari::zero(), amount, + TariAddress::default(), ) .await .unwrap(); @@ -141,6 +142,7 @@ pub async fn test_db_backend(backend: T) { sender_protocol: stp.clone(), status: TransactionStatus::Pending, message: messages[i].clone(), + payment_id: PaymentId::Empty, timestamp: Utc::now().naive_utc(), cancelled: false, direct_send_success: false, @@ -499,6 +501,7 @@ pub async fn test_db_backend(backend: T) { stp, TransactionStatus::Pending, "To be cancelled".to_string(), + PaymentId::Empty, Utc::now().naive_utc(), false, ), diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index c913d985b3b..a47919c5cbd 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -49,7 +49,7 @@ mod test { }; use tari_core::transactions::{ tari_amount::{uT, MicroMinotari}, - transaction_components::Transaction, + transaction_components::{encrypted_data::PaymentId, Transaction}, ReceiverTransactionProtocol, SenderTransactionProtocol, }; @@ -374,6 +374,7 @@ mod test { stp, TransactionStatus::Pending, "3".to_string(), + PaymentId::Empty, Utc::now().naive_utc(), false, ); diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index b522120bb73..e1de32e03a2 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -7394,23 +7394,24 @@ pub unsafe extern "C" fn wallet_send_transaction( } }; + let payment_id = if payment_id_string.is_null() { + PaymentId::Empty + } else { + match CStr::from_ptr(payment_id_string).to_str() { + Ok(v) => { + let rust_str = v.to_owned(); + let bytes = rust_str.as_bytes().to_vec(); + PaymentId::Open(bytes) + }, + _ => { + error = LibWalletError::from(InterfaceError::NullError("payment_id".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + }, + } + }; + if one_sided { - let payment_id = if payment_id_string.is_null() { - PaymentId::Empty - } else { - match CStr::from_ptr(payment_id_string).to_str() { - Ok(v) => { - let rust_str = v.to_owned(); - let bytes = rust_str.as_bytes().to_vec(); - PaymentId::Open(bytes) - }, - _ => { - error = LibWalletError::from(InterfaceError::NullError("payment_id".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return 0; - }, - } - }; match (*wallet).runtime.block_on( (*wallet) .wallet @@ -7442,6 +7443,7 @@ pub unsafe extern "C" fn wallet_send_transaction( OutputFeatures::default(), MicroMinotari::from(fee_per_gram), message_string, + payment_id, )) { Ok(tx_id) => tx_id.as_u64(), Err(e) => { diff --git a/integration_tests/tests/steps/wallet_cli_steps.rs b/integration_tests/tests/steps/wallet_cli_steps.rs index 6620ffb9e27..50bc0c5220f 100644 --- a/integration_tests/tests/steps/wallet_cli_steps.rs +++ b/integration_tests/tests/steps/wallet_cli_steps.rs @@ -154,6 +154,7 @@ async fn send_from_cli(world: &mut TariWorld, amount: u64, wallet_a: String, wal amount: MicroMinotari(amount), message: format!("Send amount {} from {} to {}", amount, wallet_a, wallet_b), destination: wallet_b_address, + payment_id: "123456".to_string(), }; cli.command2 = Some(CliCommands::SendMinotari(args)); @@ -175,6 +176,7 @@ async fn create_burn_tx_via_cli(world: &mut TariWorld, amount: u64, wallet: Stri let args = BurnMinotariArgs { amount: MicroMinotari(amount), message: format!("Burn, burn amount {} !!!", amount,), + payment_id: "123456".to_string(), }; cli.command2 = Some(CliCommands::BurnMinotari(args)); diff --git a/integration_tests/tests/steps/wallet_steps.rs b/integration_tests/tests/steps/wallet_steps.rs index c7747f39364..375040e5807 100644 --- a/integration_tests/tests/steps/wallet_steps.rs +++ b/integration_tests/tests/steps/wallet_steps.rs @@ -2792,6 +2792,7 @@ async fn burn_transaction(world: &mut TariWorld, amount: u64, wallet: String, fe fee_per_gram: fee, message: "Burning some tari".to_string(), claim_public_key: identity.public_key, + payment_id: "123456".to_string(), }; let result = client.create_burn_transaction(req).await.unwrap();