diff --git a/crates/nostr-sdk/examples/nip47.rs b/crates/nostr-sdk/examples/nip47.rs index 322e91915..76a17a710 100644 --- a/crates/nostr-sdk/examples/nip47.rs +++ b/crates/nostr-sdk/examples/nip47.rs @@ -35,14 +35,11 @@ async fn main() -> Result<()> { client.connect().await; println!("Connected to relay {}", nwc_uri.relay_url); - let req = nip47::Request { - method: Method::PayInvoice, - params: RequestParams::PayInvoice(PayInvoiceRequestParams { - id: None, - invoice, - amount: None, - }), - }; + let req = nip47::Request::pay_invoice(PayInvoiceRequestParams { + id: None, + invoice, + amount: None, + }); let req_event = req.to_event(&nwc_uri).unwrap(); let subscription = Filter::new() diff --git a/crates/nostr-signer/src/lib.rs b/crates/nostr-signer/src/lib.rs index 6465c1340..0f8d59321 100644 --- a/crates/nostr-signer/src/lib.rs +++ b/crates/nostr-signer/src/lib.rs @@ -46,23 +46,6 @@ pub enum Error { #[cfg(feature = "nip46")] #[error(transparent)] NIP46(#[from] nip46::Error), - /// Response not match to the request - #[cfg(feature = "nip46")] - #[error("response not match to the request")] - ResponseNotMatchRequest, - /// Not supported yet - #[error("{0}")] - Unsupported(String), -} - -#[cfg(feature = "nip44")] -impl Error { - fn unsupported(message: S) -> Self - where - S: Into, - { - Self::Unsupported(message.into()) - } } /// Nostr Signer Type @@ -154,16 +137,7 @@ impl NostrSigner { #[cfg(all(feature = "nip07", target_arch = "wasm32"))] Self::NIP07(nip07) => Ok(nip07.sign_event(unsigned).await?), #[cfg(feature = "nip46")] - Self::NIP46(nip46) => { - let res = nip46 - .send_req_to_signer(nostr::nips::nip46::Request::SignEvent(unsigned), None) - .await?; - if let nostr::nips::nip46::Response::SignEvent(event) = res { - Ok(event) - } else { - Err(Error::ResponseNotMatchRequest) - } - } + Self::NIP46(nip46) => Ok(nip46.sign_event(unsigned).await?), } } @@ -179,19 +153,7 @@ impl NostrSigner { #[cfg(all(feature = "nip07", target_arch = "wasm32"))] Self::NIP07(signer) => Ok(signer.nip04_encrypt(public_key, content).await?), #[cfg(feature = "nip46")] - Self::NIP46(signer) => { - let req = nostr::nips::nip46::Request::Nip04Encrypt { - public_key, - text: String::from_utf8_lossy(content).to_string(), - }; - let res: nostr::nips::nip46::Response = - signer.send_req_to_signer(req, None).await?; - if let nostr::nips::nip46::Response::Nip04Encrypt(ciphertext) = res { - Ok(ciphertext) - } else { - Err(Error::ResponseNotMatchRequest) - } - } + Self::NIP46(signer) => Ok(signer.nip04_encrypt(public_key, content).await?), } } @@ -215,19 +177,7 @@ impl NostrSigner { #[cfg(all(feature = "nip07", target_arch = "wasm32"))] Self::NIP07(signer) => Ok(signer.nip04_decrypt(public_key, encrypted_content).await?), #[cfg(feature = "nip46")] - Self::NIP46(signer) => { - let req = nostr::nips::nip46::Request::Nip04Decrypt { - public_key, - text: encrypted_content.to_string(), - }; - let res: nostr::nips::nip46::Response = - signer.send_req_to_signer(req, None).await?; - if let nostr::nips::nip46::Response::Nip04Decrypt(content) = res { - Ok(content) - } else { - Err(Error::ResponseNotMatchRequest) - } - } + Self::NIP46(signer) => Ok(signer.nip04_decrypt(public_key, encrypted_content).await?), } } @@ -248,9 +198,7 @@ impl NostrSigner { #[cfg(all(feature = "nip07", target_arch = "wasm32"))] Self::NIP07(signer) => Ok(signer.nip44_encrypt(public_key, content).await?), #[cfg(feature = "nip46")] - Self::NIP46(..) => Err(Error::unsupported( - "NIP44 encryption not supported with NIP46 signer yet!", - )), + Self::NIP46(signer) => Ok(signer.nip44_encrypt(public_key, content).await?), } } @@ -266,9 +214,7 @@ impl NostrSigner { #[cfg(all(feature = "nip07", target_arch = "wasm32"))] Self::NIP07(signer) => Ok(signer.nip44_decrypt(public_key, payload).await?), #[cfg(feature = "nip46")] - Self::NIP46(..) => Err(Error::unsupported( - "NIP44 decryption not supported with NIP46 signer yet!", - )), + Self::NIP46(signer) => Ok(signer.nip44_decrypt(public_key, payload).await?), } } } diff --git a/crates/nostr-signer/src/nip46.rs b/crates/nostr-signer/src/nip46.rs index 2f5f6a946..6c6761da4 100644 --- a/crates/nostr-signer/src/nip46.rs +++ b/crates/nostr-signer/src/nip46.rs @@ -10,11 +10,15 @@ use std::sync::Arc; use std::time::Duration; use async_utility::time; -use nostr::nips::nip46::{self, Message, NostrConnectMetadata, NostrConnectURI, Request, Response}; +use nostr::nips::nip46::{ + self, Message, NostrConnectMetadata, NostrConnectURI, Request, ResponseResult, +}; use nostr::prelude::*; use nostr::{key, serde_json}; use nostr_relay_pool::pool::RelayPool; -use nostr_relay_pool::{RelayOptions, RelayPoolNotification, RelayPoolOptions, RelaySendOptions}; +use nostr_relay_pool::{ + FilterOptions, RelayOptions, RelayPoolNotification, RelayPoolOptions, RelaySendOptions, +}; use thiserror::Error; use tokio::sync::Mutex; @@ -36,12 +40,12 @@ pub enum Error { /// NIP46 error #[error(transparent)] NIP46(#[from] nip46::Error), + /// Relay + #[error(transparent)] + Relay(#[from] nostr_relay_pool::relay::Error), /// Pool #[error(transparent)] Pool(#[from] nostr_relay_pool::pool::Error), - /// Generic NIP46 error - #[error("generic error")] - Generic, /// NIP46 response error #[error("response error: {0}")] Response(String), @@ -126,78 +130,142 @@ impl Nip46Signer { NostrConnectURI::with_metadata(self.app_keys.public_key(), self.relay_url(), metadata) } - async fn get_signer_public_key(&self) -> Result { - let public_key = self.app_keys.public_key(); - let secret_key = self.app_keys.secret_key()?; + async fn send_request(&self, req: &Request) -> Result { + let public_key: PublicKey = self.app_keys.public_key(); + let secret_key: &SecretKey = self.app_keys.secret_key()?; + let signer_public_key: PublicKey = self.signer_public_key().await?; - let id = SubscriptionId::generate(); + // Convert request to event + let msg = Message::request(req.clone()); + let req_id = msg.id().to_string(); + let event: Event = EventBuilder::nostr_connect(&self.app_keys, signer_public_key, msg)? + .to_event(&self.app_keys)?; + + // Subscribe + let relay = self.pool.relay(self.relay_url()).await?; + let sub_id = SubscriptionId::generate(); let filter = Filter::new() .pubkey(public_key) .kind(Kind::NostrConnect) - .since(Timestamp::now()); - - // Subscribe - self.pool - .send_msg_to( - [self.relay_url()], - ClientMessage::req(id.clone(), vec![filter]), - RelaySendOptions::new(), + .since(Timestamp::now()) + .limit(1); + + relay + .send_req( + sub_id, + vec![filter], + Some(FilterOptions::WaitForEventsAfterEOSE(1)), ) .await?; let mut notifications = self.pool.notifications(); + + // Send request to signer + self.pool + .send_event_to([self.relay_url()], event, RelaySendOptions::new()) + .await?; + time::timeout(Some(self.timeout), async { while let Ok(notification) = notifications.recv().await { if let RelayPoolNotification::Event { event, .. } = notification { if event.kind() == Kind::NostrConnect { - let msg: String = - nip04::decrypt(secret_key, event.author_ref(), event.content())?; + let msg = nip04::decrypt(secret_key, event.author_ref(), event.content())?; let msg = Message::from_json(msg)?; - if let Ok(Request::Connect(pk)) = msg.to_request() { - // Unsubscribe - self.pool - .send_msg_to( - [self.relay_url()], - ClientMessage::close(id), - RelaySendOptions::new(), - ) - .await?; - return Ok(pk); + + if let Message::Response { id, result, error } = &msg { + if &req_id == id { + if let Some(result) = result { + return Ok(result.clone()); + } + + if let Some(error) = error { + return Err(Error::Response(error.to_owned())); + } + + break; + } } } } } - Err(Error::SignerPublicKeyNotFound) + Err(Error::Timeout) }) .await .ok_or(Error::Timeout)? } - /// Send NIP46 [`Request`] to signer - pub async fn send_req_to_signer( - &self, - req: Request, - timeout: Option, - ) -> Result { - let msg = Message::request(req.clone()); - let req_id = msg.id(); + /// Sign an [UnsignedEvent] + pub async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { + let req = Request::SignEvent(unsigned); + let res = self.send_request(&req).await?; + Ok(res.to_sign_event()?) + } - let public_key = self.app_keys.public_key(); - let secret_key = self.app_keys.secret_key()?; + /// NIP04 encrypt + pub async fn nip04_encrypt(&self, public_key: PublicKey, content: T) -> Result + where + T: AsRef<[u8]>, + { + let content: &[u8] = content.as_ref(); + let req = Request::Nip04Encrypt { + public_key, + text: String::from_utf8_lossy(content).to_string(), + }; + let res = self.send_request(&req).await?; + Ok(res.to_encrypt_decrypt()?) + } - let signer_public_key: PublicKey = self.signer_public_key().await?; + /// NIP04 decrypt + pub async fn nip04_decrypt( + &self, + public_key: PublicKey, + ciphertext: S, + ) -> Result + where + S: Into, + { + let req = Request::Nip04Decrypt { + public_key, + ciphertext: ciphertext.into(), + }; + let res = self.send_request(&req).await?; + Ok(res.to_encrypt_decrypt()?) + } - // Build request - let event = EventBuilder::nostr_connect(&self.app_keys, signer_public_key, msg)? - .to_event(&self.app_keys)?; + /// NIP44 encrypt + pub async fn nip44_encrypt(&self, public_key: PublicKey, content: T) -> Result + where + T: AsRef<[u8]>, + { + let content: &[u8] = content.as_ref(); + let req = Request::Nip44Encrypt { + public_key, + text: String::from_utf8_lossy(content).to_string(), + }; + let res = self.send_request(&req).await?; + Ok(res.to_encrypt_decrypt()?) + } - // Send request to signer - self.pool - .send_event_to([self.relay_url()], event, RelaySendOptions::new()) - .await?; + /// NIP44 decrypt + pub async fn nip44_decrypt(&self, public_key: PublicKey, payload: T) -> Result + where + T: AsRef<[u8]>, + { + let payload: &[u8] = payload.as_ref(); + let req = Request::Nip44Decrypt { + public_key, + ciphertext: String::from_utf8_lossy(payload).to_string(), + }; + let res = self.send_request(&req).await?; + Ok(res.to_encrypt_decrypt()?) + } - let sub_id = SubscriptionId::generate(); + async fn get_signer_public_key(&self) -> Result { + let public_key = self.app_keys.public_key(); + let secret_key = self.app_keys.secret_key()?; + + let id = SubscriptionId::generate(); let filter = Filter::new() .pubkey(public_key) .kind(Kind::NostrConnect) @@ -207,97 +275,38 @@ impl Nip46Signer { self.pool .send_msg_to( [self.relay_url()], - ClientMessage::req(sub_id.clone(), vec![filter]), + ClientMessage::req(id.clone(), vec![filter]), RelaySendOptions::new(), ) .await?; let mut notifications = self.pool.notifications(); - let future = async { + time::timeout(Some(self.timeout), async { while let Ok(notification) = notifications.recv().await { if let RelayPoolNotification::Event { event, .. } = notification { if event.kind() == Kind::NostrConnect { - let msg = nip04::decrypt(secret_key, event.author_ref(), event.content())?; + let msg: String = + nip04::decrypt(secret_key, event.author_ref(), event.content())?; let msg = Message::from_json(msg)?; - - if let Message::Response { id, result, error } = &msg { - if &req_id == id { - if let Some(result) = result { - let res = match req { - Request::Describe => Response::Describe( - serde_json::from_value(result.to_owned())?, - ), - Request::GetPublicKey => { - let pubkey = serde_json::from_value(result.to_owned())?; - Response::GetPublicKey(pubkey) - } - Request::SignEvent(_) => { - let sig = serde_json::from_value(result.to_owned())?; - Response::SignEvent(sig) - } - Request::Delegate { .. } => Response::Delegate( - serde_json::from_value(result.to_owned())?, - ), - Request::Nip04Encrypt { .. } => Response::Nip04Encrypt( - serde_json::from_value(result.to_owned())?, - ), - Request::Nip04Decrypt { .. } => Response::Nip04Decrypt( - serde_json::from_value(result.to_owned())?, - ), - Request::SignSchnorr { .. } => Response::SignSchnorr( - serde_json::from_value(result.to_owned())?, - ), - _ => break, - }; - - // Unsubscribe - self.pool - .send_msg_to( - [self.relay_url()], - ClientMessage::close(sub_id.clone()), - RelaySendOptions::new(), - ) - .await?; - return Ok(res); - } - - if let Some(error) = error { - // Unsubscribe - self.pool - .send_msg_to( - [self.relay_url()], - ClientMessage::close(sub_id.clone()), - RelaySendOptions::new(), - ) - .await?; - return Err(Error::Response(error.to_owned())); - } - - break; - } + if let Ok(Request::Connect { public_key, .. }) = msg.to_request() { + // Unsubscribe + self.pool + .send_msg_to( + [self.relay_url()], + ClientMessage::close(id), + RelaySendOptions::new(), + ) + .await?; + return Ok(public_key); } } } } - Err(Error::Generic) - }; - - let res: Result = - time::timeout(Some(timeout.unwrap_or(self.timeout)), future) - .await - .ok_or(Error::Timeout)?; - - // Unsubscribe - self.pool - .send_msg_to( - [self.relay_url()], - ClientMessage::close(sub_id), - RelaySendOptions::new(), - ) - .await?; - - res + Err(Error::SignerPublicKeyNotFound) + }) + .await + .ok_or(Error::Timeout)? } /// Completely shutdown diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml index 564a04dbd..6b5cebb18 100644 --- a/crates/nostr/Cargo.toml +++ b/crates/nostr/Cargo.toml @@ -54,7 +54,7 @@ nip06 = ["dep:bip39"] nip07 = ["dep:js-sys", "dep:wasm-bindgen", "dep:wasm-bindgen-futures", "dep:web-sys"] nip11 = ["dep:reqwest"] nip44 = ["dep:base64", "dep:chacha20"] -nip46 = ["nip04"] +nip46 = ["nip04", "nip44"] nip47 = ["nip04"] nip49 = ["dep:chacha20poly1305", "dep:scrypt", "dep:unicode-normalization"] nip57 = ["dep:aes", "dep:cbc"] diff --git a/crates/nostr/src/nips/nip44/v2.rs b/crates/nostr/src/nips/nip44/v2.rs index 9ed7602e3..1ba43a370 100644 --- a/crates/nostr/src/nips/nip44/v2.rs +++ b/crates/nostr/src/nips/nip44/v2.rs @@ -116,7 +116,7 @@ impl MessageKeys { } /// NIP44 v2 Conversation Key -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct ConversationKey(Hmac); impl fmt::Debug for ConversationKey { diff --git a/crates/nostr/src/nips/nip46.rs b/crates/nostr/src/nips/nip46.rs index 131ae56f8..4606d4003 100644 --- a/crates/nostr/src/nips/nip46.rs +++ b/crates/nostr/src/nips/nip46.rs @@ -12,25 +12,21 @@ use alloc::vec::Vec; use core::fmt; use core::str::FromStr; -use bitcoin::hashes::sha256::Hash as Sha256Hash; -use bitcoin::hashes::Hash; #[cfg(feature = "std")] use bitcoin::secp256k1::rand; -use bitcoin::secp256k1::rand::{CryptoRng, Rng, RngCore}; -use bitcoin::secp256k1::schnorr::Signature; -use bitcoin::secp256k1::{self, Message as Secp256k1Message, Secp256k1, Signing}; -use serde::{Deserialize, Serialize}; +use bitcoin::secp256k1::rand::RngCore; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{json, Value}; -use super::nip04; -use super::nip26::{self, sign_delegation_with_ctx, Conditions}; -use crate::event::unsigned::{self, UnsignedEvent}; -use crate::key::{self, Keys}; +use crate::event::unsigned::UnsignedEvent; use crate::types::url::form_urlencoded::byte_serialize; use crate::types::url::{ParseError, Url}; -#[cfg(feature = "std")] -use crate::SECP256K1; -use crate::{Event, JsonUtil, PublicKey}; +use crate::{key, Event, JsonUtil, PublicKey, SecretKey}; + +/// NIP46 URI Scheme +pub const NOSTR_CONNECT_URI_SCHEME: &str = "nostrconnect"; +/// NIP46 bunker URI Scheme +pub const NOSTR_CONNECT_BUNKER_URI_SCHEME: &str = "bunker"; /// NIP46 error #[derive(Debug)] @@ -41,14 +37,6 @@ pub enum Error { Json(serde_json::Error), /// Url parse error Url(ParseError), - /// Secp256k1 error - Secp256k1(secp256k1::Error), - /// NIP04 error - NIP04(nip04::Error), - /// NIP26 error - NIP26(nip26::Error), - /// Unsigned event error - UnsignedEvent(unsigned::Error), /// Invalid request InvalidRequest, /// Too many/few params @@ -59,6 +47,10 @@ pub enum Error { InvalidURI, /// Invalid URI scheme InvalidURIScheme, + /// Not request + NotRequest, + /// Unexpected result + UnexpectedResult, } #[cfg(feature = "std")] @@ -70,15 +62,13 @@ impl fmt::Display for Error { Self::Key(e) => write!(f, "Key: {e}"), Self::Json(e) => write!(f, "Json: {e}"), Self::Url(e) => write!(f, "Url: {e}"), - Self::Secp256k1(e) => write!(f, "Secp256k1: {e}"), - Self::NIP04(e) => write!(f, "NIP04: {e}"), - Self::NIP26(e) => write!(f, "NIP26: {e}"), - Self::UnsignedEvent(e) => write!(f, "{e}"), Self::InvalidRequest => write!(f, "Invalid request"), Self::InvalidParamsLength => write!(f, "Too many/few params"), Self::UnsupportedMethod(name) => write!(f, "Unsupported method: {name}"), Self::InvalidURI => write!(f, "Invalid uri"), Self::InvalidURIScheme => write!(f, "Invalid uri scheme"), + Self::NotRequest => write!(f, "This message is not a request"), + Self::UnexpectedResult => write!(f, "Unexpected result"), } } } @@ -101,50 +91,99 @@ impl From for Error { } } -impl From for Error { - fn from(e: secp256k1::Error) -> Self { - Self::Secp256k1(e) +/// NIP46 method +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Method { + /// Connect + Connect, + /// Get public key + GetPublicKey, + /// Sign event + SignEvent, + /// Get relays + GetRelays, + /// Encrypt text (NIP04) + Nip04Encrypt, + /// Decrypt (NIP04) + Nip04Decrypt, + /// Encrypt text (NIP44) + Nip44Encrypt, + /// Decrypt (NIP44) + Nip44Decrypt, + /// Ping + Ping, +} + +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Connect => write!(f, "connect"), + Self::GetPublicKey => write!(f, "get_public_key"), + Self::SignEvent => write!(f, "sign_event"), + Self::GetRelays => write!(f, "get_relays"), + Self::Nip04Encrypt => write!(f, "nip04_encrypt"), + Self::Nip04Decrypt => write!(f, "nip04_decrypt"), + Self::Nip44Encrypt => write!(f, "nip44_encrypt"), + Self::Nip44Decrypt => write!(f, "nip44_decrypt"), + Self::Ping => write!(f, "ping"), + } } } -impl From for Error { - fn from(e: nip04::Error) -> Self { - Self::NIP04(e) +impl FromStr for Method { + type Err = Error; + + fn from_str(method: &str) -> Result { + match method { + "connect" => Ok(Self::Connect), + "get_public_key" => Ok(Self::GetPublicKey), + "sign_event" => Ok(Self::SignEvent), + "get_relays" => Ok(Self::GetRelays), + "nip04_encrypt" => Ok(Self::Nip04Encrypt), + "nip04_decrypt" => Ok(Self::Nip04Decrypt), + "nip44_encrypt" => Ok(Self::Nip44Encrypt), + "nip44_decrypt" => Ok(Self::Nip44Decrypt), + "ping" => Ok(Self::Ping), + other => Err(Error::UnsupportedMethod(other.to_string())), + } } } -impl From for Error { - fn from(e: nip26::Error) -> Self { - Self::NIP26(e) +impl Serialize for Method { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) } } -impl From for Error { - fn from(e: unsigned::Error) -> Self { - Self::UnsignedEvent(e) +impl<'de> Deserialize<'de> for Method { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let method: String = String::deserialize(deserializer)?; + Self::from_str(&method).map_err(serde::de::Error::custom) } } /// Request -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Request { - /// Describe - Describe, + /// Connect + Connect { + /// Remote public key + public_key: PublicKey, + /// Optional secret key + secret_key: Option, + }, /// Get public key GetPublicKey, /// Sign [`UnsignedEvent`] SignEvent(UnsignedEvent), - /// Connect - Connect(PublicKey), - /// Disconnect - Disconnect, - /// Delegate - Delegate { - /// Pubkey - public_key: PublicKey, - /// NIP26 conditions - conditions: Conditions, - }, + /// Get relays + GetRelays, /// Encrypt text (NIP04) Nip04Encrypt { /// Pubkey @@ -157,168 +196,233 @@ pub enum Request { /// Pubkey public_key: PublicKey, /// Ciphertext + ciphertext: String, + }, + /// Encrypt text (NIP44) + Nip44Encrypt { + /// Pubkey + public_key: PublicKey, + /// Plain text text: String, }, - /// Sign Schnorr - SignSchnorr(String), + /// Decrypt (NIP44) + Nip44Decrypt { + /// Pubkey + public_key: PublicKey, + /// Ciphertext + ciphertext: String, + }, + /// Ping + Ping, } impl Request { + fn from_message(method: Method, params: Vec) -> Result { + match method { + Method::Connect => { + let public_key: Value = params.first().cloned().ok_or(Error::InvalidRequest)?; + let public_key: PublicKey = serde_json::from_value(public_key)?; + let secret_key: Option = params + .get(1) + .cloned() + .and_then(|s| serde_json::from_value(s).ok()); + Ok(Self::Connect { + public_key, + secret_key, + }) + } + Method::GetPublicKey => Ok(Self::GetPublicKey), + Method::SignEvent => { + let unsigned: Value = params.first().cloned().ok_or(Error::InvalidRequest)?; + let unsigned_event: UnsignedEvent = serde_json::from_value(unsigned)?; + Ok(Self::SignEvent(unsigned_event)) + } + Method::GetRelays => Ok(Self::GetRelays), + Method::Nip04Encrypt => { + if params.len() != 2 { + return Err(Error::InvalidParamsLength); + } + + Ok(Self::Nip04Encrypt { + public_key: serde_json::from_value(params[0].clone())?, + text: serde_json::from_value(params[1].clone())?, + }) + } + Method::Nip04Decrypt => { + if params.len() != 2 { + return Err(Error::InvalidParamsLength); + } + + Ok(Self::Nip04Decrypt { + public_key: serde_json::from_value(params[0].clone())?, + ciphertext: serde_json::from_value(params[1].clone())?, + }) + } + Method::Nip44Encrypt => { + if params.len() != 2 { + return Err(Error::InvalidParamsLength); + } + + Ok(Self::Nip44Encrypt { + public_key: serde_json::from_value(params[0].clone())?, + text: serde_json::from_value(params[1].clone())?, + }) + } + Method::Nip44Decrypt => { + if params.len() != 2 { + return Err(Error::InvalidParamsLength); + } + + Ok(Self::Nip44Decrypt { + public_key: serde_json::from_value(params[0].clone())?, + ciphertext: serde_json::from_value(params[1].clone())?, + }) + } + Method::Ping => Ok(Self::Ping), + } + } + /// Get req method - pub fn method(&self) -> String { + pub fn method(&self) -> Method { match self { - Self::Describe => "describe".to_string(), - Self::GetPublicKey => "get_public_key".to_string(), - Self::SignEvent(_) => "sign_event".to_string(), - Self::Connect(_) => "connect".to_string(), - Self::Disconnect => "disconnect".to_string(), - Self::Delegate { .. } => "delegate".to_string(), - Self::Nip04Encrypt { .. } => "nip04_encrypt".to_string(), - Self::Nip04Decrypt { .. } => "nip04_decrypt".to_string(), - Self::SignSchnorr(_) => "sign_schnorr".to_string(), + Self::Connect { .. } => Method::Connect, + Self::GetPublicKey => Method::GetPublicKey, + Self::SignEvent(_) => Method::SignEvent, + Self::GetRelays => Method::GetRelays, + Self::Nip04Encrypt { .. } => Method::Nip04Encrypt, + Self::Nip04Decrypt { .. } => Method::Nip04Decrypt, + Self::Nip44Encrypt { .. } => Method::Nip44Encrypt, + Self::Nip44Decrypt { .. } => Method::Nip44Decrypt, + Self::Ping => Method::Ping, } } /// Get req params pub fn params(&self) -> Vec { match self { - Self::Describe => Vec::new(), - Self::GetPublicKey => Vec::new(), - Self::SignEvent(event) => vec![json!(event)], - Self::Connect(pubkey) => vec![json!(pubkey)], - Self::Disconnect => Vec::new(), - Self::Delegate { + Self::Connect { public_key, - conditions, - } => vec![json!(public_key), json!(conditions)], - Self::Nip04Encrypt { public_key, text } => vec![json!(public_key), json!(text)], - Self::Nip04Decrypt { public_key, text } => vec![json!(public_key), json!(text)], - Self::SignSchnorr(value) => vec![json!(value)], - } - } - - /// Generate [`Response`] message for [`Request`] - #[cfg(feature = "std")] - pub fn generate_response(self, keys: &Keys) -> Result, Error> { - self.generate_response_with_ctx(&SECP256K1, &mut rand::thread_rng(), keys) - } - - /// Generate [`Response`] message for [`Request`] - pub fn generate_response_with_ctx( - self, - secp: &Secp256k1, - rng: &mut R, - keys: &Keys, - ) -> Result, Error> - where - C: Signing, - R: Rng + CryptoRng, - { - let res: Option = match self { - Self::Describe => Some(Response::Describe(vec![ - String::from("describe"), - String::from("get_public_key"), - String::from("sign_event"), - String::from("connect"), - String::from("disconnect"), - String::from("delegate"), - String::from("nip04_encrypt"), - String::from("nip04_decrypt"), - String::from("sign_schnorr"), - ])), - Self::GetPublicKey => Some(Response::GetPublicKey(keys.public_key())), - Self::SignEvent(unsigned_event) => { - let signed_event = unsigned_event.sign_with_ctx(secp, rng, keys)?; - Some(Response::SignEvent(signed_event)) - } - Self::Connect(_) => None, - Self::Disconnect => None, - Self::Delegate { - public_key, - conditions, + secret_key, } => { - let sig = - sign_delegation_with_ctx(secp, rng, keys, public_key, conditions.clone())?; - let delegation_result = DelegationResult { - from: keys.public_key(), - to: public_key, - cond: conditions, - sig, - }; - - Some(Response::Delegate(delegation_result)) - } - Self::Nip04Encrypt { public_key, text } => { - let encrypted_content = - nip04::encrypt_with_rng(rng, keys.secret_key()?, &public_key, text)?; - Some(Response::Nip04Encrypt(encrypted_content)) + let mut params = vec![json!(public_key)]; + if let Some(secret_key) = secret_key { + params.push(json!(secret_key.to_secret_hex())); + } + params } - Self::Nip04Decrypt { public_key, text } => { - let decrypted_content = nip04::decrypt(keys.secret_key()?, &public_key, text)?; - Some(Response::Nip04Decrypt(decrypted_content)) + Self::GetPublicKey => Vec::new(), + Self::SignEvent(event) => vec![json!(event)], + Self::GetRelays => Vec::new(), + Self::Nip04Encrypt { public_key, text } | Self::Nip44Encrypt { public_key, text } => { + vec![json!(public_key), json!(text)] } - Self::SignSchnorr(value) => { - let hash = Sha256Hash::hash(value.as_bytes()); - let message = Secp256k1Message::from(hash); - let sig: Signature = keys.sign_schnorr_with_ctx(secp, &message, rng)?; - Some(Response::SignSchnorr(sig)) + Self::Nip04Decrypt { + public_key, + ciphertext, } - }; - Ok(res) + | Self::Nip44Decrypt { + public_key, + ciphertext, + } => vec![json!(public_key), json!(ciphertext)], + Self::Ping => Vec::new(), + } } } -/// Delegation Response Result -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct DelegationResult { - /// Pubkey of Delegator - pub from: PublicKey, - /// Pubkey of Delegatee - pub to: PublicKey, - /// Conditions of delegation - pub cond: Conditions, - /// Signature of Delegation Token - pub sig: Signature, -} - /// Response #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub enum Response { - /// Describe - Describe(Vec), +#[serde(untagged)] +pub enum ResponseResult { + /// Connect ACK + #[serde(rename = "ack")] + Connect, /// Get public key GetPublicKey(PublicKey), /// Sign event SignEvent(Event), - /// Delegation - Delegate(DelegationResult), - /// Encrypted content (NIP04) - Nip04Encrypt(String), - /// Decrypted content (NIP04) - Nip04Decrypt(String), - /// Sign Schnorr - SignSchnorr(Signature), + /// Get relays + GetRelays(Vec), + /// NIP04/NIP44 encryption/decryption + EncryptionDecryption(String), + /// Pong + #[serde(rename = "pong")] + Pong, } -/// Message -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[allow(missing_docs)] +impl ResponseResult { + pub fn to_connect(self) -> Result<(), Error> { + if let Self::Connect = self { + Ok(()) + } else { + Err(Error::UnexpectedResult) + } + } + + pub fn to_get_public_key(self) -> Result { + if let Self::GetPublicKey(val) = self { + Ok(val) + } else { + Err(Error::UnexpectedResult) + } + } + + pub fn to_sign_event(self) -> Result { + if let Self::SignEvent(val) = self { + Ok(val) + } else { + Err(Error::UnexpectedResult) + } + } + + pub fn to_pong(self) -> Result<(), Error> { + if let Self::Pong = self { + Ok(()) + } else { + Err(Error::UnexpectedResult) + } + } + + pub fn to_encrypt_decrypt(self) -> Result { + if let Self::EncryptionDecryption(val) = self { + Ok(val) + } else { + Err(Error::UnexpectedResult) + } + } +} + +#[derive(Serialize, Deserialize)] #[serde(untagged)] +enum MessageIntermediate { + Request { + id: String, + method: Method, + params: Vec, + }, + Response { + id: String, + result: Option, + error: Option, + }, +} + +/// Message +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Message { /// Request Request { /// Request id id: String, - /// Method - method: String, - /// params - params: Vec, + /// Request + req: Request, }, /// Response Response { /// Request id id: String, /// Result - result: Option, + result: Option, /// Reason, if failed error: Option, }, @@ -338,27 +442,18 @@ impl Message { { Self::Request { id: rng.next_u32().to_string(), - method: req.method(), - params: req.params(), + req, } } /// Compose `Response` message - pub fn response(req_id: S, res: Option, error: Option) -> Self + pub fn response(req_id: S, result: Option, error: Option) -> Self where S: Into, { Self::Response { id: req_id.into(), - result: res.map(|res| match res { - Response::Describe(v) => json!(v), - Response::GetPublicKey(pubkey) => json!(pubkey), - Response::SignEvent(sig) => json!(sig), - Response::Delegate(delegation_result) => json!(delegation_result), - Response::Nip04Encrypt(encrypted_content) => json!(encrypted_content), - Response::Nip04Decrypt(decrypted_content) => json!(decrypted_content), - Response::SignSchnorr(sig) => json!(sig), - }), + result, error: error.map(|e| e.into()), } } @@ -371,84 +466,30 @@ impl Message { } } - /// Get [`Message`] id - pub fn id(&self) -> String { + /* pub fn as_request(&self) -> Result<&Request, Error> { match self { - Self::Request { id, .. } => id.to_owned(), - Self::Response { id, .. } => id.to_owned(), + Self::Request { req, .. } => Ok(req), + _ => Err(Error::NotRequest) } - } - - /// Convert [`Message`] to [`Request`] - pub fn to_request(&self) -> Result { - if let Message::Request { method, params, .. } = self { - match method.as_str() { - "describe" => Ok(Request::Describe), - "get_public_key" => Ok(Request::GetPublicKey), - "sign_event" => { - if let Some(value) = params.first() { - let unsigned_event: UnsignedEvent = - serde_json::from_value(value.to_owned())?; - Ok(Request::SignEvent(unsigned_event)) - } else { - Err(Error::InvalidRequest) - } - } - "connect" => { - if params.len() != 1 { - return Err(Error::InvalidParamsLength); - } + } */ - let pubkey: PublicKey = serde_json::from_value(params[0].to_owned())?; - Ok(Request::Connect(pubkey)) - } - "disconnect" => Ok(Request::Disconnect), - "delegate" => { - if params.len() != 2 { - return Err(Error::InvalidParamsLength); - } - - Ok(Request::Delegate { - public_key: serde_json::from_value(params[0].clone())?, - conditions: serde_json::from_value(params[1].clone())?, - }) - } - "nip04_encrypt" => { - if params.len() != 2 { - return Err(Error::InvalidParamsLength); - } - - Ok(Request::Nip04Encrypt { - public_key: serde_json::from_value(params[0].clone())?, - text: serde_json::from_value(params[1].clone())?, - }) - } - "nip04_decrypt" => { - if params.len() != 2 { - return Err(Error::InvalidParamsLength); - } - - Ok(Request::Nip04Decrypt { - public_key: serde_json::from_value(params[0].clone())?, - text: serde_json::from_value(params[1].clone())?, - }) - } - "sign_schnorr" => { - if params.len() != 1 { - return Err(Error::InvalidParamsLength); - } + /// Consume [Message] and return [Request] + pub fn to_request(self) -> Result { + match self { + Self::Request { req, .. } => Ok(req), + _ => Err(Error::NotRequest), + } + } - let value: String = serde_json::from_value(params[0].clone())?; - Ok(Request::SignSchnorr(value)) - } - other => Err(Error::UnsupportedMethod(other.to_string())), - } - } else { - Err(Error::InvalidRequest) + /// Get [`Message`] id + pub fn id(&self) -> &str { + match self { + Self::Request { id, .. } => id, + Self::Response { id, .. } => id, } } - /// Generate [`Response`] message for [`Request`] + /* /// Generate [`Response`] message for [`Request`] #[cfg(feature = "std")] pub fn generate_response(&self, keys: &Keys) -> Result, Error> { self.generate_response_wit_ctx(&SECP256K1, &mut rand::thread_rng(), keys) @@ -465,23 +506,72 @@ impl Message { C: Signing, R: Rng + CryptoRng, { - let req = self.to_request()?; + let req = self.as_request()?; // TODO: remove if let Some(res) = ... if let Some(res) = req.generate_response_with_ctx(secp, rng, keys)? { Ok(Some(Self::response(self.id(), Some(res), None))) } else { Ok(None) } - } + } */ /// Generate error [`Response`] message for [`Request`] pub fn generate_error_response(&self, error: S) -> Result where - S: Into, + S: AsRef, { // Check if Message is a Request - let _req: Request = self.to_request()?; - Ok(Self::response(self.id(), None, Some(error.into()))) + if self.is_request() { + let error: &str = error.as_ref(); + Ok(Self::response(self.id(), None, Some(error))) + } else { + Err(Error::NotRequest) + } + } +} + +impl Serialize for Message { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let intermediate: MessageIntermediate = match self { + Self::Request { id, req } => MessageIntermediate::Request { + id: id.to_owned(), + method: req.method(), + params: req.params(), + }, + Self::Response { id, result, error } => MessageIntermediate::Response { + id: id.to_owned(), + result: result.clone().map(|res| json!(res)), + error: error.clone(), + }, + }; + intermediate.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Message { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let intermediate: MessageIntermediate = MessageIntermediate::deserialize(deserializer)?; + match intermediate { + MessageIntermediate::Request { id, method, params } => Ok(Self::Request { + id, + req: Request::from_message(method, params).map_err(serde::de::Error::custom)?, + }), + MessageIntermediate::Response { id, result, error } => { + let result: Option = match result { + Some(res) => { + Some(serde_json::from_value(res).map_err(serde::de::Error::custom)?) + } + None => None, + }; + Ok(Self::Response { id, result, error }) + } + } } } @@ -496,9 +586,6 @@ where byte_serialize(data.as_ref()).collect() } -/// NIP46 URI Scheme -pub const NOSTR_CONNECT_URI_SCHEME: &str = "nostrconnect"; - /// Nostr Connect Metadata #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct NostrConnectMetadata {