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