use alloc::boxed::Box;
use alloc::string::String;
use core::num::NonZeroU8;
use secp256k1::schnorr::Signature;
use secp256k1::{Secp256k1, Verification};
use super::error::Error;
use super::{FinalizeEvent, FinalizeEventAsync};
#[cfg(feature = "std")]
use crate::SECP256K1;
use crate::nips::nip13::{AsyncPowAdapter, PowAdapter};
use crate::signer::{AsyncSignEvent, SignEvent, SignerError};
use crate::util::BoxedFuture;
use crate::{Event, EventId, JsonUtil, Kind, PublicKey, Tag, Tags, Timestamp};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct UnsignedEvent {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<EventId>,
pub pubkey: PublicKey,
pub created_at: Timestamp,
pub kind: Kind,
pub tags: Tags,
pub content: String,
}
impl UnsignedEvent {
pub fn new<I, S>(
public_key: PublicKey,
created_at: Timestamp,
kind: Kind,
tags: I,
content: S,
) -> Self
where
I: IntoIterator<Item = Tag>,
S: Into<String>,
{
Self {
id: None,
pubkey: public_key,
created_at,
kind,
tags: Tags::from_list(tags.into_iter().collect()),
content: content.into(),
}
}
#[inline]
pub fn ensure_id(&mut self) {
if self.id.is_none() {
self.id = Some(self.compute_id());
}
}
pub fn id(&mut self) -> EventId {
self.ensure_id();
self.id.unwrap()
}
#[inline]
pub fn compute_id(&self) -> EventId {
EventId::new(
&self.pubkey,
&self.created_at,
&self.kind,
&self.tags,
&self.content,
)
}
pub fn verify_id(&self) -> Result<(), Error> {
if let Some(id) = self.id {
let computed_id: EventId = self.compute_id();
if id != computed_id {
return Err(Error::InvalidId);
}
}
Ok(())
}
#[inline]
pub fn mine<T>(self, adapter: &T, difficulty: NonZeroU8) -> Result<Self, T::Error>
where
T: PowAdapter,
{
adapter.compute(self, difficulty)
}
#[inline]
pub async fn mine_async<T>(self, adapter: &T, difficulty: NonZeroU8) -> Result<Self, T::Error>
where
T: AsyncPowAdapter,
{
adapter.compute_async(self, difficulty).await
}
#[inline]
#[cfg(feature = "std")]
pub fn add_signature(self, sig: Signature) -> Result<Event, Error> {
self.add_signature_with_ctx(&SECP256K1, sig)
}
pub fn add_signature_with_ctx<C>(
self,
secp: &Secp256k1<C>,
sig: Signature,
) -> Result<Event, Error>
where
C: Verification,
{
let (id, verify_id): (EventId, bool) = match self.id {
Some(id) => (id, true),
None => (self.compute_id(), false),
};
let event: Event = Event::new(
id,
self.pubkey,
self.created_at,
self.kind,
self.tags,
self.content,
sig,
);
if verify_id && !event.verify_id() {
return Err(Error::InvalidId);
}
if !event.verify_signature_with_ctx(secp) {
return Err(Error::InvalidSignature);
}
Ok(event)
}
}
impl<S> FinalizeEvent<S> for UnsignedEvent
where
S: SignEvent + ?Sized,
{
type Error = SignerError;
#[inline]
fn finalize(self, signer: &S) -> Result<Event, Self::Error> {
signer.sign_event(self)
}
}
impl<S> FinalizeEventAsync<S> for UnsignedEvent
where
S: AsyncSignEvent + ?Sized,
{
type Error = SignerError;
#[inline]
fn finalize_async<'a>(self, signer: &'a S) -> BoxedFuture<'a, Result<Event, Self::Error>>
where
Self: 'a,
S: 'a,
{
Box::pin(async move { signer.sign_event_async(self).await })
}
}
impl JsonUtil for UnsignedEvent {
type Err = Error;
}
impl From<Event> for UnsignedEvent {
fn from(event: Event) -> Self {
Self {
id: Some(event.id),
pubkey: event.pubkey,
created_at: event.created_at,
kind: event.kind,
tags: event.tags,
content: event.content,
}
}
}
pub trait FinalizeUnsignedEvent: Sized {
fn finalize_unsigned(self, public_key: PublicKey) -> UnsignedEvent;
}
pub trait FinalizeUnsignedEventAsync: Sized {
fn finalize_unsigned_async<'a>(self, public_key: PublicKey) -> BoxedFuture<'a, UnsignedEvent>
where
Self: 'a;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_id() {
let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
let mut unsigned = UnsignedEvent::from_json(json).unwrap();
let expected_id: EventId =
EventId::from_hex("2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45")
.unwrap();
assert!(unsigned.id.is_none());
assert_eq!(unsigned.id(), expected_id);
}
#[test]
fn test_deserialize_unsigned_event_with_id() {
let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
let event_id: EventId =
EventId::from_hex("2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45")
.unwrap();
let unsigned = UnsignedEvent::from_json(json).unwrap();
assert_eq!(unsigned.id, Some(event_id));
assert_eq!(
unsigned.content,
"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA=="
);
assert_eq!(unsigned.kind, Kind::EncryptedDirectMessage);
}
#[test]
fn test_deserialize_unsigned_event_without_id() {
let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
let unsigned = UnsignedEvent::from_json(json).unwrap();
assert_eq!(unsigned.id, None);
assert_eq!(
unsigned.content,
"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA=="
);
assert_eq!(unsigned.kind, Kind::EncryptedDirectMessage);
}
}