Skip to main content

mostro_core/chat/
unwrap.rs

1//! Decrypt a Mostro P2P chat gift wrap and verify its inner signature.
2
3use nostr_sdk::nips::nip44;
4use nostr_sdk::prelude::*;
5
6use crate::error::{MostroError, ServiceError};
7
8/// A decrypted P2P chat message.
9#[derive(Debug, Clone)]
10pub struct ChatMessage {
11    /// Plain-text body of the inner kind 1 event.
12    pub content: String,
13    /// Trade public key of the sender, taken from the inner event's
14    /// signature — already verified by [`unwrap_chat_message`].
15    pub sender: PublicKey,
16    /// `created_at` of the inner kind 1 event.
17    pub created_at: Timestamp,
18}
19
20/// Unwrap a Mostro P2P chat gift wrap.
21///
22/// * `shared_keys` — the `Keys` instance derived from the channel's
23///   [`SharedKey`](super::SharedKey); the secret half is needed to NIP-44
24///   decrypt the outer ciphertext.
25/// * `event` — a kind 1059 event previously fetched from a relay.
26///
27/// On success the inner kind 1 event's signature is verified before
28/// returning, so the [`ChatMessage::sender`] field can be trusted as the
29/// authentic author of the message.
30pub async fn unwrap_chat_message(
31    shared_keys: &Keys,
32    event: &Event,
33) -> Result<ChatMessage, MostroError> {
34    if event.kind != Kind::GiftWrap {
35        return Err(MostroError::MostroInternalErr(
36            ServiceError::UnexpectedError("event is not a GiftWrap".to_string()),
37        ));
38    }
39
40    let decrypted = nip44::decrypt(shared_keys.secret_key(), &event.pubkey, &event.content)
41        .map_err(|e| {
42            MostroError::MostroInternalErr(ServiceError::DecryptionError(format!(
43                "shared-key decrypt failed: {e}"
44            )))
45        })?;
46
47    let inner = Event::from_json(&decrypted).map_err(|e| {
48        MostroError::MostroInternalErr(ServiceError::NostrError(format!(
49            "malformed inner chat event: {e}"
50        )))
51    })?;
52
53    if inner.kind != Kind::TextNote {
54        return Err(MostroError::MostroInternalErr(
55            ServiceError::UnexpectedError("inner chat event is not a TextNote".to_string()),
56        ));
57    }
58
59    inner.verify().map_err(|e| {
60        MostroError::MostroInternalErr(ServiceError::NostrError(format!(
61            "invalid inner chat signature: {e}"
62        )))
63    })?;
64
65    Ok(ChatMessage {
66        content: inner.content,
67        sender: inner.pubkey,
68        created_at: inner.created_at,
69    })
70}