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