Skip to content

Commit

Permalink
Implement gift wrap
Browse files Browse the repository at this point in the history
  • Loading branch information
grunch committed Sep 11, 2024
1 parent 41f182c commit fa99828
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod lnurl;
pub mod messages;
pub mod models;
pub mod nip33;
pub mod nip59;
pub mod scheduler;
pub mod util;

Expand Down
96 changes: 96 additions & 0 deletions src/nip59.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use base64::engine::{general_purpose, Engine};
use nip44::v2::{decrypt_to_bytes, encrypt_to_bytes, ConversationKey};
use nostr_sdk::event::builder::Error as BuilderError;
use nostr_sdk::prelude::*;

/// Creates a new nip59 event
///
/// # Arguments
///
/// * `sender_keys` - The keys of the sender
/// * `receiver` - The public key of the receiver
/// * `rumor` - A regular nostr event, but is not signed.
/// * `expiration` - Time of the expiration of the event
///
/// # Returns
/// Returns a gift wrap event
///
pub fn gift_wrap(
sender_keys: &Keys,
receiver: PublicKey,
content: String,
expiration: Option<Timestamp>,
) -> Result<Event, BuilderError> {
let rumor: UnsignedEvent = EventBuilder::text_note(content, []).to_unsigned_event(receiver);
let seal: Event = seal(sender_keys, &receiver, rumor)?.to_event(sender_keys)?;

gift_wrap_from_seal(sender_keys, &receiver, &seal, expiration)
}

pub fn seal(
sender_keys: &Keys,
receiver_pubkey: &PublicKey,
rumor: UnsignedEvent,
) -> Result<EventBuilder, BuilderError> {
let sender_private_key = sender_keys.secret_key()?;

// Derive conversation key
let ck = ConversationKey::derive(sender_private_key, receiver_pubkey);
// Encrypt content
let encrypted_content = encrypt_to_bytes(&ck, rumor.as_json()).unwrap();
// Encode with base64
let b64decoded_content = general_purpose::STANDARD.encode(encrypted_content);
// Compose builder
Ok(EventBuilder::new(Kind::Seal, b64decoded_content, [])
.custom_created_at(Timestamp::tweaked(nip59::RANGE_RANDOM_TIMESTAMP_TWEAK)))
}

pub fn gift_wrap_from_seal(
sender_keys: &Keys,
receiver: &PublicKey,
seal: &Event,
expiration: Option<Timestamp>,
) -> Result<Event, BuilderError> {
let ephemeral_keys: Keys = Keys::generate();
// Derive conversation key
let ck = ConversationKey::derive(sender_keys.secret_key()?, receiver);
// Encrypt content
let encrypted_content = encrypt_to_bytes(&ck, seal.as_json()).unwrap();

let mut tags: Vec<Tag> = Vec::with_capacity(1 + usize::from(expiration.is_some()));
tags.push(Tag::public_key(*receiver));

if let Some(timestamp) = expiration {
tags.push(Tag::expiration(timestamp));
}
// Encode with base64
let b64decoded_content = general_purpose::STANDARD.encode(encrypted_content);
EventBuilder::new(Kind::GiftWrap, b64decoded_content, tags)
.custom_created_at(Timestamp::tweaked(nip59::RANGE_RANDOM_TIMESTAMP_TWEAK))
.to_event(&ephemeral_keys)
}

pub fn unwrap_gift_wrap(keys: &Keys, gift_wrap: &Event) -> Result<UnwrappedGift, BuilderError> {
let ck = ConversationKey::derive(keys.secret_key()?, &gift_wrap.pubkey);
let b64decoded_content = general_purpose::STANDARD
.decode(gift_wrap.content.as_bytes())
.unwrap();
// Decrypt and verify seal
let seal = decrypt_to_bytes(&ck, b64decoded_content)?;
let seal = String::from_utf8(seal).expect("Found invalid UTF-8");
let seal: Event = Event::from_json(seal).unwrap();
seal.verify().unwrap();

let ck = ConversationKey::derive(keys.secret_key()?, &seal.pubkey);
let b64decoded_content = general_purpose::STANDARD
.decode(seal.content.as_bytes())
.unwrap();
// Decrypt rumor
let rumor = decrypt_to_bytes(&ck, b64decoded_content)?;
let rumor = String::from_utf8(rumor).expect("Found invalid UTF-8");

Ok(UnwrappedGift {
sender: seal.pubkey,
rumor: UnsignedEvent::from_json(rumor)?,
})
}
5 changes: 2 additions & 3 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::lightning::LndConnector;
use crate::messages;
use crate::models::Yadio;
use crate::nip33::{new_event, order_to_tags};
use crate::nip59::gift_wrap;
use crate::NOSTR_CLIENT;

use anyhow::{Context, Error, Result};
Expand Down Expand Up @@ -243,9 +244,7 @@ pub async fn send_dm(receiver_pubkey: &PublicKey, content: String) -> Result<()>
info!("DM content: {content:#?}");
// Get mostro keys
let sender_keys = crate::util::get_keys().unwrap();

let event = EventBuilder::encrypted_direct_msg(&sender_keys, *receiver_pubkey, content, None)?
.to_event(&sender_keys)?;
let event = gift_wrap(&sender_keys, *receiver_pubkey, content, None)?;
info!("Sending event: {event:#?}");
NOSTR_CLIENT.get().unwrap().send_event(event).await?;

Expand Down

0 comments on commit fa99828

Please sign in to comment.