[bug fix] Working faucet + small fixes

This commit is contained in:
Sosthene 2024-05-22 20:17:53 +02:00
parent 0f7bc644c8
commit a93aed0ede
2 changed files with 95 additions and 168 deletions

View File

@ -34,7 +34,7 @@ use wasm_bindgen::convert::FromWasmAbi;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use sdk_common::network::{ use sdk_common::network::{
self, AnkFlag, AnkNetworkMsg, FaucetMessage, NewTxMessage, UnknownMessage, self, AnkFlag, AnkNetworkMsg, CachedMessage, CachedMessageStatus, FaucetMessage, NewTxMessage, UnknownMessage
}; };
use sdk_common::silentpayments::{ use sdk_common::silentpayments::{
create_transaction, create_transaction_for_address_with_shared_secret, create_transaction, create_transaction_for_address_with_shared_secret,
@ -371,10 +371,10 @@ fn handle_recover_transaction(
sp_wallet: &mut SpWallet, sp_wallet: &mut SpWallet,
tweak_data: PublicKey, tweak_data: PublicKey,
fee_rate: u32, fee_rate: u32,
) -> anyhow::Result<NetworkMessage> { ) -> anyhow::Result<CachedMessage> {
// We need to look for different case: // We need to look for different case:
// 1) faucet // 1) faucet
// This one is the simplest, we only care about finding the commitment // This one is the simplest, we only care about finding the commitment.clone()
let op_return = tx.output.iter().find(|o| o.script_pubkey.is_op_return()); let op_return = tx.output.iter().find(|o| o.script_pubkey.is_op_return());
let commitment = if op_return.is_none() { let commitment = if op_return.is_none() {
vec![] vec![]
@ -382,14 +382,18 @@ fn handle_recover_transaction(
op_return.unwrap().script_pubkey.as_bytes()[2..].to_vec() op_return.unwrap().script_pubkey.as_bytes()[2..].to_vec()
}; };
let commitment_str = commitment.to_lower_hex_string(); let commitment_str = commitment.to_lower_hex_string();
let pos = lock_messages()? {
.iter() let mut messages = lock_messages()?;
.position(|m| m.commitment.as_ref() == Some(&commitment_str)); let pos = messages
.iter()
.position(|m| m.commitment.as_ref() == Some(&commitment_str));
if pos.is_some() { if pos.is_some() {
let messages = lock_messages()?; let mut message = messages.swap_remove(pos.unwrap());
let message = messages.get(pos.unwrap()); message.commited_in = updated.into_iter().next().map(|(outpoint, _)| outpoint);
return Ok(message.cloned().unwrap()); message.status = CachedMessageStatus::FaucetComplete;
return Ok(message);
}
} }
// If we got updates from a transaction, it means that it creates an output to us, spend an output we owned, or both // If we got updates from a transaction, it means that it creates an output to us, spend an output we owned, or both
@ -423,7 +427,7 @@ fn handle_recover_transaction(
// If we are receiver, that's pretty much it, just set status to complete // If we are receiver, that's pretty much it, just set status to complete
if message.recipient == Some(sp_wallet.get_client().get_receiving_address()) { if message.recipient == Some(sp_wallet.get_client().get_receiving_address()) {
debug_assert!(message.confirmed_by == Some(input.previous_output)); debug_assert!(message.confirmed_by == Some(input.previous_output));
message.status = NetworkMessageStatus::Complete; message.status = CachedMessageStatus::Complete;
return Ok(message.clone()); return Ok(message.clone());
} }
@ -443,11 +447,11 @@ fn handle_recover_transaction(
let mut messages = lock_messages()?; let mut messages = lock_messages()?;
let cipher_pos = messages.iter().position(|m| { let cipher_pos = messages.iter().position(|m| {
if m.status != NetworkMessageStatus::CipherWaitingTx { if m.status != CachedMessageStatus::CipherWaitingTx {
return false; return false;
} }
m.try_decrypt_with_shared_secret(shared_secret.to_byte_array()) m.try_decrypt_with_shared_secret(shared_secret.to_byte_array())
.is_some() .is_ok()
}); });
if cipher_pos.is_some() { if cipher_pos.is_some() {
@ -468,15 +472,17 @@ fn handle_recover_transaction(
return Ok(message.clone()) return Ok(message.clone())
} else { } else {
// store it and wait for the message // store it and wait for the message
let mut new_msg = NetworkMessage::default(); let mut new_msg = CachedMessage::new();
let (outpoint, output) = utxo_created.iter().next().unwrap(); debug!("{:?}", utxo_created);
new_msg.commited_in = Some(**outpoint); let (outpoint, output) = utxo_created.into_iter().next().expect("utxo_created shouldn't be empty");
new_msg.commited_in = Some(outpoint.clone());
new_msg.commitment = Some(commitment.to_lower_hex_string()); new_msg.commitment = Some(commitment.to_lower_hex_string());
new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address()); new_msg.recipient = Some(sp_wallet.get_client().get_receiving_address());
new_msg.shared_secret = new_msg.shared_secret =
Some(shared_secret.to_byte_array().to_lower_hex_string()); Some(shared_secret.to_byte_array().to_lower_hex_string());
new_msg.status = NetworkMessageStatus::TxWaitingCipher; new_msg.status = CachedMessageStatus::TxWaitingCipher;
lock_messages()?.push(new_msg.clone()); lock_messages()?.push(new_msg.clone());
debug!("returning {:?}", new_msg);
return Ok(new_msg.clone()); return Ok(new_msg.clone());
} }
} }
@ -497,14 +503,14 @@ fn handle_recover_transaction(
} }
} }
/// If the transaction has anything to do with us, we create/update the relevant `NetworkMessage` /// If the transaction has anything to do with us, we create/update the relevant `CachedMessage`
/// and return it to caller for persistent storage /// and return it to caller for persistent storage
fn process_transaction( fn process_transaction(
tx_hex: String, tx_hex: String,
blockheight: u32, blockheight: u32,
tweak_data_hex: String, tweak_data_hex: String,
fee_rate: u32, fee_rate: u32,
) -> anyhow::Result<NetworkMessage> { ) -> anyhow::Result<CachedMessage> {
let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?; let tx = deserialize::<Transaction>(&Vec::from_hex(&tx_hex)?)?;
// check that we don't already have scanned the tx // check that we don't already have scanned the tx
@ -545,16 +551,8 @@ fn process_transaction(
Err(anyhow::Error::msg("No output found")) Err(anyhow::Error::msg("No output found"))
} }
#[derive(Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[allow(non_camel_case_types)]
pub struct parseNetworkMsgReturn {
topic: String,
message: NetworkMessage,
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<parseNetworkMsgReturn> { pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<CachedMessage> {
if let Ok(ank_msg) = serde_json::from_str::<AnkNetworkMsg>(&raw) { if let Ok(ank_msg) = serde_json::from_str::<AnkNetworkMsg>(&raw) {
match ank_msg.flag { match ank_msg.flag {
AnkFlag::NewTx => { AnkFlag::NewTx => {
@ -570,29 +568,23 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<parseNetworkMs
tx_message.tweak_data.unwrap(), tx_message.tweak_data.unwrap(),
fee_rate, fee_rate,
)?; )?;
debug!("{:?}", network_msg); return Ok(network_msg);
return Ok(parseNetworkMsgReturn {
topic: ank_msg.flag.as_str().to_owned(),
message: network_msg,
});
} }
AnkFlag::Faucet => unimplemented!(), AnkFlag::Faucet => unimplemented!(),
AnkFlag::Error => { AnkFlag::Error => {
let error_msg = NetworkMessage::new_error(ank_msg.content); let error_msg = CachedMessage::new_error(ank_msg.content);
return Ok(parseNetworkMsgReturn { return Ok(error_msg)
topic: AnkFlag::Error.as_str().to_owned(),
message: error_msg,
})
} }
AnkFlag::Unknown => { AnkFlag::Unknown => {
// let's try to decrypt with keys we found in transactions but haven't used yet // let's try to decrypt with keys we found in transactions but haven't used yet
let mut messages = lock_messages()?; let mut messages = lock_messages()?;
let cipher = Vec::from_hex(&ank_msg.content)?; let cipher = Vec::from_hex(&ank_msg.content.trim_matches('\"'))?;
let cipher_pos = messages.iter().position(|m| { let cipher_pos = messages.iter().position(|m| {
if m.status != NetworkMessageStatus::TxWaitingCipher { debug!("Trying message: {:?}", m);
if m.status != CachedMessageStatus::TxWaitingCipher {
return false; return false;
} }
m.try_decrypt_cipher(cipher.clone()).is_some() m.try_decrypt_cipher(cipher.clone()).is_ok()
}); });
if cipher_pos.is_some() { if cipher_pos.is_some() {
let mut message = messages.get_mut(cipher_pos.unwrap()).unwrap(); let mut message = messages.get_mut(cipher_pos.unwrap()).unwrap();
@ -601,14 +593,11 @@ pub fn parse_network_msg(raw: String, fee_rate: u32) -> ApiResult<parseNetworkMs
message.plaintext = Some(unknown_msg.message); message.plaintext = Some(unknown_msg.message);
message.sender = Some(unknown_msg.sender); message.sender = Some(unknown_msg.sender);
message.ciphertext = Some(ank_msg.content); message.ciphertext = Some(ank_msg.content);
return Ok(parseNetworkMsgReturn { return Ok(message.clone());
topic: AnkFlag::Unknown.as_str().to_owned(),
message: message.clone(),
});
} else { } else {
// let's keep it in case we receive the transaction later // let's keep it in case we receive the transaction later
let mut new_msg = NetworkMessage::default(); let mut new_msg = CachedMessage::default();
new_msg.status = NetworkMessageStatus::CipherWaitingTx; new_msg.status = CachedMessageStatus::CipherWaitingTx;
new_msg.ciphertext = Some(ank_msg.content); new_msg.ciphertext = Some(ank_msg.content);
messages.push(new_msg); messages.push(new_msg);
return Err(ApiError { return Err(ApiError {
@ -677,18 +666,61 @@ pub fn is_tx_owned_by_user(pre_id: String, tx: String) -> ApiResult<bool> {
#[derive(Tsify, Serialize, Deserialize)] #[derive(Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)] #[tsify(into_wasm_abi, from_wasm_abi)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub struct createNotificationTransactionReturn { pub struct createTransactionReturn {
pub txid: String, pub txid: String,
pub transaction: String, pub transaction: String,
pub new_network_msg: NetworkMessage, pub new_network_msg: CachedMessage,
}
/// This is what we call to answer a confirmation as a sender
#[wasm_bindgen]
pub fn answer_confirmation_transaction(
message: CachedMessage,
fee_rate: u32,
) -> ApiResult<createTransactionReturn> {
if message.recipient.is_none() || message.confirmed_by.is_none() {
return Err(ApiError { message: "Invalid network message".to_owned() });
}
let sp_address: SilentPaymentAddress = message.recipient.as_ref().unwrap().as_str().try_into()?;
let connected_user = lock_connected_user()?;
let sp_wallet: &SpWallet;
if sp_address.is_testnet() {
sp_wallet = connected_user.try_get_recover()?;
} else {
sp_wallet = connected_user.try_get_main()?;
}
let recipient = Recipient {
address: sp_address.into(),
amount: Amount::from_sat(0), // we'll set amount to what's available in the confirmed_by output we don't want change
nb_outputs: 1,
};
let signed_psbt = create_transaction_spend_outpoint(
&message.confirmed_by.unwrap(),
sp_wallet,
recipient,
Amount::from_sat(fee_rate.into())
)?;
let final_tx = signed_psbt.extract_tx()?;
Ok(createTransactionReturn {
txid: final_tx.txid().to_string(),
transaction: serialize(&final_tx).to_lower_hex_string(),
new_network_msg: message
})
} }
/// This is what we call to confirm as a receiver /// This is what we call to confirm as a receiver
#[wasm_bindgen] #[wasm_bindgen]
pub fn create_confirmation_transaction( pub fn create_confirmation_transaction(
message: NetworkMessage, message: CachedMessage,
fee_rate: u32, fee_rate: u32,
) -> ApiResult<createNotificationTransactionReturn> { ) -> ApiResult<createTransactionReturn> {
if message.sender.is_none() || message.confirmed_by.is_none() { if message.sender.is_none() || message.confirmed_by.is_none() {
return Err(ApiError { message: "Invalid network message".to_owned() }); return Err(ApiError { message: "Invalid network message".to_owned() });
} }
@ -706,7 +738,7 @@ pub fn create_confirmation_transaction(
let recipient = Recipient { let recipient = Recipient {
address: sp_address.into(), address: sp_address.into(),
amount: Amount::from_sat(1200), amount: Amount::from_sat(0),
nb_outputs: 1, nb_outputs: 1,
}; };
@ -719,7 +751,7 @@ pub fn create_confirmation_transaction(
let final_tx = signed_psbt.extract_tx()?; let final_tx = signed_psbt.extract_tx()?;
Ok(createNotificationTransactionReturn { Ok(createTransactionReturn {
txid: final_tx.txid().to_string(), txid: final_tx.txid().to_string(),
transaction: serialize(&final_tx).to_lower_hex_string(), transaction: serialize(&final_tx).to_lower_hex_string(),
new_network_msg: message new_network_msg: message
@ -731,7 +763,7 @@ pub fn create_notification_transaction(
address: String, address: String,
commitment: Option<String>, commitment: Option<String>,
fee_rate: u32, fee_rate: u32,
) -> ApiResult<createNotificationTransactionReturn> { ) -> ApiResult<createTransactionReturn> {
let sp_address: SilentPaymentAddress = address.as_str().try_into()?; let sp_address: SilentPaymentAddress = address.as_str().try_into()?;
let connected_user = lock_connected_user()?; let connected_user = lock_connected_user()?;
@ -782,16 +814,17 @@ pub fn create_notification_transaction(
let recipients_vouts = sp_address2vouts.get::<String>(&address).expect("recipients didn't change").as_slice(); let recipients_vouts = sp_address2vouts.get::<String>(&address).expect("recipients didn't change").as_slice();
// for now let's just take the smallest vout that belongs to the recipient // for now let's just take the smallest vout that belongs to the recipient
let final_tx = psbt.extract_tx()?; let final_tx = psbt.extract_tx()?;
let mut new_msg = NetworkMessage::default(); let mut new_msg = CachedMessage::default();
new_msg.commitment = commitment; new_msg.commitment = commitment;
new_msg.commited_in = Some(OutPoint { txid: final_tx.txid(), vout: recipients_vouts[0] as u32 }); new_msg.commited_in = Some(OutPoint { txid: final_tx.txid(), vout: recipients_vouts[0] as u32 });
new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string()); new_msg.shared_secret = Some(shared_secret.to_byte_array().to_lower_hex_string());
new_msg.recipient = Some(address); new_msg.recipient = Some(address);
new_msg.sender = Some(sp_wallet.get_client().get_receiving_address()); new_msg.sender = Some(sp_wallet.get_client().get_receiving_address());
new_msg.status = CachedMessageStatus::SentWaitingConfirmation;
// plaintext and ciphertext to be added later when sending the encrypted message // plaintext and ciphertext to be added later when sending the encrypted message
lock_messages()?.push(new_msg.clone()); lock_messages()?.push(new_msg.clone());
Ok(createNotificationTransactionReturn { Ok(createTransactionReturn {
txid: final_tx.txid().to_string(), txid: final_tx.txid().to_string(),
transaction: serialize(&final_tx).to_lower_hex_string(), transaction: serialize(&final_tx).to_lower_hex_string(),
new_network_msg: new_msg, new_network_msg: new_msg,
@ -872,8 +905,9 @@ pub fn create_faucet_msg() -> ApiResult<FaucetMessage> {
let sp_address = user.try_get_recover()?.get_client().get_receiving_address(); let sp_address = user.try_get_recover()?.get_client().get_receiving_address();
let faucet_msg = FaucetMessage::new(sp_address); let faucet_msg = FaucetMessage::new(sp_address);
// we write the commitment in a networkmessage so that we can keep track // we write the commitment in a networkmessage so that we can keep track
let mut network_msg = NetworkMessage::default(); let mut network_msg = CachedMessage::new();
network_msg.commitment = Some(faucet_msg.commitment.clone()); network_msg.commitment = Some(faucet_msg.commitment.clone());
network_msg.status = CachedMessageStatus::FaucetWaiting;
lock_messages()?.push(network_msg); lock_messages()?.push(network_msg);
Ok(faucet_msg) Ok(faucet_msg)
} }
@ -885,110 +919,3 @@ pub fn create_commitment(payload_to_hash: String) -> String {
let hash = sha256::Hash::from_engine(engine); let hash = sha256::Hash::from_engine(engine);
hash.to_byte_array().to_lower_hex_string() hash.to_byte_array().to_lower_hex_string()
} }
#[derive(Debug, Serialize, Deserialize, PartialEq, Tsify, Clone)]
pub enum NetworkMessageStatus {
NoStatus, // Default
CipherWaitingTx,
TxWaitingCipher,
SentWaitingConfirmation,
MustSpentConfirmation,
Complete,
}
impl Default for NetworkMessageStatus {
fn default() -> Self {
Self::NoStatus
}
}
/// Unique struct for both 4nk messages and notification/key exchange, both rust and ts
/// 1. Faucet: commited_in with nothing else, status is NoStatus
/// 2. notification:
/// 1. sender: ciphertext, plaintext, commited_in, sender, recipient, shared_secret, key
/// 2. receiver (without tx): ciphertext
/// 3. receiver (tx without msg): commited_in, commitment, recipient, shared_secret
/// 4. receiver (receive tx after msg): plaintext, key, sender, commited_in, commitment, recipient, shared_secret
/// 5. receiver (msg after tx): ciphertext, key, plaintext, sender
/// 3. confirmation:
/// 1. receiver (spend the smallest vout that pays him in the first tx): confirmed_by
/// 2. sender (detect a transaction that pays him and spend commited_by): confirmed_by
/// 3. sender toggle status to complete when it spent confirmed_by, receiver when it detects the confirmed_by is spent
#[derive(Debug, Default, Serialize, Deserialize, Tsify, Clone)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[allow(non_camel_case_types)]
pub struct NetworkMessage {
pub id: u32,
pub status: NetworkMessageStatus,
pub ciphertext: Option<String>, // When we receive message we can't decrypt we only have this and commited_in_tx
pub plaintext: Option<String>, // Never None when message sent
pub commited_in: Option<OutPoint>,
pub commitment: Option<String>, // content of the op_return
pub sender: Option<String>, // Never None when message sent
pub recipient: Option<String>, // Never None when message sent
pub shared_secret: Option<String>, // Never None when message sent
pub key: Option<String>, // Never None when message sent
pub confirmed_by: Option<OutPoint>, // If this None, Sender keeps sending
pub timestamp: u64,
pub error: Option<String>,
}
impl NetworkMessage {
pub fn new() -> Self {
let mut new = NetworkMessage::default();
let mut buf = [0u8;4];
thread_rng().fill_bytes(&mut buf);
new.id = u32::from_be_bytes(buf);
new
}
pub fn new_error(error: String) -> Self {
let mut new = NetworkMessage::default();
new.error = Some(error);
new
}
pub fn try_decrypt_cipher(&self, cipher: Vec<u8>) -> Option<Vec<u8>> {
if self.ciphertext.is_some() || self.shared_secret.is_none() {
log::error!(
"Can't try decrypt this message, there's already a ciphertext or no shared secret"
);
return None;
}
let mut shared_secret = [0u8; 32];
shared_secret
.copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap()).unwrap());
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher, shared_secret);
if aes_decrypt.is_err() {
log::error!("Failed to create decrypt object");
return None;
}
aes_decrypt.unwrap().decrypt_with_key().ok()
}
pub fn try_decrypt_with_shared_secret(&self, shared_secret: [u8; 32]) -> Option<Vec<u8>> {
if self.ciphertext.is_none() || self.shared_secret.is_some() {
log::error!(
"Can't try decrypt this message, ciphertext is none or shared_secret already found"
);
return None;
}
let cipher_bin = Vec::from_hex(self.ciphertext.as_ref().unwrap());
if cipher_bin.is_err() {
let error = cipher_bin.unwrap_err();
log::error!("Invalid hex in ciphertext: {}", error.to_string());
return None;
}
let aes_decrypt =
Aes256Decryption::new(Purpose::Arbitrary, cipher_bin.unwrap(), shared_secret);
if aes_decrypt.is_err() {
log::error!("Failed to create decrypt object");
return None;
}
aes_decrypt.unwrap().decrypt_with_key().ok()
}
}

View File

@ -1,7 +1,7 @@
#![allow(warnings)] #![allow(warnings)]
use anyhow::Error; use anyhow::Error;
use api::NetworkMessage;
use sdk_common::crypto::AnkSharedSecret; use sdk_common::crypto::AnkSharedSecret;
use sdk_common::network::CachedMessage;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sp_client::bitcoin::{OutPoint, Txid}; use sp_client::bitcoin::{OutPoint, Txid};
use sp_client::silentpayments::sending::SilentPaymentAddress; use sp_client::silentpayments::sending::SilentPaymentAddress;
@ -17,10 +17,10 @@ mod peers;
mod process; mod process;
mod user; mod user;
pub static NETWORKMESSAGES: OnceLock<Mutex<Vec<NetworkMessage>>> = OnceLock::new(); pub static CACHEDMESSAGES: OnceLock<Mutex<Vec<CachedMessage>>> = OnceLock::new();
pub fn lock_messages() -> Result<MutexGuard<'static, Vec<NetworkMessage>>, Error> { pub fn lock_messages() -> Result<MutexGuard<'static, Vec<CachedMessage>>, Error> {
NETWORKMESSAGES CACHEDMESSAGES
.get_or_init(|| Mutex::new(vec![])) .get_or_init(|| Mutex::new(vec![]))
.lock_anyhow() .lock_anyhow()
} }