pub mod builder;
pub mod coordinate;
#[allow(
clippy::module_inception,
reason = "the inner `event` module exposes the `Event` struct; the outer module groups the event-related submodules"
)]
pub mod event;
pub mod id;
pub mod kind;
pub mod tag;
pub mod unsigned;
pub use self::builder::{EventBuilder, EventBuilderError};
pub use self::coordinate::{Coordinate, CoordinateError};
pub use self::event::{Event, EventError};
pub use self::id::{EventId, EventIdError};
pub use self::kind::Kind;
pub use self::tag::{
Alphabet, AlphabetError, SingleLetterTag, SingleLetterTagError, Tag, TagError, TagKind, Tags,
};
pub use self::unsigned::{UnsignedEvent, UnsignedEventError};
use crate::key::PublicKey;
use crate::types::Timestamp;
fn canonical_bytes(
pubkey: &PublicKey,
created_at: Timestamp,
kind: Kind,
tags: &Tags,
content: &str,
) -> Vec<u8> {
use serde::ser::SerializeTuple;
use serde::{Serialize, Serializer};
struct Canonical<'a> {
pubkey: &'a PublicKey,
created_at: Timestamp,
kind: Kind,
tags: &'a Tags,
content: &'a str,
}
impl Serialize for Canonical<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tuple = serializer.serialize_tuple(6)?;
tuple.serialize_element(&0_u8)?;
tuple.serialize_element(&self.pubkey.to_hex())?;
tuple.serialize_element(&self.created_at.as_secs())?;
tuple.serialize_element(&self.kind.as_u16())?;
tuple.serialize_element(self.tags)?;
tuple.serialize_element(self.content)?;
tuple.end()
}
}
let canonical = Canonical {
pubkey,
created_at,
kind,
tags,
content,
};
let mut buf = Vec::with_capacity(estimate_canonical_capacity(content, tags));
if serde_json::to_writer(&mut buf, &canonical).is_err() {
debug_assert!(false, "serde_json::to_writer cannot fail on a Vec<u8>");
}
buf
}
fn estimate_canonical_capacity(content: &str, tags: &Tags) -> usize {
let tag_bytes = tags
.iter()
.map(|t| t.values().iter().map(String::len).sum::<usize>() + 4 * t.len())
.sum::<usize>();
96 + tag_bytes + content.len() + (content.len() / 8)
}
#[must_use]
pub fn compute_event_id(
pubkey: &PublicKey,
created_at: Timestamp,
kind: Kind,
tags: &Tags,
content: &str,
) -> EventId {
let bytes = canonical_bytes(pubkey, created_at, kind, tags, content);
EventId::compute_from_canonical(&bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::JsonUtil;
#[test]
fn canonical_serialization_is_compact() {
let pubkey =
PublicKey::parse("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
.unwrap();
let tags = Tags::from_vec(vec![
Tag::new(["e", "id-1"]).unwrap(),
Tag::new(["p", "pk-1"]).unwrap(),
]);
let bytes = canonical_bytes(
&pubkey,
Timestamp::from_secs(1_700_000_000),
Kind::TEXT_NOTE,
&tags,
"hello",
);
let s = String::from_utf8(bytes).unwrap();
assert!(!s.contains(' '));
assert!(s.starts_with("[0,"));
assert!(s.ends_with(",\"hello\"]"));
}
#[test]
fn event_id_is_stable_for_known_input() {
let pubkey =
PublicKey::parse("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
.unwrap();
let id_a = compute_event_id(&pubkey, Timestamp::ZERO, Kind::TEXT_NOTE, &Tags::new(), "");
let id_b = compute_event_id(&pubkey, Timestamp::ZERO, Kind::TEXT_NOTE, &Tags::new(), "");
assert_eq!(id_a, id_b);
}
#[test]
fn changing_any_field_changes_id() {
let pubkey =
PublicKey::parse("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
.unwrap();
let baseline = compute_event_id(
&pubkey,
Timestamp::from_secs(1),
Kind::TEXT_NOTE,
&Tags::new(),
"hello",
);
let other_kind = compute_event_id(
&pubkey,
Timestamp::from_secs(1),
Kind::REACTION,
&Tags::new(),
"hello",
);
let other_content = compute_event_id(
&pubkey,
Timestamp::from_secs(1),
Kind::TEXT_NOTE,
&Tags::new(),
"world",
);
let other_time = compute_event_id(
&pubkey,
Timestamp::from_secs(2),
Kind::TEXT_NOTE,
&Tags::new(),
"hello",
);
assert_ne!(baseline, other_kind);
assert_ne!(baseline, other_content);
assert_ne!(baseline, other_time);
}
#[allow(dead_code, reason = "import sanity check for crate::JsonUtil")]
fn _imports() -> Option<String> {
let kind: Kind = Kind::TEXT_NOTE;
kind.try_to_json().ok()
}
#[test]
fn nip01_control_character_escapes_are_canonical() {
let pubkey =
PublicKey::parse("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
.unwrap();
let content = "\u{0008}\t\n\u{000C}\r\"\\\0\u{001F}";
let bytes = canonical_bytes(
&pubkey,
Timestamp::ZERO,
Kind::TEXT_NOTE,
&Tags::new(),
content,
);
let s = String::from_utf8(bytes).expect("canonical bytes are UTF-8 by construction");
assert!(s.contains(r"\b"), "missing \\b in {s}");
assert!(s.contains(r"\t"));
assert!(s.contains(r"\n"));
assert!(s.contains(r"\f"));
assert!(s.contains(r"\r"));
assert!(s.contains(r#"\""#));
assert!(s.contains(r"\\"));
assert!(
s.contains(r"\u0000") || s.contains(r"\u0000"),
"missing \\u0000 in {s}",
);
assert!(s.contains(r"\u001f"));
let id_a = compute_event_id(
&pubkey,
Timestamp::ZERO,
Kind::TEXT_NOTE,
&Tags::new(),
content,
);
let id_b = compute_event_id(
&pubkey,
Timestamp::ZERO,
Kind::TEXT_NOTE,
&Tags::new(),
content,
);
assert_eq!(id_a, id_b);
}
#[test]
fn canonical_serialization_is_infallible() {
use serde::ser::SerializeTuple;
use serde::{Serialize, Serializer};
struct Probe<'a> {
pubkey: &'a PublicKey,
content: &'a str,
}
impl Serialize for Probe<'_> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut t = s.serialize_tuple(6)?;
t.serialize_element(&0_u8)?;
t.serialize_element(&self.pubkey.to_hex())?;
t.serialize_element(&0_u64)?;
t.serialize_element(&1_u16)?;
let empty: &[Vec<String>] = &[];
t.serialize_element(empty)?;
t.serialize_element(self.content)?;
t.end()
}
}
let pubkey =
PublicKey::parse("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
.unwrap();
for content in [
"",
"\u{0008}\u{0009}\u{000A}\u{000C}\u{000D}\"\\",
"naïve résumé — café",
"\u{1F4A9}",
"\0\u{0001}\u{001F}",
] {
let probe = Probe {
pubkey: &pubkey,
content,
};
let mut buf = Vec::new();
assert!(
serde_json::to_writer(&mut buf, &probe).is_ok(),
"serde_json::to_writer must be infallible for the canonical layout"
);
assert!(!buf.is_empty(), "writer must have produced bytes");
}
}
}