nostr_rust 0.20.3

A Rust implementation of the Nostr protocol
Documentation
use std::fmt;

use secp256k1::{schnorr::Signature, KeyPair, XOnlyPublicKey, SECP256K1};
use serde_derive::{Deserialize, Serialize};
use serde_json::json;
use std::str::FromStr;

use thiserror::Error;

use crate::Identity;

/// EventPrepare is the struct used to prepare an event before publishing it (signing it and assigning it an id)
#[derive(Serialize, Deserialize, Debug)]
pub struct EventPrepare {
    /// 32-bytes hex-encoded public key of the event creator
    #[serde(rename = "pubkey")]
    pub pub_key: String,
    /// unix timestamp in seconds
    pub created_at: u64,
    /// integer
    /// 0: NostrEvent
    pub kind: u16,
    /// Tags
    pub tags: Vec<Vec<String>>,
    /// arbitrary string
    pub content: String,
}

impl EventPrepare {
    /// get_content returns the content of the event to be signed
    /// # Example
    /// ```rust
    /// use nostr_rust::{events::EventPrepare, utils::get_timestamp};
    ///
    /// let actual_time = get_timestamp();
    ///
    /// let event = EventPrepare {
    ///    pub_key: env!("PUBLIC_KEY").to_string(),
    ///    created_at: get_timestamp(),
    ///    kind: 0,
    ///    tags: vec![],
    ///    content: "content".to_string(),
    /// };
    ///
    /// assert_eq!(event.get_content(), format!("[0,\"c5aec31e83bdf980939b5ef7c6bcaa2be8bd39d38667da58ba6dba240eb8b69d\",{},0,[],\"content\"]", actual_time));
    /// ```
    pub fn get_content(&self) -> String {
        json!([
            0,
            self.pub_key,
            self.created_at,
            self.kind,
            self.tags,
            self.content
        ])
        .to_string()
    }

    /// Get the id of the event which is the sha256 hash of the content
    /// # Example
    /// ```rust
    /// use nostr_rust::{events::EventPrepare};
    ///
    /// let event = EventPrepare {
    ///   pub_key: env!("PUBLIC_KEY").to_string(),
    ///   created_at: 0, // Don't use this in production
    ///   kind: 0,
    ///   tags: vec![],
    ///   content: "content".to_string(),
    /// };
    ///
    /// assert_eq!(event.get_content_id(), "4a57aad22fc0fd374e8ceeaaaf8817fa6cb661ca2229c66309d7dba69dfe2359");
    /// ```
    pub fn get_content_id(&self) -> String {
        sha256::digest(self.get_content())
    }

    /// Transform the event to NostrEvent
    /// # Example
    /// ```rust
    /// use std::str::FromStr;
    /// use nostr_rust::{events::EventPrepare, Identity};
    ///
    /// let mut event = EventPrepare {
    ///  pub_key: env!("PUBLIC_KEY").to_string(),
    ///  created_at: 0, // Don't use this in production
    ///  kind: 0,
    ///  tags: vec![],
    ///  content: "content".to_string(),
    /// };
    ///
    /// let identity = Identity::from_str(env!("SECRET_KEY")).unwrap();
    /// // Test to_event without Proof of Work
    /// let nostr_event = event.to_event(&identity, 0);
    /// assert_eq!(nostr_event.id, "4a57aad22fc0fd374e8ceeaaaf8817fa6cb661ca2229c66309d7dba69dfe2359");
    /// assert_eq!(nostr_event.content, "content");
    /// assert_eq!(nostr_event.kind, 0);
    /// assert_eq!(nostr_event.tags.len(), 0);
    /// assert_eq!(nostr_event.created_at, 0);
    /// assert_eq!(nostr_event.pub_key, env!("PUBLIC_KEY"));
    /// assert_eq!(nostr_event.sig.len(), 128);
    ///
    /// // Test to_event with Proof of Work
    /// let difficulty = 10;
    /// let mut nostr_event_pow = event.to_event(&identity, difficulty);
    /// let event_id = hex::decode(nostr_event_pow.id).unwrap();
    /// let event_difficulty = EventPrepare::count_leading_zero_bits(event_id);
    /// assert!(event_difficulty >= difficulty.into());
    /// assert_eq!(nostr_event_pow.content, "content");
    /// assert_eq!(nostr_event_pow.kind, 0);
    /// assert_eq!(nostr_event_pow.tags.len(), 1);
    /// assert!(nostr_event_pow.created_at > 0);
    /// assert_eq!(nostr_event_pow.pub_key, env!("PUBLIC_KEY"));
    /// assert_eq!(nostr_event_pow.sig.len(), 128);
    /// ```
    pub fn to_event(&mut self, secret_key: &Identity, difficulty_target: u16) -> Event {
        if difficulty_target > 0 {
            self.to_pow_event(difficulty_target).unwrap();
        }

        let message = secp256k1::Message::from_hashed_data::<secp256k1::hashes::sha256::Hash>(
            self.get_content().as_bytes(),
        );

        let signature = SECP256K1
            .sign_schnorr(
                &message,
                &KeyPair::from_secret_key(SECP256K1, &secret_key.secret_key),
            )
            .to_string();

        Event {
            id: self.get_content_id(),
            pub_key: self.pub_key.clone(),
            created_at: self.created_at,
            kind: self.kind,
            tags: self.tags.clone(),
            content: self.content.clone(),
            sig: signature,
        }
    }
}

