1mod filter;
50mod shared_key;
51mod unwrap;
52mod wrap;
53
54pub use filter::{chat_filter, CHAT_DEFAULT_LOOKBACK_SECS};
55pub use shared_key::SharedKey;
56pub use unwrap::{unwrap_chat_message, ChatMessage};
57pub use wrap::wrap_chat_message;
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use nostr_sdk::nips::nip44;
63 use nostr_sdk::prelude::*;
64
65 fn shared_pair() -> (Keys, Keys, SharedKey, SharedKey) {
66 let alice = Keys::generate();
67 let bob = Keys::generate();
68 let alice_shared = SharedKey::derive(alice.secret_key(), &bob.public_key()).unwrap();
69 let bob_shared = SharedKey::derive(bob.secret_key(), &alice.public_key()).unwrap();
70 (alice, bob, alice_shared, bob_shared)
71 }
72
73 #[tokio::test]
74 async fn wrap_and_unwrap_roundtrip() {
75 let (alice, _bob, alice_shared, bob_shared) = shared_pair();
76 let body = "hello from alice";
77
78 let event = wrap_chat_message(&alice, &alice_shared.public_key(), body)
79 .await
80 .expect("wrap");
81
82 assert_eq!(event.kind, Kind::GiftWrap);
83 assert!(event
84 .tags
85 .public_keys()
86 .any(|pk| *pk == alice_shared.public_key()));
87
88 let decoded = unwrap_chat_message(bob_shared.keys(), &event)
89 .await
90 .expect("unwrap");
91
92 assert_eq!(decoded.content, body);
93 assert_eq!(decoded.sender, alice.public_key());
94 }
95
96 #[tokio::test]
97 async fn unwrap_with_wrong_shared_key_fails() {
98 let (alice, _bob, alice_shared, _bob_shared) = shared_pair();
99 let intruder = Keys::generate();
100 let intruder_shared =
101 SharedKey::derive(intruder.secret_key(), &Keys::generate().public_key()).unwrap();
102
103 let event = wrap_chat_message(&alice, &alice_shared.public_key(), "for bob only")
104 .await
105 .expect("wrap");
106
107 let err = unwrap_chat_message(intruder_shared.keys(), &event)
108 .await
109 .expect_err("must not decrypt with foreign shared key");
110 assert!(matches!(
111 err,
112 crate::error::MostroError::MostroInternalErr(_)
113 ));
114 }
115
116 #[tokio::test]
117 async fn unwrap_tampered_event_fails() {
118 let (_alice, _bob, alice_shared, bob_shared) = shared_pair();
119
120 let impostor = Keys::generate();
125 let inner = EventBuilder::text_note("forged")
126 .build(impostor.public_key())
127 .sign(&impostor)
128 .await
129 .unwrap();
130
131 let mut json: serde_json::Value = serde_json::from_str(&inner.as_json()).unwrap();
134 json["content"] = serde_json::Value::String("tampered".to_string());
135 let tampered_inner = json.to_string();
136
137 let ephemeral = Keys::generate();
138 let encrypted = nip44::encrypt(
139 ephemeral.secret_key(),
140 &alice_shared.public_key(),
141 tampered_inner,
142 nip44::Version::V2,
143 )
144 .unwrap();
145 let event = EventBuilder::new(Kind::GiftWrap, encrypted)
146 .tag(Tag::public_key(alice_shared.public_key()))
147 .sign_with_keys(&ephemeral)
148 .unwrap();
149
150 let err = unwrap_chat_message(bob_shared.keys(), &event)
151 .await
152 .expect_err("tampered inner must not verify");
153 assert!(matches!(
154 err,
155 crate::error::MostroError::MostroInternalErr(_)
156 ));
157 }
158
159 #[tokio::test]
160 async fn unwrap_rejects_non_giftwrap_event() {
161 let alice = Keys::generate();
162 let other = Keys::generate();
163 let shared = SharedKey::derive(alice.secret_key(), &other.public_key()).unwrap();
164
165 let bogus = EventBuilder::text_note("not a wrap")
167 .build(alice.public_key())
168 .sign(&alice)
169 .await
170 .unwrap();
171
172 let err = unwrap_chat_message(shared.keys(), &bogus)
173 .await
174 .expect_err("non-giftwrap must error");
175 assert!(matches!(
176 err,
177 crate::error::MostroError::MostroInternalErr(_)
178 ));
179 }
180}