Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/nostr/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
- Add `MachineReadablePrefix::Custom` variant (https://github.com/rust-nostr/nostr/pull/1258)
- Add `RelayMetadata::{is_read, is_write}` functions (https://github.com/rust-nostr/nostr/pull/1290)
- Add the kind number in the kind doc (https://github.com/rust-nostr/nostr/pull/1293)
- Add `EventBuilder::private_file_msg` and `EventBuilder::file_msg`
- Add `Kind::FileMessage`
- Add `TagStandard::FileType`, `TagStandard::EncryptionAlgorithm`, `TagStandard::DecryptionKey` and `TagStandard::DecryptionNonce`

### Removed

Expand Down
24 changes: 24 additions & 0 deletions crates/nostr/src/event/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,30 @@ impl EventBuilder {
Self::gift_wrap(signer, &receiver, rumor, []).await
}

/// Private Direct file message
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
#[inline]
#[cfg(all(feature = "std", feature = "os-rng", feature = "nip59"))]
pub async fn private_file_msg<T, I>(
signer: &T,
receiver: PublicKey,
encrypted_file: EncryptedFile,
rumor_extra_tags: I,
) -> Result<Event, Error>
where
T: NostrSigner,
I: IntoIterator<Item = Tag>,
{
let public_key: PublicKey = signer.get_public_key().await?;
let rumor = encrypted_file
.to_event_builder()?
.tag(Tag::public_key(receiver))
.tags(rumor_extra_tags)
.build(public_key);
Self::gift_wrap(signer, &receiver, rumor, []).await
}

/// Mute list
///
/// <https://github.com/nostr-protocol/nips/blob/master/51.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 @@ -138,6 +138,7 @@ kind_variants! {
Seal => 13, "Seal", "<https://github.com/nostr-protocol/nips/blob/master/59.md>",
GiftWrap => 1059, "Gift Wrap", "<https://github.com/nostr-protocol/nips/blob/master/59.md>",
PrivateDirectMessage => 14, "Private Direct message", "<https://github.com/nostr-protocol/nips/blob/master/17.md>",
FileMessage => 15, "File message", "<https://github.com/nostr-protocol/nips/blob/master/17.md>",
SetStall => 30017, "Set stall", "<https://github.com/nostr-protocol/nips/blob/master/15.md>",
SetProduct => 30018, "Set product", "<https://github.com/nostr-protocol/nips/blob/master/15.md>",
JobFeedback => 7000, "Job Feedback", "<https://github.com/nostr-protocol/nips/blob/master/90.md>",
Expand Down
20 changes: 20 additions & 0 deletions crates/nostr/src/event/tag/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,24 @@ pub enum TagKind<'a> {
Dependency,
/// Description
Description,
/// Decryption key
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
DecryptionKey,
/// Decryption nonce
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
DecryptionNonce,
/// Size of the file in pixels
Dim,
/// Emoji
Emoji,
/// Encrypted
Encrypted,
/// Encryption algorithm
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
EncryptionAlgorithm,
/// Ends
Ends,
/// Expiration
Expand All @@ -81,6 +93,10 @@ pub enum TagKind<'a> {
Extension,
/// File
File,
/// File type
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
FileType,
/// Image
Image,
/// License of the shared content
Expand Down Expand Up @@ -333,13 +349,17 @@ impl<'a> TagKind<'a> {
Self::CurrentParticipants => "current_participants",
Self::Dependency => "dep",
Self::Description => "description",
Self::DecryptionKey => "decryption-key",
Self::DecryptionNonce => "decryption-nonce",
Self::Dim => "dim",
Self::Emoji => "emoji",
Self::Encrypted => "encrypted",
Self::EncryptionAlgorithm => "encryption-algorithm",
Self::Ends => "ends",
Self::Expiration => "expiration",
Self::Extension => "extension",
Self::File => "file",
Self::FileType => "file-type",
Self::Head => "HEAD",
Self::Image => "image",
Self::License => "license",
Expand Down
29 changes: 29 additions & 0 deletions crates/nostr/src/event/tag/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,22 @@ pub enum TagStandard {
/// List of web URLs
Web(Vec<Url>),
Word(String),
/// File type
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
FileType(String),
/// Encryption algorithm. Note: NIP17 specifies that the encryption algorithm is aes-gcm
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
EncryptionAlgorithm,
/// Decryption key
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
DecryptionKey(String),
/// Decryption nonce
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
DecryptionNonce(u128),
}

impl TagStandard {
Expand Down Expand Up @@ -750,6 +766,10 @@ impl TagStandard {
Self::Protected => TagKind::Protected,
Self::Alt(..) => TagKind::Alt,
Self::Web(..) => TagKind::Web,
Self::FileType(..) => TagKind::FileType,
Self::EncryptionAlgorithm => TagKind::EncryptionAlgorithm,
Self::DecryptionKey(..) => TagKind::DecryptionKey,
Self::DecryptionNonce(..) => TagKind::DecryptionNonce,
}
}

Expand Down Expand Up @@ -1083,6 +1103,15 @@ impl From<TagStandard> for Vec<String> {
tag.extend(urls.into_iter().map(|url| url.to_string()));
tag
}
TagStandard::FileType(mime) => vec![tag_kind, mime],
TagStandard::EncryptionAlgorithm => {
// NIP17 specifies that the encryption algorithm is aes-gcm
//
// <https://github.com/nostr-protocol/nips/blob/master/17.md>
vec![tag_kind, "aes-gcm".to_string()]
}
TagStandard::DecryptionKey(key) => vec![tag_kind, key],
TagStandard::DecryptionNonce(nonce) => vec![tag_kind, nonce.to_string()],
};

// Tag can't be empty, require at least 1 value
Expand Down
60 changes: 59 additions & 1 deletion crates/nostr/src/nips/nip17.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,65 @@
//!
//! <https://github.com/nostr-protocol/nips/blob/master/17.md>

use crate::{Event, RelayUrl, TagStandard};
#![allow(clippy::wrong_self_convention)]

use url::Url;

use crate::event::builder::{Error, EventBuilder};
use crate::{Event, Kind, RelayUrl, Tag, TagStandard};

/// Encrypted file
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EncryptedFile {
/// URL of the encrypted file
pub url: Url,
/// File type (e.g. "image/png", "application/pdf")
pub file_type: String,
/// Decryption key
pub decryption_key: String,
/// Decryption nonce
pub decryption_nonce: u128,
/// SHA256 hash of the file
pub hash: hashes::sha256::Hash,
}

impl EncryptedFile {
pub(crate) fn to_event_builder(self) -> Result<EventBuilder, Error> {
let mut tags: Vec<Tag> = Vec::with_capacity(5);

// Add file type
if !self.file_type.is_empty() {
tags.push(Tag::from_standardized_without_cell(TagStandard::FileType(
self.file_type,
)));
}

// Add decryption key
if !self.decryption_key.is_empty() {
tags.push(Tag::from_standardized_without_cell(
TagStandard::DecryptionKey(self.decryption_key),
));
}

// Add decryption nonce
tags.push(Tag::from_standardized_without_cell(
TagStandard::DecryptionNonce(self.decryption_nonce),
));

// Add hash
tags.push(Tag::from_standardized_without_cell(TagStandard::Sha256(
self.hash,
)));

// Add encryption algorithm
tags.push(Tag::from_standardized_without_cell(
TagStandard::EncryptionAlgorithm,
));

// Build
Ok(EventBuilder::new(Kind::FileMessage, self.url).tags(tags))
}
}

/// Extracts the relay list
///
Expand Down