/// Event is the struct used to represent a Nostr event
#[derive(Serialize, Deserialize, Debug)]
pub struct Event {
    /// 32-bytes sha256 of the serialized event data
    pub id: String,
    /// 32-bytes hex-encoded public key of the event creator
    #[serde(rename = "pubkey")]
    pub pub_key: String,
    /// unix timestamp in seconds
    pub created_at: u64,
    /// integer
    /// 0: NostrEvent
    pub kind: u16,
    /// Tags
    pub tags: Vec<Vec<String>>,
    /// arbitrary string
    pub content: String,
    /// 64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field
    pub sig: String,
}

#[derive(Error, Debug, Eq, PartialEq)]
pub enum EventError {
    #[error("Secp256k1 Error: {}", _0)]
    Secp256k1Error(secp256k1::Error),
}

impl From<secp256k1::Error> for EventError {
    fn from(err: secp256k1::Error) -> Self {
        Self::Secp256k1Error(err)
    }
}

impl Event {
    /// get_content returns the content of the event
    /// # Example
    /// ```rust
    /// use nostr_rust::{events::EventPrepare, utils::get_timestamp, Identity};
    /// use std::str::FromStr;
    ///
    /// let actual_time = get_timestamp();
    /// let identity = Identity::from_str(env!("SECRET_KEY")).unwrap();
    /// let event = EventPrepare {
    ///    pub_key: env!("PUBLIC_KEY").to_string(),
    ///    created_at: get_timestamp(),
    ///    kind: 0,
    ///    tags: vec![],
    ///    content: "content".to_string(),
    /// }.to_event(&identity, 0);
    /// assert_eq!(event.get_content(), format!("[0,\"{}\",{},0,[],\"content\"]", env!("PUBLIC_KEY"), actual_time));
    /// ```
    pub fn get_content(&self) -> String {
        json!([
            0,
            self.pub_key,
            self.created_at,
            self.kind,
            self.tags,
            self.content
        ])
        .to_string()
    }

    /// Get the id of the event which is the sha256 hash of the content
    /// # Example
    /// ```rust
    /// use nostr_rust::{events::EventPrepare, Identity};
    /// use std::str::FromStr;
    /// let identity = Identity::from_str(env!("SECRET_KEY")).unwrap();
    /// let event = EventPrepare {
    ///   pub_key: env!("PUBLIC_KEY").to_string(),
    ///   created_at: 0, // Don't use this in production
    ///   kind: 0,
    ///   tags: vec![],
    ///   content: "content".to_string(),
    /// }.to_event(&identity, 0);
    ///
    /// assert_eq!(event.get_content_id().len(), 64);
    /// ```
    pub fn get_content_id(&self) -> String {
        sha256::digest(self.get_content())
    }

