contextvm_sdk/encryption/
mod.rs1use crate::core::constants::{EPHEMERAL_GIFT_WRAP_KIND, GIFT_WRAP_KIND};
7use crate::core::error::{Error, Result};
8use nostr_sdk::prelude::*;
9
10pub async fn encrypt_nip44<T>(
12 signer: &T,
13 receiver_pubkey: &PublicKey,
14 plaintext: &str,
15) -> Result<String>
16where
17 T: NostrSigner,
18{
19 signer
20 .nip44_encrypt(receiver_pubkey, plaintext)
21 .await
22 .map_err(|e| Error::Encryption(e.to_string()))
23}
24
25pub async fn decrypt_nip44<T>(
27 signer: &T,
28 sender_pubkey: &PublicKey,
29 ciphertext: &str,
30) -> Result<String>
31where
32 T: NostrSigner,
33{
34 signer
35 .nip44_decrypt(sender_pubkey, ciphertext)
36 .await
37 .map_err(|e| Error::Decryption(e.to_string()))
38}
39
40pub async fn decrypt_gift_wrap_single_layer<T>(signer: &T, event: &Event) -> Result<String>
43where
44 T: NostrSigner,
45{
46 let sender_pubkey = event.pubkey;
47 decrypt_nip44(signer, &sender_pubkey, &event.content).await
48}
49
50pub async fn gift_wrap_single_layer<T>(
53 _signer: &T,
54 recipient: &PublicKey,
55 plaintext: &str,
56) -> Result<Event>
57where
58 T: NostrSigner,
59{
60 let ephemeral = Keys::generate();
61
62 let encrypted = encrypt_nip44(&ephemeral, recipient, plaintext).await?;
63
64 let builder =
65 EventBuilder::new(Kind::Custom(GIFT_WRAP_KIND), encrypted).tag(Tag::public_key(*recipient));
66
67 builder
68 .sign_with_keys(&ephemeral)
69 .map_err(|e| Error::Encryption(e.to_string()))
70}
71
72pub async fn gift_wrap_single_layer_with_kind<T>(
77 _signer: &T,
78 recipient: &PublicKey,
79 plaintext: &str,
80 gift_wrap_kind: u16,
81) -> Result<Event>
82where
83 T: NostrSigner,
84{
85 if gift_wrap_kind != GIFT_WRAP_KIND && gift_wrap_kind != EPHEMERAL_GIFT_WRAP_KIND {
86 return Err(Error::Encryption(format!(
87 "Unsupported gift-wrap kind for single-layer encryption: {gift_wrap_kind}"
88 )));
89 }
90
91 let ephemeral = Keys::generate();
92
93 let encrypted = encrypt_nip44(&ephemeral, recipient, plaintext).await?;
94
95 let builder =
96 EventBuilder::new(Kind::Custom(gift_wrap_kind), encrypted).tag(Tag::public_key(*recipient));
97
98 builder
99 .sign_with_keys(&ephemeral)
100 .map_err(|e| Error::Encryption(e.to_string()))
101}
102
103#[deprecated(note = "Use decrypt_gift_wrap_single_layer for ContextVM compatibility")]
110pub async fn decrypt_gift_wrap(client: &Client, event: &Event) -> Result<UnsignedEvent> {
111 let unwrapped = client
112 .unwrap_gift_wrap(event)
113 .await
114 .map_err(|e| Error::Decryption(e.to_string()))?;
115 Ok(unwrapped.rumor)
116}
117
118#[deprecated(note = "Use gift_wrap_single_layer for ContextVM compatibility")]
122pub async fn gift_wrap(
123 client: &Client,
124 recipient: &PublicKey,
125 rumor: UnsignedEvent,
126) -> Result<EventId> {
127 let output = client
128 .gift_wrap(recipient, rumor, Vec::<Tag>::new())
129 .await
130 .map_err(|e| Error::Encryption(e.to_string()))?;
131 Ok(output.val)
132}
133
134#[cfg(test)]
135mod tests {
136 use crate::core::constants::{EPHEMERAL_GIFT_WRAP_KIND, GIFT_WRAP_KIND};
137
138 use super::*;
139
140 #[tokio::test]
141 async fn test_nip44_roundtrip() {
142 let keys1 = Keys::generate();
143 let keys2 = Keys::generate();
144
145 let plaintext = "Hello, ContextVM!";
146
147 let ciphertext = encrypt_nip44(&keys1, &keys2.public_key(), plaintext)
148 .await
149 .unwrap();
150
151 let decrypted = decrypt_nip44(&keys2, &keys1.public_key(), &ciphertext)
152 .await
153 .unwrap();
154
155 assert_eq!(plaintext, decrypted);
156 }
157
158 async fn create_simple_gift_wrap(plaintext: &str, recipient: &PublicKey) -> (Event, Keys) {
167 let ephemeral = Keys::generate();
168
169 let encrypted = encrypt_nip44(&ephemeral, recipient, plaintext)
171 .await
172 .unwrap();
173
174 let builder = EventBuilder::new(Kind::from(GIFT_WRAP_KIND), encrypted)
176 .tag(Tag::public_key(*recipient));
177
178 let event = builder.sign_with_keys(&ephemeral).unwrap();
179 (event, ephemeral)
180 }
181
182 #[tokio::test]
183 async fn test_decrypt_js_style_gift_wrap() {
184 let client_keys = Keys::generate();
189 let server_keys = Keys::generate();
190
191 let mcp_content = r#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
192
193 let inner_event = EventBuilder::new(Kind::Custom(25910), mcp_content)
195 .tag(Tag::public_key(server_keys.public_key()))
196 .sign_with_keys(&client_keys)
197 .unwrap();
198
199 let inner_json = serde_json::to_string(&inner_event).unwrap();
201
202 let (gift_wrap, _ephemeral) =
204 create_simple_gift_wrap(&inner_json, &server_keys.public_key()).await;
205
206 assert_eq!(gift_wrap.kind, Kind::Custom(1059));
207
208 let decrypted = decrypt_gift_wrap_single_layer(&server_keys, &gift_wrap)
210 .await
211 .unwrap();
212
213 let parsed: Event = serde_json::from_str(&decrypted).unwrap();
215 assert_eq!(parsed.pubkey, client_keys.public_key());
216 assert_eq!(parsed.content, mcp_content);
217 }
218
219 #[tokio::test]
220 async fn test_gift_wrap_roundtrip_single_layer() {
221 let sender_keys = Keys::generate();
222 let recipient_keys = Keys::generate();
223
224 let mcp_content = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
226 let inner_event = EventBuilder::new(Kind::Custom(25910), mcp_content)
227 .tag(Tag::public_key(recipient_keys.public_key()))
228 .sign_with_keys(&sender_keys)
229 .unwrap();
230 let inner_json = serde_json::to_string(&inner_event).unwrap();
231
232 let gift_wrap_event =
234 gift_wrap_single_layer(&sender_keys, &recipient_keys.public_key(), &inner_json)
235 .await
236 .unwrap();
237
238 assert_eq!(gift_wrap_event.kind, Kind::Custom(1059));
239
240 let decrypted = decrypt_gift_wrap_single_layer(&recipient_keys, &gift_wrap_event)
242 .await
243 .unwrap();
244
245 let parsed: Event = serde_json::from_str(&decrypted).unwrap();
246 assert_eq!(parsed.pubkey, sender_keys.public_key());
247 assert_eq!(parsed.content, mcp_content);
248 }
249
250 #[tokio::test]
251 async fn test_gift_wrap_has_correct_tags() {
252 let sender_keys = Keys::generate();
253 let recipient_keys = Keys::generate();
254
255 let gift_wrap_event =
256 gift_wrap_single_layer(&sender_keys, &recipient_keys.public_key(), "test")
257 .await
258 .unwrap();
259
260 let p_tags: Vec<_> = gift_wrap_event
262 .tags
263 .iter()
264 .filter(|t| t.kind() == TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::P)))
265 .collect();
266 assert_eq!(p_tags.len(), 1);
267
268 let p_value = p_tags[0].clone().to_vec();
269 assert_eq!(p_value[1], recipient_keys.public_key().to_hex());
270 }
271
272 #[tokio::test]
273 async fn test_gift_wrap_uses_ephemeral_key() {
274 let sender_keys = Keys::generate();
275 let recipient_keys = Keys::generate();
276
277 let gift_wrap_event =
278 gift_wrap_single_layer(&sender_keys, &recipient_keys.public_key(), "test")
279 .await
280 .unwrap();
281
282 assert_ne!(gift_wrap_event.pubkey, sender_keys.public_key());
285 }
286
287 #[tokio::test]
290 async fn test_forged_inner_event_detected_by_verify() {
291 let real_sender = Keys::generate();
292 let impersonated = Keys::generate();
293 let recipient = Keys::generate();
294
295 let mcp_content = r#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
296
297 let inner_event = EventBuilder::new(Kind::Custom(25910), mcp_content)
299 .tag(Tag::public_key(recipient.public_key()))
300 .sign_with_keys(&real_sender)
301 .unwrap();
302
303 let mut forged_json: serde_json::Value = serde_json::to_value(&inner_event).unwrap();
305 forged_json["pubkey"] = serde_json::Value::String(impersonated.public_key().to_hex());
306 let forged_str = serde_json::to_string(&forged_json).unwrap();
307
308 let (gift_wrap, _) = create_simple_gift_wrap(&forged_str, &recipient.public_key()).await;
310
311 let decrypted = decrypt_gift_wrap_single_layer(&recipient, &gift_wrap)
313 .await
314 .unwrap();
315 let parsed: Event = serde_json::from_str(&decrypted).unwrap();
316 assert_eq!(parsed.pubkey, impersonated.public_key());
317
318 assert!(
320 parsed.verify().is_err(),
321 "forged inner event must fail signature verification"
322 );
323 }
324
325 #[tokio::test]
326 async fn test_ephemeral_gift_wrap_roundtrip_single_layer() {
327 let sender_keys = Keys::generate();
328 let recipient_keys = Keys::generate();
329
330 let mcp_content = r#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
331 let inner_event = EventBuilder::new(Kind::Custom(25910), mcp_content)
332 .tag(Tag::public_key(recipient_keys.public_key()))
333 .sign_with_keys(&sender_keys)
334 .unwrap();
335 let inner_json = serde_json::to_string(&inner_event).unwrap();
336
337 let gift_wrap_event = gift_wrap_single_layer_with_kind(
338 &sender_keys,
339 &recipient_keys.public_key(),
340 &inner_json,
341 EPHEMERAL_GIFT_WRAP_KIND,
342 )
343 .await
344 .unwrap();
345
346 assert_eq!(gift_wrap_event.kind, Kind::Custom(EPHEMERAL_GIFT_WRAP_KIND));
347
348 let decrypted = decrypt_gift_wrap_single_layer(&recipient_keys, &gift_wrap_event)
349 .await
350 .unwrap();
351 let parsed: Event = serde_json::from_str(&decrypted).unwrap();
352 assert_eq!(parsed.pubkey, sender_keys.public_key());
353 assert_eq!(parsed.content, mcp_content);
354 }
355
356 #[tokio::test]
357 async fn test_invalid_gift_wrap_kind_rejected() {
358 let sender_keys = Keys::generate();
359 let recipient_keys = Keys::generate();
360
361 let error = gift_wrap_single_layer_with_kind(
362 &sender_keys,
363 &recipient_keys.public_key(),
364 "test",
365 4242,
366 )
367 .await
368 .unwrap_err();
369
370 assert!(
371 error.to_string().contains("Unsupported gift-wrap kind"),
372 "unexpected error: {error}"
373 );
374 }
375}