lib

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

authored.rs (5984B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{string::ToString, vec, vec::Vec};
      3 
      4 use radroots_event::{
      5     envelope::kind::KIND_DELETION_REQUEST, post::deletion::AuthoredNip09DeletionRequest,
      6     wire::Nip01EventWireParts,
      7 };
      8 
      9 /// Builds deterministic unsigned kind-5 wire parts for a strict NIP-09
     10 /// deletion request.
     11 ///
     12 /// Every tag has exactly two elements. Canonical event targets precede
     13 /// canonical address targets, followed by the complete unique ascending set of
     14 /// target-kind advisories.
     15 pub fn authored_nip09_deletion_request_to_wire_parts(
     16     request: &AuthoredNip09DeletionRequest,
     17 ) -> Nip01EventWireParts {
     18     let mut tags = Vec::with_capacity(
     19         request
     20             .target_count()
     21             .saturating_add(request.kind_hints().len()),
     22     );
     23     tags.extend(
     24         request
     25             .event_targets()
     26             .iter()
     27             .map(|target| vec!["e".to_string(), target.event_id().to_hex()]),
     28     );
     29     tags.extend(
     30         request
     31             .address_targets()
     32             .iter()
     33             .map(|target| vec!["a".to_string(), target.coordinate().as_str().to_string()]),
     34     );
     35     tags.extend(
     36         request
     37             .kind_hints()
     38             .iter()
     39             .map(|kind| vec!["k".to_string(), kind.to_string()]),
     40     );
     41 
     42     Nip01EventWireParts {
     43         kind: KIND_DELETION_REQUEST,
     44         content: request.content().to_string(),
     45         tags,
     46     }
     47 }
     48 
     49 #[cfg(test)]
     50 mod tests {
     51     use super::*;
     52     use radroots_event::post::deletion::{
     53         Nip09DeletionAddressTarget, Nip09DeletionEventTarget,
     54         RADROOTS_NIP09_DELETION_EVENT_WIRE_MAX_BYTES,
     55     };
     56 
     57     fn h(character: char) -> String {
     58         crate::test_fixtures::fixture_public_key_hex(character)
     59     }
     60 
     61     #[test]
     62     fn emits_exact_canonical_two_element_target_and_kind_tags() {
     63         let request = AuthoredNip09DeletionRequest::new(
     64             "superseded",
     65             vec![
     66                 Nip09DeletionEventTarget::parse(h('f'), 30_402).expect("event target"),
     67                 Nip09DeletionEventTarget::parse(h('a'), 1).expect("event target"),
     68             ],
     69             vec![
     70                 Nip09DeletionAddressTarget::parse(format!("31923:{}:market", h('e')))
     71                     .expect("address target"),
     72                 Nip09DeletionAddressTarget::parse(format!("30402:{}:produce", h('b')))
     73                     .expect("address target"),
     74             ],
     75         )
     76         .expect("deletion request");
     77 
     78         let parts = authored_nip09_deletion_request_to_wire_parts(&request);
     79         assert_eq!(parts.kind, KIND_DELETION_REQUEST);
     80         assert_eq!(parts.content, "superseded");
     81         assert_eq!(
     82             parts.tags,
     83             vec![
     84                 vec!["e".to_string(), h('a')],
     85                 vec!["e".to_string(), h('f')],
     86                 vec!["a".to_string(), format!("30402:{}:produce", h('b'))],
     87                 vec!["a".to_string(), format!("31923:{}:market", h('e'))],
     88                 vec!["k".to_string(), "1".to_string()],
     89                 vec!["k".to_string(), "30402".to_string()],
     90                 vec!["k".to_string(), "31923".to_string()],
     91             ]
     92         );
     93         assert!(parts.tags.iter().all(|tag| tag.len() == 2));
     94     }
     95 
     96     #[test]
     97     fn core_wire_estimator_matches_emitted_json_at_the_strict_boundary() {
     98         let target = Nip09DeletionEventTarget::parse(h('a'), 1).expect("event target");
     99         let mut lower = 0usize;
    100         let mut upper = radroots_event::post::deletion::RADROOTS_NIP09_DELETION_CONTENT_MAX_BYTES;
    101         while lower < upper {
    102             let candidate = lower + (upper - lower).div_ceil(2);
    103             if AuthoredNip09DeletionRequest::new(
    104                 "\u{0001}".repeat(candidate),
    105                 vec![target.clone()],
    106                 Vec::new(),
    107             )
    108             .is_ok()
    109             {
    110                 lower = candidate;
    111             } else {
    112                 upper = candidate - 1;
    113             }
    114         }
    115 
    116         let request =
    117             AuthoredNip09DeletionRequest::new("\u{0001}".repeat(lower), vec![target], Vec::new())
    118                 .expect("largest content fitting strict signed wire");
    119         let parts = authored_nip09_deletion_request_to_wire_parts(&request);
    120         let json = serde_json::json!({
    121             "id": "0".repeat(64),
    122             "pubkey": "0".repeat(64),
    123             "created_at": u64::MAX,
    124             "kind": KIND_DELETION_REQUEST,
    125             "tags": parts.tags,
    126             "content": parts.content,
    127             "sig": "0".repeat(128),
    128         })
    129         .to_string();
    130         assert!(json.len() <= RADROOTS_NIP09_DELETION_EVENT_WIRE_MAX_BYTES);
    131         assert_eq!(json.len(), request.maximum_signed_event_wire_bytes());
    132     }
    133 
    134     #[test]
    135     fn strict_wire_size_equals_independent_max_u64_serialization_with_escaping() {
    136         let request = AuthoredNip09DeletionRequest::new(
    137             "reason \"quoted\" \\\n\u{0001} 🌱",
    138             vec![Nip09DeletionEventTarget::parse(h('A'), 5).expect("event target")],
    139             vec![
    140                 Nip09DeletionAddressTarget::parse(format!(
    141                     "30000:{}:victoria:\"crop\\row:\u{0002}:雪",
    142                     h('B')
    143                 ))
    144                 .expect("address target"),
    145             ],
    146         )
    147         .expect("escaped deletion request");
    148         let parts = authored_nip09_deletion_request_to_wire_parts(&request);
    149         let serialized = format!(
    150             "{{\"id\":{},\"pubkey\":{},\"created_at\":{},\"kind\":5,\"tags\":{},\"content\":{},\"sig\":{}}}",
    151             serde_json::to_string(&"0".repeat(64)).expect("id JSON"),
    152             serde_json::to_string(&"0".repeat(64)).expect("pubkey JSON"),
    153             u64::MAX,
    154             serde_json::to_string(&parts.tags).expect("tags JSON"),
    155             serde_json::to_string(&parts.content).expect("content JSON"),
    156             serde_json::to_string(&"0".repeat(128)).expect("signature JSON"),
    157         );
    158 
    159         assert_eq!(request.maximum_signed_event_wire_bytes(), serialized.len());
    160     }
    161 }