use axum::{
body::Body,
http::{Request, StatusCode},
};
use http_body_util::BodyExt;
use mostro_core::message::Message as MostroMessage;
use mostro_webtool::app;
use nostr_sdk::prelude::*;
use serde_json::{json, Value};
use tower::ServiceExt;
const SAMPLE_MNEMONIC: &str =
"leader monkey parrot ring guide accident before fence cannon height naive bean";
fn get_test_keys(trade_index: u32) -> (Keys, Keys) {
let identity_keys = Keys::from_mnemonic_advanced(
SAMPLE_MNEMONIC,
None::<&str>,
Some(38_383),
Some(0),
Some(0),
)
.unwrap();
let trade_keys = Keys::from_mnemonic_advanced(
SAMPLE_MNEMONIC,
None::<&str>,
Some(38_383),
Some(0),
Some(trade_index),
)
.unwrap();
(identity_keys, trade_keys)
}
fn create_test_message() -> MostroMessage {
use mostro_core::message::{Action, Payload};
use mostro_core::order::{Kind as OrderKind, SmallOrder, Status};
let order = SmallOrder {
id: None,
kind: Some(OrderKind::Buy),
status: Some(Status::Pending),
amount: 1000,
fiat_code: "USD".to_string(),
min_amount: None,
max_amount: None,
fiat_amount: 100,
payment_method: "bank transfer".to_string(),
premium: 0,
buyer_trade_pubkey: None,
seller_trade_pubkey: None,
buyer_invoice: None,
created_at: Some(1234567890),
expires_at: None,
};
MostroMessage::new_order(
None,
Some(1),
Some(1),
Action::NewOrder,
Some(Payload::Order(order)),
)
}
async fn call_build_gift_wrap_api(
message: &MostroMessage,
mostro_pubkey: &str,
trade_index: u32,
) -> Event {
let router = app();
let message_json = message.as_json().unwrap();
let payload = json!({
"mnemonic": SAMPLE_MNEMONIC,
"trade_index": trade_index,
"mostro_pubkey": mostro_pubkey,
"message_json": message_json,
});
let response = router
.oneshot(
Request::builder()
.method("POST")
.uri("/api/build-gift-wrap")
.header("content-type", "application/json")
.body(Body::from(payload.to_string()))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let bytes = BodyExt::collect(response.into_body())
.await
.unwrap()
.to_bytes();
let value: Value = serde_json::from_slice(&bytes).unwrap();
let event_json = serde_json::to_string(&value["gift_wrap_event"]).unwrap();
Event::from_json(&event_json).unwrap()
}
#[tokio::test]
async fn test_gift_wrap_contains_valid_rumor() {
let trade_index = 1u32;
let (identity_keys, trade_keys) = get_test_keys(trade_index);
let message = create_test_message();
let gift_wrap =
call_build_gift_wrap_api(&message, &identity_keys.public_key().to_hex(), trade_index).await;
assert_eq!(gift_wrap.kind, Kind::GiftWrap);
let unwrapped = nip59::extract_rumor(&identity_keys, &gift_wrap)
.await
.unwrap();
assert_eq!(
unwrapped.rumor.pubkey,
trade_keys.public_key(),
"Rumor should be signed with trade key (index {})",
trade_index
);
assert_eq!(unwrapped.rumor.kind, Kind::Custom(1));
let (message_from_rumor, signature): (MostroMessage, Signature) =
serde_json::from_str(&unwrapped.rumor.content).unwrap();
assert_eq!(
message.as_json().unwrap(),
message_from_rumor.as_json().unwrap(),
"Message in rumor should match original"
);
let is_valid = MostroMessage::verify_signature(
message.as_json().unwrap(),
trade_keys.public_key(),
signature,
);
assert!(is_valid, "Signature should be valid");
}
#[tokio::test]
async fn test_gift_wrap_seal_signed_with_identity_key() {
let trade_index = 1u32;
let (identity_keys, _trade_keys) = get_test_keys(trade_index);
let message = create_test_message();
let gift_wrap =
call_build_gift_wrap_api(&message, &identity_keys.public_key().to_hex(), trade_index).await;
let unwrapped = nip59::extract_rumor(&identity_keys, &gift_wrap)
.await
.unwrap();
assert_eq!(
unwrapped.sender,
identity_keys.public_key(),
"Seal should be signed with identity key (index 0)"
);
}
#[tokio::test]
async fn test_gift_wrap_signature_verification() {
let trade_index = 1u32;
let (identity_keys, trade_keys) = get_test_keys(trade_index);
let message = create_test_message();
let message_str = message.as_json().unwrap();
let gift_wrap =
call_build_gift_wrap_api(&message, &identity_keys.public_key().to_hex(), trade_index).await;
let unwrapped = nip59::extract_rumor(&identity_keys, &gift_wrap)
.await
.unwrap();
let (_, signature): (MostroMessage, Signature) =
serde_json::from_str(&unwrapped.rumor.content).unwrap();
let is_valid = MostroMessage::verify_signature(message_str, trade_keys.public_key(), signature);
assert!(
is_valid,
"Signature should be valid for trade key (index {})",
trade_index
);
}
#[tokio::test]
async fn test_gift_wrap_with_different_trade_indices() {
let (identity_keys, _) = get_test_keys(1);
for trade_index in [1, 2, 5, 10] {
let (_, trade_keys) = get_test_keys(trade_index);
let message = create_test_message();
let gift_wrap =
call_build_gift_wrap_api(&message, &identity_keys.public_key().to_hex(), trade_index)
.await;
let unwrapped = nip59::extract_rumor(&identity_keys, &gift_wrap)
.await
.unwrap();
assert_eq!(
unwrapped.rumor.pubkey,
trade_keys.public_key(),
"Rumor should use correct trade key for index {}",
trade_index
);
assert_eq!(
unwrapped.sender,
identity_keys.public_key(),
"Seal should always be signed with identity key"
);
}
}
#[tokio::test]
async fn test_gift_wrap_message_contains_trade_index() {
let trade_index = 5u32; let (identity_keys, _trade_keys) = get_test_keys(trade_index);
let message = create_test_message();
let gift_wrap =
call_build_gift_wrap_api(&message, &identity_keys.public_key().to_hex(), trade_index).await;
let unwrapped = nip59::extract_rumor(&identity_keys, &gift_wrap)
.await
.unwrap();
let (message_from_rumor, _): (MostroMessage, Signature) =
serde_json::from_str(&unwrapped.rumor.content).unwrap();
let inner = message_from_rumor.get_inner_message_kind();
assert_eq!(
inner.trade_index,
Some(trade_index as i64),
"Message should contain trade_index = {}",
trade_index
);
}