From c444b4c2cc326b6fe3ca37b10bf2d3728cdd9aca Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Wed, 19 Jun 2024 08:49:31 +0200 Subject: [PATCH] feat!: export unblinded outputs (#6361) Description --- Exported unblinded UTXOs. In a previous PR the ability to export unblinded UTXOs were removed; this PR reinstates that ability. Also updated the wallet FFI to include all related methods. Motivation and Context --- See above. How Has This Been Tested? --- System-level testing Updated the wallet FFI unit test (`test_import_external_utxo`) What process can a PR reviewer use to test or verify this change? --- Export utxos Breaking Changes --- - [ ] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [X] Other - Please specify BREAKING CHANGE: Wallet FFI interface change to import UTXOs. --- Cargo.lock | 1 + .../minotari_app_grpc/proto/transaction.proto | 2 + .../src/conversions/transaction_output.rs | 12 +- .../src/conversions/unblinded_output.rs | 23 +- .../src/automation/commands.rs | 107 ++- .../minotari_console_wallet/src/cli.rs | 1 + .../core/src/transactions/test_helpers.rs | 7 +- .../unblinded_output.rs | 28 +- .../transaction_components/wallet_output.rs | 12 +- .../chain_storage_tests/chain_backend.rs | 2 +- .../wallet/src/output_manager_service/mod.rs | 7 +- .../src/output_manager_service/service.rs | 3 +- .../storage/sqlite_db/mod.rs | 3 +- .../storage/sqlite_db/new_output_sql.rs | 2 +- .../wallet/src/transaction_service/handle.rs | 11 +- .../wallet/src/transaction_service/service.rs | 6 +- .../tasks/check_faux_transaction_status.rs | 10 + base_layer/wallet/src/wallet.rs | 8 +- base_layer/wallet/tests/other/mod.rs | 2 +- .../output_manager_service_tests/service.rs | 2 +- base_layer/wallet_ffi/Cargo.toml | 1 + base_layer/wallet_ffi/src/lib.rs | 621 ++++++++++++++---- base_layer/wallet_ffi/wallet.h | 165 ++++- integration_tests/log4rs/cucumber.yml | 2 +- .../tests/features/WalletTransactions.feature | 1 + .../tests/steps/wallet_cli_steps.rs | 1 + integration_tests/tests/steps/wallet_steps.rs | 48 +- 27 files changed, 841 insertions(+), 247 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d11aebe151..5747d4ec64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3547,6 +3547,7 @@ dependencies = [ "cbindgen", "chacha20poly1305", "chrono", + "env_logger 0.7.1", "futures 0.3.29", "itertools 0.10.5", "libc", diff --git a/applications/minotari_app_grpc/proto/transaction.proto b/applications/minotari_app_grpc/proto/transaction.proto index 8133e70525..efef3ee014 100644 --- a/applications/minotari_app_grpc/proto/transaction.proto +++ b/applications/minotari_app_grpc/proto/transaction.proto @@ -178,5 +178,7 @@ message UnblindedOutput { bytes encrypted_data = 12; // The minimum value of the commitment that is proven by the range proof (in MicroMinotari) uint64 minimum_value_promise = 13; + // The range proof + RangeProof range_proof = 14; } diff --git a/applications/minotari_app_grpc/src/conversions/transaction_output.rs b/applications/minotari_app_grpc/src/conversions/transaction_output.rs index 88a0d9bb46..2994f982a6 100644 --- a/applications/minotari_app_grpc/src/conversions/transaction_output.rs +++ b/applications/minotari_app_grpc/src/conversions/transaction_output.rs @@ -23,7 +23,7 @@ use std::convert::{TryFrom, TryInto}; use borsh::{BorshDeserialize, BorshSerialize}; -use tari_common_types::types::{BulletRangeProof, Commitment, PublicKey}; +use tari_common_types::types::{BulletRangeProof, Commitment, PublicKey, RangeProof}; use tari_core::transactions::{ tari_amount::MicroMinotari, transaction_components::{EncryptedData, TransactionOutput, TransactionOutputVersion}, @@ -31,7 +31,7 @@ use tari_core::transactions::{ use tari_script::TariScript; use tari_utilities::ByteArray; -use crate::tari_rpc as grpc; +use crate::{tari_rpc as grpc, tari_rpc::RangeProof as GrpcRangeProof}; impl TryFrom for TransactionOutput { type Error = String; @@ -113,3 +113,11 @@ impl TryFrom for grpc::TransactionOutput { }) } } + +impl From for GrpcRangeProof { + fn from(proof: RangeProof) -> Self { + Self { + proof_bytes: proof.to_vec(), + } + } +} diff --git a/applications/minotari_app_grpc/src/conversions/unblinded_output.rs b/applications/minotari_app_grpc/src/conversions/unblinded_output.rs index 76a6f415cc..0a3764cfd2 100644 --- a/applications/minotari_app_grpc/src/conversions/unblinded_output.rs +++ b/applications/minotari_app_grpc/src/conversions/unblinded_output.rs @@ -23,7 +23,7 @@ use std::convert::{TryFrom, TryInto}; use borsh::{BorshDeserialize, BorshSerialize}; -use tari_common_types::types::{PrivateKey, PublicKey}; +use tari_common_types::types::{PrivateKey, PublicKey, RangeProof}; use tari_core::transactions::{ tari_amount::MicroMinotari, transaction_components::{EncryptedData, TransactionOutputVersion, UnblindedOutput}, @@ -59,6 +59,14 @@ impl TryFrom for grpc::UnblindedOutput { covenant, encrypted_data: output.encrypted_data.to_byte_vec(), minimum_value_promise: output.minimum_value_promise.into(), + range_proof: { + let proof_bytes = if let Some(proof) = output.range_proof { + proof.to_vec() + } else { + vec![] + }; + Some(grpc::RangeProof { proof_bytes }) + }, }) } } @@ -99,6 +107,18 @@ impl TryFrom for UnblindedOutput { let minimum_value_promise = MicroMinotari::from(output.minimum_value_promise); + let range_proof = if let Some(proof) = output.range_proof { + if proof.proof_bytes.is_empty() { + None + } else { + let proof = + RangeProof::from_vec(&proof.proof_bytes).map_err(|e| format!("Range proof is invalid: {}", e))?; + Some(proof) + } + } else { + return Err("Range proof not provided".to_string()); + }; + // zeroize output sensitive data output.spending_key.zeroize(); output.script_private_key.zeroize(); @@ -117,6 +137,7 @@ impl TryFrom for UnblindedOutput { covenant, encrypted_data, minimum_value_promise, + range_proof, )) } } diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index aed03fa4fc..d83578e337 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -64,7 +64,13 @@ use tari_comms::{ use tari_comms_dht::{envelope::NodeDestination, DhtDiscoveryRequester}; use tari_core::transactions::{ tari_amount::{uT, MicroMinotari, Minotari}, - transaction_components::{encrypted_data::PaymentId, OutputFeatures, TransactionOutput, WalletOutput}, + transaction_components::{ + encrypted_data::PaymentId, + OutputFeatures, + TransactionOutput, + UnblindedOutput, + WalletOutput, + }, }; use tari_crypto::ristretto::RistrettoSecretKey; use tari_utilities::{hex::Hex, ByteArray}; @@ -811,17 +817,37 @@ pub async fn command_runner( }, ExportUtxos(args) => match output_service.get_unspent_outputs().await { Ok(utxos) => { - let utxos: Vec<(WalletOutput, Commitment)> = - utxos.into_iter().map(|v| (v.wallet_output, v.commitment)).collect(); - let count = utxos.len(); - let sum: MicroMinotari = utxos.iter().map(|utxo| utxo.0.value).sum(); + let mut unblinded_utxos: Vec<(UnblindedOutput, Commitment)> = Vec::with_capacity(utxos.len()); + for output in utxos { + let unblinded = + UnblindedOutput::from_wallet_output(output.wallet_output, &wallet.key_manager_service) + .await?; + unblinded_utxos.push((unblinded, output.commitment)); + } + let count = unblinded_utxos.len(); + let sum: MicroMinotari = unblinded_utxos.iter().map(|utxo| utxo.0.value).sum(); if let Some(file) = args.output_file { - if let Err(e) = write_utxos_to_csv_file(utxos, file) { + if let Err(e) = write_utxos_to_csv_file(unblinded_utxos, file, args.with_private_keys) { eprintln!("ExportUtxos error! {}", e); } } else { - for (i, utxo) in utxos.iter().enumerate() { - println!("{}. Value: {} {}", i + 1, utxo.0.value, utxo.0.features); + for (i, utxo) in unblinded_utxos.iter().enumerate() { + println!( + "{}. Value: {}, Spending Key: {:?}, Script Key: {:?}, Features: {}", + i + 1, + utxo.0.value, + if args.with_private_keys { + utxo.0.spending_key.to_hex() + } else { + "*hidden*".to_string() + }, + if args.with_private_keys { + utxo.0.script_private_key.to_hex() + } else { + "*hidden*".to_string() + }, + utxo.0.features + ); } } println!("Total number of UTXOs: {}", count); @@ -859,17 +885,37 @@ pub async fn command_runner( }, ExportSpentUtxos(args) => match output_service.get_spent_outputs().await { Ok(utxos) => { - let utxos: Vec<(WalletOutput, Commitment)> = - utxos.into_iter().map(|v| (v.wallet_output, v.commitment)).collect(); - let count = utxos.len(); - let sum: MicroMinotari = utxos.iter().map(|utxo| utxo.0.value).sum(); + let mut unblinded_utxos: Vec<(UnblindedOutput, Commitment)> = Vec::with_capacity(utxos.len()); + for output in utxos { + let unblinded = + UnblindedOutput::from_wallet_output(output.wallet_output, &wallet.key_manager_service) + .await?; + unblinded_utxos.push((unblinded, output.commitment)); + } + let count = unblinded_utxos.len(); + let sum: MicroMinotari = unblinded_utxos.iter().map(|utxo| utxo.0.value).sum(); if let Some(file) = args.output_file { - if let Err(e) = write_utxos_to_csv_file(utxos, file) { + if let Err(e) = write_utxos_to_csv_file(unblinded_utxos, file, args.with_private_keys) { eprintln!("ExportSpentUtxos error! {}", e); } } else { - for (i, utxo) in utxos.iter().enumerate() { - println!("{}. Value: {} {}", i + 1, utxo.0.value, utxo.0.features); + for (i, utxo) in unblinded_utxos.iter().enumerate() { + println!( + "{}. Value: {}, Spending Key: {:?}, Script Key: {:?}, Features: {}", + i + 1, + utxo.0.value, + if args.with_private_keys { + utxo.0.spending_key.to_hex() + } else { + "*hidden*".to_string() + }, + if args.with_private_keys { + utxo.0.script_private_key.to_hex() + } else { + "*hidden*".to_string() + }, + utxo.0.features + ); } } println!("Total number of UTXOs: {}", count); @@ -1098,22 +1144,26 @@ pub async fn command_runner( Ok(()) } -fn write_utxos_to_csv_file(utxos: Vec<(WalletOutput, Commitment)>, file_path: PathBuf) -> Result<(), CommandError> { +fn write_utxos_to_csv_file( + utxos: Vec<(UnblindedOutput, Commitment)>, + file_path: PathBuf, + with_private_keys: bool, +) -> Result<(), CommandError> { let file = File::create(file_path).map_err(|e| CommandError::CSVFile(e.to_string()))?; let mut csv_file = LineWriter::new(file); writeln!( csv_file, - r##""index","version","value","spending_key","commitment","flags","maturity","coinbase_extra","script","covenant","input_data","script_private_key","sender_offset_public_key","ephemeral_commitment","ephemeral_nonce","signature_u_x","signature_u_a","signature_u_y","script_lock_height","encrypted_data","minimum_value_promise""## + r##""index","version","value","spending_key","commitment","output_type","maturity","coinbase_extra","script","covenant","input_data","script_private_key","sender_offset_public_key","ephemeral_commitment","ephemeral_nonce","signature_u_x","signature_u_a","signature_u_y","script_lock_height","encrypted_data","minimum_value_promise","range_proof""## ) - .map_err(|e| CommandError::CSVFile(e.to_string()))?; + .map_err(|e| CommandError::CSVFile(e.to_string()))?; for (i, (utxo, commitment)) in utxos.iter().enumerate() { writeln!( csv_file, - r##""{}","V{}","{}","{}","{}","{:?}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}""##, + r##""{}","V{}","{}","{}","{}","{:?}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}""##, i + 1, utxo.version.as_u8(), utxo.value.0, - utxo.spending_key_id, + if with_private_keys {utxo.spending_key.to_hex()} else { "*hidden*".to_string() }, commitment.to_hex(), utxo.features.output_type, utxo.features.maturity, @@ -1122,7 +1172,7 @@ fn write_utxos_to_csv_file(utxos: Vec<(WalletOutput, Commitment)>, file_path: Pa utxo.script.to_hex(), utxo.covenant.to_bytes().to_hex(), utxo.input_data.to_hex(), - utxo.script_key_id, + if with_private_keys {utxo.script_private_key.to_hex()} else { "*hidden*".to_string() }, utxo.sender_offset_public_key.to_hex(), utxo.metadata_signature.ephemeral_commitment().to_hex(), utxo.metadata_signature.ephemeral_pubkey().to_hex(), @@ -1131,9 +1181,20 @@ fn write_utxos_to_csv_file(utxos: Vec<(WalletOutput, Commitment)>, file_path: Pa utxo.metadata_signature.u_y().to_hex(), utxo.script_lock_height, utxo.encrypted_data.to_byte_vec().to_hex(), - utxo.minimum_value_promise.as_u64() + utxo.minimum_value_promise.as_u64(), + if let Some(proof) = utxo.range_proof.clone() { + proof.to_hex() + } else { + "".to_string() + }, ) - .map_err(|e| CommandError::CSVFile(e.to_string()))?; + .map_err(|e| CommandError::CSVFile(e.to_string()))?; + debug!( + target: LOG_TARGET, + "UTXO {} exported: {:?}", + i + 1, + utxo + ); } Ok(()) } diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index 496347181a..1c92318e9a 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -241,6 +241,7 @@ pub struct WhoisArgs { pub struct ExportUtxosArgs { #[clap(short, long)] pub output_file: Option, + pub with_private_keys: bool, } #[derive(Debug, Args, Clone)] diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index ed1665b446..610233dbeb 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -372,7 +372,12 @@ pub async fn create_wallet_output_with_data< UtxoTestParams { value, script, - features: output_features, + features: output_features.clone(), + minimum_value_promise: if output_features.range_proof_type == RangeProofType::BulletProofPlus { + MicroMinotari::zero() + } else { + value + }, ..Default::default() }, key_manager, 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 7dfa148411..d20047143c 100644 --- a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs +++ b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs @@ -29,10 +29,10 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{ComAndPubSignature, PrivateKey, PublicKey}; +use tari_common_types::types::{ComAndPubSignature, PrivateKey, PublicKey, RangeProof}; use tari_script::{ExecutionStack, TariScript}; -use super::TransactionOutputVersion; +use super::{RangeProofType, TransactionOutputVersion}; use crate::{ covenants::Covenant, transactions::{ @@ -66,6 +66,7 @@ pub struct UnblindedOutput { pub script_lock_height: u64, pub encrypted_data: EncryptedData, pub minimum_value_promise: MicroMinotari, + pub range_proof: Option, } impl UnblindedOutput { @@ -86,7 +87,13 @@ impl UnblindedOutput { covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + range_proof: Option, ) -> Self { + let range_proof = if features.range_proof_type == RangeProofType::BulletProofPlus { + range_proof + } else { + None + }; Self { version, value, @@ -101,9 +108,11 @@ impl UnblindedOutput { covenant, encrypted_data, minimum_value_promise, + range_proof, } } + #[allow(clippy::too_many_arguments)] pub fn new_current_version( value: MicroMinotari, spending_key: PrivateKey, @@ -117,6 +126,7 @@ impl UnblindedOutput { covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + range_proof: Option, ) -> Self { Self::new( TransactionOutputVersion::get_current_version(), @@ -132,6 +142,7 @@ impl UnblindedOutput { covenant, encrypted_data, minimum_value_promise, + range_proof, ) } @@ -142,7 +153,7 @@ impl UnblindedOutput { ) -> 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?; - let wallet_output = WalletOutput::new( + let wallet_output = WalletOutput::new_with_rangeproof( self.version, self.value, spending_key_id, @@ -156,10 +167,9 @@ impl UnblindedOutput { self.covenant, self.encrypted_data, self.minimum_value_promise, + self.range_proof, payment_id, - key_manager, - ) - .await?; + ); Ok(wallet_output) } @@ -169,6 +179,11 @@ impl UnblindedOutput { ) -> Result { let spending_key = key_manager.get_private_key(&output.spending_key_id).await?; let script_private_key = key_manager.get_private_key(&output.script_key_id).await?; + let range_proof = if output.features.range_proof_type == RangeProofType::BulletProofPlus { + output.range_proof + } else { + None + }; let unblinded_output = UnblindedOutput { version: output.version, value: output.value, @@ -183,6 +198,7 @@ impl UnblindedOutput { script_lock_height: output.script_lock_height, encrypted_data: output.encrypted_data, minimum_value_promise: output.minimum_value_promise, + range_proof, }; Ok(unblinded_output) } 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 b645f80344..dcd6f259ad 100644 --- a/base_layer/core/src/transactions/transaction_components/wallet_output.rs +++ b/base_layer/core/src/transactions/transaction_components/wallet_output.rs @@ -71,7 +71,7 @@ pub struct WalletOutput { pub script_lock_height: u64, pub encrypted_data: EncryptedData, pub minimum_value_promise: MicroMinotari, - pub rangeproof: Option, + pub range_proof: Option, pub payment_id: PaymentId, } @@ -96,7 +96,7 @@ impl WalletOutput { payment_id: PaymentId, key_manager: &KM, ) -> Result { - let rangeproof = if features.range_proof_type == RangeProofType::BulletProofPlus { + let range_proof = if features.range_proof_type == RangeProofType::BulletProofPlus { Some( key_manager .construct_range_proof(&spending_key_id, value.into(), minimum_value_promise.into()) @@ -119,8 +119,8 @@ impl WalletOutput { covenant, encrypted_data, minimum_value_promise, + range_proof, payment_id, - rangeproof, }) } @@ -156,7 +156,7 @@ impl WalletOutput { covenant, encrypted_data, minimum_value_promise, - rangeproof, + range_proof: rangeproof, payment_id, } } @@ -205,7 +205,7 @@ impl WalletOutput { ) -> Result { let value = self.value.into(); let commitment = key_manager.get_commitment(&self.spending_key_id, &value).await?; - let rangeproof_hash = match &self.rangeproof { + let rangeproof_hash = match &self.range_proof { Some(rp) => rp.hash(), None => FixedHash::zero(), }; @@ -271,7 +271,7 @@ impl WalletOutput { self.version, self.features.clone(), commitment, - self.rangeproof.clone(), + self.range_proof.clone(), self.script.clone(), self.sender_offset_public_key.clone(), self.metadata_signature.clone(), diff --git a/base_layer/core/tests/chain_storage_tests/chain_backend.rs b/base_layer/core/tests/chain_storage_tests/chain_backend.rs index abb92a8df6..590219de03 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_backend.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_backend.rs @@ -179,7 +179,7 @@ fn test_utxo_order() { let read_utxos = db.fetch_outputs_in_block_with_spend_state(&block_hash).unwrap(); assert_eq!(utxos.len(), read_utxos.len()); for i in 0..2000 { - assert_eq!(&utxos[i], read_utxos[i].as_transaction_output().unwrap()); + assert_eq!(&utxos[i], read_utxos[i].to_transaction_output().unwrap()); } } diff --git a/base_layer/wallet/src/output_manager_service/mod.rs b/base_layer/wallet/src/output_manager_service/mod.rs index c13ba230b0..f441c48588 100644 --- a/base_layer/wallet/src/output_manager_service/mod.rs +++ b/base_layer/wallet/src/output_manager_service/mod.rs @@ -40,7 +40,10 @@ use log::*; use tari_comms::NodeIdentity; use tari_core::{ consensus::NetworkConsensus, - transactions::{key_manager::TransactionKeyManagerInterface, CryptoFactories}, + transactions::{ + key_manager::{SecretTransactionKeyManagerInterface, TransactionKeyManagerInterface}, + CryptoFactories, + }, }; use tari_service_framework::{ async_trait, @@ -103,7 +106,7 @@ where T: OutputManagerBackend + 'static impl ServiceInitializer for OutputManagerServiceInitializer where T: OutputManagerBackend + 'static, - TKeyManagerInterface: TransactionKeyManagerInterface, + TKeyManagerInterface: TransactionKeyManagerInterface + SecretTransactionKeyManagerInterface, { async fn initialize(&mut self, context: ServiceInitializerContext) -> Result<(), ServiceInitializationError> { let (sender, receiver) = reply_channel::unbounded(); diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index d5c63cc397..48c9f62e75 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -627,7 +627,7 @@ where ) -> Result<(), OutputManagerError> { debug!( target: LOG_TARGET, - "Add unvalidated output of value {} to Output Manager", output.value + "Add unvalidated output of value {} to Output Manager with TxId {}", output.value, tx_id ); let output = DbWalletOutput::from_wallet_output( output, @@ -638,6 +638,7 @@ where None, ) .await?; + trace!(target: LOG_TARGET, "TxId: {}, {:?}", tx_id, output); self.resources.db.add_unvalidated_output(tx_id, output)?; // Because we added new outputs, let try to trigger a validation for them diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index 35d937adfa..eaa987158d 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -60,6 +60,7 @@ use crate::{ schema::{known_one_sided_payment_scripts, outputs}, storage::sqlite_utilities::wallet_db_connection::WalletDbConnection, }; + mod new_output_sql; mod output_sql; const LOG_TARGET: &str = "wallet::output_manager_service::database::wallet"; @@ -1130,7 +1131,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { if OutputSql::find_by_commitment_and_cancelled(&output.commitment.to_vec(), false, &mut conn).is_ok() { return Err(OutputManagerStorageError::DuplicateOutput); } - let new_output = NewOutputSql::new(output, Some(OutputStatus::EncumberedToBeReceived), Some(tx_id))?; + let new_output = NewOutputSql::new(output, Some(OutputStatus::UnspentMinedUnconfirmed), Some(tx_id))?; new_output.commit(&mut conn)?; if start.elapsed().as_millis() > 0 { diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs index b6f03d9fb9..8407219629 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs @@ -80,7 +80,7 @@ impl NewOutputSql { let output = Self { commitment: output.commitment.to_vec(), spending_key: output.wallet_output.spending_key_id.to_string(), - rangeproof: output.wallet_output.rangeproof.map(|proof| proof.to_vec()), + rangeproof: output.wallet_output.range_proof.map(|proof| proof.to_vec()), value: output.wallet_output.value.as_u64() as i64, output_type: i32::from(output.wallet_output.features.output_type.as_byte()), maturity: output.wallet_output.features.maturity as i64, diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 24d5a76103..589c1ea6cf 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -180,7 +180,11 @@ impl fmt::Display for TransactionServiceRequest { amount, message, .. - } => write!(f, "SendTransaction (to {}, {}, {})", destination, amount, message), + } => write!( + f, + "SendTransaction (amount: {}, to: {}, message: {})", + amount, destination, message + ), Self::BurnTari { amount, message, .. } => write!(f, "Burning Tari ({}, {})", amount, message), Self::RegisterValidatorNode { validator_node_public_key, @@ -222,8 +226,9 @@ impl fmt::Display for TransactionServiceRequest { .. } => write!( f, - "ImportUtxo (from {}, {}, {} and {:?} and {:?} and {:?} and {:?}", - source_address, amount, message, import_status, tx_id, current_height, mined_timestamp + "ImportUtxoWithStatus (amount: {}, from: {}, message: {}, import status: {:?}, TxId: {:?}, height: \ + {:?}, mined at: {:?}", + amount, source_address, message, import_status, tx_id, current_height, mined_timestamp ), Self::SubmitTransactionToSelf(tx_id, _, _, _, _) => write!(f, "SubmitTransaction ({})", tx_id), Self::SetLowPowerMode => write!(f, "SetLowPowerMode "), diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index c2968732f2..a1cb3c7fa2 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -2906,7 +2906,11 @@ where payment_id, )?; let transaction_event = match import_status { - ImportStatus::Imported => TransactionEvent::TransactionImported(tx_id), + ImportStatus::Imported => TransactionEvent::DetectedTransactionUnconfirmed { + tx_id, + num_confirmations: 0, + is_valid: true, + }, ImportStatus::OneSidedUnconfirmed | ImportStatus::CoinbaseUnconfirmed => { TransactionEvent::DetectedTransactionUnconfirmed { tx_id, diff --git a/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs b/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs index c4799fc81c..67baa0ca07 100644 --- a/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs +++ b/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs @@ -110,6 +110,11 @@ pub async fn check_detected_transactions "Checking {} detected transaction statuses", all_detected_transactions.len() ); + trace!( + target: LOG_TARGET, + "Checking transaction statuses for {:?} ", + all_detected_transactions.iter().map(|tx| tx.tx_id).collect::>() + ); for tx in all_detected_transactions { let output_info_for_tx_id = match output_manager.get_output_info_for_tx_id(tx.tx_id).await { Ok(s) => s, @@ -118,6 +123,11 @@ pub async fn check_detected_transactions return; }, }; + trace!( + target: LOG_TARGET, + "TxId: {}, {:?} ", + tx.tx_id, output_info_for_tx_id + ); // Its safe to assume that statuses should be the same as they are all in the same transaction and they cannot // be different. let output_status = output_info_for_tx_id.statuses[0]; diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 1b7dcbcb63..7f7a880442 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -31,7 +31,7 @@ use tari_common::configuration::bootstrap::ApplicationType; use tari_common_types::{ tari_address::TariAddress, transaction::{ImportStatus, TxId}, - types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, SignatureWithDomain}, + types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, SignatureWithDomain}, wallet_types::WalletType, }; use tari_comms::{ @@ -521,8 +521,8 @@ where covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + range_proof: Option, ) -> Result { - // lets import the private keys let unblinded_output = UnblindedOutput::new_current_version( amount, spending_key.clone(), @@ -536,6 +536,7 @@ where covenant, encrypted_data, minimum_value_promise, + range_proof, ); self.import_unblinded_output_as_non_rewindable(unblinded_output, source_address, message) .await @@ -574,9 +575,10 @@ where .await?; info!( target: LOG_TARGET, - "UTXO (Commitment: {}, value: {}) imported into wallet as 'ImportStatus::Imported' and is non-rewindable", + "UTXO (Commitment: {}, value: {}, txID: {}) imported into wallet as 'ImportStatus::Imported' and is non-rewindable", wallet_output.commitment(&self.key_manager_service).await?.to_hex(), wallet_output.value, + tx_id, ); Ok(tx_id) diff --git a/base_layer/wallet/tests/other/mod.rs b/base_layer/wallet/tests/other/mod.rs index 1ce90e1003..1641e92098 100644 --- a/base_layer/wallet/tests/other/mod.rs +++ b/base_layer/wallet/tests/other/mod.rs @@ -736,7 +736,7 @@ async fn test_import_utxo() { let key_manager = create_memory_db_key_manager(); let p = TestParams::new(&key_manager); let utxo = create_wallet_output_with_data(script.clone(), temp_features, &p, 20000 * uT, &key_manager).await.unwrap(); - let output = utxo.as_transaction_output(&key_manager).unwrap(); + let output = utxo.to_transaction_output(&key_manager).unwrap(); let expected_output_hash = output.hash(); let node_address = TariAddress::new_single_address_with_interactive_only(node_identity.public_key().clone(), network); alice_wallet 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 0e4df26c94..0074054774 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -2154,7 +2154,7 @@ async fn test_get_status_by_tx_id() { assert_eq!(output_statuses_by_tx_id.statuses.len(), 1); assert_eq!( output_statuses_by_tx_id.statuses[0], - OutputStatus::EncumberedToBeReceived + OutputStatus::UnspentMinedUnconfirmed ); } diff --git a/base_layer/wallet_ffi/Cargo.toml b/base_layer/wallet_ffi/Cargo.toml index 5b53812379..5dc915eb56 100644 --- a/base_layer/wallet_ffi/Cargo.toml +++ b/base_layer/wallet_ffi/Cargo.toml @@ -50,6 +50,7 @@ tari_test_utils = { path = "../../infrastructure/test_utils"} tari_service_framework = { path = "../../base_layer/service_framework" } tari_core = { path = "../../base_layer/core", default-features = false, features = ["base_node"] } borsh = "1.2" +env_logger = "0.7.1" [build-dependencies] cbindgen = "0.24.3" diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 065b6f5e9b..44fb99f3c3 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -121,7 +121,7 @@ use tari_common_types::{ emoji::emoji_set, tari_address::{TariAddress, TariAddressError}, transaction::{TransactionDirection, TransactionStatus, TxId}, - types::{ComAndPubSignature, Commitment, PublicKey, SignatureWithDomain}, + types::{ComAndPubSignature, Commitment, PublicKey, RangeProof, SignatureWithDomain}, wallet_types::WalletType, }; use tari_comms::{ @@ -206,36 +206,36 @@ mod consts { const LOG_TARGET: &str = "wallet_ffi"; -pub type TariTransportConfig = tari_p2p::TransportConfig; -pub type TariPublicKey = tari_common_types::types::PublicKey; -pub type TariWalletAddress = tari_common_types::tari_address::TariAddress; +pub type TariTransportConfig = TransportConfig; +pub type TariPublicKey = PublicKey; +pub type TariWalletAddress = TariAddress; pub type TariNodeId = tari_comms::peer_manager::NodeId; pub type TariPrivateKey = tari_common_types::types::PrivateKey; -pub type TariOutputFeatures = tari_core::transactions::transaction_components::OutputFeatures; +pub type TariRangeProof = RangeProof; +pub type TariOutputFeatures = OutputFeatures; pub type TariCommsConfig = tari_p2p::P2pConfig; pub type TariTransactionKernel = tari_core::transactions::transaction_components::TransactionKernel; pub type TariCovenant = tari_core::covenants::Covenant; pub type TariEncryptedOpenings = tari_core::transactions::transaction_components::EncryptedData; -pub type TariComAndPubSignature = tari_common_types::types::ComAndPubSignature; -pub type TariUnblindedOutput = tari_core::transactions::transaction_components::UnblindedOutput; - +pub type TariComAndPubSignature = ComAndPubSignature; +pub type TariUnblindedOutput = UnblindedOutput; pub struct TariUnblindedOutputs(Vec); pub struct TariContacts(Vec); -pub type TariContact = tari_contacts::contacts_service::types::Contact; -pub type TariCompletedTransaction = minotari_wallet::transaction_service::storage::models::CompletedTransaction; +pub type TariContact = Contact; +pub type TariCompletedTransaction = CompletedTransaction; pub type TariTransactionSendStatus = minotari_wallet::transaction_service::handle::TransactionSendStatus; pub type TariFeePerGramStats = minotari_wallet::transaction_service::handle::FeePerGramStatsResponse; pub type TariFeePerGramStat = tari_core::mempool::FeePerGramStat; pub type TariContactsLivenessData = tari_contacts::contacts_service::handle::ContactsLivenessData; pub type TariBalance = minotari_wallet::output_manager_service::service::Balance; -pub type TariMnemonicLanguage = tari_key_manager::mnemonic::MnemonicLanguage; +pub type TariMnemonicLanguage = MnemonicLanguage; pub struct TariCompletedTransactions(Vec); -pub type TariPendingInboundTransaction = minotari_wallet::transaction_service::storage::models::InboundTransaction; -pub type TariPendingOutboundTransaction = minotari_wallet::transaction_service::storage::models::OutboundTransaction; +pub type TariPendingInboundTransaction = InboundTransaction; +pub type TariPendingOutboundTransaction = OutboundTransaction; pub struct TariPendingInboundTransactions(Vec); @@ -485,7 +485,7 @@ impl TariVector { } #[allow(dead_code)] - fn to_utxo_vec(&self) -> Result, InterfaceError> { + pub fn to_utxo_vec(&self) -> Result, InterfaceError> { if self.tag != TariTypeTag::Utxo { return Err(InterfaceError::InvalidArgument(format!( "expecting Utxo, got {}", @@ -1481,6 +1481,7 @@ pub unsafe extern "C" fn create_tari_unblinded_output( encrypted_data: *mut TariEncryptedOpenings, minimum_value_promise: c_ulonglong, script_lock_height: c_ulonglong, + range_proof: *mut TariRangeProof, error_out: *mut c_int, ) -> *mut TariUnblindedOutput { let mut error = 0; @@ -1578,6 +1579,16 @@ pub unsafe extern "C" fn create_tari_unblinded_output( (*encrypted_data).clone() }; + let proof = if range_proof.is_null() { + error = LibWalletError::from(InterfaceError::NullError("range_proof".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } else if *range_proof == TariRangeProof::default() { + None + } else { + Some((*range_proof).clone()) + }; + let unblinded_output = UnblindedOutput::new_current_version( amount.into(), (*spending_key).clone(), @@ -1591,6 +1602,7 @@ pub unsafe extern "C" fn create_tari_unblinded_output( covenant, encrypted_data, minimum_value_promise.into(), + proof, ); Box::into_raw(Box::new(unblinded_output)) @@ -1796,22 +1808,75 @@ pub unsafe extern "C" fn unblinded_outputs_destroy(outputs: *mut TariUnblindedOu } } +/// Get the TariUnblindedOutputs from a TariWallet +/// +/// ## Arguments +/// `wallet` - The TariWallet pointer +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if +/// wallet is null +/// +/// # Safety +/// The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a +/// memory leak +#[no_mangle] +pub unsafe extern "C" fn wallet_get_unspent_outputs( + wallet: *mut TariWallet, + error_out: *mut c_int, +) -> *mut TariUnblindedOutputs { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let mut outputs = Vec::new(); + if wallet.is_null() { + error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + + let received_outputs = (*wallet) + .runtime + .block_on((*wallet).wallet.output_manager_service.get_unspent_outputs()); + match received_outputs { + Ok(rec_outputs) => { + for output in rec_outputs { + let unblinded = (*wallet).runtime.block_on(UnblindedOutput::from_wallet_output( + output.wallet_output, + &(*wallet).wallet.key_manager_service, + )); + match unblinded { + Ok(uo) => { + outputs.push(uo); + }, + Err(e) => { + error = LibWalletError::from(WalletError::TransactionError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } + } + Box::into_raw(Box::new(TariUnblindedOutputs(outputs))) + }, + Err(e) => { + error = LibWalletError::from(WalletError::OutputManagerError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + /// Import an external UTXO into the wallet as a non-rewindable (i.e. non-recoverable) output. This will add a spendable /// UTXO (as EncumberedToBeReceived) and create a faux completed transaction to record the event. /// /// ## Arguments /// `wallet` - The TariWallet pointer -/// `amount` - The value of the UTXO in MicroMinotari -/// `spending_key` - The private spending key +/// `output` - The pointer to a TariUnblindedOutput +/// `range_proof` - The pointer to a TariRangeProof. If the 'range_proof_type' is 'RevealedValue', a default range proof +/// can be provided. /// `source_address` - The tari address of the source of the transaction -/// `features` - Options for an output's structure or use -/// `metadata_signature` - UTXO signature with the script offset private key, k_O -/// `sender_offset_public_key` - Tari script offset pubkey, K_O -/// `script_private_key` - Tari script private key, k_S, is used to create the script signature -/// `covenant` - The covenant that will be executed when spending this output /// `message` - The message that the transaction will have -/// `encrypted_data` - Encrypted data. -/// `minimum_value_promise` - The minimum value of the commitment that is proven by the range proof /// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions /// as an out parameter. /// @@ -1836,6 +1901,11 @@ pub unsafe extern "C" fn wallet_import_external_utxo_as_non_rewindable( ptr::swap(error_out, &mut error as *mut c_int); return 0; } + if output.is_null() { + error = LibWalletError::from(InterfaceError::NullError("output".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + }; let source_address = if source_address.is_null() { TariWalletAddress::default() } else { @@ -1881,68 +1951,7 @@ pub unsafe extern "C" fn wallet_import_external_utxo_as_non_rewindable( }, } } - -/// Get the TariUnblindedOutputs from a TariWallet -/// -/// ## Arguments -/// `wallet` - The TariWallet pointer -/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions -/// as an out parameter. -/// -/// ## Returns -/// `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if -/// wallet is null -/// -/// # Safety -/// The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a -/// memory leak -#[no_mangle] -pub unsafe extern "C" fn wallet_get_unspent_outputs( - wallet: *mut TariWallet, - error_out: *mut c_int, -) -> *mut TariUnblindedOutputs { - let mut error = 0; - ptr::swap(error_out, &mut error as *mut c_int); - let mut outputs = Vec::new(); - if wallet.is_null() { - error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return ptr::null_mut(); - } - - let received_outputs = (*wallet) - .runtime - .block_on((*wallet).wallet.output_manager_service.get_unspent_outputs()); - match received_outputs { - Ok(rec_outputs) => { - for output in rec_outputs { - let unblinded = (*wallet).runtime.block_on(UnblindedOutput::from_wallet_output( - output.wallet_output, - &(*wallet).wallet.key_manager_service, - )); - match unblinded { - Ok(uo) => { - outputs.push(uo); - }, - Err(e) => { - error = LibWalletError::from(WalletError::TransactionError(e)).code; - ptr::swap(error_out, &mut error as *mut c_int); - return ptr::null_mut(); - }, - } - } - Box::into_raw(Box::new(TariUnblindedOutputs(outputs))) - }, - Err(e) => { - error = LibWalletError::from(WalletError::OutputManagerError(e)).code; - ptr::swap(error_out, &mut error as *mut c_int); - ptr::null_mut() - }, - } -} - /// -------------------------------------------------------------------------------------------- /// - /// -------------------------------- Private Key ----------------------------------------------- /// /// Creates a TariPrivateKey from a ByteVector @@ -2089,6 +2098,188 @@ pub unsafe extern "C" fn private_key_from_hex(key: *const c_char, error_out: *mu } } +/// -------------------------------------------------------------------------------------------- /// +/// -------------------------------- Range Proof ----------------------------------------------- /// + +/// Creates a default TariRangeProof +/// +/// ## Arguments +/// None. +/// +/// ## Returns +/// `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() +/// if bytes is null or if there was an error creating the TariRangeProof from bytes +/// +/// # Safety +/// The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn range_proof_default() -> *mut TariRangeProof { + Box::into_raw(Box::default()) +} + +/// Gets a TariRangeProof from a TariUnblindedOutput +/// +/// ## Arguments +/// `unblinded_output` - The pointer to a TariUnblindedOutput +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `*mut TariRangeProof` - Returns a TariRangeProof, note that it returns ptr::null_mut() +/// if TariUnblindedOutput is null or position is invalid +/// +/// # Safety +/// The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak +#[allow(clippy::cast_possible_wrap)] +#[no_mangle] +pub unsafe extern "C" fn range_proof_get( + unblinded_output: *mut TariUnblindedOutput, + error_out: *mut c_int, +) -> *mut TariRangeProof { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if unblinded_output.is_null() { + error = LibWalletError::from(InterfaceError::NullError("output_with_proof".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + Box::into_raw(Box::new((*unblinded_output).clone().range_proof.unwrap_or_default())) +} + +/// Creates a TariRangeProof from a ByteVector +/// +/// ## Arguments +/// `bytes` - The pointer to a ByteVector +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() +/// if bytes is null or if there was an error creating the TariRangeProof from bytes +/// +/// # Safety +/// The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn range_proof_from_bytes( + bytes_ptr: *mut ByteVector, + error_out: *mut c_int, +) -> *mut TariRangeProof { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if bytes_ptr.is_null() { + error = LibWalletError::from(InterfaceError::NullError("bytes".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + let v = (*bytes_ptr).0.clone(); + let range_proof = TariRangeProof::from_canonical_bytes(&v); + match range_proof { + Ok(proof) => Box::into_raw(Box::new(proof)), + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + +/// Creates a TariRangeProof from a char array +/// +/// ## Arguments +/// `char_ptr` - The pointer to a char array which is hex encoded +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() +/// if proof is null or if there was an error creating the TariRangeProof from proof +/// +/// # Safety +/// The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn range_proof_from_hex(char_ptr: *const c_char, error_out: *mut c_int) -> *mut TariRangeProof { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let proof_hex; + if char_ptr.is_null() { + error = LibWalletError::from(InterfaceError::NullError("proof".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } else { + match CStr::from_ptr(char_ptr).to_str() { + Ok(v) => { + proof_hex = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("proof".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + } + + let range_proof = TariRangeProof::from_hex(proof_hex.as_str()); + + match range_proof { + Ok(proof) => Box::into_raw(Box::new(proof)), + Err(e) => { + error!(target: LOG_TARGET, "Error creating a range proof from Hex: {:?}", e); + + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + +/// Gets a ByteVector from a TariRangeProof +/// +/// ## Arguments +/// `proof` - The pointer to a TariRangeProof +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `*mut ByteVectror` - Returns a pointer to a ByteVector. Note that it returns ptr::null_mut() +/// if pk is null +/// +/// # Safety +/// The ```byte_vector_destroy``` must be called when finished with a ByteVector to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn range_proof_get_bytes( + proof_ptr: *mut TariRangeProof, + error_out: *mut c_int, +) -> *mut ByteVector { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let mut bytes = ByteVector(Vec::new()); + if proof_ptr.is_null() { + error = LibWalletError::from(InterfaceError::NullError("pk".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } else { + bytes.0 = (*proof_ptr).to_vec(); + } + Box::into_raw(Box::new(bytes)) +} + +/// Frees memory for a TariRangeProof +/// +/// ## Arguments +/// `proof` - The pointer to a TariRangeProof +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn range_proof_destroy(proof_ptr: *mut TariRangeProof) { + if !proof_ptr.is_null() { + drop(Box::from_raw(proof_ptr)) + } +} + /// -------------------------------------------------------------------------------------------- /// /// --------------------------------------- Covenant --------------------------------------------/// @@ -8982,27 +9173,32 @@ mod test { type_of((*tx).clone()), std::any::type_name::() ); - assert_eq!((*tx).status, TransactionStatus::OneSidedUnconfirmed); - let mut lock = CALLBACK_STATE_FFI.lock().unwrap(); - lock.scanned_tx_unconfirmed_callback_called = true; - let mut error = 0; - let error_ptr = &mut error as *mut c_int; - let kernel = completed_transaction_get_transaction_kernel(tx, error_ptr); - let excess_hex_ptr = transaction_kernel_get_excess_hex(kernel, error_ptr); - let excess_hex = CString::from_raw(excess_hex_ptr).to_str().unwrap().to_owned(); - assert!(!excess_hex.is_empty()); - let nonce_hex_ptr = transaction_kernel_get_excess_public_nonce_hex(kernel, error_ptr); - let nonce_hex = CString::from_raw(nonce_hex_ptr).to_str().unwrap().to_owned(); - assert!(!nonce_hex.is_empty()); - let sig_hex_ptr = transaction_kernel_get_excess_signature_hex(kernel, error_ptr); - let sig_hex = CString::from_raw(sig_hex_ptr).to_str().unwrap().to_owned(); - assert!(!sig_hex.is_empty()); - string_destroy(excess_hex_ptr as *mut c_char); - string_destroy(sig_hex_ptr as *mut c_char); - string_destroy(nonce_hex_ptr); - transaction_kernel_destroy(kernel); - drop(lock); - completed_transaction_destroy(tx); + match (*tx).status { + TransactionStatus::Imported => {}, + TransactionStatus::OneSidedUnconfirmed => { + let mut lock = CALLBACK_STATE_FFI.lock().unwrap(); + lock.scanned_tx_unconfirmed_callback_called = true; + let mut error = 0; + let error_ptr = &mut error as *mut c_int; + let kernel = completed_transaction_get_transaction_kernel(tx, error_ptr); + let excess_hex_ptr = transaction_kernel_get_excess_hex(kernel, error_ptr); + let excess_hex = CString::from_raw(excess_hex_ptr).to_str().unwrap().to_owned(); + assert!(!excess_hex.is_empty()); + let nonce_hex_ptr = transaction_kernel_get_excess_public_nonce_hex(kernel, error_ptr); + let nonce_hex = CString::from_raw(nonce_hex_ptr).to_str().unwrap().to_owned(); + assert!(!nonce_hex.is_empty()); + let sig_hex_ptr = transaction_kernel_get_excess_signature_hex(kernel, error_ptr); + let sig_hex = CString::from_raw(sig_hex_ptr).to_str().unwrap().to_owned(); + assert!(!sig_hex.is_empty()); + string_destroy(excess_hex_ptr as *mut c_char); + string_destroy(sig_hex_ptr as *mut c_char); + string_destroy(nonce_hex_ptr); + transaction_kernel_destroy(kernel); + drop(lock); + completed_transaction_destroy(tx); + }, + _ => panic!("Invalid transaction status"), + } } unsafe extern "C" fn transaction_send_result_callback(_tx_id: c_ulonglong, status: *mut TransactionSendStatus) { @@ -11104,6 +11300,7 @@ mod test { .block_on(key_manager.get_private_key(&utxo_1.script_key_id)) .unwrap(); let spending_key_ptr = Box::into_raw(Box::new(spending_key)); + let range_proof_ptr = Box::into_raw(Box::new(utxo_1.range_proof.clone().unwrap_or_default())); let features_ptr = Box::into_raw(Box::new(utxo_1.features.clone())); let metadata_signature_ptr = Box::into_raw(Box::new(utxo_1.metadata_signature.clone())); let sender_offset_public_key_ptr = Box::into_raw(Box::new(utxo_1.sender_offset_public_key.clone())); @@ -11127,6 +11324,7 @@ mod test { encrypted_data_ptr, minimum_value_promise, 0, + range_proof_ptr, error_ptr, ); @@ -11232,7 +11430,10 @@ mod test { error_ptr, ); - // Test the consistent features case + let source_address_ptr = Box::into_raw(Box::default()); + let message_ptr = CString::into_raw(CString::new("For my friend").unwrap()) as *const c_char; + + // Test import with bulletproof range proof let utxo_1 = runtime .block_on(create_wallet_output_with_data( script!(Nop), @@ -11242,70 +11443,200 @@ mod test { key_manager, )) .unwrap(); - let amount = utxo_1.value.as_u64(); + // Test all range proof methods; convenient because we have the data + { + // - Range proof from hex + let proof_char_ptr = + CString::into_raw(CString::new(utxo_1.range_proof.as_ref().unwrap().to_hex()).unwrap()) + as *const c_char; + let ptr_a = range_proof_from_hex(proof_char_ptr, error_ptr); + // - Range proof from bytes + let proof_bytes_ptr = + Box::into_raw(Box::new(ByteVector(utxo_1.range_proof.as_ref().unwrap().to_vec()))); + let ptr_b = range_proof_from_bytes(proof_bytes_ptr, error_ptr); + // - Verify + let ptr_a_bytes = range_proof_get_bytes(ptr_a, error_ptr); + let ptr_b_bytes = range_proof_get_bytes(ptr_b, error_ptr); + for i in 0..utxo_1.range_proof.as_ref().unwrap().0.len() { + let byte_a = byte_vector_get_at(ptr_a_bytes, i.try_into().unwrap(), error_ptr); + let byte_b = byte_vector_get_at(ptr_b_bytes, i.try_into().unwrap(), error_ptr); + assert_eq!(byte_a, byte_b); + } + // - Cleanup + string_destroy(proof_char_ptr as *mut c_char); + byte_vector_destroy(proof_bytes_ptr); + byte_vector_destroy(ptr_a_bytes); + byte_vector_destroy(ptr_b_bytes); + range_proof_destroy(ptr_a); + range_proof_destroy(ptr_b); + }; + let amount = utxo_1.value.as_u64(); let spending_key = runtime .block_on(key_manager.get_private_key(&utxo_1.spending_key_id)) .unwrap(); let script_private_key = runtime .block_on(key_manager.get_private_key(&utxo_1.script_key_id)) .unwrap(); - let spending_key_ptr = Box::into_raw(Box::new(spending_key)); - let features_ptr = Box::into_raw(Box::new(utxo_1.features.clone())); - let source_address_ptr = Box::into_raw(Box::default()); - let metadata_signature_ptr = Box::into_raw(Box::new(utxo_1.metadata_signature.clone())); - let sender_offset_public_key_ptr = Box::into_raw(Box::new(utxo_1.sender_offset_public_key.clone())); - let script_private_key_ptr = Box::into_raw(Box::new(script_private_key)); - let covenant_ptr = Box::into_raw(Box::new(utxo_1.covenant.clone())); - let encrypted_data_ptr = Box::into_raw(Box::new(utxo_1.encrypted_data)); + let spending_key_ptr_1 = Box::into_raw(Box::new(spending_key)); + let proof_ptr_1 = Box::into_raw(Box::new(utxo_1.range_proof.clone().unwrap_or_default())); + let features_ptr_1 = Box::into_raw(Box::new(utxo_1.features.clone())); + let metadata_signature_ptr_1 = Box::into_raw(Box::new(utxo_1.metadata_signature.clone())); + let sender_offset_public_key_ptr_1 = Box::into_raw(Box::new(utxo_1.sender_offset_public_key.clone())); + let script_private_key_ptr_1 = Box::into_raw(Box::new(script_private_key)); + let covenant_ptr_1 = Box::into_raw(Box::new(utxo_1.covenant.clone())); + let encrypted_data_ptr_1 = Box::into_raw(Box::new(utxo_1.encrypted_data)); let minimum_value_promise = utxo_1.minimum_value_promise.as_u64(); - let message_ptr = CString::into_raw(CString::new("For my friend").unwrap()) as *const c_char; - let script_ptr = CString::into_raw(CString::new(script!(Nop).to_hex()).unwrap()) as *const c_char; - let input_data_ptr = CString::into_raw(CString::new(utxo_1.input_data.to_hex()).unwrap()) as *const c_char; + let script_ptr_1 = CString::into_raw(CString::new(script!(Nop).to_hex()).unwrap()) as *const c_char; + let input_data_ptr_1 = + CString::into_raw(CString::new(utxo_1.input_data.to_hex()).unwrap()) as *const c_char; - let tari_utxo = create_tari_unblinded_output( + let tari_utxo_ptr_1 = create_tari_unblinded_output( amount, - spending_key_ptr, - features_ptr, - script_ptr, - input_data_ptr, - metadata_signature_ptr, - sender_offset_public_key_ptr, - script_private_key_ptr, - covenant_ptr, - encrypted_data_ptr, + spending_key_ptr_1, + features_ptr_1, + script_ptr_1, + input_data_ptr_1, + metadata_signature_ptr_1, + sender_offset_public_key_ptr_1, + script_private_key_ptr_1, + covenant_ptr_1, + encrypted_data_ptr_1, + minimum_value_promise, + 0, + proof_ptr_1, + error_ptr, + ); + let tx_id_1 = wallet_import_external_utxo_as_non_rewindable( + wallet_ptr, + tari_utxo_ptr_1, + source_address_ptr, + message_ptr, + error_ptr, + ); + + assert_eq!(error, 0); + assert!(tx_id_1 > 0); + + // Test import with revealed value range proof + let features = OutputFeatures { + version: OutputFeaturesVersion::V0, + output_type: Default::default(), + maturity: 0, + coinbase_extra: vec![], + sidechain_feature: None, + range_proof_type: RangeProofType::RevealedValue, + }; + let utxo_2 = runtime + .block_on(create_wallet_output_with_data( + script!(Nop), + features, + &runtime.block_on(TestParams::new(key_manager)), + MicroMinotari(12345u64), + key_manager, + )) + .unwrap(); + + let amount = utxo_2.value.as_u64(); + let spending_key = runtime + .block_on(key_manager.get_private_key(&utxo_2.spending_key_id)) + .unwrap(); + let script_private_key = runtime + .block_on(key_manager.get_private_key(&utxo_2.script_key_id)) + .unwrap(); + let spending_key_ptr_2 = Box::into_raw(Box::new(spending_key)); + let features_ptr_2 = Box::into_raw(Box::new(utxo_2.features.clone())); + let proof_ptr_2 = range_proof_default(); + let metadata_signature_ptr_2 = Box::into_raw(Box::new(utxo_2.metadata_signature.clone())); + let sender_offset_public_key_ptr_2 = Box::into_raw(Box::new(utxo_2.sender_offset_public_key.clone())); + let script_private_key_ptr_2 = Box::into_raw(Box::new(script_private_key)); + let covenant_ptr_2 = Box::into_raw(Box::new(utxo_2.covenant.clone())); + let encrypted_data_ptr_2 = Box::into_raw(Box::new(utxo_2.encrypted_data)); + let minimum_value_promise = utxo_2.minimum_value_promise.as_u64(); + let script_ptr_2 = CString::into_raw(CString::new(script!(Nop).to_hex()).unwrap()) as *const c_char; + let input_data_ptr_2 = + CString::into_raw(CString::new(utxo_2.input_data.to_hex()).unwrap()) as *const c_char; + + let tari_utxo_ptr_2 = create_tari_unblinded_output( + amount, + spending_key_ptr_2, + features_ptr_2, + script_ptr_2, + input_data_ptr_2, + metadata_signature_ptr_2, + sender_offset_public_key_ptr_2, + script_private_key_ptr_2, + covenant_ptr_2, + encrypted_data_ptr_2, minimum_value_promise, 0, + proof_ptr_2, error_ptr, ); - let tx_id = wallet_import_external_utxo_as_non_rewindable( + let tx_id_2 = wallet_import_external_utxo_as_non_rewindable( wallet_ptr, - tari_utxo, + tari_utxo_ptr_2, source_address_ptr, message_ptr, error_ptr, ); assert_eq!(error, 0); - assert!(tx_id > 0); + assert!(tx_id_2 > 0); - let outputs = wallet_get_unspent_outputs(wallet_ptr, error_ptr); - assert_eq!((*outputs).0.len(), 0); - assert_eq!(unblinded_outputs_get_length(outputs, error_ptr), 0); + let outputs_vec = wallet_get_all_utxos(wallet_ptr, error_ptr); + let outputs = (*outputs_vec).to_utxo_vec().unwrap(); + assert_eq!(outputs.len(), 2); + + let unspent_outputs_ptr = wallet_get_unspent_outputs(wallet_ptr, error_ptr); + let unblinded_output_ptr_1 = unblinded_outputs_get_at(unspent_outputs_ptr, 0, error_ptr); + let range_proof_ptr_1 = range_proof_get(unblinded_output_ptr_1, error_ptr); + let unblinded_output_ptr_2 = unblinded_outputs_get_at(unspent_outputs_ptr, 1, error_ptr); + let range_proof_ptr_2 = range_proof_get(unblinded_output_ptr_2, error_ptr); + + assert_eq!((*tari_utxo_ptr_1).spending_key, (*unblinded_output_ptr_1).spending_key); + assert_eq!( + (*tari_utxo_ptr_1).encrypted_data, + (*unblinded_output_ptr_1).encrypted_data + ); + assert_eq!((*proof_ptr_1).0, (*range_proof_ptr_1).0); + assert_eq!((*tari_utxo_ptr_2).spending_key, (*unblinded_output_ptr_2).spending_key); + assert_eq!( + (*tari_utxo_ptr_2).encrypted_data, + (*unblinded_output_ptr_2).encrypted_data + ); + assert_eq!((*proof_ptr_2).0, (*range_proof_ptr_2).0); // Cleanup - tari_unblinded_output_destroy(tari_utxo); - unblinded_outputs_destroy(outputs); + string_destroy(script_ptr_1 as *mut c_char); + string_destroy(input_data_ptr_1 as *mut c_char); + let _covenant = Box::from_raw(covenant_ptr_1); + let _script_private_key = Box::from_raw(script_private_key_ptr_1); + let _sender_offset_public_key = Box::from_raw(sender_offset_public_key_ptr_1); + let _metadata_signature = Box::from_raw(metadata_signature_ptr_1); + let _features = Box::from_raw(features_ptr_1); + range_proof_destroy(proof_ptr_1); + let _spending_key = Box::from_raw(spending_key_ptr_1); + tari_unblinded_output_destroy(tari_utxo_ptr_1); + range_proof_destroy(range_proof_ptr_1); + tari_unblinded_output_destroy(unblinded_output_ptr_1); + + string_destroy(script_ptr_2 as *mut c_char); + string_destroy(input_data_ptr_2 as *mut c_char); + let _covenant = Box::from_raw(covenant_ptr_2); + let _script_private_key = Box::from_raw(script_private_key_ptr_2); + let _sender_offset_public_key = Box::from_raw(sender_offset_public_key_ptr_2); + let _metadata_signature = Box::from_raw(metadata_signature_ptr_2); + let _features = Box::from_raw(features_ptr_2); + range_proof_destroy(proof_ptr_2); + let _spending_key = Box::from_raw(spending_key_ptr_2); + tari_unblinded_output_destroy(tari_utxo_ptr_2); + range_proof_destroy(range_proof_ptr_2); + tari_unblinded_output_destroy(unblinded_output_ptr_2); + string_destroy(message_ptr as *mut c_char); - string_destroy(script_ptr as *mut c_char); - string_destroy(input_data_ptr as *mut c_char); - let _covenant = Box::from_raw(covenant_ptr); - let _script_private_key = Box::from_raw(script_private_key_ptr); - let _sender_offset_public_key = Box::from_raw(sender_offset_public_key_ptr); - let _metadata_signature = Box::from_raw(metadata_signature_ptr); - let _features = Box::from_raw(features_ptr); let _source_address = Box::from_raw(source_address_ptr); - let _spending_key = Box::from_raw(spending_key_ptr); + unblinded_outputs_destroy(unspent_outputs_ptr); let _base_node_peer_public_key = Box::from_raw(base_node_peer_public_key_ptr); string_destroy(base_node_peer_address_ptr as *mut c_char); @@ -11346,6 +11677,7 @@ mod test { .block_on(key_manager.get_private_key(&utxo_1.script_key_id)) .unwrap(); let spending_key_ptr = Box::into_raw(Box::new(spending_key)); + let proof_ptr_1 = Box::into_raw(Box::new(utxo_1.range_proof.clone().unwrap_or_default())); let features_ptr = Box::into_raw(Box::new(utxo_1.features.clone())); let source_address_ptr = Box::into_raw(Box::::default()); let metadata_signature_ptr = Box::into_raw(Box::new(utxo_1.metadata_signature.clone())); @@ -11371,6 +11703,7 @@ mod test { encrypted_data_ptr, minimum_value_promise, 0, + proof_ptr_1, error_ptr, ); let json_string = tari_unblinded_output_to_json(tari_utxo, error_ptr); diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 0915b088ee..d6f44a818e 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -33,6 +33,8 @@ enum TariUtxoSort { */ struct Balance; +struct BulletRangeProof; + struct ByteVector; /** @@ -318,6 +320,13 @@ typedef struct Covenant TariCovenant; typedef struct EncryptedData TariEncryptedOpenings; +/** + * Specify the range proof + */ +typedef struct BulletRangeProof RangeProof; + +typedef RangeProof TariRangeProof; + typedef struct Contact TariContact; typedef struct ContactsLivenessData TariContactsLivenessData; @@ -872,6 +881,7 @@ TariUnblindedOutput *create_tari_unblinded_output(unsigned long long amount, TariEncryptedOpenings *encrypted_data, unsigned long long minimum_value_promise, unsigned long long script_lock_height, + TariRangeProof *range_proof, int *error_out); /** @@ -977,56 +987,50 @@ TariUnblindedOutput *unblinded_outputs_get_at(struct TariUnblindedOutputs *outpu void unblinded_outputs_destroy(struct TariUnblindedOutputs *outputs); /** - * Import an external UTXO into the wallet as a non-rewindable (i.e. non-recoverable) output. This will add a spendable - * UTXO (as EncumberedToBeReceived) and create a faux completed transaction to record the event. + * Get the TariUnblindedOutputs from a TariWallet * * ## Arguments * `wallet` - The TariWallet pointer - * `amount` - The value of the UTXO in MicroMinotari - * `spending_key` - The private spending key - * `source_address` - The tari address of the source of the transaction - * `features` - Options for an output's structure or use - * `metadata_signature` - UTXO signature with the script offset private key, k_O - * `sender_offset_public_key` - Tari script offset pubkey, K_O - * `script_private_key` - Tari script private key, k_S, is used to create the script signature - * `covenant` - The covenant that will be executed when spending this output - * `message` - The message that the transaction will have - * `encrypted_data` - Encrypted data. - * `minimum_value_promise` - The minimum value of the commitment that is proven by the range proof * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions * as an out parameter. * * ## Returns - * `c_ulonglong` - Returns the TransactionID of the generated transaction, note that it will be zero if the - * transaction is null + * `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if + * wallet is null * * # Safety - * None + * The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a + * memory leak */ -unsigned long long wallet_import_external_utxo_as_non_rewindable(struct TariWallet *wallet, - TariUnblindedOutput *output, - TariWalletAddress *source_address, - const char *message, - int *error_out); +struct TariUnblindedOutputs *wallet_get_unspent_outputs(struct TariWallet *wallet, + int *error_out); /** - * Get the TariUnblindedOutputs from a TariWallet + * Import an external UTXO into the wallet as a non-rewindable (i.e. non-recoverable) output. This will add a spendable + * UTXO (as EncumberedToBeReceived) and create a faux completed transaction to record the event. * * ## Arguments * `wallet` - The TariWallet pointer + * `output` - The pointer to a TariUnblindedOutput + * `range_proof` - The pointer to a TariRangeProof. If the 'range_proof_type' is 'RevealedValue', a default range proof + * can be provided. + * `source_address` - The tari address of the source of the transaction + * `message` - The message that the transaction will have * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions * as an out parameter. * * ## Returns - * `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if - * wallet is null + * `c_ulonglong` - Returns the TransactionID of the generated transaction, note that it will be zero if the + * transaction is null * * # Safety - * The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a - * memory leak + * None */ -struct TariUnblindedOutputs *wallet_get_unspent_outputs(struct TariWallet *wallet, - int *error_out); +unsigned long long wallet_import_external_utxo_as_non_rewindable(struct TariWallet *wallet, + TariUnblindedOutput *output, + TariWalletAddress *source_address, + const char *message, + int *error_out); /** * -------------------------------------------------------------------------------------------- /// @@ -1112,6 +1116,109 @@ TariPrivateKey *private_key_generate(void); TariPrivateKey *private_key_from_hex(const char *key, int *error_out); +/** + * -------------------------------------------------------------------------------------------- /// + * -------------------------------- Range Proof ----------------------------------------------- /// + * Creates a default TariRangeProof + * + * ## Arguments + * None. + * + * ## Returns + * `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() + * if bytes is null or if there was an error creating the TariRangeProof from bytes + * + * # Safety + * The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak + */ +TariRangeProof *range_proof_default(void); + +/** + * Gets a TariRangeProof from a TariUnblindedOutput + * + * ## Arguments + * `unblinded_output` - The pointer to a TariUnblindedOutput + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `*mut TariRangeProof` - Returns a TariRangeProof, note that it returns ptr::null_mut() + * if TariUnblindedOutput is null or position is invalid + * + * # Safety + * The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak + */ +TariRangeProof *range_proof_get(TariUnblindedOutput *unblinded_output, + int *error_out); + +/** + * Creates a TariRangeProof from a ByteVector + * + * ## Arguments + * `bytes` - The pointer to a ByteVector + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() + * if bytes is null or if there was an error creating the TariRangeProof from bytes + * + * # Safety + * The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak + */ +TariRangeProof *range_proof_from_bytes(struct ByteVector *bytes_ptr, + int *error_out); + +/** + * Creates a TariRangeProof from a char array + * + * ## Arguments + * `char_ptr` - The pointer to a char array which is hex encoded + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() + * if proof is null or if there was an error creating the TariRangeProof from proof + * + * # Safety + * The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak + */ +TariRangeProof *range_proof_from_hex(const char *char_ptr, + int *error_out); + +/** + * Gets a ByteVector from a TariRangeProof + * + * ## Arguments + * `proof` - The pointer to a TariRangeProof + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `*mut ByteVectror` - Returns a pointer to a ByteVector. Note that it returns ptr::null_mut() + * if pk is null + * + * # Safety + * The ```byte_vector_destroy``` must be called when finished with a ByteVector to prevent a memory leak + */ +struct ByteVector *range_proof_get_bytes(TariRangeProof *proof_ptr, + int *error_out); + +/** + * Frees memory for a TariRangeProof + * + * ## Arguments + * `proof` - The pointer to a TariRangeProof + * + * ## Returns + * `()` - Does not return a value, equivalent to void in C + * + * # Safety + * None + */ +void range_proof_destroy(TariRangeProof *proof_ptr); + /** * -------------------------------------------------------------------------------------------- /// * --------------------------------------- Covenant --------------------------------------------/// @@ -3149,7 +3256,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, + const char *payment_id_string, int *error_out); /** diff --git a/integration_tests/log4rs/cucumber.yml b/integration_tests/log4rs/cucumber.yml index 2025a1b01c..6c99dbceed 100644 --- a/integration_tests/log4rs/cucumber.yml +++ b/integration_tests/log4rs/cucumber.yml @@ -166,7 +166,7 @@ loggers: appenders: - base_layer_contacts wallet: - level: debug + level: trace appenders: - base_layer_wallet # miner diff --git a/integration_tests/tests/features/WalletTransactions.feature b/integration_tests/tests/features/WalletTransactions.feature index 1e2409d8d4..1120dbd487 100644 --- a/integration_tests/tests/features/WalletTransactions.feature +++ b/integration_tests/tests/features/WalletTransactions.feature @@ -166,6 +166,7 @@ Feature: Wallet Transactions When node C is at height 11 Then I check if last imported transactions are invalid in wallet WALLET_IMPORTED + @critical Scenario: Wallet imports faucet UTXO Given I have a seed node NODE When I have 1 base nodes connected to all seed nodes diff --git a/integration_tests/tests/steps/wallet_cli_steps.rs b/integration_tests/tests/steps/wallet_cli_steps.rs index 83b7c889a0..5771eaa9f6 100644 --- a/integration_tests/tests/steps/wallet_cli_steps.rs +++ b/integration_tests/tests/steps/wallet_cli_steps.rs @@ -328,6 +328,7 @@ async fn export_utxos(world: &mut TariWorld, wallet: String) { let args = ExportUtxosArgs { output_file: Some(path_buf.clone()), + with_private_keys: true, }; cli.command2 = Some(CliCommands::ExportUtxos(args)); diff --git a/integration_tests/tests/steps/wallet_steps.rs b/integration_tests/tests/steps/wallet_steps.rs index af6e37a8c6..ffb495e18c 100644 --- a/integration_tests/tests/steps/wallet_steps.rs +++ b/integration_tests/tests/steps/wallet_steps.rs @@ -42,7 +42,7 @@ use grpc::{ use minotari_app_grpc::tari_rpc::{self as grpc, TransactionStatus}; use minotari_console_wallet::{CliCommands, ExportUtxosArgs}; use minotari_wallet::transaction_service::config::TransactionRoutingMechanism; -use tari_common_types::types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey}; +use tari_common_types::types::{ComAndPubSignature, PrivateKey, PublicKey, RangeProof}; use tari_core::{ covenants::Covenant, transactions::{ @@ -57,7 +57,7 @@ use tari_core::{ }, }, }; -use tari_crypto::{commitment::HomomorphicCommitment, keys::PublicKey as PublicKeyTrait}; +use tari_crypto::commitment::HomomorphicCommitment; use tari_integration_tests::{ transaction::{ build_transaction_with_output, @@ -67,7 +67,7 @@ use tari_integration_tests::{ wallet_process::{create_wallet_client, get_default_cli, spawn_wallet}, TariWorld, }; -use tari_script::{ExecutionStack, StackItem, TariScript}; +use tari_script::{ExecutionStack, TariScript}; use tari_utilities::hex::Hex; use crate::steps::{mining_steps::create_miner, CONFIRMATION_PERIOD, HALF_SECOND, TWO_MINUTES_WITH_HALF_SECOND_SLEEP}; @@ -2225,6 +2225,7 @@ async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, let args = ExportUtxosArgs { output_file: Some(path_buf.clone()), + with_private_keys: true, }; cli.command2 = Some(CliCommands::ExportUtxos(args)); @@ -2271,14 +2272,19 @@ async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, let script_lock_height = output[18].parse::().unwrap(); let encrypted_data = EncryptedData::from_hex(&output[19]).unwrap(); let minimum_value_promise = MicroMinotari(output[20].parse::().unwrap()); + let proof = if output[21].is_empty() { + None + } else { + Some(RangeProof::from_hex(&output[21]).unwrap()) + }; let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None, RangeProofType::BulletProofPlus); let metadata_signature = ComAndPubSignature::new( ephemeral_commitment, ephemeral_nonce, - signature_u_x, signature_u_a, + signature_u_x, signature_u_y, ); let utxo = UnblindedOutput::new( @@ -2295,6 +2301,7 @@ async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, covenant, encrypted_data, minimum_value_promise, + proof, ); outputs.push(utxo); @@ -2330,6 +2337,7 @@ async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wa let args = ExportUtxosArgs { output_file: Some(path_buf.clone()), + with_private_keys: true, }; cli.command2 = Some(CliCommands::ExportSpentUtxos(args)); @@ -2375,14 +2383,19 @@ async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wa let script_lock_height = output[18].parse::().unwrap(); let encrypted_data = EncryptedData::from_hex(&output[19]).unwrap(); let minimum_value_promise = MicroMinotari(output[20].parse::().unwrap()); + let proof = if output[21].is_empty() { + None + } else { + Some(RangeProof::from_hex(&output[21]).unwrap()) + }; let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None, RangeProofType::BulletProofPlus); let metadata_signature = ComAndPubSignature::new( ephemeral_commitment, ephemeral_nonce, - signature_u_x, signature_u_a, + signature_u_x, signature_u_y, ); let utxo = UnblindedOutput::new( @@ -2399,6 +2412,7 @@ async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wa covenant, encrypted_data, minimum_value_promise, + proof, ); outputs.push(utxo); @@ -2434,6 +2448,7 @@ async fn import_unspent_outputs_as_faucets(world: &mut TariWorld, wallet_a: Stri let args = ExportUtxosArgs { output_file: Some(path_buf.clone()), + with_private_keys: true, }; cli.command2 = Some(CliCommands::ExportUtxos(args)); @@ -2479,17 +2494,22 @@ async fn import_unspent_outputs_as_faucets(world: &mut TariWorld, wallet_a: Stri let script_lock_height = output[18].parse::().unwrap(); let encrypted_data = EncryptedData::from_hex(&output[19]).unwrap(); let minimum_value_promise = MicroMinotari(output[20].parse::().unwrap()); + let proof = if output[21].is_empty() { + None + } else { + Some(RangeProof::from_hex(&output[21]).unwrap()) + }; let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None, RangeProofType::BulletProofPlus); let metadata_signature = ComAndPubSignature::new( ephemeral_commitment, ephemeral_nonce, - signature_u_x, signature_u_a, + signature_u_x, signature_u_y, ); - let mut utxo = UnblindedOutput::new( + let utxo = UnblindedOutput::new( version, value, spending_key, @@ -2503,20 +2523,10 @@ async fn import_unspent_outputs_as_faucets(world: &mut TariWorld, wallet_a: Stri covenant, encrypted_data, minimum_value_promise, + proof, ); - utxo.metadata_signature = ComAndPubSignature::new( - Commitment::default(), - PublicKey::default(), - PrivateKey::default(), - PrivateKey::default(), - PrivateKey::default(), - ); - utxo.script_private_key = utxo.clone().spending_key; - - let script_public_key = PublicKey::from_secret_key(&utxo.script_private_key); - utxo.input_data = ExecutionStack::new(vec![StackItem::PublicKey(script_public_key)]); - outputs.push(utxo.clone()); + outputs.push(utxo); } let mut wallet_b_client = create_wallet_client(world, wallet_b.clone()).await.unwrap();