1use std::str::FromStr;
43
44use crate::message::Message;
45use crate::nip59::{self, UnwrappedMessage, WrapOptions};
46use crate::prelude::{MostroError, ServiceError};
47use nostr_sdk::nips::nip44;
48use nostr_sdk::prelude::*;
49use serde::{Deserialize, Serialize};
50
51type DirectTuple = (Message, Option<String>, Option<(String, String)>);
54
55fn identity_proof_payload(trade_pubkey: &PublicKey, message_json: &str) -> String {
64 format!(
65 "mostro-transport-v2-identity:{}:{}",
66 trade_pubkey.to_hex(),
67 message_json
68 )
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76pub enum Transport {
77 #[deprecated(
85 note = "protocol v1 (gift-wrap) is being phased out — mostrod v0.19.0 runs protocol v2 (nip44) only; see https://github.com/MostroP2P/mostro/issues/786"
86 )]
87 #[serde(rename = "gift-wrap")]
88 GiftWrap,
89 #[serde(rename = "nip44")]
91 Nip44Direct,
92}
93
94impl Default for Transport {
99 #[allow(deprecated)]
100 fn default() -> Self {
101 Transport::GiftWrap
102 }
103}
104
105#[allow(deprecated)]
108impl Transport {
109 pub fn event_kind(&self) -> Kind {
111 match self {
112 Transport::GiftWrap => Kind::GiftWrap,
113 Transport::Nip44Direct => Kind::PrivateDirectMessage,
114 }
115 }
116
117 pub fn protocol_version(&self) -> u8 {
120 match self {
121 Transport::GiftWrap => 1,
122 Transport::Nip44Direct => 2,
123 }
124 }
125}
126
127#[allow(deprecated)]
128impl FromStr for Transport {
129 type Err = MostroError;
130
131 fn from_str(s: &str) -> Result<Self, Self::Err> {
132 match s {
133 "gift-wrap" => Ok(Transport::GiftWrap),
134 "nip44" => Ok(Transport::Nip44Direct),
135 other => Err(MostroError::MostroInternalErr(
136 ServiceError::UnexpectedError(format!(
137 "unknown transport {other:?}; expected \"gift-wrap\" or \"nip44\""
138 )),
139 )),
140 }
141 }
142}
143
144#[allow(deprecated)]
145impl std::fmt::Display for Transport {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 let s = match self {
148 Transport::GiftWrap => "gift-wrap",
149 Transport::Nip44Direct => "nip44",
150 };
151 write!(f, "{s}")
152 }
153}
154
155pub fn wrap_message_nip44(
175 message: &Message,
176 identity_keys: &Keys,
177 trade_keys: &Keys,
178 receiver: PublicKey,
179 opts: WrapOptions,
180) -> Result<Event, MostroError> {
181 let message_json = message.as_json().map_err(MostroError::MostroInternalErr)?;
182
183 let trade_sig = opts
184 .signed
185 .then(|| Message::sign(message_json.clone(), trade_keys).to_string());
186
187 let identity_proof = (identity_keys.public_key() != trade_keys.public_key()).then(|| {
188 let payload = identity_proof_payload(&trade_keys.public_key(), &message_json);
189 (
190 identity_keys.public_key().to_hex(),
191 Message::sign(payload, identity_keys).to_string(),
192 )
193 });
194
195 let tuple: (&Message, Option<String>, Option<(String, String)>) =
196 (message, trade_sig, identity_proof);
197 let content = serde_json::to_string(&tuple)
198 .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?;
199
200 let encrypted = nip44::encrypt(
201 trade_keys.secret_key(),
202 &receiver,
203 content,
204 nip44::Version::default(),
205 )
206 .map_err(|e| MostroError::MostroInternalErr(ServiceError::EncryptionError(e.to_string())))?;
207
208 let mut tags: Vec<Tag> = vec![Tag::public_key(receiver)];
209 if let Some(exp) = opts.expiration {
210 tags.push(Tag::expiration(exp));
211 }
212
213 EventBuilder::new(Kind::PrivateDirectMessage, encrypted)
214 .tags(tags)
215 .pow(opts.pow)
216 .sign_with_keys(trade_keys)
217 .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))
218}
219
220pub fn unwrap_message_nip44(
233 event: &Event,
234 receiver_keys: &Keys,
235) -> Result<Option<UnwrappedMessage>, MostroError> {
236 if event.kind != Kind::PrivateDirectMessage {
237 return Err(MostroError::MostroInternalErr(
238 ServiceError::UnexpectedError("event is not a direct message".to_string()),
239 ));
240 }
241
242 event.verify().map_err(|_| {
246 MostroError::MostroInternalErr(ServiceError::NostrError(
247 "invalid event signature".to_string(),
248 ))
249 })?;
250
251 let plaintext = match nip44::decrypt(receiver_keys.secret_key(), &event.pubkey, &event.content)
254 {
255 Ok(p) => p,
256 Err(_) => return Ok(None),
257 };
258
259 let (message, trade_sig, identity_proof): DirectTuple = serde_json::from_str(&plaintext)
260 .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?;
261
262 let message_json = message.as_json().map_err(MostroError::MostroInternalErr)?;
263
264 let signature = match trade_sig {
265 Some(s) => {
266 let sig = Signature::from_str(&s).map_err(|e| {
267 MostroError::MostroInternalErr(ServiceError::UnexpectedError(format!(
268 "malformed trade signature: {e}"
269 )))
270 })?;
271 if !Message::verify_signature(message_json.clone(), event.pubkey, sig) {
272 return Err(MostroError::MostroInternalErr(
273 ServiceError::UnexpectedError(
274 "trade signature does not verify against event author".to_string(),
275 ),
276 ));
277 }
278 Some(sig)
279 }
280 None => None,
281 };
282
283 let identity = match identity_proof {
284 Some((pubkey, sig)) => {
285 let identity_pubkey = PublicKey::from_str(&pubkey).map_err(|e| {
286 MostroError::MostroInternalErr(ServiceError::UnexpectedError(format!(
287 "malformed identity pubkey: {e}"
288 )))
289 })?;
290 let identity_sig = Signature::from_str(&sig).map_err(|e| {
291 MostroError::MostroInternalErr(ServiceError::UnexpectedError(format!(
292 "malformed identity signature: {e}"
293 )))
294 })?;
295 let payload = identity_proof_payload(&event.pubkey, &message_json);
296 if !Message::verify_signature(payload, identity_pubkey, identity_sig) {
297 return Err(MostroError::MostroInternalErr(
298 ServiceError::UnexpectedError(
299 "identity signature does not verify against identity pubkey".to_string(),
300 ),
301 ));
302 }
303 identity_pubkey
304 }
305 None => event.pubkey,
306 };
307
308 Ok(Some(UnwrappedMessage {
309 message,
310 signature,
311 sender: event.pubkey,
312 identity,
313 created_at: event.created_at,
314 }))
315}
316
317pub async fn wrap_message_with(
321 transport: Transport,
322 message: &Message,
323 identity_keys: &Keys,
324 trade_keys: &Keys,
325 receiver: PublicKey,
326 opts: WrapOptions,
327) -> Result<Event, MostroError> {
328 #[allow(deprecated)]
331 match transport {
332 Transport::GiftWrap => {
333 nip59::wrap_message(message, identity_keys, trade_keys, receiver, opts).await
334 }
335 Transport::Nip44Direct => {
336 wrap_message_nip44(message, identity_keys, trade_keys, receiver, opts)
337 }
338 }
339}
340
341pub async fn unwrap_incoming(
351 event: &Event,
352 receiver_keys: &Keys,
353) -> Result<Option<UnwrappedMessage>, MostroError> {
354 match event.kind {
355 Kind::GiftWrap => nip59::unwrap_message(event, receiver_keys).await,
356 Kind::PrivateDirectMessage => unwrap_message_nip44(event, receiver_keys),
357 other => Err(MostroError::MostroInternalErr(
358 ServiceError::UnexpectedError(format!("no Mostro transport for event kind {other}")),
359 )),
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366 use crate::message::{Action, MessageKind, Payload, Peer};
367 use crate::nip59::wrap_message;
368 use uuid::uuid;
369
370 fn sample_order_message(request_id: Option<u64>) -> Message {
371 let peer = Peer::new(
372 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
373 None,
374 );
375 Message::Order(MessageKind::new(
376 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
377 request_id,
378 Some(1),
379 Action::FiatSentOk,
380 Some(Payload::Peer(peer)),
381 ))
382 }
383
384 fn wrap_raw_nip44(trade_keys: &Keys, receiver: PublicKey, plaintext: &str) -> Event {
387 let encrypted = nip44::encrypt(
388 trade_keys.secret_key(),
389 &receiver,
390 plaintext,
391 nip44::Version::default(),
392 )
393 .expect("encrypt");
394 EventBuilder::new(Kind::PrivateDirectMessage, encrypted)
395 .tags([Tag::public_key(receiver)])
396 .sign_with_keys(trade_keys)
397 .expect("sign")
398 }
399
400 #[test]
401 fn nip44_roundtrip_reputation_mode() {
402 let identity_keys = Keys::generate();
403 let trade_keys = Keys::generate();
404 let receiver_keys = Keys::generate();
405 let message = sample_order_message(Some(42));
406
407 let event = wrap_message_nip44(
408 &message,
409 &identity_keys,
410 &trade_keys,
411 receiver_keys.public_key(),
412 WrapOptions::default(),
413 )
414 .expect("wrap");
415
416 assert_eq!(event.kind, Kind::PrivateDirectMessage);
417 assert_eq!(event.pubkey, trade_keys.public_key());
418 assert!(event
419 .tags
420 .iter()
421 .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("p")));
422
423 let unwrapped = unwrap_message_nip44(&event, &receiver_keys)
424 .expect("unwrap result")
425 .expect("unwrap some");
426
427 assert_eq!(unwrapped.sender, trade_keys.public_key());
428 assert_eq!(unwrapped.identity, identity_keys.public_key());
429 assert!(unwrapped.signature.is_some());
430 assert_eq!(
431 unwrapped.message.as_json().unwrap(),
432 message.as_json().unwrap()
433 );
434 }
435
436 #[test]
437 fn nip44_full_privacy_identity_equals_sender() {
438 let trade_keys = Keys::generate();
439 let receiver_keys = Keys::generate();
440
441 let event = wrap_message_nip44(
442 &sample_order_message(Some(1)),
443 &trade_keys,
444 &trade_keys,
445 receiver_keys.public_key(),
446 WrapOptions {
447 signed: false,
448 ..WrapOptions::default()
449 },
450 )
451 .expect("wrap");
452
453 let unwrapped = unwrap_message_nip44(&event, &receiver_keys)
454 .expect("unwrap")
455 .expect("some");
456
457 assert_eq!(unwrapped.sender, trade_keys.public_key());
458 assert_eq!(unwrapped.identity, trade_keys.public_key());
459 assert!(unwrapped.signature.is_none());
460 }
461
462 #[test]
463 fn nip44_messages_are_stamped_protocol_v2() {
464 let trade_keys = Keys::generate();
465 let receiver_keys = Keys::generate();
466
467 let event = wrap_message_nip44(
468 &sample_order_message(Some(1)),
469 &trade_keys,
470 &trade_keys,
471 receiver_keys.public_key(),
472 WrapOptions::default(),
473 )
474 .expect("wrap");
475
476 let unwrapped = unwrap_message_nip44(&event, &receiver_keys)
477 .expect("unwrap")
478 .expect("some");
479 assert_eq!(unwrapped.message.get_inner_message_kind().version, 2);
480 }
481
482 #[test]
483 fn nip44_wrong_receiver_returns_none() {
484 let trade_keys = Keys::generate();
485 let receiver_keys = Keys::generate();
486 let stranger_keys = Keys::generate();
487
488 let event = wrap_message_nip44(
489 &sample_order_message(Some(1)),
490 &trade_keys,
491 &trade_keys,
492 receiver_keys.public_key(),
493 WrapOptions::default(),
494 )
495 .expect("wrap");
496
497 let result = unwrap_message_nip44(&event, &stranger_keys).expect("call should not error");
498 assert!(result.is_none());
499 }
500
501 #[test]
502 fn nip44_forged_identity_proof_errors() {
503 let identity_keys = Keys::generate();
504 let trade_keys = Keys::generate();
505 let receiver_keys = Keys::generate();
506 let message = sample_order_message(Some(1));
507
508 let bogus_sig = Message::sign("not the real message".to_string(), &identity_keys);
510 let tuple: (&Message, Option<String>, Option<(String, String)>) = (
511 &message,
512 None,
513 Some((identity_keys.public_key().to_hex(), bogus_sig.to_string())),
514 );
515 let plaintext = serde_json::to_string(&tuple).unwrap();
516 let event = wrap_raw_nip44(&trade_keys, receiver_keys.public_key(), &plaintext);
517
518 let result = unwrap_message_nip44(&event, &receiver_keys);
519 assert!(
520 matches!(result, Err(MostroError::MostroInternalErr(_))),
521 "forged identity proof must surface as Err, got {result:?}",
522 );
523 }
524
525 #[test]
526 fn nip44_identity_proof_grafted_onto_other_trade_key_errors() {
527 let identity_keys = Keys::generate();
532 let trade_keys = Keys::generate();
533 let attacker_trade_keys = Keys::generate();
534 let receiver_keys = Keys::generate();
535 let message = sample_order_message(Some(1));
536 let message_json = message.as_json().unwrap();
537
538 let payload = identity_proof_payload(&trade_keys.public_key(), &message_json);
540 let legit_sig = Message::sign(payload, &identity_keys);
541
542 let tuple: (&Message, Option<String>, Option<(String, String)>) = (
544 &message,
545 None,
546 Some((identity_keys.public_key().to_hex(), legit_sig.to_string())),
547 );
548 let plaintext = serde_json::to_string(&tuple).unwrap();
549 let event = wrap_raw_nip44(&attacker_trade_keys, receiver_keys.public_key(), &plaintext);
550
551 let result = unwrap_message_nip44(&event, &receiver_keys);
552 assert!(
553 matches!(result, Err(MostroError::MostroInternalErr(_))),
554 "grafted identity proof must surface as Err, got {result:?}",
555 );
556
557 let event = wrap_raw_nip44(&trade_keys, receiver_keys.public_key(), &plaintext);
559 let unwrapped = unwrap_message_nip44(&event, &receiver_keys)
560 .expect("unwrap")
561 .expect("some");
562 assert_eq!(unwrapped.identity, identity_keys.public_key());
563 }
564
565 #[test]
566 fn nip44_trade_sig_from_other_key_errors() {
567 let trade_keys = Keys::generate();
568 let other_keys = Keys::generate();
569 let receiver_keys = Keys::generate();
570 let message = sample_order_message(Some(1));
571 let message_json = message.as_json().unwrap();
572
573 let foreign_sig = Message::sign(message_json, &other_keys);
575 let tuple: (&Message, Option<String>, Option<(String, String)>) =
576 (&message, Some(foreign_sig.to_string()), None);
577 let plaintext = serde_json::to_string(&tuple).unwrap();
578 let event = wrap_raw_nip44(&trade_keys, receiver_keys.public_key(), &plaintext);
579
580 let result = unwrap_message_nip44(&event, &receiver_keys);
581 assert!(
582 matches!(result, Err(MostroError::MostroInternalErr(_))),
583 "foreign trade signature must surface as Err, got {result:?}",
584 );
585 }
586
587 #[test]
588 fn nip44_malformed_tuple_errors() {
589 let trade_keys = Keys::generate();
590 let receiver_keys = Keys::generate();
591
592 let event = wrap_raw_nip44(&trade_keys, receiver_keys.public_key(), "not a tuple");
594
595 let result = unwrap_message_nip44(&event, &receiver_keys);
596 assert!(
597 matches!(result, Err(MostroError::MostroInternalErr(_))),
598 "malformed tuple must surface as Err, got {result:?}",
599 );
600 }
601
602 #[test]
603 fn nip44_expiration_tag_is_set_when_provided() {
604 let trade_keys = Keys::generate();
605 let receiver_keys = Keys::generate();
606 let exp = Timestamp::from_secs(Timestamp::now().as_secs() + 3600);
607
608 let event = wrap_message_nip44(
609 &sample_order_message(Some(1)),
610 &trade_keys,
611 &trade_keys,
612 receiver_keys.public_key(),
613 WrapOptions {
614 expiration: Some(exp),
615 ..WrapOptions::default()
616 },
617 )
618 .expect("wrap");
619
620 let has_expiration = event
621 .tags
622 .iter()
623 .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("expiration"));
624 assert!(has_expiration);
625 }
626
627 #[test]
628 fn nip44_pow_is_applied() {
629 let trade_keys = Keys::generate();
630 let receiver_keys = Keys::generate();
631
632 let event = wrap_message_nip44(
633 &sample_order_message(Some(1)),
634 &trade_keys,
635 &trade_keys,
636 receiver_keys.public_key(),
637 WrapOptions {
638 pow: 8,
639 ..WrapOptions::default()
640 },
641 )
642 .expect("wrap");
643
644 assert!(event.check_pow(8));
645 }
646
647 #[tokio::test]
648 async fn unwrap_incoming_dispatches_both_transports() {
649 let identity_keys = Keys::generate();
650 let trade_keys = Keys::generate();
651 let receiver_keys = Keys::generate();
652 let message = sample_order_message(Some(7));
653
654 let wrapped = wrap_message(
655 &message,
656 &identity_keys,
657 &trade_keys,
658 receiver_keys.public_key(),
659 WrapOptions::default(),
660 )
661 .await
662 .expect("gift wrap");
663 let direct = wrap_message_nip44(
664 &message,
665 &identity_keys,
666 &trade_keys,
667 receiver_keys.public_key(),
668 WrapOptions::default(),
669 )
670 .expect("nip44 wrap");
671
672 for event in [wrapped, direct] {
673 let unwrapped = unwrap_incoming(&event, &receiver_keys)
674 .await
675 .expect("unwrap")
676 .expect("some");
677 assert_eq!(unwrapped.sender, trade_keys.public_key());
678 assert_eq!(unwrapped.identity, identity_keys.public_key());
679 assert_eq!(
680 unwrapped.message.as_json().unwrap(),
681 message.as_json().unwrap()
682 );
683 }
684 }
685
686 #[tokio::test]
687 async fn unwrap_incoming_rejects_unknown_kind() {
688 let keys = Keys::generate();
689 let event = EventBuilder::text_note("hello")
690 .sign_with_keys(&keys)
691 .expect("sign");
692
693 let result = unwrap_incoming(&event, &keys).await;
694 assert!(matches!(result, Err(MostroError::MostroInternalErr(_))));
695 }
696
697 #[tokio::test]
698 #[allow(deprecated)]
701 async fn wrap_message_with_dispatches_by_transport() {
702 let trade_keys = Keys::generate();
703 let receiver_keys = Keys::generate();
704 let message = sample_order_message(Some(1));
705
706 let gift = wrap_message_with(
707 Transport::GiftWrap,
708 &message,
709 &trade_keys,
710 &trade_keys,
711 receiver_keys.public_key(),
712 WrapOptions::default(),
713 )
714 .await
715 .expect("gift wrap");
716 let direct = wrap_message_with(
717 Transport::Nip44Direct,
718 &message,
719 &trade_keys,
720 &trade_keys,
721 receiver_keys.public_key(),
722 WrapOptions::default(),
723 )
724 .await
725 .expect("nip44");
726
727 assert_eq!(gift.kind, Kind::GiftWrap);
728 assert_eq!(direct.kind, Kind::PrivateDirectMessage);
729 }
730
731 #[test]
732 #[allow(deprecated)]
735 fn transport_config_parsing() {
736 for (s, expected) in [
737 ("gift-wrap", Transport::GiftWrap),
738 ("nip44", Transport::Nip44Direct),
739 ] {
740 assert_eq!(s.parse::<Transport>().unwrap(), expected);
741 let from_serde: Transport = serde_json::from_str(&format!("{s:?}")).unwrap();
742 assert_eq!(from_serde, expected);
743 assert_eq!(expected.to_string(), s);
744 }
745 assert!("dual".parse::<Transport>().is_err());
746 assert!("bogus".parse::<Transport>().is_err());
747 assert_eq!(Transport::default(), Transport::GiftWrap);
748 }
749
750 #[test]
751 #[allow(deprecated)]
754 fn transport_kind_and_version() {
755 assert_eq!(Transport::GiftWrap.event_kind(), Kind::GiftWrap);
756 assert_eq!(
757 Transport::Nip44Direct.event_kind(),
758 Kind::PrivateDirectMessage
759 );
760 assert_eq!(Transport::GiftWrap.protocol_version(), 1);
761 assert_eq!(Transport::Nip44Direct.protocol_version(), 2);
762 }
763}