From 1dc1a5b6ebd5094adc83152506d759ad0cda60c1 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Mon, 3 Jun 2024 18:43:15 +0200 Subject: [PATCH] feat!: add payment id (#6340) Description --- Add payment id as buckets into encrypted data field Motivation and Context --- Allows wallets to send encrypted data via the block chain to other wallets --------- Co-authored-by: stringhandler --- .../minotari_app_grpc/proto/wallet.proto | 1 + .../src/automation/commands.rs | 10 +- .../src/grpc/wallet_grpc_server.rs | 9 +- .../src/ui/components/send_tab.rs | 38 ++- .../src/ui/components/transactions_tab.rs | 26 +- .../src/ui/state/app_state.rs | 14 +- .../src/ui/state/tasks.rs | 6 +- .../src/block_template_protocol.rs | 3 +- applications/minotari_miner/src/run_miner.rs | 2 + .../src/grpc/base_node_grpc_server.rs | 3 + base_layer/core/src/blocks/genesis_block.rs | 14 +- .../core/src/consensus/consensus_constants.rs | 35 ++- base_layer/core/src/test_helpers/mod.rs | 3 +- .../core/src/transactions/coinbase_builder.rs | 92 +++++-- .../src/transactions/key_manager/inner.rs | 10 +- .../src/transactions/key_manager/interface.rs | 4 +- .../src/transactions/key_manager/wrapper.rs | 6 +- .../core/src/transactions/test_helpers.rs | 7 +- .../transaction_components/encrypted_data.rs | 234 +++++++++++++----- .../transaction_components/test.rs | 12 +- .../transaction_output.rs | 5 +- .../unblinded_output.rs | 10 +- .../transaction_components/wallet_output.rs | 12 +- .../wallet_output_builder.rs | 18 +- .../transaction_protocol/recipient.rs | 2 +- .../transaction_protocol/sender.rs | 9 +- .../transaction_protocol/single_receiver.rs | 4 + .../transaction_initializer.rs | 4 +- .../aggregate_body_chain_validator.rs | 9 +- .../aggregate_body_internal_validator.rs | 16 ++ .../core/src/validation/block_body/test.rs | 43 +++- base_layer/core/src/validation/error.rs | 9 + base_layer/core/src/validation/helpers.rs | 23 +- .../core/tests/tests/node_comms_interface.rs | 4 +- base_layer/tari_mining_helper_ffi/src/lib.rs | 3 +- .../2024-05-13-101400_payment_id/down.sql | 1 + .../2024-05-13-101400_payment_id/up.sql | 7 + .../recovery/standard_outputs_recoverer.rs | 28 ++- .../src/output_manager_service/service.rs | 18 +- .../output_manager_service/storage/models.rs | 5 +- .../storage/sqlite_db/output_sql.rs | 25 +- base_layer/wallet/src/schema.rs | 2 + .../wallet/src/transaction_service/handle.rs | 10 + .../protocols/transaction_receive_protocol.rs | 1 + .../protocols/transaction_send_protocol.rs | 1 + .../wallet/src/transaction_service/service.rs | 37 ++- .../transaction_service/storage/database.rs | 8 +- .../src/transaction_service/storage/models.rs | 7 +- .../transaction_service/storage/sqlite_db.rs | 30 ++- .../utxo_scanner_service/utxo_scanner_task.rs | 1 + base_layer/wallet/src/wallet.rs | 17 +- .../output_manager_service_tests/service.rs | 5 +- base_layer/wallet/tests/support/utils.rs | 9 +- .../transaction_service_tests/service.rs | 167 ++++++++++++- .../transaction_service_tests/storage.rs | 8 +- .../transaction_protocols.rs | 1 + base_layer/wallet/tests/utxo_scanner/mod.rs | 4 + .../wallet_ffi/src/callback_handler_tests.rs | 3 + base_layer/wallet_ffi/src/lib.rs | 50 +++- base_layer/wallet_ffi/wallet.h | 2 + integration_tests/src/miner.rs | 3 +- integration_tests/tests/steps/wallet_steps.rs | 11 + 62 files changed, 970 insertions(+), 191 deletions(-) create mode 100644 base_layer/wallet/migrations/2024-05-13-101400_payment_id/down.sql create mode 100644 base_layer/wallet/migrations/2024-05-13-101400_payment_id/up.sql diff --git a/applications/minotari_app_grpc/proto/wallet.proto b/applications/minotari_app_grpc/proto/wallet.proto index c82268545a..25d660c3dc 100644 --- a/applications/minotari_app_grpc/proto/wallet.proto +++ b/applications/minotari_app_grpc/proto/wallet.proto @@ -120,6 +120,7 @@ message PaymentRecipient { ONE_SIDED_TO_STEALTH_ADDRESS = 2; } PaymentType payment_type = 5; + bytes payment_id = 6; } message TransferResponse { diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index a912c612ad..aed03fa4fc 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -64,7 +64,7 @@ use tari_comms::{ use tari_comms_dht::{envelope::NodeDestination, DhtDiscoveryRequester}; use tari_core::transactions::{ tari_amount::{uT, MicroMinotari, Minotari}, - transaction_components::{OutputFeatures, TransactionOutput, WalletOutput}, + transaction_components::{encrypted_data::PaymentId, OutputFeatures, TransactionOutput, WalletOutput}, }; use tari_crypto::ristretto::RistrettoSecretKey; use tari_utilities::{hex::Hex, ByteArray}; @@ -232,6 +232,7 @@ pub async fn send_one_sided( selection_criteria: UtxoSelectionCriteria, dest_address: TariAddress, message: String, + payment_id: PaymentId, ) -> Result { wallet_transaction_service .send_one_sided_transaction( @@ -241,6 +242,7 @@ pub async fn send_one_sided( OutputFeatures::default(), fee_per_gram * uT, message, + payment_id, ) .await .map_err(CommandError::TransactionServiceError) @@ -253,6 +255,7 @@ pub async fn send_one_sided_to_stealth_address( selection_criteria: UtxoSelectionCriteria, dest_address: TariAddress, message: String, + payment_id: PaymentId, ) -> Result { wallet_transaction_service .send_one_sided_to_stealth_address_transaction( @@ -262,6 +265,7 @@ pub async fn send_one_sided_to_stealth_address( OutputFeatures::default(), fee_per_gram * uT, message, + payment_id, ) .await .map_err(CommandError::TransactionServiceError) @@ -452,6 +456,7 @@ pub async fn make_it_rain( UtxoSelectionCriteria::default(), address.clone(), msg.clone(), + PaymentId::Empty, ) .await }, @@ -463,6 +468,7 @@ pub async fn make_it_rain( UtxoSelectionCriteria::default(), address.clone(), msg.clone(), + PaymentId::Empty, ) .await }, @@ -725,6 +731,7 @@ pub async fn command_runner( UtxoSelectionCriteria::default(), args.destination, args.message, + PaymentId::Empty, ) .await { @@ -743,6 +750,7 @@ pub async fn command_runner( UtxoSelectionCriteria::default(), args.destination, args.message, + PaymentId::Empty, ) .await { 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..61c259faf6 100644 --- a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -100,6 +100,7 @@ use tari_core::{ transactions::{ tari_amount::{MicroMinotari, T}, transaction_components::{ + encrypted_data::PaymentId, CodeTemplateRegistration, OutputFeatures, OutputType, @@ -501,13 +502,17 @@ impl wallet_server::Wallet for WalletGrpcServer { dest.fee_per_gram, dest.message, dest.payment_type, + dest.payment_id, )) }) .collect::, _>>() .map_err(Status::invalid_argument)?; let mut transfers = Vec::new(); - for (hex_address, address, amount, fee_per_gram, message, payment_type) in recipients { + for (hex_address, address, amount, fee_per_gram, message, payment_type, payment_id) in recipients { + let payment_id = PaymentId::from_bytes(&payment_id) + .map_err(|_| "Invalid payment id".to_string()) + .map_err(Status::invalid_argument)?; let mut transaction_service = self.get_transaction_service(); transfers.push(async move { ( @@ -532,6 +537,7 @@ impl wallet_server::Wallet for WalletGrpcServer { OutputFeatures::default(), fee_per_gram.into(), message, + payment_id, ) .await } else { @@ -543,6 +549,7 @@ impl wallet_server::Wallet for WalletGrpcServer { OutputFeatures::default(), fee_per_gram.into(), message, + payment_id, ) .await }, 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 884dff315a..b6ad0e34d5 100644 --- a/applications/minotari_console_wallet/src/ui/components/send_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/send_tab.rs @@ -29,6 +29,7 @@ pub struct SendTab { send_input_mode: SendInputMode, show_contacts: bool, to_field: String, + payment_id_field: String, amount_field: String, fee_field: String, message_field: String, @@ -49,6 +50,7 @@ impl SendTab { send_input_mode: SendInputMode::None, show_contacts: false, to_field: String::new(), + payment_id_field: String::new(), amount_field: String::new(), fee_field: app_state.get_default_fee_per_gram().as_u64().to_string(), message_field: String::new(), @@ -104,6 +106,9 @@ impl SendTab { Span::raw(" field, "), Span::styled("C", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to select a contact."), + Span::styled("P", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to edit "), + Span::styled("Payment-id", Style::default().add_modifier(Modifier::BOLD)), ]), Spans::from(vec![ Span::raw("Press "), @@ -167,6 +172,14 @@ impl SendTab { .block(Block::default().borders(Borders::ALL).title("(M)essage:")); f.render_widget(message_input, vert_chunks[3]); + let payment_id_input = Paragraph::new(self.payment_id_field.as_ref()) + .style(match self.send_input_mode { + SendInputMode::PaymentId => Style::default().fg(Color::Magenta), + _ => Style::default(), + }) + .block(Block::default().borders(Borders::ALL).title("(P)ayment-id:")); + f.render_widget(payment_id_input, vert_chunks[4]); + match self.send_input_mode { SendInputMode::None => (), SendInputMode::To => f.set_cursor( @@ -197,6 +210,12 @@ impl SendTab { // Move one line down, from the border to the input line vert_chunks[3].y + 1, ), + SendInputMode::PaymentId => f.set_cursor( + // Put cursor past the end of the input text + vert_chunks[4].x + self.payment_id_field.width() as u16 + 1, + // Move one line down, from the border to the input line + vert_chunks[4].y + 1, + ), } } @@ -275,6 +294,7 @@ impl SendTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), + self.payment_id_field.clone(), tx, )) { Err(e) => { @@ -294,6 +314,7 @@ impl SendTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), + self.payment_id_field.clone(), tx, ), ) { @@ -332,6 +353,7 @@ impl SendTab { self.selected_unique_id = None; 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.send_input_mode = SendInputMode::None; self.send_result_watch = Some(rx); } @@ -386,12 +408,19 @@ impl SendTab { }, }, SendInputMode::Message => match c { - '\n' => self.send_input_mode = SendInputMode::None, + '\n' => self.send_input_mode = SendInputMode::PaymentId, c => { self.message_field.push(c); return KeyHandled::Handled; }, }, + SendInputMode::PaymentId => match c { + '\n' => self.send_input_mode = SendInputMode::None, + c => { + self.payment_id_field.push(c); + return KeyHandled::Handled; + }, + }, } } @@ -424,7 +453,7 @@ impl Component for SendTab { .constraints( [ Constraint::Length(3), - Constraint::Length(14), + Constraint::Length(17), Constraint::Min(42), Constraint::Length(1), ] @@ -579,6 +608,7 @@ impl Component for SendTab { }, 'f' => self.send_input_mode = SendInputMode::Fee, 'm' => self.send_input_mode = SendInputMode::Message, + 'p' => self.send_input_mode = SendInputMode::PaymentId, 's' | 'o' | 'x' => { if self.to_field.is_empty() { self.error_message = @@ -651,6 +681,9 @@ impl Component for SendTab { SendInputMode::Message => { let _ = self.message_field.pop(); }, + SendInputMode::PaymentId => { + let _ = self.payment_id_field.pop(); + }, SendInputMode::None => {}, } } @@ -663,6 +696,7 @@ pub enum SendInputMode { Amount, Message, Fee, + PaymentId, } #[derive(PartialEq, Debug)] 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 2f02f523c9..3e6199184d 100644 --- a/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs @@ -7,6 +7,7 @@ use chrono::{DateTime, Local}; use log::*; use minotari_wallet::transaction_service::storage::models::TxCancellationReason; use tari_common_types::transaction::{TransactionDirection, TransactionStatus}; +use tari_core::transactions::transaction_components::encrypted_data::PaymentId; use tokio::runtime::Handle; use tui::{ backend::Backend, @@ -288,7 +289,7 @@ impl TransactionsTab { .split(area); // Labels - let constraints = [Constraint::Length(1); 14]; + let constraints = [Constraint::Length(1); 15]; let label_layout = Layout::default().constraints(constraints).split(columns[0]); let tx_id = Span::styled("TxID:", Style::default().fg(Color::Magenta)); @@ -305,6 +306,7 @@ impl TransactionsTab { let confirmations = Span::styled("Confirmations:", Style::default().fg(Color::Magenta)); let mined_height = Span::styled("Mined Height:", Style::default().fg(Color::Magenta)); let maturity = Span::styled("Maturity:", Style::default().fg(Color::Magenta)); + let payment_id = Span::styled("Payment Id:", Style::default().fg(Color::Magenta)); let trim = Wrap { trim: true }; let paragraph = Paragraph::new(tx_id).wrap(trim); @@ -335,11 +337,13 @@ impl TransactionsTab { f.render_widget(paragraph, label_layout[12]); let paragraph = Paragraph::new(maturity).wrap(trim); f.render_widget(paragraph, label_layout[13]); + let paragraph = Paragraph::new(payment_id).wrap(trim); + f.render_widget(paragraph, label_layout[14]); // Content let required_confirmations = app_state.get_required_confirmations(); if let Some(tx) = self.detailed_transaction.as_ref() { - let constraints = [Constraint::Length(1); 14]; + let constraints = [Constraint::Length(1); 15]; let content_layout = Layout::default().constraints(constraints).split(columns[1]); let tx_id = Span::styled(format!("{}", tx.tx_id), Style::default().fg(Color::White)); @@ -429,6 +433,20 @@ impl TransactionsTab { }; let maturity = Span::styled(maturity, Style::default().fg(Color::White)); + let payment_id = match tx.payment_id.clone() { + Some(v) => { + if let PaymentId::Open(bytes) = v { + String::from_utf8(bytes) + .unwrap_or_else(|_| "Invalid".to_string()) + .to_string() + } else { + format!("#{}", v) + } + }, + None => "None".to_string(), + }; + let payment_id = Span::styled(payment_id, Style::default().fg(Color::White)); + let paragraph = Paragraph::new(tx_id).wrap(trim); f.render_widget(paragraph, content_layout[0]); let paragraph = Paragraph::new(source_address).wrap(trim); @@ -457,6 +475,8 @@ impl TransactionsTab { f.render_widget(paragraph, content_layout[12]); let paragraph = Paragraph::new(maturity).wrap(trim); f.render_widget(paragraph, content_layout[13]); + let paragraph = Paragraph::new(payment_id).wrap(trim); + f.render_widget(paragraph, content_layout[14]); } } } @@ -469,7 +489,7 @@ impl Component for TransactionsTab { Constraint::Length(3), Constraint::Length(1), Constraint::Min(9), - Constraint::Length(16), + Constraint::Length(17), ] .as_ref(), ) 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..e83f1c75b5 100644 --- a/applications/minotari_console_wallet/src/ui/state/app_state.rs +++ b/applications/minotari_console_wallet/src/ui/state/app_state.rs @@ -57,7 +57,7 @@ use tari_comms::{ use tari_contacts::contacts_service::{handle::ContactsLivenessEvent, types::Contact}; use tari_core::transactions::{ tari_amount::{uT, MicroMinotari}, - transaction_components::{OutputFeatures, TemplateType, TransactionError}, + transaction_components::{encrypted_data::PaymentId, OutputFeatures, TemplateType, TransactionError}, weight::TransactionWeight, }; use tari_shutdown::ShutdownSignal; @@ -337,6 +337,7 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, + payment_id: String, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; @@ -346,6 +347,8 @@ impl AppState { .map_err(|_| UiError::PublicKeyParseError)?, }; let output_features = OutputFeatures { ..Default::default() }; + let payment_id_bytes: Vec = payment_id.as_bytes().to_vec(); + let payment_id = PaymentId::Open(payment_id_bytes); let fee_per_gram = fee_per_gram * uT; let tx_service_handle = inner.wallet.transaction_service.clone(); @@ -356,6 +359,7 @@ impl AppState { output_features, message, fee_per_gram, + payment_id, tx_service_handle, result_tx, )); @@ -370,6 +374,7 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, + payment_id_hex: String, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; @@ -378,6 +383,10 @@ impl AppState { Err(_) => TariAddress::from_bytes(&from_hex(&address).map_err(|_| UiError::PublicKeyParseError)?) .map_err(|_| UiError::PublicKeyParseError)?, }; + let payment_id_u64: u64 = payment_id_hex + .parse::() + .map_err(|_| UiError::HexError("Could not convert payment_id to bytes".to_string()))?; + let payment_id = PaymentId::U64(payment_id_u64); let output_features = OutputFeatures { ..Default::default() }; @@ -390,6 +399,7 @@ impl AppState { output_features, message, fee_per_gram, + payment_id, tx_service_handle, result_tx, )); @@ -1203,6 +1213,7 @@ pub struct CompletedTransactionInfo { pub weight: u64, pub inputs_count: usize, pub outputs_count: usize, + pub payment_id: Option, } impl CompletedTransactionInfo { @@ -1243,6 +1254,7 @@ impl CompletedTransactionInfo { weight, inputs_count, outputs_count, + payment_id: tx.payment_id, }) } } diff --git a/applications/minotari_console_wallet/src/ui/state/tasks.rs b/applications/minotari_console_wallet/src/ui/state/tasks.rs index 7019e8d377..0e0b8d7282 100644 --- a/applications/minotari_console_wallet/src/ui/state/tasks.rs +++ b/applications/minotari_console_wallet/src/ui/state/tasks.rs @@ -39,7 +39,7 @@ use tari_core::{ consensus::{DomainSeparatedConsensusHasher, MaxSizeBytes, MaxSizeString}, transactions::{ tari_amount::MicroMinotari, - transaction_components::{BuildInfo, OutputFeatures, TemplateType}, + transaction_components::{encrypted_data::PaymentId, BuildInfo, OutputFeatures, TemplateType}, }, }; use tari_crypto::{keys::PublicKey as PublicKeyTrait, ristretto::RistrettoPublicKey}; @@ -138,6 +138,7 @@ pub async fn send_one_sided_transaction_task( output_features: OutputFeatures, message: String, fee_per_gram: MicroMinotari, + payment_id: PaymentId, mut transaction_service_handle: TransactionServiceHandle, result_tx: watch::Sender, ) { @@ -151,6 +152,7 @@ pub async fn send_one_sided_transaction_task( output_features, fee_per_gram, message, + payment_id, ) .await { @@ -192,6 +194,7 @@ pub async fn send_one_sided_to_stealth_address_transaction( output_features: OutputFeatures, message: String, fee_per_gram: MicroMinotari, + payment_id: PaymentId, mut transaction_service_handle: TransactionServiceHandle, result_tx: watch::Sender, ) { @@ -205,6 +208,7 @@ pub async fn send_one_sided_to_stealth_address_transaction( output_features, fee_per_gram, message, + payment_id, ) .await { diff --git a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs index 08db7354ac..d3ace5117c 100644 --- a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs +++ b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs @@ -33,7 +33,7 @@ use tari_core::{ transactions::{ generate_coinbase, key_manager::{create_memory_db_key_manager, MemoryDbKeyManager}, - transaction_components::{TransactionKernel, TransactionOutput}, + transaction_components::{encrypted_data::PaymentId, TransactionKernel, TransactionOutput}, }, }; use tari_utilities::{hex::Hex, ByteArray}; @@ -338,6 +338,7 @@ impl BlockTemplateProtocol<'_> { self.config.stealth_payment, self.consensus_manager.consensus_constants(tari_height), self.config.range_proof_type, + PaymentId::Empty, ) .await?; Ok((coinbase_output, coinbase_kernel)) diff --git a/applications/minotari_miner/src/run_miner.rs b/applications/minotari_miner/src/run_miner.rs index e3e41cd4e2..d616634862 100644 --- a/applications/minotari_miner/src/run_miner.rs +++ b/applications/minotari_miner/src/run_miner.rs @@ -48,6 +48,7 @@ use tari_core::{ generate_coinbase, key_manager::{create_memory_db_key_manager, MemoryDbKeyManager}, tari_amount::MicroMinotari, + transaction_components::encrypted_data::PaymentId, }, }; use tari_crypto::ristretto::RistrettoPublicKey; @@ -312,6 +313,7 @@ async fn mining_cycle( config.stealth_payment, consensus_manager.consensus_constants(height), config.range_proof_type, + PaymentId::Empty, ) .await .map_err(|e| MinerError::CoinbaseError(e.to_string()))?; diff --git a/applications/minotari_node/src/grpc/base_node_grpc_server.rs b/applications/minotari_node/src/grpc/base_node_grpc_server.rs index 01a0e6c978..8426941889 100644 --- a/applications/minotari_node/src/grpc/base_node_grpc_server.rs +++ b/applications/minotari_node/src/grpc/base_node_grpc_server.rs @@ -62,6 +62,7 @@ use tari_core::{ TxoStage, }, transaction_components::{ + encrypted_data::PaymentId, KernelBuilder, RangeProofType, Transaction, @@ -855,6 +856,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { coinbase.stealth_payment, self.consensus_rules.consensus_constants(height), range_proof_type, + PaymentId::Empty, ) .await .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e.to_string())))?; @@ -1049,6 +1051,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { coinbase.stealth_payment, self.consensus_rules.consensus_constants(height), range_proof_type, + PaymentId::Empty, ) .await .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e.to_string())))?; diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index aba2019421..e6822e4055 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -107,7 +107,7 @@ pub fn get_stagenet_genesis_block() -> ChainBlock { let mut block = get_stagenet_genesis_block_raw(); // Add faucet utxos - enable/disable as required - let add_faucet_utxos = true; + let add_faucet_utxos = false; if add_faucet_utxos { // NB! Update 'consensus_constants.rs/pub fn igor()/ConsensusConstants {faucet_value: ?}' with total value // NB: `stagenet_genesis_sanity_check` must pass @@ -159,7 +159,7 @@ pub fn get_nextnet_genesis_block() -> ChainBlock { let mut block = get_nextnet_genesis_block_raw(); // Add faucet utxos - enable/disable as required - let add_faucet_utxos = true; + let add_faucet_utxos = false; if add_faucet_utxos { // NB! Update 'consensus_constants.rs/pub fn igor()/ConsensusConstants {faucet_value: ?}' with total value // NB: `nextnet_genesis_sanity_check` must pass @@ -271,7 +271,7 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { let mut block = get_esmeralda_genesis_block_raw(); // Add faucet utxos - enable/disable as required - let add_faucet_utxos = true; + let add_faucet_utxos = false; if add_faucet_utxos { // NB! Update 'consensus_constants.rs/pub fn esmeralda()/ConsensusConstants {faucet_value: ?}' with total value // NB: `esmeralda_genesis_sanity_check` must pass @@ -304,7 +304,7 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { fn get_esmeralda_genesis_block_raw() -> Block { // Set genesis timestamp - let genesis_timestamp = DateTime::parse_from_rfc2822("26 Apr 2024 08:00:00 +0200").expect("parse may not fail"); + let genesis_timestamp = DateTime::parse_from_rfc2822("03 Jun 2024 08:00:00 +0200").expect("parse may not fail"); // Let us add a "not before" proof to the genesis block let not_before_proof = b"as I sip my drink, thoughts of esmeralda consume my mind, like a refreshing nourishing draught \ @@ -421,7 +421,7 @@ mod test { // Note: Generate new data for `pub fn get_esmeralda_genesis_block()` and `fn get_esmeralda_genesis_block_raw()` // if consensus values change, e.g. new faucet or other let block = get_esmeralda_genesis_block(); - check_block(Network::Esmeralda, &block, 100, 1); + check_block(Network::Esmeralda, &block, 0, 0); } #[test] @@ -430,7 +430,7 @@ mod test { // Note: Generate new data for `pub fn get_nextnet_genesis_block()` and `fn get_stagenet_genesis_block_raw()` // if consensus values change, e.g. new faucet or other let block = get_nextnet_genesis_block(); - check_block(Network::NextNet, &block, 100, 1); + check_block(Network::NextNet, &block, 0, 0); } #[test] @@ -440,7 +440,7 @@ mod test { // Note: Generate new data for `pub fn get_stagenet_genesis_block()` and `fn get_stagenet_genesis_block_raw()` // if consensus values change, e.g. new faucet or other let block = get_stagenet_genesis_block(); - check_block(Network::StageNet, &block, 100, 1); + check_block(Network::StageNet, &block, 0, 0); } #[test] diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index 530c9aa9f9..c66c0281d8 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -97,6 +97,8 @@ pub struct ConsensusConstants { transaction_weight: TransactionWeight, /// Maximum byte size of TariScript max_script_byte_size: usize, + /// Maximum byte size of encrypted data + max_extra_encrypted_data_byte_size: usize, /// Range of valid transaction input versions input_version_range: RangeInclusive, /// Range of valid transaction output (and features) versions @@ -155,9 +157,7 @@ pub struct PowAlgorithmConstants { pub target_time: u64, } -const FAUCET_VALUE: u64 = 6_030_157_777_181_012; -const ESMERALDA_FAUCET_VALUE: u64 = FAUCET_VALUE; -// const IGOR_FAUCET_VALUE: u64 = 1_897_859_637_874_722; +const FAUCET_VALUE: u64 = 0; // 6_030_157_777_181_012; const INITIAL_EMISSION: MicroMinotari = MicroMinotari(13_952_877_857); const ESMERALDA_INITIAL_EMISSION: MicroMinotari = INITIAL_EMISSION; @@ -274,6 +274,11 @@ impl ConsensusConstants { self.max_script_byte_size } + /// The maximum serialized byte size of TariScript + pub fn max_extra_encrypted_data_byte_size(&self) -> usize { + self.max_extra_encrypted_data_byte_size + } + /// This is the min initial difficulty that can be requested for the pow pub fn min_pow_difficulty(&self, pow_algo: PowAlgorithm) -> Difficulty { match self.proof_of_work.get(&pow_algo) { @@ -397,6 +402,7 @@ impl ConsensusConstants { faucet_value: 0.into(), transaction_weight: TransactionWeight::latest(), max_script_byte_size: 2048, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -461,6 +467,7 @@ impl ConsensusConstants { faucet_value: 0.into(), // IGOR_FAUCET_VALUE.into(), transaction_weight: TransactionWeight::v1(), max_script_byte_size: 2048, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -515,9 +522,10 @@ impl ConsensusConstants { max_randomx_seed_height: 3000, max_extra_field_size: 200, proof_of_work: algos, - faucet_value: ESMERALDA_FAUCET_VALUE.into(), + faucet_value: FAUCET_VALUE.into(), transaction_weight: TransactionWeight::v1(), max_script_byte_size: 2048, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -574,6 +582,7 @@ impl ConsensusConstants { faucet_value: FAUCET_VALUE.into(), transaction_weight: TransactionWeight::v1(), max_script_byte_size: 2048, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -624,6 +633,7 @@ impl ConsensusConstants { faucet_value: FAUCET_VALUE.into(), transaction_weight: TransactionWeight::v1(), max_script_byte_size: 2048, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -676,6 +686,7 @@ impl ConsensusConstants { faucet_value: MicroMinotari::from(0), transaction_weight: TransactionWeight::v1(), max_script_byte_size: 2048, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -893,7 +904,7 @@ mod test { ConsensusConstants, }, transactions::{ - tari_amount::{uT, MicroMinotari, T}, + tari_amount::{uT, MicroMinotari}, transaction_components::{OutputType, RangeProofType}, }, }; @@ -985,15 +996,15 @@ mod test { let (block_num, reward, supply) = rewards.next().unwrap(); assert_eq!(block_num, 3255553 + coinbase_offset); assert_eq!(reward, 800_000_415 * uT); - assert_eq!(supply, 20_999_999_999_819_869 * uT); + assert_eq!(supply, 14_969_842_222_638_857 * uT); let (_, reward, _) = rewards.next().unwrap(); assert_eq!(reward, 799_999_715 * uT); // Inflating tail emission let mut rewards = schedule.iter().skip(3259845); let (block_num, reward, supply) = rewards.next().unwrap(); assert_eq!(block_num, 3259846); - assert_eq!(reward, 797 * T); - assert_eq!(supply, 21_003_427_156_818_122 * uT); + assert_eq!(reward, 796_998_899.into()); + assert_eq!(supply, 14_973_269_379_635_607 * uT); } #[test] @@ -1027,8 +1038,8 @@ mod test { let mut rewards = schedule.iter().skip(3259845); let (block_num, reward, supply) = rewards.next().unwrap(); assert_eq!(block_num, 3259846); - assert_eq!(reward, 797 * T); - assert_eq!(supply, 21_003_427_156_818_122 * uT); + assert_eq!(reward, 796_998_899.into()); + assert_eq!(supply, 14_973_269_379_635_607 * uT); } #[test] @@ -1062,8 +1073,8 @@ mod test { let mut rewards = schedule.iter().skip(3259845); let (block_num, reward, supply) = rewards.next().unwrap(); assert_eq!(block_num, 3259846); - assert_eq!(reward, 797 * T); - assert_eq!(supply, 21_003_427_156_818_122 * uT); + assert_eq!(reward, 796_998_899.into()); + assert_eq!(supply, 14_973_269_379_635_607 * uT); } #[test] diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index d5c54e5135..401054dfbb 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -48,7 +48,7 @@ use crate::{ generate_coinbase_with_wallet_output, key_manager::{MemoryDbKeyManager, TariKeyId}, tari_amount::MicroMinotari, - transaction_components::{RangeProofType, Transaction, WalletOutput}, + transaction_components::{encrypted_data::PaymentId, RangeProofType, Transaction, WalletOutput}, }, }; @@ -115,6 +115,7 @@ pub async fn create_block( false, rules.consensus_constants(header.height), range_proof_type.unwrap_or(RangeProofType::BulletProofPlus), + PaymentId::Empty, ) .await .unwrap(); diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index ead4800baa..7976565084 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -56,6 +56,7 @@ use crate::{ }, tari_amount::{uT, MicroMinotari}, transaction_components::{ + encrypted_data::PaymentId, KernelBuilder, KernelFeatures, OutputFeatures, @@ -241,10 +242,11 @@ where TKeyManagerInterface: TransactionKeyManagerInterface self, constants: &ConsensusConstants, emission_schedule: &EmissionSchedule, + payment_id: PaymentId, ) -> Result<(Transaction, WalletOutput), CoinbaseBuildError> { let height = self.block_height.ok_or(CoinbaseBuildError::MissingBlockHeight)?; let reward = emission_schedule.block_reward(height); - self.build_with_reward(constants, reward).await + self.build_with_reward(constants, reward, payment_id).await } /// Try and construct a Coinbase Transaction while specifying the block reward. The other parameters (keys, nonces @@ -257,6 +259,7 @@ where TKeyManagerInterface: TransactionKeyManagerInterface self, constants: &ConsensusConstants, block_reward: MicroMinotari, + payment_id: PaymentId, ) -> Result<(Transaction, WalletOutput), CoinbaseBuildError> { // gets tx details let height = self.block_height.ok_or(CoinbaseBuildError::MissingBlockHeight)?; @@ -310,7 +313,12 @@ where TKeyManagerInterface: TransactionKeyManagerInterface OutputFeatures::create_coinbase(height + constants.coinbase_min_maturity(), self.extra, range_proof_type); let encrypted_data = self .key_manager - .encrypt_data_for_recovery(&spending_key_id, Some(&encryption_key_id), total_reward.into()) + .encrypt_data_for_recovery( + &spending_key_id, + Some(&encryption_key_id), + total_reward.into(), + payment_id.clone(), + ) .await?; let minimum_value_promise = match range_proof_type { RangeProofType::BulletProofPlus => MicroMinotari::zero(), @@ -355,6 +363,7 @@ where TKeyManagerInterface: TransactionKeyManagerInterface covenant, encrypted_data, minimum_value_promise, + payment_id, &self.key_manager, ) .await?; @@ -399,6 +408,7 @@ pub async fn generate_coinbase( stealth_payment: bool, consensus_constants: &ConsensusConstants, range_proof_type: RangeProofType, + payment_id: PaymentId, ) -> Result<(TransactionOutput, TransactionKernel), CoinbaseBuildError> { // The script key is not used in the Diffie-Hellmann protocol, so we assign default. let script_key_id = TariKeyId::default(); @@ -413,6 +423,7 @@ pub async fn generate_coinbase( stealth_payment, consensus_constants, range_proof_type, + payment_id, ) .await?; Ok((coinbase_output, coinbase_kernel)) @@ -431,6 +442,7 @@ pub async fn generate_coinbase_with_wallet_output( stealth_payment: bool, consensus_constants: &ConsensusConstants, range_proof_type: RangeProofType, + payment_id: PaymentId, ) -> Result<(Transaction, TransactionOutput, TransactionKernel, WalletOutput), CoinbaseBuildError> { let (sender_offset_key_id, _) = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) @@ -464,7 +476,7 @@ pub async fn generate_coinbase_with_wallet_output( .with_script(script) .with_extra(extra.to_vec()) .with_range_proof_type(range_proof_type) - .build_with_reward(consensus_constants, reward) + .build_with_reward(consensus_constants, reward, payment_id) .await?; let output = transaction @@ -520,7 +532,11 @@ mod test { assert_eq!( builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty + ) .await .unwrap_err(), CoinbaseBuildError::MissingBlockHeight @@ -533,7 +549,11 @@ mod test { let builder = builder.with_block_height(42); assert_eq!( builder - .build(rules.consensus_constants(42), rules.emission_schedule(),) + .build( + rules.consensus_constants(42), + rules.emission_schedule(), + PaymentId::Empty + ) .await .unwrap_err(), CoinbaseBuildError::MissingFees @@ -548,7 +568,11 @@ mod test { let builder = builder.with_block_height(42).with_fees(fees); assert_eq!( builder - .build(rules.consensus_constants(42), rules.emission_schedule(),) + .build( + rules.consensus_constants(42), + rules.emission_schedule(), + PaymentId::Empty + ) .await .unwrap_err(), CoinbaseBuildError::MissingSpendKey @@ -570,7 +594,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx, _unblinded_output) = builder - .build(rules.consensus_constants(42), rules.emission_schedule()) + .build( + rules.consensus_constants(42), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let utxo = &tx.body.outputs()[0]; @@ -621,7 +649,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (mut tx, _) = builder - .build(rules.consensus_constants(42), rules.emission_schedule()) + .build( + rules.consensus_constants(42), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let mut outputs = tx.body.outputs().clone(); @@ -656,7 +688,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (mut tx, _) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let block_reward = rules.emission_schedule().block_reward(42) + missing_fee; @@ -671,7 +707,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (tx2, _) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let mut coinbase2 = tx2.body.outputs()[0].clone(); @@ -703,7 +743,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (tx3, _) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); assert!(tx3 @@ -729,7 +773,7 @@ mod test { TransactionKeyManagerInterface, TxoStage, }, - transaction_components::{KernelBuilder, RangeProofType, TransactionKernelVersion}, + transaction_components::{encrypted_data::PaymentId, KernelBuilder, RangeProofType, TransactionKernelVersion}, }; #[tokio::test] @@ -753,7 +797,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (mut tx, _) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); @@ -770,7 +818,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx2, output) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let mut tx_kernel_test = tx.clone(); @@ -885,7 +937,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx1, wo1) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); @@ -902,7 +958,11 @@ mod test { .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx2, wo2) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); diff --git a/base_layer/core/src/transactions/key_manager/inner.rs b/base_layer/core/src/transactions/key_manager/inner.rs index f7cdcd3400..496d56aee7 100644 --- a/base_layer/core/src/transactions/key_manager/inner.rs +++ b/base_layer/core/src/transactions/key_manager/inner.rs @@ -77,6 +77,7 @@ use crate::{ }, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, KernelFeatures, RangeProofType, @@ -1188,6 +1189,7 @@ where TBackend: KeyManagerBackend + 'static spend_key_id: &TariKeyId, custom_recovery_key_id: Option<&TariKeyId>, value: u64, + payment_id: PaymentId, ) -> Result { let recovery_key = if let Some(key_id) = custom_recovery_key_id { self.get_private_key(key_id).await? @@ -1197,7 +1199,7 @@ where TBackend: KeyManagerBackend + 'static let value_key = value.into(); let commitment = self.get_commitment(spend_key_id, &value_key).await?; let spend_key = self.get_private_key(spend_key_id).await?; - let data = EncryptedData::encrypt_data(&recovery_key, &commitment, value.into(), &spend_key)?; + let data = EncryptedData::encrypt_data(&recovery_key, &commitment, value.into(), &spend_key, payment_id)?; Ok(data) } @@ -1205,13 +1207,13 @@ where TBackend: KeyManagerBackend + 'static &self, output: &TransactionOutput, custom_recovery_key_id: Option<&TariKeyId>, - ) -> Result<(TariKeyId, MicroMinotari), TransactionError> { + ) -> Result<(TariKeyId, MicroMinotari, PaymentId), TransactionError> { 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? }; - let (value, private_key) = + let (value, private_key, payment_id) = EncryptedData::decrypt_data(&recovery_key, output.commitment(), output.encrypted_data())?; self.crypto_factories .range_proof @@ -1233,6 +1235,6 @@ where TBackend: KeyManagerBackend + 'static KeyId::Imported { key: public_key } }, }; - Ok((key, value)) + Ok((key, value, payment_id)) } } diff --git a/base_layer/core/src/transactions/key_manager/interface.rs b/base_layer/core/src/transactions/key_manager/interface.rs index e3eae15ecb..cc2624728c 100644 --- a/base_layer/core/src/transactions/key_manager/interface.rs +++ b/base_layer/core/src/transactions/key_manager/interface.rs @@ -33,6 +33,7 @@ use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface, KeyManag use crate::transactions::{ tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, KernelFeatures, RangeProofType, @@ -216,13 +217,14 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { spend_key_id: &TariKeyId, custom_recovery_key_id: Option<&TariKeyId>, value: u64, + payment_id: PaymentId, ) -> Result; async fn try_output_key_recovery( &self, output: &TransactionOutput, custom_recovery_key_id: Option<&TariKeyId>, - ) -> Result<(TariKeyId, MicroMinotari), TransactionError>; + ) -> Result<(TariKeyId, MicroMinotari, PaymentId), TransactionError>; async fn get_script_offset( &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..4ed4958763 100644 --- a/base_layer/core/src/transactions/key_manager/wrapper.rs +++ b/base_layer/core/src/transactions/key_manager/wrapper.rs @@ -51,6 +51,7 @@ use crate::transactions::{ }, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, KernelFeatures, RangeProofType, @@ -351,11 +352,12 @@ where TBackend: KeyManagerBackend + 'static spend_key_id: &TariKeyId, custom_recovery_key_id: Option<&TariKeyId>, value: u64, + payment_id: PaymentId, ) -> Result { self.transaction_key_manager_inner .read() .await - .encrypt_data_for_recovery(spend_key_id, custom_recovery_key_id, value) + .encrypt_data_for_recovery(spend_key_id, custom_recovery_key_id, value, payment_id) .await } @@ -363,7 +365,7 @@ where TBackend: KeyManagerBackend + 'static &self, output: &TransactionOutput, custom_recovery_key_id: Option<&TariKeyId>, - ) -> Result<(TariKeyId, MicroMinotari), TransactionError> { + ) -> Result<(TariKeyId, MicroMinotari, PaymentId), TransactionError> { self.transaction_key_manager_inner .read() .await diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index bd8c9e8d16..ed1665b446 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -50,6 +50,7 @@ use crate::{ }, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, KernelBuilder, KernelFeatures, OutputFeatures, @@ -169,7 +170,7 @@ impl TestParams { let output = WalletOutputBuilder::new(params.value, self.spend_key_id.clone()) .with_features(params.features) .with_script(params.script.clone()) - .encrypt_data_for_recovery(key_manager, None) + .encrypt_data_for_recovery(key_manager, None, PaymentId::Empty) .await .unwrap() .with_input_data(input_data) @@ -747,7 +748,7 @@ pub async fn create_stx_protocol_internal( let output = WalletOutputBuilder::new(val, spending_key) .with_features(schema.features.clone()) .with_script(schema.script.clone()) - .encrypt_data_for_recovery(key_manager, None) + .encrypt_data_for_recovery(key_manager, None, PaymentId::Empty) .await .unwrap() .with_input_data(input_data) @@ -852,7 +853,7 @@ pub async fn create_utxo( .await .unwrap(); let encrypted_data = key_manager - .encrypt_data_for_recovery(&spending_key_id, None, value.into()) + .encrypt_data_for_recovery(&spending_key_id, None, value.into(), PaymentId::Empty) .await .unwrap(); let (sender_offset_key_id, sender_offset_public_key) = key_manager 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 f2cd2e52d4..bacdce8b4f 100644 --- a/base_layer/core/src/transactions/transaction_components/encrypted_data.rs +++ b/base_layer/core/src/transactions/transaction_components/encrypted_data.rs @@ -25,7 +25,12 @@ //! Encrypted data using the extended-nonce variant XChaCha20-Poly1305 encryption with secure random nonce. -use std::mem::size_of; +use std::{ + convert::TryInto, + fmt, + fmt::{Display, Formatter}, + mem::size_of, +}; use blake2::Blake2b; use borsh::{BorshDeserialize, BorshSerialize}; @@ -37,8 +42,11 @@ use chacha20poly1305::{ XNonce, }; use digest::{consts::U32, generic_array::GenericArray, FixedOutput}; +use primitive_types::U256; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{Commitment, PrivateKey}; +use tari_common_types::{ + types::{Commitment, PrivateKey}, +}; use tari_crypto::{hashing::DomainSeparatedHasher, keys::SecretKey}; use tari_hashing::TransactionSecureNonceKdfDomain; use tari_utilities::{ @@ -58,17 +66,74 @@ 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_TOTAL: usize = SIZE_NONCE + SIZE_VALUE + SIZE_MASK + SIZE_TAG; +pub const STATIC_ENCRYPTED_DATA_SIZE_TOTAL: usize = SIZE_NONCE + SIZE_VALUE + SIZE_MASK + SIZE_TAG; // Number of hex characters of encrypted data to display on each side of ellipsis when truncating const DISPLAY_CUTOFF: usize = 16; -#[derive( - Debug, Copy, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, Zeroize, -)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, Zeroize)] pub struct EncryptedData { #[serde(with = "tari_utilities::serde::hex")] - data: [u8; SIZE_TOTAL], // nonce, encrypted value, encrypted mask, tag + data: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub enum PaymentId { + Empty, + U64(u64), + U256(U256), + Open(Vec), +} + +impl PaymentId { + pub fn get_size(&self) -> usize { + match self { + PaymentId::Empty => 0, + PaymentId::U64(_) => size_of::(), + PaymentId::U256(_) => size_of::(), + PaymentId::Open(v) => v.len(), + } + } + + pub fn as_bytes(&self) -> Vec { + match self { + PaymentId::Empty => Vec::new(), + PaymentId::U64(v) => (*v).to_le_bytes().to_vec(), + PaymentId::U256(v) => { + let mut bytes = vec![0; 32]; + v.to_little_endian(&mut bytes); + bytes + }, + PaymentId::Open(v) => v.clone(), + } + } + + 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"); + let v = u64::from_le_bytes(bytes); + Ok(PaymentId::U64(v)) + }, + 32 => { + let v = U256::from_little_endian(bytes); + Ok(PaymentId::U256(v)) + }, + _ => Ok(PaymentId::Open(bytes.to_vec())), + } + } +} + +impl Display for PaymentId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + PaymentId::Empty => write!(f, "N/A"), + PaymentId::U64(v) => write!(f, "{}", v), + PaymentId::U256(v) => write!(f, "{}", v), + PaymentId::Open(v) => write!(f, "byte vector of len: {}", v.len()), + } + } } /// AEAD associated data @@ -85,11 +150,13 @@ impl EncryptedData { commitment: &Commitment, value: MicroMinotari, mask: &PrivateKey, + payment_id: PaymentId, ) -> Result { // Encode the value and mask - let mut bytes = Zeroizing::new([0u8; SIZE_VALUE + SIZE_MASK]); + let mut bytes = Zeroizing::new(vec![0; SIZE_VALUE + SIZE_MASK + payment_id.get_size()]); bytes[..SIZE_VALUE].clone_from_slice(value.as_u64().to_le_bytes().as_ref()); - bytes[SIZE_VALUE..].clone_from_slice(mask.as_bytes()); + bytes[SIZE_VALUE..SIZE_VALUE + SIZE_MASK].clone_from_slice(mask.as_bytes()); + bytes[SIZE_VALUE + SIZE_MASK..].clone_from_slice(&payment_id.as_bytes()); // Produce a secure random nonce let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng); @@ -102,10 +169,11 @@ impl EncryptedData { let tag = cipher.encrypt_in_place_detached(&nonce, ENCRYPTED_DATA_AAD, bytes.as_mut_slice())?; // Put everything together: nonce, ciphertext, tag - let mut data = [0u8; SIZE_TOTAL]; - data[..SIZE_NONCE].clone_from_slice(&nonce); - data[SIZE_NONCE..SIZE_NONCE + SIZE_VALUE + SIZE_MASK].clone_from_slice(bytes.as_slice()); - data[SIZE_NONCE + SIZE_VALUE + SIZE_MASK..].clone_from_slice(&tag); + let mut data = vec![0; STATIC_ENCRYPTED_DATA_SIZE_TOTAL + payment_id.get_size()]; + data[..SIZE_TAG].clone_from_slice(&tag); + data[SIZE_TAG..SIZE_TAG + SIZE_NONCE].clone_from_slice(&nonce); + data[SIZE_TAG + SIZE_NONCE..SIZE_TAG + SIZE_NONCE + SIZE_VALUE + SIZE_MASK + payment_id.get_size()] + .clone_from_slice(bytes.as_slice()); Ok(Self { data }) } @@ -117,12 +185,19 @@ impl EncryptedData { encryption_key: &PrivateKey, commitment: &Commitment, encrypted_data: &EncryptedData, - ) -> Result<(MicroMinotari, PrivateKey), EncryptedDataError> { + ) -> Result<(MicroMinotari, PrivateKey, PaymentId), EncryptedDataError> { // Extract the nonce, ciphertext, and tag - let nonce = XNonce::from_slice(&encrypted_data.as_bytes()[..SIZE_NONCE]); - let mut bytes = Zeroizing::new([0u8; SIZE_VALUE + SIZE_MASK]); - bytes.clone_from_slice(&encrypted_data.as_bytes()[SIZE_NONCE..SIZE_NONCE + SIZE_VALUE + SIZE_MASK]); - let tag = Tag::from_slice(&encrypted_data.as_bytes()[SIZE_NONCE + SIZE_VALUE + SIZE_MASK..]); + let tag = Tag::from_slice(&encrypted_data.as_bytes()[..SIZE_TAG]); + let nonce = XNonce::from_slice(&encrypted_data.as_bytes()[SIZE_TAG..SIZE_TAG + SIZE_NONCE]); + let mut bytes = Zeroizing::new(vec![ + 0; + encrypted_data + .data + .len() + .saturating_sub(SIZE_TAG) + .saturating_sub(SIZE_NONCE) + ]); + bytes.clone_from_slice(&encrypted_data.as_bytes()[SIZE_TAG + SIZE_NONCE..]); // Set up the AEAD let aead_key = kdf_aead(encryption_key, commitment); @@ -136,32 +211,33 @@ impl EncryptedData { value_bytes.clone_from_slice(&bytes[0..SIZE_VALUE]); Ok(( u64::from_le_bytes(value_bytes).into(), - PrivateKey::from_canonical_bytes(&bytes[SIZE_VALUE..])?, + PrivateKey::from_canonical_bytes(&bytes[SIZE_VALUE..SIZE_VALUE + SIZE_MASK])?, + PaymentId::from_bytes(&bytes[SIZE_VALUE + SIZE_MASK..])?, )) } /// Parse encrypted data from a byte slice pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != SIZE_TOTAL { + if bytes.len() < STATIC_ENCRYPTED_DATA_SIZE_TOTAL { return Err(EncryptedDataError::IncorrectLength(format!( - "Expected {} bytes, got {}", - SIZE_TOTAL, + "Expected bytes to be at least {}, got {}", + STATIC_ENCRYPTED_DATA_SIZE_TOTAL, bytes.len() ))); } - let mut data = [0u8; SIZE_TOTAL]; + let mut data = vec![0; bytes.len()]; data.copy_from_slice(bytes); Ok(Self { data }) } - /// Get a byte vector with the encrypted data contents - pub fn to_byte_vec(&self) -> Vec { - self.data.to_vec() + #[cfg(test)] + pub fn from_vec_unsafe(data: Vec) -> Self { + Self { data } } - /// Get a byte array with the encrypted data contents - pub fn to_bytes(&self) -> [u8; SIZE_TOTAL] { - self.data + /// Get a byte vector with the encrypted data contents + pub fn to_byte_vec(&self) -> Vec { + self.data.clone() } /// Get a byte slice with the encrypted data contents @@ -186,6 +262,12 @@ impl EncryptedData { } } } + + /// Returns the size of the payment id + pub fn get_payment_id_size(&self) -> usize { + // the length should always at least be the static total size, the extra len is the payment id + self.data.len().saturating_sub(STATIC_ENCRYPTED_DATA_SIZE_TOTAL) + } } impl Hex for EncryptedData { @@ -202,7 +284,7 @@ impl Hex for EncryptedData { impl Default for EncryptedData { fn default() -> Self { Self { - data: [0u8; SIZE_TOTAL], + data: Vec::with_capacity(STATIC_ENCRYPTED_DATA_SIZE_TOTAL), } } } @@ -250,46 +332,74 @@ mod test { #[test] fn it_encrypts_and_decrypts_correctly() { - for (value, mask) in [ - (0, PrivateKey::default()), - (0, PrivateKey::random(&mut OsRng)), - (123456, PrivateKey::default()), - (654321, PrivateKey::random(&mut OsRng)), - (u64::MAX, PrivateKey::random(&mut OsRng)), + for payment_id in [ + PaymentId::Empty, + PaymentId::U64(1), + PaymentId::U64(156486946518564), + PaymentId::U256( + U256::from_dec_str("465465489789785458694894263185648978947864164681631").expect("Should not fail"), + ), + PaymentId::Address(DualAddress::from_hex("2603bc3d05fb55446f18031feb5494d19d6c795fc93d6218c65a285c7a88fd03917c72e4a70cbabcc52ad79cb2ac170df4a29912ffb345f20b0f8ae5524c749b9425f0").unwrap()), + PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + PaymentId::Open(vec![1;256]), ] { - 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).unwrap(); - let (decrypted_value, decrypted_mask) = - EncryptedData::decrypt_data(&encryption_key, &commitment, &encrypted_data).unwrap(); - assert_eq!(amount, decrypted_value); - assert_eq!(mask, decrypted_mask); - if let Ok((decrypted_value, decrypted_mask)) = - EncryptedData::decrypt_data(&PrivateKey::random(&mut OsRng), &commitment, &encrypted_data) - { - assert_ne!(amount, decrypted_value); - assert_ne!(mask, decrypted_mask); + for (value, mask) in [ + (0, PrivateKey::default()), + (0, PrivateKey::random(&mut OsRng)), + (123456, PrivateKey::default()), + (654321, PrivateKey::random(&mut OsRng)), + (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(); + assert_eq!(amount, decrypted_value); + assert_eq!(mask, decrypted_mask); + assert_eq!(payment_id, decrypted_payment_id); + if let Ok((decrypted_value, decrypted_mask, decrypted_payment_id)) = + EncryptedData::decrypt_data(&PrivateKey::random(&mut OsRng), &commitment, &encrypted_data) + { + assert_ne!(amount, decrypted_value); + assert_ne!(mask, decrypted_mask); + assert_ne!(payment_id, decrypted_payment_id); + } } } } #[test] fn it_converts_correctly() { - for (value, mask) in [ - (0, PrivateKey::default()), - (0, PrivateKey::random(&mut OsRng)), - (123456, PrivateKey::default()), - (654321, PrivateKey::random(&mut OsRng)), - (u64::MAX, PrivateKey::random(&mut OsRng)), + for payment_id in [ + PaymentId::Empty, + PaymentId::U64(1), + PaymentId::U64(156486946518564), + PaymentId::U256( + U256::from_dec_str("465465489789785458694894263185648978947864164681631").expect("Should not fail"), + ), + PaymentId::Address(DualAddress::from_hex("2603bc3d05fb55446f18031feb5494d19d6c795fc93d6218c65a285c7a88fd03917c72e4a70cbabcc52ad79cb2ac170df4a29912ffb345f20b0f8ae5524c749b9425f0").unwrap()), + PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + PaymentId::Open(vec![1;256]), ] { - 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).unwrap(); - let bytes = encrypted_data.to_byte_vec(); - let encrypted_data_from_bytes = EncryptedData::from_bytes(&bytes).unwrap(); - assert_eq!(encrypted_data, encrypted_data_from_bytes); + for (value, mask) in [ + (0, PrivateKey::default()), + (0, PrivateKey::random(&mut OsRng)), + (123456, PrivateKey::default()), + (654321, PrivateKey::random(&mut OsRng)), + (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 bytes = encrypted_data.to_byte_vec(); + let encrypted_data_from_bytes = EncryptedData::from_bytes(&bytes).unwrap(); + assert_eq!(encrypted_data, encrypted_data_from_bytes); + } } } } diff --git a/base_layer/core/src/transactions/transaction_components/test.rs b/base_layer/core/src/transactions/transaction_components/test.rs index 3777128d7f..48edf69cfd 100644 --- a/base_layer/core/src/transactions/transaction_components/test.rs +++ b/base_layer/core/src/transactions/transaction_components/test.rs @@ -45,7 +45,11 @@ use crate::{ tari_amount::{uT, T}, test_helpers, test_helpers::{TestParams, UtxoTestParams}, - transaction_components::{transaction_output::batch_verify_range_proofs, OutputFeatures}, + transaction_components::{ + encrypted_data::PaymentId, + transaction_output::batch_verify_range_proofs, + OutputFeatures, + }, transaction_protocol::TransactionProtocolError, CryptoFactories, }, @@ -97,7 +101,7 @@ async fn key_manager_input() { .expect("Should be able to create transaction output"); assert_eq!(*input.features().unwrap(), OutputFeatures::default()); - let (_, value) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); + let (_, value, _) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); assert_eq!(value, i.value); } @@ -126,7 +130,7 @@ async fn range_proof_verification() { let wallet_output2 = WalletOutputBuilder::new((2u64.pow(32) + 1u64).into(), test_params_2.spend_key_id.clone()) .with_features(OutputFeatures::default()) .with_script(script![Nop]) - .encrypt_data_for_recovery(&key_manager, None) + .encrypt_data_for_recovery(&key_manager, None, PaymentId::Empty) .await .unwrap() .with_input_data(input_data) @@ -560,7 +564,7 @@ async fn test_output_recover_openings() { .unwrap(); let output = wallet_output.to_transaction_output(&key_manager).await.unwrap(); - let (mask, value) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); + let (mask, value, _) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); assert_eq!(value, wallet_output.value); assert_eq!(mask, test_params.spend_key_id); } diff --git a/base_layer/core/src/transactions/transaction_components/transaction_output.rs b/base_layer/core/src/transactions/transaction_components/transaction_output.rs index 03cec2ab6b..74fe70228c 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -473,7 +473,8 @@ impl TransactionOutput { pub fn get_features_and_scripts_size(&self) -> std::io::Result { Ok(self.features.get_serialized_size()? + self.script.get_serialized_size()? + - self.covenant.get_serialized_size()?) + self.covenant.get_serialized_size()? + + self.encrypted_data.get_payment_id_size()) } } @@ -600,7 +601,7 @@ mod test { assert!(tx_output.verify_range_proof(&factories.range_proof).is_ok()); assert!(tx_output.verify_metadata_signature().is_ok()); - let (_, recovered_value) = key_manager.try_output_key_recovery(&tx_output, None).await.unwrap(); + let (_, recovered_value, _) = key_manager.try_output_key_recovery(&tx_output, None).await.unwrap(); assert_eq!(recovered_value, value); } diff --git a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs index b3c2b0650c..7dfa148411 100644 --- a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs +++ b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs @@ -38,7 +38,13 @@ use crate::{ transactions::{ key_manager::{SecretTransactionKeyManagerInterface, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, - transaction_components::{EncryptedData, OutputFeatures, TransactionError, WalletOutput}, + transaction_components::{ + encrypted_data::PaymentId, + EncryptedData, + OutputFeatures, + TransactionError, + WalletOutput, + }, }, }; @@ -132,6 +138,7 @@ impl UnblindedOutput { pub async fn to_wallet_output( self, key_manager: &KM, + payment_id: PaymentId, ) -> Result { let spending_key_id = key_manager.import_key(self.spending_key).await?; let script_key_id = key_manager.import_key(self.script_private_key).await?; @@ -149,6 +156,7 @@ impl UnblindedOutput { self.covenant, self.encrypted_data, self.minimum_value_promise, + payment_id, key_manager, ) .await?; diff --git a/base_layer/core/src/transactions/transaction_components/wallet_output.rs b/base_layer/core/src/transactions/transaction_components/wallet_output.rs index 955e4178ae..b645f80344 100644 --- a/base_layer/core/src/transactions/transaction_components/wallet_output.rs +++ b/base_layer/core/src/transactions/transaction_components/wallet_output.rs @@ -41,6 +41,7 @@ use crate::{ tari_amount::MicroMinotari, transaction_components, transaction_components::{ + encrypted_data::PaymentId, transaction_input::{SpentOutput, TransactionInput}, transaction_output::TransactionOutput, EncryptedData, @@ -71,6 +72,7 @@ pub struct WalletOutput { pub encrypted_data: EncryptedData, pub minimum_value_promise: MicroMinotari, pub rangeproof: Option, + pub payment_id: PaymentId, } impl WalletOutput { @@ -91,6 +93,7 @@ impl WalletOutput { covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + payment_id: PaymentId, key_manager: &KM, ) -> Result { let rangeproof = if features.range_proof_type == RangeProofType::BulletProofPlus { @@ -116,6 +119,7 @@ impl WalletOutput { covenant, encrypted_data, minimum_value_promise, + payment_id, rangeproof, }) } @@ -136,6 +140,7 @@ impl WalletOutput { encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, rangeproof: Option, + payment_id: PaymentId, ) -> Self { Self { version, @@ -152,6 +157,7 @@ impl WalletOutput { encrypted_data, minimum_value_promise, rangeproof, + payment_id, } } @@ -169,6 +175,7 @@ impl WalletOutput { covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + payment_id: PaymentId, key_manager: &KM, ) -> Result { Self::new( @@ -185,6 +192,7 @@ impl WalletOutput { covenant, encrypted_data, minimum_value_promise, + payment_id, key_manager, ) .await @@ -221,7 +229,7 @@ impl WalletOutput { sender_offset_public_key: self.sender_offset_public_key.clone(), covenant: self.covenant.clone(), version: self.version, - encrypted_data: self.encrypted_data, + encrypted_data: self.encrypted_data.clone(), metadata_signature: self.metadata_signature.clone(), rangeproof_hash, minimum_value_promise: self.minimum_value_promise, @@ -268,7 +276,7 @@ impl WalletOutput { self.sender_offset_public_key.clone(), self.metadata_signature.clone(), self.covenant.clone(), - self.encrypted_data, + self.encrypted_data.clone(), self.minimum_value_promise, ); diff --git a/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs b/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs index 2eaf9ebb9a..3d90bd1694 100644 --- a/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs +++ b/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs @@ -30,6 +30,7 @@ use crate::{ key_manager::{TariKeyId, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, OutputFeatures, TransactionError, @@ -58,6 +59,7 @@ pub struct WalletOutputBuilder { encrypted_data: EncryptedData, custom_recovery_key_id: Option, minimum_value_promise: MicroMinotari, + payment_id: PaymentId, } #[allow(dead_code)] @@ -79,6 +81,7 @@ impl WalletOutputBuilder { encrypted_data: EncryptedData::default(), custom_recovery_key_id: None, minimum_value_promise: MicroMinotari::zero(), + payment_id: PaymentId::Empty, } } @@ -111,9 +114,15 @@ impl WalletOutputBuilder { mut self, key_manager: &KM, custom_recovery_key_id: Option<&TariKeyId>, + payment_id: PaymentId, ) -> Result { self.encrypted_data = key_manager - .encrypt_data_for_recovery(&self.spending_key_id, custom_recovery_key_id, self.value.as_u64()) + .encrypt_data_for_recovery( + &self.spending_key_id, + custom_recovery_key_id, + self.value.as_u64(), + payment_id, + ) .await?; Ok(self) } @@ -217,6 +226,7 @@ impl WalletOutputBuilder { self.covenant, self.encrypted_data, self.minimum_value_promise, + self.payment_id, key_manager, ) .await?; @@ -249,7 +259,7 @@ mod test { let kmob = kmob.with_script_key(script_key_id); let kmob = kmob.with_features(OutputFeatures::default()); let kmob = kmob - .encrypt_data_for_recovery(&key_manager, None) + .encrypt_data_for_recovery(&key_manager, None, PaymentId::Empty) .await .unwrap() .sign_as_sender_and_receiver(&key_manager, &sender_offset_private_key_id) @@ -264,7 +274,7 @@ mod test { .await .unwrap()); - let (recovered_key_id, recovered_value) = + let (recovered_key_id, recovered_value, _) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); assert_eq!(recovered_key_id, spending_key_id); assert_eq!(recovered_value, value); @@ -289,7 +299,7 @@ mod test { let kmob = kmob.with_script_key(script_key_id); let kmob = kmob.with_features(OutputFeatures::default()); let kmob = kmob - .encrypt_data_for_recovery(&key_manager, None) + .encrypt_data_for_recovery(&key_manager, None, PaymentId::Empty) .await .unwrap() .sign_as_sender_and_receiver(&key_manager, &sender_offset_private_key_id) diff --git a/base_layer/core/src/transactions/transaction_protocol/recipient.rs b/base_layer/core/src/transactions/transaction_protocol/recipient.rs index 115e5edba8..1f20080dd6 100644 --- a/base_layer/core/src/transactions/transaction_protocol/recipient.rs +++ b/base_layer/core/src/transactions/transaction_protocol/recipient.rs @@ -310,7 +310,7 @@ mod test { .unwrap(); assert_eq!(data.partial_signature, kernel_signature); - let (mask, value) = key_manager.try_output_key_recovery(&data.output, None).await.unwrap(); + let (mask, value, _) = key_manager.try_output_key_recovery(&data.output, None).await.unwrap(); assert_eq!(output.spending_key_id, mask); assert_eq!(output.value, value); } diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index 01a15de043..99be0d75d7 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -883,6 +883,7 @@ mod test { tari_amount::*, test_helpers::{create_test_input, create_wallet_output_with_data, TestParams}, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, OutputFeatures, TransactionOutput, @@ -984,7 +985,7 @@ mod test { // Encrypted value let encrypted_data = key_manager - .encrypt_data_for_recovery(&spending_key_id, None, value) + .encrypt_data_for_recovery(&spending_key_id, None, value, PaymentId::Empty) .await .unwrap(); @@ -1154,6 +1155,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -1276,6 +1278,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -1386,6 +1389,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -1572,6 +1576,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager_bob, ) .await @@ -1601,7 +1606,7 @@ mod test { let output = tx.body.outputs().iter().find(|o| o.script.size() > 1).unwrap(); - let (key, _value) = key_manager_alice.try_output_key_recovery(output, None).await.unwrap(); + let (key, _value, _) = key_manager_alice.try_output_key_recovery(output, None).await.unwrap(); assert_eq!(key, change_params.spend_key_id); } } diff --git a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs index 8fb6feee3f..e0117d2531 100644 --- a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs +++ b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs @@ -153,6 +153,7 @@ mod test { tari_amount::*, test_helpers::TestParams, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, OutputFeatures, TransactionKernel, @@ -189,6 +190,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -229,6 +231,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -305,6 +308,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .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..e32f470806 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -40,6 +40,7 @@ use crate::{ key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface}, tari_amount::*, transaction_components::{ + encrypted_data::PaymentId, OutputFeatures, TransactionOutput, TransactionOutputVersion, @@ -384,7 +385,7 @@ where KM: TransactionKeyManagerInterface let encrypted_data = self .key_manager - .encrypt_data_for_recovery(&change_key_id, None, v.as_u64()) + .encrypt_data_for_recovery(&change_key_id, None, v.as_u64(), PaymentId::Empty) .await .map_err(|e| e.to_string())?; @@ -428,6 +429,7 @@ where KM: TransactionKeyManagerInterface covenant, encrypted_data, minimum_value_promise, + PaymentId::Empty, &self.key_manager, ) .await diff --git a/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs b/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs index d1a799233c..7032b5906a 100644 --- a/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs +++ b/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs @@ -34,7 +34,12 @@ use crate::{ transaction_components::{TransactionError, TransactionInput, TransactionOutput}, }, validation::{ - helpers::{check_input_is_utxo, check_not_duplicate_txo, check_tari_script_byte_size}, + helpers::{ + check_input_is_utxo, + check_not_duplicate_txo, + check_tari_encrypted_data_byte_size, + check_tari_script_byte_size, + }, ValidationError, }, }; @@ -257,8 +262,10 @@ pub fn check_outputs( body: &AggregateBody, ) -> Result<(), ValidationError> { let max_script_size = constants.max_script_byte_size(); + let max_encrypted_data_size = constants.max_extra_encrypted_data_byte_size(); for output in body.outputs() { check_tari_script_byte_size(&output.script, max_script_size)?; + check_tari_encrypted_data_byte_size(&output.encrypted_data, max_encrypted_data_size)?; check_not_duplicate_txo(db, output)?; check_validator_node_registration_utxo(constants, output)?; } diff --git a/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs b/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs index f2a2b4d929..92d1a086c9 100644 --- a/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs +++ b/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs @@ -52,6 +52,7 @@ use crate::{ check_covenant_length, check_permitted_output_types, check_permitted_range_proof_types, + check_tari_encrypted_data_byte_size, check_tari_script_byte_size, is_all_unique_and_sorted, validate_input_version, @@ -113,6 +114,7 @@ impl AggregateBodyInternalConsistencyValidator { for output in body.outputs() { check_permitted_output_types(constants, output)?; check_script_size(output, constants.max_script_byte_size())?; + check_encrypted_data_byte_size(output, constants.max_extra_encrypted_data_byte_size())?; check_covenant_length(&output.covenant, constants.max_covenant_length())?; check_permitted_range_proof_types(constants, output)?; check_validator_node_registration_utxo(constants, output)?; @@ -167,6 +169,20 @@ fn check_script_size(output: &TransactionOutput, max_script_size: usize) -> Resu }) } +/// Verify that the TariScript is not larger than the max size +fn check_encrypted_data_byte_size( + output: &TransactionOutput, + max_encrypted_data_size: usize, +) -> Result<(), ValidationError> { + check_tari_encrypted_data_byte_size(output.encrypted_data(), max_encrypted_data_size).map_err(|e| { + warn!( + target: LOG_TARGET, + "output ({}) script size exceeded max size {:?}.", output, e + ); + e + }) +} + /// This function checks for duplicate inputs and outputs. There should be no duplicate inputs or outputs in a /// aggregated body fn check_sorting_and_duplicates(body: &AggregateBody) -> Result<(), ValidationError> { diff --git a/base_layer/core/src/validation/block_body/test.rs b/base_layer/core/src/validation/block_body/test.rs index 2a689f350e..e81243fab7 100644 --- a/base_layer/core/src/validation/block_body/test.rs +++ b/base_layer/core/src/validation/block_body/test.rs @@ -40,14 +40,18 @@ use crate::{ key_manager::{TariKeyId, TransactionKeyManagerBranch}, tari_amount::{uT, T}, test_helpers::schema_to_transaction, - transaction_components::{RangeProofType, TransactionError}, + transaction_components::{ + encrypted_data::{PaymentId, STATIC_ENCRYPTED_DATA_SIZE_TOTAL}, + EncryptedData, + RangeProofType, + TransactionError, + }, CoinbaseBuilder, CryptoFactories, }, txn_schema, validation::{BlockBodyValidator, ValidationError}, }; - async fn setup_with_rules(rules: ConsensusManager, check_rangeproof: bool) -> (TestBlockchain, BlockBodyFullValidator) { let blockchain = TestBlockchain::create(rules.clone()).await; let validator = BlockBodyFullValidator::new(rules, check_rangeproof); @@ -248,7 +252,11 @@ async fn it_allows_multiple_coinbases() { .with_script_key_id(TariKeyId::default()) .with_script(one_sided_payment_script(wallet_payment_address.public_key())) .with_range_proof_type(RangeProofType::RevealedValue) - .build_with_reward(blockchain.rules().consensus_constants(1), coinbase.value) + .build_with_reward( + blockchain.rules().consensus_constants(1), + coinbase.value, + PaymentId::Empty, + ) .await .unwrap(); @@ -410,6 +418,35 @@ async fn it_limits_the_script_byte_size() { assert!(matches!(err, ValidationError::TariScriptExceedsMaxSize { .. })); } +#[tokio::test] +async fn it_limits_the_encrypted_data_byte_size() { + let rules = ConsensusManager::builder(Network::LocalNet) + .add_consensus_constants( + ConsensusConstantsBuilder::new(Network::LocalNet) + .with_coinbase_lockheight(0) + .build(), + ) + .build() + .unwrap(); + let (mut blockchain, validator) = setup_with_rules(rules, true).await; + + let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).await.unwrap(); + + let mut schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T, 12 * T]); + schema1.script = script!(Nop Nop Nop); + let (txs, _) = schema_to_transaction(&[schema1], &blockchain.km).await; + let mut txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::>(); + let mut outputs = txs[0].body.outputs().clone(); + outputs[0].encrypted_data = EncryptedData::from_vec_unsafe(vec![0; STATIC_ENCRYPTED_DATA_SIZE_TOTAL + 257]); + txs[0].body = AggregateBody::new(txs[0].body.inputs().clone(), outputs, txs[0].body.kernels().clone()); + let (block, _) = blockchain.create_next_tip(block_spec!("B", transactions: txs)).await; + + let txn = blockchain.db().db_read_access().unwrap(); + let smt = blockchain.db().smt(); + let err = validator.validate_body(&*txn, block.block(), smt).unwrap_err(); + assert!(matches!(err, ValidationError::EncryptedDataExceedsMaxSize { .. })); +} + #[tokio::test] async fn it_rejects_invalid_input_metadata() { let rules = ConsensusManager::builder(Network::LocalNet) diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index e5cbb243f2..b5966adfc6 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -95,6 +95,14 @@ pub enum ValidationError { max_script_size: usize, actual_script_size: usize, }, + #[error( + "Encrypted data exceeded maximum encrytped data size, expected less than {max_encrypted_data_size} but was \ + {actual_encrypted_data_size}" + )] + EncryptedDataExceedsMaxSize { + max_encrypted_data_size: usize, + actual_encrypted_data_size: usize, + }, #[error("Consensus Error: {0}")] ConsensusError(String), #[error("Duplicate kernel Error: {0}")] @@ -162,6 +170,7 @@ impl ValidationError { err @ ValidationError::IncorrectPreviousHash { .. } | err @ ValidationError::BadBlockFound { .. } | err @ ValidationError::TariScriptExceedsMaxSize { .. } | + err @ ValidationError::EncryptedDataExceedsMaxSize { .. } | err @ ValidationError::ConsensusError(_) | err @ ValidationError::DuplicateKernelError(_) | err @ ValidationError::CovenantError(_) | diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 4ceac26a70..555740e268 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -42,7 +42,13 @@ use crate::{ PowAlgorithm, PowError, }, - transactions::transaction_components::{TransactionInput, TransactionKernel, TransactionOutput}, + transactions::transaction_components::{ + encrypted_data::STATIC_ENCRYPTED_DATA_SIZE_TOTAL, + EncryptedData, + TransactionInput, + TransactionKernel, + TransactionOutput, + }, validation::ValidationError, }; @@ -223,6 +229,21 @@ pub fn check_tari_script_byte_size(script: &TariScript, max_script_size: usize) Ok(()) } +/// Checks the byte size of TariScript is less than or equal to the given size, otherwise returns an error. +pub fn check_tari_encrypted_data_byte_size( + encrypted_data: &EncryptedData, + max_encrypted_data_size: usize, +) -> Result<(), ValidationError> { + let encrypted_data_size = encrypted_data.as_bytes().len(); + if encrypted_data_size > max_encrypted_data_size + STATIC_ENCRYPTED_DATA_SIZE_TOTAL { + return Err(ValidationError::EncryptedDataExceedsMaxSize { + max_encrypted_data_size: max_encrypted_data_size + STATIC_ENCRYPTED_DATA_SIZE_TOTAL, + actual_encrypted_data_size: encrypted_data_size, + }); + } + Ok(()) +} + /// This function checks that the outputs do not already exist in the TxO set. pub fn check_not_duplicate_txo( db: &B, diff --git a/base_layer/core/tests/tests/node_comms_interface.rs b/base_layer/core/tests/tests/node_comms_interface.rs index 1b03c14cdd..74aa97d98a 100644 --- a/base_layer/core/tests/tests/node_comms_interface.rs +++ b/base_layer/core/tests/tests/node_comms_interface.rs @@ -51,6 +51,7 @@ use tari_core::{ tari_amount::MicroMinotari, test_helpers::{create_utxo, TestParams, TransactionSchema}, transaction_components::{ + encrypted_data::PaymentId, OutputFeatures, TransactionOutput, TransactionOutputVersion, @@ -323,7 +324,7 @@ async fn initialize_sender_transaction_protocol_for_overflow_test( let output = WalletOutputBuilder::new(tx_output, spending_key) .with_features(txn_schema.features.clone()) .with_script(txn_schema.script.clone()) - .encrypt_data_for_recovery(key_manager, None) + .encrypt_data_for_recovery(key_manager, None, PaymentId::Empty) .await .unwrap() .with_input_data(input_data) @@ -399,6 +400,7 @@ async fn test_sender_transaction_protocol_for_overflow() { utxo.encrypted_data, utxo.minimum_value_promise, utxo.proof, + PaymentId::Empty, ); // Test overflow in inputs diff --git a/base_layer/tari_mining_helper_ffi/src/lib.rs b/base_layer/tari_mining_helper_ffi/src/lib.rs index ede36a6464..f24a824f80 100644 --- a/base_layer/tari_mining_helper_ffi/src/lib.rs +++ b/base_layer/tari_mining_helper_ffi/src/lib.rs @@ -43,7 +43,7 @@ use tari_core::{ transactions::{ generate_coinbase, key_manager::create_memory_db_key_manager, - transaction_components::RangeProofType, + transaction_components::{encrypted_data::PaymentId, RangeProofType}, }, }; use tari_crypto::tari_utilities::hex::Hex; @@ -380,6 +380,7 @@ pub unsafe extern "C" fn inject_coinbase( stealth_payment, consensus_manager.consensus_constants(height), range_proof_type, + PaymentId::Empty, ) .await }) { diff --git a/base_layer/wallet/migrations/2024-05-13-101400_payment_id/down.sql b/base_layer/wallet/migrations/2024-05-13-101400_payment_id/down.sql new file mode 100644 index 0000000000..291a97c5ce --- /dev/null +++ b/base_layer/wallet/migrations/2024-05-13-101400_payment_id/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` \ No newline at end of file diff --git a/base_layer/wallet/migrations/2024-05-13-101400_payment_id/up.sql b/base_layer/wallet/migrations/2024-05-13-101400_payment_id/up.sql new file mode 100644 index 0000000000..9ce50b940d --- /dev/null +++ b/base_layer/wallet/migrations/2024-05-13-101400_payment_id/up.sql @@ -0,0 +1,7 @@ +-- Any old 'outputs' will not be valid due to the removal of 'coinbase_block_height' and removal of default value for +-- 'spending_priority', so we drop and recreate the table. +ALTER TABLE outputs + ADD payment_id BLOB NULL; + +ALTER TABLE completed_transactions + ADD payment_id BLOB NULL; \ No newline at end of file diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index b8e18bbbe2..a63a1fa25c 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -27,7 +27,13 @@ use tari_common_types::{transaction::TxId, types::FixedHash}; use tari_core::transactions::{ key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface, TransactionKeyManagerLabel}, tari_amount::MicroMinotari, - transaction_components::{OutputType, TransactionError, TransactionOutput, WalletOutput}, + transaction_components::{ + encrypted_data::PaymentId, + OutputType, + TransactionError, + TransactionOutput, + WalletOutput, + }, }; use tari_key_manager::key_manager_service::KeyId; use tari_script::{inputs, script, ExecutionStack, Opcode, TariScript}; @@ -81,7 +87,7 @@ where continue; } - let (spending_key, committed_value) = match self.attempt_output_recovery(&output).await? { + let (spending_key, committed_value, payment_id) = match self.attempt_output_recovery(&output).await? { Some(recovered) => recovered, None => continue, }; @@ -109,6 +115,7 @@ where output.encrypted_data, output.minimum_value_promise, output.proof.clone(), + payment_id, ); rewound_outputs.push((uo, known_script_index.is_some(), hash)); @@ -234,7 +241,7 @@ where async fn attempt_output_recovery( &self, output: &TransactionOutput, - ) -> Result, OutputManagerError> { + ) -> Result, OutputManagerError> { // lets first check if the output exists in the db, if it does we dont have to try recovery as we already know // about the output. match self.db.fetch_by_commitment(output.commitment().clone()) { @@ -242,14 +249,15 @@ where Err(OutputManagerStorageError::ValueNotFound) => {}, Err(e) => return Err(e.into()), }; - let (key, committed_value) = match self.master_key_manager.try_output_key_recovery(output, None).await { - Ok(value) => value, - // Key manager errors here are actual errors and should not be suppressed. - Err(TransactionError::KeyManagerError(e)) => return Err(TransactionError::KeyManagerError(e).into()), - Err(_) => return Ok(None), - }; + let (key, committed_value, payment_id) = + match self.master_key_manager.try_output_key_recovery(output, None).await { + Ok(value) => value, + // Key manager errors here are actual errors and should not be suppressed. + Err(TransactionError::KeyManagerError(e)) => return Err(TransactionError::KeyManagerError(e).into()), + Err(_) => return Ok(None), + }; - Ok(Some((key, committed_value))) + Ok(Some((key, committed_value, payment_id))) } /// Find the key manager index that corresponds to the spending key in the rewound output, if found then modify diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 185eb1cfc1..228b4806e9 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -42,6 +42,7 @@ use tari_core::{ key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, KernelFeatures, OutputFeatures, @@ -711,7 +712,12 @@ where let encrypted_data = self .resources .key_manager - .encrypt_data_for_recovery(&spending_key_id, None, single_round_sender_data.amount.as_u64()) + .encrypt_data_for_recovery( + &spending_key_id, + None, + single_round_sender_data.amount.as_u64(), + PaymentId::Empty, + ) .await .unwrap(); let minimum_value_promise = single_round_sender_data.minimum_value_promise; @@ -752,6 +758,7 @@ where single_round_sender_data.covenant.clone(), encrypted_data, minimum_value_promise, + PaymentId::Empty, &self.resources.key_manager, ) .await?; @@ -1888,7 +1895,7 @@ where let encrypted_data = self .resources .key_manager - .encrypt_data_for_recovery(&spending_key_id, None, amount.as_u64()) + .encrypt_data_for_recovery(&spending_key_id, None, amount.as_u64(), PaymentId::Empty) .await?; let minimum_value_promise = MicroMinotari::zero(); let metadata_message = TransactionOutput::metadata_signature_message_from_parts( @@ -1931,6 +1938,7 @@ where covenant, encrypted_data, minimum_value_promise, + PaymentId::Empty, &self.resources.key_manager, ) .await?, @@ -2091,7 +2099,7 @@ where ) .await?; let encryption_key = shared_secret_to_output_encryption_key(&shared_secret)?; - if let Ok((amount, spending_key)) = + if let Ok((amount, spending_key, payment_id)) = EncryptedData::decrypt_data(&encryption_key, &output.commitment, &output.encrypted_data) { if output.verify_mask(&self.resources.factories.range_proof, &spending_key, amount.as_u64())? { @@ -2113,6 +2121,7 @@ where output.encrypted_data, output.minimum_value_promise, output.proof, + payment_id, ); let message = "SHA-XTR atomic swap".to_string(); @@ -2382,7 +2391,7 @@ where for (output, output_source, script_private_key, shared_secret) in scanned_outputs { let encryption_key = shared_secret_to_output_encryption_key(&shared_secret)?; - if let Ok((committed_value, spending_key)) = + if let Ok((committed_value, spending_key, payment_id)) = EncryptedData::decrypt_data(&encryption_key, &output.commitment, &output.encrypted_data) { if output.verify_mask( @@ -2407,6 +2416,7 @@ where output.encrypted_data, output.minimum_value_promise, output.proof, + payment_id, ); let tx_id = TxId::new_random(); diff --git a/base_layer/wallet/src/output_manager_service/storage/models.rs b/base_layer/wallet/src/output_manager_service/storage/models.rs index 0e8b4202e4..45a144357d 100644 --- a/base_layer/wallet/src/output_manager_service/storage/models.rs +++ b/base_layer/wallet/src/output_manager_service/storage/models.rs @@ -30,7 +30,7 @@ use tari_common_types::{ }; use tari_core::transactions::{ key_manager::{TariKeyId, TransactionKeyManagerInterface}, - transaction_components::WalletOutput, + transaction_components::{encrypted_data::PaymentId, WalletOutput}, }; use tari_script::{ExecutionStack, TariScript}; @@ -56,6 +56,7 @@ pub struct DbWalletOutput { pub source: OutputSource, pub received_in_tx_id: Option, pub spent_in_tx_id: Option, + pub payment_id: PaymentId, } impl DbWalletOutput { @@ -68,6 +69,7 @@ impl DbWalletOutput { spent_in_tx_id: Option, ) -> Result { let tx_output = output.to_transaction_output(key_manager).await?; + let payment_id = output.payment_id.clone(); Ok(DbWalletOutput { hash: tx_output.hash(), commitment: tx_output.commitment, @@ -82,6 +84,7 @@ impl DbWalletOutput { source, received_in_tx_id, spent_in_tx_id, + payment_id, }) } } diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs index 95e6449ef3..0fff2dc300 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs @@ -37,7 +37,14 @@ use tari_common_types::{ }; use tari_core::transactions::{ tari_amount::MicroMinotari, - transaction_components::{EncryptedData, OutputFeatures, OutputType, TransactionOutputVersion, WalletOutput}, + transaction_components::{ + encrypted_data::PaymentId, + EncryptedData, + OutputFeatures, + OutputType, + TransactionOutputVersion, + WalletOutput, + }, }; use tari_crypto::tari_utilities::ByteArray; use tari_key_manager::key_manager_service::KeyId; @@ -102,6 +109,7 @@ pub struct OutputSql { pub minimum_value_promise: i64, pub source: i32, pub last_validation_timestamp: Option, + pub payment_id: Option>, } impl OutputSql { @@ -667,6 +675,19 @@ impl OutputSql { })?; let encrypted_data = EncryptedData::from_bytes(&self.encrypted_data)?; + let payment_id = match self.payment_id { + Some(bytes) => PaymentId::from_bytes(&bytes).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create payment id from stored bytes" + ); + OutputManagerStorageError::ConversionError { + reason: "payment id could not be converted from bytes".to_string(), + } + })?, + None => PaymentId::Empty, + }; + let wallet_output = WalletOutput::new_with_rangeproof( TransactionOutputVersion::get_current_version(), MicroMinotari::from(self.value as u64), @@ -755,6 +776,7 @@ impl OutputSql { Some(bytes) => Some(RangeProof::from_canonical_bytes(&bytes)?), None => None, }, + payment_id.clone(), ); let commitment = Commitment::from_vec(&self.commitment)?; @@ -800,6 +822,7 @@ impl OutputSql { source: self.source.try_into()?, received_in_tx_id: self.received_in_tx_id.map(|d| (d as u64).into()), spent_in_tx_id: self.spent_in_tx_id.map(|d| (d as u64).into()), + payment_id, }) } } diff --git a/base_layer/wallet/src/schema.rs b/base_layer/wallet/src/schema.rs index fb49078d48..3421bcc2b0 100644 --- a/base_layer/wallet/src/schema.rs +++ b/base_layer/wallet/src/schema.rs @@ -37,6 +37,7 @@ diesel::table! { mined_timestamp -> Nullable, transaction_signature_nonce -> Binary, transaction_signature_key -> Binary, + payment_id -> Nullable, } } @@ -117,6 +118,7 @@ diesel::table! { minimum_value_promise -> BigInt, source -> Integer, last_validation_timestamp -> Nullable, + payment_id -> Nullable, } } diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 22b49cdea4..24d5a76103 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -42,6 +42,7 @@ use tari_core::{ transactions::{ tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, BuildInfo, CodeTemplateRegistration, OutputFeatures, @@ -124,6 +125,7 @@ pub enum TransactionServiceRequest { output_features: Box, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, }, SendOneSidedToStealthAddressTransaction { destination: TariAddress, @@ -132,6 +134,7 @@ pub enum TransactionServiceRequest { output_features: Box, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, }, SendShaAtomicSwapTransaction(TariAddress, MicroMinotari, UtxoSelectionCriteria, MicroMinotari, String), CancelTransaction(TxId), @@ -144,6 +147,7 @@ pub enum TransactionServiceRequest { current_height: Option, mined_timestamp: Option, scanned_output: TransactionOutput, + payment_id: PaymentId, }, SubmitTransactionToSelf(TxId, Transaction, MicroMinotari, MicroMinotari, String), SetLowPowerMode, @@ -540,6 +544,7 @@ impl TransactionServiceHandle { output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { match self .handle @@ -550,6 +555,7 @@ impl TransactionServiceHandle { output_features: Box::new(output_features), fee_per_gram, message, + payment_id, }) .await?? { @@ -591,6 +597,7 @@ impl TransactionServiceHandle { output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { match self .handle @@ -601,6 +608,7 @@ impl TransactionServiceHandle { output_features: Box::new(output_features), fee_per_gram, message, + payment_id, }) .await?? { @@ -747,6 +755,7 @@ impl TransactionServiceHandle { current_height: Option, mined_timestamp: Option, scanned_output: TransactionOutput, + payment_id: PaymentId, ) -> Result { match self .handle @@ -759,6 +768,7 @@ impl TransactionServiceHandle { current_height, mined_timestamp, scanned_output, + payment_id, }) .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..c30ad4a77d 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 @@ -448,6 +448,7 @@ where TransactionDirection::Inbound, None, None, + None, ) .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; 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..879665803d 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 @@ -606,6 +606,7 @@ where TransactionDirection::Outbound, None, None, + None, ) .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 3054de0136..31dc1e79cc 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -56,6 +56,7 @@ use tari_core::{ key_manager::TransactionKeyManagerInterface, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, CodeTemplateRegistration, KernelFeatures, OutputFeatures, @@ -622,6 +623,7 @@ where output_features, fee_per_gram, message, + payment_id, } => self .send_one_sided_transaction( destination, @@ -630,6 +632,7 @@ where *output_features, fee_per_gram, message, + payment_id, transaction_broadcast_join_handles, ) .await @@ -641,6 +644,7 @@ where output_features, fee_per_gram, message, + payment_id, } => self .send_one_sided_to_stealth_address_transaction( destination, @@ -649,6 +653,7 @@ where *output_features, fee_per_gram, message, + payment_id, transaction_broadcast_join_handles, ) .await @@ -811,6 +816,7 @@ where current_height, mined_timestamp, scanned_output, + payment_id, } => self .add_utxo_import_transaction_with_status( amount, @@ -821,6 +827,7 @@ where current_height, mined_timestamp, scanned_output, + payment_id, ) .await .map(TransactionServiceResponse::UtxoImported), @@ -1032,6 +1039,7 @@ where TransactionDirection::Inbound, None, None, + None, )?, ) .await?; @@ -1201,7 +1209,11 @@ where .clone(), ) .with_script(script) - .encrypt_data_for_recovery(&self.resources.transaction_key_manager_service, Some(&encryption_key)) + .encrypt_data_for_recovery( + &self.resources.transaction_key_manager_service, + Some(&encryption_key), + PaymentId::Empty, + ) .await? .with_input_data(inputs!(PublicKey::from_secret_key( self.resources.wallet_identity.node_identity.secret_key() @@ -1282,6 +1294,7 @@ where TransactionDirection::Outbound, None, None, + None, )?, ) .await?; @@ -1306,6 +1319,7 @@ where JoinHandle>>, >, script: TariScript, + payment_id: PaymentId, ) -> Result { let tx_id = TxId::new_random(); @@ -1397,7 +1411,11 @@ where .clone(), ) .with_script(script) - .encrypt_data_for_recovery(&self.resources.transaction_key_manager_service, Some(&encryption_key)) + .encrypt_data_for_recovery( + &self.resources.transaction_key_manager_service, + Some(&encryption_key), + payment_id.clone(), + ) .await? .with_input_data(inputs!(PublicKey::from_secret_key( self.resources.wallet_identity.node_identity.secret_key() @@ -1471,6 +1489,7 @@ where TransactionDirection::Outbound, None, None, + Some(payment_id), )?, ) .await?; @@ -1491,6 +1510,7 @@ where output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, >, @@ -1514,6 +1534,7 @@ where message, transaction_broadcast_join_handles, one_sided_payment_script(&dest_pubkey), + payment_id, ) .await } @@ -1632,7 +1653,11 @@ where .clone(), ) .with_script(script!(Nop)) - .encrypt_data_for_recovery(&self.resources.transaction_key_manager_service, Some(&recovery_key_id)) + .encrypt_data_for_recovery( + &self.resources.transaction_key_manager_service, + Some(&recovery_key_id), + PaymentId::Empty, + ) .await? .with_input_data(inputs!(PublicKey::from_secret_key( self.resources.wallet_identity.node_identity.secret_key() @@ -1733,6 +1758,7 @@ where TransactionDirection::Outbound, None, None, + None, )?, ) .await?; @@ -1822,6 +1848,7 @@ where output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, >, @@ -1852,6 +1879,7 @@ where message, transaction_broadcast_join_handles, stealth_payment_script(&nonce_public_key, &script_spending_key), + payment_id, ) .await } @@ -2831,6 +2859,7 @@ where current_height: Option, mined_timestamp: Option, scanned_output: TransactionOutput, + payment_id: PaymentId, ) -> Result { let tx_id = if let Some(id) = tx_id { id } else { TxId::new_random() }; self.db.add_utxo_import_transaction_with_status( @@ -2843,6 +2872,7 @@ where current_height, mined_timestamp, scanned_output, + payment_id, )?; let transaction_event = match import_status { ImportStatus::Imported => TransactionEvent::TransactionImported(tx_id), @@ -2942,6 +2972,7 @@ where TransactionDirection::Inbound, None, None, + None, )?, ) .await?; diff --git a/base_layer/wallet/src/transaction_service/storage/database.rs b/base_layer/wallet/src/transaction_service/storage/database.rs index 7e22f7133f..3a1bd1490d 100644 --- a/base_layer/wallet/src/transaction_service/storage/database.rs +++ b/base_layer/wallet/src/transaction_service/storage/database.rs @@ -37,7 +37,7 @@ use tari_common_types::{ }; use tari_core::transactions::{ tari_amount::MicroMinotari, - transaction_components::{Transaction, TransactionOutput}, + transaction_components::{encrypted_data::PaymentId, Transaction, TransactionOutput}, }; use crate::transaction_service::{ @@ -681,7 +681,12 @@ where T: TransactionBackend + 'static current_height: Option, mined_timestamp: Option, scanned_output: TransactionOutput, + payment_id: PaymentId, ) -> Result<(), TransactionStorageError> { + let payment_id = match payment_id { + PaymentId::Empty => None, + v => Some(v), + }; let transaction = CompletedTransaction::new( tx_id, source_address, @@ -701,6 +706,7 @@ where T: TransactionBackend + 'static TransactionDirection::Inbound, current_height, mined_timestamp, + payment_id, )?; self.db diff --git a/base_layer/wallet/src/transaction_service/storage/models.rs b/base_layer/wallet/src/transaction_service/storage/models.rs index 9830c2a8fa..fa81435234 100644 --- a/base_layer/wallet/src/transaction_service/storage/models.rs +++ b/base_layer/wallet/src/transaction_service/storage/models.rs @@ -34,7 +34,7 @@ use tari_common_types::{ }; use tari_core::transactions::{ tari_amount::MicroMinotari, - transaction_components::Transaction, + transaction_components::{encrypted_data::PaymentId, Transaction}, ReceiverTransactionProtocol, SenderTransactionProtocol, }; @@ -147,6 +147,7 @@ pub struct CompletedTransaction { pub mined_height: Option, pub mined_in_block: Option, pub mined_timestamp: Option, + pub payment_id: Option, } impl CompletedTransaction { @@ -163,6 +164,7 @@ impl CompletedTransaction { direction: TransactionDirection, mined_height: Option, mined_timestamp: Option, + payment_id: Option, ) -> Result { if status == TransactionStatus::Coinbase { return Err(TransactionStorageError::CoinbaseNotSupported); @@ -191,6 +193,7 @@ impl CompletedTransaction { mined_height, mined_in_block: None, mined_timestamp, + payment_id, }) } } @@ -270,6 +273,7 @@ impl From for CompletedTransaction { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, } } } @@ -299,6 +303,7 @@ impl From for CompletedTransaction { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, } } } 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..95e5d009a2 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -43,7 +43,7 @@ use tari_common_types::{ }, types::{BlockHash, PrivateKey, PublicKey, Signature}, }; -use tari_core::transactions::tari_amount::MicroMinotari; +use tari_core::transactions::{tari_amount::MicroMinotari, transaction_components::encrypted_data::PaymentId}; use tari_utilities::{hex::Hex, ByteArray, Hidden}; use thiserror::Error; use tokio::time::Instant; @@ -66,6 +66,7 @@ use crate::{ }, }, }; + const LOG_TARGET: &str = "wallet::transaction_service::database::wallet"; /// A Sqlite backend for the Transaction Service. The Backend is accessed via a connection pool to the Sqlite file. @@ -1672,6 +1673,7 @@ pub struct CompletedTransactionSql { mined_timestamp: Option, transaction_signature_nonce: Vec, transaction_signature_key: Vec, + payment_id: Option>, } impl CompletedTransactionSql { @@ -1936,7 +1938,10 @@ impl CompletedTransactionSql { fn try_from(c: CompletedTransaction, cipher: &XChaCha20Poly1305) -> Result { let transaction_bytes = bincode::serialize(&c.transaction).map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?; - + let payment_id = match c.payment_id { + Some(id) => Some(id.as_bytes()), + None => Some(Vec::new()), + }; let output = Self { tx_id: c.tx_id.as_u64() as i64, source_address: c.source_address.to_bytes().to_vec(), @@ -1957,6 +1962,7 @@ impl CompletedTransactionSql { mined_timestamp: c.mined_timestamp, transaction_signature_nonce: c.transaction_signature.get_public_nonce().to_vec(), transaction_signature_key: c.transaction_signature.get_signature().to_vec(), + payment_id, }; output.encrypt(cipher).map_err(TransactionStorageError::AeadError) @@ -2030,6 +2036,18 @@ impl CompletedTransaction { }, None => None, }; + let payment_id = match c.payment_id { + Some(bytes) => PaymentId::from_bytes(&bytes).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create payment id from stored bytes" + ); + CompletedTransactionConversionError::BincodeDeserialize( + "payment id could not be converted from bytes".to_string(), + ) + })?, + None => PaymentId::Empty, + }; let output = Self { tx_id: (c.tx_id as u64).into(), @@ -2054,6 +2072,7 @@ impl CompletedTransaction { mined_height: c.mined_height.map(|ic| ic as u64), mined_in_block, mined_timestamp: c.mined_timestamp, + payment_id: Some(payment_id), }; // zeroize sensitive data @@ -2174,7 +2193,7 @@ mod test { key_manager::create_memory_db_key_manager, tari_amount::MicroMinotari, test_helpers::{create_wallet_output_with_data, TestParams}, - transaction_components::{OutputFeatures, Transaction}, + transaction_components::{encrypted_data::PaymentId, OutputFeatures, Transaction}, transaction_protocol::sender::TransactionSenderMessage, ReceiverTransactionProtocol, SenderTransactionProtocol, @@ -2458,6 +2477,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; let source_address = TariAddress::new( PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), @@ -2486,6 +2506,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; CompletedTransactionSql::try_from(completed_tx1.clone(), &cipher) @@ -2721,6 +2742,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: Some(PaymentId::Empty), }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx.clone(), &cipher).unwrap(); @@ -2849,6 +2871,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx, &cipher).unwrap(); @@ -2989,6 +3012,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx.clone(), &cipher).unwrap(); 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 593eedb24f..541fc4be98 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 @@ -715,6 +715,7 @@ where Some(current_height), Some(mined_timestamp), scanned_output, + wallet_output.payment_id, ) .await?; diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 726f5ddb42..c293dda41c 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -56,7 +56,7 @@ use tari_core::{ transactions::{ key_manager::{SecretTransactionKeyManagerInterface, TransactionKeyManagerInitializer}, tari_amount::MicroMinotari, - transaction_components::{EncryptedData, OutputFeatures, UnblindedOutput}, + transaction_components::{encrypted_data::PaymentId, EncryptedData, OutputFeatures, UnblindedOutput}, CryptoFactories, }, }; @@ -528,25 +528,24 @@ where source_address: TariAddress, message: String, ) -> Result { + let value = unblinded_output.value; + let wallet_output = unblinded_output + .to_wallet_output(&self.key_manager_service, PaymentId::Empty) + .await?; let tx_id = self .transaction_service .import_utxo_with_status( - unblinded_output.value, + value, source_address, message, ImportStatus::Imported, None, None, None, - unblinded_output - .clone() - .to_wallet_output(&self.key_manager_service) - .await? - .to_transaction_output(&self.key_manager_service) - .await?, + wallet_output.to_transaction_output(&self.key_manager_service).await?, + PaymentId::Empty, ) .await?; - let wallet_output = unblinded_output.to_wallet_output(&self.key_manager_service).await?; // As non-rewindable self.output_manager_service .add_unvalidated_output(tx_id, wallet_output.clone(), None) 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..7f1fafc1bd 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -70,7 +70,7 @@ use tari_core::{ }, tari_amount::{uT, MicroMinotari, T}, test_helpers::{create_wallet_output_with_data, TestParams}, - transaction_components::{OutputFeatures, TransactionOutput, WalletOutput}, + transaction_components::{encrypted_data::PaymentId, OutputFeatures, TransactionOutput, WalletOutput}, transaction_protocol::{sender::TransactionSenderMessage, TransactionMetadata}, weight::TransactionWeight, CryptoFactories, @@ -2192,7 +2192,7 @@ async fn scan_for_recovery_test() { let features = OutputFeatures::default(); let encrypted_data = oms .key_manager_handle - .encrypt_data_for_recovery(&spending_key_result, None, amount) + .encrypt_data_for_recovery(&spending_key_result, None, amount, PaymentId::Empty) .await .unwrap(); @@ -2209,6 +2209,7 @@ async fn scan_for_recovery_test() { Covenant::new(), encrypted_data, MicroMinotari::zero(), + PaymentId::Empty, &oms.key_manager_handle, ) .await diff --git a/base_layer/wallet/tests/support/utils.rs b/base_layer/wallet/tests/support/utils.rs index 65da7d7baa..031592ff8e 100644 --- a/base_layer/wallet/tests/support/utils.rs +++ b/base_layer/wallet/tests/support/utils.rs @@ -28,6 +28,7 @@ use tari_core::{ tari_amount::MicroMinotari, test_helpers::{create_wallet_output_with_data, TestParams}, transaction_components::{ + encrypted_data::PaymentId, OutputFeatures, RangeProofType, TransactionOutput, @@ -74,7 +75,12 @@ pub async fn create_wallet_output_from_sender_data( .await .unwrap(); let encrypted_data = key_manager - .encrypt_data_for_recovery(&test_params.spend_key_id, None, sender_data.amount.as_u64()) + .encrypt_data_for_recovery( + &test_params.spend_key_id, + None, + sender_data.amount.as_u64(), + PaymentId::Empty, + ) .await .unwrap(); let mut utxo = WalletOutput::new( @@ -91,6 +97,7 @@ pub async fn create_wallet_output_from_sender_data( Covenant::default(), encrypted_data, MicroMinotari::zero(), + PaymentId::Empty, key_manager, ) .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..80eeacb360 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -130,7 +130,13 @@ use tari_core::{ }, tari_amount::*, test_helpers::{create_wallet_output_with_data, TestParams}, - transaction_components::{KernelBuilder, OutputFeatures, RangeProofType, Transaction}, + transaction_components::{ + encrypted_data::PaymentId, + KernelBuilder, + OutputFeatures, + RangeProofType, + Transaction, + }, transaction_protocol::{ proto::protocol as proto, recipient::RecipientSignedMessage, @@ -1522,7 +1528,7 @@ async fn single_transaction_burn_tari() { .try_output_key_recovery(output, Some(&recovery_key_id)) .await { - Ok((spending_key_id, value)) => { + Ok((spending_key_id, value, _)) => { assert_eq!(value, burn_value); assert_eq!(spending_key_id, spending_key_id_from_reciprocal_claim_public_key) }, @@ -1612,6 +1618,7 @@ async fn send_one_sided_transaction_to_other() { OutputFeatures::default(), 20.into(), message.clone(), + PaymentId::Empty, ) .await .expect("Alice sending one-sided tx to Bob"); @@ -1755,6 +1762,152 @@ async fn recover_one_sided_transaction() { OutputFeatures::default(), 20.into(), message.clone(), + PaymentId::Empty, + ) + .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, + value, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), + 20.into(), + message.clone(), + PaymentId::Empty, ) .await .expect("Alice sending one-sided tx to Bob"); @@ -1984,6 +2137,7 @@ async fn send_one_sided_transaction_to_self() { OutputFeatures::default(), 20.into(), message.clone(), + PaymentId::Empty, ) .await { @@ -2927,6 +3081,7 @@ async fn test_power_mode_updates() { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; let source_address = TariAddress::new( @@ -2956,6 +3111,7 @@ async fn test_power_mode_updates() { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; tx_backend @@ -5603,6 +5759,7 @@ async fn broadcast_all_completed_transactions_on_startup() { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; let completed_tx2 = CompletedTransaction { @@ -5741,6 +5898,7 @@ async fn test_update_faux_tx_on_oms_validation() { uo_1.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5757,6 +5915,7 @@ async fn test_update_faux_tx_on_oms_validation() { uo_2.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5773,6 +5932,7 @@ async fn test_update_faux_tx_on_oms_validation() { uo_3.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5916,6 +6076,7 @@ async fn test_update_coinbase_tx_on_oms_validation() { uo_1.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5932,6 +6093,7 @@ async fn test_update_coinbase_tx_on_oms_validation() { uo_2.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5948,6 +6110,7 @@ async fn test_update_coinbase_tx_on_oms_validation() { uo_3.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + 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 69f749ffda..8a3979da94 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -58,6 +58,7 @@ use tari_core::{ tari_amount::{uT, MicroMinotari}, test_helpers::{create_wallet_output_with_data, TestParams}, transaction_components::{ + encrypted_data::PaymentId, OutputFeatures, RangeProofType, Transaction, @@ -191,7 +192,7 @@ pub async fn test_db_backend(backend: T) { let public_script_key = key_manager.get_public_key_at_key_id(&script_key_id).await.unwrap(); let encrypted_data = key_manager - .encrypt_data_for_recovery(&spending_key_id, None, sender.amount.as_u64()) + .encrypt_data_for_recovery(&spending_key_id, None, sender.amount.as_u64(), PaymentId::Empty) .await .unwrap(); let mut output = WalletOutput::new( @@ -208,6 +209,7 @@ pub async fn test_db_backend(backend: T) { Covenant::default(), encrypted_data, MicroMinotari::zero(), + PaymentId::Empty, &key_manager, ) .await @@ -338,6 +340,7 @@ pub async fn test_db_backend(backend: T) { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: Some(PaymentId::Empty), }); db.complete_outbound_transaction(outbound_txs[i].tx_id, completed_txs[i].clone()) .unwrap(); @@ -586,6 +589,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(5), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), + None, ) .unwrap(); @@ -615,6 +619,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(6), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), + None, ) .unwrap(); @@ -644,6 +649,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(7), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), + None, ) .unwrap(); 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..17af262964 100644 --- a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs @@ -217,6 +217,7 @@ pub async fn add_transaction_to_database( TransactionDirection::Outbound, None, None, + None, ) .unwrap(); db.insert_completed_transaction(tx_id, completed_tx1).unwrap(); diff --git a/base_layer/wallet/tests/utxo_scanner/mod.rs b/base_layer/wallet/tests/utxo_scanner/mod.rs index ba46a85ec7..9b9b09821b 100644 --- a/base_layer/wallet/tests/utxo_scanner/mod.rs +++ b/base_layer/wallet/tests/utxo_scanner/mod.rs @@ -477,6 +477,7 @@ async fn test_utxo_scanner_recovery_with_restart() { current_height: _, mined_timestamp: _, scanned_output: _, + payment_id: _, } = req { assert_eq!(message, "Output found on blockchain during Wallet Recovery".to_string()); @@ -544,6 +545,7 @@ async fn test_utxo_scanner_recovery_with_restart() { current_height: _, mined_timestamp: _, scanned_output: _, + payment_id: _, } = req { assert_eq!(message, "recovery".to_string()); @@ -955,6 +957,7 @@ async fn test_utxo_scanner_one_sided_payments() { current_height: _, mined_timestamp: _, scanned_output: _, + payment_id: _, } = req { assert_eq!(message, "Output found on blockchain during Wallet Recovery".to_string()); @@ -1053,6 +1056,7 @@ async fn test_utxo_scanner_one_sided_payments() { current_height: h, mined_timestamp: _, scanned_output: _, + payment_id: _, } = req { println!("{:?}", h); diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index 7ff55a7bc8..b30b76f581 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -322,6 +322,7 @@ mod test { TransactionDirection::Inbound, None, None, + None, ) .unwrap(); db.insert_completed_transaction(2u64.into(), completed_tx.clone()) @@ -390,6 +391,7 @@ mod test { TransactionDirection::Inbound, Some(2), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap_or(NaiveDateTime::MIN)), + None, ) .unwrap(); db.insert_completed_transaction(6u64.into(), faux_unconfirmed_tx.clone()) @@ -422,6 +424,7 @@ mod test { TransactionDirection::Inbound, Some(5), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), + None, ) .unwrap(); db.insert_completed_transaction(7u64.into(), faux_confirmed_tx.clone()) diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 93644e7737..5d4251e506 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -143,7 +143,14 @@ use tari_core::{ consensus::ConsensusManager, transactions::{ tari_amount::MicroMinotari, - transaction_components::{OutputFeatures, OutputFeaturesVersion, OutputType, RangeProofType, UnblindedOutput}, + transaction_components::{ + encrypted_data::PaymentId, + OutputFeatures, + OutputFeaturesVersion, + OutputType, + RangeProofType, + UnblindedOutput, + }, CryptoFactories, }, }; @@ -300,6 +307,7 @@ pub struct TariUtxo { pub lock_height: u64, pub status: u8, pub coinbase_extra: *const c_char, + pub payment_id: *const c_char, } impl From for TariUtxo { @@ -331,6 +339,11 @@ impl From for TariUtxo { coinbase_extra: CString::new(x.wallet_output.features.coinbase_extra.to_hex()) .expect("failed to obtain hex from a commitment") .into_raw(), + payment_id: CString::new( + String::from_utf8(x.payment_id.as_bytes()).unwrap_or_else(|_| "Invalid".to_string()), + ) + .expect("failed to obtain string from a payment id") + .into_raw(), } } } @@ -1603,7 +1616,7 @@ pub unsafe extern "C" fn create_tari_unblinded_output( let encrypted_data = if encrypted_data.is_null() { TariEncryptedOpenings::default() } else { - *encrypted_data + (*encrypted_data).clone() }; let unblinded_output = UnblindedOutput::new_current_version( @@ -6694,6 +6707,7 @@ pub unsafe extern "C" fn wallet_send_transaction( fee_per_gram: c_ulonglong, message: *const c_char, one_sided: bool, + payment_id_string: *const c_char, error_out: *mut c_int, ) -> c_ulonglong { let mut error = 0; @@ -6739,16 +6753,27 @@ pub unsafe extern "C" fn wallet_send_transaction( _ => { error = LibWalletError::from(InterfaceError::NullError("message".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); - message_string = CString::new("") - .expect("Blank CString will not fail") - .to_str() - .expect("CString.to_str() will not fail") - .to_owned(); + 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 bytes = v.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 @@ -6760,6 +6785,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(), @@ -9331,8 +9357,14 @@ mod test { let commitment = Commitment::from_public_key(&PublicKey::from_secret_key(&spending_key)); let encryption_key = PrivateKey::random(&mut OsRng); let amount = MicroMinotari::from(123456); - let encrypted_data = - TariEncryptedOpenings::encrypt_data(&encryption_key, &commitment, amount, &spending_key).unwrap(); + let encrypted_data = TariEncryptedOpenings::encrypt_data( + &encryption_key, + &commitment, + amount, + &spending_key, + PaymentId::Empty, + ) + .unwrap(); let encrypted_data_bytes = encrypted_data.to_byte_vec(); let encrypted_data_1 = Box::into_raw(Box::new(encrypted_data)); diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 9ef96d45c9..6c076980e6 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -348,6 +348,7 @@ struct TariUtxo { uint64_t lock_height; uint8_t status; const char *coinbase_extra; + const char *payment_id; }; #ifdef __cplusplus @@ -3167,6 +3168,7 @@ unsigned long long wallet_send_transaction(struct TariWallet *wallet, unsigned long long fee_per_gram, const char *message, bool one_sided, + unsigned long long payment_id, int *error_out); /** diff --git a/integration_tests/src/miner.rs b/integration_tests/src/miner.rs index 840107b3a2..5bb351b99e 100644 --- a/integration_tests/src/miner.rs +++ b/integration_tests/src/miner.rs @@ -43,7 +43,7 @@ use tari_core::{ generate_coinbase_with_wallet_output, key_manager::{MemoryDbKeyManager, TariKeyId}, tari_amount::MicroMinotari, - transaction_components::{RangeProofType, WalletOutput}, + transaction_components::{encrypted_data::PaymentId, RangeProofType, WalletOutput}, }, }; use tari_utilities::ByteArray; @@ -301,6 +301,7 @@ async fn create_block_template_with_coinbase( stealth_payment, consensus_manager.consensus_constants(height), RangeProofType::BulletProofPlus, + PaymentId::Empty, ) .await .unwrap(); diff --git a/integration_tests/tests/steps/wallet_steps.rs b/integration_tests/tests/steps/wallet_steps.rs index 47568e3a2c..af6e37a8c6 100644 --- a/integration_tests/tests/steps/wallet_steps.rs +++ b/integration_tests/tests/steps/wallet_steps.rs @@ -706,6 +706,7 @@ async fn send_amount_from_source_wallet_to_dest_wallet_without_broadcast( dest_wallet.as_str() ), payment_type: 0, // normal mimblewimble payment type + payment_id: Vec::new(), }; let transfer_req = TransferRequest { recipients: vec![payment_recipient], @@ -766,6 +767,7 @@ async fn send_one_sided_transaction_from_source_wallet_to_dest_wallt( dest_wallet.as_str() ), payment_type: 1, // one sided transaction + payment_id: Vec::new(), }; let transfer_req = TransferRequest { recipients: vec![payment_recipient], @@ -865,6 +867,7 @@ async fn send_amount_from_wallet_to_wallet_at_fee( fee_per_gram ), payment_type: 0, // mimblewimble transaction + payment_id: Vec::new(), }; let transfer_req = TransferRequest { recipients: vec![payment_recipient], @@ -1294,6 +1297,7 @@ async fn send_num_transactions_to_wallets_at_fee( receiver_wallet.as_str() ), payment_type: 0, // standard mimblewimble transaction + payment_id: Vec::new(), }; let transfer_req = TransferRequest { recipients: vec![payment_recipient], @@ -1435,6 +1439,7 @@ async fn transfer_tari_from_wallet_to_receiver(world: &mut TariWorld, amount: u6 receiver.as_str() ), payment_type: 0, // normal mimblewimble payment type + payment_id: Vec::new(), }; let transfer_req = TransferRequest { recipients: vec![payment_recipient], @@ -1629,6 +1634,7 @@ async fn transfer_from_wallet_to_two_recipients_at_fee( receiver1.as_str() ), payment_type: 0, // normal mimblewimble payment type + payment_id: Vec::new(), }; let payment_recipient2 = PaymentRecipient { @@ -1642,6 +1648,7 @@ async fn transfer_from_wallet_to_two_recipients_at_fee( receiver2.as_str() ), payment_type: 0, // normal mimblewimble payment type + payment_id: Vec::new(), }; let transfer_req = TransferRequest { recipients: vec![payment_recipient1, payment_recipient2], @@ -1753,6 +1760,7 @@ async fn transfer_tari_to_self(world: &mut TariWorld, amount: u64, sender: Strin fee_per_gram, message: format!("transfer amount {} from {} to self", amount, sender.as_str(),), payment_type: 0, // normal mimblewimble payment type + payment_id: Vec::new(), }; let transfer_req = TransferRequest { recipients: vec![payment_recipient], @@ -1838,6 +1846,7 @@ async fn htlc_transaction(world: &mut TariWorld, amount: u64, sender: String, re fee_per_gram ), payment_type: 0, // normal mimblewimble transaction + payment_id: Vec::new(), }; let atomic_swap_request = SendShaAtomicSwapRequest { @@ -2127,6 +2136,7 @@ async fn send_one_sided_stealth_transaction( receiver.as_str() ), payment_type: 2, // one sided stealth transaction + payment_id: Vec::new(), }; let transfer_req = TransferRequest { recipients: vec![payment_recipient], @@ -2608,6 +2618,7 @@ async fn multi_send_txs_from_wallet( fee_per_gram ), payment_type: 0, // mimblewimble transaction + payment_id: Vec::new(), }; let transfer_req = TransferRequest {