v1.rs (22243B)
1 #![forbid(unsafe_code)] 2 3 //! Frozen NIP-01 wire-v1 parsing and canonical-identifier semantics. 4 5 #[cfg(all(not(feature = "std"), not(test)))] 6 use alloc::{ 7 collections::BTreeMap, 8 string::{String, ToString}, 9 vec::Vec, 10 }; 11 12 #[cfg(any(feature = "std", test))] 13 use std::{collections::BTreeMap, string::String, vec::Vec}; 14 15 use crate::envelope::{EventEnvelope, EventEnvelopeError, EventEnvelopeParts}; 16 use crate::id::{EventId, EventSignature, ParseError, parse_public_key}; 17 use core::fmt; 18 use serde_json::{Map, Value}; 19 use sha2::{Digest, Sha256}; 20 21 pub const DEFAULT_RAW_JSON_MAX_BYTES: usize = 256 * 1024; 22 pub const DEFAULT_CONTENT_MAX_BYTES: usize = 128 * 1024; 23 pub const DEFAULT_TAG_MAX_COUNT: usize = 1024; 24 pub const DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT: usize = 4096; 25 pub const DEFAULT_TAG_ELEMENT_MAX_BYTES: usize = 4 * 1024; 26 pub const DEFAULT_TAG_TOTAL_MAX_BYTES: usize = 128 * 1024; 27 pub const DEFAULT_EXTRA_MAX_FIELDS: usize = 64; 28 pub const DEFAULT_EXTRA_TOTAL_JSON_MAX_BYTES: usize = 64 * 1024; 29 30 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 31 pub struct EventWireLimits { 32 pub max_raw_json_bytes: usize, 33 pub max_content_bytes: usize, 34 pub max_tag_count: usize, 35 pub max_total_tag_elements: usize, 36 pub max_tag_element_bytes: usize, 37 pub max_total_tag_bytes: usize, 38 pub max_extra_fields: usize, 39 pub max_total_extra_json_bytes: usize, 40 } 41 42 impl Default for EventWireLimits { 43 fn default() -> Self { 44 Self { 45 max_raw_json_bytes: DEFAULT_RAW_JSON_MAX_BYTES, 46 max_content_bytes: DEFAULT_CONTENT_MAX_BYTES, 47 max_tag_count: DEFAULT_TAG_MAX_COUNT, 48 max_total_tag_elements: DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT, 49 max_tag_element_bytes: DEFAULT_TAG_ELEMENT_MAX_BYTES, 50 max_total_tag_bytes: DEFAULT_TAG_TOTAL_MAX_BYTES, 51 max_extra_fields: DEFAULT_EXTRA_MAX_FIELDS, 52 max_total_extra_json_bytes: DEFAULT_EXTRA_TOTAL_JSON_MAX_BYTES, 53 } 54 } 55 } 56 57 #[derive(Clone, Debug, PartialEq, Eq)] 58 pub enum CanonicalEventIdError { 59 InvalidPubkey(ParseError), 60 InvalidComputedEventId(ParseError), 61 } 62 63 impl fmt::Display for CanonicalEventIdError { 64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 match self { 66 Self::InvalidPubkey(error) => { 67 write!(f, "canonical event id pubkey is invalid: {error}") 68 } 69 Self::InvalidComputedEventId(error) => { 70 write!(f, "canonical event id digest is invalid: {error}") 71 } 72 } 73 } 74 } 75 76 #[cfg(feature = "std")] 77 impl std::error::Error for CanonicalEventIdError {} 78 79 #[derive(Clone, Debug, PartialEq, Eq)] 80 pub enum EventWireError { 81 Json(String), 82 RootNotObject, 83 MissingField(&'static str), 84 InvalidField(&'static str), 85 InvalidIdentifier { 86 field: &'static str, 87 error: ParseError, 88 }, 89 NonCanonicalIdentifier { 90 field: &'static str, 91 }, 92 RawJsonTooLarge { 93 max: usize, 94 actual: usize, 95 }, 96 ContentTooLarge { 97 max: usize, 98 actual: usize, 99 }, 100 TooManyTags { 101 max: usize, 102 actual: usize, 103 }, 104 TooManyTagElements { 105 max: usize, 106 actual: usize, 107 }, 108 EmptyTag { 109 index: usize, 110 }, 111 EmptyTagKey { 112 index: usize, 113 }, 114 ControlCharacterTagKey { 115 index: usize, 116 }, 117 TagElementTooLarge { 118 tag_index: usize, 119 element_index: usize, 120 max: usize, 121 actual: usize, 122 }, 123 TagsTooLarge { 124 max: usize, 125 actual: usize, 126 }, 127 TooManyExtraFields { 128 max: usize, 129 actual: usize, 130 }, 131 ExtraJsonTooLarge { 132 max: usize, 133 actual: usize, 134 }, 135 CanonicalEventId(CanonicalEventIdError), 136 Envelope(EventEnvelopeError), 137 EventIdMismatch { 138 declared: String, 139 computed: String, 140 }, 141 } 142 143 impl fmt::Display for EventWireError { 144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 145 match self { 146 Self::Json(error) => write!(f, "event wire json is invalid: {error}"), 147 Self::RootNotObject => write!(f, "event wire root must be a JSON object"), 148 Self::MissingField(field) => write!(f, "event wire missing required field {field}"), 149 Self::InvalidField(field) => write!(f, "event wire field {field} is invalid"), 150 Self::InvalidIdentifier { field, error } => { 151 write!(f, "event wire field {field} is invalid: {error}") 152 } 153 Self::NonCanonicalIdentifier { field } => { 154 write!( 155 f, 156 "event wire field {field} must be canonical lowercase hex" 157 ) 158 } 159 Self::RawJsonTooLarge { max, actual } => { 160 write!(f, "event wire raw JSON size {actual} exceeds {max} bytes") 161 } 162 Self::ContentTooLarge { max, actual } => { 163 write!(f, "event wire content size {actual} exceeds {max} bytes") 164 } 165 Self::TooManyTags { max, actual } => { 166 write!(f, "event wire tag count {actual} exceeds {max}") 167 } 168 Self::TooManyTagElements { max, actual } => { 169 write!(f, "event wire tag element count {actual} exceeds {max}") 170 } 171 Self::EmptyTag { index } => write!(f, "event wire tag {index} is empty"), 172 Self::EmptyTagKey { index } => write!(f, "event wire tag {index} key is empty"), 173 Self::ControlCharacterTagKey { index } => { 174 write!(f, "event wire tag {index} key contains a control character") 175 } 176 Self::TagElementTooLarge { 177 tag_index, 178 element_index, 179 max, 180 actual, 181 } => write!( 182 f, 183 "event wire tag {tag_index} element {element_index} size {actual} exceeds {max} bytes" 184 ), 185 Self::TagsTooLarge { max, actual } => { 186 write!(f, "event wire tag bytes {actual} exceed {max}") 187 } 188 Self::TooManyExtraFields { max, actual } => { 189 write!(f, "event wire extra field count {actual} exceeds {max}") 190 } 191 Self::ExtraJsonTooLarge { max, actual } => { 192 write!(f, "event wire extra JSON bytes {actual} exceed {max}") 193 } 194 Self::CanonicalEventId(error) => write!(f, "{error}"), 195 Self::Envelope(error) => write!(f, "{error}"), 196 Self::EventIdMismatch { declared, computed } => write!( 197 f, 198 "event wire id mismatch: declared {declared}, computed {computed}" 199 ), 200 } 201 } 202 } 203 204 #[cfg(feature = "std")] 205 impl std::error::Error for EventWireError {} 206 207 impl From<CanonicalEventIdError> for EventWireError { 208 fn from(value: CanonicalEventIdError) -> Self { 209 Self::CanonicalEventId(value) 210 } 211 } 212 213 impl From<EventEnvelopeError> for EventWireError { 214 fn from(value: EventEnvelopeError) -> Self { 215 Self::Envelope(value) 216 } 217 } 218 219 #[cfg_attr( 220 any(feature = "serde", test), 221 derive(serde::Serialize, serde::Deserialize) 222 )] 223 #[derive(Clone, Debug, PartialEq, Eq)] 224 pub struct Nip01EventWireParts { 225 pub kind: u32, 226 pub content: String, 227 pub tags: Vec<Vec<String>>, 228 } 229 230 #[cfg_attr( 231 any(feature = "serde", test), 232 derive(serde::Serialize, serde::Deserialize) 233 )] 234 #[derive(Clone, Debug, PartialEq, Eq)] 235 pub struct Nip01EventWire { 236 pub id: String, 237 pub pubkey: String, 238 pub created_at: u64, 239 pub kind: u32, 240 pub tags: Vec<Vec<String>>, 241 pub content: String, 242 pub sig: String, 243 #[cfg_attr(any(feature = "serde", test), serde(flatten))] 244 pub extra: BTreeMap<String, Value>, 245 } 246 247 impl Nip01EventWire { 248 pub fn parse_json(raw_json: &str) -> Result<Self, EventWireError> { 249 Self::parse_json_with_limits(raw_json, EventWireLimits::default()) 250 } 251 252 /// Parses and structurally validates an event without verifying its ID. 253 /// 254 /// This boundary enforces every [`EventWireLimits`] budget. Callers must 255 /// invoke [`Self::verify_id`] before treating the result as ID-verified. 256 pub fn parse_json_unverified(raw_json: &str) -> Result<Self, EventWireError> { 257 Self::parse_json_unverified_with_limits(raw_json, EventWireLimits::default()) 258 } 259 260 pub fn parse_json_with_limits( 261 raw_json: &str, 262 limits: EventWireLimits, 263 ) -> Result<Self, EventWireError> { 264 let wire = Self::parse_json_unverified_with_limits(raw_json, limits)?; 265 wire.verify_id()?; 266 Ok(wire) 267 } 268 269 /// Parses and structurally validates an event under explicit limits 270 /// without verifying its ID. 271 pub fn parse_json_unverified_with_limits( 272 raw_json: &str, 273 limits: EventWireLimits, 274 ) -> Result<Self, EventWireError> { 275 let raw_len = raw_json.len(); 276 if raw_len > limits.max_raw_json_bytes { 277 return Err(EventWireError::RawJsonTooLarge { 278 max: limits.max_raw_json_bytes, 279 actual: raw_len, 280 }); 281 } 282 let value = serde_json::from_str::<Value>(raw_json) 283 .map_err(|error| EventWireError::Json(error.to_string()))?; 284 Self::from_json_value(value, limits) 285 } 286 287 pub fn canonical_id_preimage(&self) -> Result<String, CanonicalEventIdError> { 288 canonical_nip01_event_id_preimage( 289 self.pubkey.as_str(), 290 self.created_at, 291 self.kind, 292 &self.tags, 293 self.content.as_str(), 294 ) 295 } 296 297 pub fn computed_event_id(&self) -> Result<EventId, CanonicalEventIdError> { 298 compute_canonical_nip01_event_id( 299 self.pubkey.as_str(), 300 self.created_at, 301 self.kind, 302 &self.tags, 303 self.content.as_str(), 304 ) 305 } 306 307 pub fn verify_id(&self) -> Result<(), EventWireError> { 308 let computed = self.computed_event_id()?.into_string(); 309 if computed.as_str() != self.id.as_str() { 310 return Err(EventWireError::EventIdMismatch { 311 declared: self.id.clone(), 312 computed, 313 }); 314 } 315 Ok(()) 316 } 317 318 pub fn into_envelope(self) -> Result<EventEnvelope, EventWireError> { 319 self.verify_id()?; 320 self.into_unverified_envelope() 321 .map_err(EventWireError::Envelope) 322 } 323 324 /// Converts structurally validated wire data without verifying its ID. 325 /// 326 /// The returned envelope remains untrusted until an admission typestate 327 /// transition verifies its canonical ID and signature. 328 pub fn into_unverified_envelope(self) -> Result<EventEnvelope, EventEnvelopeError> { 329 EventEnvelope::new(EventEnvelopeParts { 330 id: self.id, 331 author: self.pubkey, 332 created_at: self.created_at, 333 kind: self.kind, 334 tags: self.tags, 335 content: self.content, 336 sig: self.sig, 337 }) 338 } 339 340 fn from_json_value(value: Value, limits: EventWireLimits) -> Result<Self, EventWireError> { 341 let mut object = match value { 342 Value::Object(object) => object, 343 _ => return Err(EventWireError::RootNotObject), 344 }; 345 let id = take_canonical_event_id(&mut object)?; 346 let pubkey = take_canonical_pubkey(&mut object)?; 347 let created_at = take_u64(&mut object, "created_at")?; 348 let kind = take_u32(&mut object, "kind")?; 349 let tags = take_tags(&mut object, limits)?; 350 let content = take_string(&mut object, "content")?; 351 let content_len = content.len(); 352 if content_len > limits.max_content_bytes { 353 return Err(EventWireError::ContentTooLarge { 354 max: limits.max_content_bytes, 355 actual: content_len, 356 }); 357 } 358 let sig = take_canonical_signature(&mut object)?; 359 let extra = validate_extra(object, limits)?; 360 let wire = Self { 361 id, 362 pubkey, 363 created_at, 364 kind, 365 tags, 366 content, 367 sig, 368 extra, 369 }; 370 Ok(wire) 371 } 372 } 373 374 pub fn canonical_nip01_event_id_preimage( 375 pubkey: &str, 376 created_at: u64, 377 kind: u32, 378 tags: &[Vec<String>], 379 content: &str, 380 ) -> Result<String, CanonicalEventIdError> { 381 canonical_nip01_event_id_preimage_v1(pubkey, created_at, kind, tags, content) 382 } 383 384 /// Serializes the canonical NIP-01 event-id preimage with wire-v1 semantics. 385 pub fn canonical_nip01_event_id_preimage_v1( 386 pubkey: &str, 387 created_at: u64, 388 kind: u32, 389 tags: &[Vec<String>], 390 content: &str, 391 ) -> Result<String, CanonicalEventIdError> { 392 let pubkey = parse_public_key(pubkey).map_err(CanonicalEventIdError::InvalidPubkey)?; 393 let pubkey = pubkey.to_hex(); 394 let mut preimage = String::new(); 395 preimage.push_str("[0,"); 396 push_canonical_json_string(&mut preimage, pubkey.as_str()); 397 preimage.push(','); 398 preimage.push_str(created_at.to_string().as_str()); 399 preimage.push(','); 400 preimage.push_str(kind.to_string().as_str()); 401 preimage.push_str(",["); 402 for (tag_index, tag) in tags.iter().enumerate() { 403 if tag_index > 0 { 404 preimage.push(','); 405 } 406 preimage.push('['); 407 for (value_index, value) in tag.iter().enumerate() { 408 if value_index > 0 { 409 preimage.push(','); 410 } 411 push_canonical_json_string(&mut preimage, value); 412 } 413 preimage.push(']'); 414 } 415 preimage.push_str("],"); 416 push_canonical_json_string(&mut preimage, content); 417 preimage.push(']'); 418 Ok(preimage) 419 } 420 421 pub fn compute_canonical_nip01_event_id( 422 pubkey: &str, 423 created_at: u64, 424 kind: u32, 425 tags: &[Vec<String>], 426 content: &str, 427 ) -> Result<EventId, CanonicalEventIdError> { 428 compute_canonical_nip01_event_id_v1(pubkey, created_at, kind, tags, content) 429 } 430 431 /// Computes the canonical NIP-01 event identifier with wire-v1 semantics. 432 pub fn compute_canonical_nip01_event_id_v1( 433 pubkey: &str, 434 created_at: u64, 435 kind: u32, 436 tags: &[Vec<String>], 437 content: &str, 438 ) -> Result<EventId, CanonicalEventIdError> { 439 let preimage = canonical_nip01_event_id_preimage_v1(pubkey, created_at, kind, tags, content)?; 440 let digest = Sha256::digest(preimage.as_bytes()); 441 let event_id = hex::encode(digest); 442 EventId::parse(event_id).map_err(CanonicalEventIdError::InvalidComputedEventId) 443 } 444 445 fn take_string( 446 object: &mut Map<String, Value>, 447 field: &'static str, 448 ) -> Result<String, EventWireError> { 449 match object.remove(field) { 450 Some(Value::String(value)) => Ok(value), 451 Some(_) => Err(EventWireError::InvalidField(field)), 452 None => Err(EventWireError::MissingField(field)), 453 } 454 } 455 456 fn take_canonical_event_id(object: &mut Map<String, Value>) -> Result<String, EventWireError> { 457 let raw = take_string(object, "id")?; 458 let parsed = EventId::parse(raw.as_str()) 459 .map_err(|error| EventWireError::InvalidIdentifier { field: "id", error })?; 460 canonical_identifier_string("id", raw, parsed.into_string()) 461 } 462 463 fn take_canonical_pubkey(object: &mut Map<String, Value>) -> Result<String, EventWireError> { 464 let raw = take_string(object, "pubkey")?; 465 let parsed = 466 parse_public_key(raw.as_str()).map_err(|error| EventWireError::InvalidIdentifier { 467 field: "pubkey", 468 error, 469 })?; 470 canonical_identifier_string("pubkey", raw, parsed.to_hex()) 471 } 472 473 fn take_canonical_signature(object: &mut Map<String, Value>) -> Result<String, EventWireError> { 474 let raw = take_string(object, "sig")?; 475 let parsed = 476 EventSignature::parse(raw.as_str()).map_err(|error| EventWireError::InvalidIdentifier { 477 field: "sig", 478 error, 479 })?; 480 canonical_identifier_string("sig", raw, parsed.into_string()) 481 } 482 483 fn canonical_identifier_string( 484 field: &'static str, 485 raw: String, 486 canonical: String, 487 ) -> Result<String, EventWireError> { 488 if canonical.as_str() != raw.as_str() { 489 return Err(EventWireError::NonCanonicalIdentifier { field }); 490 } 491 Ok(canonical) 492 } 493 494 fn take_u64(object: &mut Map<String, Value>, field: &'static str) -> Result<u64, EventWireError> { 495 match object.remove(field) { 496 Some(Value::Number(value)) => value.as_u64().ok_or(EventWireError::InvalidField(field)), 497 Some(_) => Err(EventWireError::InvalidField(field)), 498 None => Err(EventWireError::MissingField(field)), 499 } 500 } 501 502 fn take_u32(object: &mut Map<String, Value>, field: &'static str) -> Result<u32, EventWireError> { 503 let value = take_u64(object, field)?; 504 u32::try_from(value).map_err(|_| EventWireError::InvalidField(field)) 505 } 506 507 fn take_tags( 508 object: &mut Map<String, Value>, 509 limits: EventWireLimits, 510 ) -> Result<Vec<Vec<String>>, EventWireError> { 511 let raw_tags = match object.remove("tags") { 512 Some(Value::Array(raw_tags)) => raw_tags, 513 Some(_) => return Err(EventWireError::InvalidField("tags")), 514 None => return Err(EventWireError::MissingField("tags")), 515 }; 516 let tag_count = raw_tags.len(); 517 if tag_count > limits.max_tag_count { 518 return Err(EventWireError::TooManyTags { 519 max: limits.max_tag_count, 520 actual: tag_count, 521 }); 522 } 523 let total_tag_elements = raw_tags.iter().try_fold(0usize, |total, raw_tag| { 524 let Value::Array(values) = raw_tag else { 525 return Err(EventWireError::InvalidField("tags")); 526 }; 527 Ok(total.saturating_add(values.len())) 528 })?; 529 if total_tag_elements > limits.max_total_tag_elements { 530 return Err(EventWireError::TooManyTagElements { 531 max: limits.max_total_tag_elements, 532 actual: total_tag_elements, 533 }); 534 } 535 let mut total_tag_bytes = 0usize; 536 let mut tags = Vec::with_capacity(tag_count); 537 for (tag_index, raw_tag) in raw_tags.into_iter().enumerate() { 538 let raw_values = match raw_tag { 539 Value::Array(values) => values, 540 _ => return Err(EventWireError::InvalidField("tags")), 541 }; 542 if raw_values.is_empty() { 543 return Err(EventWireError::EmptyTag { index: tag_index }); 544 } 545 let mut tag = Vec::with_capacity(raw_values.len()); 546 for (element_index, raw_value) in raw_values.into_iter().enumerate() { 547 let value = match raw_value { 548 Value::String(value) => value, 549 _ => return Err(EventWireError::InvalidField("tags")), 550 }; 551 let value_len = value.len(); 552 if value_len > limits.max_tag_element_bytes { 553 return Err(EventWireError::TagElementTooLarge { 554 tag_index, 555 element_index, 556 max: limits.max_tag_element_bytes, 557 actual: value_len, 558 }); 559 } 560 if element_index == 0 { 561 validate_tag_key(tag_index, value.as_str())?; 562 } 563 total_tag_bytes = total_tag_bytes.saturating_add(value_len); 564 if total_tag_bytes > limits.max_total_tag_bytes { 565 return Err(EventWireError::TagsTooLarge { 566 max: limits.max_total_tag_bytes, 567 actual: total_tag_bytes, 568 }); 569 } 570 tag.push(value); 571 } 572 tags.push(tag); 573 } 574 Ok(tags) 575 } 576 577 fn validate_tag_key(index: usize, value: &str) -> Result<(), EventWireError> { 578 if value.is_empty() { 579 return Err(EventWireError::EmptyTagKey { index }); 580 } 581 if value.chars().any(char::is_control) { 582 return Err(EventWireError::ControlCharacterTagKey { index }); 583 } 584 Ok(()) 585 } 586 587 fn validate_extra( 588 object: Map<String, Value>, 589 limits: EventWireLimits, 590 ) -> Result<BTreeMap<String, Value>, EventWireError> { 591 let extra_count = object.len(); 592 if extra_count > limits.max_extra_fields { 593 return Err(EventWireError::TooManyExtraFields { 594 max: limits.max_extra_fields, 595 actual: extra_count, 596 }); 597 } 598 let mut total_json_bytes = 0usize; 599 let mut extra = BTreeMap::new(); 600 for (key, value) in object { 601 let key_json_len = serialized_json_string_len(&key); 602 let value_json_len = serialized_json_value_len(&value); 603 total_json_bytes = total_json_bytes 604 .saturating_add(key_json_len) 605 .saturating_add(1) 606 .saturating_add(value_json_len); 607 if total_json_bytes > limits.max_total_extra_json_bytes { 608 return Err(EventWireError::ExtraJsonTooLarge { 609 max: limits.max_total_extra_json_bytes, 610 actual: total_json_bytes, 611 }); 612 } 613 extra.insert(key, value); 614 } 615 Ok(extra) 616 } 617 618 #[cfg_attr(coverage_nightly, coverage(off))] 619 fn serialized_json_string_len(value: &String) -> usize { 620 serde_json::to_vec(value) 621 .expect("JSON strings always serialize") 622 .len() 623 } 624 625 #[cfg_attr(coverage_nightly, coverage(off))] 626 fn serialized_json_value_len(value: &Value) -> usize { 627 serde_json::to_vec(value) 628 .expect("JSON values always serialize") 629 .len() 630 } 631 632 fn push_canonical_json_string(target: &mut String, value: &str) { 633 target.push('"'); 634 for character in value.chars() { 635 match character { 636 '"' => target.push_str("\\\""), 637 '\\' => target.push_str("\\\\"), 638 '\n' => target.push_str("\\n"), 639 '\r' => target.push_str("\\r"), 640 '\t' => target.push_str("\\t"), 641 '\u{08}' => target.push_str("\\b"), 642 '\u{0c}' => target.push_str("\\f"), 643 '\u{00}'..='\u{1f}' => push_unicode_escape(target, character), 644 _ => target.push(character), 645 } 646 } 647 target.push('"'); 648 } 649 650 fn push_unicode_escape(target: &mut String, character: char) { 651 const HEX: &[u8; 16] = b"0123456789abcdef"; 652 let value = character as u32; 653 target.push_str("\\u00"); 654 target.push(HEX[((value >> 4) & 0x0f) as usize] as char); 655 target.push(HEX[(value & 0x0f) as usize] as char); 656 } 657 658 #[cfg(test)] 659 #[cfg_attr(coverage_nightly, coverage(off))] 660 mod tests;