mostro_core/
message.rs

1use crate::error::ServiceError;
2use crate::PROTOCOL_VER;
3use crate::{error::CantDoReason, order::SmallOrder};
4use anyhow::Result;
5use bitcoin::hashes::sha256::Hash as Sha256Hash;
6use bitcoin::hashes::Hash;
7use bitcoin::key::Secp256k1;
8use bitcoin::secp256k1::Message as BitcoinMessage;
9use nostr_sdk::prelude::*;
10use serde::{Deserialize, Serialize};
11use std::fmt;
12use uuid::Uuid;
13
14// Max rating
15pub const MAX_RATING: u8 = 5;
16// Min rating
17pub const MIN_RATING: u8 = 1;
18
19/// One party of the trade
20#[derive(Debug, Deserialize, Serialize, Clone)]
21pub struct Peer {
22    pub pubkey: String,
23}
24
25impl Peer {
26    pub fn new(pubkey: String) -> Self {
27        Self { pubkey }
28    }
29
30    pub fn from_json(json: &str) -> Result<Self> {
31        Ok(serde_json::from_str(json)?)
32    }
33
34    pub fn as_json(&self) -> Result<String> {
35        Ok(serde_json::to_string(&self)?)
36    }
37}
38
39/// Action is used to identify each message between Mostro and users
40#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
41#[serde(rename_all = "kebab-case")]
42pub enum Action {
43    NewOrder,
44    TakeSell,
45    TakeBuy,
46    PayInvoice,
47    FiatSent,
48    FiatSentOk,
49    Release,
50    Released,
51    Cancel,
52    Canceled,
53    CooperativeCancelInitiatedByYou,
54    CooperativeCancelInitiatedByPeer,
55    DisputeInitiatedByYou,
56    DisputeInitiatedByPeer,
57    CooperativeCancelAccepted,
58    BuyerInvoiceAccepted,
59    PurchaseCompleted,
60    HoldInvoicePaymentAccepted,
61    HoldInvoicePaymentSettled,
62    HoldInvoicePaymentCanceled,
63    WaitingSellerToPay,
64    WaitingBuyerInvoice,
65    AddInvoice,
66    BuyerTookOrder,
67    Rate,
68    RateUser,
69    RateReceived,
70    CantDo,
71    Dispute,
72    AdminCancel,
73    AdminCanceled,
74    AdminSettle,
75    AdminSettled,
76    AdminAddSolver,
77    AdminTakeDispute,
78    AdminTookDispute,
79    PaymentFailed,
80    InvoiceUpdated,
81    SendDm,
82    TradePubkey,
83}
84
85impl fmt::Display for Action {
86    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87        write!(f, "{self:?}")
88    }
89}
90
91/// Use this Message to establish communication between users and Mostro
92#[derive(Debug, Clone, Deserialize, Serialize)]
93#[serde(rename_all = "kebab-case")]
94pub enum Message {
95    Order(MessageKind),
96    Dispute(MessageKind),
97    CantDo(MessageKind),
98    Rate(MessageKind),
99    Dm(MessageKind),
100}
101
102impl Message {
103    /// New order message
104    pub fn new_order(
105        id: Option<Uuid>,
106        request_id: Option<u64>,
107        trade_index: Option<i64>,
108        action: Action,
109        payload: Option<Payload>,
110    ) -> Self {
111        let kind = MessageKind::new(id, request_id, trade_index, action, payload);
112        Self::Order(kind)
113    }
114
115    /// New dispute message
116    pub fn new_dispute(
117        id: Option<Uuid>,
118        request_id: Option<u64>,
119        trade_index: Option<i64>,
120        action: Action,
121        payload: Option<Payload>,
122    ) -> Self {
123        let kind = MessageKind::new(id, request_id, trade_index, action, payload);
124
125        Self::Dispute(kind)
126    }
127
128    /// New can't do template message message
129    pub fn cant_do(id: Option<Uuid>, request_id: Option<u64>, payload: Option<Payload>) -> Self {
130        let kind = MessageKind::new(id, request_id, None, Action::CantDo, payload);
131
132        Self::CantDo(kind)
133    }
134
135    /// New DM message
136    pub fn new_dm(
137        id: Option<Uuid>,
138        request_id: Option<u64>,
139        action: Action,
140        payload: Option<Payload>,
141    ) -> Self {
142        let kind = MessageKind::new(id, request_id, None, action, payload);
143
144        Self::Dm(kind)
145    }
146
147    /// Get message from json string
148    pub fn from_json(json: &str) -> Result<Self> {
149        Ok(serde_json::from_str(json)?)
150    }
151
152    /// Get message as json string
153    pub fn as_json(&self) -> Result<String> {
154        Ok(serde_json::to_string(&self)?)
155    }
156
157    // Get inner message kind
158    pub fn get_inner_message_kind(&self) -> &MessageKind {
159        match self {
160            Message::Dispute(k)
161            | Message::Order(k)
162            | Message::CantDo(k)
163            | Message::Rate(k)
164            | Message::Dm(k) => k,
165        }
166    }
167
168    // Get action from the inner message
169    pub fn inner_action(&self) -> Option<Action> {
170        match self {
171            Message::Dispute(a)
172            | Message::Order(a)
173            | Message::CantDo(a)
174            | Message::Rate(a)
175            | Message::Dm(a) => Some(a.get_action()),
176        }
177    }
178
179    /// Verify if is valid the inner message
180    pub fn verify(&self) -> bool {
181        match self {
182            Message::Order(m)
183            | Message::Dispute(m)
184            | Message::CantDo(m)
185            | Message::Rate(m)
186            | Message::Dm(m) => m.verify(),
187        }
188    }
189
190    pub fn sign(message: String, keys: &Keys) -> Signature {
191        let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
192        let hash = hash.to_byte_array();
193        let hash_str = hex::encode(hash);
194        println!("hash en sign() en core: {:?}", hash_str);
195        let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
196
197        keys.sign_schnorr(&message)
198    }
199
200    pub fn verify_signature(message: String, pubkey: PublicKey, sig: Signature) -> bool {
201        // Create payload hash
202        let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
203        let hash = hash.to_byte_array();
204        let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
205        // Create a verification-only context for better performance
206        let secp = Secp256k1::verification_only();
207        // Verify signature
208        pubkey.verify(&secp, &message, &sig).is_ok()
209    }
210}
211
212/// Use this Message to establish communication between users and Mostro
213#[derive(Debug, Clone, Deserialize, Serialize)]
214pub struct MessageKind {
215    /// Message version
216    pub version: u8,
217    /// Request_id for test on client
218    pub request_id: Option<u64>,
219    /// Trade key index
220    pub trade_index: Option<i64>,
221    /// Message id is not mandatory
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub id: Option<Uuid>,
224    /// Action to be taken
225    pub action: Action,
226    /// Payload of the Message
227    pub payload: Option<Payload>,
228}
229
230type Amount = i64;
231
232/// Message payload
233#[derive(Debug, Deserialize, Serialize, Clone)]
234#[serde(rename_all = "snake_case")]
235pub enum Payload {
236    /// Order
237    Order(SmallOrder),
238    /// Payment request
239    PaymentRequest(Option<SmallOrder>, String, Option<Amount>),
240    /// Use to send a message to another user
241    TextMessage(String),
242    /// Peer information
243    Peer(Peer),
244    /// Used to rate a user
245    RatingUser(u8),
246    /// In some cases we need to send an amount
247    Amount(Amount),
248    /// Dispute
249    Dispute(Uuid, Option<u16>),
250    /// Here the reason why we can't do the action
251    CantDo(Option<CantDoReason>),
252    /// This is used by the maker of a range order only on
253    /// messages with action release and fiat-sent
254    /// to inform the next trade pubkey and trade index
255    NextTrade(String, u32),
256}
257
258#[allow(dead_code)]
259impl MessageKind {
260    /// New message
261    pub fn new(
262        id: Option<Uuid>,
263        request_id: Option<u64>,
264        trade_index: Option<i64>,
265        action: Action,
266        payload: Option<Payload>,
267    ) -> Self {
268        Self {
269            version: PROTOCOL_VER,
270            request_id,
271            trade_index,
272            id,
273            action,
274            payload,
275        }
276    }
277    /// Get message from json string
278    pub fn from_json(json: &str) -> Result<Self> {
279        Ok(serde_json::from_str(json)?)
280    }
281    /// Get message as json string
282    pub fn as_json(&self) -> Result<String> {
283        Ok(serde_json::to_string(&self)?)
284    }
285
286    // Get action from the inner message
287    pub fn get_action(&self) -> Action {
288        self.action.clone()
289    }
290
291    /// Get the next trade keys when order is settled
292    pub fn get_next_trade_key(&self) -> Result<Option<(String, u32)>, ServiceError> {
293        match &self.payload {
294            Some(Payload::NextTrade(key, index)) => Ok(Some((key.to_string(), *index))),
295            None => Ok(None),
296            _ => Err(ServiceError::InvalidPayload),
297        }
298    }
299
300    pub fn get_rating(&self) -> Result<u8, ServiceError> {
301        if let Some(Payload::RatingUser(v)) = self.payload.to_owned() {
302            if !(MIN_RATING..=MAX_RATING).contains(&v) {
303                return Err(ServiceError::InvalidRatingValue);
304            }
305            Ok(v)
306        } else {
307            Err(ServiceError::InvalidRating)
308        }
309    }
310
311    /// Verify if is valid message
312    pub fn verify(&self) -> bool {
313        match &self.action {
314            Action::NewOrder => matches!(&self.payload, Some(Payload::Order(_))),
315            Action::PayInvoice | Action::AddInvoice => {
316                if self.id.is_none() {
317                    return false;
318                }
319                matches!(&self.payload, Some(Payload::PaymentRequest(_, _, _)))
320            }
321            Action::TakeSell
322            | Action::TakeBuy
323            | Action::FiatSent
324            | Action::FiatSentOk
325            | Action::Release
326            | Action::Released
327            | Action::Dispute
328            | Action::AdminCancel
329            | Action::AdminCanceled
330            | Action::AdminSettle
331            | Action::AdminSettled
332            | Action::Rate
333            | Action::RateReceived
334            | Action::AdminTakeDispute
335            | Action::AdminTookDispute
336            | Action::DisputeInitiatedByYou
337            | Action::DisputeInitiatedByPeer
338            | Action::WaitingBuyerInvoice
339            | Action::PurchaseCompleted
340            | Action::HoldInvoicePaymentAccepted
341            | Action::HoldInvoicePaymentSettled
342            | Action::HoldInvoicePaymentCanceled
343            | Action::WaitingSellerToPay
344            | Action::BuyerTookOrder
345            | Action::BuyerInvoiceAccepted
346            | Action::CooperativeCancelInitiatedByYou
347            | Action::CooperativeCancelInitiatedByPeer
348            | Action::CooperativeCancelAccepted
349            | Action::Cancel
350            | Action::PaymentFailed
351            | Action::InvoiceUpdated
352            | Action::AdminAddSolver
353            | Action::SendDm
354            | Action::TradePubkey
355            | Action::Canceled => {
356                if self.id.is_none() {
357                    return false;
358                }
359                true
360            }
361            Action::RateUser => {
362                matches!(&self.payload, Some(Payload::RatingUser(_)))
363            }
364            Action::CantDo => {
365                matches!(&self.payload, Some(Payload::CantDo(_)))
366            }
367        }
368    }
369
370    pub fn get_order(&self) -> Option<&SmallOrder> {
371        if self.action != Action::NewOrder {
372            return None;
373        }
374        match &self.payload {
375            Some(Payload::Order(o)) => Some(o),
376            _ => None,
377        }
378    }
379
380    pub fn get_payment_request(&self) -> Option<String> {
381        if self.action != Action::TakeSell
382            && self.action != Action::AddInvoice
383            && self.action != Action::NewOrder
384        {
385            return None;
386        }
387        match &self.payload {
388            Some(Payload::PaymentRequest(_, pr, _)) => Some(pr.to_owned()),
389            Some(Payload::Order(ord)) => ord.buyer_invoice.to_owned(),
390            _ => None,
391        }
392    }
393
394    pub fn get_amount(&self) -> Option<Amount> {
395        if self.action != Action::TakeSell && self.action != Action::TakeBuy {
396            return None;
397        }
398        match &self.payload {
399            Some(Payload::PaymentRequest(_, _, amount)) => *amount,
400            Some(Payload::Amount(amount)) => Some(*amount),
401            _ => None,
402        }
403    }
404
405    pub fn get_payload(&self) -> Option<&Payload> {
406        self.payload.as_ref()
407    }
408
409    pub fn has_trade_index(&self) -> (bool, i64) {
410        if let Some(index) = self.trade_index {
411            return (true, index);
412        }
413        (false, 0)
414    }
415}