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 | Action::BondSlashed
334 | Action::CashuEscrowLocked
335 | Action::CashuPmSignature
336 )
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use crate::message::{Action, MessageKind, Payload};
343 use uuid::uuid;
344
345 fn sample_order_message(request_id: Option<u64>) -> Message {
346 let peer = crate::message::Peer::new(
347 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
348 None,
349 );
350 Message::Order(MessageKind::new(
351 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
352 request_id,
353 Some(1),
354 Action::FiatSentOk,
355 Some(Payload::Peer(peer)),
356 ))
357 }
358
359 #[tokio::test]
360 async fn wrap_then_unwrap_roundtrip() {
361 let identity_keys = Keys::generate();
362 let trade_keys = Keys::generate();
363 let receiver_keys = Keys::generate();
364
365 let message = sample_order_message(Some(42));
366
367 let wrapped = wrap_message(
368 &message,
369 &identity_keys,
370 &trade_keys,
371 receiver_keys.public_key(),
372 WrapOptions::default(),
373 )
374 .await
375 .expect("wrap");
376
377 assert_eq!(wrapped.kind, Kind::GiftWrap);
378 assert!(wrapped
379 .tags
380 .iter()
381 .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("p")));
382
383 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
384 .await
385 .expect("unwrap result")
386 .expect("unwrap some");
387
388 assert_eq!(unwrapped.sender, trade_keys.public_key());
389 assert_eq!(unwrapped.identity, identity_keys.public_key());
390 assert_eq!(
391 unwrapped.message.as_json().unwrap(),
392 message.as_json().unwrap()
393 );
394 assert!(unwrapped.signature.is_some());
395 }
396
397 #[tokio::test]
398 async fn full_privacy_mode_identity_equals_sender() {
399 let trade_keys = Keys::generate();
401 let receiver_keys = Keys::generate();
402
403 let wrapped = wrap_message(
404 &sample_order_message(Some(1)),
405 &trade_keys,
406 &trade_keys,
407 receiver_keys.public_key(),
408 WrapOptions::default(),
409 )
410 .await
411 .expect("wrap");
412
413 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
414 .await
415 .expect("unwrap")
416 .expect("some");
417
418 assert_eq!(unwrapped.sender, trade_keys.public_key());
419 assert_eq!(unwrapped.identity, trade_keys.public_key());
420 }
421
422 #[tokio::test]
423 async fn unwrap_with_corrupted_seal_returns_err() {
424 let receiver_keys = Keys::generate();
425 let ephemeral = Keys::generate();
426
427 let encrypted = nip44::encrypt(
431 ephemeral.secret_key(),
432 &receiver_keys.public_key(),
433 "not a seal",
434 nip44::Version::default(),
435 )
436 .expect("encrypt");
437
438 let corrupted = EventBuilder::new(Kind::GiftWrap, encrypted)
439 .tags([Tag::public_key(receiver_keys.public_key())])
440 .sign_with_keys(&ephemeral)
441 .expect("sign");
442
443 let result = unwrap_message(&corrupted, &receiver_keys).await;
444 assert!(
445 matches!(result, Err(MostroError::MostroInternalErr(_))),
446 "expected Err for corrupted gift wrap, got {result:?}",
447 );
448 }
449
450 async fn wrap_with_raw_inner(
454 identity_keys: &Keys,
455 trade_keys: &Keys,
456 receiver: PublicKey,
457 inner: (&Message, Option<String>),
458 ) -> Event {
459 let content = serde_json::to_string(&inner).unwrap();
460 let rumor = EventBuilder::text_note(content).build(trade_keys.public_key());
461 let seal = EventBuilder::seal(identity_keys, &receiver, rumor)
462 .await
463 .unwrap()
464 .sign(identity_keys)
465 .await
466 .unwrap();
467 gift_wrap_from_seal_with_pow(&seal, receiver, 0, None).unwrap()
468 }
469
470 #[tokio::test]
471 async fn unwrap_with_malformed_signature_errors() {
472 let identity_keys = Keys::generate();
473 let trade_keys = Keys::generate();
474 let receiver_keys = Keys::generate();
475 let msg = sample_order_message(Some(1));
476
477 let wrapped = wrap_with_raw_inner(
478 &identity_keys,
479 &trade_keys,
480 receiver_keys.public_key(),
481 (&msg, Some("not-a-hex-signature".to_string())),
482 )
483 .await;
484
485 let result = unwrap_message(&wrapped, &receiver_keys).await;
486 assert!(
487 matches!(result, Err(MostroError::MostroInternalErr(_))),
488 "malformed signature must surface as Err, got {result:?}",
489 );
490 }
491
492 #[tokio::test]
493 async fn unwrap_with_signature_for_other_content_errors() {
494 let identity_keys = Keys::generate();
495 let trade_keys = Keys::generate();
496 let receiver_keys = Keys::generate();
497 let msg = sample_order_message(Some(1));
498 let bogus = Message::sign("not the real message".to_string(), &trade_keys);
500
501 let wrapped = wrap_with_raw_inner(
502 &identity_keys,
503 &trade_keys,
504 receiver_keys.public_key(),
505 (&msg, Some(bogus.to_string())),
506 )
507 .await;
508
509 let result = unwrap_message(&wrapped, &receiver_keys).await;
510 assert!(
511 matches!(result, Err(MostroError::MostroInternalErr(_))),
512 "non-verifying signature must surface as Err, got {result:?}",
513 );
514 }
515
516 #[tokio::test]
517 async fn unwrap_with_wrong_receiver_keys_returns_none() {
518 let identity_keys = Keys::generate();
519 let trade_keys = Keys::generate();
520 let receiver_keys = Keys::generate();
521 let stranger_keys = Keys::generate();
522
523 let wrapped = wrap_message(
524 &sample_order_message(Some(1)),
525 &identity_keys,
526 &trade_keys,
527 receiver_keys.public_key(),
528 WrapOptions::default(),
529 )
530 .await
531 .expect("wrap");
532
533 let result = unwrap_message(&wrapped, &stranger_keys)
534 .await
535 .expect("call should not error");
536 assert!(result.is_none());
537 }
538
539 #[tokio::test]
540 async fn signature_is_verifiable_with_trade_pubkey() {
541 let identity_keys = Keys::generate();
542 let trade_keys = Keys::generate();
543 let receiver_keys = Keys::generate();
544 let message = sample_order_message(Some(7));
545
546 let wrapped = wrap_message(
547 &message,
548 &identity_keys,
549 &trade_keys,
550 receiver_keys.public_key(),
551 WrapOptions::default(),
552 )
553 .await
554 .unwrap();
555
556 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
557 .await
558 .unwrap()
559 .unwrap();
560
561 let sig = unwrapped.signature.expect("signed");
562 let json = unwrapped.message.as_json().unwrap();
563 assert!(Message::verify_signature(
564 json,
565 trade_keys.public_key(),
566 sig
567 ));
568 }
569
570 #[tokio::test]
571 async fn unsigned_wrap_has_no_signature() {
572 let identity_keys = Keys::generate();
573 let trade_keys = Keys::generate();
574 let receiver_keys = Keys::generate();
575
576 let wrapped = wrap_message(
577 &sample_order_message(Some(3)),
578 &identity_keys,
579 &trade_keys,
580 receiver_keys.public_key(),
581 WrapOptions {
582 signed: false,
583 ..WrapOptions::default()
584 },
585 )
586 .await
587 .expect("wrap");
588
589 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
590 .await
591 .unwrap()
592 .unwrap();
593 assert!(unwrapped.signature.is_none());
594 }
595
596 #[tokio::test]
597 async fn expiration_tag_is_set_when_provided() {
598 let identity_keys = Keys::generate();
599 let trade_keys = Keys::generate();
600 let receiver_keys = Keys::generate();
601 let exp = Timestamp::from_secs(Timestamp::now().as_secs() + 3600);
602
603 let wrapped = wrap_message(
604 &sample_order_message(Some(1)),
605 &identity_keys,
606 &trade_keys,
607 receiver_keys.public_key(),
608 WrapOptions {
609 expiration: Some(exp),
610 ..WrapOptions::default()
611 },
612 )
613 .await
614 .expect("wrap");
615
616 let has_expiration = wrapped
617 .tags
618 .iter()
619 .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("expiration"));
620 assert!(has_expiration);
621 }
622
623 #[test]
624 fn validate_response_cant_do_short_circuits() {
625 let msg = Message::cant_do(
626 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
627 Some(5),
628 Some(Payload::CantDo(Some(CantDoReason::NotAuthorized))),
629 );
630 let err = validate_response(&msg, Some(5)).unwrap_err();
631 match err {
632 MostroError::MostroCantDo(CantDoReason::NotAuthorized) => {}
633 _ => panic!("expected CantDo(NotAuthorized)"),
634 }
635 }
636
637 #[test]
638 fn validate_response_request_id_match() {
639 let msg = sample_order_message(Some(9));
640 validate_response(&msg, Some(9)).unwrap();
641 }
642
643 #[test]
644 fn validate_response_request_id_mismatch_errors() {
645 let msg = sample_order_message(Some(9));
646 let err = validate_response(&msg, Some(10)).unwrap_err();
647 assert!(matches!(err, MostroError::MostroInternalErr(_)));
648 }
649
650 #[test]
651 fn validate_response_allows_unsolicited_actions_without_request_id() {
652 let msg = Message::Order(MessageKind::new(
653 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
654 None,
655 None,
656 Action::BuyerTookOrder,
657 None,
658 ));
659 validate_response(&msg, Some(1)).unwrap();
660 }
661
662 #[test]
663 fn validate_response_with_no_expected_id_is_ok() {
664 let msg = sample_order_message(None);
665 validate_response(&msg, None).unwrap();
666 }
667
668 #[test]
669 fn validate_response_allows_cashu_server_events_without_request_id() {
670 for action in [Action::CashuEscrowLocked, Action::CashuPmSignature] {
673 let msg = Message::Order(MessageKind::new(
674 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
675 None,
676 None,
677 action,
678 None,
679 ));
680 validate_response(&msg, Some(1)).unwrap();
681 }
682 }
683}