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)]
230#[serde(rename_all = "snake_case")]
231pub enum Payload {
232 Order(SmallOrder),
234 PaymentRequest(Option<SmallOrder>, String, Option<Amount>),
236 TextMessage(String),
238 Peer(Peer),
240 RatingUser(u8),
242 Amount(Amount),
244 Dispute(Uuid, Option<u16>, Option<SolverDisputeInfo>),
246 CantDo(Option<CantDoReason>),
248 NextTrade(String, u32),
252}
253
254#[allow(dead_code)]
255impl MessageKind {
256 pub fn new(
258 id: Option<Uuid>,
259 request_id: Option<u64>,
260 trade_index: Option<i64>,
261 action: Action,
262 payload: Option<Payload>,
263 ) -> Self {
264 Self {
265 version: PROTOCOL_VER,
266 request_id,
267 trade_index,
268 id,
269 action,
270 payload,
271 }
272 }
273 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
275 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
276 }
277 pub fn as_json(&self) -> Result<String, ServiceError> {
279 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
280 }
281
282 pub fn get_action(&self) -> Action {
284 self.action.clone()
285 }
286
287 pub fn get_next_trade_key(&self) -> Result<Option<(String, u32)>, ServiceError> {
289 match &self.payload {
290 Some(Payload::NextTrade(key, index)) => Ok(Some((key.to_string(), *index))),
291 None => Ok(None),
292 _ => Err(ServiceError::InvalidPayload),
293 }
294 }
295
296 pub fn get_rating(&self) -> Result<u8, ServiceError> {
297 if let Some(Payload::RatingUser(v)) = self.payload.to_owned() {
298 if !(MIN_RATING..=MAX_RATING).contains(&v) {
299 return Err(ServiceError::InvalidRatingValue);
300 }
301 Ok(v)
302 } else {
303 Err(ServiceError::InvalidRating)
304 }
305 }
306
307 pub fn verify(&self) -> bool {
309 match &self.action {
310 Action::NewOrder => matches!(&self.payload, Some(Payload::Order(_))),
311 Action::PayInvoice | Action::AddInvoice => {
312 if self.id.is_none() {
313 return false;
314 }
315 matches!(&self.payload, Some(Payload::PaymentRequest(_, _, _)))
316 }
317 Action::TakeSell
318 | Action::TakeBuy
319 | Action::FiatSent
320 | Action::FiatSentOk
321 | Action::Release
322 | Action::Released
323 | Action::Dispute
324 | Action::AdminCancel
325 | Action::AdminCanceled
326 | Action::AdminSettle
327 | Action::AdminSettled
328 | Action::Rate
329 | Action::RateReceived
330 | Action::AdminTakeDispute
331 | Action::AdminTookDispute
332 | Action::DisputeInitiatedByYou
333 | Action::DisputeInitiatedByPeer
334 | Action::WaitingBuyerInvoice
335 | Action::PurchaseCompleted
336 | Action::HoldInvoicePaymentAccepted
337 | Action::HoldInvoicePaymentSettled
338 | Action::HoldInvoicePaymentCanceled
339 | Action::WaitingSellerToPay
340 | Action::BuyerTookOrder
341 | Action::BuyerInvoiceAccepted
342 | Action::CooperativeCancelInitiatedByYou
343 | Action::CooperativeCancelInitiatedByPeer
344 | Action::CooperativeCancelAccepted
345 | Action::Cancel
346 | Action::PaymentFailed
347 | Action::InvoiceUpdated
348 | Action::AdminAddSolver
349 | Action::SendDm
350 | Action::TradePubkey
351 | Action::Canceled => {
352 if self.id.is_none() {
353 return false;
354 }
355 true
356 }
357 Action::RateUser => {
358 matches!(&self.payload, Some(Payload::RatingUser(_)))
359 }
360 Action::CantDo => {
361 matches!(&self.payload, Some(Payload::CantDo(_)))
362 }
363 }
364 }
365
366 pub fn get_order(&self) -> Option<&SmallOrder> {
367 if self.action != Action::NewOrder {
368 return None;
369 }
370 match &self.payload {
371 Some(Payload::Order(o)) => Some(o),
372 _ => None,
373 }
374 }
375
376 pub fn get_payment_request(&self) -> Option<String> {
377 if self.action != Action::TakeSell
378 && self.action != Action::AddInvoice
379 && self.action != Action::NewOrder
380 {
381 return None;
382 }
383 match &self.payload {
384 Some(Payload::PaymentRequest(_, pr, _)) => Some(pr.to_owned()),
385 Some(Payload::Order(ord)) => ord.buyer_invoice.to_owned(),
386 _ => None,
387 }
388 }
389
390 pub fn get_amount(&self) -> Option<Amount> {
391 if self.action != Action::TakeSell && self.action != Action::TakeBuy {
392 return None;
393 }
394 match &self.payload {
395 Some(Payload::PaymentRequest(_, _, amount)) => *amount,
396 Some(Payload::Amount(amount)) => Some(*amount),
397 _ => None,
398 }
399 }
400
401 pub fn get_payload(&self) -> Option<&Payload> {
402 self.payload.as_ref()
403 }
404
405 pub fn has_trade_index(&self) -> (bool, i64) {
406 if let Some(index) = self.trade_index {
407 return (true, index);
408 }
409 (false, 0)
410 }
411
412 pub fn trade_index(&self) -> i64 {
413 if let Some(index) = self.trade_index {
414 return index;
415 }
416 0
417 }
418}
419
420#[cfg(test)]
421mod test {
422 use crate::message::{Action, Message, MessageKind, Payload, Peer};
423 use crate::user::UserInfo;
424 use nostr_sdk::Keys;
425 use uuid::uuid;
426
427 #[test]
428 fn test_peer_with_reputation() {
429 let reputation = UserInfo {
431 rating: 4.5,
432 reviews: 10,
433 operating_days: 30,
434 };
435 let peer = Peer::new(
436 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
437 Some(reputation.clone()),
438 );
439
440 assert_eq!(
442 peer.pubkey,
443 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
444 );
445 assert!(peer.reputation.is_some());
446 let peer_reputation = peer.reputation.clone().unwrap();
447 assert_eq!(peer_reputation.rating, 4.5);
448 assert_eq!(peer_reputation.reviews, 10);
449 assert_eq!(peer_reputation.operating_days, 30);
450
451 let json = peer.as_json().unwrap();
453 let deserialized_peer = Peer::from_json(&json).unwrap();
454 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
455 assert!(deserialized_peer.reputation.is_some());
456 let deserialized_reputation = deserialized_peer.reputation.unwrap();
457 assert_eq!(deserialized_reputation.rating, 4.5);
458 assert_eq!(deserialized_reputation.reviews, 10);
459 assert_eq!(deserialized_reputation.operating_days, 30);
460 }
461
462 #[test]
463 fn test_peer_without_reputation() {
464 let peer = Peer::new(
466 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
467 None,
468 );
469
470 assert_eq!(
472 peer.pubkey,
473 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
474 );
475 assert!(peer.reputation.is_none());
476
477 let json = peer.as_json().unwrap();
479 let deserialized_peer = Peer::from_json(&json).unwrap();
480 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
481 assert!(deserialized_peer.reputation.is_none());
482 }
483
484 #[test]
485 fn test_peer_in_message() {
486 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
487
488 let reputation = UserInfo {
490 rating: 4.5,
491 reviews: 10,
492 operating_days: 30,
493 };
494 let peer_with_reputation = Peer::new(
495 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
496 Some(reputation),
497 );
498 let payload_with_reputation = Payload::Peer(peer_with_reputation);
499 let message_with_reputation = Message::Order(MessageKind::new(
500 Some(uuid),
501 Some(1),
502 Some(2),
503 Action::FiatSentOk,
504 Some(payload_with_reputation),
505 ));
506
507 assert!(message_with_reputation.verify());
509 let message_json = message_with_reputation.as_json().unwrap();
510 let deserialized_message = Message::from_json(&message_json).unwrap();
511 assert!(deserialized_message.verify());
512
513 let peer_without_reputation = Peer::new(
515 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
516 None,
517 );
518 let payload_without_reputation = Payload::Peer(peer_without_reputation);
519 let message_without_reputation = Message::Order(MessageKind::new(
520 Some(uuid),
521 Some(1),
522 Some(2),
523 Action::FiatSentOk,
524 Some(payload_without_reputation),
525 ));
526
527 assert!(message_without_reputation.verify());
529 let message_json = message_without_reputation.as_json().unwrap();
530 let deserialized_message = Message::from_json(&message_json).unwrap();
531 assert!(deserialized_message.verify());
532 }
533
534 #[test]
535 fn test_message_payload_signature() {
536 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
537 let peer = Peer::new(
538 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
539 None, );
541 let payload = Payload::Peer(peer);
542 let test_message = Message::Order(MessageKind::new(
543 Some(uuid),
544 Some(1),
545 Some(2),
546 Action::FiatSentOk,
547 Some(payload),
548 ));
549 assert!(test_message.verify());
550 let test_message_json = test_message.as_json().unwrap();
551 let trade_keys =
553 Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
554 .unwrap();
555 let sig = Message::sign(test_message_json.clone(), &trade_keys);
556
557 assert!(Message::verify_signature(
558 test_message_json,
559 trade_keys.public_key(),
560 sig
561 ));
562 }
563}