Skip to main content

mostro_core/chat/
wrap.rs

1//! Build a Mostro P2P chat gift wrap.
2//!
3//! The wire format is a non-standard NIP-59 envelope: a kind 1 text note
4//! signed by the sender's trade key is encrypted with NIP-44 v2 using an
5//! ephemeral key as the encryption side and the **shared key's public key**
6//! as the recipient. The outer kind 1059 event carries that ciphertext, a
7//! `p` tag pointing at the shared pubkey for relay routing, and is signed
8//! with the same ephemeral key.
9//!
10//! See <https://mostro.network/protocol/chat.html> for the protocol spec.
11
12use nostr_sdk::nips::{nip44, nip59};
13use nostr_sdk::prelude::*;
14
15use crate::error::{MostroError, ServiceError};
16
17/// Wrap a plain-text chat message into a Mostro P2P gift wrap (kind 1059).
18///
19/// * `sender_trade_keys` — the sender's per-trade keys; sign the inner kind 1
20///   so the receiver can verify authorship.
21/// * `shared_pubkey` — public key of the [`SharedKey`](super::SharedKey)
22///   shared by the two chat parties; used as the NIP-44 recipient and as
23///   the value of the outer `p` tag.
24/// * `message` — the chat content to deliver.
25///
26/// The outer `created_at` is randomized within
27/// [`nip59::RANGE_RANDOM_TIMESTAMP_TWEAK`] to defeat timing correlation.
28pub async fn wrap_chat_message(
29    sender_trade_keys: &Keys,
30    shared_pubkey: &PublicKey,
31    message: &str,
32) -> Result<Event, MostroError> {
33    let inner = EventBuilder::text_note(message)
34        .build(sender_trade_keys.public_key())
35        .sign(sender_trade_keys)
36        .await
37        .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))?;
38
39    let ephemeral = Keys::generate();
40    let encrypted = nip44::encrypt(
41        ephemeral.secret_key(),
42        shared_pubkey,
43        inner.as_json(),
44        nip44::Version::V2,
45    )
46    .map_err(|e| MostroError::MostroInternalErr(ServiceError::EncryptionError(e.to_string())))?;
47
48    EventBuilder::new(Kind::GiftWrap, encrypted)
49        .tag(Tag::public_key(*shared_pubkey))
50        .custom_created_at(Timestamp::tweaked(nip59::RANGE_RANDOM_TIMESTAMP_TWEAK))
51        .sign_with_keys(&ephemeral)
52        .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))
53}