Kaynağa Gözat

Added raw_content

The `content` is parsed to be easily usable by rust if possible, but it
should not be possible to serialize it back, because if a single byte is
off, the signature will be invalid.
`
Cesar Rodas 2 yıl önce
ebeveyn
işleme
de92bbb788

+ 7 - 0
Cargo.lock

@@ -18,6 +18,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
+[[package]]
 name = "bech32"
 version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -252,6 +258,7 @@ version = "0.1.0"
 name = "nostr-protocol-types"
 version = "0.1.0"
 dependencies = [
+ "base64",
  "bech32",
  "chrono",
  "hex",

+ 1 - 0
crates/types/Cargo.toml

@@ -6,6 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+base64 = "0.21.0"
 bech32 = "0.9.1"
 chrono = "0.4.23"
 hex = "0.4"

+ 41 - 2
crates/types/src/lib.rs

@@ -14,10 +14,10 @@ pub enum Error {}
 
 #[derive(Serialize, Debug, Clone)]
 pub enum Message {
-    Request(client::Request),
+    Close(types::SubscriptionId),
     EventFromClient(types::Event),
     EventFromServer(types::SubscriptionId, types::Event),
-    Close(types::SubscriptionId),
+    Request(client::Request),
     Notice(String),
 }
 
@@ -160,6 +160,8 @@ impl<'de> de::Deserialize<'de> for Message {
 
 #[cfg(test)]
 mod test {
+    use crate::types::{content::EncryptedData, Content};
+
     use super::*;
 
     #[test]
@@ -183,6 +185,43 @@ mod test {
     }
 
     #[test]
+    fn follow_list() {
+        let json = r#"["EVENT","640e914a22321",{"content":"{\"wss:\\/\\/nos.lol\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.damus.io\":{\"write\":true,\"read\":true},\"wss:\\/\\/brb.io\":{\"write\":true,\"read\":true},\"wss:\\/\\/nostr.orangepill.dev\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.current.fyi\":{\"write\":true,\"read\":true},\"wss:\\/\\/eden.nostr.land\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.snort.social\":{\"write\":true,\"read\":true}}","created_at":1678476548,"id":"b8d7f6a19c3d9625b9aade947166708fbc6ab2dd7e3f3af84f1de08ea10d6f38","kind":3,"pubkey":"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78","sig":"352485a162805e72a1e278a4a7bc33facd54a71e0f4c23934f35eee57eaa38c62e6260bf7624e2bd96bc6bcf93373b06c5245e28a266bfe2fbf064224e713fd6","tags":[["p","3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"],["p","b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78"],["p","387519cafd325668ecffe59577f37238638da4cf2d985b82f932fc81d33da1e8"],["p","81d0ccce4591fc4e19e3ef752a2b003ef23a986cb31e7835ea7d8d7cd96d47ea"],["p","0861144c765ea10e39a48473a51bee604886e18abd0f831cc5ed7651e68a1caf"],["p","1779284c21126b5e1af6dcb84949ceacad781ce4ce0d1691292a41229465a54a"],["p","d7df5567015930b17c125b3a7cf29bef23aa5a68d09cd6518d291359606aab7b"],["p","82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["p","a47457722e10ba3a271fbe7040259a3c4da2cf53bfd1e198138214d235064fc2"],["p","e1055729d51e037b3c14e8c56e2c79c22183385d94aadb32e5dc88092cd0fef4"],["p","2bda4f03446bc1c6ff00594e350a1e7ec57fb9cdc4a7b52694223d56ce0599e9"],["p","c57717ec7a6af20b836a9468282948dc0adba64d30abfb40aa8c6664dde3cfc7"],["p","6094dd769f94fab1a2915c1a3d8360e49cec84977f433a872bea1763466db784"]]}]"#;
+        let message: Message = serde_json::from_str(json).expect("valid message");
+        assert!(message.as_event_from_server().is_some());
+
+        let (_, event) = message.as_event_from_server().expect("event");
+        assert_eq!(event.data.tags.len(), 14);
+        event
+            .data
+            .tags
+            .iter()
+            .map(|tag| {
+                tag.is_pubkey();
+            })
+            .for_each(drop);
+    }
+
+    #[test]
+    fn direct_message() {
+        let json = r#"["EVENT","640e9851d461a",{"content":"/9Xk4PfEF8hU+C1wq4grww==?iv=XB/ytLhL0WKQ1of4wXIXCg==","created_at":1677726088,"id":"4643c79276730e25a8510163622335bb9d44aecdd0c596409804abb29910652d","kind":4,"pubkey":"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78","sig":"23790fae080eaa87f2abf2df4e5fd49cecc67e58271910d7e871fb197641294cad92c2a6fff1501982d776648eecb1e4650ccfbb742a2d901226a9b00c310489","tags":[["p","b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78"]]}]"#;
+        let message: Message = serde_json::from_str(json).expect("valid message");
+        assert!(message.as_event_from_server().is_some());
+
+        let (_, event) = message.as_event_from_server().expect("event");
+        let content = Content::DirectMessage(EncryptedData {
+            encrypted_message: vec![
+                255, 213, 228, 224, 247, 196, 23, 200, 84, 248, 45, 112, 171, 136, 43, 195,
+            ],
+            iv: vec![
+                255, 213, 228, 224, 247, 196, 23, 200, 84, 248, 45, 112, 171, 136, 43, 195,
+            ],
+        });
+        assert_eq!(event.data.tags.len(), 1);
+        assert_eq!(event.data.content, content);
+    }
+
+    #[test]
     fn close() {
         let json = "[\"CLOSE\", \"foo\"]";
         let message: Message = serde_json::from_str(json).expect("valid message");

+ 1 - 1
crates/types/src/types/addr.rs

@@ -57,7 +57,7 @@ impl Addr {
     }
 
     pub fn try_from_bytes(typ: Type, bytes: Vec<u8>) -> Result<Self, Error> {
-        if bytes.len() < 32 {
+        if bytes.len() > 32 {
             return Err(Error::TooLong(bytes.len()));
         }
 

+ 48 - 1
crates/types/src/types/content/mod.rs

@@ -1,4 +1,9 @@
-use serde::ser::{self, Serializer};
+use base64::{engine::general_purpose, Engine as _};
+use serde::{
+    ser::{self, Serializer},
+    Deserialize, Serialize,
+};
+use std::collections::HashMap;
 use thiserror::Error;
 
 pub mod profile;
@@ -7,6 +12,21 @@ pub mod profile;
 pub enum Error {
     #[error("Issue at the serializer: {0}")]
     Json(#[from] serde_json::Error),
+
+    #[error("Base64: {0}")]
+    Base64(#[from] base64::DecodeError),
+}
+
+#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
+pub struct RelaySettings {
+    pub read: bool,
+    pub write: bool,
+}
+
+#[derive(Eq, PartialEq, Clone, Debug)]
+pub struct EncryptedData {
+    pub encrypted_message: Vec<u8>,
+    pub iv: Vec<u8>,
 }
 
 #[derive(Eq, PartialEq, Clone, Debug)]
@@ -14,6 +34,8 @@ pub enum Content {
     Profile(profile::Profile),
     Text(String),
     RecommendServer(String),
+    ContactList(HashMap<String, RelaySettings>),
+    DirectMessage(EncryptedData),
     Unknown(u32, String),
 }
 
@@ -36,6 +58,8 @@ impl Content {
             Self::Profile(_) => 0,
             Self::Text(_) => 1,
             Self::RecommendServer(_) => 2,
+            Self::ContactList(_) => 3,
+            Self::DirectMessage(_) => 4,
             Self::Unknown(k, _) => *k,
         }
     }
@@ -45,6 +69,21 @@ impl Content {
             0 => Ok(Self::Profile(serde_json::from_str(content)?)),
             1 => Ok(Self::Text(content.to_owned())),
             2 => Ok(Self::RecommendServer(content.to_owned())),
+            3 => Ok(Self::ContactList(serde_json::from_str(content)?)),
+            4 => {
+                let parts = content.split("?iv=").collect::<Vec<&str>>();
+                if parts.len() == 2 {
+                    let encrypted_message = general_purpose::STANDARD.decode(&parts[0])?;
+                    let iv = general_purpose::STANDARD.decode(&parts[0])?;
+
+                    Ok(Self::DirectMessage(EncryptedData {
+                        encrypted_message,
+                        iv,
+                    }))
+                } else {
+                    Ok(Self::Unknown(4, content.to_owned()))
+                }
+            }
             _ => Ok(Self::Unknown(kind, content.to_owned())),
         }
     }
@@ -52,6 +91,14 @@ impl Content {
     pub fn try_to_string(&self) -> Result<String, Error> {
         match self {
             Self::Profile(p) => Ok(serde_json::to_string(&p)?),
+            Self::ContactList(p) => Ok(serde_json::to_string(&p)?),
+            Self::DirectMessage(message) => {
+                let encrypted_message =
+                    general_purpose::STANDARD.encode(&message.encrypted_message);
+                let iv = general_purpose::STANDARD.encode(&message.iv);
+                let to_encode = format!("{}?iv={}", encrypted_message, iv);
+                Ok(serde_json::to_string(&to_encode)?)
+            }
             Self::Text(t) | Self::RecommendServer(t) | Self::Unknown(_, t) => Ok(t.to_owned()),
         }
     }

+ 7 - 1
crates/types/src/types/event.rs

@@ -35,7 +35,10 @@ pub struct Data {
     pub created_at: DateTime<Utc>,
     pub kind: u32,
     pub tags: Vec<super::Tag>,
+    #[serde(skip_serializing)]
     pub content: Content,
+    #[serde(rename = "content")]
+    pub raw_content: String,
 }
 
 impl<'de> de::Deserialize<'de> for Data {
@@ -61,6 +64,7 @@ impl<'de> de::Deserialize<'de> for Data {
             created_at: s.created_at,
             kind: s.kind,
             tags: s.tags,
+            raw_content: s.content,
             content,
         })
     }
@@ -78,6 +82,7 @@ impl Data {
             created_at: created_at.unwrap_or_else(Utc::now),
             tags,
             kind: content.kind(),
+            raw_content: content.try_to_string()?,
             content,
         })
     }
@@ -89,8 +94,9 @@ impl Data {
             json!(self.created_at.timestamp()),
             json!(self.content.kind()),
             json!(self.tags),
-            json!(self.content.try_to_string()?),
+            json!(self.raw_content),
         ]))?;
+        println!("{:?}", self.tags);
         let mut hasher = Sha256::new();
         hasher.update(&data_to_hash);
         Ok(Id(hasher.finalize().into()))

+ 4 - 2
crates/types/src/types/filter.rs

@@ -9,8 +9,10 @@ pub struct Filter {
     pub authors: Vec<super::Addr>,
     #[serde(default, skip_serializing_if = "Vec::is_empty")]
     pub kinds: Vec<u32>,
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub tags: Vec<super::Tag>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "#e")]
+    pub references_to_event: Vec<super::Addr>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "#p")]
+    pub references_to_public_key: Vec<super::Addr>,
     #[serde(
         default,
         with = "super::option_ts_seconds",

+ 81 - 68
crates/types/src/types/tag.rs

@@ -12,52 +12,24 @@ pub enum Error {
 }
 
 #[derive(Debug, PartialEq, Eq, Clone)]
-pub enum TagType {
-    Event,
-    PubKey,
-    Custom(String),
+pub enum Tag {
+    Event(Addr, Option<String>, Vec<String>),
+    PubKey(Addr, Option<String>, Vec<String>),
+    Unknown(String, Vec<String>),
 }
 
-impl From<&str> for TagType {
-    fn from(s: &str) -> Self {
-        match s {
-            "e" => TagType::Event,
-            "p" => TagType::PubKey,
-            e => TagType::Custom(e.to_owned()),
-        }
-    }
-}
-
-impl ToString for TagType {
-    fn to_string(&self) -> String {
+impl Tag {
+    pub fn is_pubkey(&self) -> bool {
         match self {
-            TagType::Event => "e".to_owned(),
-            TagType::PubKey => "p".to_owned(),
-            TagType::Custom(x) => x.to_owned(),
+            Self::PubKey(_, _, _) => true,
+            _ => false,
         }
     }
-}
 
-#[derive(Debug, Clone)]
-pub struct Tag {
-    pub typ: TagType,
-    pub value: Addr,
-    pub relayer_url: Option<String>,
-    pub extra: Vec<String>,
-}
-
-impl Tag {
-    pub fn new<T: Into<TagType>>(
-        typ: T,
-        value: Addr,
-        relayer_url: Option<String>,
-        extra: Vec<String>,
-    ) -> Self {
-        Self {
-            typ: typ.into(),
-            value,
-            relayer_url,
-            extra,
+    pub fn is_event(&self) -> bool {
+        match self {
+            Self::Event(_, _, _) => true,
+            _ => false,
         }
     }
 }
@@ -67,18 +39,29 @@ impl ser::Serialize for Tag {
     where
         S: Serializer,
     {
-        let len = if self.relayer_url.is_some() {
-            3 + self.extra.len()
-        } else {
-            2
+        let typ = match self {
+            Tag::Event(_, _, _) => "e",
+            Tag::PubKey(_, _, _) => "p",
+            Tag::Unknown(u, _) => u,
         };
-        let mut seq = serializer.serialize_seq(Some(len))?;
-        seq.serialize_element(&self.typ.to_string())?;
-        seq.serialize_element(&self.value.to_hex())?;
-        if let Some(addr) = self.relayer_url.as_ref() {
-            seq.serialize_element(addr)?;
-            for extra in self.extra.iter() {
-                seq.serialize_element(extra)?;
+
+        let mut seq = serializer.serialize_seq(Some(2))?;
+        seq.serialize_element(typ)?;
+
+        match self {
+            Tag::Event(addr, relayer, extra) | Tag::PubKey(addr, relayer, extra) => {
+                seq.serialize_element(&addr.to_hex())?;
+                if let Some(relayer) = relayer {
+                    seq.serialize_element(&relayer)?;
+                    for extra in extra.iter() {
+                        seq.serialize_element(extra)?;
+                    }
+                }
+            }
+            Tag::Unknown(_, extra) => {
+                for extra in extra.iter() {
+                    seq.serialize_element(extra)?;
+                }
             }
         }
         seq.end()
@@ -90,37 +73,67 @@ impl<'de> Deserialize<'de> for Tag {
     where
         D: Deserializer<'de>,
     {
-        let v: Vec<String> = Deserialize::deserialize(deserializer)?;
-        if v.len() < 2 {
+        let parts: Vec<String> = Deserialize::deserialize(deserializer)?;
+        if parts.len() < 2 {
             return Err(de::Error::custom("expected array of two strings"));
         }
-        let value: Addr = v[1]
-            .as_str()
-            .try_into()
-            .map_err(|e: super::addr::Error| de::Error::custom(e.to_string()))?;
-        let relayer_url = v.get(2).map(|x| x.to_owned());
 
-        let extra = if v.len() > 3 {
-            v[3..].to_owned()
-        } else {
-            vec![]
-        };
+        Ok(match parts[0].as_str() {
+            "e" | "p" => {
+                let addr = parts[1]
+                    .as_str()
+                    .try_into()
+                    .map_err(|e: super::addr::Error| de::Error::custom(e.to_string()))?;
 
-        Ok(Self::new(v[0].as_str(), value, relayer_url, extra))
+                let relayer_addr = parts.get(2).cloned();
+                let extra = if parts.len() < 3 {
+                    vec![]
+                } else {
+                    parts[3..].to_owned()
+                };
+
+                if "e" == parts[0].as_str() {
+                    Tag::Event(addr, relayer_addr, extra)
+                } else {
+                    Tag::PubKey(addr, relayer_addr, extra)
+                }
+            }
+            extra => Tag::Unknown(extra.to_owned(), parts[1..].to_owned()),
+        })
     }
 }
 
 #[cfg(test)]
 mod test {
     use super::*;
+    use crate::Message;
+
+    #[test]
+    fn bug_01() {
+        let json = r#"["EVENT","d45a98f898820258a3313f5cb14f5fe8a9263437931ac6309f23ae0324833f39",{"content":"Will fight under the light of lightnings! No darkness on nostr! 🐶🐾😂","created_at":1678685477,"id":"5462822032c16d32267ba40536409fd51ea188b20e7dd5f9e7a0aa5561346f79","kind":1,"pubkey":"8fb140b4e8ddef97ce4b821d247278a1a4353362623f64021484b372f948000c","sig":"62c8e09a0fedd8096a313ef12436b0f0bcad56e9058d4bc12f61ae6094c099bb966dce47efa2d721c5bfdf923614db46d1d1c248105cb99c4ec495292cc875b1","tags":[["e","5f63f9e7d37673e76ddf7448cd67c3d74f9be96c240a40199b59a30db32d7f43"],["e","c70894331986c66a2baf6fc12dd5c86280e4616cee0e57bbee90972ebbb4b735"],["p","ac3fb436a663b25893657f4b6a3d9d2f02d1974bb5ced603f4d0c8ee32d7e0a2"]]}]"#;
+        let message: Result<Message, _> = serde_json::from_str(json);
+        assert!(message.is_ok());
+    }
+
+    #[test]
+    fn parse_unexpected_tag() {
+        let json = r#"["EVENT","f4e62282fda9c7d93a6e3b03fd1f1a3a34936f74584b0e021edf9659fd7da9d6",{"content":"1678682368","created_at":1678682368,"id":"7506ccd8ce4de835c61c04fd16f8489b2acb8cc052ed6730cba203188dfedf57","kind":30000,"pubkey":"228cc1e37a8fec2eee3dda3a3dbd04a60968086d8f42751a7632499d938eda8f","sig":"73ec91ee5d23a93287700f31274a57d84f46c0aa06523138c8f5b0cf2f20bd8a7db72346ecae9c717d9642cea81d72c35ea95ec615146b1640f62de5e3fbbb69","tags":[["d","chats/null/lastOpened"]]}]"#;
+        let message: Result<Message, _> = serde_json::from_str(json);
+        assert!(message.is_ok());
+    }
 
     #[test]
     fn serialize_deserialize() {
         let json = "[\"p\",\"a0b0c0d0\"]";
         let tag: Tag = serde_json::from_str(&json).expect("valid json");
-        assert_eq!(tag.typ, TagType::PubKey);
-        assert_eq!(tag.value.to_hex(), "a0b0c0d0".to_owned());
-        assert!(tag.relayer_url.is_none());
-        assert!(tag.extra.is_empty());
+        assert_eq!(
+            Tag::PubKey(
+                Addr::try_from_public_key_str(crate::types::addr::Type::Unknown, "a0b0c0d0")
+                    .expect("valid addr"),
+                None,
+                vec![]
+            ),
+            tag
+        );
     }
 }