lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }