notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 9a9bb13e086f7db394ad2a2da8fdf579ba2c48cd
parent 6c2c352e6f8ba99561f4f1bb07a46ce7a877d6a6
Author: William Casarin <jb55@jb55.com>
Date:   Wed,  7 Feb 2024 12:02:13 -0800

enostr: use data instead of strings in Pubkey and EventId

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Menostr/src/error.rs | 40++++++++++++++++++++++++++++++++++++----
Menostr/src/event.rs | 62+++++++++++++++++++++++++++++++++++++++-----------------------
Menostr/src/pubkey.rs | 61+++++++++++++++++++++++++++++++++++++++++++++++--------------
Menostr/src/relay/message.rs | 57++++++++++++++++++++++++++++++++-------------------------
Menostr/src/relay/pool.rs | 6+++++-
5 files changed, 159 insertions(+), 67 deletions(-)

diff --git a/enostr/src/error.rs b/enostr/src/error.rs @@ -1,10 +1,14 @@ +use nostr::prelude::secp256k1; use serde_json; +use std::fmt; #[derive(Debug)] pub enum Error { - MessageEmpty, - MessageDecodeFailed, + Empty, + DecodeFailed, + HexDecodeFailed, InvalidSignature, + Secp(secp256k1::Error), Json(serde_json::Error), Generic(String), } @@ -12,17 +16,33 @@ pub enum Error { impl std::cmp::PartialEq for Error { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Error::MessageEmpty, Error::MessageEmpty) => true, - (Error::MessageDecodeFailed, Error::MessageDecodeFailed) => true, + (Error::Empty, Error::Empty) => true, + (Error::DecodeFailed, Error::DecodeFailed) => true, + (Error::HexDecodeFailed, Error::HexDecodeFailed) => true, (Error::InvalidSignature, Error::InvalidSignature) => true, // This is slightly wrong but whatevs (Error::Json(..), Error::Json(..)) => true, (Error::Generic(left), Error::Generic(right)) => left == right, + (Error::Secp(left), Error::Secp(right)) => left == right, _ => false, } } } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Empty => write!(f, "message is empty"), + Self::DecodeFailed => write!(f, "decoding failed"), + Self::InvalidSignature => write!(f, "invalid signature"), + Self::HexDecodeFailed => write!(f, "hex decoding failed"), + Self::Secp(e) => write!(f, "{e}"), + Self::Json(e) => write!(f, "{e}"), + Self::Generic(e) => write!(f, "{e}"), + } + } +} + impl std::cmp::Eq for Error {} impl From<String> for Error { @@ -31,6 +51,18 @@ impl From<String> for Error { } } +impl From<hex::FromHexError> for Error { + fn from(_e: hex::FromHexError) -> Self { + Error::HexDecodeFailed + } +} + +impl From<secp256k1::Error> for Error { + fn from(e: secp256k1::Error) -> Self { + Error::Secp(e) + } +} + impl From<serde_json::Error> for Error { fn from(e: serde_json::Error) -> Self { Error::Json(e) diff --git a/enostr/src/event.rs b/enostr/src/event.rs @@ -1,5 +1,6 @@ -use crate::{Error, Pubkey, Result}; -use serde_derive::{Deserialize, Serialize}; +use crate::{Error, Pubkey}; +use log::debug; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::hash::{Hash, Hasher}; /// Event is the struct used to represent a Nostr event @@ -8,7 +9,6 @@ pub struct Event { /// 32-bytes sha256 of the the serialized event data pub id: EventId, /// 32-bytes hex-encoded public key of the event creator - #[serde(rename = "pubkey")] pub pubkey: Pubkey, /// unix timestamp in seconds pub created_at: u64, @@ -39,11 +39,11 @@ impl PartialEq for Event { impl Eq for Event {} impl Event { - pub fn from_json(s: &str) -> Result<Self> { + pub fn from_json(s: &str) -> Result<Self, Error> { serde_json::from_str(s).map_err(Into::into) } - pub fn verify(&self) -> Result<Self> { + pub fn verify(&self) -> Result<Self, Error> { return Err(Error::InvalidSignature); } @@ -57,46 +57,62 @@ impl Event { tags: Vec<Vec<String>>, content: &str, sig: &str, - ) -> Result<Self> { - let event = Event { - id: id.to_string().into(), - pubkey: pubkey.to_string().into(), + ) -> Result<Self, Error> { + Ok(Event { + id: EventId::from_hex(id)?, + pubkey: Pubkey::from_hex(pubkey)?, created_at, kind, tags, content: content.to_string(), sig: sig.to_string(), - }; - - event.verify() + }) } } impl std::str::FromStr for Event { type Err = Error; - fn from_str(s: &str) -> Result<Self> { + fn from_str(s: &str) -> Result<Self, Error> { Event::from_json(s) } } -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Hash)] -pub struct EventId(String); +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub struct EventId([u8; 32]); + +impl EventId { + pub fn hex(&self) -> String { + hex::encode(self.bytes()) + } -impl AsRef<str> for EventId { - fn as_ref(&self) -> &str { + pub fn bytes(&self) -> &[u8; 32] { &self.0 } + + pub fn from_hex(hex_str: &str) -> Result<Self, Error> { + let evid = EventId(hex::decode(hex_str)?.as_slice().try_into().unwrap()); + Ok(evid) + } } -impl From<String> for EventId { - fn from(s: String) -> Self { - EventId(s) +// Custom serialize function for Pubkey +impl Serialize for EventId { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_str(&self.hex()) } } -impl From<EventId> for String { - fn from(evid: EventId) -> Self { - evid.0 +// Custom deserialize function for Pubkey +impl<'de> Deserialize<'de> for EventId { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + EventId::from_hex(&s).map_err(serde::de::Error::custom) } } diff --git a/enostr/src/pubkey.rs b/enostr/src/pubkey.rs @@ -1,28 +1,61 @@ -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Hash)] -pub struct Pubkey(String); +use crate::Error; +use hex; +use log::debug; +use nostr::key::XOnlyPublicKey; +use std::fmt; -impl AsRef<str> for Pubkey { - fn as_ref(&self) -> &str { - &self.0 +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub struct Pubkey(XOnlyPublicKey); + +impl Pubkey { + pub fn hex(&self) -> String { + hex::encode(self.bytes()) } -} -impl From<String> for Pubkey { - fn from(s: String) -> Self { - Pubkey(s) + pub fn bytes(&self) -> [u8; 32] { + self.0.serialize() + } + + pub fn from_hex(hex_str: &str) -> Result<Self, Error> { + Ok(Pubkey(XOnlyPublicKey::from_slice( + hex::decode(hex_str)?.as_slice(), + )?)) } } -impl From<&str> for Pubkey { - fn from(s: &str) -> Self { - Pubkey(s.to_owned()) +impl fmt::Display for Pubkey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.hex()) } } impl From<Pubkey> for String { fn from(pk: Pubkey) -> Self { - pk.0 + pk.hex() + } +} + +// Custom serialize function for Pubkey +impl Serialize for Pubkey { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_str(&self.hex()) + } +} + +// Custom deserialize function for Pubkey +impl<'de> Deserialize<'de> for Pubkey { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + debug!("decoding pubkey start"); + let s = String::deserialize(deserializer)?; + debug!("decoding pubkey {}", &s); + Pubkey::from_hex(&s).map_err(serde::de::Error::custom) } } diff --git a/enostr/src/relay/message.rs b/enostr/src/relay/message.rs @@ -1,6 +1,7 @@ use crate::Error; use crate::Event; use crate::Result; +use log::debug; use serde_json::Value; use ewebsock::{WsEvent, WsMessage}; @@ -76,19 +77,19 @@ impl RelayMessage { // I was lazy and took this from the nostr crate. thx yuki! pub fn from_json(msg: &str) -> Result<Self> { if msg.is_empty() { - return Err(Error::MessageEmpty); + return Err(Error::Empty); } - let v: Vec<Value> = serde_json::from_str(msg).map_err(|_| Error::MessageDecodeFailed)?; + let v: Vec<Value> = serde_json::from_str(msg).map_err(|_| Error::DecodeFailed)?; // Notice // Relay response format: ["NOTICE", <message>] if v[0] == "NOTICE" { if v.len() != 2 { - return Err(Error::MessageDecodeFailed); + return Err(Error::DecodeFailed); } let v_notice: String = - serde_json::from_value(v[1].clone()).map_err(|_| Error::MessageDecodeFailed)?; + serde_json::from_value(v[1].clone()).map_err(|_| Error::DecodeFailed)?; return Ok(Self::notice(v_notice)); } @@ -96,14 +97,13 @@ impl RelayMessage { // Relay response format: ["EVENT", <subscription id>, <event JSON>] if v[0] == "EVENT" { if v.len() != 3 { - return Err(Error::MessageDecodeFailed); + return Err(Error::DecodeFailed); } - let event = - Event::from_json(&v[2].to_string()).map_err(|_| Error::MessageDecodeFailed)?; + let event = Event::from_json(&v[2].to_string()).map_err(|_| Error::DecodeFailed)?; let subscription_id: String = - serde_json::from_value(v[1].clone()).map_err(|_| Error::MessageDecodeFailed)?; + serde_json::from_value(v[1].clone()).map_err(|_| Error::DecodeFailed)?; return Ok(Self::event(event, subscription_id)); } @@ -112,11 +112,11 @@ impl RelayMessage { // Relay response format: ["EOSE", <subscription_id>] if v[0] == "EOSE" { if v.len() != 2 { - return Err(Error::MessageDecodeFailed); + return Err(Error::DecodeFailed); } let subscription_id: String = - serde_json::from_value(v[1].clone()).map_err(|_| Error::MessageDecodeFailed)?; + serde_json::from_value(v[1].clone()).map_err(|_| Error::DecodeFailed)?; return Ok(Self::eose(subscription_id)); } @@ -125,22 +125,22 @@ impl RelayMessage { // Relay response format: ["OK", <event_id>, <true|false>, <message>] if v[0] == "OK" { if v.len() != 4 { - return Err(Error::MessageDecodeFailed); + return Err(Error::DecodeFailed); } let event_id: String = - serde_json::from_value(v[1].clone()).map_err(|_| Error::MessageDecodeFailed)?; + serde_json::from_value(v[1].clone()).map_err(|_| Error::DecodeFailed)?; let status: bool = - serde_json::from_value(v[2].clone()).map_err(|_| Error::MessageDecodeFailed)?; + serde_json::from_value(v[2].clone()).map_err(|_| Error::DecodeFailed)?; let message: String = - serde_json::from_value(v[3].clone()).map_err(|_| Error::MessageDecodeFailed)?; + serde_json::from_value(v[3].clone()).map_err(|_| Error::DecodeFailed)?; return Ok(Self::ok(event_id, status, message)); } - Err(Error::MessageDecodeFailed) + Err(Error::DecodeFailed) } } @@ -169,16 +169,19 @@ mod tests { assert_eq!( RelayMessage::from_json(invalid_notice_msg).unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); assert_eq!( RelayMessage::from_json(invalid_notice_msg_content).unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); } #[test] fn test_handle_valid_event() -> Result<()> { + use log::debug; + + env_logger::init(); let valid_event_msg = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]"#; let id = "70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5"; @@ -190,9 +193,13 @@ mod tests { let sig = "273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"; let handled_event = Event::new_dummy(id, pubkey, created_at, kind, tags, content, sig); + debug!("event {:?}", handled_event); + + let msg = RelayMessage::from_json(valid_event_msg); + debug!("msg {:?}", msg); assert_eq!( - RelayMessage::from_json(valid_event_msg)?, + msg?, RelayMessage::event(handled_event?, "random_string".to_string()) ); @@ -208,12 +215,12 @@ mod tests { assert_eq!( RelayMessage::from_json(invalid_event_msg).unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); assert_eq!( RelayMessage::from_json(invalid_event_msg_content).unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); } @@ -234,13 +241,13 @@ mod tests { // Missing subscription ID assert_eq!( RelayMessage::from_json(r#"["EOSE"]"#).unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); // The subscription ID is not string assert_eq!( RelayMessage::from_json(r#"["EOSE", 404]"#).unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); } @@ -265,19 +272,19 @@ mod tests { r#"["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"# ) .unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); // Invalid status assert_eq!( RelayMessage::from_json(r#"["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", hello, ""]"#).unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); // Invalid message assert_eq!( RelayMessage::from_json(r#"["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", hello, 404]"#).unwrap_err(), - Error::MessageDecodeFailed + Error::DecodeFailed ); } } diff --git a/enostr/src/relay/pool.rs b/enostr/src/relay/pool.rs @@ -141,7 +141,10 @@ impl RelayPool { Ok(()) } - /// Attempts to receive a pool event from a list of relays. The function searches each relay in the list in order, attempting to receive a message from each. If a message is received, return it. If no message is received from any relays, None is returned. + /// Attempts to receive a pool event from a list of relays. The + /// function searches each relay in the list in order, attempting to + /// receive a message from each. If a message is received, return it. + /// If no message is received from any relays, None is returned. pub fn try_recv(&mut self) -> Option<PoolEvent<'_>> { for relay in &mut self.relays { let relay = &mut relay.relay; @@ -149,6 +152,7 @@ impl RelayPool { match msg.try_into() { Ok(event) => { relay.status = RelayStatus::Connected; + // let's just handle pongs here. // We only need to do this natively. #[cfg(not(target_arch = "wasm32"))]