use alloc::string::String;
use core::fmt;
use core::ops::Range;
use secp256k1::{Secp256k1, Verification};
use crate::event::unsigned::UnsignedEvent;
use crate::event::{self, Event};
use crate::signer::SignerError;
#[cfg(feature = "std")]
use crate::{EventBuilder, Timestamp, SECP256K1};
use crate::{JsonUtil, Kind, NostrSigner, PublicKey};
pub const RANGE_RANDOM_TIMESTAMP_TWEAK: Range<u64> = 0..172800;
#[derive(Debug, PartialEq)]
pub enum Error {
Signer(SignerError),
Event(event::Error),
NotGiftWrap,
SenderMismatch,
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Signer(e) => e.fmt(f),
Self::Event(e) => e.fmt(f),
Self::NotGiftWrap => f.write_str("Not a Gift Wrap"),
Self::SenderMismatch => f.write_str("sender public key mismatch"),
}
}
}
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 async fn from_gift_wrap<T>(signer: &T, gift_wrap: &Event) -> Result<Self, Error>
where
T: NostrSigner,
{
Self::from_gift_wrap_with_ctx(SECP256K1, signer, gift_wrap).await
}
pub async fn from_gift_wrap_with_ctx<C, T>(
secp: &Secp256k1<C>,
signer: &T,
gift_wrap: &Event,
) -> Result<Self, Error>
where
C: Verification,
T: NostrSigner,
{
if gift_wrap.kind != Kind::GiftWrap {
return Err(Error::NotGiftWrap);
}
let seal: String = signer
.nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content)
.await?;
let seal: Event = Event::from_json(seal)?;
seal.verify_with_ctx(secp)?;
let rumor: String = signer.nip44_decrypt(&seal.pubkey, &seal.content).await?;
let rumor: UnsignedEvent = UnsignedEvent::from_json(rumor)?;
if rumor.pubkey != seal.pubkey {
return Err(Error::SenderMismatch);
}
Ok(Self {
sender: seal.pubkey,
rumor,
})
}
}
#[inline]
#[cfg(feature = "std")]
pub async fn extract_rumor<T>(signer: &T, gift_wrap: &Event) -> Result<UnwrappedGift, Error>
where
T: NostrSigner,
{
UnwrappedGift::from_gift_wrap(signer, gift_wrap).await
}
#[cfg(feature = "std")]
pub async fn make_seal<T>(
signer: &T,
receiver_pubkey: &PublicKey,
mut rumor: UnsignedEvent, ) -> Result<EventBuilder, Error>
where
T: NostrSigner,
{
rumor.ensure_id();
let content: String = signer
.nip44_encrypt(receiver_pubkey, &rumor.as_json())
.await?;
Ok(EventBuilder::new(Kind::Seal, content)
.custom_created_at(Timestamp::tweaked(RANGE_RANDOM_TIMESTAMP_TWEAK)))
}
#[cfg(feature = "std")]
#[cfg(test)]
mod tests {
use super::*;
use crate::{EventBuilder, Keys};
#[tokio::test]
async 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").build(sender_keys.public_key);
let event: Event =
EventBuilder::gift_wrap(&sender_keys, &receiver_keys.public_key(), rumor.clone(), [])
.await
.unwrap();
let unwrapped = extract_rumor(&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");
assert!(unwrapped.rumor.tags.is_empty());
assert!(extract_rumor(&sender_keys, &event).await.is_err());
let event: Event = EventBuilder::text_note("")
.sign(&sender_keys)
.await
.unwrap();
assert!(matches!(
extract_rumor(&receiver_keys, &event).await.unwrap_err(),
Error::NotGiftWrap
));
}
#[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").build(impersonated_keys.public_key());
let gift_wrap: Event =
EventBuilder::gift_wrap(&sender_keys, &receiver_keys.public_key(), rumor, [])
.await
.unwrap();
match extract_rumor(&receiver_keys, &gift_wrap).await {
Err(Error::SenderMismatch) => {}
other => panic!("expected SenderMismatch, got {other:?}"),
}
}
}