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 }