lib

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

event_verify.rs (6883B)


      1 //! NIP-01 identity/signature verification for inbound Nostr events.
      2 //!
      3 //! Cryptographic verification does not establish product-contract admission,
      4 //! referenced-event existence, relay trust, or application authorization.
      5 
      6 #![forbid(unsafe_code)]
      7 
      8 use radroots_event::{
      9     admission::{Error as VerificationError, RawEvent, SignatureVerifier},
     10     envelope::EventEnvelope,
     11 };
     12 use radroots_event_codec::verify;
     13 
     14 /// Deterministic BIP-340 verification supplied by the Nostr adapter.
     15 #[derive(Clone, Copy, Debug, Default)]
     16 pub struct NostrSignatureVerifier;
     17 
     18 impl SignatureVerifier for NostrSignatureVerifier {
     19     fn verify_signature(&self, event: &EventEnvelope) -> Result<(), VerificationError> {
     20         validate_nostr_kind(event)?;
     21         let public_key = nostr::secp256k1::XOnlyPublicKey::from_slice(event.author().as_bytes())
     22             .map_err(|_| VerificationError::MalformedEnvelope)?;
     23         let signature = nostr::secp256k1::schnorr::Signature::from_slice(event.sig().as_bytes())
     24             .map_err(|_| VerificationError::MalformedEnvelope)?;
     25         let message = nostr::secp256k1::Message::from_digest(*event.id().as_bytes());
     26         nostr::SECP256K1
     27             .verify_schnorr(&signature, &message, &public_key)
     28             .map_err(|_| VerificationError::SignatureInvalid)
     29     }
     30 }
     31 
     32 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     33 pub enum Verification {
     34     Verified,
     35     IdVerified,
     36     IdMismatch,
     37     SignatureInvalid,
     38     MalformedEnvelope,
     39 }
     40 
     41 pub fn verify_event(event: &EventEnvelope) -> Verification {
     42     let result = validate_nostr_kind(event)
     43         .and_then(|()| verify::id(RawEvent::new(event.clone())))
     44         .and_then(|event| verify::signature(event, &NostrSignatureVerifier));
     45     match result {
     46         Ok(_) => Verification::Verified,
     47         Err(error) => verification_error_status(&error),
     48     }
     49 }
     50 
     51 pub fn verify_event_id(event: &EventEnvelope) -> Verification {
     52     let result = validate_nostr_kind(event).and_then(|()| verify::id(RawEvent::new(event.clone())));
     53     match result {
     54         Ok(_) => Verification::IdVerified,
     55         Err(error) => verification_error_status(&error),
     56     }
     57 }
     58 
     59 fn validate_nostr_kind(event: &EventEnvelope) -> Result<(), VerificationError> {
     60     u16::try_from(event.kind_u32())
     61         .map(|_| ())
     62         .map_err(|_| VerificationError::MalformedEnvelope)
     63 }
     64 
     65 fn verification_error_status(error: &VerificationError) -> Verification {
     66     match error {
     67         VerificationError::IdMismatch { .. } => Verification::IdMismatch,
     68         VerificationError::SignatureInvalid => Verification::SignatureInvalid,
     69         VerificationError::MalformedEnvelope | VerificationError::ContractValidation(_) => {
     70             Verification::MalformedEnvelope
     71         }
     72         _ => Verification::MalformedEnvelope,
     73     }
     74 }
     75 
     76 #[cfg(test)]
     77 mod tests {
     78     use super::*;
     79     use crate::event_convert::from_nostr;
     80     use crate::events::build_event_unchecked;
     81     use crate::test_fixtures::FIXTURE_ALICE;
     82     use crate::types::{RadrootsNostrKeys, RadrootsNostrTimestamp};
     83     use nostr::SecretKey;
     84     use radroots_event::{
     85         envelope::EventEnvelopeParts, envelope::kind::KIND_POST,
     86         wire::compute_canonical_nip01_event_id,
     87     };
     88 
     89     fn fixture_keys() -> RadrootsNostrKeys {
     90         let secret_key = SecretKey::from_hex(FIXTURE_ALICE.secret_key_hex).expect("secret key");
     91         RadrootsNostrKeys::new(secret_key)
     92     }
     93 
     94     fn signed_event() -> EventEnvelope {
     95         let raw_event = build_event_unchecked(
     96             KIND_POST,
     97             "hello",
     98             vec![vec!["t".to_owned(), "soil".to_owned()]],
     99         )
    100         .expect("builder")
    101         .custom_created_at(RadrootsNostrTimestamp::from_secs(1_700_000_000))
    102         .sign_with_keys(&fixture_keys())
    103         .expect("signed event");
    104         from_nostr(&raw_event).expect("Radroots event envelope")
    105     }
    106 
    107     fn envelope_with(
    108         event: &EventEnvelope,
    109         content: String,
    110         kind: u32,
    111         sig: String,
    112     ) -> EventEnvelope {
    113         EventEnvelope::new(EventEnvelopeParts {
    114             id: event.id_hex(),
    115             author: event.author().to_hex().to_owned(),
    116             created_at: event.created_at_u64(),
    117             kind,
    118             tags: event.tags_as_vec(),
    119             content,
    120             sig,
    121         })
    122         .expect("envelope")
    123     }
    124 
    125     #[test]
    126     fn verifies_signed_event_id_and_signature() {
    127         let event = signed_event();
    128 
    129         assert_eq!(verify_event(&event), Verification::Verified);
    130         assert_eq!(verify_event_id(&event), Verification::IdVerified);
    131     }
    132 
    133     #[test]
    134     fn reports_id_mismatch_before_signature_checks() {
    135         let original = signed_event();
    136         let event = envelope_with(
    137             &original,
    138             "tampered".to_owned(),
    139             original.kind_u32(),
    140             original.signature_hex(),
    141         );
    142 
    143         assert_eq!(verify_event(&event), Verification::IdMismatch);
    144     }
    145 
    146     #[test]
    147     fn reports_signature_invalid_for_valid_id_with_wrong_signature() {
    148         let original = signed_event();
    149         let mut sig = original.signature_hex();
    150         let replacement = if sig.starts_with('0') { "1" } else { "0" };
    151         sig.replace_range(0..1, replacement);
    152         let event = envelope_with(
    153             &original,
    154             original.content().to_owned(),
    155             original.kind_u32(),
    156             sig,
    157         );
    158 
    159         assert_eq!(verify_event(&event), Verification::SignatureInvalid);
    160     }
    161 
    162     #[test]
    163     fn out_of_range_kind_precedes_id_mismatch() {
    164         let original = signed_event();
    165         let event = envelope_with(
    166             &original,
    167             original.content().to_owned(),
    168             u32::from(u16::MAX) + 1,
    169             original.signature_hex(),
    170         );
    171 
    172         assert_eq!(verify_event(&event), Verification::MalformedEnvelope);
    173         assert_eq!(verify_event_id(&event), Verification::MalformedEnvelope);
    174     }
    175 
    176     #[test]
    177     fn reports_malformed_envelope_for_id_valid_out_of_range_kind() {
    178         let original = signed_event();
    179         let kind = u32::from(u16::MAX) + 1;
    180         let id = compute_canonical_nip01_event_id(
    181             &original.author().to_hex(),
    182             original.created_at_u64(),
    183             kind,
    184             &original.tags_as_vec(),
    185             original.content(),
    186         )
    187         .expect("canonical id")
    188         .into_string();
    189         let event = EventEnvelope::new(EventEnvelopeParts {
    190             id,
    191             author: original.author().to_hex().to_owned(),
    192             created_at: original.created_at_u64(),
    193             kind,
    194             tags: original.tags_as_vec(),
    195             content: original.content().to_owned(),
    196             sig: original.signature_hex(),
    197         })
    198         .expect("envelope");
    199 
    200         assert_eq!(verify_event(&event), Verification::MalformedEnvelope);
    201         assert_eq!(verify_event_id(&event), Verification::MalformedEnvelope);
    202     }
    203 }