From a1cfba63ff5c99f8b50082504cea87313aa6237f Mon Sep 17 00:00:00 2001 From: Sosthene Date: Thu, 27 Jun 2024 17:19:04 +0200 Subject: [PATCH 1/6] Add optional payload to create_transaction_spend_outpoint --- src/silentpayments.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/silentpayments.rs b/src/silentpayments.rs index c2b58e5..ff9301f 100644 --- a/src/silentpayments.rs +++ b/src/silentpayments.rs @@ -77,6 +77,7 @@ pub fn create_transaction_spend_outpoint( sp_wallet: &SpWallet, mut recipient: Recipient, commited_in_txid: &Txid, + payload: Option>, fee_rate: Amount ) -> Result { let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); @@ -105,19 +106,30 @@ pub fn create_transaction_spend_outpoint( // Take the recipient address let address = recipient.address.clone(); - // create a dummy commitment that is H(b_scan | commited_in txid) - let mut buf = [0u8;64]; - buf[..32].copy_from_slice(commited_in_txid.as_raw_hash().as_byte_array()); - buf[32..].copy_from_slice(&sp_wallet.get_client().get_scan_key().secret_bytes()); - - let mut engine = sha256::HashEngine::default(); - engine.write_all(&buf)?; - let hash = sha256::Hash::from_engine(engine); + let mut commitment = [0u8;32]; + if let Some(p) = payload { + let mut engine = sha256::HashEngine::default(); + engine.write_all(&p)?; + let hash = sha256::Hash::from_engine(engine); + + commitment.copy_from_slice(hash.as_byte_array()); + } else { + // create a dummy commitment that is H(b_scan | commited_in txid) + let mut buf = [0u8;64]; + buf[..32].copy_from_slice(commited_in_txid.as_raw_hash().as_byte_array()); + buf[32..].copy_from_slice(&sp_wallet.get_client().get_scan_key().secret_bytes()); + + let mut engine = sha256::HashEngine::default(); + engine.write_all(&buf)?; + let hash = sha256::Hash::from_engine(engine); + + commitment.copy_from_slice(hash.as_byte_array()); + } let mut new_psbt = sp_wallet.get_client().create_new_psbt( inputs, vec![recipient], - Some(hash.as_byte_array()), + Some(&commitment), )?; SpClient::set_fees(&mut new_psbt, fee_rate, address)?; From 207e2d1d73ffe567028487a25faf2587357811c5 Mon Sep 17 00:00:00 2001 From: Sosthene Date: Fri, 19 Jul 2024 22:46:07 +0200 Subject: [PATCH 2/6] Add to_string and from_string methods to network types --- src/network.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/network.rs b/src/network.rs index c989730..54f3c94 100644 --- a/src/network.rs +++ b/src/network.rs @@ -105,6 +105,10 @@ impl CipherMessage { error: None, } } + + pub fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } } #[derive(Debug, Serialize, Deserialize, Tsify)] @@ -121,6 +125,15 @@ impl AnkNetworkMsg { content: raw.into(), } } + + pub fn from_string(json: &str) -> Result { + let res: Self = serde_json::from_str(json)?; + Ok(res) + } + + pub fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } } #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Tsify, Clone)] @@ -178,6 +191,15 @@ impl CachedMessage { new } + pub fn from_string(json: &str) -> Result { + let res = serde_json::from_str(json)?; + Ok(res) + } + + pub fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } + pub fn try_decrypt_cipher(&self, cipher: Vec) -> Result> { if self.ciphertext.is_some() || self.shared_secret.is_none() { return Err(Error::msg( From 9087e0a5353e47449dbacf58a57ad0fb32b64bf3 Mon Sep 17 00:00:00 2001 From: Sosthene Date: Fri, 19 Jul 2024 22:47:09 +0200 Subject: [PATCH 3/6] Replace silentpayments methods with one create_transaction --- src/silentpayments.rs | 453 ++++++++++++++++++++++++++++-------------- 1 file changed, 303 insertions(+), 150 deletions(-) diff --git a/src/silentpayments.rs b/src/silentpayments.rs index ff9301f..750c91c 100644 --- a/src/silentpayments.rs +++ b/src/silentpayments.rs @@ -5,88 +5,43 @@ 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::psbt::raw; -use sp_client::bitcoin::{Psbt, Transaction, Txid}; -use sp_client::bitcoin::{Amount, OutPoint}; -use sp_client::bitcoin::consensus::deserialize; +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 sp_client::constants; -pub fn create_transaction(sp_address: SilentPaymentAddress, sp_wallet: &SpWallet, fee_rate: Amount) -> Result { - let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); +use crate::crypto::AnkSharedSecret; - // Here we need to add more heuristics about which outpoint we spend - // For now let's keep it simple - - let mut inputs: HashMap = HashMap::new(); - - let mut total_available = Amount::from_sat(0); - for (outpoint, output) in available_outpoints { - total_available += output.amount; - inputs.insert(outpoint, output); - if total_available > Amount::from_sat(1000) { - break; - } - } - - if total_available < Amount::from_sat(1000) { - return Err(Error::msg("Not enough available funds")); - } - - let recipient = Recipient { - address: sp_address.into(), - amount: Amount::from_sat(1000), - nb_outputs: 1, - }; - - let mut new_psbt = sp_wallet.get_client().create_new_psbt( - inputs, - vec![recipient], - None, - )?; - - let change_addr = sp_wallet.get_client().sp_receiver.get_change_address(); - SpClient::set_fees(&mut new_psbt, fee_rate, change_addr)?; - - let partial_secret = sp_wallet - .get_client() - .get_partial_secret_from_psbt(&new_psbt)?; - - // This wouldn't work with many recipients in the same transaction - // each address (or more precisely each scan public key) would have its own point - // let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret); - - sp_wallet - .get_client() - .fill_sp_outputs(&mut new_psbt, partial_secret)?; - let mut aux_rand = [0u8; 32]; - rand::thread_rng().fill(&mut aux_rand); - let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?; - SpClient::finalize_psbt(&mut signed)?; - - let final_tx = signed.extract_tx()?; - - Ok(final_tx) -} - -pub fn create_transaction_spend_outpoint( - outpoint: &OutPoint, +pub fn create_transaction( + mandatory_inputs: &[&OutPoint], sp_wallet: &SpWallet, mut recipient: Recipient, - commited_in_txid: &Txid, payload: Option>, - fee_rate: Amount + 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 inputs: HashMap = HashMap::new(); let mut total_available = Amount::from_sat(0); - let (must_outpoint, must_output) = available_outpoints.get_key_value(outpoint).ok_or_else(|| Error::msg("Mandatory outpoint unknown"))?; - total_available += must_output.amount; - inputs.insert(*must_outpoint, must_output.clone()); + for outpoint in mandatory_inputs { + let (must_outpoint, must_output) = available_outpoints + .get_key_value(outpoint) + .ok_or_else(|| Error::msg("Mandatory outpoint unknown"))?; + total_available += must_output.amount; + inputs.insert(*must_outpoint, must_output.clone()); + } for (outpoint, output) in available_outpoints { if total_available > Amount::from_sat(1000) { @@ -100,44 +55,99 @@ pub fn create_transaction_spend_outpoint( return Err(Error::msg("Not enough available funds")); } - // update the amount for the recipient - recipient.amount = total_available; - - // Take the recipient address - let address = recipient.address.clone(); - - let mut commitment = [0u8;32]; - if let Some(p) = payload { - let mut engine = sha256::HashEngine::default(); - engine.write_all(&p)?; - let hash = sha256::Hash::from_engine(engine); - - commitment.copy_from_slice(hash.as_byte_array()); - } else { - // create a dummy commitment that is H(b_scan | commited_in txid) - let mut buf = [0u8;64]; - buf[..32].copy_from_slice(commited_in_txid.as_raw_hash().as_byte_array()); - buf[32..].copy_from_slice(&sp_wallet.get_client().get_scan_key().secret_bytes()); - - let mut engine = sha256::HashEngine::default(); - engine.write_all(&buf)?; - let hash = sha256::Hash::from_engine(engine); - - commitment.copy_from_slice(hash.as_byte_array()); + if recipient.amount == Amount::from_sat(0) { + // update the amount for the recipient + recipient.amount = total_available; } - let mut new_psbt = sp_wallet.get_client().create_new_psbt( - inputs, - vec![recipient], - Some(&commitment), - )?; + let mut commitment = [0u8; 32]; + if let Some(ref p) = payload { + commitment.copy_from_slice(&p); + } else { + thread_rng().fill(&mut commitment); + } - SpClient::set_fees(&mut new_psbt, fee_rate, address)?; + let mut new_psbt = + sp_wallet + .get_client() + .create_new_psbt(inputs, vec![recipient], Some(&commitment))?; + + let sender_address = sp_wallet.get_client().get_receiving_address(); + let change_address = sp_wallet.get_client().sp_receiver.get_change_address(); + if let Some(address) = fee_payer { + SpClient::set_fees(&mut new_psbt, fee_rate, address)?; + } else { + let candidates: Vec> = new_psbt.outputs + .iter() + .map(|o| { + if let Some(value) = o.proprietary.get(&raw::ProprietaryKey { + prefix: PSBT_SP_PREFIX.as_bytes().to_vec(), + subtype: PSBT_SP_SUBTYPE, + key: PSBT_SP_ADDRESS_KEY.as_bytes().to_vec(), + }) { + let candidate: String = + SilentPaymentAddress::try_from(deserialize::(value).unwrap()) + .unwrap() + .into(); + return Some(candidate); + } else { + return None; + } + }) + .collect(); + + let mut fee_set = false; + for candidate in candidates { + if let Some(c) = candidate { + if c == change_address { + SpClient::set_fees(&mut new_psbt, fee_rate, change_address.clone())?; + fee_set = true; + break; + } else if c == sender_address { + SpClient::set_fees(&mut new_psbt, fee_rate, sender_address.clone())?; + fee_set = true; + break; + } + } + } + + if !fee_set { + return Err(Error::msg("Must specify payer for fee")); + } + }; let partial_secret = sp_wallet .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)?; @@ -149,58 +159,6 @@ pub fn create_transaction_spend_outpoint( Ok(signed) } -pub fn create_transaction_for_address_with_shared_secret( - recipient: Recipient, - sp_wallet: &SpWallet, - message: Option<&str>, - fee_rate: Amount, -) -> Result { - let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); - - // Here we need to add more heuristics about which outpoint we spend - // For now let's keep it simple - - let mut inputs: HashMap = HashMap::new(); - - let mut total_available = Amount::from_sat(0); - for (outpoint, output) in available_outpoints { - total_available += output.amount; - inputs.insert(outpoint, output); - if total_available > Amount::from_sat(1000) { - break; - } - } - - if total_available < Amount::from_sat(1000) { - return Err(Error::msg("Not enough available funds")); - } - - let message_bin = if message.is_some() { Vec::from_hex(message.unwrap())? } else { vec![] }; - - let mut new_psbt = sp_wallet.get_client().create_new_psbt( - inputs, - vec![recipient], - if !message_bin.is_empty() { Some(&message_bin) } else { None }, - )?; - - let change_addr = sp_wallet.get_client().sp_receiver.get_change_address(); - SpClient::set_fees(&mut new_psbt, fee_rate, change_addr)?; - - let partial_secret = sp_wallet - .get_client() - .get_partial_secret_from_psbt(&new_psbt)?; - - sp_wallet - .get_client() - .fill_sp_outputs(&mut new_psbt, partial_secret)?; - let mut aux_rand = [0u8; 32]; - rand::thread_rng().fill(&mut aux_rand); - let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?; - SpClient::finalize_psbt(&mut signed)?; - - Ok(signed.to_string()) -} - pub fn map_outputs_to_sp_address(psbt_str: &str) -> Result>> { let psbt = Psbt::from_str(&psbt_str)?; @@ -225,3 +183,198 @@ pub fn map_outputs_to_sp_address(psbt_str: &str) -> Result PublicKey { + let prevout = tx.input.get(0).unwrap().to_owned(); + let outpoint_data = ( + prevout.previous_output.txid.to_string(), + prevout.previous_output.vout, + ); + let input_pubkey = + get_pubkey_from_input(&vec![], &prevout.witness.to_vec(), spk.as_bytes()).unwrap(); + let tweak_data = + calculate_tweak_data(&vec![&input_pubkey.unwrap()], &vec![outpoint_data]).unwrap(); + tweak_data + } + + fn helper_create_commitment(payload_to_hash: String) -> String { + let mut engine = sha256::HashEngine::default(); + engine.write_all(&payload_to_hash.as_bytes()); + let hash = sha256::Hash::from_engine(engine); + hash.to_byte_array().to_lower_hex_string() + } + + #[test] + fn it_creates_notification_transaction() { + let recipient = Recipient { + address: BOB_ADDRESS.to_owned(), + amount: Amount::from_sat(1200), + nb_outputs: 1, + }; + let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET).unwrap(); + let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET).unwrap(); + let message: CipherMessage = CipherMessage::new(ALICE_ADDRESS.to_owned(), "TEST".to_owned()); + let commitment = helper_create_commitment(serde_json::to_string(&message).unwrap()); + + assert!(commitment == "d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99"); + + let psbt = create_transaction( + &vec![], + &alice_wallet, + recipient, + Some(Vec::from_hex(COMMITMENT).unwrap()), + FEE_RATE, + None, + ) + .unwrap(); + + let final_tx = psbt.extract_tx().unwrap(); + let spk = "51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1"; + + let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap()); + + // Check that Alice and Bob are both able to find that transaction + let alice_update = alice_wallet + .update_wallet_with_transaction(&final_tx, 0, tweak_data) + .unwrap(); + assert!(alice_update.len() > 0); + let bob_update = bob_wallet + .update_wallet_with_transaction(&final_tx, 0, tweak_data) + .unwrap(); + assert!(bob_update.len() > 0); + println!("{:?}", alice_wallet.get_outputs().to_outpoints_list()); + println!("{:?}", bob_wallet.get_outputs().to_outpoints_list()); + assert!(false); + } + + #[test] + fn it_creates_confirmation_transaction() { + let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET_CONFIRMATION).unwrap(); + let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET_CONFIRMATION).unwrap(); + + // Bob must spend notification output + let (confirmation_outpoint, _) = bob_wallet + .get_outputs() + .get_outpoint( + OutPoint::from_str( + "148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:0", + ) + .unwrap(), + ) + .unwrap(); + + let recipient = Recipient { + address: ALICE_ADDRESS.to_owned(), + amount: Amount::from_sat(0), + nb_outputs: 1, + }; + + let psbt = create_transaction( + &vec![&confirmation_outpoint], + &bob_wallet, + recipient, + None, + FEE_RATE, + Some(ALICE_ADDRESS.to_owned()), + ) + .unwrap(); + + let final_tx = psbt.extract_tx().unwrap(); + // println!( + // "{}", + // serialize::(&final_tx).to_lower_hex_string() + // ); + let spk = "512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c"; + + let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap()); + + // Check that Alice and Bob are both able to find that transaction + let alice_update = alice_wallet + .update_wallet_with_transaction(&final_tx, 0, tweak_data) + .unwrap(); + assert!(alice_update.len() > 0); + let bob_update = bob_wallet + .update_wallet_with_transaction(&final_tx, 0, tweak_data) + .unwrap(); + assert!(bob_update.len() > 0); + println!("{:?}", alice_wallet.get_outputs().to_outpoints_list()); + println!("{:?}", bob_wallet.get_outputs().to_outpoints_list()); + assert!(false); + } + + #[test] + fn it_creates_answer_transaction() { + let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET_ANSWER).unwrap(); + let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET_ANSWER).unwrap(); + + // Bob must spend notification output + let (confirmation_outpoint, _) = alice_wallet + .get_outputs() + .get_outpoint( + OutPoint::from_str( + "bc207c02bc4f1d4359fcd604296c0938bf1e6ff827662a56410676b8cbd768d9:0", + ) + .unwrap(), + ) + .unwrap(); + + let recipient = Recipient { + address: BOB_ADDRESS.to_owned(), + amount: Amount::from_sat(0), + nb_outputs: 1, + }; + + let psbt = create_transaction( + &vec![&confirmation_outpoint], + &alice_wallet, + recipient, + None, + FEE_RATE, + Some(BOB_ADDRESS.to_owned()), + ) + .unwrap(); + + let final_tx = psbt.extract_tx().unwrap(); + // println!("{}", serialize::(&final_tx).to_lower_hex_string()); + let spk = "5120646bdb98d89a2573acc6064a5c806d00e34beb65588c91a32733b62255b4dafa"; + + let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap()); + + // Check that Alice and Bob are both able to find that transaction + let alice_update = alice_wallet + .update_wallet_with_transaction(&final_tx, 0, tweak_data) + .unwrap(); + assert!(alice_update.len() > 0); + let bob_update = bob_wallet + .update_wallet_with_transaction(&final_tx, 0, tweak_data) + .unwrap(); + assert!(bob_update.len() > 0); + println!("{:?}", alice_wallet.get_outputs().to_outpoints_list()); + println!("{:?}", bob_wallet.get_outputs().to_outpoints_list()); + assert!(false); + } +} From e1f70cf8496203a0adf3eb354ea3356bd7827603 Mon Sep 17 00:00:00 2001 From: Sosthene Date: Fri, 19 Jul 2024 22:48:15 +0200 Subject: [PATCH 4/6] fmt --- src/crypto.rs | 25 ++++++++++++------------- src/error.rs | 2 +- src/lib.rs | 2 +- src/network.rs | 31 ++++++++++++++++++++----------- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index e4d1945..6caefa5 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -4,12 +4,14 @@ use anyhow::{Error, Result}; use serde::{Deserialize, Serialize}; use sp_client::{ bitcoin::{ - hex::{DisplayHex, FromHex}, key::constants::SECRET_KEY_SIZE, Txid + hex::{DisplayHex, FromHex}, + key::constants::SECRET_KEY_SIZE, + Txid, }, silentpayments::{ bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine}, + secp256k1::PublicKey, utils::SilentPaymentAddress, - secp256k1::PublicKey }, }; use tsify::Tsify; @@ -41,15 +43,17 @@ pub struct AnkSharedSecret { impl AnkSharedSecret { pub fn new(shared_point: PublicKey) -> Self { - let mut shared_point_bin = [0u8;64]; + let mut shared_point_bin = [0u8; 64]; shared_point_bin.copy_from_slice(&shared_point.serialize_uncompressed()[1..]); let secret = AnkSharedSecretHash::from_shared_point(shared_point_bin).to_byte_array(); - Self { secret: secret.to_lower_hex_string() } + Self { + secret: secret.to_lower_hex_string(), + } } pub fn to_byte_array(&self) -> [u8; 32] { let bytes = Vec::from_hex(&self.secret).unwrap(); - let mut buf = [0u8;32]; + let mut buf = [0u8; 32]; buf.copy_from_slice(&bytes); buf } @@ -113,11 +117,7 @@ pub struct Aes256Decryption { } impl Aes256Decryption { - pub fn new( - purpose: Purpose, - cipher_text: CipherText, - aes_key: [u8;32], - ) -> Result { + pub fn new(purpose: Purpose, cipher_text: CipherText, aes_key: [u8; 32]) -> Result { if cipher_text.len() <= 12 { return Err(Error::msg("cipher_text is shorter than nonce length")); } @@ -254,7 +254,7 @@ impl Aes256Encryption { }) } - pub fn export_key(&self) -> [u8;32] { + pub fn export_key(&self) -> [u8; 32] { self.aes_key } @@ -376,8 +376,7 @@ mod tests { let mut plain_key = [0u8; 32]; plain_key.copy_from_slice(&aes_key.to_vec()); - let aes_dec = - Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key); + let aes_dec = Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key); assert!(aes_dec.is_ok()); } diff --git a/src/error.rs b/src/error.rs index dcb6304..438540d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ -use std::fmt; use std::error::Error; +use std::fmt; use serde::{Deserialize, Serialize}; diff --git a/src/lib.rs b/src/lib.rs index d9d184f..7d693bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ pub use sp_client; pub mod crypto; +pub mod error; pub mod network; pub mod silentpayments; -pub mod error; diff --git a/src/network.rs b/src/network.rs index 54f3c94..f1d82b6 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1,9 +1,16 @@ +use std::str::FromStr; + +use aes_gcm::Aes256Gcm; use anyhow::{Error, Result}; use js_sys::Date; use rand::{thread_rng, RngCore}; use serde::{Deserialize, Serialize}; +use sp_client::bitcoin::consensus::serialize; +use sp_client::bitcoin::hashes::Hash; use sp_client::bitcoin::hex::{DisplayHex, FromHex}; -use sp_client::bitcoin::OutPoint; +use sp_client::bitcoin::secp256k1::PublicKey; +use sp_client::bitcoin::{BlockHash, OutPoint, Transaction}; +use sp_client::silentpayments::utils::SilentPaymentAddress; use tsify::Tsify; use crate::crypto::{Aes256Decryption, Purpose}; @@ -65,9 +72,13 @@ pub struct FaucetMessage { impl FaucetMessage { pub fn new(sp_address: String) -> Self { - let mut buf = [0u8;64]; + let mut buf = [0u8; 64]; thread_rng().fill_bytes(&mut buf); - Self { sp_address, commitment: buf.to_lower_hex_string(), error: None } + Self { + sp_address, + commitment: buf.to_lower_hex_string(), + error: None, + } } } @@ -150,7 +161,7 @@ pub enum CachedMessageStatus { Complete, } -/// Unique struct for both 3nk messages and notification/key exchange, both rust and ts +/// Unique struct for both 4nk messages and notification/key exchange, both rust and ts /// 0. Faucet: commited_in with nothing else, status is NoStatus /// 1. notification: /// 0. sender: ciphertext, plaintext, commited_in, sender, recipient, shared_secret, key @@ -184,7 +195,7 @@ pub struct CachedMessage { impl CachedMessage { pub fn new() -> Self { let mut new = Self::default(); - let mut buf = [0u8;4]; + let mut buf = [0u8; 4]; thread_rng().fill_bytes(&mut buf); new.id = u32::from_be_bytes(buf); new.timestamp = Date::now().floor() as u64; @@ -203,12 +214,11 @@ impl CachedMessage { pub fn try_decrypt_cipher(&self, cipher: Vec) -> Result> { if self.ciphertext.is_some() || 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 already a ciphertext or no shared secret", )); } let mut shared_secret = [0u8; 32]; - shared_secret - .copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap())?); + shared_secret.copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap())?); let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher, shared_secret)?; aes_decrypt.decrypt_with_key() @@ -217,12 +227,11 @@ impl CachedMessage { pub fn try_decrypt_with_shared_secret(&self, shared_secret: [u8; 32]) -> Result> { if self.ciphertext.is_none() || self.shared_secret.is_some() { return Err(Error::msg( - "Can't try decrypt this message, ciphertext is none or shared_secret already found" + "Can't try decrypt this message, ciphertext is none or shared_secret already found", )); } let cipher_bin = Vec::from_hex(self.ciphertext.as_ref().unwrap())?; - let aes_decrypt = - Aes256Decryption::new(Purpose::Arbitrary, cipher_bin, shared_secret)?; + let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher_bin, shared_secret)?; aes_decrypt.decrypt_with_key() } From f59dc042b62c9c56bba5fe3e8d02f04f4d9f4e46 Mon Sep 17 00:00:00 2001 From: Sosthene Date: Fri, 19 Jul 2024 22:48:45 +0200 Subject: [PATCH 5/6] Update CachedMessage --- src/network.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/network.rs b/src/network.rs index f1d82b6..3ae93af 100644 --- a/src/network.rs +++ b/src/network.rs @@ -150,15 +150,15 @@ impl AnkNetworkMsg { #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Tsify, Clone)] pub enum CachedMessageStatus { #[default] - NoStatus, + NoStatus, FaucetWaiting, - FaucetComplete, CipherWaitingTx, TxWaitingCipher, SentWaitingConfirmation, // we're sender and wait for commited_in to be spent - ReceivedMustConfirm, // we're receiver and we spend commited_in to sender - MustSpendConfirmation, // we're sender and we must spend confirmed_by - Complete, + ReceivedMustConfirm, // we're receiver and we spend commited_in to sender + MustSpendConfirmation, // we're sender and we must spend confirmed_by + Trusted, // Can receive more messages + Closed, // No more messages will be trusted from this handshake } /// Unique struct for both 4nk messages and notification/key exchange, both rust and ts @@ -179,14 +179,15 @@ pub enum CachedMessageStatus { pub struct CachedMessage { pub id: u32, pub status: CachedMessageStatus, - pub ciphertext: Option, // When we receive message we can't decrypt we only have this and commited_in_tx - pub plaintext: Option, // Never None when message sent + 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 commitment: Option, // content of the op_return - pub sender: Option, // Never None when message sent - pub recipient: Option, // Never None when message sent - pub shared_secret: Option, // Never None when message sent - pub key: Option, // Never None when message sent + pub tie_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 + pub shared_secret: Option, // Never None when message sent + pub key: Option, // Never None when message sent pub confirmed_by: Option, // If this None, Sender keeps sending pub timestamp: u64, pub error: Option, From b5881d63b080c69239f1bb24826f3b8efefdb13b Mon Sep 17 00:00:00 2001 From: Sosthene Date: Fri, 19 Jul 2024 22:48:57 +0200 Subject: [PATCH 6/6] Add TrustedChannel --- src/network.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/network.rs b/src/network.rs index 3ae93af..09a4e18 100644 --- a/src/network.rs +++ b/src/network.rs @@ -237,3 +237,52 @@ impl CachedMessage { aes_decrypt.decrypt_with_key() } } + +#[derive(Debug, Serialize, Deserialize, Tsify, Default)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[allow(non_camel_case_types)] +pub struct TrustedChannel { + id: u32, // Just take the id of the cached message? + revokation_outpoint: OutPoint, // revokation output is locked by the shared_secret + shared_secret: [u8; 32], + revoked_in_block: [u8; 32], + with: String, // Silent payment address +} + +impl TrustedChannel { + pub fn new(with: String) -> Result { + // check that with is valid silent payment address + SilentPaymentAddress::try_from(with.as_str())?; + + // Generating random id + let mut new = Self::default(); + let mut buf = [0u8; 4]; + thread_rng().fill_bytes(&mut buf); + new.id = u32::from_be_bytes(buf); + new.with = with; + + Ok(new) + } + + pub fn set_revokation( + &mut self, + revokation_outpoint: String, + shared_secret: String, + ) -> Result<()> { + let mut buf = [0u8; 32]; + buf.copy_from_slice(&Vec::from_hex(&shared_secret)?); + self.revokation_outpoint = OutPoint::from_str(&revokation_outpoint)?; + self.shared_secret.copy_from_slice(&buf); + + Ok(()) + } + + pub fn revoke(&mut self, revoked_in_block: String) -> Result<()> { + let block_hash = BlockHash::from_str(&revoked_in_block)?; + let mut buf = [0u8; 32]; + buf.copy_from_slice(&serialize::(&block_hash)); + self.revoked_in_block = buf; + + Ok(()) + } +}