Skip to content

Commit

Permalink
nostr: add NIP-62 support
Browse files Browse the repository at this point in the history
Closes rust-nostr#775
Pull-Request: rust-nostr#777

Reviewed-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
Co-authored-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
Signed-off-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
Signed-off-by: Awiteb <a@4rs.nl>
Signed-off-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
  • Loading branch information
TheAwiteb and yukibtc committed Feb 20, 2025
1 parent 934ee09 commit 6b1323c
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

### Added

* nostr: add NIP-62 support ([awiteb])
* nostr: add `nip21::extract_from_text` function ([Yuki Kishimoto])
* nostr: add `EventBuilder::allow_self_tagging` ([Yuki Kishimoto])
* nostr: add `Nip19Event::from_event` ([Yuki Kishimoto])
Expand Down Expand Up @@ -1141,6 +1142,7 @@ added `nostrdb` storage backend, added NIP32 and completed NIP51 support and mor
[Roland Bewick]: https://github.com/rolznz (nostr:npub1zk6u7mxlflguqteghn8q7xtu47hyerruv6379c36l8lxzzr4x90q0gl6ef)
[Francisco Calderón]: https://github.com/grunch (nostr:npub1qqqqqqqx2tj99mng5qgc07cgezv5jm95dj636x4qsq7svwkwmwnse3rfkq)
[cipres]: https://github.com/PancakesArchitect (nostr:npub1r3cnzta52fee26c83cnes8wvzkch3kud2kll67k402x04mttt26q0wfx0c)
[awiteb]: https://git.4rs.nl (nostr:nprofile1qqsqqqqqq9g9uljgjfcyd6dm4fegk8em2yfz0c3qp3tc6mntkrrhawgpzfmhxue69uhkummnw3ezudrjwvhxumq3dg0ly)

