Skip to content

Commit e5b3958

Browse files
committed
nostr: fix re-serialization of events that contains unknown keys during deserialization
* Add private `EventKey` enum * Add event JSON keys constants Fixes #433 Signed-off-by: Yuki Kishimoto <[email protected]>
1 parent 1195ee3 commit e5b3958

File tree

2 files changed

+111
-28
lines changed

2 files changed

+111
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ removed zap split from `client.zap` method, many improvements and more!
8282
### Fixed
8383

8484
* nostr: fix NIP19 event (`nevent`) serialization ([Yuki Kishimoto])
85+
* nostr: fix re-serialization of events that contains unknown keys during deserialization ([Yuki Kishimoto])
8586

8687
### Removed
8788

crates/nostr/src/event/mod.rs

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use core::cmp::Ordering;
1414
use core::fmt;
1515
use core::hash::{Hash, Hasher};
1616
use core::ops::Deref;
17+
use core::str::FromStr;
1718

1819
use bitcoin::secp256k1::schnorr::Signature;
1920
use bitcoin::secp256k1::{self, Message, Secp256k1, Verification};
@@ -48,13 +49,52 @@ use crate::{Alphabet, JsonUtil, PublicKey, SingleLetterTag, Timestamp};
4849
/// Tags Indexes
4950
pub type TagsIndexes = BTreeMap<SingleLetterTag, BTreeSet<String>>;
5051

