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 }