draft.rs (15427B)
1 #![forbid(unsafe_code)] 2 3 #[cfg(all(not(feature = "std"), not(test)))] 4 use alloc::{string::String, vec::Vec}; 5 6 #[cfg(any(feature = "std", test))] 7 use std::{string::String, vec::Vec}; 8 9 use radroots_identity::PublicKey; 10 11 use crate::contract::registry_v7::ContractValidationError; 12 use crate::envelope::{EventEnvelope, EventEnvelopeError}; 13 use crate::id::{EventId, EventSignature, ParseError, parse_public_key}; 14 use crate::wire::v1::{ 15 CanonicalEventIdError, EventWireError, Nip01EventWire, canonical_nip01_event_id_preimage, 16 compute_canonical_nip01_event_id, 17 }; 18 use core::fmt; 19 20 #[derive(Clone, Debug, PartialEq, Eq)] 21 pub enum DraftError { 22 UnknownContract(String), 23 ContractKindMismatch { 24 contract_id: String, 25 expected_kind: u32, 26 actual_kind: u32, 27 }, 28 ContractNotDraftAuthorable { 29 contract_id: String, 30 }, 31 ContractRegistryVersionMismatch { 32 expected: u32, 33 actual: u32, 34 }, 35 DraftExpectedEventIdMismatch { 36 expected_event_id: String, 37 actual_event_id: String, 38 }, 39 ContractShape { 40 contract_id: String, 41 error: ContractValidationError, 42 }, 43 IdParse(ParseError), 44 CanonicalEventId(CanonicalEventIdError), 45 Envelope(EventEnvelopeError), 46 SignedEvent(SignedEventError), 47 } 48 49 impl fmt::Display for DraftError { 50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 51 match self { 52 Self::UnknownContract(contract_id) => { 53 write!(f, "unknown event contract `{contract_id}`") 54 } 55 Self::ContractKindMismatch { 56 contract_id, 57 expected_kind, 58 actual_kind, 59 } => write!( 60 f, 61 "event contract `{contract_id}` expects kind {expected_kind}, got {actual_kind}" 62 ), 63 Self::ContractNotDraftAuthorable { contract_id } => write!( 64 f, 65 "event contract `{contract_id}` is not authorable through generic frozen drafts" 66 ), 67 Self::ContractRegistryVersionMismatch { expected, actual } => write!( 68 f, 69 "event contract registry version mismatch: expected {expected}, got {actual}" 70 ), 71 Self::DraftExpectedEventIdMismatch { 72 expected_event_id, 73 actual_event_id, 74 } => write!( 75 f, 76 "frozen draft event ID mismatch: expected {expected_event_id}, got {actual_event_id}" 77 ), 78 Self::ContractShape { contract_id, error } => write!( 79 f, 80 "event contract `{contract_id}` shape validation failed with code {}", 81 error.code() 82 ), 83 Self::IdParse(error) => write!(f, "{error}"), 84 Self::CanonicalEventId(error) => write!(f, "{error}"), 85 Self::Envelope(error) => write!(f, "{error}"), 86 Self::SignedEvent(error) => write!(f, "{error}"), 87 } 88 } 89 } 90 91 #[cfg(feature = "std")] 92 impl std::error::Error for DraftError {} 93 94 impl From<ParseError> for DraftError { 95 fn from(value: ParseError) -> Self { 96 Self::IdParse(value) 97 } 98 } 99 100 impl From<CanonicalEventIdError> for DraftError { 101 fn from(value: CanonicalEventIdError) -> Self { 102 match value { 103 CanonicalEventIdError::InvalidPubkey(error) => Self::IdParse(error), 104 error => Self::CanonicalEventId(error), 105 } 106 } 107 } 108 109 impl From<EventEnvelopeError> for DraftError { 110 fn from(value: EventEnvelopeError) -> Self { 111 Self::Envelope(value) 112 } 113 } 114 115 impl From<SignedEventError> for DraftError { 116 fn from(value: SignedEventError) -> Self { 117 Self::SignedEvent(value) 118 } 119 } 120 121 #[cfg_attr( 122 any(feature = "serde", test), 123 derive(serde::Serialize, serde::Deserialize) 124 )] 125 #[derive(Clone, Debug, PartialEq, Eq)] 126 pub struct SignedEventParts { 127 pub id: String, 128 pub pubkey: String, 129 pub created_at: u64, 130 pub kind: u32, 131 pub tags: Vec<Vec<String>>, 132 pub content: String, 133 pub sig: String, 134 pub raw_json: String, 135 } 136 137 #[derive(Clone, Debug, PartialEq, Eq)] 138 pub struct SignedEvent { 139 envelope: EventEnvelope, 140 wire: Nip01EventWire, 141 raw_json: String, 142 } 143 144 #[cfg(any(feature = "serde", test))] 145 impl serde::Serialize for SignedEvent { 146 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 147 where 148 S: serde::Serializer, 149 { 150 use serde::ser::SerializeStruct; 151 152 let mut state = serializer.serialize_struct("SignedEvent", 2)?; 153 state.serialize_field("wire", &self.wire)?; 154 state.serialize_field("raw_json", &self.raw_json)?; 155 state.end() 156 } 157 } 158 159 #[cfg(any(feature = "serde", test))] 160 impl<'de> serde::Deserialize<'de> for SignedEvent { 161 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 162 where 163 D: serde::Deserializer<'de>, 164 { 165 #[derive(serde::Deserialize)] 166 struct SignedEventSerde { 167 wire: Nip01EventWire, 168 raw_json: String, 169 } 170 171 let value = SignedEventSerde::deserialize(deserializer)?; 172 SignedEvent::from_wire_verified_id(value.wire, value.raw_json) 173 .map_err(serde::de::Error::custom) 174 } 175 } 176 177 #[derive(Clone, Debug, PartialEq, Eq)] 178 pub enum SignedEventError { 179 Wire(EventWireError), 180 RawJson(EventWireError), 181 RawJsonMismatch, 182 Envelope(EventEnvelopeError), 183 } 184 185 impl fmt::Display for SignedEventError { 186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 187 match self { 188 Self::Wire(error) => write!(f, "signed event wire is invalid: {error}"), 189 Self::RawJson(error) => write!(f, "signed event raw JSON is invalid: {error}"), 190 Self::RawJsonMismatch => { 191 write!( 192 f, 193 "signed event raw JSON does not match the provided wire event" 194 ) 195 } 196 Self::Envelope(error) => write!(f, "signed event envelope is invalid: {error}"), 197 } 198 } 199 } 200 201 #[cfg(feature = "std")] 202 impl std::error::Error for SignedEventError {} 203 204 impl From<EventEnvelopeError> for SignedEventError { 205 fn from(value: EventEnvelopeError) -> Self { 206 Self::Envelope(value) 207 } 208 } 209 210 impl SignedEvent { 211 pub fn new(parts: SignedEventParts) -> Result<Self, SignedEventError> { 212 let id = EventId::parse(parts.id) 213 .map_err(EventEnvelopeError::InvalidId)? 214 .into_string(); 215 let pubkey = parse_public_key(parts.pubkey) 216 .map_err(EventEnvelopeError::InvalidAuthor)? 217 .to_hex(); 218 let sig = EventSignature::parse(parts.sig) 219 .map_err(EventEnvelopeError::InvalidSignature)? 220 .into_string(); 221 let wire = Nip01EventWire { 222 id, 223 pubkey, 224 created_at: parts.created_at, 225 kind: parts.kind, 226 tags: parts.tags, 227 content: parts.content, 228 sig, 229 extra: Default::default(), 230 }; 231 Self::from_wire_verified_id(wire, parts.raw_json) 232 } 233 234 pub fn from_wire_verified_id( 235 wire: Nip01EventWire, 236 raw_json: impl Into<String>, 237 ) -> Result<Self, SignedEventError> { 238 wire.verify_id().map_err(SignedEventError::Wire)?; 239 let raw_json = raw_json.into(); 240 let parsed = 241 Nip01EventWire::parse_json(raw_json.as_str()).map_err(SignedEventError::RawJson)?; 242 crate::require_invariant(parsed == wire, &|| SignedEventError::RawJsonMismatch)?; 243 let envelope = wire 244 .clone() 245 .into_unverified_envelope() 246 .map_err(SignedEventError::Envelope)?; 247 Ok(Self { 248 envelope, 249 wire, 250 raw_json, 251 }) 252 } 253 254 #[inline] 255 pub fn envelope(&self) -> &EventEnvelope { 256 &self.envelope 257 } 258 259 #[inline] 260 pub fn wire(&self) -> &Nip01EventWire { 261 &self.wire 262 } 263 264 #[inline] 265 pub fn raw_json(&self) -> &str { 266 self.raw_json.as_str() 267 } 268 269 #[inline] 270 pub fn id(&self) -> &EventId { 271 self.envelope.id() 272 } 273 274 /// Returns the canonical NIP-01 event-id encoding retained by the wire boundary. 275 #[inline] 276 pub fn id_str(&self) -> &str { 277 self.wire.id.as_str() 278 } 279 280 #[inline] 281 pub fn id_hex(&self) -> String { 282 self.envelope.id().to_hex() 283 } 284 285 #[inline] 286 pub fn pubkey(&self) -> &PublicKey { 287 self.envelope.author() 288 } 289 290 #[inline] 291 pub fn created_at(&self) -> u64 { 292 self.envelope.created_at_u64() 293 } 294 295 #[inline] 296 pub fn kind(&self) -> u32 { 297 self.envelope.kind_u32() 298 } 299 300 pub fn tags_as_vec(&self) -> Vec<Vec<String>> { 301 self.envelope.tags_as_vec() 302 } 303 304 #[inline] 305 pub fn content(&self) -> &str { 306 self.envelope.content() 307 } 308 309 #[inline] 310 pub fn sig(&self) -> &EventSignature { 311 self.envelope.sig() 312 } 313 314 /// Returns the canonical NIP-01 signature encoding retained by the wire boundary. 315 #[inline] 316 pub fn sig_str(&self) -> &str { 317 self.wire.sig.as_str() 318 } 319 320 #[inline] 321 pub fn signature_hex(&self) -> String { 322 self.envelope.sig().to_hex() 323 } 324 } 325 326 pub fn compute_nip01_event_id( 327 pubkey: &str, 328 created_at: u64, 329 kind: u32, 330 tags: &[Vec<String>], 331 content: &str, 332 ) -> Result<EventId, DraftError> { 333 parse_public_key(pubkey)?; 334 Ok(compute_nip01_event_id_for_valid_pubkey( 335 pubkey, created_at, kind, tags, content, 336 )) 337 } 338 339 pub fn nip01_event_id_preimage( 340 pubkey: &str, 341 created_at: u64, 342 kind: u32, 343 tags: &[Vec<String>], 344 content: &str, 345 ) -> Result<String, DraftError> { 346 parse_public_key(pubkey)?; 347 Ok(nip01_event_id_preimage_for_valid_pubkey( 348 pubkey, created_at, kind, tags, content, 349 )) 350 } 351 352 #[cfg_attr(coverage_nightly, coverage(off))] 353 fn compute_nip01_event_id_for_valid_pubkey( 354 pubkey: &str, 355 created_at: u64, 356 kind: u32, 357 tags: &[Vec<String>], 358 content: &str, 359 ) -> EventId { 360 compute_canonical_nip01_event_id(pubkey, created_at, kind, tags, content) 361 .expect("a validated public key always produces a canonical event id") 362 } 363 364 #[cfg_attr(coverage_nightly, coverage(off))] 365 fn nip01_event_id_preimage_for_valid_pubkey( 366 pubkey: &str, 367 created_at: u64, 368 kind: u32, 369 tags: &[Vec<String>], 370 content: &str, 371 ) -> String { 372 canonical_nip01_event_id_preimage(pubkey, created_at, kind, tags, content) 373 .expect("a validated public key always produces a canonical preimage") 374 } 375 376 #[cfg(test)] 377 #[cfg_attr(coverage_nightly, coverage(off))] 378 mod tests { 379 use super::*; 380 use crate::envelope::kind::KIND_POST; 381 382 fn hex_64(character: char) -> String { 383 crate::test_valid_hex_64(character) 384 } 385 386 fn hex_128(character: char) -> String { 387 core::iter::repeat_n(character, 128).collect() 388 } 389 390 fn verified_wire(content: &str) -> Nip01EventWire { 391 let pubkey = hex_64('a'); 392 let id = compute_canonical_nip01_event_id(&pubkey, 1_700_000_000, KIND_POST, &[], content) 393 .expect("event id") 394 .into_string(); 395 Nip01EventWire { 396 id, 397 pubkey, 398 created_at: 1_700_000_000, 399 kind: KIND_POST, 400 tags: Vec::new(), 401 content: content.to_owned(), 402 sig: hex_128('b'), 403 extra: Default::default(), 404 } 405 } 406 407 fn raw_json_for_wire(wire: &Nip01EventWire) -> String { 408 serde_json::to_string(&serde_json::json!({ 409 "id": wire.id, 410 "pubkey": wire.pubkey, 411 "created_at": wire.created_at, 412 "kind": wire.kind, 413 "tags": wire.tags, 414 "content": wire.content, 415 "sig": wire.sig, 416 })) 417 .expect("raw JSON") 418 } 419 420 #[test] 421 fn signed_event_preserves_verified_wire_and_typed_accessors() { 422 let wire = verified_wire("hello"); 423 let raw_json = raw_json_for_wire(&wire); 424 let signed = SignedEvent::from_wire_verified_id(wire.clone(), raw_json.clone()) 425 .expect("signed event"); 426 427 assert_eq!(signed.wire(), &wire); 428 assert_eq!(signed.raw_json(), raw_json); 429 assert_eq!(signed.envelope().id_hex(), signed.id_hex()); 430 assert_eq!(signed.id().to_hex(), signed.id_hex()); 431 assert_eq!(signed.pubkey().to_hex(), wire.pubkey); 432 assert_eq!(signed.created_at(), wire.created_at); 433 assert_eq!(signed.kind(), wire.kind); 434 assert_eq!(signed.tags_as_vec(), wire.tags); 435 assert_eq!(signed.content(), wire.content); 436 assert_eq!(signed.sig().to_hex(), signed.signature_hex()); 437 assert_eq!(signed.sig_str(), wire.sig); 438 } 439 440 #[test] 441 fn signed_event_rejects_noncanonical_or_mismatched_wire() { 442 let wire = verified_wire("hello"); 443 let raw_json = raw_json_for_wire(&wire); 444 let mut different_wire = verified_wire("different"); 445 let error = 446 SignedEvent::from_wire_verified_id(wire.clone(), raw_json_for_wire(&different_wire)) 447 .expect_err("raw JSON mismatch"); 448 assert_eq!(error, SignedEventError::RawJsonMismatch); 449 450 different_wire.id = hex_64('c'); 451 let error = SignedEvent::from_wire_verified_id( 452 different_wire.clone(), 453 raw_json_for_wire(&different_wire), 454 ) 455 .expect_err("event id mismatch"); 456 assert!(matches!(error, SignedEventError::Wire(_))); 457 458 let error = SignedEvent::new(SignedEventParts { 459 id: "not-hex".to_owned(), 460 pubkey: hex_64('a'), 461 created_at: 1, 462 kind: KIND_POST, 463 tags: Vec::new(), 464 content: String::new(), 465 sig: hex_128('b'), 466 raw_json, 467 }) 468 .expect_err("invalid id"); 469 assert!(matches!( 470 error, 471 SignedEventError::Envelope(EventEnvelopeError::InvalidId(_)) 472 )); 473 } 474 475 #[test] 476 fn event_id_helpers_are_canonical_and_reject_invalid_pubkeys() { 477 let expected = "bb46df8e0d14e08773c7c6c88dfbb0925e6432048a2f2e82592afa415462d62a"; 478 let event_id = compute_nip01_event_id(&hex_64('a'), 1_700_000_000, KIND_POST, &[], "hello") 479 .expect("event id"); 480 assert_eq!(event_id.to_hex(), expected); 481 assert_eq!( 482 nip01_event_id_preimage(&hex_64('a'), 1_700_000_000, KIND_POST, &[], "hello") 483 .expect("preimage"), 484 "[0,\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",1700000000,1,[],\"hello\"]" 485 ); 486 487 assert!(matches!( 488 compute_nip01_event_id("not-hex", 1, KIND_POST, &[], ""), 489 Err(DraftError::IdParse(_)) 490 )); 491 assert!(matches!( 492 nip01_event_id_preimage("not-hex", 1, KIND_POST, &[], ""), 493 Err(DraftError::IdParse(_)) 494 )); 495 } 496 497 #[test] 498 fn errors_have_stable_nonempty_messages() { 499 let errors = [ 500 DraftError::UnknownContract("missing".to_owned()), 501 DraftError::ContractNotDraftAuthorable { 502 contract_id: "typed-only".to_owned(), 503 }, 504 DraftError::from(ParseError::Empty), 505 DraftError::from(EventEnvelopeError::NonCanonicalId), 506 DraftError::from(SignedEventError::RawJsonMismatch), 507 ]; 508 for error in errors { 509 assert!(!error.to_string().is_empty()); 510 } 511 } 512 }