52+
const ID: &str = "id";
53+
const PUBKEY: &str = "pubkey";
54+
const CREATED_AT: &str = "created_at";
55+
const KIND: &str = "kind";
56+
const TAGS: &str = "tags";
57+
const CONTENT: &str = "content";
58+
const SIG: &str = "sig";
59+
60+
/// Supported event keys
61+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
62+
enum EventKey {
63+
Id,
64+
PubKey,
65+
CreatedAt,
66+
Kind,
67+
Tags,
68+
Content,
69+
Sig,
70+
}
71+
72+
impl FromStr for EventKey {
73+
type Err = Error;
74+
75+
fn from_str(key: &str) -> Result<Self, Self::Err> {
76+
match key {
77+
ID => Ok(Self::Id),
78+
PUBKEY => Ok(Self::PubKey),
79+
CREATED_AT => Ok(Self::CreatedAt),
80+
KIND => Ok(Self::Kind),
81+
TAGS => Ok(Self::Tags),
82+
CONTENT => Ok(Self::Content),
83+
SIG => Ok(Self::Sig),
84+
k => Err(Error::UnknownKey(k.to_string())),
85+
}
86+
}
87+
}
88+
5189
/// [`Event`] error
5290
#[derive(Debug, PartialEq, Eq)]
5391
pub enum Error {
5492
/// Invalid signature
5593
InvalidSignature,
5694
/// Invalid event id
5795
InvalidId,
96+
/// Unknown JSON event key
97+
UnknownKey(String),
5898
/// Error serializing or deserializing JSON data
5999
Json(String),
60100
/// Secp256k1 error
@@ -69,6 +109,7 @@ impl fmt::Display for Error {
69109
match self {
70110
Self::InvalidSignature => write!(f, "Invalid signature"),
71111
Self::InvalidId => write!(f, "Invalid event id"),
112+
Self::UnknownKey(key) => write!(f, "Unknown JSON event key: {key}"),
72113
Self::Json(e) => write!(f, "Json: {e}"),
73114
Self::Secp256k1(e) => write!(f, "Secp256k1: {e}"),
74115
}
@@ -93,7 +134,7 @@ pub struct Event {
93134
/// Event
94135
inner: EventIntermediate,
95136
/// JSON deserialization key order
96-
deser_order: Vec<String>,
137+
deser_order: Vec<EventKey>,
97138
/// Tags indexes
98139
#[cfg(feature = "std")]
99140
tags_indexes: Arc<OnceCell<TagsIndexes>>,
@@ -102,13 +143,13 @@ pub struct Event {
102143
impl fmt::Debug for Event {
103144
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104145
f.debug_struct("Event")
105-
.field("id", &self.inner.id)
106-
.field("pubkey", &self.inner.pubkey)
107-
.field("created_at", &self.inner.created_at)
108-
.field("kind", &self.inner.kind)
109-
.field("tags", &self.inner.tags)
110-
.field("content", &self.inner.content)
111-
.field("sig", &self.inner.sig)
146+
.field(ID, &self.inner.id)
147+
.field(PUBKEY, &self.inner.pubkey)
148+
.field(CREATED_AT, &self.inner.created_at)
149+
.field(KIND, &self.inner.kind)
150+
.field(TAGS, &self.inner.tags)
151+
.field(CONTENT, &self.inner.content)
152+
.field(SIG, &self.inner.sig)
112153
.finish()
113154
}
114155
}
@@ -531,15 +572,14 @@ impl Serialize for Event {
531572
} else {
532573
let mut s = serializer.serialize_struct("Event", 7)?;
533574
for key in self.deser_order.iter() {
534-
match key.as_str() {
535-
"id" => s.serialize_field("id", &self.inner.id)?,
536-
"pubkey" => s.serialize_field("pubkey", &self.inner.pubkey)?,
537-
"created_at" => s.serialize_field("created_at", &self.inner.created_at)?,
538-
"kind" => s.serialize_field("kind", &self.inner.kind)?,
539-
"tags" => s.serialize_field("tags", &self.inner.tags)?,
540-
"content" => s.serialize_field("content", &self.inner.content)?,
541-
"sig" => s.serialize_field("sig", &self.inner.sig)?,
542-
_ => return Err(serde::ser::Error::custom(format!("Unknown key: {}", key))),
575+
match key {
576+
EventKey::Id => s.serialize_field(ID, &self.inner.id)?,
577+
EventKey::PubKey => s.serialize_field(PUBKEY, &self.inner.pubkey)?,
578+
EventKey::CreatedAt => s.serialize_field(CREATED_AT, &self.inner.created_at)?,
579+
EventKey::Kind => s.serialize_field(KIND, &self.inner.kind)?,
580+
EventKey::Tags => s.serialize_field(TAGS, &self.inner.tags)?,
581+
EventKey::Content => s.serialize_field(CONTENT, &self.inner.content)?,
582+
EventKey::Sig => s.serialize_field(SIG, &self.inner.sig)?,
543583
}
544584
}
545585
s.end()
@@ -554,10 +594,13 @@ impl<'de> Deserialize<'de> for Event {
554594
{
555595
let value: Value = Value::deserialize(deserializer)?;
556596

557-
let mut deser_order: Vec<String> = Vec::with_capacity(7);
558-
if let Value::Object(map) = &value {
559-
deser_order = map.keys().cloned().collect();
560-
}
597+
let deser_order: Vec<EventKey> = if let Value::Object(map) = &value {
598+
map.keys()
599+
.filter_map(|k| EventKey::from_str(k).ok())
600+
.collect()
601+
} else {
602+
Vec::new()
603+
};
561604

562605
Ok(Self {
563606
inner: serde_json::from_value(value).map_err(serde::de::Error::custom)?,
@@ -661,13 +704,13 @@ mod tests {
661704
assert_eq!(
662705
event.deser_order,
663706
vec![
664-
"kind",
665-
"pubkey",
666-
"content",
667-
"created_at",
668-
"id",
669-
"sig",
670-
"tags"
707+
EventKey::Kind,
708+
EventKey::PubKey,
709+
EventKey::Content,
710+
EventKey::CreatedAt,
711+
EventKey::Id,
712+
EventKey::Sig,
713+
EventKey::Tags
671714
]
672715
);
673716
assert_eq!(json, reserialized_json);
@@ -677,6 +720,45 @@ mod tests {
677720
let reserialized_json = event.as_json();
678721
assert_eq!(json, reserialized_json);
679722
}
723+
724+
#[test]
725+
fn test_event_with_unknown_fields() {
726+
let json: &str = r##"{
727+
"citedNotesCache": [],
728+
"citedUsersCache": [
729+
"aac07d95089ce6adf08b9156d43c1a4ab594c6130b7dcb12ec199008c5819a2f"
730+
],
731+
"content": "#JoininBox is a minimalistic, security focused Linux environment for #JoinMarket with a terminal based graphical menu.\n\nnostr:npub14tq8m9ggnnn2muytj9tdg0q6f26ef3snpd7ukyhvrxgq33vpnghs8shy62 👍🧡\n\nhttps://www.nobsbitcoin.com/joininbox-v0-8-0/",
732+
"created_at": 1687070234,
733+
"id": "c8acc12a232ea6caedfaaf0c52148635de6ffd312c3f432c6eca11720c102e54",
734+
"kind": 1,
735+
"pubkey": "27154fb873badf69c3ea83a0da6e65d6a150d2bf8f7320fc3314248d74645c64",
736+
"sig": "e27062b1b7187ffa0b521dab23fff6c6b62c00fd1b029e28368d7d070dfb225f7e598e3b1c6b1e2335b286ec3702492bce152035105b934f594cd7323d84f0ee",
737+
"tags": [
738+
[
739+
"t",
740+
"joininbox"
741+
],
742+
[
743+
"t",
744+
"joinmarket"
745+
],
746+
[
747+
"p",
748+
"aac07d95089ce6adf08b9156d43c1a4ab594c6130b7dcb12ec199008c5819a2f"
749+
]
750+
]
751+
}"##;
752+
753+
// Deserialize
754+
let event = Event::from_json(json).unwrap();
755+
756+
// Re-serialize
757+
let re_serialized_json = event.as_json();
758+
759+
let expected_json: &str = r##"{"content":"#JoininBox is a minimalistic, security focused Linux environment for #JoinMarket with a terminal based graphical menu.\n\nnostr:npub14tq8m9ggnnn2muytj9tdg0q6f26ef3snpd7ukyhvrxgq33vpnghs8shy62 👍🧡\n\nhttps://www.nobsbitcoin.com/joininbox-v0-8-0/","created_at":1687070234,"id":"c8acc12a232ea6caedfaaf0c52148635de6ffd312c3f432c6eca11720c102e54","kind":1,"pubkey":"27154fb873badf69c3ea83a0da6e65d6a150d2bf8f7320fc3314248d74645c64","sig":"e27062b1b7187ffa0b521dab23fff6c6b62c00fd1b029e28368d7d070dfb225f7e598e3b1c6b1e2335b286ec3702492bce152035105b934f594cd7323d84f0ee","tags":[["t","joininbox"],["t","joinmarket"],["p","aac07d95089ce6adf08b9156d43c1a4ab594c6130b7dcb12ec199008c5819a2f"]]}"##;
760+
assert_eq!(re_serialized_json, expected_json.trim());
761+
}
680762
}
681763

682764
#[cfg(bench)]

0 commit comments

Comments
 (0)