1use std::str::FromStr;
16
17use crate::message::{Action, Message, Payload};
18use crate::prelude::{CantDoReason, MostroError, ServiceError};
19use nostr_sdk::nips::{nip44, nip59};
20use nostr_sdk::prelude::*;
21
22#[derive(Debug, Clone)]
24pub struct WrapOptions {
25 pub pow: u8,
27 pub expiration: Option<Timestamp>,
29 pub signed: bool,
34}
35
36impl Default for WrapOptions {
37 fn default() -> Self {
38 Self {
39 pow: 0,
40 expiration: None,
41 signed: true,
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
49pub struct UnwrappedMessage {
50 pub message: Message,
52 pub signature: Option<Signature>,
55 pub sender: PublicKey,
57 pub created_at: Timestamp,
59}
60
61pub async fn wrap_message(
74 message: &Message,
75 trade_keys: &Keys,
76 receiver: PublicKey,
77 opts: WrapOptions,
78) -> Result<Event, MostroError> {
79 let message_json = message.as_json().map_err(MostroError::MostroInternalErr)?;
80
81 let content = if opts.signed {
82 let sig = Message::sign(message_json, trade_keys);
83 serde_json::to_string(&(message, Some(sig.to_string())))
84 .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?
85 } else {
86 serde_json::to_string(&(message, Option::<String>::None))
87 .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?
88 };
89
90 let rumor = EventBuilder::text_note(content).build(trade_keys.public_key());
94
95 let seal: Event = EventBuilder::seal(trade_keys, &receiver, rumor)
96 .await
97 .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))?
98 .sign(trade_keys)
99 .await
100 .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))?;
101
102 gift_wrap_from_seal_with_pow(&seal, receiver, opts.pow, opts.expiration)
103}
104
105fn gift_wrap_from_seal_with_pow(
109 seal: &Event,
110 receiver: PublicKey,
111 pow: u8,
112 expiration: Option<Timestamp>,
113) -> Result<Event, MostroError> {
114 if seal.kind != Kind::Seal {
115 return Err(MostroError::MostroInternalErr(
116 ServiceError::UnexpectedError("expected Seal kind".to_string()),
117 ));
118 }
119
120 let ephemeral = Keys::generate();
121 let encrypted = nip44::encrypt(
122 ephemeral.secret_key(),
123 &receiver,
124 seal.as_json(),
125 nip44::Version::default(),
126 )
127 .map_err(|e| MostroError::MostroInternalErr(ServiceError::EncryptionError(e.to_string())))?;
128
129 let mut tags: Vec<Tag> = Vec::new();
130 if let Some(exp) = expiration {
131 tags.push(Tag::expiration(exp));
132 }
133 tags.push(Tag::public_key(receiver));
134
135 EventBuilder::new(Kind::GiftWrap, encrypted)
136 .tags(tags)
137 .custom_created_at(Timestamp::tweaked(nip59::RANGE_RANDOM_TIMESTAMP_TWEAK))
138 .pow(pow)
139 .sign_with_keys(&ephemeral)
140 .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))
141}
142
143pub async fn unwrap_message(
152 event: &Event,
153 trade_keys: &Keys,
154) -> Result<Option<UnwrappedMessage>, MostroError> {
155 if event.kind != Kind::GiftWrap {
156 return Err(MostroError::MostroInternalErr(
157 ServiceError::UnexpectedError("event is not a GiftWrap".to_string()),
158 ));
159 }
160
161 let unwrapped = match nip59::extract_rumor(trade_keys, event).await {
162 Ok(u) => u,
163 Err(nip59::Error::Signer(_)) => return Ok(None),
165 Err(e) => {
166 return Err(MostroError::MostroInternalErr(ServiceError::NostrError(
167 e.to_string(),
168 )));
169 }
170 };
171
172 let (message, sig_str): (Message, Option<String>) =
173 serde_json::from_str(&unwrapped.rumor.content)
174 .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?;
175
176 let signature = match sig_str {
177 Some(s) => {
178 let sig = Signature::from_str(&s).map_err(|e| {
179 MostroError::MostroInternalErr(ServiceError::UnexpectedError(format!(
180 "malformed rumor signature: {e}"
181 )))
182 })?;
183 let message_json = message.as_json().map_err(MostroError::MostroInternalErr)?;
184 if !Message::verify_signature(message_json, unwrapped.sender, sig) {
185 return Err(MostroError::MostroInternalErr(
186 ServiceError::UnexpectedError(
187 "rumor signature does not verify against sender".to_string(),
188 ),
189 ));
190 }
191 Some(sig)
192 }
193 None => None,
194 };
195
196 Ok(Some(UnwrappedMessage {
197 message,
198 signature,
199 sender: unwrapped.sender,
200 created_at: unwrapped.rumor.created_at,
201 }))
202}
203
204pub fn validate_response(
217 message: &Message,
218 expected_request_id: Option<u64>,
219) -> Result<(), MostroError> {
220 let inner = message.get_inner_message_kind();
221
222 if let Some(Payload::CantDo(reason)) = &inner.payload {
223 return Err(MostroError::MostroCantDo(
224 reason.clone().unwrap_or(CantDoReason::InvalidAction),
225 ));
226 }
227
228 if let Some(expected) = expected_request_id {
229 match inner.request_id {
230 Some(got) if got == expected => {}
231 Some(_) => {
232 return Err(MostroError::MostroInternalErr(
233 ServiceError::UnexpectedError("mismatched request_id".to_string()),
234 ));
235 }
236 None => {
237 if !action_accepts_missing_request_id(&inner.action) {
238 return Err(MostroError::MostroInternalErr(
239 ServiceError::UnexpectedError(
240 "missing request_id on a response that requires one".to_string(),
241 ),
242 ));
243 }
244 }
245 }
246 }
247
248 Ok(())
249}
250
251fn action_accepts_missing_request_id(action: &Action) -> bool {
254 matches!(
255 action,
256 Action::BuyerTookOrder
257 | Action::HoldInvoicePaymentAccepted
258 | Action::HoldInvoicePaymentSettled
259 | Action::HoldInvoicePaymentCanceled
260 | Action::WaitingSellerToPay
261 | Action::WaitingBuyerInvoice
262 | Action::BuyerInvoiceAccepted
263 | Action::PurchaseCompleted
264 | Action::Released
265 | Action::FiatSentOk
266 | Action::Canceled
267 | Action::CooperativeCancelInitiatedByPeer
268 | Action::CooperativeCancelAccepted
269 | Action::DisputeInitiatedByPeer
270 | Action::AdminSettled
271 | Action::AdminCanceled
272 | Action::AdminTookDispute
273 | Action::PaymentFailed
274 | Action::InvoiceUpdated
275 | Action::Rate
276 | Action::RateReceived
277 | Action::SendDm
278 )
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use crate::message::{Action, MessageKind, Payload};
285 use uuid::uuid;
286
287 fn sample_order_message(request_id: Option<u64>) -> Message {
288 let peer = crate::message::Peer::new(
289 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
290 None,
291 );
292 Message::Order(MessageKind::new(
293 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
294 request_id,
295 Some(1),
296 Action::FiatSentOk,
297 Some(Payload::Peer(peer)),
298 ))
299 }
300
301 #[tokio::test]
302 async fn wrap_then_unwrap_roundtrip() {
303 let trade_keys = Keys::generate();
304 let receiver_keys = Keys::generate();
305
306 let message = sample_order_message(Some(42));
307
308 let wrapped = wrap_message(
309 &message,
310 &trade_keys,
311 receiver_keys.public_key(),
312 WrapOptions::default(),
313 )
314 .await
315 .expect("wrap");
316
317 assert_eq!(wrapped.kind, Kind::GiftWrap);
318 assert!(wrapped
319 .tags
320 .iter()
321 .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("p")));
322
323 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
324 .await
325 .expect("unwrap result")
326 .expect("unwrap some");
327
328 assert_eq!(unwrapped.sender, trade_keys.public_key());
329 assert_eq!(
330 unwrapped.message.as_json().unwrap(),
331 message.as_json().unwrap()
332 );
333 assert!(unwrapped.signature.is_some());
334 }
335
336 #[tokio::test]
337 async fn unwrap_with_corrupted_seal_returns_err() {
338 let receiver_keys = Keys::generate();
339 let ephemeral = Keys::generate();
340
341 let encrypted = nip44::encrypt(
346 ephemeral.secret_key(),
347 &receiver_keys.public_key(),
348 "not a seal",
349 nip44::Version::default(),
350 )
351 .expect("encrypt");
352
353 let corrupted = EventBuilder::new(Kind::GiftWrap, encrypted)
354 .tags([Tag::public_key(receiver_keys.public_key())])
355 .sign_with_keys(&ephemeral)
356 .expect("sign");
357
358 let result = unwrap_message(&corrupted, &receiver_keys).await;
359 assert!(
360 matches!(result, Err(MostroError::MostroInternalErr(_))),
361 "expected Err for corrupted gift wrap, got {result:?}",
362 );
363 }
364
365 async fn wrap_with_raw_inner(
369 trade_keys: &Keys,
370 receiver: PublicKey,
371 inner: (&Message, Option<String>),
372 ) -> Event {
373 let content = serde_json::to_string(&inner).unwrap();
374 let rumor = EventBuilder::text_note(content).build(trade_keys.public_key());
375 let seal = EventBuilder::seal(trade_keys, &receiver, rumor)
376 .await
377 .unwrap()
378 .sign(trade_keys)
379 .await
380 .unwrap();
381 gift_wrap_from_seal_with_pow(&seal, receiver, 0, None).unwrap()
382 }
383
384 #[tokio::test]
385 async fn unwrap_with_malformed_signature_errors() {
386 let trade_keys = Keys::generate();
387 let receiver_keys = Keys::generate();
388 let msg = sample_order_message(Some(1));
389
390 let wrapped = wrap_with_raw_inner(
391 &trade_keys,
392 receiver_keys.public_key(),
393 (&msg, Some("not-a-hex-signature".to_string())),
394 )
395 .await;
396
397 let result = unwrap_message(&wrapped, &receiver_keys).await;
398 assert!(
399 matches!(result, Err(MostroError::MostroInternalErr(_))),
400 "malformed signature must surface as Err, got {result:?}",
401 );
402 }
403
404 #[tokio::test]
405 async fn unwrap_with_signature_for_other_content_errors() {
406 let trade_keys = Keys::generate();
407 let receiver_keys = Keys::generate();
408 let msg = sample_order_message(Some(1));
409 let bogus = Message::sign("not the real message".to_string(), &trade_keys);
411
412 let wrapped = wrap_with_raw_inner(
413 &trade_keys,
414 receiver_keys.public_key(),
415 (&msg, Some(bogus.to_string())),
416 )
417 .await;
418
419 let result = unwrap_message(&wrapped, &receiver_keys).await;
420 assert!(
421 matches!(result, Err(MostroError::MostroInternalErr(_))),
422 "non-verifying signature must surface as Err, got {result:?}",
423 );
424 }
425
426 #[tokio::test]
427 async fn unwrap_with_wrong_trade_keys_returns_none() {
428 let trade_keys = Keys::generate();
429 let receiver_keys = Keys::generate();
430 let stranger_keys = Keys::generate();
431
432 let wrapped = wrap_message(
433 &sample_order_message(Some(1)),
434 &trade_keys,
435 receiver_keys.public_key(),
436 WrapOptions::default(),
437 )
438 .await
439 .expect("wrap");
440
441 let result = unwrap_message(&wrapped, &stranger_keys)
442 .await
443 .expect("call should not error");
444 assert!(result.is_none());
445 }
446
447 #[tokio::test]
448 async fn signature_is_verifiable_with_trade_pubkey() {
449 let trade_keys = Keys::generate();
450 let receiver_keys = Keys::generate();
451 let message = sample_order_message(Some(7));
452
453 let wrapped = wrap_message(
454 &message,
455 &trade_keys,
456 receiver_keys.public_key(),
457 WrapOptions::default(),
458 )
459 .await
460 .unwrap();
461
462 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
463 .await
464 .unwrap()
465 .unwrap();
466
467 let sig = unwrapped.signature.expect("signed");
468 let json = unwrapped.message.as_json().unwrap();
469 assert!(Message::verify_signature(
470 json,
471 trade_keys.public_key(),
472 sig
473 ));
474 }
475
476 #[tokio::test]
477 async fn unsigned_wrap_has_no_signature() {
478 let trade_keys = Keys::generate();
479 let receiver_keys = Keys::generate();
480
481 let wrapped = wrap_message(
482 &sample_order_message(Some(3)),
483 &trade_keys,
484 receiver_keys.public_key(),
485 WrapOptions {
486 signed: false,
487 ..WrapOptions::default()
488 },
489 )
490 .await
491 .expect("wrap");
492
493 let unwrapped = unwrap_message(&wrapped, &receiver_keys)
494 .await
495 .unwrap()
496 .unwrap();
497 assert!(unwrapped.signature.is_none());
498 }
499
500 #[tokio::test]
501 async fn expiration_tag_is_set_when_provided() {
502 let trade_keys = Keys::generate();
503 let receiver_keys = Keys::generate();
504 let exp = Timestamp::from_secs(Timestamp::now().as_secs() + 3600);
505
506 let wrapped = wrap_message(
507 &sample_order_message(Some(1)),
508 &trade_keys,
509 receiver_keys.public_key(),
510 WrapOptions {
511 expiration: Some(exp),
512 ..WrapOptions::default()
513 },
514 )
515 .await
516 .expect("wrap");
517
518 let has_expiration = wrapped
519 .tags
520 .iter()
521 .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("expiration"));
522 assert!(has_expiration);
523 }
524
525 #[test]
526 fn validate_response_cant_do_short_circuits() {
527 let msg = Message::cant_do(
528 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
529 Some(5),
530 Some(Payload::CantDo(Some(CantDoReason::NotAuthorized))),
531 );
532 let err = validate_response(&msg, Some(5)).unwrap_err();
533 match err {
534 MostroError::MostroCantDo(CantDoReason::NotAuthorized) => {}
535 _ => panic!("expected CantDo(NotAuthorized)"),
536 }
537 }
538
539 #[test]
540 fn validate_response_request_id_match() {
541 let msg = sample_order_message(Some(9));
542 validate_response(&msg, Some(9)).unwrap();
543 }
544
545 #[test]
546 fn validate_response_request_id_mismatch_errors() {
547 let msg = sample_order_message(Some(9));
548 let err = validate_response(&msg, Some(10)).unwrap_err();
549 assert!(matches!(err, MostroError::MostroInternalErr(_)));
550 }
551
552 #[test]
553 fn validate_response_allows_unsolicited_actions_without_request_id() {
554 let msg = Message::Order(MessageKind::new(
555 Some(uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23")),
556 None,
557 None,
558 Action::BuyerTookOrder,
559 None,
560 ));
561 validate_response(&msg, Some(1)).unwrap();
562 }
563
564 #[test]
565 fn validate_response_with_no_expected_id_is_ok() {
566 let msg = sample_order_message(None);
567 validate_response(&msg, None).unwrap();
568 }
569}