diff --git a/src/network.rs b/src/network.rs index 09a4e18..6be4c6d 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1,6 +1,7 @@ +use std::default; use std::str::FromStr; -use aes_gcm::Aes256Gcm; +use aes_gcm::{AeadCore, Aes256Gcm}; use anyhow::{Error, Result}; use js_sys::Date; use rand::{thread_rng, RngCore}; @@ -13,7 +14,7 @@ use sp_client::bitcoin::{BlockHash, OutPoint, Transaction}; use sp_client::silentpayments::utils::SilentPaymentAddress; use tsify::Tsify; -use crate::crypto::{Aes256Decryption, Purpose}; +use crate::crypto::{Aes256Decryption, Aes256Encryption, Purpose}; use crate::error::AnkError; #[derive(Debug, Serialize, Deserialize, Tsify)] @@ -152,6 +153,8 @@ pub enum CachedMessageStatus { #[default] NoStatus, FaucetWaiting, + Pairing, + Login, CipherWaitingTx, TxWaitingCipher, SentWaitingConfirmation, // we're sender and wait for commited_in to be spent @@ -182,7 +185,7 @@ pub struct CachedMessage { pub ciphertext: Option, // Needed when we are waiting for a key in transaction, discarded after pub plaintext: Vec, // Append new messages received while in Trusted state pub commited_in: Option, - pub tie_by: Option, // index of the output that ties the proposal + pub tied_by: Option, // index of the output that ties the proposal pub commitment: Option, // content of the op_return pub sender: Option, // Never None when message sent pub recipient: Option, // Never None when message sent @@ -213,9 +216,9 @@ impl CachedMessage { } pub fn try_decrypt_cipher(&self, cipher: Vec) -> Result> { - if self.ciphertext.is_some() || self.shared_secret.is_none() { + if self.shared_secret.is_none() { return Err(Error::msg( - "Can't try decrypt this message, there's already a ciphertext or no shared secret", + "Can't try decrypt this message, there's no shared secret", )); } let mut shared_secret = [0u8; 32]; @@ -285,4 +288,20 @@ impl TrustedChannel { Ok(()) } + + pub fn is_set(&self) -> bool { + self.revokation_outpoint == OutPoint::default() + } + + pub fn is_revoked(&self) -> bool { + self.revoked_in_block != [0u8; 32] + } + + pub fn encrypt_msg_for(&self, msg: String) -> Result { + let mut rng = thread_rng(); + let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into(); + let aes256_encryption = Aes256Encryption::import_key(Purpose::Arbitrary, msg.into_bytes(), self.shared_secret, nonce)?; + let cipher = aes256_encryption.encrypt_with_aes_key()?; + Ok(cipher.to_lower_hex_string()) + } } diff --git a/src/silentpayments.rs b/src/silentpayments.rs index 750c91c..b1088de 100644 --- a/src/silentpayments.rs +++ b/src/silentpayments.rs @@ -1,61 +1,75 @@ -use std::collections::HashMap; -use std::io::Write; +use std::collections::{HashMap, HashSet}; use std::str::FromStr; use anyhow::{Error, Result}; use rand::{thread_rng, Rng}; use sp_client::bitcoin::consensus::deserialize; -use sp_client::bitcoin::hashes::{sha256, Hash}; -use sp_client::bitcoin::hex::FromHex; use sp_client::bitcoin::key::{Keypair, Secp256k1, TapTweak}; use sp_client::bitcoin::psbt::{raw, Output}; -use sp_client::bitcoin::secp256k1::SecretKey; use sp_client::bitcoin::{Address, Psbt, ScriptBuf, Transaction, Txid}; use sp_client::bitcoin::{Amount, OutPoint, TxOut}; use sp_client::constants::{ self, DUST_THRESHOLD, PSBT_SP_ADDRESS_KEY, PSBT_SP_PREFIX, PSBT_SP_SUBTYPE, }; -use sp_client::silentpayments::utils::sending::calculate_ecdh_shared_secret; use sp_client::silentpayments::utils::SilentPaymentAddress; use sp_client::spclient::{OwnedOutput, Recipient, SpClient, SpWallet}; -use crate::crypto::AnkSharedSecret; - pub fn create_transaction( mandatory_inputs: &[&OutPoint], + freezed_utxos: &HashSet, sp_wallet: &SpWallet, - mut recipient: Recipient, + mut recipients: Vec, payload: Option>, fee_rate: Amount, fee_payer: Option, // None means sender pays everything ) -> Result { - let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); - let recipient_address = SilentPaymentAddress::try_from(recipient.address.as_str())?; + let mut available_outpoints: HashMap = sp_wallet + .get_outputs() + .to_spendable_list() + // filter out freezed utxos + .into_iter() + .filter(|(outpoint, _)| { + !freezed_utxos.contains(outpoint) + }) + .collect(); + + // if we have a payload, it means we are notifying, so let's add a revokation output + if payload.is_some() { + recipients.push(Recipient { + address: sp_wallet.get_client().get_receiving_address(), + amount: DUST_THRESHOLD, + nb_outputs: 1 + }) + } + + let sum_outputs = recipients.iter().fold(Amount::from_sat(0), |acc, x| acc + x.amount); + + let zero_value_recipient = recipients.iter_mut().find(|r| r.amount == Amount::from_sat(0)); let mut inputs: HashMap = HashMap::new(); let mut total_available = Amount::from_sat(0); for outpoint in mandatory_inputs { let (must_outpoint, must_output) = available_outpoints - .get_key_value(outpoint) + .remove_entry(&outpoint) .ok_or_else(|| Error::msg("Mandatory outpoint unknown"))?; total_available += must_output.amount; - inputs.insert(*must_outpoint, must_output.clone()); + inputs.insert(must_outpoint, must_output); } for (outpoint, output) in available_outpoints { - if total_available > Amount::from_sat(1000) { + if total_available > sum_outputs { break; } total_available += output.amount; inputs.insert(outpoint, output); } - if total_available < Amount::from_sat(1000) { + if total_available < sum_outputs { return Err(Error::msg("Not enough available funds")); } - if recipient.amount == Amount::from_sat(0) { + if let Some(recipient) = zero_value_recipient { // update the amount for the recipient recipient.amount = total_available; } @@ -70,7 +84,7 @@ pub fn create_transaction( let mut new_psbt = sp_wallet .get_client() - .create_new_psbt(inputs, vec![recipient], Some(&commitment))?; + .create_new_psbt(inputs, recipients, Some(&commitment))?; let sender_address = sp_wallet.get_client().get_receiving_address(); let change_address = sp_wallet.get_client().sp_receiver.get_change_address(); @@ -120,34 +134,6 @@ pub fn create_transaction( .get_client() .get_partial_secret_from_psbt(&new_psbt)?; - // if we have a payload, it means we are notifying, so let's add a revokation output - if payload.is_some() { - let shared_point = - calculate_ecdh_shared_secret(&recipient_address.get_scan_key(), &partial_secret); - - let shared_secret = AnkSharedSecret::new(shared_point); - - // add the revokation output - let revokation_key = - Keypair::from_seckey_slice(&Secp256k1::signing_only(), &shared_secret.to_byte_array())?; - let spk = ScriptBuf::new_p2tr_tweaked( - revokation_key - .x_only_public_key() - .0 - .dangerous_assume_tweaked(), - ); - - let txout = TxOut { - value: Amount::from_sat(0), - script_pubkey: spk, - }; - - // For now let's just push it to the last output - new_psbt.unsigned_tx.output.push(txout); - - new_psbt.outputs.push(Output::default()); - } - sp_wallet .get_client() .fill_sp_outputs(&mut new_psbt, partial_secret)?; @@ -186,23 +172,25 @@ pub fn map_outputs_to_sp_address(psbt_str: &str) -> Result 0); println!("{:?}", alice_wallet.get_outputs().to_outpoints_list()); println!("{:?}", bob_wallet.get_outputs().to_outpoints_list()); - assert!(false); } #[test] @@ -295,8 +283,9 @@ mod tests { let psbt = create_transaction( &vec![&confirmation_outpoint], + &HashSet::new(), &bob_wallet, - recipient, + vec![recipient], None, FEE_RATE, Some(ALICE_ADDRESS.to_owned()), @@ -323,7 +312,6 @@ mod tests { assert!(bob_update.len() > 0); println!("{:?}", alice_wallet.get_outputs().to_outpoints_list()); println!("{:?}", bob_wallet.get_outputs().to_outpoints_list()); - assert!(false); } #[test] @@ -350,8 +338,9 @@ mod tests { let psbt = create_transaction( &vec![&confirmation_outpoint], + &HashSet::new(), &alice_wallet, - recipient, + vec![recipient], None, FEE_RATE, Some(BOB_ADDRESS.to_owned()), @@ -375,6 +364,5 @@ mod tests { assert!(bob_update.len() > 0); println!("{:?}", alice_wallet.get_outputs().to_outpoints_list()); println!("{:?}", bob_wallet.get_outputs().to_outpoints_list()); - assert!(false); } }