lib

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

event_verify.rs (4541B)


      1 #![forbid(unsafe_code)]
      2 
      3 use alloc::vec::Vec;
      4 use core::str::FromStr;
      5 
      6 use crate::types::{
      7     RadrootsNostrEvent as RadrootsNostrRawEvent, RadrootsNostrEventId, RadrootsNostrKind,
      8     RadrootsNostrPublicKey, RadrootsNostrTag, RadrootsNostrTimestamp,
      9 };
     10 use nostr::secp256k1::schnorr::Signature;
     11 use radroots_events::RadrootsNostrEvent;
     12 
     13 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     14 pub enum RadrootsNostrEventVerification {
     15     Verified,
     16     IdVerified,
     17     IdMismatch,
     18     SignatureInvalid,
     19     MalformedEnvelope,
     20 }
     21 
     22 pub fn radroots_nostr_verify_event(event: &RadrootsNostrEvent) -> RadrootsNostrEventVerification {
     23     let Some(raw_event) = raw_event_from_radroots(event) else {
     24         return RadrootsNostrEventVerification::MalformedEnvelope;
     25     };
     26     if !raw_event.verify_id() {
     27         return RadrootsNostrEventVerification::IdMismatch;
     28     }
     29     if !raw_event.verify_signature() {
     30         return RadrootsNostrEventVerification::SignatureInvalid;
     31     }
     32     RadrootsNostrEventVerification::Verified
     33 }
     34 
     35 pub fn radroots_nostr_verify_event_id(
     36     event: &RadrootsNostrEvent,
     37 ) -> RadrootsNostrEventVerification {
     38     let Some(raw_event) = raw_event_from_radroots(event) else {
     39         return RadrootsNostrEventVerification::MalformedEnvelope;
     40     };
     41     if raw_event.verify_id() {
     42         RadrootsNostrEventVerification::IdVerified
     43     } else {
     44         RadrootsNostrEventVerification::IdMismatch
     45     }
     46 }
     47 
     48 fn raw_event_from_radroots(event: &RadrootsNostrEvent) -> Option<RadrootsNostrRawEvent> {
     49     let id = RadrootsNostrEventId::from_hex(event.id.as_str()).ok()?;
     50     let public_key = RadrootsNostrPublicKey::from_hex(event.author.as_str()).ok()?;
     51     let kind_u16 = u16::try_from(event.kind).ok()?;
     52     let mut tags = Vec::with_capacity(event.tags.len());
     53     for tag in event.tags.iter().cloned() {
     54         tags.push(RadrootsNostrTag::parse(tag).ok()?);
     55     }
     56     let sig = Signature::from_str(event.sig.as_str()).ok()?;
     57     Some(RadrootsNostrRawEvent::new(
     58         id,
     59         public_key,
     60         RadrootsNostrTimestamp::from_secs(u64::from(event.created_at)),
     61         RadrootsNostrKind::Custom(kind_u16),
     62         tags,
     63         event.content.clone(),
     64         sig,
     65     ))
     66 }
     67 
     68 #[cfg(test)]
     69 mod tests {
     70     use super::*;
     71     use crate::event_convert::radroots_event_from_nostr;
     72     use crate::events::radroots_nostr_build_event;
     73     use crate::test_fixtures::FIXTURE_ALICE;
     74     use crate::types::{RadrootsNostrKeys, RadrootsNostrSecretKey};
     75     use radroots_events::kinds::KIND_POST;
     76 
     77     fn fixture_keys() -> RadrootsNostrKeys {
     78         let secret_key =
     79             RadrootsNostrSecretKey::from_hex(FIXTURE_ALICE.secret_key_hex).expect("secret key");
     80         RadrootsNostrKeys::new(secret_key)
     81     }
     82 
     83     fn signed_event() -> RadrootsNostrEvent {
     84         let raw_event = radroots_nostr_build_event(
     85             KIND_POST,
     86             "hello",
     87             vec![vec!["t".to_owned(), "soil".to_owned()]],
     88         )
     89         .expect("builder")
     90         .custom_created_at(RadrootsNostrTimestamp::from_secs(1_700_000_000))
     91         .sign_with_keys(&fixture_keys())
     92         .expect("signed event");
     93         radroots_event_from_nostr(&raw_event)
     94     }
     95 
     96     #[test]
     97     fn verifies_signed_event_id_and_signature() {
     98         let event = signed_event();
     99 
    100         assert_eq!(
    101             radroots_nostr_verify_event(&event),
    102             RadrootsNostrEventVerification::Verified
    103         );
    104         assert_eq!(
    105             radroots_nostr_verify_event_id(&event),
    106             RadrootsNostrEventVerification::IdVerified
    107         );
    108     }
    109 
    110     #[test]
    111     fn reports_id_mismatch_before_signature_checks() {
    112         let mut event = signed_event();
    113         event.content = "tampered".to_owned();
    114 
    115         assert_eq!(
    116             radroots_nostr_verify_event(&event),
    117             RadrootsNostrEventVerification::IdMismatch
    118         );
    119     }
    120 
    121     #[test]
    122     fn reports_signature_invalid_for_valid_id_with_wrong_signature() {
    123         let mut event = signed_event();
    124         let replacement = if event.sig.starts_with('0') { "1" } else { "0" };
    125         event.sig.replace_range(0..1, replacement);
    126 
    127         assert_eq!(
    128             radroots_nostr_verify_event(&event),
    129             RadrootsNostrEventVerification::SignatureInvalid
    130         );
    131     }
    132 
    133     #[test]
    134     fn reports_malformed_envelope_for_unparseable_wire_fields() {
    135         let mut event = signed_event();
    136         event.kind = u32::from(u16::MAX) + 1;
    137 
    138         assert_eq!(
    139             radroots_nostr_verify_event(&event),
    140             RadrootsNostrEventVerification::MalformedEnvelope
    141         );
    142     }
    143 }