diff --git a/src/cli.rs b/src/cli.rs index 17ff099..414bcdd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -149,6 +149,9 @@ pub enum Commands { #[arg(short, long)] #[clap(default_value_t = 30)] since: i64, + /// If true, get messages from counterparty, otherwise from Mostro + #[arg(short)] + from_user: bool, }, /// Send direct message to a user SendDm { @@ -223,9 +226,6 @@ pub enum Commands { /// Pubkey of the counterpart #[arg(short, long)] pubkey: String, - /// Event id of the message to derive the key - #[arg(short, long)] - event_id: String, }, } @@ -305,9 +305,8 @@ pub async fn run() -> Result<()> { if let Some(cmd) = cli.command { match &cmd { - Commands::ConversationKey { event_id, pubkey } => { - execute_conversation_key(&my_key, PublicKey::from_str(pubkey)?, &client, event_id) - .await? + Commands::ConversationKey { pubkey } => { + execute_conversation_key(&my_key, PublicKey::from_str(pubkey)?).await? } Commands::ListOrders { status, @@ -327,7 +326,9 @@ pub async fn run() -> Result<()> { Commands::AddInvoice { order_id, invoice } => { execute_add_invoice(order_id, invoice, &my_key, mostro_key, &client).await? } - Commands::GetDm { since } => execute_get_dm(since, &my_key, &client).await?, + Commands::GetDm { since, from_user } => { + execute_get_dm(since, &my_key, &client, *from_user).await? + } Commands::FiatSent { order_id } | Commands::Release { order_id } | Commands::Dispute { order_id } diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index e82854a..6f383be 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -34,7 +34,7 @@ pub async fn execute_add_invoice( .as_json() .unwrap(); - send_order_id_cmd(client, my_key, mostro_key, add_invoice_message, true).await?; + send_order_id_cmd(client, my_key, mostro_key, add_invoice_message, true, true).await?; Ok(()) } diff --git a/src/cli/conversation_key.rs b/src/cli/conversation_key.rs index 35d5675..989fc11 100644 --- a/src/cli/conversation_key.rs +++ b/src/cli/conversation_key.rs @@ -1,38 +1,17 @@ use anyhow::Result; use nip44::v2::ConversationKey; use nostr_sdk::prelude::*; -use std::str::FromStr; -use crate::util::send_relays_requests; - -pub async fn execute_conversation_key( - my_key: &Keys, - receiver: PublicKey, - client: &Client, - event_id: &str, -) -> Result<()> { - let id = EventId::from_str(event_id)?; - let filter = Filter::new().id(id).limit(1); - let mostro_req = send_relays_requests(client, filter).await; - let event = mostro_req.first().unwrap().first().unwrap(); - // Derive gift wrap conversation key - let gw_ck = ConversationKey::derive(my_key.secret_key()?, &event.pubkey); - let gw_key = gw_ck.as_bytes(); - let mut gw_ck_hex = vec![]; - for i in gw_key { - gw_ck_hex.push(format!("{:02x}", i)); - } - let gw_ck_hex = gw_ck_hex.join(""); - // Derive seal conversation key - let seal_ck = ConversationKey::derive(my_key.secret_key()?, &receiver); - let seal_key = seal_ck.as_bytes(); - let mut seal_ck_hex = vec![]; - for i in seal_key { - seal_ck_hex.push(format!("{:02x}", i)); +pub async fn execute_conversation_key(my_key: &Keys, receiver: PublicKey) -> Result<()> { + // Derive conversation key + let ck = ConversationKey::derive(my_key.secret_key()?, &receiver); + let key = ck.as_bytes(); + let mut ck_hex = vec![]; + for i in key { + ck_hex.push(format!("{:02x}", i)); } - let seal_ck_hex = seal_ck_hex.join(""); - println!("Gift wrap Conversation key: {:?}", gw_ck_hex); - println!("Seal Conversation key: {:?}", seal_ck_hex); + let ck_hex = ck_hex.join(""); + println!("Conversation key: {:?}", ck_hex); Ok(()) } diff --git a/src/cli/get_dm.rs b/src/cli/get_dm.rs index ba83837..528e98b 100644 --- a/src/cli/get_dm.rs +++ b/src/cli/get_dm.rs @@ -4,8 +4,13 @@ use nostr_sdk::prelude::*; use crate::util::get_direct_messages; -pub async fn execute_get_dm(since: &i64, my_key: &Keys, client: &Client) -> Result<()> { - let dm = get_direct_messages(client, my_key, *since).await; +pub async fn execute_get_dm( + since: &i64, + my_key: &Keys, + client: &Client, + from_user: bool, +) -> Result<()> { + let dm = get_direct_messages(client, my_key, *since, from_user).await; if dm.is_empty() { println!(); println!("No new messages"); diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 91a77ae..76ace62 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -113,6 +113,6 @@ pub async fn execute_new_order( .as_json() .unwrap(); - send_order_id_cmd(client, my_key, mostro_key, message, false).await?; + send_order_id_cmd(client, my_key, mostro_key, message, false, true).await?; Ok(()) } diff --git a/src/cli/rate_user.rs b/src/cli/rate_user.rs index 498d951..8e45a3a 100644 --- a/src/cli/rate_user.rs +++ b/src/cli/rate_user.rs @@ -28,7 +28,7 @@ pub async fn execute_rate_user( .as_json() .unwrap(); - send_order_id_cmd(client, my_key, mostro_key, rate_message, true).await?; + send_order_id_cmd(client, my_key, mostro_key, rate_message, true, true).await?; std::process::exit(0); } diff --git a/src/cli/send_dm.rs b/src/cli/send_dm.rs index ce0dcc6..041bd68 100644 --- a/src/cli/send_dm.rs +++ b/src/cli/send_dm.rs @@ -8,7 +8,7 @@ pub async fn execute_send_dm( client: &Client, message: &str, ) -> Result<()> { - send_order_id_cmd(client, my_key, receiver, message.to_string(), true).await?; + send_order_id_cmd(client, my_key, receiver, message.to_string(), true, true).await?; Ok(()) } diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index 1623685..bc385ea 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -47,7 +47,7 @@ pub async fn execute_send_msg( .as_json() .unwrap(); info!("Sending message: {:#?}", message); - send_order_id_cmd(client, my_key, mostro_key, message, false).await?; + send_order_id_cmd(client, my_key, mostro_key, message, false, true).await?; Ok(()) } diff --git a/src/cli/take_buy.rs b/src/cli/take_buy.rs index b1e4127..e41eef2 100644 --- a/src/cli/take_buy.rs +++ b/src/cli/take_buy.rs @@ -26,7 +26,7 @@ pub async fn execute_take_buy( .as_json() .unwrap(); - send_order_id_cmd(client, my_key, mostro_key, take_buy_message, true).await?; + send_order_id_cmd(client, my_key, mostro_key, take_buy_message, true, true).await?; Ok(()) } diff --git a/src/cli/take_dispute.rs b/src/cli/take_dispute.rs index 19d2d78..c8d055e 100644 --- a/src/cli/take_dispute.rs +++ b/src/cli/take_dispute.rs @@ -22,7 +22,7 @@ pub async fn execute_take_dispute( .as_json() .unwrap(); - send_order_id_cmd(client, my_key, mostro_key, take_dispute_message, true).await?; + send_order_id_cmd(client, my_key, mostro_key, take_dispute_message, true, true).await?; Ok(()) } diff --git a/src/cli/take_sell.rs b/src/cli/take_sell.rs index 89e2f04..d8cb259 100644 --- a/src/cli/take_sell.rs +++ b/src/cli/take_sell.rs @@ -50,6 +50,6 @@ pub async fn execute_take_sell( .as_json() .unwrap(); - send_order_id_cmd(client, my_key, mostro_key, take_sell_message, true).await?; + send_order_id_cmd(client, my_key, mostro_key, take_sell_message, true, true).await?; Ok(()) } diff --git a/src/util.rs b/src/util.rs index 5c366f6..d91d09c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,6 +2,8 @@ use crate::nip33::{dispute_from_tags, order_from_tags}; use crate::nip59::{gift_wrap, unwrap_gift_wrap}; use anyhow::{Error, Result}; +use base64::engine::general_purpose; +use base64::Engine; use chrono::DateTime; use dotenvy::var; use log::{error, info}; @@ -14,6 +16,7 @@ use nostr_sdk::prelude::*; use std::time::Duration; use tokio::time::timeout; use uuid::Uuid; +use v2::{decrypt_to_bytes, encrypt_to_bytes, ConversationKey}; pub fn get_keys() -> Result { // nostr private key @@ -28,9 +31,26 @@ pub async fn send_dm( sender_keys: &Keys, receiver_pubkey: &PublicKey, content: String, + to_user: bool, ) -> Result<()> { let pow: u8 = var("POW").unwrap_or('0'.to_string()).parse().unwrap(); - let event = gift_wrap(sender_keys, *receiver_pubkey, content, None, pow)?; + let event = if to_user { + // Derive conversation key + let ck = ConversationKey::derive(sender_keys.secret_key()?, receiver_pubkey); + // Encrypt content + let encrypted_content = encrypt_to_bytes(&ck, content)?; + // Encode with base64 + let b64decoded_content = general_purpose::STANDARD.encode(encrypted_content); + // Compose builder + EventBuilder::new( + Kind::PrivateDirectMessage, + b64decoded_content, + [Tag::public_key(*receiver_pubkey)], + ) + .to_pow_event(sender_keys, pow)? + } else { + gift_wrap(sender_keys, *receiver_pubkey, content, None, pow)? + }; info!("Sending event: {event:#?}"); println!("Sending event Id: {}", event.id()); @@ -62,18 +82,20 @@ pub async fn connect_nostr() -> Result { pub async fn send_order_id_cmd( client: &Client, my_key: &Keys, - mostro_pubkey: PublicKey, + receiver_pubkey: PublicKey, message: String, wait_for_dm_ans: bool, + to_user: bool, ) -> Result<()> { - // Send dm to mostro pub id - send_dm(client, my_key, &mostro_pubkey, message).await?; + println!("to_user: {to_user}"); + // Send dm to receiver pubkey + send_dm(client, my_key, &receiver_pubkey, message, to_user).await?; let mut notifications = client.notifications(); while let Ok(notification) = notifications.recv().await { if wait_for_dm_ans { - let dm = get_direct_messages(client, my_key, 1).await; + let dm = get_direct_messages(client, my_key, 1, to_user).await; for el in dm.iter() { match Message::from_json(&el.0) { @@ -223,6 +245,7 @@ pub async fn get_direct_messages( client: &Client, my_key: &Keys, since: i64, + from_user: bool, ) -> Vec<(String, String, u64)> { // We use a fake timestamp to thwart time-analysis attacks let fake_since = 2880; @@ -232,10 +255,22 @@ pub async fn get_direct_messages( .timestamp() as u64; let fake_timestamp = Timestamp::from(fake_since_time); - let filters = Filter::new() - .kind(Kind::GiftWrap) - .pubkey(my_key.public_key()) - .since(fake_timestamp); + let filters = if from_user { + let since_time = chrono::Utc::now() + .checked_sub_signed(chrono::Duration::minutes(since)) + .unwrap() + .timestamp() as u64; + let timestamp = Timestamp::from(since_time); + Filter::new() + .kind(Kind::PrivateDirectMessage) + .pubkey(my_key.public_key()) + .since(timestamp) + } else { + Filter::new() + .kind(Kind::GiftWrap) + .pubkey(my_key.public_key()) + .since(fake_timestamp) + }; info!("Request events with event kind : {:?} ", filters.kinds); @@ -252,30 +287,43 @@ pub async fn get_direct_messages( for dm in dms { if !id_list.contains(&dm.id()) { id_list.push(dm.id()); - let unwrapped_gift = match unwrap_gift_wrap(Some(my_key), None, None, dm) { - Ok(u) => u, - Err(_) => { - continue; - } - }; + let created_at: Timestamp; + let content: String; + if from_user { + let ck = ConversationKey::derive(my_key.secret_key().unwrap(), &dm.pubkey); + let b64decoded_content = + match general_purpose::STANDARD.decode(dm.content.as_bytes()) { + Ok(b64decoded_content) => b64decoded_content, + Err(_) => { + continue; + } + }; + // Decrypt + let unencrypted_content = decrypt_to_bytes(&ck, b64decoded_content).unwrap(); + content = String::from_utf8(unencrypted_content).expect("Found invalid UTF-8"); + created_at = dm.created_at; + } else { + let unwrapped_gift = match unwrap_gift_wrap(Some(my_key), None, None, dm) { + Ok(u) => u, + Err(_) => { + continue; + } + }; + content = unwrapped_gift.rumor.content; + created_at = unwrapped_gift.rumor.created_at; + } // Here we discard messages older than the real since parameter let since_time = chrono::Utc::now() .checked_sub_signed(chrono::Duration::minutes(since)) .unwrap() .timestamp() as u64; - if unwrapped_gift.rumor.created_at.as_u64() < since_time { + if created_at.as_u64() < since_time { continue; } - let date = - DateTime::from_timestamp(unwrapped_gift.rumor.created_at.as_u64() as i64, 0); + let date = DateTime::from_timestamp(created_at.as_u64() as i64, 0); let human_date = date.unwrap().format("%H:%M:%S date - %d/%m/%Y").to_string(); - - direct_messages.push(( - unwrapped_gift.rumor.content, - human_date, - unwrapped_gift.rumor.created_at.as_u64(), - )); + direct_messages.push((content, human_date, created_at.as_u64())); } } }