1use crate::prelude::*;
2use bitcoin::hashes::sha256::Hash as Sha256Hash;
3use bitcoin::hashes::Hash;
4use bitcoin::key::Secp256k1;
5use bitcoin::secp256k1::Message as BitcoinMessage;
6use nostr_sdk::prelude::*;
7
8use std::fmt;
9use uuid::Uuid;
10
11#[derive(Debug, Deserialize, Serialize, Clone)]
13pub struct Peer {
14 pub pubkey: String,
15 pub reputation: Option<UserInfo>,
16}
17
18impl Peer {
19 pub fn new(pubkey: String, reputation: Option<UserInfo>) -> Self {
20 Self { pubkey, reputation }
21 }
22
23 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
24 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
25 }
26
27 pub fn as_json(&self) -> Result<String, ServiceError> {
28 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
29 }
30}
31
32#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
34#[serde(rename_all = "kebab-case")]
35pub enum Action {
36 NewOrder,
37 TakeSell,
38 TakeBuy,
39 PayInvoice,
40 FiatSent,
41 FiatSentOk,
42 Release,
43 Released,
44 Cancel,
45 Canceled,
46 CooperativeCancelInitiatedByYou,
47 CooperativeCancelInitiatedByPeer,
48 DisputeInitiatedByYou,
49 DisputeInitiatedByPeer,
50 CooperativeCancelAccepted,
51 BuyerInvoiceAccepted,
52 PurchaseCompleted,
53 HoldInvoicePaymentAccepted,
54 HoldInvoicePaymentSettled,
55 HoldInvoicePaymentCanceled,
56 WaitingSellerToPay,
57 WaitingBuyerInvoice,
58 AddInvoice,
59 BuyerTookOrder,
60 Rate,
61 RateUser,
62 RateReceived,
63 CantDo,
64 Dispute,
65 AdminCancel,
66 AdminCanceled,
67 AdminSettle,
68 AdminSettled,
69 AdminAddSolver,
70 AdminTakeDispute,
71 AdminTookDispute,
72 PaymentFailed,
73 InvoiceUpdated,
74 SendDm,
75 TradePubkey,
76}
77
78impl fmt::Display for Action {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 write!(f, "{self:?}")
81 }
82}
83
84#[derive(Debug, Clone, Deserialize, Serialize)]
86#[serde(rename_all = "kebab-case")]
87pub enum Message {
88 Order(MessageKind),
89 Dispute(MessageKind),
90 CantDo(MessageKind),
91 Rate(MessageKind),
92 Dm(MessageKind),
93}
94
95impl Message {
96 pub fn new_order(
98 id: Option<Uuid>,
99 request_id: Option<u64>,
100 trade_index: Option<i64>,
101 action: Action,
102 payload: Option<Payload>,
103 ) -> Self {
104 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
105 Self::Order(kind)
106 }
107
108 pub fn new_dispute(
110 id: Option<Uuid>,
111 request_id: Option<u64>,
112 trade_index: Option<i64>,
113 action: Action,
114 payload: Option<Payload>,
115 ) -> Self {
116 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
117
118 Self::Dispute(kind)
119 }
120
121 pub fn cant_do(id: Option<Uuid>, request_id: Option<u64>, payload: Option<Payload>) -> Self {
123 let kind = MessageKind::new(id, request_id, None, Action::CantDo, payload);
124
125 Self::CantDo(kind)
126 }
127
128 pub fn new_dm(
130 id: Option<Uuid>,
131 request_id: Option<u64>,
132 action: Action,
133 payload: Option<Payload>,
134 ) -> Self {
135 let kind = MessageKind::new(id, request_id, None, action, payload);
136
137 Self::Dm(kind)
138 }
139
140 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
142 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
143 }
144
145 pub fn as_json(&self) -> Result<String, ServiceError> {
147 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
148 }
149
150 pub fn get_inner_message_kind(&self) -> &MessageKind {
152 match self {
153 Message::Dispute(k)
154 | Message::Order(k)
155 | Message::CantDo(k)
156 | Message::Rate(k)
157 | Message::Dm(k) => k,
158 }
159 }
160
161 pub fn inner_action(&self) -> Option<Action> {
163 match self {
164 Message::Dispute(a)
165 | Message::Order(a)
166 | Message::CantDo(a)
167 | Message::Rate(a)
168 | Message::Dm(a) => Some(a.get_action()),
169 }
170 }
171
172 pub fn verify(&self) -> bool {
174 match self {
175 Message::Order(m)
176 | Message::Dispute(m)
177 | Message::CantDo(m)
178 | Message::Rate(m)
179 | Message::Dm(m) => m.verify(),
180 }
181 }
182
183 pub fn sign(message: String, keys: &Keys) -> Signature {
184 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
185 let hash = hash.to_byte_array();
186 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
187
188 keys.sign_schnorr(&message)
189 }
190
191 pub fn verify_signature(message: String, pubkey: PublicKey, sig: Signature) -> bool {
192 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
194 let hash = hash.to_byte_array();
195 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
196
197 let secp = Secp256k1::verification_only();
199 if let Ok(xonlykey) = pubkey.xonly() {
201 xonlykey.verify(&secp, &message, &sig).is_ok()
202 } else {
203 false
204 }
205 }
206}
207
208#[derive(Debug, Clone, Deserialize, Serialize)]
210pub struct MessageKind {
211 pub version: u8,
213 pub request_id: Option<u64>,
215 pub trade_index: Option<i64>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub id: Option<Uuid>,
220 pub action: Action,
222 pub payload: Option<Payload>,
224}
225
226type Amount = i64;
227
228#[derive(Debug, Deserialize, Serialize, Clone)]
230pub struct PaymentFailedInfo {
231 pub payment_attempts: u32,
233 pub payment_retries_interval: u32,
235}
236
237#[derive(Debug, Deserialize, Serialize, Clone)]
239#[serde(rename_all = "snake_case")]
240pub enum Payload {
241 Order(SmallOrder),
243 PaymentRequest(Option<SmallOrder>, String, Option<Amount>),
245 TextMessage(String),
247 Peer(Peer),
249 RatingUser(u8),
251 Amount(Amount),
253 Dispute(Uuid, Option<u16>, Option<SolverDisputeInfo>),
255 CantDo(Option<CantDoReason>),
257 NextTrade(String, u32),
261 PaymentFailed(PaymentFailedInfo),
263}
264
265#[allow(dead_code)]
266impl MessageKind {
267 pub fn new(
269 id: Option<Uuid>,
270 request_id: Option<u64>,
271 trade_index: Option<i64>,
272 action: Action,
273 payload: Option<Payload>,
274 ) -> Self {
275 Self {
276 version: PROTOCOL_VER,
277 request_id,
278 trade_index,
279 id,
280 action,
281 payload,
282 }
283 }
284 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
286 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
287 }
288 pub fn as_json(&self) -> Result<String, ServiceError> {
290 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
291 }
292
293 pub fn get_action(&self) -> Action {
295 self.action.clone()
296 }
297
298 pub fn get_next_trade_key(&self) -> Result<Option<(String, u32)>, ServiceError> {
300 match &self.payload {
301 Some(Payload::NextTrade(key, index)) => Ok(Some((key.to_string(), *index))),
302 None => Ok(None),
303 _ => Err(ServiceError::InvalidPayload),
304 }
305 }
306
307 pub fn get_rating(&self) -> Result<u8, ServiceError> {
308 if let Some(Payload::RatingUser(v)) = self.payload.to_owned() {
309 if !(MIN_RATING..=MAX_RATING).contains(&v) {
310 return Err(ServiceError::InvalidRatingValue);
311 }
312 Ok(v)
313 } else {
314 Err(ServiceError::InvalidRating)
315 }
316 }
317
318 pub fn verify(&self) -> bool {
320 match &self.action {
321 Action::NewOrder => matches!(&self.payload, Some(Payload::Order(_))),
322 Action::PayInvoice | Action::AddInvoice => {
323 if self.id.is_none() {
324 return false;
325 }
326 matches!(&self.payload, Some(Payload::PaymentRequest(_, _, _)))
327 }
328 Action::TakeSell
329 | Action::TakeBuy
330 | Action::FiatSent
331 | Action::FiatSentOk
332 | Action::Release
333 | Action::Released
334 | Action::Dispute
335 | Action::AdminCancel
336 | Action::AdminCanceled
337 | Action::AdminSettle
338 | Action::AdminSettled
339 | Action::Rate
340 | Action::RateReceived
341 | Action::AdminTakeDispute
342 | Action::AdminTookDispute
343 | Action::DisputeInitiatedByYou
344 | Action::DisputeInitiatedByPeer
345 | Action::WaitingBuyerInvoice
346 | Action::PurchaseCompleted
347 | Action::HoldInvoicePaymentAccepted
348 | Action::HoldInvoicePaymentSettled
349 | Action::HoldInvoicePaymentCanceled
350 | Action::WaitingSellerToPay
351 | Action::BuyerTookOrder
352 | Action::BuyerInvoiceAccepted
353 | Action::CooperativeCancelInitiatedByYou
354 | Action::CooperativeCancelInitiatedByPeer
355 | Action::CooperativeCancelAccepted
356 | Action::Cancel
357 | Action::InvoiceUpdated
358 | Action::AdminAddSolver
359 | Action::SendDm
360 | Action::TradePubkey
361 | Action::Canceled => {
362 if self.id.is_none() {
363 return false;
364 }
365 true
366 }
367 Action::PaymentFailed => {
368 if self.id.is_none() {
369 return false;
370 }
371 matches!(&self.payload, Some(Payload::PaymentFailed(_)))
372 }
373 Action::RateUser => {
374 matches!(&self.payload, Some(Payload::RatingUser(_)))
375 }
376 Action::CantDo => {
377 matches!(&self.payload, Some(Payload::CantDo(_)))
378 }
379 }
380 }
381
382 pub fn get_order(&self) -> Option<&SmallOrder> {
383 if self.action != Action::NewOrder {
384 return None;
385 }
386 match &self.payload {
387 Some(Payload::Order(o)) => Some(o),
388 _ => None,
389 }
390 }
391
392 pub fn get_payment_request(&self) -> Option<String> {
393 if self.action != Action::TakeSell
394 && self.action != Action::AddInvoice
395 && self.action != Action::NewOrder
396 {
397 return None;
398 }
399 match &self.payload {
400 Some(Payload::PaymentRequest(_, pr, _)) => Some(pr.to_owned()),
401 Some(Payload::Order(ord)) => ord.buyer_invoice.to_owned(),
402 _ => None,
403 }
404 }
405
406 pub fn get_amount(&self) -> Option<Amount> {
407 if self.action != Action::TakeSell && self.action != Action::TakeBuy {
408 return None;
409 }
410 match &self.payload {
411 Some(Payload::PaymentRequest(_, _, amount)) => *amount,
412 Some(Payload::Amount(amount)) => Some(*amount),
413 _ => None,
414 }
415 }
416
417 pub fn get_payload(&self) -> Option<&Payload> {
418 self.payload.as_ref()
419 }
420
421 pub fn has_trade_index(&self) -> (bool, i64) {
422 if let Some(index) = self.trade_index {
423 return (true, index);
424 }
425 (false, 0)
426 }
427
428 pub fn trade_index(&self) -> i64 {
429 if let Some(index) = self.trade_index {
430 return index;
431 }
432 0
433 }
434}
435
436#[cfg(test)]
437mod test {
438 use crate::message::{Action, Message, MessageKind, Payload, Peer};
439 use crate::user::UserInfo;
440 use nostr_sdk::Keys;
441 use uuid::uuid;
442
443 #[test]
444 fn test_peer_with_reputation() {
445 let reputation = UserInfo {
447 rating: 4.5,
448 reviews: 10,
449 operating_days: 30,
450 };
451 let peer = Peer::new(
452 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
453 Some(reputation.clone()),
454 );
455
456 assert_eq!(
458 peer.pubkey,
459 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
460 );
461 assert!(peer.reputation.is_some());
462 let peer_reputation = peer.reputation.clone().unwrap();
463 assert_eq!(peer_reputation.rating, 4.5);
464 assert_eq!(peer_reputation.reviews, 10);
465 assert_eq!(peer_reputation.operating_days, 30);
466
467 let json = peer.as_json().unwrap();
469 let deserialized_peer = Peer::from_json(&json).unwrap();
470 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
471 assert!(deserialized_peer.reputation.is_some());
472 let deserialized_reputation = deserialized_peer.reputation.unwrap();
473 assert_eq!(deserialized_reputation.rating, 4.5);
474 assert_eq!(deserialized_reputation.reviews, 10);
475 assert_eq!(deserialized_reputation.operating_days, 30);
476 }
477
478 #[test]
479 fn test_peer_without_reputation() {
480 let peer = Peer::new(
482 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
483 None,
484 );
485
486 assert_eq!(
488 peer.pubkey,
489 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
490 );
491 assert!(peer.reputation.is_none());
492
493 let json = peer.as_json().unwrap();
495 let deserialized_peer = Peer::from_json(&json).unwrap();
496 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
497 assert!(deserialized_peer.reputation.is_none());
498 }
499
500 #[test]
501 fn test_peer_in_message() {
502 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
503
504 let reputation = UserInfo {
506 rating: 4.5,
507 reviews: 10,
508 operating_days: 30,
509 };
510 let peer_with_reputation = Peer::new(
511 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
512 Some(reputation),
513 );
514 let payload_with_reputation = Payload::Peer(peer_with_reputation);
515 let message_with_reputation = Message::Order(MessageKind::new(
516 Some(uuid),
517 Some(1),
518 Some(2),
519 Action::FiatSentOk,
520 Some(payload_with_reputation),
521 ));
522
523 assert!(message_with_reputation.verify());
525 let message_json = message_with_reputation.as_json().unwrap();
526 let deserialized_message = Message::from_json(&message_json).unwrap();
527 assert!(deserialized_message.verify());
528
529 let peer_without_reputation = Peer::new(
531 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
532 None,
533 );
534 let payload_without_reputation = Payload::Peer(peer_without_reputation);
535 let message_without_reputation = Message::Order(MessageKind::new(
536 Some(uuid),
537 Some(1),
538 Some(2),
539 Action::FiatSentOk,
540 Some(payload_without_reputation),
541 ));
542
543 assert!(message_without_reputation.verify());
545 let message_json = message_without_reputation.as_json().unwrap();
546 let deserialized_message = Message::from_json(&message_json).unwrap();
547 assert!(deserialized_message.verify());
548 }
549
550 #[test]
551 fn test_payment_failed_payload() {
552 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
553
554 let payment_failed_info = crate::message::PaymentFailedInfo {
556 payment_attempts: 3,
557 payment_retries_interval: 60,
558 };
559
560 let payload = Payload::PaymentFailed(payment_failed_info);
561 let message = Message::Order(MessageKind::new(
562 Some(uuid),
563 Some(1),
564 Some(2),
565 Action::PaymentFailed,
566 Some(payload),
567 ));
568
569 assert!(message.verify());
571
572 let message_json = message.as_json().unwrap();
574 println!("PaymentFailed message JSON: {}", message_json);
575
576 let deserialized_message = Message::from_json(&message_json).unwrap();
578 assert!(deserialized_message.verify());
579
580 if let Message::Order(kind) = deserialized_message {
582 if let Some(Payload::PaymentFailed(info)) = kind.payload {
583 assert_eq!(info.payment_attempts, 3);
584 assert_eq!(info.payment_retries_interval, 60);
585 } else {
586 panic!("Expected PaymentFailed payload");
587 }
588 } else {
589 panic!("Expected Order message");
590 }
591 }
592
593 #[test]
594 fn test_message_payload_signature() {
595 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
596 let peer = Peer::new(
597 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
598 None, );
600 let payload = Payload::Peer(peer);
601 let test_message = Message::Order(MessageKind::new(
602 Some(uuid),
603 Some(1),
604 Some(2),
605 Action::FiatSentOk,
606 Some(payload),
607 ));
608 assert!(test_message.verify());
609 let test_message_json = test_message.as_json().unwrap();
610 let trade_keys =
612 Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
613 .unwrap();
614 let sig = Message::sign(test_message_json.clone(), &trade_keys);
615
616 assert!(Message::verify_signature(
617 test_message_json,
618 trade_keys.public_key(),
619 sig
620 ));
621 }
622}