diff --git a/rust/src/api/orders.rs b/rust/src/api/orders.rs index 93a0a26c..d9ccc3bb 100644 --- a/rust/src/api/orders.rs +++ b/rust/src/api/orders.rs @@ -1048,7 +1048,11 @@ pub(crate) async fn subscribe_gift_wraps(trade_pubkey: nostr_sdk::PublicKey, tra /// Dispatch a Mostro `Message` recovered from a gift-wrap. /// -/// Authenticates the sender against the active Mostro pubkey, runs the +/// The caller recovers the `UnwrappedMessage` via +/// `crate::nostr::gift_wrap::unwrap_mostro_message`, which verifies the seal +/// signature so the `identity` field is cryptographically attributable. +/// This function authenticates that seal signer's `identity` against the +/// active Mostro pubkey (never the self-asserted rumor `sender`), runs the /// centralized `validate_response` check (catches `CantDo` responses and /// malformed `request_id` fields), then routes by action. async fn dispatch_mostro_message( @@ -1058,29 +1062,36 @@ async fn dispatch_mostro_message( ) { use mostro_core::message::Action; - // mostro-core 0.10 adds `identity` (seal signer, long-lived) alongside - // `sender` (rumor author, per-trade). A Mostro node that opts out of the - // identity/trade split — the current daemon — reuses the same key for - // both, so `identity == sender`. We keep the existing sender-based - // authentication check below; the extra `identity` field is deliberately - // ignored here. + // mostro-core 0.10 splits the wrap into two pubkeys: + // + // * `identity` — seal signer, proven by `seal.verify_signature()` in + // `unwrap_message`. This is the only field a forger cannot spoof. + // * `sender` — rumor author. Self-asserted unless an inner + // `signature` is present and verifies against the rumor pubkey. + // + // In Mostro's reputation-mode key split `sender` (per-trade key) and + // `identity` (long-lived seal key) are expected to differ, and protocol + // responses commonly omit the inner signature, so we cannot use a + // sender/identity equality check to gate dispatch. Authenticate against + // `identity` only — a forger who seals with their own key cannot make + // it match the configured Mostro pubkey. let mostro_core::nip59::UnwrappedMessage { message: msg, - sender, - identity: _, + sender: _, + identity, signature: _, created_at: _, } = unwrapped; - // Daemon authentication: the active Mostro pubkey is the only legitimate - // sender for protocol responses. Reject anything else loudly — previously - // we trusted whatever decrypted under our trade key. + // Daemon authentication: the seal signer must be the active Mostro + // pubkey. The seal signature is verified inside `unwrap_message`, so + // `identity` is the cryptographically authoritative origin. match nostr_sdk::PublicKey::from_hex(&crate::config::active_mostro_pubkey()) { - Ok(expected) if expected == sender => {} + Ok(expected) if expected == identity => {} Ok(expected) => { crate::api::logging::blog_warn("gift-wrap", format!( - "rejecting gift-wrap: sender={} != active mostro={} (trade={})", - &sender.to_hex()[..8], + "rejecting gift-wrap: identity={} != active mostro={} (trade={})", + &identity.to_hex()[..8], &expected.to_hex()[..8], &trade_pubkey_hex[..8], )); diff --git a/rust/src/nostr/gift_wrap.rs b/rust/src/nostr/gift_wrap.rs index 678abed0..dcc5f6aa 100644 --- a/rust/src/nostr/gift_wrap.rs +++ b/rust/src/nostr/gift_wrap.rs @@ -62,11 +62,13 @@ pub async fn wrap_mostro_message( /// differ. The returned `UnwrappedMessage` exposes both (`identity` vs. /// `sender`) so callers can route and authorize accordingly. /// -/// Daemon authentication — verifying that `UnwrappedMessage.sender` equals -/// the active Mostro pubkey — is NOT performed here. The seal author is -/// proven via the inner signature but could in principle be any key; the -/// upstream dispatcher in `api/orders.rs` enforces the Mostro-pubkey check -/// before routing the message. +/// Daemon authentication is NOT performed here. The seal signature is +/// verified (so `identity` is cryptographically attributable) but the seal +/// signer could in principle be any key, and the rumor pubkey (`sender`) +/// is only meaningful when an inner signature accompanies it. The upstream +/// dispatcher in `api/orders.rs` enforces the Mostro-pubkey check against +/// `identity` — never against the unauthenticated `sender` — before +/// routing the message. pub async fn unwrap_mostro_message( trade_keys: &Keys, event: &Event,