1use std::str::FromStr;
24
25use crate::message::{Action, Message, Payload};
26use crate::prelude::{CantDoReason, MostroError, ServiceError};
27use nostr_sdk::nips::{nip44, nip59};
28use nostr_sdk::prelude::*;
29
30#[derive(Debug, Clone)]
32pub struct WrapOptions {
33 pub pow: u8,
35 pub expiration: Option<Timestamp>,
37 pub signed: bool,
42}
43
44impl Default for WrapOptions {
45 fn default() -> Self {
46 Self {
47 pow: 0,
48 expiration: None,
49 signed: true,
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
57pub struct UnwrappedMessage {
58 pub message: Message,
60 pub signature: Option<Signature>,
63 pub sender: PublicKey,
65 pub identity: PublicKey,
69 pub created_at: Timestamp,
71}
72
73pub async fn wrap_message(
85 message: &Message,
86 identity_keys: &Keys,
87 trade_keys: &Keys,
88 receiver: PublicKey,
89 opts: WrapOptions,
90) -> Result<Event, MostroError> {
91 let message_json = message.as_json().map_err(MostroError::MostroInternalErr)?;
92
93 let content = if opts.signed {
94 let sig = Message::sign(message_json, trade_keys);
95 serde_json::to_string(&(message, Some(sig.to_string())))
96 .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?
97 } else {
98 serde_json::to_string(&(message, Option::<String>::None))
99 .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?
100 };
101
102 let rumor = EventBuilder::text_note(content).build(trade_keys.public_key());
106
107 let seal: Event = EventBuilder::seal(identity_keys, &receiver, rumor)
112 .await
113 .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))?
114 .sign(identity_keys)
115 .await
116 .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))?;
117
118 gift_wrap_from_seal_with_pow(&seal, receiver, opts.pow, opts.expiration)
119}
120
121fn gift_wrap_from_seal_with_pow(
125 seal: &Event,
126 receiver: PublicKey,
127 pow: u8,
128 expiration: Option<Timestamp>,
129) -> Result<Event, MostroError> {
130 if seal.kind != Kind::Seal {
131 return Err(MostroError::MostroInternalErr(
132 ServiceError::UnexpectedError("expected Seal kind".to_string()),
133 ));
134 }
135
136 let ephemeral = Keys::generate();
137 let encrypted = nip44::encrypt(
138 ephemeral.secret_key(),
139 &receiver,
140 seal.as_json(),
141 nip44::Version::default(),
142 )
143 .map_err(|e| MostroError::MostroInternalErr(ServiceError::EncryptionError(e.to_string())))?;
144
145 let mut tags: Vec<Tag> = Vec::new();
146 if let Some(exp) = expiration {
147 tags.push(Tag::expiration(exp));
148 }
149 tags.push(Tag::public_key(receiver));
150
151 EventBuilder::new(Kind::GiftWrap, encrypted)
152 .tags(tags)
153 .custom_created_at(Timestamp::tweaked(nip59::RANGE_RANDOM_TIMESTAMP_TWEAK))
154 .pow(pow)
155 .sign_with_keys(&ephemeral)
156 .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))
157}
158
159pub async fn unwrap_message(
172 event: &Event,
173 receiver_keys: &Keys,
174) -> Result<Option<UnwrappedMessage>, MostroError> {
175 if event.kind != Kind::GiftWrap {
176 return Err(MostroError::MostroInternalErr(
177 ServiceError::UnexpectedError("event is not a GiftWrap".to_string()),
178 ));
179 }
180
181 let seal_json = match nip44::decrypt(receiver_keys.secret_key(), &event.pubkey, &event.content)
184 {
185 Ok(s) => s,
186 Err(_) => return Ok(None),
187 };
188
189 let seal: Event = Event::from_json(&seal_json).map_err(|e| {
190 MostroError::MostroInternalErr(ServiceError::NostrError(format!(
191 "malformed seal JSON: {e}"
192 )))
193 })?;
194
195 if seal.kind != Kind::Seal {
196 return Err(MostroError::MostroInternalErr(
197 ServiceError::UnexpectedError("inner event is not a Seal".to_string()),
198 ));
199 }
200
201 seal.verify_signature().then_some(()).ok_or_else(|| {
202 MostroError::MostroInternalErr(ServiceError::NostrError(
203 "invalid seal signature".to_string(),
204 ))
205 })?;
206
207 let rumor_json = nip44::decrypt(receiver_keys.secret_key(), &seal.pubkey, &seal.content)
211 .map_err(|e| {
212 MostroError::MostroInternalErr(ServiceError::DecryptionError(e.to_string()))
213 })?;
214
215 let rumor: UnsignedEvent = UnsignedEvent::from_json(&rumor_json).map_err(|e| {
216 MostroError::MostroInternalErr(ServiceError::NostrError(format!(
217 "malformed rumor JSON: {e}"
218 )))
219 })?;
220
221 if rumor.kind != Kind::TextNote {
222 return Err(MostroError::MostroInternalErr(
223 ServiceError::UnexpectedError("rumor is not a TextNote".to_string()),
224 ));
225 }
226
227 let (message, sig_str): (Message, Option<String>) = serde_json::from_str(&rumor.content)
228 .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?;
229
230 let signature = match sig_str {
231 Some(s) => {
232 let sig = Signature::from_str(&s).map_err(|e| {
233 MostroError::MostroInternalErr(ServiceError::UnexpectedError(format!(
234 "malformed rumor signature: {e}"
235 )))
236 })?;
237 let message_json = message.as_json().map_err(MostroError::MostroInternalErr)?;
238 if !Message::verify_signature(message_json, rumor.pubkey, sig) {
239 return Err(MostroError::MostroInternalErr(
240 ServiceError::UnexpectedError(
241 "rumor signature does not verify against sender".to_string(),
242 ),
243 ));
244 }
245 Some(sig)
246 }
247 None => None,
248 };
249
250 Ok(Some(UnwrappedMessage {
251 message,
252 signature,
253 sender: rumor.pubkey,
254 identity: seal.pubkey,
255 created_at: rumor.created_at,
256 }))
257}
258
259pub fn validate_response(
272 message: &Message,
273 expected_request_id: Option<u64>,
274) -> Result<(), MostroError> {
275 let inner = message.get_inner_message_kind();
276
277 if let Some(Payload::CantDo(reason)) = &inner.payload {
278 return Err(MostroError::MostroCantDo(
279 reason.clone().unwrap_or(CantDoReason::InvalidAction),
280 ));
281 }
282
283 if let Some(expected) = expected_request_id {
284 match inner.request_id {
285 Some(got) if got == expected => {}
286 Some(_) => {
287 return Err(MostroError::MostroInternalErr(
288 ServiceError::UnexpectedError("mismatched request_id".to_string()),
289 ));
290 }
291 None => {
292 if !action_accepts_missing_request_id(&inner.action) {
293 return Err(MostroError::MostroInternalErr(
294 ServiceError::UnexpectedError(
295 "missing request_id on a response that requires one".to_string(),
296 ),
297 ));
298 }
299 }
300 }
301 }
302
303 Ok(())
304}
305
306fn action_accepts_missing_request_id(action: &Action) -> bool {
309 matches!(
310 action,
311 Action::BuyerTookOrder
312 | Action::HoldInvoicePaymentAccepted
313 | Action::HoldInvoicePaymentSettled
314 | Action::HoldInvoicePaymentCanceled
315 | Action::WaitingSellerToPay
316 | Action::WaitingBuyerInvoice
317 | Action::BuyerInvoiceAccepted
318 | Action::PurchaseCompleted
319 | Action::Released
320 | Action::FiatSentOk
321 | Action::Canceled
322 | Action::CooperativeCancelInitiatedByPeer
323 | Action::CooperativeCancelAccepted
324 | Action::DisputeInitiatedByPeer
325 | Action::AdminSettled
326 | Action::AdminCanceled
327 | Action::AdminTookDispute
328 | Action::PaymentFailed
329 | Action::InvoiceUpdated
330 | Action::Rate
331 | Action::RateReceived
332 | Action::SendDm
333 )
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use crate::message::{Action, MessageKind, Payload};
340 use uuid::uuid;
341
342 fn sample_order_message(request_id: Option<u64>) -> Message {
343 let peer = crate::message::Peer::new(
344 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
345 None,
346 );
347 Message::Order(MessageKind::new(
348 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
349 request_id,
350 Some(1),
351 Action::FiatSentOk,
352 Some(Payload::Peer(peer)),
353 ))
354 }
355
356 #[tokio::test]
357 async fn wrap_then_unwrap_roundtrip() {
358 let identity_keys = Keys::generate();
359 let trade_keys = Keys::generate();
360 let receiver_keys = Keys::generate();
361
362 let message = sample_order_message(Some(42));
363
364 let wrapped = wrap_message(
365 &message,
366 &identity_keys,
367 &trade_keys,
368 receiver_keys.public_key(),
369 WrapOptions::default(),
370 )
371 .await
372 .expect("wrap");
373
374 assert_eq!(wrapped.kind, Kind::GiftWrap);
375 assert!(wrapped
376 .tags
377 .iter()
378 .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("p")));
379
380 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
381 .await
382 .expect("unwrap result")
383 .expect("unwrap some");
384
385 assert_eq!(unwrapped.sender, trade_keys.public_key());
386 assert_eq!(unwrapped.identity, identity_keys.public_key());
387 assert_eq!(
388 unwrapped.message.as_json().unwrap(),
389 message.as_json().unwrap()
390 );
391 assert!(unwrapped.signature.is_some());
392 }
393
394 #[tokio::test]
395 async fn full_privacy_mode_identity_equals_sender() {
396 let trade_keys = Keys::generate();
398 let receiver_keys = Keys::generate();
399
400 let wrapped = wrap_message(
401 &sample_order_message(Some(1)),
402 &trade_keys,
403 &trade_keys,
404 receiver_keys.public_key(),
405 WrapOptions::default(),
406 )
407 .await
408 .expect("wrap");
409
410 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
411 .await
412 .expect("unwrap")
413 .expect("some");
414
415 assert_eq!(unwrapped.sender, trade_keys.public_key());
416 assert_eq!(unwrapped.identity, trade_keys.public_key());
417 }
418
419 #[tokio::test]
420 async fn unwrap_with_corrupted_seal_returns_err() {
421 let receiver_keys = Keys::generate();
422 let ephemeral = Keys::generate();
423
424 let encrypted = nip44::encrypt(
428 ephemeral.secret_key(),
429 &receiver_keys.public_key(),
430 "not a seal",
431 nip44::Version::default(),
432 )
433 .expect("encrypt");
434
435 let corrupted = EventBuilder::new(Kind::GiftWrap, encrypted)
436 .tags([Tag::public_key(receiver_keys.public_key())])
437 .sign_with_keys(&ephemeral)
438 .expect("sign");
439
440 let result = unwrap_message(&corrupted, &receiver_keys).await;
441 assert!(
442 matches!(result, Err(MostroError::MostroInternalErr(_))),
443 "expected Err for corrupted gift wrap, got {result:?}",
444 );
445 }
446
447 async fn wrap_with_raw_inner(
451 identity_keys: &Keys,
452 trade_keys: &Keys,
453 receiver: PublicKey,
454 inner: (&Message, Option<String>),
455 ) -> Event {
456 let content = serde_json::to_string(&inner).unwrap();
457 let rumor = EventBuilder::text_note(content).build(trade_keys.public_key());
458 let seal = EventBuilder::seal(identity_keys, &receiver, rumor)
459 .await
460 .unwrap()
461 .sign(identity_keys)
462 .await
463 .unwrap();
464 gift_wrap_from_seal_with_pow(&seal, receiver, 0, None).unwrap()
465 }
466
467 #[tokio::test]
468 async fn unwrap_with_malformed_signature_errors() {
469 let identity_keys = Keys::generate();
470 let trade_keys = Keys::generate();
471 let receiver_keys = Keys::generate();
472 let msg = sample_order_message(Some(1));
473
474 let wrapped = wrap_with_raw_inner(
475 &identity_keys,
476 &trade_keys,
477 receiver_keys.public_key(),
478 (&msg, Some("not-a-hex-signature".to_string())),
479 )
480 .await;
481
482 let result = unwrap_message(&wrapped, &receiver_keys).await;
483 assert!(
484 matches!(result, Err(MostroError::MostroInternalErr(_))),
485 "malformed signature must surface as Err, got {result:?}",
486 );
487 }
488
489 #[tokio::test]
490 async fn unwrap_with_signature_for_other_content_errors() {
491 let identity_keys = Keys::generate();
492 let trade_keys = Keys::generate();
493 let receiver_keys = Keys::generate();
494 let msg = sample_order_message(Some(1));
495 let bogus = Message::sign("not the real message".to_string(), &trade_keys);
497
498 let wrapped = wrap_with_raw_inner(
499 &identity_keys,
500 &trade_keys,
501 receiver_keys.public_key(),
502 (&msg, Some(bogus.to_string())),
503 )
504 .await;
505
506 let result = unwrap_message(&wrapped, &receiver_keys).await;
507 assert!(
508 matches!(result, Err(MostroError::MostroInternalErr(_))),
509 "non-verifying signature must surface as Err, got {result:?}",
510 );
511 }
512
513 #[tokio::test]
514 async fn unwrap_with_wrong_receiver_keys_returns_none() {
515 let identity_keys = Keys::generate();
516 let trade_keys = Keys::generate();
517 let receiver_keys = Keys::generate();
518 let stranger_keys = Keys::generate();
519
520 let wrapped = wrap_message(
521 &sample_order_message(Some(1)),
522 &identity_keys,
523 &trade_keys,
524 receiver_keys.public_key(),
525 WrapOptions::default(),
526 )
527 .await
528 .expect("wrap");
529
530 let result = unwrap_message(&wrapped, &stranger_keys)
531 .await
532 .expect("call should not error");
533 assert!(result.is_none());
534 }
535
536 #[tokio::test]
537 async fn signature_is_verifiable_with_trade_pubkey() {
538 let identity_keys = Keys::generate();
539 let trade_keys = Keys::generate();
540 let receiver_keys = Keys::generate();
541 let message = sample_order_message(Some(7));
542
543 let wrapped = wrap_message(
544 &message,
545 &identity_keys,
546 &trade_keys,
547 receiver_keys.public_key(),
548 WrapOptions::default(),
549 )
550 .await
551 .unwrap();
552
553 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
554 .await
555 .unwrap()
556 .unwrap();
557
558 let sig = unwrapped.signature.expect("signed");
559 let json = unwrapped.message.as_json().unwrap();
560 assert!(Message::verify_signature(
561 json,
562 trade_keys.public_key(),
563 sig
564 ));
565 }
566
567 #[tokio::test]
568 async fn unsigned_wrap_has_no_signature() {
569 let identity_keys = Keys::generate();
570 let trade_keys = Keys::generate();
571 let receiver_keys = Keys::generate();
572
573 let wrapped = wrap_message(
574 &sample_order_message(Some(3)),
575 &identity_keys,
576 &trade_keys,
577 receiver_keys.public_key(),
578 WrapOptions {
579 signed: false,
580 ..WrapOptions::default()
581 },
582 )
583 .await
584 .expect("wrap");
585
586 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
587 .await
588 .unwrap()
589 .unwrap();
590 assert!(unwrapped.signature.is_none());
591 }
592
593 #[tokio::test]
594 async fn expiration_tag_is_set_when_provided() {
595 let identity_keys = Keys::generate();
596 let trade_keys = Keys::generate();
597 let receiver_keys = Keys::generate();
598 let exp = Timestamp::from_secs(Timestamp::now().as_secs() + 3600);
599
600 let wrapped = wrap_message(
601 &sample_order_message(Some(1)),
602 &identity_keys,
603 &trade_keys,
604 receiver_keys.public_key(),
605 WrapOptions {
606 expiration: Some(exp),
607 ..WrapOptions::default()
608 },
609 )
610 .await
611 .expect("wrap");
612
613 let has_expiration = wrapped
614 .tags
615 .iter()
616 .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("expiration"));
617 assert!(has_expiration);
618 }
619
620 #[test]
621 fn validate_response_cant_do_short_circuits() {
622 let msg = Message::cant_do(
623 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
624 Some(5),
625 Some(Payload::CantDo(Some(CantDoReason::NotAuthorized))),
626 );
627 let err = validate_response(&msg, Some(5)).unwrap_err();
628 match err {
629 MostroError::MostroCantDo(CantDoReason::NotAuthorized) => {}
630 _ => panic!("expected CantDo(NotAuthorized)"),
631 }
632 }
633
634 #[test]
635 fn validate_response_request_id_match() {
636 let msg = sample_order_message(Some(9));
637 validate_response(&msg, Some(9)).unwrap();
638 }
639
640 #[test]
641 fn validate_response_request_id_mismatch_errors() {
642 let msg = sample_order_message(Some(9));
643 let err = validate_response(&msg, Some(10)).unwrap_err();
644 assert!(matches!(err, MostroError::MostroInternalErr(_)));
645 }
646
647 #[test]
648 fn validate_response_allows_unsolicited_actions_without_request_id() {
649 let msg = Message::Order(MessageKind::new(
650 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
651 None,
652 None,
653 Action::BuyerTookOrder,
654 None,
655 ));
656 validate_response(&msg, Some(1)).unwrap();
657 }
658
659 #[test]
660 fn validate_response_with_no_expected_id_is_ok() {
661 let msg = sample_order_message(None);
662 validate_response(&msg, None).unwrap();
663 }
664}