<!-- Tags -->
[Unreleased]: https://github.com/rust-nostr/nostr/compare/v0.39.0...HEAD
Expand Down
6 changes: 6 additions & 0 deletions bindings/nostr-sdk-ffi/src/protocol/event/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,10 @@ pub enum KindStandard {
///
/// <https://github.com/nostr-protocol/nips/blob/master/69.md>
PeerToPeerOrder,
/// Request to Vanish (NIP62)
///
/// <https://github.com/nostr-protocol/nips/blob/master/62.md>
RequestToVanish,
/// Client Authentication (NIP42)
Authentication,
/// Wallet Connect Request (NIP47)
Expand Down Expand Up @@ -406,6 +410,7 @@ fn convert(k: nostr::Kind) -> Option<KindStandard> {
nostr::Kind::Torrent => Some(KindStandard::Torrent),
nostr::Kind::TorrentComment => Some(KindStandard::TorrentComment),
nostr::Kind::PeerToPeerOrder => Some(KindStandard::PeerToPeerOrder),
nostr_sdk::Kind::RequestToVanish => Some(KindStandard::RequestToVanish),
nostr::Kind::Custom(..) => None,
}
}
Expand Down Expand Up @@ -487,6 +492,7 @@ impl From<KindStandard> for nostr::Kind {
KindStandard::Torrent => Self::Torrent,
KindStandard::TorrentComment => Self::TorrentComment,
KindStandard::PeerToPeerOrder => Self::PeerToPeerOrder,
KindStandard::RequestToVanish => Self::RequestToVanish,
}
}
}
6 changes: 6 additions & 0 deletions bindings/nostr-sdk-ffi/src/protocol/event/tag/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ pub enum TagStandard {
RelayUrl {
relay_url: String,
},
/// All relays tag
///
/// <https://github.com/nostr-protocol/nips/blob/master/62.md>
AllRelays,
POW {
nonce: String,
difficulty: u8,
Expand Down Expand Up @@ -415,6 +419,7 @@ impl From<tag::TagStandard> for TagStandard {
tag::TagStandard::Relay(url) => Self::RelayUrl {
relay_url: url.to_string(),
},
tag::TagStandard::AllRelays => Self::AllRelays,
tag::TagStandard::POW { nonce, difficulty } => Self::POW {
nonce: nonce.to_string(),
difficulty,
Expand Down Expand Up @@ -656,6 +661,7 @@ impl TryFrom<TagStandard> for tag::TagStandard {
uppercase,
}),
TagStandard::RelayUrl { relay_url } => Ok(Self::Relay(RelayUrl::parse(&relay_url)?)),
TagStandard::AllRelays => Ok(Self::AllRelays),
TagStandard::POW { nonce, difficulty } => Ok(Self::POW {
nonce: nonce.parse()?,
difficulty,
Expand Down
3 changes: 2 additions & 1 deletion crates/nostr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,12 @@ The following crate feature flags are available:
|| [52 - Calendar Events](https://github.com/nostr-protocol/nips/blob/master/52.md) |
|| [53 - Live Activities](https://github.com/nostr-protocol/nips/blob/master/53.md) |
|| [54 - Wiki](https://github.com/nostr-protocol/nips/blob/master/54.md) |
| - | [55 - Android Signer Application](https://github.com/nostr-protocol/nips/blob/master/55.md) |
| - | [55 - Android Signer Application](https://github.com/nostr-protocol/nips/blob/master/55.md) |
|| [56 - Reporting](https://github.com/nostr-protocol/nips/blob/master/56.md) |
|| [57 - Lightning Zaps](https://github.com/nostr-protocol/nips/blob/master/57.md) |
|| [58 - Badges](https://github.com/nostr-protocol/nips/blob/master/58.md) |
|| [59 - Gift Wrap](https://github.com/nostr-protocol/nips/blob/master/59.md) |
|| [62 - Request to Vanish](https://github.com/nostr-protocol/nips/blob/master/62.md) |
|| [65 - Relay List Metadata](https://github.com/nostr-protocol/nips/blob/master/65.md) |
|| [70 - Protected Events](https://github.com/nostr-protocol/nips/blob/master/70.md) |
|| [71 - Video Events](https://github.com/nostr-protocol/nips/blob/master/71.md) |
Expand Down
39 changes: 39 additions & 0 deletions crates/nostr/src/event/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use serde_json::{json, Value};

#[cfg(all(feature = "std", feature = "nip04", feature = "nip46"))]
use crate::nips::nip46::Message as NostrConnectMessage;
use crate::nips::nip62::VanishTarget;
use crate::prelude::*;

/// Wrong kind error
Expand Down Expand Up @@ -65,6 +66,8 @@ pub enum Error {
/// The expected kind (single or range)
expected: WrongKindError,
},
/// Empty tags, while at least one tag is required
EmptyTags,
}

#[cfg(feature = "std")]
Expand All @@ -87,6 +90,7 @@ impl fmt::Display for Error {
Self::WrongKind { received, expected } => {
write!(f, "Wrong kind: received={received}, expected={expected}")
}
Self::EmptyTags => write!(f, "Empty tags, while at least one tag is required"),
}
}
}
Expand Down Expand Up @@ -724,6 +728,41 @@ impl EventBuilder {
Self::new(Kind::EventDeletion, reason.into()).tags(tags)
}

/// Request to vanish
///
/// <https://github.com/nostr-protocol/nips/blob/master/62.md>
#[inline]
pub fn request_vanish<I>(target: VanishTarget) -> Result<Self, Error> {
Self::request_vanish_with_reason(target, "")
}

/// Request to vanish with reason
///
/// <https://github.com/nostr-protocol/nips/blob/master/62.md>
pub fn request_vanish_with_reason<S>(target: VanishTarget, reason: S) -> Result<Self, Error>
where
S: Into<String>,
{
let mut builder = Self::new(Kind::RequestToVanish, reason);

match target {
VanishTarget::AllRelays => {
builder = builder.tag(Tag::all_relays());
}
VanishTarget::Relays(list) => {
// Check if the list is empty
if list.is_empty() {
// Empty list, return error.
return Err(Error::EmptyTags);
}

builder = builder.tags(list.into_iter().map(Tag::relay));
}
}

Ok(builder)
}

/// Add reaction (like/upvote, dislike/downvote or emoji) to an event
///
/// <https://github.com/nostr-protocol/nips/blob/master/25.md>
Expand Down
1 change: 1 addition & 0 deletions crates/nostr/src/event/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ kind_variants! {
Torrent => 2003, "Torrent", "<https://github.com/nostr-protocol/nips/blob/master/35.md>",
TorrentComment => 2004, "Torrent Comment", "<https://github.com/nostr-protocol/nips/blob/master/35.md>",
PeerToPeerOrder => 38383, "Peer-to-peer Order events", "<https://github.com/nostr-protocol/nips/blob/master/69.md>",
RequestToVanish => 62, "Request to Vanish", "<https://github.com/nostr-protocol/nips/blob/master/62.md>",
}

impl PartialEq for Kind {
Expand Down
18 changes: 18 additions & 0 deletions crates/nostr/src/event/tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,24 @@ impl Tag {
})
}

/// Relay url
///
/// JSON: `["relay", "<relay-url>"]`
#[inline]
pub fn relay(url: RelayUrl) -> Self {
Self::from_standardized_without_cell(TagStandard::Relay(url))
}

/// All relays
///
/// JSON: `["relay", "ALL_RELAYS"]`
///
/// <https://github.com/nostr-protocol/nips/blob/master/62.md>
#[inline]
pub fn all_relays() -> Self {
Self::from_standardized_without_cell(TagStandard::AllRelays)
}

/// Compose `["t", "<hashtag>"]` tag
#[inline]
pub fn hashtag<T>(hashtag: T) -> Self
Expand Down
34 changes: 32 additions & 2 deletions crates/nostr/src/event/tag/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ use crate::{
Alphabet, Event, ImageDimensions, JsonUtil, Kind, PublicKey, SingleLetterTag, Timestamp,
};

const ALL_RELAYS: &str = "ALL_RELAYS";

/// Standardized tag
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -135,6 +137,10 @@ pub enum TagStandard {
},
Relay(RelayUrl),
Relays(Vec<RelayUrl>),
/// All relays tag
///
/// <https://github.com/nostr-protocol/nips/blob/master/62.md>
AllRelays,
/// Proof of Work
///
/// <https://github.com/nostr-protocol/nips/blob/master/13.md>
Expand Down Expand Up @@ -374,7 +380,13 @@ impl TagStandard {
character: Alphabet::U,
uppercase: false,
}) => Ok(Self::AbsoluteURL(Url::parse(tag_1)?)),
TagKind::Relay => Ok(Self::Relay(RelayUrl::parse(tag_1)?)),
TagKind::Relay => {
if tag_1 == ALL_RELAYS {
Ok(Self::AllRelays)
} else {
Ok(Self::Relay(RelayUrl::parse(tag_1)?))
}
}
TagKind::Expiration => Ok(Self::Expiration(Timestamp::from_str(tag_1)?)),
TagKind::Subject => Ok(Self::Subject(tag_1.to_string())),
TagKind::Challenge => Ok(Self::Challenge(tag_1.to_string())),
Expand Down Expand Up @@ -568,7 +580,7 @@ impl TagStandard {
character: Alphabet::K,
uppercase: *uppercase,
}),
Self::Relay(..) => TagKind::Relay,
Self::Relay(..) | Self::AllRelays => TagKind::Relay,
Self::POW { .. } => TagKind::Nonce,
Self::Client { .. } => TagKind::Client,
Self::Delegation { .. } => TagKind::Delegation,
Expand Down Expand Up @@ -790,6 +802,7 @@ impl From<TagStandard> for Vec<String> {
}
TagStandard::Kind { kind, .. } => vec![tag_kind, kind.to_string()],
TagStandard::Relay(url) => vec![tag_kind, url.to_string()],
TagStandard::AllRelays => vec![tag_kind, ALL_RELAYS.to_string()],
TagStandard::POW { nonce, difficulty } => {
vec![tag_kind, nonce.to_string(), difficulty.to_string()]
}
Expand Down Expand Up @@ -1797,6 +1810,13 @@ mod tests {
.to_vec()
);

assert_eq!(
vec!["relay", "wss://relay.damus.io"],
TagStandard::Relay(RelayUrl::parse("wss://relay.damus.io").unwrap()).to_vec()
);

assert_eq!(vec!["relay", "ALL_RELAYS"], TagStandard::AllRelays.to_vec());

assert_eq!(
vec![
"delegation",
Expand Down Expand Up @@ -2395,6 +2415,16 @@ mod tests {
).unwrap(), conditions: Conditions::from_str("kind=1").unwrap(), sig: Signature::from_str("fd0954de564cae9923c2d8ee9ab2bf35bc19757f8e328a978958a2fcc950eaba0754148a203adec29b7b64080d0cf5a32bebedd768ea6eb421a6b751bb4584a8").unwrap() }
);

assert_eq!(
TagStandard::parse(&["relay", "wss://relay.damus.io"]).unwrap(),
TagStandard::Relay(RelayUrl::parse("wss://relay.damus.io").unwrap())
);

assert_eq!(
TagStandard::parse(&["relay", "ALL_RELAYS"]).unwrap(),
TagStandard::AllRelays
);

assert_eq!(
TagStandard::parse(&[
"relays",
Expand Down
1 change: 1 addition & 0 deletions crates/nostr/src/nips/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub mod nip57;
pub mod nip58;
#[cfg(feature = "nip59")]
pub mod nip59;
pub mod nip62;
pub mod nip65;
pub mod nip73;
pub mod nip90;
Expand Down
42 changes: 42 additions & 0 deletions crates/nostr/src/nips/nip62.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2022-2023 Yuki Kishimoto
// Copyright (c) 2023-2025 Rust Nostr Developers
// Distributed under the MIT software license

//! NIP-62: Request to Vanish
//!
//! https://github.com/nostr-protocol/nips/blob/master/62.md
use alloc::vec::Vec;

use crate::RelayUrl;

/// Request to Vanish target
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VanishTarget {
/// Request to vanish from all relays
AllRelays,
/// Request to vanish from a specific list of relays.
Relays(Vec<RelayUrl>),
}

impl VanishTarget {
/// Vanish from a single relay
#[inline]
pub fn relay(relay: RelayUrl) -> Self {
Self::Relays(vec![relay])
}

/// Vanish from multiple relays
#[inline]
pub fn relays<I>(relays: I) -> Self
where
I: IntoIterator<Item = RelayUrl>,
{
Self::Relays(relays.into_iter().collect())
}

/// Vanish from all relays
pub fn all_relays() -> Self {
Self::AllRelays
}
}
1 change: 1 addition & 0 deletions crates/nostr/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub use crate::nips::nip57::{self, *};
pub use crate::nips::nip58;
#[cfg(feature = "nip59")]
pub use crate::nips::nip59::{self, *};
pub use crate::nips::nip62::{self, *};
pub use crate::nips::nip65::{self, *};
pub use crate::nips::nip90::{self, *};
pub use crate::nips::nip94::{self, *};
Expand Down

0 comments on commit 6b1323c

Please sign in to comment.