diff --git a/Cargo.lock b/Cargo.lock index 38a526b1d6..d03a44bc5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1653,6 +1653,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "1.0.2" @@ -1673,6 +1682,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -3286,6 +3307,7 @@ dependencies = [ "console-subscriber", "crossterm 0.25.0", "digest 0.10.7", + "dirs", "futures 0.3.29", "ledger-transport-hid", "log", @@ -3985,6 +4007,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.1" diff --git a/applications/minotari_console_wallet/Cargo.toml b/applications/minotari_console_wallet/Cargo.toml index 345cd1b111..c4dfb24e96 100644 --- a/applications/minotari_console_wallet/Cargo.toml +++ b/applications/minotari_console_wallet/Cargo.toml @@ -38,6 +38,7 @@ clap = { version = "3.2", features = ["derive", "env"] } config = "0.14.0" crossterm = { version = "0.25.0" } digest = "0.10" +dirs = "5.0" futures = { version = "^0.3.16", default-features = false, features = [ "alloc", ] } diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 9d71989b70..2c2a6a45d0 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -33,7 +33,7 @@ use std::{ use blake2::Blake2b; use chrono::{DateTime, Utc}; -use digest::{consts::U32, Digest}; +use digest::{consts::U32, crypto_common::rand_core::OsRng, Digest}; use futures::FutureExt; use log::*; use minotari_app_grpc::tls::certs::{generate_self_signed_certs, print_warning, write_cert_to_disk}; @@ -55,7 +55,7 @@ use tari_common_types::{ emoji::EmojiId, tari_address::TariAddress, transaction::TxId, - types::{Commitment, FixedHash, PrivateKey, PublicKey, Signature}, + types::{Commitment, FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, }; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityRequester}, @@ -72,7 +72,6 @@ use tari_core::{ tari_amount::{uT, MicroMinotari, Minotari}, transaction_components::{ encrypted_data::PaymentId, - EncryptedData, OutputFeatures, Transaction, TransactionInput, @@ -84,10 +83,13 @@ use tari_core::{ }, }, }; -use tari_crypto::ristretto::{pedersen::PedersenCommitment, RistrettoSecretKey}; +use tari_crypto::{ + keys::SecretKey, + ristretto::{pedersen::PedersenCommitment, RistrettoSecretKey}, +}; use tari_key_manager::key_manager_service::KeyManagerInterface; -use tari_script::{script, CheckSigSchnorrSignature, ExecutionStack, TariScript}; -use tari_utilities::{hex::Hex, ByteArray}; +use tari_script::{script, CheckSigSchnorrSignature}; +use tari_utilities::{encoding::Base58, hex::Hex, ByteArray}; use tokio::{ sync::{broadcast, mpsc}, time::{sleep, timeout}, @@ -95,12 +97,36 @@ use tokio::{ use super::error::CommandError; use crate::{ - automation::{Step1OutputsForLeader, Step1OutputsForSelf, Step2OutputsForParties, Step2OutputsForSelf}, + automation::{ + utils::{ + get_file_name, + move_session_file_to_session_dir, + out_dir, + read_and_verify, + read_session_info, + write_json_object_to_file_as_line, + write_to_json_file, + }, + Step1SessionInfo, + Step2OutputsForLeader, + Step2OutputsForSelf, + Step3OutputsForParties, + Step3OutputsForSelf, + Step4OutputsForLeader, + }, cli::{CliCommands, MakeItRainTransactionType}, utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, }; pub const LOG_TARGET: &str = "wallet::automation::commands"; +// Faucet file names +pub(crate) const FILE_EXTENSION: &str = "json"; +pub(crate) const SESSION_INFO: &str = "step_1_session_info"; +pub(crate) const STEP_2_LEADER: &str = "step_2_for_leader_from_"; +pub(crate) const STEP_2_SELF: &str = "step_2_for_self"; +pub(crate) const STEP_3_SELF: &str = "step_3_for_self"; +pub(crate) const STEP_3_PARTIES: &str = "step_3_for_parties"; +pub(crate) const STEP_4_LEADER: &str = "step_4_for_leader_from_"; #[derive(Debug)] pub struct SentTransaction {} @@ -150,7 +176,7 @@ pub async fn burn_tari( async fn encumber_aggregate_utxo( mut wallet_transaction_service: TransactionServiceHandle, fee_per_gram: MicroMinotari, - output_hash: String, + output_hash: HashOutput, expected_commitment: PedersenCommitment, script_input_shares: HashMap, script_signature_public_nonces: Vec, @@ -718,19 +744,59 @@ pub async fn command_runner( Err(e) => eprintln!("BurnMinotari error! {}", e), } }, + FaucetGenerateSessionInfo(args) => { + if Commitment::from_hex(&args.commitment).is_err() { + eprintln!("\nInvalid 'commitment' provided!\n"); + continue; + } + if FixedHash::from_hex(&args.output_hash).is_err() { + eprintln!("\nInvalid 'output_hash' provided!\n"); + continue; + } + + let mut session_id = PrivateKey::random(&mut OsRng).to_base58(); + session_id.truncate(10); + let session_info = Step1SessionInfo { + session_id: session_id.clone(), + commitment_to_spend: args.commitment, + output_hash: args.output_hash, + recipient_address: args.recipient_address, + fee_per_gram: args.fee_per_gram, + }; + let out_dir = out_dir(&session_info.session_id)?; + let out_file = out_dir.join(get_file_name(SESSION_INFO, None)); + write_to_json_file(&out_file, true, session_info)?; + println!(); + println!("Concluded step 1 'faucet-generate-session-info'"); + println!("Your session ID is: '{}'", session_id); + println!("Your session's output directory is: '{}'", out_dir.display()); + println!("Session info saved to: '{}'", out_file.display()); + println!("Send '{}' to parties for step 2", get_file_name(SESSION_INFO, None)); + println!(); + }, FaucetCreatePartyDetails(args) => { + if args.alias.is_empty() || args.alias.contains(" ") { + eprintln!("\nAlias cannot contain spaces!\n"); + continue; + } + if args.alias.chars().any(|c| !c.is_alphanumeric() && c != '_') { + eprintln!("\nAlias contains invalid characters! Only alphanumeric and '_' are allowed.\n"); + continue; + } + let wallet_spend_key_id = wallet.get_wallet_id().await?.wallet_node_key_id.clone(); let wallet_public_spend_key = key_manager_service .get_public_key_at_key_id(&wallet_spend_key_id) .await?; let (script_nonce_key_id, public_script_nonce_key) = key_manager_service.get_random_key().await?; - let (sender_offset_key_id, public_sender_offset_key) = key_manager_service.get_random_key().await?; - let (sender_offset_nonce_key_id, public_sender_offset_nonce_key) = key_manager_service.get_random_key().await?; - let commitment = Commitment::from_hex(&args.commitment)?; + // Read session info + let session_info = read_session_info(&args.session_id, Some(args.input_file.clone()))?; + + let commitment = Commitment::from_hex(&session_info.commitment_to_spend)?; let commitment_hash: [u8; 32] = DomainSeparatedConsensusHasher::>::new("com_hash") .chain(&commitment) @@ -739,7 +805,8 @@ pub async fn command_runner( let shared_secret = key_manager_service .get_diffie_hellman_shared_secret( &sender_offset_key_id, - args.recipient_address + session_info + .recipient_address .public_view_key() .ok_or(CommandError::InvalidArgument("Missing public view key".to_string()))?, ) @@ -750,80 +817,73 @@ pub async fn command_runner( .sign_script_message(&wallet_spend_key_id, &commitment_hash) .await?; - println!( - "Party details created with: - 1. script input share: ({},{},{}), - 3. wallet public spend key_id: {}, - 4. spend nonce key_id: {}, - 5. public spend nonce key: {}, - 6. sender offset key_id: {}, - 7. public sender offset key: {}, - 8. sender offset nonce key_id: {}, - 9. public sender offset nonce key: {}, - 10. public shared secret: {}", + let out_dir = out_dir(&args.session_id)?; + let step_2_outputs_for_leader = Step2OutputsForLeader { + script_input_signature, wallet_public_spend_key, - script_input_signature.get_signature().to_hex(), - script_input_signature.get_public_nonce().to_hex(), - wallet_spend_key_id, - script_nonce_key_id, public_script_nonce_key, - sender_offset_key_id, public_sender_offset_key, - sender_offset_nonce_key_id, public_sender_offset_nonce_key, - shared_secret_public_key - ); + dh_shared_secret_public_key: shared_secret_public_key, + }; + let out_file_leader = out_dir.join(get_file_name(STEP_2_LEADER, Some(args.alias.clone()))); + write_json_object_to_file_as_line(&out_file_leader, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file_leader, false, step_2_outputs_for_leader)?; - let outputs_for_self = Step1OutputsForSelf { + let step_2_outputs_for_self = Step2OutputsForSelf { + alias: args.alias.clone(), wallet_spend_key_id, script_nonce_key_id, sender_offset_key_id, sender_offset_nonce_key_id, }; - let outputs_for_leader = Step1OutputsForLeader { - script_input_signature, - wallet_public_spend_key, - public_script_nonce_key, - public_sender_offset_key, - public_sender_offset_nonce_key, - shared_secret_public_key, - }; + let out_file_self = out_dir.join(get_file_name(STEP_2_SELF, None)); + write_json_object_to_file_as_line(&out_file_self, true, session_info)?; + write_json_object_to_file_as_line(&out_file_self, false, step_2_outputs_for_self)?; + + println!(); + println!("Concluded step 2 'faucet-create-party-details'"); + println!("Your session's output directory is '{}'", out_dir.display()); + move_session_file_to_session_dir(&args.session_id, &args.input_file)?; + println!( + "Send '{}' to leader for step 3", + get_file_name(STEP_2_LEADER, Some(args.alias)) + ); + println!(); }, FaucetEncumberAggregateUtxo(args) => { + // Read session info + let session_info = read_session_info(&args.session_id, None)?; + #[allow(clippy::mutable_key_type)] let mut input_shares = HashMap::new(); - for share in args.script_input_shares { - let data = share.split(',').collect::>(); - let public_key = PublicKey::from_hex(data[0])?; - let signature = PrivateKey::from_hex(data[1])?; - let public_nonce = PublicKey::from_hex(data[2])?; - let sig = CheckSigSchnorrSignature::new(public_nonce, signature); - input_shares.insert(public_key, sig); + let mut script_signature_public_nonces = Vec::with_capacity(args.input_file_names.len()); + let mut sender_offset_public_key_shares = Vec::with_capacity(args.input_file_names.len()); + let mut metadata_ephemeral_public_key_shares = Vec::with_capacity(args.input_file_names.len()); + let mut dh_shared_secret_shares = Vec::with_capacity(args.input_file_names.len()); + for file_name in args.input_file_names { + // Read party input + let party_info = + read_and_verify::(&args.session_id, &file_name, &session_info)?; + input_shares.insert(party_info.wallet_public_spend_key, party_info.script_input_signature); + script_signature_public_nonces.push(party_info.public_script_nonce_key); + sender_offset_public_key_shares.push(party_info.public_sender_offset_key); + metadata_ephemeral_public_key_shares.push(party_info.public_sender_offset_nonce_key); + dh_shared_secret_shares.push(party_info.dh_shared_secret_public_key); } match encumber_aggregate_utxo( transaction_service.clone(), - args.fee_per_gram, - args.output_hash, - Commitment::from_hex(&args.commitment)?, + session_info.fee_per_gram, + FixedHash::from_hex(&session_info.output_hash) + .map_err(|e| CommandError::InvalidArgument(e.to_string()))?, + Commitment::from_hex(&session_info.commitment_to_spend)?, input_shares, - args.script_signature_public_nonces - .iter() - .map(|v| v.clone().into()) - .collect::>(), - args.sender_offset_public_key_shares - .iter() - .map(|v| v.clone().into()) - .collect::>(), - args.metadata_ephemeral_public_key_shares - .iter() - .map(|v| v.clone().into()) - .collect::>(), - args.dh_shared_secret_shares - .iter() - .map(|v| v.clone().into()) - .collect::>(), - args.recipient_address, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + session_info.recipient_address.clone(), ) .await { @@ -834,46 +894,13 @@ pub async fn command_runner( total_metadata_ephemeral_public_key, total_script_nonce, )) => { - println!( - "Encumbered aggregate UTXO: - 1. tx_id: {}, - 2. input_commitment: {}, - 3. input_stack: {}, - 4. input_script: {}, - 5. total_script_key: {}, - 6. script_signature_ephemeral_commitment: {}, - 7. script_signature_ephemeral_pubkey: {}, - 8. output_commitment: {}, - 9. sender_offset_pubkey: {}, - 10. meta_signature_ephemeral_commitment: {}, - 11. meta_signature_ephemeral_pubkey: {}, - 12. encrypted_data: {}, - 13. output_features: {}", - tx_id, - transaction.body.inputs()[0].commitment().unwrap().to_hex(), - transaction.body.inputs()[0].input_data.to_hex(), - transaction.body.inputs()[0].script().unwrap().to_hex(), - script_pubkey.to_hex(), - transaction.body.inputs()[0] - .script_signature - .ephemeral_commitment() - .to_hex(), - total_script_nonce.to_hex(), - transaction.body.outputs()[0].commitment().to_hex(), - transaction.body.outputs()[0].sender_offset_public_key.to_hex(), - transaction.body.outputs()[0] - .metadata_signature - .ephemeral_commitment() - .to_hex(), - total_metadata_ephemeral_public_key.to_hex(), - transaction.body.outputs()[0].encrypted_data.to_hex(), - serde_json::to_string(&transaction.body.outputs()[0].features) - .unwrap_or("Could not serialize output features".to_string()) - ); - - let step_2_outputs_for_self = Step2OutputsForSelf { tx_id }; + let out_dir = out_dir(&args.session_id)?; + let step_3_outputs_for_self = Step3OutputsForSelf { tx_id }; + let out_file = out_dir.join(get_file_name(STEP_3_SELF, None)); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, step_3_outputs_for_self)?; - let step_2_outputs_for_parties = Step2OutputsForParties { + let step_3_outputs_for_parties = Step3OutputsForParties { input_commitment: transaction.body.inputs()[0].commitment().unwrap().clone(), input_stack: transaction.body.inputs()[0].clone().input_data, input_script: transaction.body.inputs()[0].script().unwrap().clone(), @@ -893,164 +920,52 @@ pub async fn command_runner( encrypted_data: transaction.body.outputs()[0].clone().encrypted_data, output_features: transaction.body.outputs()[0].clone().features, }; + let out_file = out_dir.join(get_file_name(STEP_3_PARTIES, None)); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, step_3_outputs_for_parties)?; }, Err(e) => println!("Encumber aggregate transaction error! {}", e), } - }, - FaucetSpendAggregateUtxo(args) => { - let mut offset = PrivateKey::default(); - for key in args.script_offset_keys { - let secret_key = - PrivateKey::from_hex(&key).map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - offset = &offset + &secret_key; - } - match finalise_aggregate_utxo( - transaction_service.clone(), - args.tx_id, - args.meta_signatures - .iter() - .map(|sgn| sgn.clone().into()) - .collect::>(), - args.script_signatures - .iter() - .map(|sgn| sgn.clone().into()) - .collect::>(), - offset, - ) - .await - { - Ok(_v) => println!("Transactions successfully completed"), - Err(e) => println!("Error completing transaction! {}", e), - } - }, - FaucetCreateScriptSig(args) => { - let script = TariScript::from_hex(&args.input_script) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let input_data = ExecutionStack::from_hex(&args.input_stack) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let commitment = Commitment::from_hex(&args.input_commitment) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let ephemeral_commitment = Commitment::from_hex(&args.script_ephemeral_commitment) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let ephemeral_pubkey = PublicKey::from(args.script_ephemeral_pubkey); - let challenge = TransactionInput::build_script_signature_challenge( - &TransactionInputVersion::get_current_version(), - &ephemeral_commitment, - &ephemeral_pubkey, - &script, - &input_data, - &args.total_script_key.into(), - &commitment, - ); - - match key_manager_service - .sign_with_nonce_and_message(&args.wallet_spend_key_id, &args.spend_nonce_key_id, &challenge) - .await - { - Ok(signature) => { - println!( - "Script signature created: - 1. signature: ({},{})", - signature.get_signature().to_hex(), - signature.get_public_nonce().to_hex(), - ) - }, - Err(e) => eprintln!("SignMessage error! {}", e), - } - }, - FaucetCreateMetaSig(args) => { - let offset = key_manager_service - .get_script_offset(&vec![args.wallet_spend_key_id], &vec![args - .sender_offset_key_id - .clone()]) - .await?; - let script = script!(PushPubKey(Box::new(args.recipient_address.public_spend_key().clone()))); - let commitment = Commitment::from_hex(&args.output_commitment) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let covenant = Covenant::default(); - let encrypted_data = EncryptedData::from_hex(&args.encrypted_data) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let output_features = serde_json::from_str(&args.output_features) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let ephemeral_commitment = Commitment::from_hex(&args.metadata_ephemeral_commitment) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let ephemeral_pubkey = PublicKey::from_hex(&args.metadata_ephemeral_pubkey) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let minimum_value_promise = MicroMinotari::zero(); - trace!( - target: LOG_TARGET, - "version: {:?}", - TransactionOutputVersion::get_current_version() - ); - trace!(target: LOG_TARGET, "script: {:?}", script); - trace!(target: LOG_TARGET, "output features: {:?}", output_features); - let offsetkey: PublicKey = args.total_metadata_key.clone().into(); - trace!(target: LOG_TARGET, "sender_offset_public_key: {:?}", offsetkey); - trace!(target: LOG_TARGET, "ephemeral_commitment: {:?}", ephemeral_commitment); - trace!(target: LOG_TARGET, "ephemeral_pubkey: {:?}", ephemeral_pubkey); - trace!(target: LOG_TARGET, "commitment: {:?}", commitment); - trace!(target: LOG_TARGET, "covenant: {:?}", covenant); - trace!(target: LOG_TARGET, "encrypted_value: {:?}", encrypted_data); - trace!(target: LOG_TARGET, "minimum_value_promise: {:?}", minimum_value_promise); - let challenge = TransactionOutput::build_metadata_signature_challenge( - &TransactionOutputVersion::get_current_version(), - &script, - &output_features, - &args.total_metadata_key.into(), - &ephemeral_commitment, - &ephemeral_pubkey, - &commitment, - &covenant, - &encrypted_data, - minimum_value_promise, - ); - trace!(target: LOG_TARGET, "meta challenge: {:?}", challenge); - match key_manager_service - .sign_with_nonce_and_message( - &args.sender_offset_key_id, - &args.sender_offset_nonce_key_id, - &challenge, - ) - .await - { - Ok(signature) => { - println!( - "Metadata signature created: - 1. signature: ({},{}), - 2. script offset: {}", - signature.get_signature().to_hex(), - signature.get_public_nonce().to_hex(), - offset.to_hex(), - ) - }, - Err(e) => eprintln!("SignMessage error! {}", e), - } + println!(); + println!("Concluded step 3 'faucet-encumber-aggregate-utxo'"); + println!("Send '{}' to parties for step 4", get_file_name(STEP_3_PARTIES, None)); + println!(); }, FaucetCreateInputOutputSigs(args) => { + // Read session info + let session_info = read_session_info(&args.session_id, None)?; + // Read leader input + let leader_info = read_and_verify::( + &args.session_id, + &get_file_name(STEP_3_PARTIES, None), + &session_info, + )?; + // Read own party info + let party_info = read_and_verify::( + &args.session_id, + &get_file_name(STEP_2_SELF, None), + &session_info, + )?; + // Script signature - let script = TariScript::from_hex(&args.input_script) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let input_data = ExecutionStack::from_hex(&args.input_stack) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let commitment = Commitment::from_hex(&args.input_commitment) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let ephemeral_commitment = Commitment::from_hex(&args.script_ephemeral_commitment) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let ephemeral_pubkey = PublicKey::from(args.script_ephemeral_pubkey); let challenge = TransactionInput::build_script_signature_challenge( &TransactionInputVersion::get_current_version(), - &ephemeral_commitment, - &ephemeral_pubkey, - &script, - &input_data, - &args.total_script_key.into(), - &commitment, + &leader_info.script_signature_ephemeral_commitment, + &leader_info.script_signature_ephemeral_pubkey, + &leader_info.input_script, + &leader_info.input_stack, + &leader_info.total_script_key, + &leader_info.input_commitment, ); let mut script_signature = Signature::default(); match key_manager_service - .sign_with_nonce_and_message(&args.wallet_spend_key_id, &args.spend_nonce_key_id, &challenge) + .sign_with_nonce_and_message( + &party_info.wallet_spend_key_id, + &party_info.script_nonce_key_id, + &challenge, + ) .await { Ok(signature) => { @@ -1060,58 +975,31 @@ pub async fn command_runner( } // Metadata signature - let offset = key_manager_service - .get_script_offset(&vec![args.wallet_spend_key_id], &vec![args + let script_offset = key_manager_service + .get_script_offset(&vec![party_info.wallet_spend_key_id], &vec![party_info .sender_offset_key_id .clone()]) .await?; - let script = script!(PushPubKey(Box::new(args.recipient_address.public_spend_key().clone()))); - let commitment = Commitment::from_hex(&args.output_commitment) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let covenant = Covenant::default(); - let encrypted_data = EncryptedData::from_hex(&args.encrypted_data) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let output_features = serde_json::from_str(&args.output_features) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let ephemeral_commitment = Commitment::from_hex(&args.metadata_ephemeral_commitment) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let ephemeral_pubkey = PublicKey::from_hex(&args.metadata_ephemeral_pubkey) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?; - let minimum_value_promise = MicroMinotari::zero(); - trace!( - target: LOG_TARGET, - "version: {:?}", - TransactionOutputVersion::get_current_version() - ); - trace!(target: LOG_TARGET, "script: {:?}", script); - trace!(target: LOG_TARGET, "output features: {:?}", output_features); - let offsetkey: PublicKey = args.total_metadata_key.clone().into(); - trace!(target: LOG_TARGET, "sender_offset_public_key: {:?}", offsetkey); - trace!(target: LOG_TARGET, "ephemeral_commitment: {:?}", ephemeral_commitment); - trace!(target: LOG_TARGET, "ephemeral_pubkey: {:?}", ephemeral_pubkey); - trace!(target: LOG_TARGET, "commitment: {:?}", commitment); - trace!(target: LOG_TARGET, "covenant: {:?}", covenant); - trace!(target: LOG_TARGET, "encrypted_value: {:?}", encrypted_data); - trace!(target: LOG_TARGET, "minimum_value_promise: {:?}", minimum_value_promise); let challenge = TransactionOutput::build_metadata_signature_challenge( &TransactionOutputVersion::get_current_version(), - &script, - &output_features, - &args.total_metadata_key.into(), - &ephemeral_commitment, - &ephemeral_pubkey, - &commitment, - &covenant, - &encrypted_data, - minimum_value_promise, + &script!(PushPubKey(Box::new( + session_info.recipient_address.public_spend_key().clone() + ))), + &leader_info.output_features, + &leader_info.sender_offset_pubkey, + &leader_info.metadata_signature_ephemeral_commitment, + &leader_info.metadata_signature_ephemeral_pubkey, + &leader_info.output_commitment, + &Covenant::default(), + &leader_info.encrypted_data, + MicroMinotari::zero(), ); - trace!(target: LOG_TARGET, "meta challenge: {:?}", challenge); let mut metadata_signature = Signature::default(); match key_manager_service .sign_with_nonce_and_message( - &args.sender_offset_key_id, - &args.sender_offset_nonce_key_id, + &party_info.sender_offset_key_id, + &party_info.sender_offset_nonce_key_id, &challenge, ) .await @@ -1127,17 +1015,64 @@ pub async fn command_runner( { eprintln!("Script and/or metadata signatures not created") } else { + let step_4_outputs_for_leader = Step4OutputsForLeader { + script_signature, + metadata_signature, + script_offset, + }; + + let out_dir = out_dir(&args.session_id)?; + let out_file = out_dir.join(get_file_name(STEP_4_LEADER, Some(party_info.alias.clone()))); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, step_4_outputs_for_leader)?; + + println!(); + println!("Concluded step 4 'faucet-create-input-output-sigs'"); println!( - "Input and output signatures created: - 1. script signature: ({},{}) - 1. metadata signature: ({},{}) - 2. script offset: {}", - script_signature.get_signature().to_hex(), - script_signature.get_public_nonce().to_hex(), - metadata_signature.get_signature().to_hex(), - metadata_signature.get_public_nonce().to_hex(), - offset.to_hex(), + "Send '{}' to leader for step 5", + get_file_name(STEP_4_LEADER, Some(party_info.alias)) ); + println!(); + } + }, + FaucetSpendAggregateUtxo(args) => { + // Read session info + let session_info = read_session_info(&args.session_id, None)?; + + let mut metadata_signatures = Vec::with_capacity(args.input_file_names.len()); + let mut script_signatures = Vec::with_capacity(args.input_file_names.len()); + let mut offset = PrivateKey::default(); + for file_name in args.input_file_names { + // Read party input + let party_info = + read_and_verify::(&args.session_id, &file_name, &session_info)?; + metadata_signatures.push(party_info.metadata_signature); + script_signatures.push(party_info.script_signature); + offset = &offset + &party_info.script_offset; + } + + // Read own party info + let leader_info = read_and_verify::( + &args.session_id, + &get_file_name(STEP_3_SELF, None), + &session_info, + )?; + + match finalise_aggregate_utxo( + transaction_service.clone(), + leader_info.tx_id.as_u64(), + metadata_signatures, + script_signatures, + offset, + ) + .await + { + Ok(_v) => { + println!(); + println!("Concluded step 5 'faucet-spend-aggregate-utxo'"); + println!(); + }, + Err(e) => println!("Error completing transaction! {}", e), } }, SendMinotari(args) => { diff --git a/applications/minotari_console_wallet/src/automation/mod.rs b/applications/minotari_console_wallet/src/automation/mod.rs index 13da4f173f..c29ca35d4b 100644 --- a/applications/minotari_console_wallet/src/automation/mod.rs +++ b/applications/minotari_console_wallet/src/automation/mod.rs @@ -22,49 +22,63 @@ pub mod commands; pub mod error; +mod utils; // removed temporarily add back in when used. // mod prompt; use serde::{Deserialize, Serialize}; use tari_common_types::{ + tari_address::TariAddress, transaction::TxId, types::{Commitment, PrivateKey, PublicKey, Signature}, }; use tari_core::transactions::{ key_manager::TariKeyId, + tari_amount::MicroMinotari, transaction_components::{EncryptedData, OutputFeatures}, }; use tari_script::{CheckSigSchnorrSignature, ExecutionStack, TariScript}; -/// Outputs for self with `FaucetCreatePartyDetails` +// Outputs for self with `FaucetCreatePartyDetails` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -struct Step1OutputsForSelf { +struct Step1SessionInfo { + session_id: String, + fee_per_gram: MicroMinotari, + commitment_to_spend: String, + output_hash: String, + recipient_address: TariAddress, +} + +// Outputs for self with `FaucetCreatePartyDetails` +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step2OutputsForSelf { + alias: String, wallet_spend_key_id: TariKeyId, script_nonce_key_id: TariKeyId, sender_offset_key_id: TariKeyId, sender_offset_nonce_key_id: TariKeyId, } -/// Outputs for leader with `FaucetCreatePartyDetails` +// Outputs for leader with `FaucetCreatePartyDetails` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -struct Step1OutputsForLeader { +struct Step2OutputsForLeader { script_input_signature: CheckSigSchnorrSignature, wallet_public_spend_key: PublicKey, public_script_nonce_key: PublicKey, public_sender_offset_key: PublicKey, public_sender_offset_nonce_key: PublicKey, - shared_secret_public_key: PublicKey, + dh_shared_secret_public_key: PublicKey, } -/// Outputs for self with `FaucetEncumberAggregateUtxo` +// Outputs for self with `FaucetEncumberAggregateUtxo` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -struct Step2OutputsForSelf { +struct Step3OutputsForSelf { tx_id: TxId, } -/// Outputs for parties with `FaucetEncumberAggregateUtxo` +// Outputs for parties with `FaucetEncumberAggregateUtxo` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -struct Step2OutputsForParties { +struct Step3OutputsForParties { input_commitment: Commitment, input_stack: ExecutionStack, input_script: TariScript, @@ -79,9 +93,9 @@ struct Step2OutputsForParties { output_features: OutputFeatures, } -/// Outputs for leader with `FaucetCreateScriptSig` and `FaucetCreateMetaSig` +// Outputs for leader with `FaucetCreateScriptSig` and `FaucetCreateMetaSig` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -struct Step3OutputsForLeader { +struct Step4OutputsForLeader { script_signature: Signature, metadata_signature: Signature, script_offset: PrivateKey, diff --git a/applications/minotari_console_wallet/src/automation/utils.rs b/applications/minotari_console_wallet/src/automation/utils.rs new file mode 100644 index 0000000000..c0a7337219 --- /dev/null +++ b/applications/minotari_console_wallet/src/automation/utils.rs @@ -0,0 +1,217 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + fs, + fs::{File, OpenOptions}, + io::{BufRead, BufReader, Write}, + path::{Path, PathBuf}, +}; + +use serde::{de::DeserializeOwned, Serialize}; + +use crate::automation::{ + commands::{FILE_EXTENSION, SESSION_INFO}, + error::CommandError, + Step1SessionInfo, +}; + +#[derive(Debug)] +pub(crate) struct PartialRead { + pub(crate) lines_to_read: usize, + pub(crate) lines_to_skip: usize, +} + +/// Reads an entire file into a single JSON object +pub(crate) fn json_from_file_single_object, T: DeserializeOwned>( + path: P, + partial_read: Option, +) -> Result { + if let Some(val) = partial_read { + let lines = BufReader::new( + File::open(path.as_ref()) + .map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display())))?, + ) + .lines() + .take(val.lines_to_read) + .skip(val.lines_to_skip); + let mut json_str = String::new(); + for line in lines { + let line = line.map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display())))?; + json_str.push_str(&line); + } + serde_json::from_str(&json_str) + .map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display()))) + } else { + serde_json::from_reader(BufReader::new( + File::open(path.as_ref()) + .map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display())))?, + )) + .map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display()))) + } +} + +/// Write a single JSON object to file as a single line +pub(crate) fn write_json_object_to_file_as_line( + file: &Path, + reset_file: bool, + outputs: T, +) -> Result<(), CommandError> { + if let Some(file_path) = file.parent() { + if !file_path.exists() { + fs::create_dir_all(file_path).map_err(|e| CommandError::JsonFile(format!("{} ({})", e, file.display())))?; + } + } + if reset_file && file.exists() { + fs::remove_file(file).map_err(|e| CommandError::JsonFile(e.to_string()))?; + } + append_json_line_to_file(file, outputs)?; + Ok(()) +} + +fn append_json_line_to_file, T: Serialize>(file: P, output: T) -> Result<(), CommandError> { + fs::create_dir_all(file.as_ref().parent().unwrap()).map_err(|e| CommandError::JsonFile(e.to_string()))?; + let mut file_object = OpenOptions::new() + .create(true) + .append(true) + .open(file) + .map_err(|e| CommandError::JsonFile(e.to_string()))?; + let json = serde_json::to_string(&output).map_err(|e| CommandError::JsonFile(e.to_string()))?; + writeln!(file_object, "{json}").map_err(|e| CommandError::JsonFile(e.to_string()))?; + Ok(()) +} + +/// Write outputs to a JSON file +pub(crate) fn write_to_json_file(file: &Path, reset_file: bool, data: T) -> Result<(), CommandError> { + if let Some(file_path) = file.parent() { + if !file_path.exists() { + fs::create_dir_all(file_path).map_err(|e| CommandError::JsonFile(format!("{} ({})", e, file.display())))?; + } + } + if reset_file && file.exists() { + fs::remove_file(file).map_err(|e| CommandError::JsonFile(e.to_string()))?; + } + append_to_json_file(file, data)?; + Ok(()) +} + +fn append_to_json_file, T: Serialize>(file: P, data: T) -> Result<(), CommandError> { + fs::create_dir_all(file.as_ref().parent().unwrap()).map_err(|e| CommandError::JsonFile(e.to_string()))?; + let mut file_object = OpenOptions::new() + .create(true) + .append(true) + .open(file) + .map_err(|e| CommandError::JsonFile(e.to_string()))?; + let json = serde_json::to_string_pretty(&data).map_err(|e| CommandError::JsonFile(e.to_string()))?; + writeln!(file_object, "{json}").map_err(|e| CommandError::JsonFile(e.to_string()))?; + Ok(()) +} + +/// Return the output directory for the session +pub(crate) fn out_dir(session_id: &str) -> Result { + let base_dir = dirs::cache_dir().ok_or(CommandError::InvalidArgument( + "Could not find cache directory".to_string(), + ))?; + Ok(base_dir.join(session_id)) +} + +/// Move the session file to the session directory +pub(crate) fn move_session_file_to_session_dir(session_id: &str, input_file: &PathBuf) -> Result<(), CommandError> { + let out_dir = out_dir(session_id)?; + let session_file = out_dir.join(get_file_name(SESSION_INFO, None)); + if input_file != &session_file { + fs::copy(input_file.clone(), session_file.clone())?; + fs::remove_file(input_file.clone())?; + println!( + "Session info file '{}' moved to '{}'", + input_file.display(), + session_file.display() + ); + } + Ok(()) +} + +/// Read the session info from the session directory +pub(crate) fn read_session_info( + session_id: &str, + session_file: Option, +) -> Result { + let file_path = if let Some(file) = session_file { + file + } else { + out_dir(session_id)?.join(get_file_name(SESSION_INFO, None)) + }; + let session_info = json_from_file_single_object::<_, Step1SessionInfo>(&file_path, None)?; + if session_info.session_id != session_id { + return Err(CommandError::InvalidArgument(format!( + "Session ID in session info file '{}' mismatch", + get_file_name(SESSION_INFO, None) + ))); + } + Ok(session_info) +} + +/// Read the inputs from the session directory and verify the header +pub(crate) fn read_and_verify( + session_id: &str, + file_name: &str, + session_info: &Step1SessionInfo, +) -> Result { + let out_dir = out_dir(session_id)?; + let header = json_from_file_single_object::<_, Step1SessionInfo>( + &out_dir.join(file_name), + Some(PartialRead { + lines_to_read: 1, + lines_to_skip: 0, + }), + )?; + if session_id != header.session_id { + return Err(CommandError::InvalidArgument(format!( + "Session ID in header for file '{}' mismatch", + file_name + ))); + } + if session_info != &header { + return Err(CommandError::InvalidArgument(format!( + "Session info in header for file '{}' mismatch", + file_name + ))); + } + json_from_file_single_object::<_, T>( + &out_dir.join(file_name), + Some(PartialRead { + lines_to_read: usize::MAX, + lines_to_skip: 1, + }), + ) +} + +/// Create the file name with the given stem and optional suffix +pub(crate) fn get_file_name(stem: &str, suffix: Option) -> String { + let mut file_name = stem.to_string(); + if let Some(suffix) = suffix { + file_name.push_str(&suffix); + } + file_name.push('.'); + file_name.push_str(FILE_EXTENSION); + file_name +} diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index 1354ba18b3..0fa3dedf87 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -28,14 +28,11 @@ use std::{ use chrono::{DateTime, Utc}; use clap::{Args, Parser, Subcommand}; -use minotari_app_utilities::{ - common_cli_args::CommonCliArgs, - utilities::{UniPublicKey, UniSignature}, -}; +use minotari_app_utilities::{common_cli_args::CommonCliArgs, utilities::UniPublicKey}; use tari_common::configuration::{ConfigOverrideProvider, Network}; use tari_common_types::tari_address::TariAddress; use tari_comms::multiaddr::Multiaddr; -use tari_core::transactions::{key_manager::TariKeyId, tari_amount, tari_amount::MicroMinotari}; +use tari_core::transactions::{tari_amount, tari_amount::MicroMinotari}; use tari_key_manager::SeedWords; use tari_utilities::{ hex::{Hex, HexError}, @@ -119,12 +116,11 @@ pub enum CliCommands { GetBalance, SendMinotari(SendMinotariArgs), BurnMinotari(BurnMinotariArgs), - FaucetEncumberAggregateUtxo(FaucetEncumberAggregateUtxoArgs), - FaucetSpendAggregateUtxo(FaucetSpendAggregateUtxoArgs), + FaucetGenerateSessionInfo(FaucetGenerateSessionInfoArgs), FaucetCreatePartyDetails(FaucetCreatePartyDetailsArgs), - FaucetCreateScriptSig(FaucetCreateScriptSigArgs), - FaucetCreateMetaSig(FaucetCreateMetaSigArgs), + FaucetEncumberAggregateUtxo(FaucetEncumberAggregateUtxoArgs), FaucetCreateInputOutputSigs(FaucetCreateInputOutputSigArgs), + FaucetSpendAggregateUtxo(FaucetSpendAggregateUtxoArgs), SendOneSidedToStealthAddress(SendMinotariArgs), MakeItRain(MakeItRainArgs), CoinSplit(CoinSplitArgs), @@ -167,47 +163,7 @@ pub struct BurnMinotariArgs { } #[derive(Debug, Args, Clone)] -pub struct FaucetCreateKeyPairArgs { - #[clap(long)] - pub key_branch: String, -} - -#[derive(Debug, Args, Clone)] -pub struct FaucetCreateAggregateSignatureUtxoArgs { - #[clap(long)] - pub amount: MicroMinotari, - #[clap(long)] - pub fee_per_gram: MicroMinotari, - #[clap(long)] - pub n: u8, - #[clap(long)] - pub m: u8, - #[clap(long)] - pub message: String, - #[clap(long)] - pub maturity: u64, - #[clap(long)] - pub public_keys: Vec, -} - -#[derive(Debug, Args, Clone)] -pub struct FaucetCreatePartyDetailsArgs { - #[clap(long)] - pub commitment: String, - #[clap(long)] - pub recipient_address: TariAddress, -} - -#[derive(Debug, Args, Clone)] -pub struct FaucetSignMessageArgs { - #[clap(long)] - pub private_key_id: TariKeyId, - #[clap(long)] - pub challenge: String, -} - -#[derive(Debug, Args, Clone)] -pub struct FaucetEncumberAggregateUtxoArgs { +pub struct FaucetGenerateSessionInfoArgs { #[clap(long)] pub fee_per_gram: MicroMinotari, #[clap(long)] @@ -215,115 +171,39 @@ pub struct FaucetEncumberAggregateUtxoArgs { #[clap(long)] pub output_hash: String, #[clap(long)] - pub script_input_shares: Vec, - #[clap(long)] - pub script_public_key_shares: Vec, - #[clap(long)] - pub script_signature_public_nonces: Vec, - #[clap(long)] - pub sender_offset_public_key_shares: Vec, - #[clap(long)] - pub metadata_ephemeral_public_key_shares: Vec, - #[clap(long)] - pub dh_shared_secret_shares: Vec, - #[clap(long)] pub recipient_address: TariAddress, } #[derive(Debug, Args, Clone)] -pub struct FaucetSpendAggregateUtxoArgs { - #[clap(long)] - pub tx_id: u64, +pub struct FaucetCreatePartyDetailsArgs { #[clap(long)] - pub meta_signatures: Vec, + pub session_id: String, #[clap(long)] - pub script_signatures: Vec, + pub input_file: PathBuf, #[clap(long)] - pub script_offset_keys: Vec, + pub alias: String, } #[derive(Debug, Args, Clone)] -pub struct FaucetCreateScriptSigArgs { - #[clap(long)] - pub wallet_spend_key_id: TariKeyId, - #[clap(long)] - pub spend_nonce_key_id: TariKeyId, - #[clap(long)] - pub input_script: String, - #[clap(long)] - pub input_stack: String, - #[clap(long)] - pub script_ephemeral_commitment: String, - #[clap(long)] - pub script_ephemeral_pubkey: UniPublicKey, +pub struct FaucetEncumberAggregateUtxoArgs { #[clap(long)] - pub total_script_key: UniPublicKey, + pub session_id: String, #[clap(long)] - pub input_commitment: String, + pub input_file_names: Vec, } #[derive(Debug, Args, Clone)] -pub struct FaucetCreateMetaSigArgs { - #[clap(long)] - pub wallet_spend_key_id: TariKeyId, - #[clap(long)] - pub sender_offset_key_id: TariKeyId, - #[clap(long)] - pub sender_offset_nonce_key_id: TariKeyId, - #[clap(long)] - pub metadata_ephemeral_commitment: String, - #[clap(long)] - pub metadata_ephemeral_pubkey: String, - #[clap(long)] - pub total_metadata_key: UniPublicKey, - #[clap(long)] - pub output_commitment: String, - #[clap(long)] - pub encrypted_data: String, - #[clap(long)] - pub output_features: String, +pub struct FaucetCreateInputOutputSigArgs { #[clap(long)] - pub recipient_address: TariAddress, + pub session_id: String, } #[derive(Debug, Args, Clone)] -pub struct FaucetCreateInputOutputSigArgs { - #[clap(long)] - pub wallet_spend_key_id: TariKeyId, - - #[clap(long)] - pub spend_nonce_key_id: TariKeyId, - #[clap(long)] - pub input_script: String, - #[clap(long)] - pub input_stack: String, - #[clap(long)] - pub script_ephemeral_commitment: String, - #[clap(long)] - pub script_ephemeral_pubkey: UniPublicKey, - #[clap(long)] - pub total_script_key: UniPublicKey, - #[clap(long)] - pub input_commitment: String, - - #[clap(long)] - pub sender_offset_key_id: TariKeyId, - #[clap(long)] - pub sender_offset_nonce_key_id: TariKeyId, - #[clap(long)] - pub metadata_ephemeral_commitment: String, - #[clap(long)] - pub metadata_ephemeral_pubkey: String, - #[clap(long)] - pub total_metadata_key: UniPublicKey, - #[clap(long)] - pub output_commitment: String, - #[clap(long)] - pub encrypted_data: String, +pub struct FaucetSpendAggregateUtxoArgs { #[clap(long)] - pub output_features: String, + pub session_id: String, #[clap(long)] - pub recipient_address: TariAddress, + pub input_file_names: Vec, } #[derive(Debug, Args, Clone)] diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index 5ff570c7ce..f4ea2cdf29 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -168,14 +168,14 @@ pub(crate) fn command_mode( } fn force_exit_for_faucet_commands(command: &CliCommands) -> bool { - match command { - CliCommands::FaucetEncumberAggregateUtxo(_) | - CliCommands::FaucetSpendAggregateUtxo(_) | - CliCommands::FaucetCreatePartyDetails(_) | - CliCommands::FaucetCreateScriptSig(_) | - CliCommands::FaucetCreateMetaSig(_) => true, - _ => false, - } + matches!( + command, + CliCommands::FaucetGenerateSessionInfo(_) | + CliCommands::FaucetEncumberAggregateUtxo(_) | + CliCommands::FaucetSpendAggregateUtxo(_) | + CliCommands::FaucetCreatePartyDetails(_) | + CliCommands::FaucetCreateInputOutputSigs(_) + ) } pub(crate) fn parse_command_file(script: String) -> Result, ExitError> { @@ -514,7 +514,8 @@ mod test { #[test] #[allow(clippy::too_many_lines)] fn clap_parses_user_defined_commands_as_expected() { - let script = " + let script = + " # Beginning of script file get-balance @@ -524,87 +525,30 @@ mod test { discover-peer f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 send-minotari --message Our_secret! 125T \ - f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb + f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb burn-minotari --message Ups_these_funds_will_be_burned! 100T - faucet-encumber-aggregate-utxo \ - --fee-per-gram 1 \ - --output-hash f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --script-input-shares=3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03,8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --script-input-shares=3edf1ed103b0ac0bbad6a6de8369808d14dfdaaf294fe660646875d749a1f908,50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c \ - --script-public-key-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --script-public-key-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --script-signature-public-nonces=8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --script-signature-public-nonces=50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c \ - --sender-offset-public-key-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --sender-offset-public-key-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --metadata-ephemeral-public-key-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --metadata-ephemeral-public-key-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --dh-shared-secret-shares=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --dh-shared-secret-shares=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --recipient-address f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA - - faucet-spend-aggregate-utxo \ - --tx-id 12345678 \ - --meta-signatures=3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03,8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --meta-signatures=3edf1ed103b0ac0bbad6a6de8369808d14dfdaaf294fe660646875d749a1f908,50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c \ - --script-signatures=3ddde10d0775c20fb25015546c6a8068812044e7ca4ee1057e84ec9ab6705d03,8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --script-signatures=3edf1ed103b0ac0bbad6a6de8369808d14dfdaaf294fe660646875d749a1f908,50a26c646db951720c919f59cd7a34600a7fc3ee978c64fbcce0ad184c46844c \ - --script-offset-keys=5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --script-offset-keys=f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 - - faucet-create-party-details \ - --commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --recipient-address f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA - - faucet-create-script-sig \ - --wallet-spend-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --spend-nonce-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --input-script ae010268593ed2d36a2d95f0ffe0f41649b97cc36fc4ef0c8ecd6bd28f9d56c76b793b08691435a5c813578f8a7f4973166dc1c6c15f37aec2a7d65b1583c8b2129364c916d5986a0c1b3dac7d6efb94bed688ba52fa8b962cf27c0446e2fea6d66a04 \ - --input-stack 050857c14f72cf885aac9f08c9484cb7cb06b6cc20eab68c9bee1e8d5a85649b0a6d31c5cc49afc1e03ebbcf55c82f47e8cbc796c33e96c17a31eab027ee821f00 \ - --script-ephemeral-commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --script-ephemeral-pubkey 8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --total-script-key 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --input-commitment 94966b4f1b5dc050df1109cf07a516ae85912c82503b1a8c1625986a569fae67 - - faucet-create-meta-sig \ - --wallet-spend-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --sender-offset-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --sender-offset-nonce-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --metadata-ephemeral-commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --metadata-ephemeral-pubkey 8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --total-metadata-key 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --output-commitment 94966b4f1b5dc050df1109cf07a516ae85912c82503b1a8c1625986a569fae67 \ - --encrypted-data 6a7aa2053ae187f60f27df0e10184bf93d02a84cd9548320ec7da546185fc23c6daa720974007c6106cfb0361eb9828e1af979b69fff724d2bcd0d86d5b9675ef1f65b424b22bee06e52fcaf4fd2a2ed \ - --output-features 'features' \ - --recipient-address f4FB7HhYCmLw4PsivjG8bAgUuxyPS6GTjFkhMWx6d9Nv4aoBESyaH5TdS1dAkSCg4qXqehpjZU9QrSUP2Ec7v4Gj8wf - - faucet-create-input-output-sigs \ - --wallet-spend-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --spend-nonce-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --input-script ae010268593ed2d36a2d95f0ffe0f41649b97cc36fc4ef0c8ecd6bd28f9d56c76b793b08691435a5c813578f8a7f4973166dc1c6c15f37aec2a7d65b1583c8b2129364c916d5986a0c1b3dac7d6efb94bed688ba52fa8b962cf27c0446e2fea6d66a04 \ - --input-stack 050857c14f72cf885aac9f08c9484cb7cb06b6cc20eab68c9bee1e8d5a85649b0a6d31c5cc49afc1e03ebbcf55c82f47e8cbc796c33e96c17a31eab027ee821f00 \ - --script-ephemeral-commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --script-ephemeral-pubkey 8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --total-script-key 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --input-commitment 94966b4f1b5dc050df1109cf07a516ae85912c82503b1a8c1625986a569fae67 \ - --sender-offset-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --sender-offset-nonce-key-id imported.96159b07298a453c9f514f5307f70659c7561dd6d9ed376854c5cb573cb2e311 \ - --metadata-ephemeral-commitment f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 \ - --metadata-ephemeral-pubkey 8a55d1cb503be36875d38f2dc6abac7b23445bbd7253684a1506f5ee1855cd58 \ - --total-metadata-key 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d61 \ - --output-commitment 94966b4f1b5dc050df1109cf07a516ae85912c82503b1a8c1625986a569fae67 \ - --encrypted-data 6a7aa2053ae187f60f27df0e10184bf93d02a84cd9548320ec7da546185fc23c6daa720974007c6106cfb0361eb9828e1af979b69fff724d2bcd0d86d5b9675ef1f65b424b22bee06e52fcaf4fd2a2ed \ - --output-features 'features' \ - --recipient-address f4FB7HhYCmLw4PsivjG8bAgUuxyPS6GTjFkhMWx6d9Nv4aoBESyaH5TdS1dAkSCg4qXqehpjZU9QrSUP2Ec7v4Gj8wf + faucet-generate-session-info --fee-per-gram 2 --commitment \ + f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 --output-hash \ + f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 --recipient-address \ + f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA + + faucet-create-party-details --session-id ee1643655c --input-file ./step_1_session_info.txt --alias alice + + faucet-encumber-aggregate-utxo --session-id ee1643655c --input-file-names=step_2_for_leader_from_alice.txt \ + --input-file-names=step_2_for_leader_from_bob.txt --input-file-names=step_2_for_leader_from_carol.txt + + faucet-create-input-output-sigs --session-id ee1643655c + + faucet-spend-aggregate-utxo --session-id ee1643655c --input-file-names=step_2_for_leader_from_alice.txt \ + --input-file-names=step_2_for_leader_from_bob.txt --input-file-names=step_2_for_leader_from_carol.txt coin-split --message Make_many_dust_UTXOs! --fee-per-gram 2 0.001T 499 make-it-rain --duration 100 --transactions-per-second 10 --start-amount 0.009200T --increase-amount 0T \ - --start-time now --message Stressing_it_a_bit...!_(from_Feeling-a-bit-Generous) \ - f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb + --start-time now --message Stressing_it_a_bit...!_(from_Feeling-a-bit-Generous) \ + f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb export-tx 123456789 --output-file pie.txt @@ -612,18 +556,17 @@ mod test { # End of script file " - .to_string(); + .to_string(); let commands = parse_command_file(script).unwrap(); let mut get_balance = false; let mut send_tari = false; let mut burn_tari = false; + let mut faucet_generate_session_info = false; let mut faucet_encumber_aggregate_utxo = false; let mut faucet_spend_aggregate_utxo = false; let mut faucet_create_party_details = false; - let mut faucet_create_script_sig = false; - let mut faucet_create_meta_sig = false; let mut faucet_create_input_output_sigs = false; let mut make_it_rain = false; let mut coin_split = false; @@ -636,11 +579,10 @@ mod test { CliCommands::GetBalance => get_balance = true, CliCommands::SendMinotari(_) => send_tari = true, CliCommands::BurnMinotari(_) => burn_tari = true, + CliCommands::FaucetGenerateSessionInfo(_) => faucet_generate_session_info = true, CliCommands::FaucetEncumberAggregateUtxo(_) => faucet_encumber_aggregate_utxo = true, CliCommands::FaucetSpendAggregateUtxo(_) => faucet_spend_aggregate_utxo = true, CliCommands::FaucetCreatePartyDetails(_) => faucet_create_party_details = true, - CliCommands::FaucetCreateScriptSig(_) => faucet_create_script_sig = true, - CliCommands::FaucetCreateMetaSig(_) => faucet_create_meta_sig = true, CliCommands::FaucetCreateInputOutputSigs(_) => faucet_create_input_output_sigs = true, CliCommands::SendOneSidedToStealthAddress(_) => {}, CliCommands::MakeItRain(_) => make_it_rain = true, @@ -675,11 +617,10 @@ mod test { get_balance && send_tari && burn_tari && + faucet_generate_session_info && faucet_encumber_aggregate_utxo && faucet_spend_aggregate_utxo && faucet_create_party_details && - faucet_create_script_sig && - faucet_create_meta_sig && faucet_create_input_output_sigs && make_it_rain && coin_split && diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index cef456bbae..df1f6ca951 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -64,7 +64,7 @@ pub enum OutputManagerRequest { EncumberAggregateUtxo { tx_id: TxId, fee_per_gram: MicroMinotari, - output_hash: String, + output_hash: HashOutput, expected_commitment: PedersenCommitment, script_input_shares: HashMap, script_signature_public_nonces: Vec, @@ -165,7 +165,7 @@ impl fmt::Display for OutputManagerRequest { "Encumber aggregate utxo with tx_id: {} and output: ({},{})", tx_id, expected_commitment.to_hex(), - output_hash + output_hash.to_hex() ), GetRecipientTransaction(_) => write!(f, "GetRecipientTransaction"), ConfirmPendingTransaction(v) => write!(f, "ConfirmPendingTransaction ({})", v), @@ -763,7 +763,7 @@ impl OutputManagerHandle { &mut self, tx_id: TxId, fee_per_gram: MicroMinotari, - output_hash: String, + output_hash: HashOutput, expected_commitment: PedersenCommitment, script_input_shares: HashMap, script_signature_public_nonces: Vec, diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index aefd16e3e0..39631d191d 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -32,7 +32,7 @@ use tari_common::configuration::Network; use tari_common_types::{ tari_address::TariAddress, transaction::TxId, - types::{BlockHash, Commitment, FixedHash, HashOutput, PrivateKey, PublicKey}, + types::{BlockHash, Commitment, HashOutput, PrivateKey, PublicKey}, }; use tari_comms::{types::CommsDHKE, NodeIdentity}; use tari_core::{ @@ -1179,7 +1179,7 @@ where &mut self, tx_id: TxId, fee_per_gram: MicroMinotari, - output_hash: String, + output_hash: HashOutput, expected_commitment: PedersenCommitment, mut script_input_shares: HashMap, script_signature_public_nonces: Vec, @@ -1203,13 +1203,17 @@ where OutputManagerError, > { // Fetch the output from the blockchain - let output_hash = - FixedHash::from_hex(&output_hash).map_err(|e| OutputManagerError::ConversionError(e.to_string()))?; let output = self .fetch_unspent_outputs_from_node(vec![output_hash]) .await? .pop() - .ok_or_else(|| OutputManagerError::ServiceError(format!("Output not found (TxId: {})", tx_id)))?; + .ok_or_else(|| { + OutputManagerError::ServiceError(format!( + "Output with hash {} not found in blockchain (TxId: {})", + output_hash.to_hex(), + tx_id + )) + })?; if output.commitment != expected_commitment { return Err(OutputManagerError::ServiceError(format!( "Output commitment does not match expected commitment (TxId: {})", diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 841c2fdfc7..1dc59bcb37 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -32,7 +32,7 @@ use tari_common_types::{ burnt_proof::BurntProof, tari_address::TariAddress, transaction::{ImportStatus, TxId}, - types::{FixedHash, PrivateKey, PublicKey, Signature}, + types::{FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, }; use tari_comms::types::CommsPublicKey; use tari_core::{ @@ -113,7 +113,7 @@ pub enum TransactionServiceRequest { }, EncumberAggregateUtxo { fee_per_gram: MicroMinotari, - output_hash: String, + output_hash: HashOutput, expected_commitment: PedersenCommitment, script_input_shares: HashMap, script_signature_public_nonces: Vec, @@ -244,7 +244,7 @@ impl fmt::Display for TransactionServiceRequest { script_input_shares = {:?},, script_signature_shares = {:?}, sender_offset_public_key_shares = {:?}, \ metadata_ephemeral_public_key_shares = {:?}, dh_shared_secret_shares = {:?}, recipient_address = {}", fee_per_gram, - output_hash, + output_hash.to_hex(), expected_commitment.to_hex(), script_input_shares .iter() @@ -730,7 +730,7 @@ impl TransactionServiceHandle { pub async fn encumber_aggregate_utxo( &mut self, fee_per_gram: MicroMinotari, - output_hash: String, + output_hash: HashOutput, expected_commitment: PedersenCommitment, script_input_shares: HashMap, script_signature_public_nonces: Vec, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index e13e0c5be3..abcbd3769f 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -38,7 +38,7 @@ use tari_common_types::{ burnt_proof::BurntProof, tari_address::{TariAddress, TariAddressFeatures}, transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, - types::{CommitmentFactory, FixedHash, PrivateKey, PublicKey, Signature}, + types::{CommitmentFactory, FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, }; use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_comms_dht::outbound::OutboundMessageRequester; @@ -1380,7 +1380,7 @@ where pub async fn encumber_aggregate_tx( &mut self, fee_per_gram: MicroMinotari, - output_hash: String, + output_hash: HashOutput, expected_commitment: PedersenCommitment, script_input_shares: HashMap, script_signature_public_nonces: Vec, diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 321b19a998..576a0cc4bf 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -640,7 +640,7 @@ struct ByteVector *public_key_get_bytes(TariPublicKey *pk, int *error_out); /** - * Converts public key to emoji encding + * Converts public key to emoji encoding * * ## Arguments * `pk` - The pointer to a TariPublicKey