    /// Get the id of the event which is the sha256 hash of the content
    /// # Example
    /// ```rust
    /// use nostr_rust::{events::EventPrepare, Identity};
    /// use std::str::FromStr;
    ///
    /// let identity = Identity::from_str(env!("SECRET_KEY")).unwrap();
    ///
    /// let event = EventPrepare {
    ///   pub_key: env!("PUBLIC_KEY").to_string(),
    ///   created_at: 0, // Don't use this in production
    ///   kind: 0,
    ///   tags: vec![],
    ///   content: "content".to_string(),
    /// }.to_event(&identity, 0);
    ///
    /// event.verify().unwrap()
    /// ```
    pub fn verify(&self) -> Result<(), EventError> {
        let message = secp256k1::Message::from_hashed_data::<secp256k1::hashes::sha256::Hash>(
            self.get_content().as_bytes(),
        );

        SECP256K1.verify_schnorr(
            &Signature::from_str(&self.sig)?,
            &message,
            &XOnlyPublicKey::from_str(&self.pub_key)?,
        )?;
        Ok(())
    }
}

impl fmt::Display for Event {
    /// Return the serialized event
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", serde_json::to_string(&self).unwrap())
    }
}

/// Extract events from a string
///
/// # Example
/// ```rust
/// use nostr_rust::events::extract_events;
///
/// let txt = "[\"EVENT\",\"deb0ab5bd829d1642c926b7897b078d027ca41870d0a499c1fd76e4b5af5ccbd\",{\"id\":\"f0382d932ddc5876bad3f9c5fdb84fb4c2af7ccefebfb491f13fbc47c38f8ae4\",\"kind\":1,\"pubkey\":\"884704bd421721e292edbff42eb77547fe115c6ff9825b08fc366be4cd69e9f6\",\"created_at\":1673131597,\"content\":\"Does anyone know a good crate rust to handle a Lightning node?\",\"tags\":[],\"sig\":\"53a629bae11dace9b487700cbe8e85058a3d7b6989e1e0bdd6eb4fb0201a3779742682f65ca37782c0cb93019a170e0a368bb033dfce1102df71420e24e2b784\"}]";
///
/// let events = extract_events(txt);
/// assert_eq!(events.len(), 1);
/// ```
pub fn extract_events(message: &str) -> Vec<Event> {
    let mut events = vec![];
    let json = serde_json::from_str::<serde_json::Value>(message);

    if json.is_err() {
        return events;
    }

    let json = json.unwrap();

    if !json.is_array() {
        return events;
    }

    let json = json.as_array().unwrap();

    for event in json {
        if !event.is_object() {
            continue;
        }

        let event = serde_json::from_value::<Event>(event.clone());

        if event.is_err() {
            continue;
        }

        events.push(event.unwrap());
    }

    events
}

/// Extract events from a websocket message
///
/// # Example
/// ```rust
/// use nostr_rust::events::extract_events_ws;
/// use tungstenite::Message;
///
/// let txt = "[\"EVENT\",\"deb0ab5bd829d1642c926b7897b078d027ca41870d0a499c1fd76e4b5af5ccbd\",{\"id\":\"f0382d932ddc5876bad3f9c5fdb84fb4c2af7ccefebfb491f13fbc47c38f8ae4\",\"kind\":1,\"pubkey\":\"884704bd421721e292edbff42eb77547fe115c6ff9825b08fc366be4cd69e9f6\",\"created_at\":1673131597,\"content\":\"Does anyone know a good crate rust to handle a Lightning node?\",\"tags\":[],\"sig\":\"53a629bae11dace9b487700cbe8e85058a3d7b6989e1e0bdd6eb4fb0201a3779742682f65ca37782c0cb93019a170e0a368bb033dfce1102df71420e24e2b784\"}]";
///
/// let events = extract_events_ws(&Message::Text(txt.to_string()));
/// assert_eq!(events.len(), 1);
/// ```
pub fn extract_events_ws(message: &crate::Message) -> Vec<Event> {
    if message.is_text() {
        return extract_events(message.to_text().unwrap());
    }

    vec![]
}