#[cfg(all(feature = "std", feature = "os-rng"))]
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
#[cfg(all(feature = "std", feature = "os-rng"))]
use core::ops::Range;
use secp256k1::{Secp256k1, Verification};
#[cfg(feature = "std")]
use crate::SECP256K1;
use crate::event::unsigned::UnsignedEvent;
use crate::event::{self, Event};
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::event::{FinalizeEvent, FinalizeEventAsync};
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::key::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::signer::{AsyncGetPublicKey, AsyncSignEvent, GetPublicKey, SignEvent, SignerError};
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::util::BoxedFuture;
#[cfg(all(feature = "std", feature = "os-rng"))]
use crate::{EventBuilder, Timestamp};
use crate::{JsonUtil, Kind, PublicKey, Tag};
#[cfg(all(feature = "std", feature = "os-rng"))]
const RANGE_RANDOM_TIMESTAMP_TWEAK: Range<u64> = 0..172800;
#[derive(Debug, PartialEq)]
pub enum Error {
#[cfg(all(feature = "std", feature = "os-rng"))]
Signer(SignerError),
Event(event::Error),
NIP44(String),
NotGiftWrap,
SenderMismatch,
}
impl core::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(all(feature = "std", feature = "os-rng"))]
Self::Signer(e) => e.fmt(f),
Self::Event(e) => e.fmt(f),
Self::NIP44(e) => e.fmt(f),
Self::NotGiftWrap => f.write_str("Not a Gift Wrap"),
Self::SenderMismatch => f.write_str("sender public key mismatch"),
}
}
}
#[cfg(all(feature = "std", feature = "os-rng"))]
impl From<SignerError> for Error {
fn from(e: SignerError) -> Self {
Self::Signer(e)
}
}
impl From<event::Error> for Error {
fn from(e: event::Error) -> Self {
Self::Event(e)
}
}
#[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(Error::NotGiftWrap);
}
let seal: String = signer
.nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content)
.map_err(|e| Error::NIP44(e.to_string()))?;
let seal: Event = parse_and_verify_seal(secp, seal)?;
let rumor: String = signer
.nip44_decrypt(&seal.pubkey, &seal.content)
.map_err(|e| Error::NIP44(e.to_string()))?;
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(Error::NotGiftWrap);
}
let seal: String = signer
.nip44_decrypt_async(&gift_wrap.pubkey, &gift_wrap.content)
.await
.map_err(|e| Error::NIP44(e.to_string()))?;
let seal: Event = parse_and_verify_seal(secp, seal)?;
let rumor: String = signer
.nip44_decrypt_async(&seal.pubkey, &seal.content)
.await
.map_err(|e| Error::NIP44(e.to_string()))?;
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(|e| Error::NIP44(e.to_string()))?;
let seal: EventBuilder = build_seal(self.rumor, content);
Ok(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(|e| Error::NIP44(e.to_string()))?;
let seal: EventBuilder = build_seal(self.rumor, content);
Ok(seal.finalize_async(signer).await?)
})
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct GiftWrapBuilder {
pub receiver: PublicKey,
pub rumor: UnsignedEvent,
pub extra_tags: Vec<Tag>,
}
impl GiftWrapBuilder {
#[inline]
pub fn new(receiver: PublicKey, rumor: UnsignedEvent) -> Self {
Self {
receiver,
rumor,
extra_tags: Vec::new(),
}
}
#[inline]
pub fn extra_tags<T>(mut self, tags: T) -> Self
where
T: IntoIterator<Item = Tag>,
{
self.extra_tags.extend(tags);
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)
}
}
#[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)
})
}
}
#[cfg(all(feature = "std", feature = "os-rng"))]
fn make_gift_wrap(seal: Event, receiver: PublicKey, extra_tags: Vec<Tag>) -> 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(|e| Error::NIP44(e.to_string()))?;
let mut tags: Vec<Tag> = extra_tags;
tags.push(Tag::public_key(receiver));
Ok(EventBuilder::new(Kind::GiftWrap, content)
.tags(tags)
.custom_created_at(Timestamp::tweaked(RANGE_RANDOM_TIMESTAMP_TWEAK))
.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(Error::SenderMismatch);
}
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::{Error, *};
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!(matches!(
extract_rumor(&receiver_keys, &event).unwrap_err(),
Error::NotGiftWrap
));
}
#[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();
match extract_rumor(&receiver_keys, &gift_wrap) {
Err(Error::SenderMismatch) => {}
other => panic!("expected SenderMismatch, got {other:?}"),
}
}
}