#[cfg(all(feature = "std", feature = "os-rng"))]
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
#[cfg(all(feature = "std", feature = "os-rng"))]
use core::ops::Range;
use core::time::Duration;
use secp256k1::{Secp256k1, Verification};
#[cfg(feature = "std")]
use crate::SECP256K1;
use crate::error::{Error, ErrorKind};
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::event::{AsyncSignEvent, FinalizeEvent, FinalizeEventAsync, SignEvent};
use crate::event::{Event, UnsignedEvent};
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::key::{AsyncGetPublicKey, GetPublicKey, Keys};
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::nips::nip44;
use crate::nips::nip44::{AsyncNip44, Nip44};
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::util::BoxedFuture;
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::{EventBuilder, Timestamp};
use crate::{Kind, PublicKey, Tag};
#[cfg(all(feature = "std", feature = "os-rng"))]
const RANGE_RANDOM_TIMESTAMP_TWEAK: Range<u64> = 0..172800;
#[inline]
fn not_gift_wrap() -> Error {
Error::with_static_message(ErrorKind::Invalid, "not a gift wrap")
}
#[inline]
fn sender_mismatch() -> Error {
Error::with_static_message(ErrorKind::Invalid, "sender mismatch")
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnwrappedGift {
pub sender: PublicKey,
pub rumor: UnsignedEvent,
}
impl UnwrappedGift {
#[inline]
#[cfg(feature = "std")]
pub fn from_gift_wrap<T>(signer: &T, gift_wrap: &Event) -> Result<Self, Error>
where
T: Nip44,
{
Self::from_gift_wrap_with_ctx(&SECP256K1, signer, gift_wrap)
}
#[inline]
#[cfg(feature = "std")]
pub async fn from_gift_wrap_async<T>(signer: &T, gift_wrap: &Event) -> Result<Self, Error>
where
T: AsyncNip44,
{
Self::from_gift_wrap_with_ctx_async(&SECP256K1, signer, gift_wrap).await
}
pub fn from_gift_wrap_with_ctx<C, T>(
secp: &Secp256k1<C>,
signer: &T,
gift_wrap: &Event,
) -> Result<Self, Error>
where
C: Verification,
T: Nip44,
{
if gift_wrap.kind != Kind::GiftWrap {
return Err(not_gift_wrap());
}
let seal: String = signer
.nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content)
.map_err(Error::crypto)?;
let seal: Event = parse_and_verify_seal(secp, seal)?;
let rumor: String = signer
.nip44_decrypt(&seal.pubkey, &seal.content)
.map_err(Error::crypto)?;
parse_unwrapped_gift(seal, rumor)
}
pub async fn from_gift_wrap_with_ctx_async<C, T>(
secp: &Secp256k1<C>,
signer: &T,
gift_wrap: &Event,
) -> Result<Self, Error>
where
C: Verification,
T: AsyncNip44,
{
if gift_wrap.kind != Kind::GiftWrap {
return Err(not_gift_wrap());
}
let seal: String = signer
.nip44_decrypt_async(&gift_wrap.pubkey, &gift_wrap.content)
.await
.map_err(Error::crypto)?;
let seal: Event = parse_and_verify_seal(secp, seal)?;
let rumor: String = signer
.nip44_decrypt_async(&seal.pubkey, &seal.content)
.await
.map_err(Error::crypto)?;
parse_unwrapped_gift(seal, rumor)
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct GiftWrapSealBuilder {
pub rumor: UnsignedEvent,
pub receiver: PublicKey,
}
impl GiftWrapSealBuilder {
#[inline]
pub fn new(rumor: UnsignedEvent, receiver: PublicKey) -> Self {
Self { rumor, receiver }
}
}
#[cfg(all(feature = "std", feature = "os-rng"))]
impl<S> FinalizeEvent<S> for GiftWrapSealBuilder
where
S: GetPublicKey + SignEvent + Nip44,
{
type Error = Error;
fn finalize(self, signer: &S) -> Result<Event, Self::Error> {
let content: String = signer
.nip44_encrypt(&self.receiver, &self.rumor.as_json())
.map_err(Error::crypto)?;
let seal: EventBuilder = build_seal(self.rumor, content);
seal.finalize(signer)
}
}
#[cfg(all(feature = "std", feature = "os-rng"))]
impl<S> FinalizeEventAsync<S> for GiftWrapSealBuilder
where
S: AsyncGetPublicKey + AsyncSignEvent + AsyncNip44,
{
type Error = Error;
fn finalize_async<'a>(self, signer: &'a S) -> BoxedFuture<'a, Result<Event, Self::Error>>
where
Self: 'a,
S: 'a,
{
Box::pin(async move {
let content: String = signer
.nip44_encrypt_async(&self.receiver, &self.rumor.as_json())
.await
.map_err(Error::crypto)?;
let seal: EventBuilder = build_seal(self.rumor, content);
seal.finalize_async(signer).await
})
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct GiftWrapBuilder {
pub receiver: PublicKey,
pub rumor: UnsignedEvent,
pub extra_tags: Vec<Tag>,
pub expiration: Option<Duration>,
}
impl GiftWrapBuilder {
#[inline]
pub fn new(receiver: PublicKey, rumor: UnsignedEvent) -> Self {
Self {
receiver,
rumor,
extra_tags: Vec::new(),
expiration: None,
}
}
#[inline]
pub fn extra_tags<T>(mut self, tags: T) -> Self
where
T: IntoIterator<Item = Tag>,
{
self.extra_tags.extend(tags);
self
}
#[inline]
pub fn expiration(mut self, duration: Duration) -> Self {
self.expiration = Some(duration);
self
}
}
#[cfg(all(feature = "std", feature = "os-rng"))]
impl<S> FinalizeEvent<S> for GiftWrapBuilder
where
S: GetPublicKey + SignEvent + Nip44,
{
type Error = Error;
fn finalize(self, signer: &S) -> Result<Event, Self::Error> {
let seal: Event = GiftWrapSealBuilder::new(self.rumor, self.receiver).finalize(signer)?;
make_gift_wrap(seal, self.receiver, self.extra_tags, self.expiration)
}
}
#[cfg(all(feature = "std", feature = "os-rng"))]
impl<S> FinalizeEventAsync<S> for GiftWrapBuilder
where
S: AsyncGetPublicKey + AsyncSignEvent + AsyncNip44,
{
type Error = Error;
fn finalize_async<'a>(self, signer: &'a S) -> BoxedFuture<'a, Result<Event, Self::Error>>
where
Self: 'a,
S: 'a,
{
Box::pin(async move {
let seal: Event = GiftWrapSealBuilder::new(self.rumor, self.receiver)
.finalize_async(signer)
.await?;
make_gift_wrap(seal, self.receiver, self.extra_tags, self.expiration)
})
}
}
#[cfg(all(feature = "std", feature = "os-rng"))]
fn make_gift_wrap(
seal: Event,
receiver: PublicKey,
extra_tags: Vec<Tag>,
expiration: Option<Duration>,
) -> Result<Event, Error> {
let keys: Keys = Keys::generate();
let content: String = nip44::encrypt(
keys.secret_key(),
&receiver,
seal.as_json(),
nip44::Version::default(),
)
.map_err(Error::crypto)?;
let mut tags: Vec<Tag> = extra_tags;
tags.push(Tag::public_key(receiver));
let created_at: Timestamp = Timestamp::tweaked(RANGE_RANDOM_TIMESTAMP_TWEAK);
if let Some(duration) = expiration {
tags.push(Tag::expiration(created_at + duration));
}
EventBuilder::new(Kind::GiftWrap, content)
.tags(tags)
.custom_created_at(created_at)
.finalize(&keys)
}
fn parse_and_verify_seal<C>(secp: &Secp256k1<C>, seal: String) -> Result<Event, Error>
where
C: Verification,
{
let seal: Event = Event::from_json(seal)?;
seal.verify_with_ctx(secp)?;
Ok(seal)
}
fn parse_unwrapped_gift(seal: Event, rumor: String) -> Result<UnwrappedGift, Error> {
let rumor: UnsignedEvent = UnsignedEvent::from_json(rumor)?;
if rumor.pubkey != seal.pubkey {
return Err(sender_mismatch());
}
Ok(UnwrappedGift {
sender: seal.pubkey,
rumor,
})
}
#[inline]
#[cfg(feature = "std")]
pub fn extract_rumor<T>(signer: &T, gift_wrap: &Event) -> Result<UnwrappedGift, Error>
where
T: Nip44,
{
UnwrappedGift::from_gift_wrap(signer, gift_wrap)
}
#[inline]
#[cfg(feature = "std")]
pub async fn extract_rumor_async<T>(signer: &T, gift_wrap: &Event) -> Result<UnwrappedGift, Error>
where
T: AsyncNip44,
{
UnwrappedGift::from_gift_wrap_async(signer, gift_wrap).await
}
#[cfg(all(feature = "std", feature = "os-rng"))]
fn build_seal(mut rumor: UnsignedEvent, content: String) -> EventBuilder {
rumor.ensure_id();
EventBuilder::new(Kind::Seal, content)
.custom_created_at(Timestamp::tweaked(RANGE_RANDOM_TIMESTAMP_TWEAK))
}
#[cfg(test)]
#[cfg(all(feature = "std", feature = "os-rng"))]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_extract_rumor() {
let sender_keys =
Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let receiver_keys =
Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let rumor: UnsignedEvent =
EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key);
let event: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor.clone())
.finalize(&sender_keys)
.unwrap();
let unwrapped = extract_rumor(&receiver_keys, &event).unwrap();
assert_eq!(unwrapped.sender, sender_keys.public_key());
assert_eq!(unwrapped.rumor.kind, Kind::TextNote);
assert_eq!(unwrapped.rumor.content, "Test");
assert!(unwrapped.rumor.tags.is_empty());
assert!(extract_rumor(&sender_keys, &event).is_err());
let event: Event = EventBuilder::text_note("").finalize(&sender_keys).unwrap();
assert_eq!(
extract_rumor(&receiver_keys, &event).unwrap_err().kind(),
not_gift_wrap().kind()
);
}
#[tokio::test]
async fn test_extract_rumor_async() {
let sender_keys =
Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let receiver_keys =
Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let rumor: UnsignedEvent =
EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key);
let event: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor)
.finalize(&sender_keys)
.unwrap();
let unwrapped = extract_rumor_async(&receiver_keys, &event).await.unwrap();
assert_eq!(unwrapped.sender, sender_keys.public_key());
assert_eq!(unwrapped.rumor.kind, Kind::TextNote);
assert_eq!(unwrapped.rumor.content, "Test");
}
#[test]
fn test_make_seal() {
let sender_keys =
Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let receiver_keys =
Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let rumor: UnsignedEvent =
EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key);
let seal = GiftWrapSealBuilder::new(rumor, receiver_keys.public_key())
.finalize(&sender_keys)
.unwrap();
assert_eq!(seal.kind, Kind::Seal);
assert!(!seal.content.is_empty());
}
#[tokio::test]
async fn test_make_seal_async() {
let sender_keys =
Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let receiver_keys =
Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let rumor: UnsignedEvent =
EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key);
let seal = GiftWrapSealBuilder::new(rumor, receiver_keys.public_key())
.finalize_async(&sender_keys)
.await
.unwrap();
assert_eq!(seal.kind, Kind::Seal);
assert!(!seal.content.is_empty());
}
#[tokio::test]
async fn test_sender_mismatch() {
let sender_keys =
Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let receiver_keys =
Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let impersonated_keys =
Keys::parse("5b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let rumor: UnsignedEvent =
EventBuilder::text_note("spoofed").finalize_unsigned(impersonated_keys.public_key());
let gift_wrap: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor)
.finalize(&sender_keys)
.unwrap();
assert_eq!(
extract_rumor(&receiver_keys, &gift_wrap)
.unwrap_err()
.kind(),
sender_mismatch().kind()
);
}
#[test]
fn test_gift_wrap_expiration_anchored_to_created_at() {
let sender_keys =
Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let receiver_keys =
Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let duration: Duration = Duration::from_secs(7 * 24 * 3600);
let rumor: UnsignedEvent =
EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key);
let event: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor)
.expiration(duration)
.finalize(&sender_keys)
.unwrap();
assert_eq!(event.kind, Kind::GiftWrap);
let expiration = event.tags.expiration().expect("missing expiration tag");
assert_eq!(
expiration.as_secs() - event.created_at.as_secs(),
duration.as_secs()
);
}
#[test]
fn test_gift_wrap_without_expiration() {
let sender_keys =
Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let receiver_keys =
Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let rumor: UnsignedEvent =
EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key);
let event: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor)
.finalize(&sender_keys)
.unwrap();
assert!(event.tags.expiration().is_none());
}
#[tokio::test]
async fn test_gift_wrap_expiration_anchored_to_created_at_async() {
let sender_keys =
Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let receiver_keys =
Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
.unwrap();
let duration: Duration = Duration::from_secs(7 * 24 * 3600);
let rumor: UnsignedEvent =
EventBuilder::text_note("Test").finalize_unsigned(sender_keys.public_key);
let event: Event = GiftWrapBuilder::new(receiver_keys.public_key(), rumor)
.expiration(duration)
.finalize_async(&sender_keys)
.await
.unwrap();
let expiration = event.tags.expiration().expect("missing expiration tag");
assert_eq!(
expiration.as_secs() - event.created_at.as_secs(),
duration.as_secs()
);
}
}