瀏覽代碼

Verify signature of events

Cesar Rodas 2 年之前
父節點
當前提交
5450e92e81

+ 19 - 0
Cargo.lock

@@ -256,6 +256,7 @@ dependencies = [
  "chrono",
  "hex",
  "rand",
+ "secp256k1",
  "serde",
  "serde_json",
  "sha2",
@@ -354,6 +355,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
 
 [[package]]
+name = "secp256k1"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894"
+dependencies = [
+ "secp256k1-sys",
+]
+
+[[package]]
+name = "secp256k1-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "642a62736682fdd8c71da0eb273e453c8ac74e33b9fb310e22ba5b03ec7651ff"
+dependencies = [
+ "cc",
+]
+
+[[package]]
 name = "serde"
 version = "1.0.153"
 source = "registry+https://github.com/rust-lang/crates.io-index"

+ 1 - 0
crates/types/Cargo.toml

@@ -10,6 +10,7 @@ bech32 = "0.9.1"
 chrono = "0.4.23"
 hex = "0.4"
 rand = "0.8.5"
+secp256k1 = { version = "0.26.0", features = ["global-context"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 sha2 = "0.10.6"

+ 1 - 1
crates/types/src/client/request.rs

@@ -52,7 +52,7 @@ mod test {
 
         let serialized = serde_json::to_string(&r).expect("valid json string");
         let obj: Message = serde_json::from_str(&serialized).expect("valid request");
-        let obj = obj.get_request().expect("request");
+        let obj = obj.as_request().expect("request");
         assert_eq!(r.subscription_id, obj.subscription_id);
         assert_eq!(r.filters.len(), obj.filters.len());
     }

+ 64 - 4
crates/types/src/lib.rs

@@ -2,7 +2,9 @@ use serde::{
     de::{self, Deserializer},
     Deserialize, Serialize,
 };
+use std::convert::TryFrom;
 use thiserror::Error;
+use types::{Event, SubscriptionId};
 
 pub mod client;
 pub mod types;
@@ -13,27 +15,42 @@ pub enum Error {}
 #[derive(Serialize, Debug, Clone)]
 pub enum Message {
     Request(client::Request),
+    EventFromClient(types::Event),
     EventFromServer(types::SubscriptionId, types::Event),
     Close(types::SubscriptionId),
     Notice(String),
 }
 
 impl Message {
-    pub fn get_request(&self) -> Option<&client::Request> {
+    pub fn as_request(&self) -> Option<&client::Request> {
         match self {
             Self::Request(x) => Some(x),
             _ => None,
         }
     }
 
-    pub fn get_notice(&self) -> Option<&str> {
+    pub fn as_event_from_server(&self) -> Option<(&types::SubscriptionId, &types::Event)> {
+        match self {
+            Self::EventFromServer(id, event) => Some((id, event)),
+            _ => None,
+        }
+    }
+
+    pub fn as_event_from_client(&self) -> Option<&types::Event> {
+        match self {
+            Self::EventFromClient(event) => Some(event),
+            _ => None,
+        }
+    }
+
+    pub fn as_notice(&self) -> Option<&str> {
         match self {
             Self::Notice(x) => Some(x),
             _ => None,
         }
     }
 
-    pub fn get_close_subscription_id(&self) -> Option<&types::SubscriptionId> {
+    pub fn as_close_subscription_id(&self) -> Option<&types::SubscriptionId> {
         match self {
             Self::Close(x) => Some(x),
             _ => None,
@@ -60,6 +77,39 @@ impl<'de> de::Deserialize<'de> for Message {
             .ok_or_else(|| de::Error::custom("Invalid type for element 0 of the array"))?;
 
         match tag {
+            "EVENT" => match array.len() {
+                3 => {
+                    let subscription_id: SubscriptionId = array[1]
+                        .as_str()
+                        .map(TryFrom::try_from)
+                        .transpose()
+                        .map_err(|e: types::subscription_id::Error| {
+                            de::Error::custom(e.to_string())
+                        })?
+                        .ok_or_else(|| de::Error::custom("Invalid subscription ID"))?;
+
+                    let event: Event = serde_json::from_value(array[2].clone())
+                        .map_err(|e: serde_json::Error| de::Error::custom(e.to_string()))?;
+
+                    event
+                        .is_valid()
+                        .map_err(|e| de::Error::custom(e.to_string()))?;
+
+                    Ok(Self::EventFromServer(subscription_id, event))
+                }
+                2 => {
+                    let event: Event = serde_json::from_value(array[1].clone())
+                        .map_err(|e: serde_json::Error| de::Error::custom(e.to_string()))?;
+
+                    event
+                        .is_valid()
+                        .map_err(|e| de::Error::custom(e.to_string()))?;
+
+                    Ok(Self::EventFromClient(event))
+                }
+                _ => Err(de::Error::custom("Invalid length for EVENT")),
+            },
+
             "NOTICE" => Ok(Self::Notice(
                 serde_json::from_value(
                     array
@@ -120,11 +170,21 @@ mod test {
     }
 
     #[test]
+    fn event_from_server() {
+        let json = "[\"EVENT\",\"640bddcc93eae\",{\"content\":\"🤙\",\"created_at\":1676637072,\"id\":\"a3eaa71e4f46c1a69ac0596ae7c2af35807fc0b0d3b208b79a36eef67ef51743\",\"kind\":7,\"pubkey\":\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\",\"sig\":\"93a3e9c4f6cb9a704885c4f77f6d7b16153e8eedc967603602606147f8c78d426f547d54120b80b33bd2101de638c06f2932df4daf53d66ca9b1341f2fd45729\",\"tags\":[[\"p\",\"8fe53b37518e3dbe9bab26d912292001d8b882de9456b7b08b615f912dc8bf4a\",\"\",\"mention\"],[\"e\",\"eb278e983fcedbb0d143c4250c879d078d037586c5dca8e1cf1a104f9846a460\"],[\"p\",\"2bda4f03446bc1c6ff00594e350a1e7ec57fb9cdc4a7b52694223d56ce0599e9\"]]}]";
+        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!("🤙".to_owned(), event.content.content);
+    }
+
+    #[test]
     fn close() {
         let json = "[\"CLOSE\", \"foo\"]";
         let message: Message = serde_json::from_str(json).expect("valid message");
         let subscription_id = message
-            .get_close_subscription_id()
+            .as_close_subscription_id()
             .expect("valid subscription_id");
 
         assert_eq!("foo".to_owned(), subscription_id.to_string());

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

@@ -47,7 +47,7 @@ impl Default for Type {
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Addr {
-    bytes: Vec<u8>,
+    pub bytes: Vec<u8>,
     pub typ: Type,
 }
 
@@ -71,7 +71,7 @@ impl Addr {
         }
     }
 
-    pub fn to_public_key(&self) -> String {
+    pub fn to_hex(&self) -> String {
         hex::encode(&self.bytes)
     }
 
@@ -135,7 +135,7 @@ impl ser::Serialize for Addr {
     where
         S: Serializer,
     {
-        let str = self.to_public_key();
+        let str = self.to_hex();
         serializer.serialize_str(&str)
     }
 }

+ 77 - 19
crates/types/src/types/event.rs

@@ -1,6 +1,7 @@
-use super::{addr, Addr, Signature};
+use super::{Id, Signature};
 use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
+use secp256k1::{schnorr, Message, XOnlyPublicKey};
+use serde::{de, Deserialize, Serialize};
 use serde_json::json;
 use sha2::{Digest, Sha256};
 use thiserror::Error;
@@ -11,12 +12,19 @@ pub enum Error {
     Serialize(#[from] serde_json::Error),
 
     #[error("Error serializing id: {0}")]
-    Id(#[from] addr::Error),
+    Id(#[from] hex::FromHexError),
+
+    #[error("Signature error: {0}")]
+    Signature(#[from] secp256k1::Error),
+
+    #[error("Provided ID is not correct")]
+    InvalidProvidedId,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct EventContent {
-    pub pub_key: Addr,
+pub struct Metadata {
+    #[serde(rename = "pubkey")]
+    pub public_key: Id,
     #[serde(with = "super::ts_seconds")]
     pub created_at: DateTime<Utc>,
     pub kind: u32,
@@ -24,30 +32,38 @@ pub struct EventContent {
     pub content: String,
 }
 
-impl EventContent {
+impl Metadata {
     pub fn new<T>(
-        pub_key: Addr,
+        public_key: Id,
         kind: u32,
         tags: Vec<super::Tag>,
-        content_obj: &T,
+        content: &T,
         created_at: Option<DateTime<Utc>>,
     ) -> Result<Self, Error>
     where
         T: ?Sized + Serialize,
     {
         Ok(Self {
-            pub_key,
-            created_at: created_at.unwrap_or_else(|| Utc::now()),
+            public_key,
+            created_at: created_at.unwrap_or_else(Utc::now),
             kind,
             tags,
-            content: serde_json::to_string(&content_obj)?,
+            content: serde_json::to_string(&content)?,
         })
     }
 
-    pub fn id(&self) -> Result<Addr, Error> {
+    #[inline]
+    pub fn content<'a, T>(&'a self) -> Result<T, Error>
+    where
+        T: de::Deserialize<'a>,
+    {
+        Ok(serde_json::from_str(&self.content)?)
+    }
+
+    pub fn id(&self) -> Result<Id, Error> {
         let data_to_hash = serde_json::to_string(&json!(vec![
             json!(0),
-            json!(self.pub_key.to_public_key()),
+            json!(self.public_key.to_string()),
             json!(self.created_at.timestamp()),
             json!(self.kind),
             json!(self.tags),
@@ -55,25 +71,67 @@ impl EventContent {
         ]))?;
         let mut hasher = Sha256::new();
         hasher.update(&data_to_hash);
-        let id: [u8; 32] = hasher.finalize().into();
-        Ok(Addr::try_from_bytes(Default::default(), id.to_vec())?)
+        Ok(Id(hasher.finalize().into()))
     }
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Event {
-    pub id: Addr,
+    pub id: Id,
     #[serde(flatten)]
-    pub content: EventContent,
+    pub content: Metadata,
+    #[serde(rename = "sig")]
     pub signature: Signature,
 }
 
 impl Event {
-    pub fn new(content: EventContent, signature: Signature) -> Result<Self, Error> {
+    pub fn new(content: Metadata, signature: Signature) -> Result<Self, Error> {
+        let id = content.id()?;
+        Self::verify_signature(&content.public_key, &signature, &id)?;
+
         Ok(Self {
-            id: content.id()?,
+            id,
             content,
             signature,
         })
     }
+
+    #[inline]
+    pub fn content<'a, T>(&'a self) -> Result<T, Error>
+    where
+        T: de::Deserialize<'a>,
+    {
+        self.content.content()
+    }
+
+    pub fn is_valid(&self) -> Result<(), Error> {
+        let calculated_id = self.content.id()?;
+        if calculated_id != self.id {
+            return Err(Error::InvalidProvidedId);
+        }
+        Self::verify_signature(&self.content.public_key, &self.signature, &self.id)
+    }
+
+    fn verify_signature(
+        public_key: &Id,
+        signature: &Signature,
+        content_id: &Id,
+    ) -> Result<(), Error> {
+        let public_key = XOnlyPublicKey::from_slice(&**public_key)?;
+        let message = Message::from_slice(&**content_id)?;
+        Ok(schnorr::Signature::from_slice(&**signature)?.verify(&message, &public_key)?)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn parse_event_content() {
+        let json = "{\"content\":\"{\\\"lud06\\\":\\\"lnbc1p3a4wxvpp5x0pa6gr55fq5s9d3dxs0vz77mqxgdw63hhtgtlfz5zvm65847vnqdqqcqpjsp5402c8rtqxd4j97rnvuejuwl4sg473g6wg08d67fvn7qc4gtpkfks9q7sqqqqqqqqqqqqqqqqqqqsqqqqqysgqmqz9gxqyjw5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glclleasn65surjcsqqqqlgqqqqqeqqjqyxj968tem9ps6ttm9ukv6ag4yc6qmgj2svrccfgp4n83fpktr3dsx6fq7grfzlqt982aaemahg9q29vzl9f627kh4j8h8xc2z2mtpdqqjlekah\\\",\\\"website\\\":\\\"\\\",\\\"nip05\\\":\\\"cesar@cesar.com.py\\\",\\\"picture\\\":\\\"https://pbs.twimg.com/profile_images/1175432935337537536/_Peu9vuJ_400x400.jpg\\\",\\\"display_name\\\":\\\"C\\\",\\\"about\\\":\\\"Rust and PHP\\\",\\\"name\\\":\\\"c\\\"}\",\"created_at\":1678476588,\"id\":\"3800c787a23288641c0b96cbcc87c26cbd3ea7bee53b7748422fdb100fb7b9f0\",\"kind\":0,\"pubkey\":\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\",\"sig\":\"c8a12ce96833e4cd67bce0e9e50f831262ef0f0c0cff5e56c38a0c90867ed1a6621e9692948ef5e85a7ca3726c3f0f43fa7e1992536bc457317123bca8784f5f\",\"tags\":[]}";
+
+        let obj: Event = serde_json::from_str(json).expect("valid");
+        assert!(obj.is_valid().is_ok());
+    }
 }

+ 46 - 0
crates/types/src/types/id.rs

@@ -0,0 +1,46 @@
+use serde::{
+    de::{self, Deserializer},
+    ser::{self, Serializer},
+    Deserialize,
+};
+use std::ops::Deref;
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Id(pub [u8; 32]);
+
+impl Deref for Id {
+    type Target = [u8; 32];
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl ToString for Id {
+    fn to_string(&self) -> String {
+        hex::encode(self.0)
+    }
+}
+
+impl<'de> Deserialize<'de> for Id {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = <String>::deserialize(deserializer)?;
+        let id: [u8; 32] = hex::decode(&s)
+            .map_err(|e| de::Error::custom(e.to_string()))?
+            .try_into()
+            .map_err(|_| de::Error::custom("Invalid length for Id"))?;
+
+        Ok(Self(id))
+    }
+}
+
+impl ser::Serialize for Id {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_str(self.to_string().as_str())
+    }
+}

+ 3 - 1
crates/types/src/types/mod.rs

@@ -1,6 +1,7 @@
 pub mod addr;
 pub mod event;
 pub mod filter;
+pub mod id;
 pub mod signature;
 pub mod subscription_id;
 pub mod tag;
@@ -71,8 +72,9 @@ pub(crate) mod ts_seconds {
 
 pub use self::{
     addr::Addr,
-    event::{Event, EventContent},
+    event::{Event, Metadata},
     filter::Filter,
+    id::Id,
     signature::Signature,
     subscription_id::SubscriptionId,
     tag::Tag,

+ 10 - 1
crates/types/src/types/signature.rs

@@ -1,3 +1,5 @@
+use std::ops::Deref;
+
 use serde::{
     de::{self, Deserialize, Deserializer},
     ser::{self, Serializer},
@@ -16,9 +18,16 @@ pub enum Error {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Signature(pub [u8; 64]);
 
+impl Deref for Signature {
+    type Target = [u8; 64];
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
 impl ToString for Signature {
     fn to_string(&self) -> String {
-        hex::encode(&self.0)
+        hex::encode(self.0)
     }
 }
 

+ 3 - 3
crates/types/src/types/tag.rs

@@ -74,7 +74,7 @@ impl ser::Serialize for Tag {
         };
         let mut seq = serializer.serialize_seq(Some(len))?;
         seq.serialize_element(&self.typ.to_string())?;
-        seq.serialize_element(&self.value.to_public_key())?;
+        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() {
@@ -91,7 +91,7 @@ impl<'de> Deserialize<'de> for Tag {
         D: Deserializer<'de>,
     {
         let v: Vec<String> = Deserialize::deserialize(deserializer)?;
-        if v.len() > 2 {
+        if v.len() < 2 {
             return Err(de::Error::custom("expected array of two strings"));
         }
         let value: Addr = v[1]
@@ -119,7 +119,7 @@ mod test {
         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_public_key(), "a0b0c0d0".to_owned());
+        assert_eq!(tag.value.to_hex(), "a0b0c0d0".to_owned());
         assert!(tag.relayer_url.is_none());
         assert!(tag.extra.is_empty());
     }