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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
19#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
20pub struct EventV3 {
21 pub id: Id,
23
24 pub pubkey: PublicKey,
26
27 pub created_at: Unixtime,
29
30 pub kind: EventKind,
32
33 pub sig: Signature,
38
39 pub content: String,
41
42 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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
62#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
63pub struct PreEventV3 {
64 pub pubkey: PublicKey,
66 pub created_at: Unixtime,
68 pub kind: EventKind,
70 pub tags: Vec<TagV3>,
72 pub content: String,
74}
75
76impl PreEventV3 {
77 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 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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
98#[cfg_attr(feature = "speedy", derive(Readable, Writable))]
99pub struct RumorV3 {
100 pub id: Id,
102
103 pub pubkey: PublicKey,
105
106 pub created_at: Unixtime,
108
109 pub kind: EventKind,
111
112 pub content: String,
114
115 pub tags: Vec<TagV3>,
117}
118
119impl RumorV3 {
120 pub fn new(input: PreEventV3) -> Result<RumorV3, Error> {
122 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 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 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 self.pubkey.verify(serialized.as_bytes(), &self.sig)?;
166
167 let hash = secp256k1::hashes::sha256::Hash::hash(serialized.as_bytes());
171 let id: [u8; 32] = hash.to_byte_array();
172
173 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 #[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 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 pub fn people(&self) -> Vec<(PublicKey, Option<RelayUrl>, Option<String>)> {
218 let mut output: Vec<(PublicKey, Option<RelayUrl>, Option<String>)> = Vec::new();
219 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 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 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 pub fn referred_events(&self) -> Vec<EventReference> {
271 let mut output: Vec<EventReference> = Vec::new();
272
273 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 pub fn replies_to(&self) -> Option<EventReference> {
303 if !self.kind.is_feed_displayable() {
304 return None;
305 }
306
307 if self.kind == EventKind::Repost || self.kind == EventKind::GenericRepost {
309 return None;
310 }
311
312 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 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 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 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 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 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 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 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 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 for tag in self.tags.iter() {
492 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 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 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 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 pub fn reacts_to(&self) -> Option<(EventReference, String)> {
569 if self.kind != EventKind::Reaction {
570 return None;
571 }
572
573 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 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 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 pub fn delete_author_allowed(&self, by: PublicKey) -> bool {
641 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 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 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 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 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 if zap_request.is_none() {
721 return Ok(None);
722 }
723 let zap_request = zap_request.unwrap();
724
725 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 let (payee_from_invoice, amount_from_invoice): (PublicKey, MilliSatoshi) = {
741 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 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 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 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 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 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 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 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 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()) } else {
906 None
907 }
908 }
909
910 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 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 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 pub fn pow(&self) -> u8 {
963 let zeroes: u8 = crate::get_leading_zero_bits(&self.id.0);
965
966 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 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 match conditions.verify_signature(&pubkey, &self.pubkey, &sig) {
992 Ok(_) => {
993 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 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#[cfg(feature = "speedy")]
1055impl EventV3 {
1056 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 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 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 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 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 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 let len = u32::from_ne_bytes(bytes[140..140 + 4].try_into().unwrap());
1145 let offset = 140 + 4 + len as usize;
1146
1147 let tags: Vec<TagV3> = Vec::<TagV3>::read_from_buffer(&bytes[offset..])?;
1149
1150 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 event.content = "I'm changing this message".to_string();
1258 let result = event.verify(None);
1259 assert!(result.is_err());
1260
1261 event.content = "Hello World!".to_string();
1263 let result = event.verify(None);
1264 assert!(result.is_ok());
1265
1266 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 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 if let EventDelegation::DelegatedBy(pk) = event.delegation() {
1332 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 if let EventDelegation::InvalidDelegation(reason) = event.delegation() {
1349 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 if let EventDelegation::InvalidDelegation(reason) = event.delegation() {
1371 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 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}