nostr_types/versioned/
event3.rs

1use super::TagV3;
2use crate::types::{
3    EventDelegation, EventKind, EventReference, FileMetadata, Id, KeySigner, MilliSatoshi,
4    NostrBech32, NostrUrl, ParsedTag, PrivateKey, PublicKey, RelayUrl, Signature, Signer, Unixtime,
5    ZapData,
6};
7use crate::{Error, IntoVec};
8use lightning_invoice::Bolt11Invoice;
9#[cfg(feature = "speedy")]
10use regex::Regex;
11use serde::{Deserialize, Serialize};
12#[cfg(feature = "speedy")]
13use speedy::{Readable, Writable};
14use std::cmp::Ordering;
15use std::str::FromStr;
16
17/// The main event type
18#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
19#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
20pub struct EventV3 {
21    /// The Id of the event, generated as a SHA256 of the inner event data
22    pub id: Id,
23
24    /// The public key of the actor who created the event
25    pub pubkey: PublicKey,
26
27    /// The (unverified) time at which the event was created
28    pub created_at: Unixtime,
29
30    /// The kind of event
31    pub kind: EventKind,
32
33    /// The signature of the event, which cryptographically verifies that the holder of
34    /// the PrivateKey matching the event's PublicKey generated (or authorized) this event.
35    /// The signature is taken over the id field only, but the id field is taken over
36    /// the rest of the event data.
37    pub sig: Signature,
38
39    /// The content of the event
40    pub content: String,
41
42    /// A set of tags that apply to the event
43    pub tags: Vec<TagV3>,
44}
45
46macro_rules! serialize_inner_event {
47    ($pubkey:expr, $created_at:expr, $kind:expr, $tags:expr,
48     $content:expr) => {{
49        format!(
50            "[0,{},{},{},{},{}]",
51            serde_json::to_string($pubkey)?,
52            serde_json::to_string($created_at)?,
53            serde_json::to_string($kind)?,
54            serde_json::to_string($tags)?,
55            serde_json::to_string($content)?
56        )
57    }};
58}
59
60/// Data used to construct an event
61#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
62#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
63pub struct PreEventV3 {
64    /// The public key of the actor who is creating the event
65    pub pubkey: PublicKey,
66    /// The time at which the event was created
67    pub created_at: Unixtime,
68    /// The kind of event
69    pub kind: EventKind,
70    /// A set of tags that apply to the event
71    pub tags: Vec<TagV3>,
72    /// The content of the event
73    pub content: String,
74}
75
76impl PreEventV3 {
77    /// Generate an ID from this PreEvent for use in an Event or a Rumor
78    pub fn hash(&self) -> Result<Id, Error> {
79        use secp256k1::hashes::Hash;
80
81        let serialized: String = serialize_inner_event!(
82            &self.pubkey,
83            &self.created_at,
84            &self.kind,
85            &self.tags,
86            &self.content
87        );
88
89        // Hash
90        let hash = secp256k1::hashes::sha256::Hash::hash(serialized.as_bytes());
91        let id: [u8; 32] = hash.to_byte_array();
92        Ok(Id(id))
93    }
94}
95
96/// A Rumor is an Event without a signature
97#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
98#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
99pub struct RumorV3 {
100    /// The Id of the event, generated as a SHA256 of the inner event data
101    pub id: Id,
102
103    /// The public key of the actor who created the event
104    pub pubkey: PublicKey,
105
106    /// The (unverified) time at which the event was created
107    pub created_at: Unixtime,
108
109    /// The kind of event
110    pub kind: EventKind,
111
112    /// The content of the event
113    pub content: String,
114
115    /// A set of tags that apply to the event
116    pub tags: Vec<TagV3>,
117}
118
119impl RumorV3 {
120    /// Create a new rumor
121    pub fn new(input: PreEventV3) -> Result<RumorV3, Error> {
122        // Generate Id
123        let id = input.hash()?;
124
125        Ok(RumorV3 {
126            id,
127            pubkey: input.pubkey,
128            created_at: input.created_at,
129            kind: input.kind,
130            tags: input.tags,
131            content: input.content,
132        })
133    }
134
135    /// Turn into an Event (the signature will be all zeroes)
136    pub fn into_event_with_bad_signature(self) -> EventV3 {
137        EventV3 {
138            id: self.id,
139            pubkey: self.pubkey,
140            created_at: self.created_at,
141            kind: self.kind,
142            sig: Signature::zeroes(),
143            content: self.content,
144            tags: self.tags,
145        }
146    }
147}
148
149impl EventV3 {
150    /// Check the validity of an event. This is useful if you deserialize an event
151    /// from the network. If you create an event using new() it should already be
152    /// trustworthy.
153    pub fn verify(&self, maxtime: Option<Unixtime>) -> Result<(), Error> {
154        use secp256k1::hashes::Hash;
155
156        let serialized: String = serialize_inner_event!(
157            &self.pubkey,
158            &self.created_at,
159            &self.kind,
160            &self.tags,
161            &self.content
162        );
163
164        // Verify the signature
165        self.pubkey.verify(serialized.as_bytes(), &self.sig)?;
166
167        // Also verify the ID is the SHA256
168        // (the above verify function also does it internally,
169        //  so there is room for improvement here)
170        let hash = secp256k1::hashes::sha256::Hash::hash(serialized.as_bytes());
171        let id: [u8; 32] = hash.to_byte_array();
172
173        // Optional verify that the message was in the past
174        if let Some(mt) = maxtime {
175            if self.created_at > mt {
176                return Err(Error::EventInFuture);
177            }
178        }
179
180        if id != self.id.0 {
181            Err(Error::HashMismatch)
182        } else {
183            Ok(())
184        }
185    }
186
187    /// Mock data for testing
188    #[allow(dead_code)]
189    pub(crate) async fn mock() -> EventV3 {
190        let signer = {
191            let private_key = PrivateKey::mock();
192            KeySigner::from_private_key(private_key, "", 1).unwrap()
193        };
194        let public_key = signer.public_key();
195        let pre = PreEventV3 {
196            pubkey: public_key,
197            created_at: Unixtime::mock(),
198            kind: EventKind::mock(),
199            tags: vec![TagV3::mock(), TagV3::mock()],
200            content: "This is a test".to_string(),
201        };
202        signer.sign_event(pre).await.unwrap()
203    }
204
205    /// Get the k-tag kind, if any
206    pub fn k_tag_kind(&self) -> Option<EventKind> {
207        for tag in self.tags.iter() {
208            if let Ok(ParsedTag::Kind(kind)) = tag.parse() {
209                return Some(kind);
210            }
211        }
212        None
213    }
214
215    /// If the event refers to people by tag, get all the PublicKeys it refers to
216    /// along with recommended relay URL and petname for each
217    pub fn people(&self) -> Vec<(PublicKey, Option<RelayUrl>, Option<String>)> {
218        let mut output: Vec<(PublicKey, Option<RelayUrl>, Option<String>)> = Vec::new();
219        // All 'p' tags
220        for tag in self.tags.iter() {
221            if let Ok(ParsedTag::Pubkey {
222                pubkey,
223                recommended_relay_url,
224                petname,
225            }) = tag.parse()
226            {
227                output.push((
228                    pubkey.to_owned(),
229                    recommended_relay_url
230                        .as_ref()
231                        .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok()),
232                    petname.to_owned(),
233                ));
234            }
235        }
236
237        output
238    }
239
240    /// If the pubkey is tagged in the event
241    pub fn is_tagged(&self, pk: &PublicKey) -> bool {
242        for tag in self.tags.iter() {
243            if let Ok(ParsedTag::Pubkey { pubkey, .. }) = tag.parse() {
244                if pubkey == *pk {
245                    return true;
246                }
247            }
248        }
249
250        false
251    }
252
253    /// If the event refers to people within the contents, get all the PublicKeys it refers
254    /// to within the contents.
255    pub fn people_referenced_in_content(&self) -> Vec<PublicKey> {
256        let mut output = Vec::new();
257        for nurl in NostrUrl::find_all_in_string(&self.content).drain(..) {
258            if let NostrBech32::Pubkey(pk) = nurl.0 {
259                output.push(pk);
260            }
261            if let NostrBech32::Profile(prof) = nurl.0 {
262                output.push(prof.pubkey);
263            }
264        }
265        output
266    }
267
268    /// All events IDs that this event refers to, whether root, reply, mention, or otherwise
269    /// along with optional recommended relay URLs
270    pub fn referred_events(&self) -> Vec<EventReference> {
271        let mut output: Vec<EventReference> = Vec::new();
272
273        // Collect every 'e' tag and 'a' tag
274        for tag in self.tags.iter() {
275            if let Ok(ParsedTag::Event {
276                id,
277                recommended_relay_url: rurl,
278                marker,
279                author_pubkey,
280            }) = tag.parse()
281            {
282                output.push(EventReference::Id {
283                    id,
284                    author: author_pubkey,
285                    relays: rurl
286                        .as_ref()
287                        .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
288                        .into_vec(),
289                    marker,
290                });
291            } else if let Ok(ParsedTag::Address { address, .. }) = tag.parse() {
292                output.push(EventReference::Addr(address))
293            }
294        }
295
296        output
297    }
298
299    /// Get a reference to another event that this event replies to.
300    /// An event can only reply to one other event via 'e' or 'a' tag from a feed-displayable
301    /// event that is not a Repost.
302    pub fn replies_to(&self) -> Option<EventReference> {
303        if !self.kind.is_feed_displayable() {
304            return None;
305        }
306
307        // Repost 'e' and 'a' tags are always considered mentions, not replies.
308        if self.kind == EventKind::Repost || self.kind == EventKind::GenericRepost {
309            return None;
310        }
311
312        // In kind 1111, use the 'e' tag
313        if self.kind == EventKind::Comment {
314            let tags: Vec<&TagV3> = self.tags.iter().filter(|t| t.tagname() == "e").collect();
315            if !tags.is_empty() {
316                if let Ok(parsed) = tags[0].parse() {
317                    return parsed.into_event_reference();
318                }
319            }
320        }
321
322        // 'e' tags marked "reply"
323        let reply_tags: Vec<&TagV3> = self
324            .tags
325            .iter()
326            .filter(|t| (t.tagname() == "e" || t.tagname() == "a") && t.marker() == "reply")
327            .collect();
328        if !reply_tags.is_empty() {
329            if let Ok(parsed) = reply_tags[0].parse() {
330                return parsed.into_event_reference();
331            }
332        }
333
334        // 'e' tags marked "root" serve as replies if none were marked "reply"
335        let root_tags: Vec<&TagV3> = self
336            .tags
337            .iter()
338            .filter(|t| (t.tagname() == "e" || t.tagname() == "a") && t.marker() == "root")
339            .collect();
340        if !root_tags.is_empty() {
341            if let Ok(parsed) = root_tags[0].parse() {
342                return parsed.into_event_reference();
343            }
344        }
345
346        // deprecated positional logic:  Use the last 'e' or 'a' tag that doesn't
347        // have a marker, preferring 'a' tags (this function doesn't give you both).
348        let a_tags: Vec<&TagV3> = self
349            .tags
350            .iter()
351            .rev()
352            .filter(|t| t.tagname() == "a" && t.marker() == "")
353            .collect();
354        if !a_tags.is_empty() {
355            if let Ok(parsed) = a_tags[0].parse() {
356                return parsed.into_event_reference();
357            }
358        }
359
360        let e_tags: Vec<&TagV3> = self
361            .tags
362            .iter()
363            .rev()
364            .filter(|t| t.tagname() == "e" && t.marker() == "")
365            .collect();
366        if !e_tags.is_empty() {
367            if let Ok(parsed) = e_tags[0].parse() {
368                return parsed.into_event_reference();
369            }
370        }
371
372        None
373    }
374
375    /// If this event replies to a thread, get that threads root event Id if
376    /// available, along with an optional recommended_relay_url
377    pub fn replies_to_root(&self) -> Option<EventReference> {
378        if !self.kind.is_feed_displayable() {
379            return None;
380        }
381
382        let root_referencing_tags: Vec<&TagV3> = self
383            .tags
384            .iter()
385            .filter(|t| t.tagname() == "E" || t.tagname() == "A" || t.marker() == "root")
386            .collect();
387
388        if !root_referencing_tags.is_empty() {
389            if let Ok(parsed) = root_referencing_tags[0].parse() {
390                return parsed.into_event_reference();
391            }
392        } else {
393            // deprecated positional logic:  Use the first 'e' or 'a' tag, but only if there are
394            // multiple event referencing tags (not including 'q'), and don't mix 'e' and 'a'
395            // when counting multiples.
396
397            let e_tags: Vec<&TagV3> = self
398                .tags
399                .iter()
400                .filter(|t| t.tagname() == "e" && t.marker() == "")
401                .collect();
402            if e_tags.len() > 1 {
403                if let Ok(parsed) = e_tags[0].parse() {
404                    return parsed.into_event_reference();
405                }
406            }
407
408            let a_tags: Vec<&TagV3> = self
409                .tags
410                .iter()
411                .filter(|t| t.tagname() == "a" && t.marker() == "")
412                .collect();
413            if a_tags.len() > 1 {
414                if let Ok(parsed) = a_tags[0].parse() {
415                    return parsed.into_event_reference();
416                }
417            }
418        }
419
420        None
421    }
422
423    /// If this event quotes others, get those other events
424    pub fn quotes(&self) -> Vec<EventReference> {
425        if self.kind != EventKind::TextNote && self.kind != EventKind::Comment {
426            return vec![];
427        }
428
429        let mut output: Vec<EventReference> = Vec::new();
430
431        for tag in self.tags.iter() {
432            if let Ok(ParsedTag::Quote {
433                id,
434                recommended_relay_url,
435                author_pubkey,
436            }) = tag.parse()
437            {
438                output.push(EventReference::Id {
439                    id,
440                    author: author_pubkey,
441                    relays: recommended_relay_url
442                        .as_ref()
443                        .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
444                        .into_vec(),
445                    marker: None,
446                });
447            }
448        }
449
450        output
451    }
452
453    /// If this event mentions others, get those other event Ids
454    /// and optional recommended relay Urls
455    pub fn mentions(&self) -> Vec<EventReference> {
456        if !self.kind.is_feed_displayable() {
457            return vec![];
458        }
459
460        let mut output: Vec<EventReference> = Vec::new();
461
462        // For kind=6 and kind=16, all 'e' and 'a' tags are mentions
463        if self.kind == EventKind::Repost || self.kind == EventKind::GenericRepost {
464            for tag in self.tags.iter() {
465                if let Ok(ParsedTag::Event {
466                    id,
467                    recommended_relay_url,
468                    marker,
469                    author_pubkey,
470                }) = tag.parse()
471                {
472                    output.push(EventReference::Id {
473                        id,
474                        author: author_pubkey,
475                        relays: recommended_relay_url
476                            .as_ref()
477                            .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
478                            .into_vec(),
479                        marker,
480                    });
481                } else if let Ok(ParsedTag::Address { address, .. }) = tag.parse() {
482                    output.push(EventReference::Addr(address));
483                }
484            }
485
486            return output;
487        }
488
489        // Look for nostr links within the content
490
491        for tag in self.tags.iter() {
492            // Collect every 'e' tag marked as 'mention'
493            if let Ok(ParsedTag::Event {
494                id,
495                recommended_relay_url,
496                marker,
497                author_pubkey,
498            }) = tag.parse()
499            {
500                if marker.is_some() && marker.as_deref().unwrap() == "mention" {
501                    output.push(EventReference::Id {
502                        id,
503                        author: author_pubkey,
504                        relays: recommended_relay_url
505                            .as_ref()
506                            .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
507                            .into_vec(),
508                        marker,
509                    });
510                }
511            }
512
513            // Collect every 'q' tag
514            if let Ok(ParsedTag::Quote {
515                id,
516                recommended_relay_url,
517                author_pubkey,
518            }) = tag.parse()
519            {
520                output.push(EventReference::Id {
521                    id,
522                    author: author_pubkey,
523                    relays: recommended_relay_url
524                        .as_ref()
525                        .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
526                        .into_vec(),
527                    marker: None,
528                });
529            }
530        }
531
532        // Collect every unmarked 'e' or 'a' tag that is not the first (root) or the last (reply)
533        let e_tags: Vec<&TagV3> = self
534            .tags
535            .iter()
536            .filter(|t| (t.tagname() == "e" || t.tagname() == "a") && t.marker() == "")
537            .collect();
538        if e_tags.len() > 2 {
539            // mentions are everything other than first and last
540            for tag in &e_tags[1..e_tags.len() - 1] {
541                if let Ok(ParsedTag::Event {
542                    id,
543                    recommended_relay_url,
544                    marker,
545                    author_pubkey,
546                }) = tag.parse()
547                {
548                    output.push(EventReference::Id {
549                        id,
550                        author: author_pubkey,
551                        relays: recommended_relay_url
552                            .as_ref()
553                            .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
554                            .into_vec(),
555                        marker,
556                    });
557                } else if let Ok(ParsedTag::Address { address, .. }) = tag.parse() {
558                    output.push(EventReference::Addr(address));
559                }
560            }
561        }
562
563        output
564    }
565
566    /// If this event reacts to another, get that other event's Id,
567    /// the reaction content, and an optional Recommended relay Url
568    pub fn reacts_to(&self) -> Option<(EventReference, String)> {
569        if self.kind != EventKind::Reaction {
570            return None;
571        }
572
573        // The last 'e' tag is it
574        for tag in self.tags.iter().rev() {
575            if let Ok(ParsedTag::Event {
576                id,
577                recommended_relay_url,
578                marker,
579                author_pubkey,
580            }) = tag.parse()
581            {
582                return Some((
583                    EventReference::Id {
584                        id,
585                        author: author_pubkey,
586                        relays: recommended_relay_url
587                            .as_ref()
588                            .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
589                            .into_vec(),
590                        marker,
591                    },
592                    self.content.clone(),
593                ));
594            }
595        }
596
597        None
598    }
599
600    /// If this event deletes others, get all the EventReferences of the events that it
601    /// deletes along with the reason for the deletion
602    pub fn deletes(&self) -> Option<(Vec<EventReference>, String)> {
603        if self.kind != EventKind::EventDeletion {
604            return None;
605        }
606
607        let mut erefs: Vec<EventReference> = Vec::new();
608
609        for tag in self.tags.iter() {
610            if let Ok(ParsedTag::Event {
611                id,
612                recommended_relay_url,
613                marker,
614                author_pubkey,
615            }) = tag.parse()
616            {
617                // All 'e' tags are deleted
618                erefs.push(EventReference::Id {
619                    id,
620                    author: author_pubkey,
621                    relays: recommended_relay_url
622                        .as_ref()
623                        .and_then(|rru| RelayUrl::try_from_unchecked_url(rru).ok())
624                        .into_vec(),
625                    marker,
626                });
627            } else if let Ok(ParsedTag::Address { address, .. }) = tag.parse() {
628                erefs.push(EventReference::Addr(address));
629            }
630        }
631
632        if erefs.is_empty() {
633            None
634        } else {
635            Some((erefs, self.content.clone()))
636        }
637    }
638
639    /// Can this event be deleted by the given public key?
640    pub fn delete_author_allowed(&self, by: PublicKey) -> bool {
641        // Author can always delete
642        if self.pubkey == by {
643            return true;
644        }
645
646        if self.kind == EventKind::GiftWrap {
647            for tag in self.tags.iter() {
648                if let Ok(ParsedTag::Pubkey { pubkey, .. }) = tag.parse() {
649                    return by == pubkey;
650                }
651            }
652        }
653
654        false
655    }
656
657    /// If this event zaps another event, get data about that.
658    ///
659    /// Errors returned from this are not fatal, but may be useful for
660    /// explaining to a user why a zap receipt is invalid.
661    pub fn zaps(&self) -> Result<Option<ZapData>, Error> {
662        if self.kind != EventKind::Zap {
663            return Ok(None);
664        }
665
666        let (zap_request, bolt11invoice, payer_p_tag, target_event_from_tags): (
667            EventV3,
668            Bolt11Invoice,
669            Option<PublicKey>,
670            Option<EventReference>,
671        ) = {
672            let mut zap_request: Option<EventV3> = None;
673            let mut bolt11invoice: Option<Bolt11Invoice> = None;
674            let mut payer_p_tag: Option<PublicKey> = None;
675            let mut target_event_from_tags: Option<EventReference> = None;
676
677            for tag in self.tags.iter() {
678                if tag.tagname() == "description" {
679                    let request_string = tag.value();
680                    if let Ok(e) = serde_json::from_str::<EventV3>(request_string) {
681                        zap_request = Some(e);
682                    }
683                }
684                // we ignore the "p" tag, we have that data from two other places (invoice and request)
685                else if tag.tagname() == "P" {
686                    if let Ok(ParsedTag::Pubkey { pubkey, .. }) = tag.parse() {
687                        payer_p_tag = Some(pubkey);
688                    }
689                } else if tag.tagname() == "bolt11" {
690                    if tag.value() == "" {
691                        return Err(Error::ZapReceipt("missing bolt11 tag value".to_string()));
692                    }
693
694                    // Extract as an Invoice
695                    let invoice = match Bolt11Invoice::from_str(tag.value()) {
696                        Ok(inv) => inv,
697                        Err(e) => {
698                            return Err(Error::ZapReceipt(format!("bolt11 failed to parse: {}", e)))
699                        }
700                    };
701
702                    // Verify the signature
703                    if let Err(e) = invoice.check_signature() {
704                        return Err(Error::ZapReceipt(format!(
705                            "bolt11 signature check failed: {}",
706                            e
707                        )));
708                    }
709
710                    bolt11invoice = Some(invoice);
711                }
712            }
713
714            let re = self.referred_events();
715            if re.len() == 1 {
716                target_event_from_tags = Some(re[0].clone());
717            }
718
719            // "The zap receipt MUST contain a description tag which is the JSON-encoded zap request."
720            if zap_request.is_none() {
721                return Ok(None);
722            }
723            let zap_request = zap_request.unwrap();
724
725            // "The zap receipt MUST have a bolt11 tag containing the description hash bolt11 invoice."
726            if bolt11invoice.is_none() {
727                return Ok(None);
728            }
729            let bolt11invoice = bolt11invoice.unwrap();
730
731            (
732                zap_request,
733                bolt11invoice,
734                payer_p_tag,
735                target_event_from_tags,
736            )
737        };
738
739        // Extract data from the invoice
740        let (payee_from_invoice, amount_from_invoice): (PublicKey, MilliSatoshi) = {
741            // Get the public key
742            let secpk = match bolt11invoice.payee_pub_key() {
743                Some(pubkey) => pubkey.to_owned(),
744                None => bolt11invoice.recover_payee_pub_key(),
745            };
746            let (xonlypk, _) = secpk.x_only_public_key();
747            let pubkeybytes = xonlypk.serialize();
748            let pubkey = match PublicKey::from_bytes(&pubkeybytes, false) {
749                Ok(pubkey) => pubkey,
750                Err(e) => return Err(Error::ZapReceipt(format!("payee public key error: {}", e))),
751            };
752
753            if let Some(u) = bolt11invoice.amount_milli_satoshis() {
754                (pubkey, MilliSatoshi(u))
755            } else {
756                return Err(Error::ZapReceipt(
757                    "Amount missing from zap receipt".to_string(),
758                ));
759            }
760        };
761
762        // Extract data from request
763        let (payer_from_request, amount_from_request, target_event_from_request): (
764            PublicKey,
765            MilliSatoshi,
766            EventReference,
767        ) = {
768            let mut amount_from_request: Option<MilliSatoshi> = None;
769            let mut target_event_from_request: Option<EventReference> = None;
770            for tag in zap_request.tags.iter() {
771                if tag.tagname() == "amount" {
772                    if let Ok(m) = tag.value().parse::<u64>() {
773                        amount_from_request = Some(MilliSatoshi(m));
774                    }
775                }
776            }
777
778            let re = zap_request.referred_events();
779            if re.len() == 1 {
780                target_event_from_request = Some(re[0].clone());
781            }
782
783            if amount_from_request.is_none() {
784                return Err(Error::ZapReceipt("zap request had no amount".to_owned()));
785            }
786            let amount_from_request = amount_from_request.unwrap();
787
788            if target_event_from_request.is_none() {
789                return Ok(None);
790            }
791            let target_event_from_request = target_event_from_request.unwrap();
792
793            (
794                zap_request.pubkey,
795                amount_from_request,
796                target_event_from_request,
797            )
798        };
799
800        if let Some(p) = payer_p_tag {
801            if p != payer_from_request {
802                return Err(Error::ZapReceipt(
803                    "Payer Mismatch between receipt P-tag and invoice".to_owned(),
804                ));
805            }
806        }
807
808        if amount_from_invoice != amount_from_request {
809            return Err(Error::ZapReceipt(
810                "Amount Mismatch between request and invoice".to_owned(),
811            ));
812        }
813
814        if let Some(te) = target_event_from_tags {
815            if te != target_event_from_request {
816                return Err(Error::ZapReceipt(
817                    "Zapped event Mismatch receipt and request".to_owned(),
818                ));
819            }
820        }
821
822        Ok(Some(ZapData {
823            zapped_event: target_event_from_request,
824            amount: amount_from_invoice,
825            payee: payee_from_invoice,
826            payer: payer_from_request,
827            provider_pubkey: self.pubkey,
828        }))
829    }
830
831    /// If this event specifies the client that created it, return that client string
832    pub fn client(&self) -> Option<String> {
833        for tag in self.tags.iter() {
834            if tag.tagname() == "client" && !tag.value().is_empty() {
835                return Some(tag.value().to_owned());
836            }
837        }
838
839        None
840    }
841
842    /// If this event specifies a subject, return that subject string
843    pub fn subject(&self) -> Option<String> {
844        for tag in self.tags.iter() {
845            if let Ok(ParsedTag::Subject(subject)) = tag.parse() {
846                return Some(subject);
847            }
848        }
849
850        None
851    }
852
853    /// If this event specifies a title, return that title string
854    pub fn title(&self) -> Option<String> {
855        for tag in self.tags.iter() {
856            if let Ok(ParsedTag::Title(title)) = tag.parse() {
857                return Some(title);
858            }
859        }
860
861        None
862    }
863
864    /// If this event specifies a summary, return that summary string
865    pub fn summary(&self) -> Option<String> {
866        for tag in self.tags.iter() {
867            if let Ok(ParsedTag::Summary(summary)) = tag.parse() {
868                return Some(summary);
869            }
870        }
871
872        None
873    }
874
875    /// Is this event an annotation
876    pub fn is_annotation(&self) -> bool {
877        for tag in self.tags.iter() {
878            if tag.get_index(0) == "annotation" {
879                return true;
880            }
881        }
882        false
883    }
884
885    /// If this event specifies a content warning, return that content warning
886    pub fn content_warning(&self) -> Option<Option<String>> {
887        for tag in self.tags.iter() {
888            if let Ok(ParsedTag::ContentWarning(contentwarning)) = tag.parse() {
889                return Some(contentwarning);
890            }
891        }
892
893        None
894    }
895
896    /// If this is a parameterized event, get the parameter
897    pub fn parameter(&self) -> Option<String> {
898        if self.kind.is_parameterized_replaceable() {
899            for tag in self.tags.iter() {
900                if let Ok(ParsedTag::Identifier(ident)) = tag.parse() {
901                    return Some(ident);
902                }
903            }
904            Some("".to_owned()) // implicit
905        } else {
906            None
907        }
908    }
909
910    /// Return all the hashtags this event refers to
911    pub fn hashtags(&self) -> Vec<String> {
912        if !self.kind.is_feed_displayable() {
913            return vec![];
914        }
915
916        let mut output: Vec<String> = Vec::new();
917
918        for tag in self.tags.iter() {
919            if let Ok(ParsedTag::Hashtag(hashtag)) = tag.parse() {
920                output.push(hashtag);
921            }
922        }
923
924        output
925    }
926
927    /// Return all attached FileMetadata objects
928    pub fn file_metadata(&self) -> Vec<FileMetadata> {
929        let mut output: Vec<FileMetadata> = Vec::new();
930
931        for tag in self.tags.iter() {
932            if tag.tagname() == "imeta" {
933                if let Some(fm) = FileMetadata::from_imeta_tag(tag) {
934                    output.push(fm);
935                }
936            }
937        }
938
939        output
940    }
941
942    /// Return all the URLs this event refers to
943    pub fn urls(&self) -> Vec<RelayUrl> {
944        if !self.kind.is_feed_displayable() {
945            return vec![];
946        }
947
948        let mut output: Vec<RelayUrl> = Vec::new();
949
950        for tag in self.tags.iter() {
951            if let Ok(ParsedTag::RelayUsage { url, .. }) = tag.parse() {
952                if let Ok(relay_url) = RelayUrl::try_from_unchecked_url(&url) {
953                    output.push(relay_url);
954                }
955            }
956        }
957
958        output
959    }
960
961    /// Get the proof-of-work count of leading bits
962    pub fn pow(&self) -> u8 {
963        // Count leading bits in the Id field
964        let zeroes: u8 = crate::get_leading_zero_bits(&self.id.0);
965
966        // Check that they meant it
967        for tag in self.tags.iter() {
968            if let Ok(ParsedTag::Nonce {
969                target: Some(targ), ..
970            }) = tag.parse()
971            {
972                let target_zeroes = targ as u8;
973                return zeroes.min(target_zeroes);
974            }
975        }
976
977        0
978    }
979
980    /// Was this event delegated, was that valid, and if so what is the pubkey of
981    /// the delegator?
982    pub fn delegation(&self) -> EventDelegation {
983        for tag in self.tags.iter() {
984            if let Ok(ParsedTag::Delegation {
985                pubkey,
986                conditions,
987                sig,
988            }) = tag.parse()
989            {
990                // Verify the delegation tag
991                match conditions.verify_signature(&pubkey, &self.pubkey, &sig) {
992                    Ok(_) => {
993                        // Check conditions
994                        if let Some(kind) = conditions.kind {
995                            if self.kind != kind {
996                                return EventDelegation::InvalidDelegation(
997                                    "Event Kind not delegated".to_owned(),
998                                );
999                            }
1000                        }
1001                        if let Some(created_after) = conditions.created_after {
1002                            if self.created_at < created_after {
1003                                return EventDelegation::InvalidDelegation(
1004                                    "Event created before delegation started".to_owned(),
1005                                );
1006                            }
1007                        }
1008                        if let Some(created_before) = conditions.created_before {
1009                            if self.created_at > created_before {
1010                                return EventDelegation::InvalidDelegation(
1011                                    "Event created after delegation ended".to_owned(),
1012                                );
1013                            }
1014                        }
1015                        return EventDelegation::DelegatedBy(pubkey);
1016                    }
1017                    Err(e) => {
1018                        return EventDelegation::InvalidDelegation(format!("{e}"));
1019                    }
1020                }
1021            }
1022        }
1023
1024        EventDelegation::NotDelegated
1025    }
1026
1027    /// If the event came through a proxy, get the (Protocol, Id)
1028    pub fn proxy(&self) -> Option<(String, String)> {
1029        for tag in self.tags.iter() {
1030            if let Ok(ParsedTag::Proxy { id, protocol }) = tag.parse() {
1031                return Some((protocol, id));
1032            }
1033        }
1034        None
1035    }
1036}
1037
1038impl Ord for EventV3 {
1039    fn cmp(&self, other: &Self) -> Ordering {
1040        self.created_at
1041            .cmp(&other.created_at)
1042            .then(self.id.cmp(&other.id))
1043    }
1044}
1045
1046impl PartialOrd for EventV3 {
1047    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1048        Some(self.cmp(other))
1049    }
1050}
1051
1052// Direct access into speedy-serialized bytes, to avoid alloc-deserialize just to peek
1053// at one of these fields
1054#[cfg(feature = "speedy")]
1055impl EventV3 {
1056    /// Read the ID of the event from a speedy encoding without decoding
1057    /// (zero allocation)
1058    ///
1059    /// Note this function is fragile, if the Event structure is reordered,
1060    /// or if speedy code changes, this will break.  Neither should happen.
1061    pub fn get_id_from_speedy_bytes(bytes: &[u8]) -> Option<Id> {
1062        if bytes.len() < 32 {
1063            None
1064        } else if let Ok(arr) = <[u8; 32]>::try_from(&bytes[0..32]) {
1065            Some(unsafe { std::mem::transmute::<[u8; 32], Id>(arr) })
1066        } else {
1067            None
1068        }
1069    }
1070
1071    /// Read the pubkey of the event from a speedy encoding without decoding
1072    /// (close to zero allocation, VerifyingKey does stuff I didn't check)
1073    ///
1074    /// Note this function is fragile, if the Event structure is reordered,
1075    /// or if speedy code changes, this will break.  Neither should happen.
1076    pub fn get_pubkey_from_speedy_bytes(bytes: &[u8]) -> Option<PublicKey> {
1077        if bytes.len() < 64 {
1078            None
1079        } else {
1080            PublicKey::from_bytes(&bytes[32..64], false).ok()
1081        }
1082    }
1083
1084    /// Read the created_at of the event from a speedy encoding without decoding
1085    /// (zero allocation)
1086    ///
1087    /// Note this function is fragile, if the Event structure is reordered,
1088    /// or if speedy code changes, this will break.  Neither should happen.
1089    pub fn get_created_at_from_speedy_bytes(bytes: &[u8]) -> Option<Unixtime> {
1090        if bytes.len() < 72 {
1091            None
1092        } else if let Ok(i) = i64::read_from_buffer(&bytes[64..72]) {
1093            Some(Unixtime(i))
1094        } else {
1095            None
1096        }
1097    }
1098
1099    /// Read the kind of the event from a speedy encoding without decoding
1100    /// (zero allocation)
1101    ///
1102    /// Note this function is fragile, if the Event structure is reordered,
1103    /// or if speedy code changes, this will break.  Neither should happen.
1104    pub fn get_kind_from_speedy_bytes(bytes: &[u8]) -> Option<EventKind> {
1105        if bytes.len() < 76 {
1106            None
1107        } else if let Ok(u) = u32::read_from_buffer(&bytes[72..76]) {
1108            Some(u.into())
1109        } else {
1110            None
1111        }
1112    }
1113
1114    // Read the sig of the event from a speedy encoding without decoding
1115    // (offset would be 76..140
1116
1117    /// Read the content of the event from a speedy encoding without decoding
1118    /// (zero allocation)
1119    ///
1120    /// Note this function is fragile, if the Event structure is reordered,
1121    /// or if speedy code changes, this will break.  Neither should happen.
1122    pub fn get_content_from_speedy_bytes(bytes: &[u8]) -> Option<&str> {
1123        let len = u32::from_ne_bytes(bytes[140..140 + 4].try_into().unwrap());
1124
1125        unsafe {
1126            Some(std::str::from_utf8_unchecked(
1127                &bytes[140 + 4..140 + 4 + len as usize],
1128            ))
1129        }
1130    }
1131
1132    /// Check if any human-readable tag matches the Regex in the speedy encoding
1133    /// without decoding the whole thing (because our TagV3 representation is so complicated,
1134    /// we do deserialize the tags for now)
1135    ///
1136    /// Note this function is fragile, if the Event structure is reordered,
1137    /// or if speedy code changes, this will break.  Neither should happen.
1138    pub fn tag_search_in_speedy_bytes(bytes: &[u8], re: &Regex) -> Result<bool, Error> {
1139        if bytes.len() < 140 {
1140            return Ok(false);
1141        }
1142
1143        // skip content
1144        let len = u32::from_ne_bytes(bytes[140..140 + 4].try_into().unwrap());
1145        let offset = 140 + 4 + len as usize;
1146
1147        // Deserialize the tags
1148        let tags: Vec<TagV3> = Vec::<TagV3>::read_from_buffer(&bytes[offset..])?;
1149
1150        // Search through them
1151        for tag in &tags {
1152            match tag.tagname() {
1153                "content-warning" => {
1154                    if let Ok(ParsedTag::ContentWarning(Some(w))) = tag.parse() {
1155                        if re.is_match(w.as_ref()) {
1156                            return Ok(true);
1157                        }
1158                    }
1159                }
1160                "t" => {
1161                    if let Ok(ParsedTag::Hashtag(hashtag)) = tag.parse() {
1162                        if re.is_match(hashtag.as_ref()) {
1163                            return Ok(true);
1164                        }
1165                    }
1166                }
1167                "subject" => {
1168                    if let Ok(ParsedTag::Subject(subject)) = tag.parse() {
1169                        if re.is_match(subject.as_ref()) {
1170                            return Ok(true);
1171                        }
1172                    }
1173                }
1174                "title" => {
1175                    if let Ok(ParsedTag::Title(title)) = tag.parse() {
1176                        if re.is_match(title.as_ref()) {
1177                            return Ok(true);
1178                        }
1179                    }
1180                }
1181                _ => {
1182                    if tag.tagname() == "summary" && re.is_match(tag.value()) {
1183                        return Ok(true);
1184                    }
1185                }
1186            }
1187        }
1188
1189        Ok(false)
1190    }
1191}
1192
1193impl From<EventV3> for RumorV3 {
1194    fn from(e: EventV3) -> RumorV3 {
1195        RumorV3 {
1196            id: e.id,
1197            pubkey: e.pubkey,
1198            created_at: e.created_at,
1199            kind: e.kind,
1200            content: e.content,
1201            tags: e.tags,
1202        }
1203    }
1204}
1205
1206impl From<RumorV3> for PreEventV3 {
1207    fn from(r: RumorV3) -> PreEventV3 {
1208        PreEventV3 {
1209            pubkey: r.pubkey,
1210            created_at: r.created_at,
1211            kind: r.kind,
1212            content: r.content,
1213            tags: r.tags,
1214        }
1215    }
1216}
1217
1218impl TryFrom<PreEventV3> for RumorV3 {
1219    type Error = Error;
1220    fn try_from(e: PreEventV3) -> Result<RumorV3, Error> {
1221        RumorV3::new(e)
1222    }
1223}
1224
1225#[cfg(test)]
1226mod test {
1227    use super::*;
1228    use crate::types::{DelegationConditions, Signer, SignerExt, UncheckedUrl};
1229
1230    test_serde_async! {EventV3, test_event_serde}
1231
1232    #[tokio::test]
1233    async fn test_event_new_and_verify() {
1234        let signer = {
1235            let privkey = PrivateKey::mock();
1236            KeySigner::from_private_key(privkey, "", 1).unwrap()
1237        };
1238        let pubkey = signer.public_key();
1239        let preevent = PreEventV3 {
1240            pubkey,
1241            created_at: Unixtime::mock(),
1242            kind: EventKind::TextNote,
1243            tags: vec![ParsedTag::Event {
1244                id: Id::mock(),
1245                recommended_relay_url: Some(UncheckedUrl::mock()),
1246                marker: None,
1247                author_pubkey: None,
1248            }
1249            .into_tag()],
1250            content: "Hello World!".to_string(),
1251        };
1252        let mut event = signer.sign_event(preevent).await.unwrap();
1253
1254        assert!(event.verify(None).is_ok());
1255
1256        // Now make sure it fails when the message has been modified
1257        event.content = "I'm changing this message".to_string();
1258        let result = event.verify(None);
1259        assert!(result.is_err());
1260
1261        // Change it back
1262        event.content = "Hello World!".to_string();
1263        let result = event.verify(None);
1264        assert!(result.is_ok());
1265
1266        // Tweak the id only
1267        event.id = Id([
1268            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1269            24, 25, 26, 27, 28, 29, 30, 31,
1270        ]);
1271        let result = event.verify(None);
1272        assert!(result.is_err());
1273    }
1274
1275    // helper
1276    async fn create_event_with_delegation<S>(created_at: Unixtime, real_signer: &S) -> EventV3
1277    where
1278        S: Signer + SignerExt,
1279    {
1280        let delegated_signer = {
1281            let privkey = PrivateKey::mock();
1282            KeySigner::from_private_key(privkey, "", 1).unwrap()
1283        };
1284
1285        let conditions = DelegationConditions::try_from_str(
1286            "kind=1&created_at>1680000000&created_at<1680050000",
1287        )
1288        .unwrap();
1289
1290        let sig = real_signer
1291            .generate_delegation_signature(delegated_signer.public_key(), &conditions)
1292            .await
1293            .unwrap();
1294
1295        let preevent = PreEventV3 {
1296            pubkey: delegated_signer.public_key(),
1297            created_at,
1298            kind: EventKind::TextNote,
1299            tags: vec![
1300                ParsedTag::Event {
1301                    id: Id::mock(),
1302                    recommended_relay_url: Some(UncheckedUrl::mock()),
1303                    marker: None,
1304                    author_pubkey: None,
1305                }
1306                .into_tag(),
1307                ParsedTag::Delegation {
1308                    pubkey: real_signer.public_key(),
1309                    conditions,
1310                    sig,
1311                }
1312                .into_tag(),
1313            ],
1314            content: "Hello World!".to_string(),
1315        };
1316        delegated_signer.sign_event(preevent).await.unwrap()
1317    }
1318
1319    #[tokio::test]
1320    async fn test_event_with_delegation_ok() {
1321        let delegator_signer = {
1322            let delegator_privkey = PrivateKey::mock();
1323            KeySigner::from_private_key(delegator_privkey, "", 1).unwrap()
1324        };
1325        let delegator_pubkey = delegator_signer.public_key();
1326
1327        let event = create_event_with_delegation(Unixtime(1680000012), &delegator_signer).await;
1328        assert!(event.verify(None).is_ok());
1329
1330        // check delegation
1331        if let EventDelegation::DelegatedBy(pk) = event.delegation() {
1332            // expected type, check returned delegator key
1333            assert_eq!(pk, delegator_pubkey);
1334        } else {
1335            panic!("Expected DelegatedBy result, got {:?}", event.delegation());
1336        }
1337    }
1338
1339    #[tokio::test]
1340    async fn test_event_with_delegation_invalid_created_after() {
1341        let delegator_privkey = PrivateKey::mock();
1342        let signer = KeySigner::from_private_key(delegator_privkey, "", 1).unwrap();
1343
1344        let event = create_event_with_delegation(Unixtime(1690000000), &signer).await;
1345        assert!(event.verify(None).is_ok());
1346
1347        // check delegation
1348        if let EventDelegation::InvalidDelegation(reason) = event.delegation() {
1349            // expected type, check returned delegator key
1350            assert_eq!(reason, "Event created after delegation ended");
1351        } else {
1352            panic!(
1353                "Expected InvalidDelegation result, got {:?}",
1354                event.delegation()
1355            );
1356        }
1357    }
1358
1359    #[tokio::test]
1360    async fn test_event_with_delegation_invalid_created_before() {
1361        let signer = {
1362            let delegator_privkey = PrivateKey::mock();
1363            KeySigner::from_private_key(delegator_privkey, "", 1).unwrap()
1364        };
1365
1366        let event = create_event_with_delegation(Unixtime(1610000000), &signer).await;
1367        assert!(event.verify(None).is_ok());
1368
1369        // check delegation
1370        if let EventDelegation::InvalidDelegation(reason) = event.delegation() {
1371            // expected type, check returned delegator key
1372            assert_eq!(reason, "Event created before delegation started");
1373        } else {
1374            panic!(
1375                "Expected InvalidDelegation result, got {:?}",
1376                event.delegation()
1377            );
1378        }
1379    }
1380
1381    #[test]
1382    fn test_realworld_event_with_naddr_tag() {
1383        let raw = r##"{"id":"7760408f6459b9546c3a4e70e3e56756421fba34526b7d460db3fcfd2f8817db","pubkey":"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c","created_at":1687616920,"kind":1,"tags":[["p","1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411","","mention"],["a","30311:1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411:1687612774","","mention"]],"content":"Watching Karnage's stream to see if I learn something about design. \n\nnostr:naddr1qq9rzd3cxumrzv3hxu6qygqmcu9qzj9n7vtd5vl78jyly037wxkyl7vcqflvwy4eqhxjfa4yzypsgqqqwens0qfplk","sig":"dbc5d05a24bfe990a1faaedfcb81a98940d86a105711dbdad9145d05b0ad0f46e3e24eaa3fc283818f27e057fe836a029fd9a68e7f1de06ff477493199d64064"}"##;
1384        let _: EventV3 = serde_json::from_str(raw).unwrap();
1385    }
1386
1387    #[cfg(feature = "speedy")]
1388    #[tokio::test]
1389    async fn test_speedy_encoded_direct_field_access() {
1390        use speedy::Writable;
1391
1392        let signer = {
1393            let privkey = PrivateKey::mock();
1394            KeySigner::from_private_key(privkey, "", 1).unwrap()
1395        };
1396
1397        let preevent = PreEventV3 {
1398            pubkey: signer.public_key(),
1399            created_at: Unixtime(1680000012),
1400            kind: EventKind::TextNote,
1401            tags: vec![
1402                ParsedTag::Event {
1403                    id: Id::mock(),
1404                    recommended_relay_url: Some(UncheckedUrl::mock()),
1405                    marker: None,
1406                    author_pubkey: None,
1407                }
1408                .into_tag(),
1409                ParsedTag::Hashtag("foodstr".to_string()).into_tag(),
1410            ],
1411            content: "Hello World!".to_string(),
1412        };
1413        let event = signer.sign_event(preevent).await.unwrap();
1414        let bytes = event.write_to_vec().unwrap();
1415
1416        let id = EventV3::get_id_from_speedy_bytes(&bytes).unwrap();
1417        assert_eq!(id, event.id);
1418
1419        let pubkey = EventV3::get_pubkey_from_speedy_bytes(&bytes).unwrap();
1420        assert_eq!(pubkey, event.pubkey);
1421
1422        let created_at = EventV3::get_created_at_from_speedy_bytes(&bytes).unwrap();
1423        assert_eq!(created_at, Unixtime(1680000012));
1424
1425        let kind = EventV3::get_kind_from_speedy_bytes(&bytes).unwrap();
1426        assert_eq!(kind, event.kind);
1427
1428        let content = EventV3::get_content_from_speedy_bytes(&bytes);
1429        assert_eq!(content, Some(&*event.content));
1430
1431        let re = regex::Regex::new("foodstr").unwrap();
1432        let found_foodstr = EventV3::tag_search_in_speedy_bytes(&bytes, &re).unwrap();
1433        assert!(found_foodstr);
1434
1435        // Print to work out encoding
1436        //   test like this to see printed data:
1437        //   cargo test --features=speedy test_speedy_encoded_direct_field_access -- --nocapture
1438        println!("EVENT BYTES: {:?}", bytes);
1439        println!("ID: {:?}", event.id.0);
1440        println!("PUBKEY: {:?}", event.pubkey.as_slice());
1441        println!("CREATED AT: {:?}", event.created_at.0.to_ne_bytes());
1442        let kind32: u32 = event.kind.into();
1443        println!("KIND: {:?}", kind32.to_ne_bytes());
1444        println!("SIG: {:?}", event.sig.0.as_ref());
1445        println!(
1446            "CONTENT: [len={:?}] {:?}",
1447            (event.content.as_bytes().len() as u32).to_ne_bytes(),
1448            event.content.as_bytes()
1449        );
1450        println!("TAGS: [len={:?}]", (event.tags.len() as u32).to_ne_bytes());
1451    }
1452
1453    #[tokio::test]
1454    async fn test_event_gift_wrap() {
1455        let signer1 = {
1456            let sec1 = PrivateKey::try_from_hex_string(
1457                "0000000000000000000000000000000000000000000000000000000000000001",
1458            )
1459            .unwrap();
1460            KeySigner::from_private_key(sec1, "", 1).unwrap()
1461        };
1462
1463        let signer2 = {
1464            let sec2 = PrivateKey::try_from_hex_string(
1465                "0000000000000000000000000000000000000000000000000000000000000002",
1466            )
1467            .unwrap();
1468            KeySigner::from_private_key(sec2, "", 1).unwrap()
1469        };
1470
1471        let pre = PreEventV3 {
1472            pubkey: signer1.public_key(),
1473            created_at: Unixtime(1_692_000_000),
1474            kind: EventKind::TextNote,
1475            content: "Hey man, this rocks! Please reply for a test.".to_string(),
1476            tags: vec![],
1477        };
1478
1479        let gift_wrap = signer1
1480            .giftwrap(pre.clone(), signer2.public_key())
1481            .await
1482            .unwrap();
1483        let rumor = signer2.unwrap_giftwrap(&gift_wrap).await.unwrap();
1484        let output_pre: PreEventV3 = rumor.into();
1485
1486        assert_eq!(pre, output_pre);
1487    }
1488
1489    #[test]
1490    fn test_a_tags_as_replies() {
1491        let raw = r#"{"id":"d4fb3aeae033baa4a9504027bff8fd065ba1bbd635c501a5e4f8c7ab0bd37c34","pubkey":"7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805","created_at":1716980987,"kind":1,"sig":"903ae95893082835a42706eda1328ea85a8bf6fbb172bb2f8696b66fccfebfae8756992894a0fb7bb592cb3f78939bdd5fac4cd1eb49138cbf3ea8069574a1dc","content":"The article is interesting, but why compiling everything when configuring meta tags in dist/index.html is sufficient? (like you did in the first version, if I'm not wrong)\nOne main selling point of Oracolo is that it does not require complex server side setup.\n\n> Every time you access the web page, the web page is compiled\n\nThis is not technically correct :)\nJavaScript code is not compiled, it is simply executed; it fetches Nostr data and so builds the page.","tags":[["p","b12b632c887f0c871d140d37bcb6e7c1e1a80264d0b7de8255aa1951d9e1ff79"],["a","30023:b12b632c887f0c871d140d37bcb6e7c1e1a80264d0b7de8255aa1951d9e1ff79:1716928135712","","root"],["r","index.html"]]}"#;
1492        let event: EventV3 = serde_json::from_str(raw).unwrap();
1493        if let Some(parent) = event.replies_to() {
1494            assert!(matches!(parent, EventReference::Addr(_)));
1495        } else {
1496            panic!("a tag reply not recognized");
1497        }
1498    }
1499}