tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

pocket_event_validation.rs (7625B)


      1 #![forbid(unsafe_code)]
      2 
      3 use crate::errors::BaseRelayError;
      4 use tangle_protocol::{EventId, Kind, PublicKeyHex, UnixTimestamp};
      5 use tangle_store_pocket::PocketEvent;
      6 
      7 pub(crate) fn pocket_event_id(event: &PocketEvent) -> Result<EventId, BaseRelayError> {
      8     EventId::new(&event.id().as_hex_string()).map_err(BaseRelayError::error)
      9 }
     10 
     11 pub(crate) fn pocket_event_pubkey(event: &PocketEvent) -> Result<PublicKeyHex, BaseRelayError> {
     12     PublicKeyHex::new(&event.pubkey().as_hex_string()).map_err(BaseRelayError::error)
     13 }
     14 
     15 pub(crate) fn pocket_event_kind(event: &PocketEvent) -> Result<Kind, BaseRelayError> {
     16     Kind::new(u64::from(event.kind().as_u16())).map_err(BaseRelayError::error)
     17 }
     18 
     19 pub(crate) fn pocket_event_created_at(event: &PocketEvent) -> UnixTimestamp {
     20     UnixTimestamp::new(event.created_at().as_u64())
     21 }
     22 
     23 pub(crate) fn validate_pocket_event_shape(
     24     event: &PocketEvent,
     25     max_event_tags: usize,
     26     max_content_length: usize,
     27 ) -> Result<(), BaseRelayError> {
     28     let tags = event.tags().map_err(|error| {
     29         BaseRelayError::invalid(format!("malformed Pocket event tags: {error}"))
     30     })?;
     31     if tags.count() > max_event_tags {
     32         return Err(BaseRelayError::invalid(format!(
     33             "event tag count exceeds runtime max_event_tags {max_event_tags}"
     34         )));
     35     }
     36     if event.content().len() > max_content_length {
     37         return Err(BaseRelayError::invalid(format!(
     38             "event content length exceeds runtime max_content_length {max_content_length}"
     39         )));
     40     }
     41     Ok(())
     42 }
     43 
     44 pub(crate) fn is_pocket_nip70_protected_event(event: &PocketEvent) -> Result<bool, BaseRelayError> {
     45     let tags = event.tags().map_err(|error| {
     46         BaseRelayError::invalid(format!("malformed Pocket event tags: {error}"))
     47     })?;
     48     for tag in tags.iter() {
     49         if tag
     50             .into_iter()
     51             .next()
     52             .map(|value| value == b"-")
     53             .unwrap_or(false)
     54         {
     55             return Ok(true);
     56         }
     57     }
     58     Ok(false)
     59 }
     60 
     61 pub(crate) fn verify_pocket_event_signature(event: &PocketEvent) -> Result<(), BaseRelayError> {
     62     event
     63         .verify()
     64         .map_err(|error| BaseRelayError::invalid(error.to_string()))
     65 }
     66 
     67 #[cfg(test)]
     68 pub(crate) fn pocket_canonical_event_json(event: &PocketEvent) -> Result<String, BaseRelayError> {
     69     use std::str;
     70 
     71     let tags = event
     72         .tags()
     73         .map_err(|error| BaseRelayError::invalid(format!("malformed Pocket event tags: {error}")))?
     74         .iter()
     75         .map(|tag| {
     76             tag.map(|value| {
     77                 str::from_utf8(value)
     78                     .map(|value| serde_json::Value::String(value.to_owned()))
     79                     .map_err(|error| BaseRelayError::invalid(error.to_string()))
     80             })
     81             .collect::<Result<Vec<_>, _>>()
     82             .map(serde_json::Value::Array)
     83         })
     84         .collect::<Result<Vec<_>, _>>()?;
     85     let content = str::from_utf8(event.content())
     86         .map_err(|error| BaseRelayError::invalid(error.to_string()))?;
     87     Ok(serde_json::json!([
     88         0,
     89         event.pubkey().as_hex_string(),
     90         event.created_at().as_u64(),
     91         u32::from(event.kind().as_u16()),
     92         tags,
     93         content
     94     ])
     95     .to_string())
     96 }
     97 
     98 #[cfg(test)]
     99 mod tests {
    100     use super::{
    101         is_pocket_nip70_protected_event, pocket_canonical_event_json, pocket_event_id,
    102         pocket_event_pubkey, validate_pocket_event_shape, verify_pocket_event_signature,
    103     };
    104     use crate::pocket_conversion::tangle_event_to_pocket;
    105     use tangle_crypto::RelaySigner;
    106     use tangle_protocol::{Tag, event_to_value};
    107     use tangle_store_pocket::{
    108         PocketKind, PocketOwnedEvent, PocketOwnedTags, PocketTime, parse_pocket_event_json,
    109     };
    110     use tangle_test_support::{FixtureKey, tangle_v2_event};
    111 
    112     #[test]
    113     fn pocket_event_validation_verifies_valid_and_invalid_signatures() {
    114         let tags = PocketOwnedTags::empty();
    115         let pocket = pocket_event(12, 1_714_124_433, 1, &tags, b"hello");
    116 
    117         assert_eq!(verify_pocket_event_signature(&pocket), Ok(()));
    118 
    119         let signature_source = pocket_event(11, 1_714_124_433, 1, &tags, b"hello");
    120         let wrong_signature = PocketOwnedEvent::new(
    121             pocket.id(),
    122             pocket.kind(),
    123             pocket.pubkey(),
    124             signature_source.sig(),
    125             pocket.tags().expect("tags"),
    126             pocket.created_at(),
    127             pocket.content(),
    128         )
    129         .expect("wrong signature");
    130         assert!(
    131             verify_pocket_event_signature(&wrong_signature)
    132                 .expect_err("signature")
    133                 .prefixed_message()
    134                 .starts_with("invalid:")
    135         );
    136 
    137         let id_source = pocket_event(12, 1_714_124_433, 1, &tags, b"other");
    138         let wrong_id = PocketOwnedEvent::new(
    139             id_source.id(),
    140             pocket.kind(),
    141             pocket.pubkey(),
    142             pocket.sig(),
    143             pocket.tags().expect("tags"),
    144             pocket.created_at(),
    145             pocket.content(),
    146         )
    147         .expect("wrong id");
    148         assert!(
    149             verify_pocket_event_signature(&wrong_id)
    150                 .expect_err("id")
    151                 .prefixed_message()
    152                 .starts_with("invalid:")
    153         );
    154     }
    155 
    156     #[test]
    157     fn pocket_event_validation_detects_protected_tags_and_shape_limits() {
    158         let event = tangle_v2_event(
    159             FixtureKey::Member,
    160             1_714_124_433,
    161             1,
    162             vec![Tag::from_parts("-", &[]).expect("protected")],
    163             "hello",
    164         )
    165         .expect("event");
    166         let pocket = tangle_event_to_pocket(&event).expect("pocket");
    167 
    168         assert!(is_pocket_nip70_protected_event(&pocket).expect("protected"));
    169         assert_eq!(validate_pocket_event_shape(&pocket, 1, 5), Ok(()));
    170         assert_eq!(
    171             validate_pocket_event_shape(&pocket, 0, 5)
    172                 .expect_err("tags")
    173                 .prefixed_message(),
    174             "invalid: event tag count exceeds runtime max_event_tags 0"
    175         );
    176         assert_eq!(
    177             validate_pocket_event_shape(&pocket, 1, 4)
    178                 .expect_err("content")
    179                 .prefixed_message(),
    180             "invalid: event content length exceeds runtime max_content_length 4"
    181         );
    182     }
    183 
    184     #[test]
    185     fn pocket_event_validation_preserves_protocol_canonical_json() {
    186         let event = tangle_v2_event(
    187             FixtureKey::Member,
    188             1_714_124_433,
    189             1,
    190             vec![Tag::from_parts("t", &["market"]).expect("t")],
    191             "hello",
    192         )
    193         .expect("event");
    194         let raw = event_to_value(&event).to_string();
    195         let pocket = parse_pocket_event_json(raw.as_bytes()).expect("pocket");
    196 
    197         assert_eq!(pocket_event_id(&pocket).expect("id"), event.id().clone());
    198         assert_eq!(
    199             pocket_event_pubkey(&pocket).expect("pubkey"),
    200             event.unsigned().pubkey().clone()
    201         );
    202         assert_eq!(
    203             pocket_canonical_event_json(&pocket).expect("canonical"),
    204             event.unsigned().canonical_json()
    205         );
    206     }
    207 
    208     fn pocket_event(
    209         secret_byte: u8,
    210         created_at: u64,
    211         kind: u16,
    212         tags: &PocketOwnedTags,
    213         content: &[u8],
    214     ) -> PocketOwnedEvent {
    215         let secret = format!("{secret_byte:02x}").repeat(32);
    216         RelaySigner::from_secret_hex(&secret)
    217             .expect("signer")
    218             .sign_pocket_event(
    219                 PocketKind::from_u16(kind),
    220                 tags,
    221                 PocketTime::from_u64(created_at),
    222                 content,
    223             )
    224             .expect("pocket event")
    225     }
    226 }