Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trade key index to msg #67

Merged
merged 5 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mostro-core"
version = "0.6.11"
version = "0.6.12"
edition = "2021"
license = "MIT"
authors = ["Francisco Calderón <negrunch@grunch.dev>"]
Expand Down Expand Up @@ -33,6 +33,8 @@ sqlx-crud = { version = "0.4.0", features = [
], optional = true }
wasm-bindgen = { version = "0.2.92", optional = true }
nostr-sdk = "0.36.0"
bitcoin = "0.32.5"
bitcoin_hashes = "0.15.0"

[features]
default = ["wasm"]
Expand Down
94 changes: 70 additions & 24 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ pub const PROTOCOL_VER: u8 = 1;

#[cfg(test)]
mod test {
use crate::message::{Action, Content, Message, MessageKind};
use crate::message::{Action, Content, Message, MessageKind, Peer};
use crate::order::{Kind, SmallOrder, Status};
use bitcoin::hashes::sha256::Hash as Sha256Hash;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::Message as BitcoinMessage;
use nostr_sdk::Keys;
use serde_json::{json, Value};
use uuid::uuid;

#[test]
fn test_status_string() {
assert_eq!(Status::Active.to_string(), "active");
Expand All @@ -33,31 +39,35 @@ mod test {
#[test]
fn test_order_message() {
let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
let content = Content::Order(SmallOrder::new(
Some(uuid),
Some(Kind::Sell),
Some(Status::Pending),
100,
"eur".to_string(),
None,
None,
100,
"SEPA".to_string(),
1,
None,
None,
None,
Some(1627371434),
None,
None,
None,
));

let test_message = Message::Order(MessageKind::new(
Some(1),
Some(uuid),
Some(1),
Some(2),
Action::NewOrder,
Some(Content::Order(SmallOrder::new(
Some(uuid),
Some(Kind::Sell),
Some(Status::Pending),
100,
"eur".to_string(),
None,
None,
100,
"SEPA".to_string(),
1,
None,
None,
None,
Some(1627371434),
None,
None,
None,
))),
Some(content),
None,
));
let sample_message = r#"{"order":{"version":1,"request_id":1,"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","pubkey":null,"action":"new-order","content":{"order":{"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","kind":"sell","status":"pending","amount":100,"fiat_code":"eur","fiat_amount":100,"payment_method":"SEPA","premium":1,"created_at":1627371434}}}}"#;
let sample_message = r#"{"order":{"version":1,"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","request_id":1,"trade_index":2,"action":"new-order","content":[{"order":{"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","kind":"sell","status":"pending","amount":100,"fiat_code":"eur","fiat_amount":100,"payment_method":"SEPA","premium":1,"created_at":1627371434}},null]}}"#;
let message = Message::from_json(sample_message).unwrap();
assert!(message.verify());
let message_json = message.as_json().unwrap();
Expand All @@ -69,8 +79,9 @@ mod test {
fn test_payment_request_content_message() {
let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
let test_message = Message::Order(MessageKind::new(
Some(1),
Some(uuid),
Some(1),
Some(3),
Action::PayInvoice,
Some(Content::PaymentRequest(
Some(SmallOrder::new(
Expand All @@ -95,12 +106,47 @@ mod test {
"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e".to_string(),
None,
)),
None,
));
let sample_message = r#"{"order":{"version":1,"request_id":1,"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","pubkey":null,"action":"pay-invoice","content":{"payment_request":[{"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","kind":"sell","status":"waiting-payment","amount":100,"fiat_code":"eur","fiat_amount":100,"payment_method":"SEPA","premium":1,"created_at":1627371434},"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e",null]}}}"#;
let sample_message = r#"{"order":{"version":1,"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","request_id":1,"trade_index":3,"action":"pay-invoice","content":[{"payment_request":[{"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","kind":"sell","status":"waiting-payment","amount":100,"fiat_code":"eur","fiat_amount":100,"payment_method":"SEPA","premium":1,"created_at":1627371434},"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e",null]},null]}}"#;
let message = Message::from_json(sample_message).unwrap();
assert!(message.verify());
let message_json = message.as_json().unwrap();
let test_message_json = test_message.as_json().unwrap();
assert_eq!(message_json, test_message_json);
}

#[test]
fn test_message_content_signature() {
let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
let peer = Peer::new(
"npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
);
let content = Content::Peer(peer);
// content should be sha256 hashed
let json: Value = json!(content);
let content_str: String = json.to_string();
let hash: Sha256Hash = Sha256Hash::hash(content_str.as_bytes());
let hash = hash.to_byte_array();
let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
// content should be signed with the trade keys
let trade_keys =
Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
.unwrap();
let sig = trade_keys.sign_schnorr(&message);

let test_message = Message::Order(MessageKind::new(
Some(uuid),
Some(1),
Some(2),
Action::FiatSentOk,
Some(content),
Some(sig),
));

assert!(test_message.verify());
assert!(test_message
.get_inner_message_kind()
.verify_content_signature(trade_keys.public_key()));
}
grunch marked this conversation as resolved.
Show resolved Hide resolved
}
82 changes: 64 additions & 18 deletions src/message.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use crate::order::SmallOrder;
use crate::PROTOCOL_VER;
use anyhow::{Ok, Result};
use bitcoin::hashes::sha256::Hash as Sha256Hash;
use bitcoin::hashes::Hash;
use bitcoin::key::Secp256k1;
use bitcoin::secp256k1::Message as BitcoinMessage;
use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::fmt;
use uuid::Uuid;

Expand Down Expand Up @@ -96,31 +102,41 @@ pub enum Message {
impl Message {
/// New order message
pub fn new_order(
request_id: Option<u64>,
id: Option<Uuid>,
request_id: Option<u64>,
trade_index: Option<u32>,
action: Action,
content: Option<Content>,
sig: Option<Signature>,
) -> Self {
let kind = MessageKind::new(request_id, id, action, content);
let kind = MessageKind::new(id, request_id, trade_index, action, content, sig);

Self::Order(kind)
}

/// New dispute message
pub fn new_dispute(
request_id: Option<u64>,
id: Option<Uuid>,
request_id: Option<u64>,
trade_index: Option<u32>,
action: Action,
content: Option<Content>,
sig: Option<Signature>,
) -> Self {
let kind = MessageKind::new(request_id, id, action, content);
let kind = MessageKind::new(id, request_id, trade_index, action, content, sig);

Self::Dispute(kind)
}

/// New can't do template message message
pub fn cant_do(request_id: Option<u64>, id: Option<Uuid>, content: Option<Content>) -> Self {
let kind = MessageKind::new(request_id, id, Action::CantDo, content);
pub fn cant_do(
id: Option<Uuid>,
request_id: Option<u64>,
trade_index: Option<u32>,
content: Option<Content>,
sig: Option<Signature>,
) -> Self {
let kind = MessageKind::new(id, request_id, trade_index, Action::CantDo, content, sig);

Self::CantDo(kind)
}
Expand Down Expand Up @@ -173,13 +189,15 @@ pub struct MessageKind {
pub version: u8,
/// Request_id for test on client
pub request_id: Option<u64>,
/// Trade key index
pub trade_index: Option<u32>,
/// Message id is not mandatory
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Uuid>,
/// Action to be taken
pub action: Action,
/// Message content
pub content: Option<Content>,
/// Tuple for Message content and its signature
pub content: (Option<Content>, Option<Signature>),
}

type Amount = i64;
Expand All @@ -201,17 +219,20 @@ pub enum Content {
impl MessageKind {
/// New message
pub fn new(
request_id: Option<u64>,
id: Option<Uuid>,
request_id: Option<u64>,
trade_index: Option<u32>,
grunch marked this conversation as resolved.
Show resolved Hide resolved
action: Action,
content: Option<Content>,
sig: Option<Signature>,
) -> Self {
Self {
version: PROTOCOL_VER,
request_id,
trade_index,
id,
action,
content,
content: (content, sig),
}
}
/// Get message from json string
Expand All @@ -231,12 +252,12 @@ impl MessageKind {
/// Verify if is valid message
pub fn verify(&self) -> bool {
match &self.action {
Action::NewOrder => matches!(&self.content, Some(Content::Order(_))),
Action::NewOrder => matches!(&self.content.0, Some(Content::Order(_))),
Action::PayInvoice | Action::AddInvoice => {
if self.id.is_none() {
return false;
}
matches!(&self.content, Some(Content::PaymentRequest(_, _, _)))
matches!(&self.content.0, Some(Content::PaymentRequest(_, _, _)))
}
Action::TakeSell
| Action::TakeBuy
Expand Down Expand Up @@ -285,10 +306,10 @@ impl MessageKind {
true
}
Action::RateUser => {
matches!(&self.content, Some(Content::RatingUser(_)))
matches!(&self.content.0, Some(Content::RatingUser(_)))
}
Action::CantDo => {
matches!(&self.content, Some(Content::TextMessage(_)))
matches!(&self.content.0, Some(Content::TextMessage(_)))
}
}
}
Expand All @@ -297,7 +318,7 @@ impl MessageKind {
if self.action != Action::NewOrder {
return None;
}
match &self.content {
match &self.content.0 {
Some(Content::Order(o)) => Some(o),
_ => None,
}
Expand All @@ -310,7 +331,7 @@ impl MessageKind {
{
return None;
}
match &self.content {
match &self.content.0 {
Some(Content::PaymentRequest(_, pr, _)) => Some(pr.to_owned()),
Some(Content::Order(ord)) => ord.buyer_invoice.to_owned(),
_ => None,
Expand All @@ -321,14 +342,39 @@ impl MessageKind {
if self.action != Action::TakeSell && self.action != Action::TakeBuy {
return None;
}
match &self.content {
match &self.content.0 {
Some(Content::PaymentRequest(_, _, amount)) => *amount,
Some(Content::Amount(amount)) => Some(*amount),
_ => None,
}
}

pub fn get_content(&self) -> Option<&Content> {
self.content.as_ref()
self.content.0.as_ref()
}

pub fn get_signature(&self) -> Option<&Signature> {
self.content.1.as_ref()
}

pub fn verify_content_signature(&self, pubkey: PublicKey) -> bool {
let content = match self.get_content() {
Some(c) => c,
_ => return false,
};
// Get signature or return false if None
let sig = match self.get_signature() {
Some(s) => s,
_ => return false,
}; // Create message hash
let json: Value = json!(content);
let content_str: String = json.to_string();
let hash: Sha256Hash = Sha256Hash::hash(content_str.as_bytes());
let hash = hash.to_byte_array();
let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
// Create a verification-only context for better performance
let secp = Secp256k1::verification_only();
// Verify signature
pubkey.verify(&secp, &message, sig).is_ok()
}
}
3 changes: 3 additions & 0 deletions src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct User {
pub is_banned: i64,
pub category: i64,
pub created_at: i64,
pub trade_index: u64,
}

impl User {
Expand All @@ -26,6 +27,7 @@ impl User {
is_solver: i64,
is_banned: i64,
category: i64,
trade_index: u64
) -> Self {
Self {
id: Uuid::new_v4(),
Expand All @@ -35,6 +37,7 @@ impl User {
is_banned,
category,
created_at: Utc::now().timestamp(),
trade_index
}
}
}
Loading