From 74275ac84c5d2edd2ee415b1fbeed6dc49caa84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Mon, 2 Dec 2024 17:09:30 -0300 Subject: [PATCH 1/5] Add tradekeyindex in message --- src/message.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/message.rs b/src/message.rs index b033461..904a21d 100644 --- a/src/message.rs +++ b/src/message.rs @@ -173,6 +173,8 @@ pub struct MessageKind { pub version: u8, /// Request_id for test on client pub request_id: Option, + /// Trade key index + pub trade_key_index: Option, /// Message id is not mandatory #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, From c3cd110bffc0b000f5b4da0ce1c5b54b8c05d44d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Tue, 3 Dec 2024 18:56:34 -0300 Subject: [PATCH 2/5] More improvements related with content signature * Add bitcoin and bitcoin_hashes dependencies * Fix unit tests with new fields `trade_index`, `trade_index`, `signature` and new content type * Add verify_content_signature() message function to verify content's signature * Add test for verify_content_signature() * Code refactoring --- Cargo.toml | 2 ++ src/lib.rs | 94 +++++++++++++++++++++++++++++++++++++------------- src/message.rs | 79 ++++++++++++++++++++++++++++++++---------- 3 files changed, 132 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a30e43f..0fcab38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/src/lib.rs b/src/lib.rs index 9de8f5a..3cac02b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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"); @@ -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(); @@ -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( @@ -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())); + } } diff --git a/src/message.rs b/src/message.rs index 904a21d..d8b1914 100644 --- a/src/message.rs +++ b/src/message.rs @@ -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; @@ -96,31 +102,41 @@ pub enum Message { impl Message { /// New order message pub fn new_order( - request_id: Option, id: Option, + request_id: Option, + trade_index: Option, action: Action, content: Option, + sig: Option, ) -> 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, id: Option, + request_id: Option, + trade_index: Option, action: Action, content: Option, + sig: Option, ) -> 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, id: Option, content: Option) -> Self { - let kind = MessageKind::new(request_id, id, Action::CantDo, content); + pub fn cant_do( + id: Option, + request_id: Option, + trade_index: Option, + content: Option, + sig: Option, + ) -> Self { + let kind = MessageKind::new(id, request_id, trade_index, Action::CantDo, content, sig); Self::CantDo(kind) } @@ -174,14 +190,14 @@ pub struct MessageKind { /// Request_id for test on client pub request_id: Option, /// Trade key index - pub trade_key_index: Option, + pub trade_index: Option, /// Message id is not mandatory #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, /// Action to be taken pub action: Action, - /// Message content - pub content: Option, + /// Tuple for Message content and its signature + pub content: (Option, Option), } type Amount = i64; @@ -203,17 +219,20 @@ pub enum Content { impl MessageKind { /// New message pub fn new( - request_id: Option, id: Option, + request_id: Option, + trade_index: Option, action: Action, content: Option, + sig: Option, ) -> Self { Self { version: PROTOCOL_VER, request_id, + trade_index, id, action, - content, + content: (content, sig), } } /// Get message from json string @@ -233,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 @@ -287,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(_))) } } } @@ -299,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, } @@ -312,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, @@ -323,7 +342,7 @@ 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, @@ -331,6 +350,28 @@ impl MessageKind { } 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, + }; + 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); + let sig = match self.get_signature() { + Some(s) => s, + _ => return false, + }; + let secp = Secp256k1::new(); + pubkey.verify(&secp, &message, sig).is_ok() } } From 7d6cc8f9d4d831220e6a2d7f0244644b01070889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Wed, 4 Dec 2024 10:36:47 -0300 Subject: [PATCH 3/5] Improving verify_content_signature() * Changed Secp256k1::new() to Secp256k1::verification_only() for better performance * Moved the signature check earlier to fail fast * Simplified the hash to message conversion by combining steps * Improved code organization for better readability --- src/message.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/message.rs b/src/message.rs index d8b1914..2aff4ce 100644 --- a/src/message.rs +++ b/src/message.rs @@ -362,16 +362,19 @@ impl MessageKind { 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); - let sig = match self.get_signature() { - Some(s) => s, - _ => return false, - }; - let secp = Secp256k1::new(); + // Create a verification-only context for better performance + let secp = Secp256k1::verification_only(); + // Verify signature pubkey.verify(&secp, &message, sig).is_ok() } } From 7f4c90fbeed64ffca1b50e7f93490e21959fd664 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Wed, 4 Dec 2024 22:23:49 +0100 Subject: [PATCH 4/5] added trade index to user struct --- src/user.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/user.rs b/src/user.rs index 309b02b..31f4c02 100644 --- a/src/user.rs +++ b/src/user.rs @@ -17,6 +17,7 @@ pub struct User { pub is_banned: i64, pub category: i64, pub created_at: i64, + pub trade_index: u64, } impl User { @@ -26,6 +27,7 @@ impl User { is_solver: i64, is_banned: i64, category: i64, + trade_index: u64 ) -> Self { Self { id: Uuid::new_v4(), @@ -35,6 +37,7 @@ impl User { is_banned, category, created_at: Utc::now().timestamp(), + trade_index } } } From bdbaa47857f660cc7ef520054d8f616ec3731fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Thu, 5 Dec 2024 23:14:56 -0300 Subject: [PATCH 5/5] bumps to 0.6.12 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0fcab38..3121c4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mostro-core" -version = "0.6.11" +version = "0.6.12" edition = "2021" license = "MIT" authors = ["Francisco Calderón "]