nip46_verification.rs (19737B)
1 //! Cryptographic and typed verification for structurally admitted NIP-46 input. 2 3 use core::fmt; 4 use std::error::Error; 5 6 use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD}; 7 use nostr::{Event, Kind}; 8 use radroots_nostr_connect::{ 9 message::{RPC_KIND, RequestMessage}, 10 method::METHOD_MAX_BYTES, 11 }; 12 use serde::Deserialize; 13 14 use crate::{ 15 MycBoundedNip46Event, MycBoundedNip46Request, MycNip46ClientPublicKey, MycNip46EventId, 16 MycNip46RequestId, MycProviderBinding, MycProviderPublicIdentity, MycProviderRole, 17 }; 18 19 const NIP04_IV_BYTES: usize = 16; 20 const NIP04_BLOCK_BYTES: usize = 16; 21 const NIP44_VERSION_V2: u8 = 2; 22 const NIP44_NONCE_BYTES: usize = 32; 23 const NIP44_LENGTH_PREFIX_BYTES: usize = 2; 24 const NIP44_HMAC_BYTES: usize = 32; 25 const NIP44_MIN_PADDED_BYTES: usize = 32; 26 const NIP44_MAX_PADDED_BYTES: usize = 65_536; 27 28 /// Encryption envelope declared by one verified NIP-46 event. 29 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 30 pub enum MycNip46EncryptionContext { 31 Nip04, 32 Nip44V2, 33 } 34 35 /// Injected UTC second at which an inbound event is verified. 36 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 37 pub struct MycNip46ObservedAtUnixSeconds(u64); 38 39 impl MycNip46ObservedAtUnixSeconds { 40 /// Validates a positive instant representable by SQLite's signed integer. 41 pub fn new(value: u64) -> Result<Self, MycNip46VerificationError> { 42 if value == 0 || i64::try_from(value).is_err() { 43 return Err(verification_error( 44 MycNip46VerificationErrorKind::InvalidObservationTime, 45 )); 46 } 47 Ok(Self(value)) 48 } 49 50 /// Returns the injected UTC second. 51 #[must_use] 52 pub const fn get(self) -> u64 { 53 self.0 54 } 55 } 56 57 /// Explicit caller-selected authored-time window with no implicit default. 58 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 59 pub struct MycNip46AuthoredTimePolicy { 60 maximum_past_seconds: u64, 61 maximum_future_seconds: u64, 62 } 63 64 impl MycNip46AuthoredTimePolicy { 65 /// Validates inclusive past and future tolerances. 66 pub fn new( 67 maximum_past_seconds: u64, 68 maximum_future_seconds: u64, 69 ) -> Result<Self, MycNip46VerificationError> { 70 if i64::try_from(maximum_past_seconds).is_err() 71 || i64::try_from(maximum_future_seconds).is_err() 72 { 73 return Err(verification_error( 74 MycNip46VerificationErrorKind::InvalidTimePolicy, 75 )); 76 } 77 Ok(Self { 78 maximum_past_seconds, 79 maximum_future_seconds, 80 }) 81 } 82 83 /// Returns the inclusive maximum event age. 84 #[must_use] 85 pub const fn maximum_past_seconds(self) -> u64 { 86 self.maximum_past_seconds 87 } 88 89 /// Returns the inclusive maximum future skew. 90 #[must_use] 91 pub const fn maximum_future_seconds(self) -> u64 { 92 self.maximum_future_seconds 93 } 94 } 95 96 /// Stable source-free classification for NIP-46 verification failures. 97 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 98 pub enum MycNip46VerificationErrorKind { 99 InvalidObservationTime, 100 InvalidTimePolicy, 101 InvalidTransportBinding, 102 MalformedEvent, 103 NonCanonicalEvent, 104 InvalidKind, 105 InvalidSender, 106 InvalidReceiver, 107 InvalidEventId, 108 InvalidSignature, 109 AuthoredTimeRejected, 110 InvalidNip04Envelope, 111 InvalidNip44Envelope, 112 InvalidTypedRequest, 113 } 114 115 impl MycNip46VerificationErrorKind { 116 const fn message(self) -> &'static str { 117 match self { 118 Self::InvalidObservationTime => "NIP-46 observation time is invalid", 119 Self::InvalidTimePolicy => "NIP-46 authored-time policy is invalid", 120 Self::InvalidTransportBinding => "NIP-46 transport binding is invalid", 121 Self::MalformedEvent => "NIP-46 event cannot be decoded", 122 Self::NonCanonicalEvent => "NIP-46 event encoding is not canonical", 123 Self::InvalidKind => "NIP-46 event kind is invalid", 124 Self::InvalidSender => "NIP-46 event sender is invalid", 125 Self::InvalidReceiver => "NIP-46 event receiver is invalid", 126 Self::InvalidEventId => "NIP-46 event identifier is invalid", 127 Self::InvalidSignature => "NIP-46 event signature is invalid", 128 Self::AuthoredTimeRejected => "NIP-46 event authored time is outside policy", 129 Self::InvalidNip04Envelope => "NIP-46 NIP-04 envelope is invalid", 130 Self::InvalidNip44Envelope => "NIP-46 NIP-44 envelope is invalid", 131 Self::InvalidTypedRequest => "NIP-46 typed request is invalid", 132 } 133 } 134 } 135 136 /// One redacted NIP-46 verification failure. 137 #[derive(Clone, Copy, PartialEq, Eq)] 138 pub struct MycNip46VerificationError { 139 kind: MycNip46VerificationErrorKind, 140 } 141 142 impl MycNip46VerificationError { 143 /// Returns the stable failure classification. 144 #[must_use] 145 pub const fn kind(self) -> MycNip46VerificationErrorKind { 146 self.kind 147 } 148 } 149 150 impl fmt::Debug for MycNip46VerificationError { 151 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 152 formatter 153 .debug_struct("MycNip46VerificationError") 154 .field("kind", &self.kind) 155 .finish() 156 } 157 } 158 159 impl fmt::Display for MycNip46VerificationError { 160 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 161 formatter.write_str(self.kind.message()) 162 } 163 } 164 165 impl Error for MycNip46VerificationError {} 166 167 /// One cryptographically verified encrypted NIP-46 event. 168 pub struct MycVerifiedNip46Event { 169 original: Box<[u8]>, 170 encrypted_content: Box<str>, 171 event_id: MycNip46EventId, 172 client_public_key: MycNip46ClientPublicKey, 173 receiver_public_key: MycProviderPublicIdentity, 174 authored_at_unix_seconds: u64, 175 encryption_context: MycNip46EncryptionContext, 176 } 177 178 impl MycVerifiedNip46Event { 179 /// Returns the exact signed event bytes admitted by Step 141. 180 #[must_use] 181 pub fn original_bytes(&self) -> &[u8] { 182 &self.original 183 } 184 185 /// Returns the verified event identity. 186 #[must_use] 187 pub const fn event_id(&self) -> MycNip46EventId { 188 self.event_id 189 } 190 191 /// Returns the verified client transport identity. 192 #[must_use] 193 pub const fn client_public_key(&self) -> &MycNip46ClientPublicKey { 194 &self.client_public_key 195 } 196 197 pub(crate) const fn receiver_public_key(&self) -> &MycProviderPublicIdentity { 198 &self.receiver_public_key 199 } 200 201 /// Returns the verified event authored time. 202 #[must_use] 203 pub const fn authored_at_unix_seconds(&self) -> u64 { 204 self.authored_at_unix_seconds 205 } 206 207 /// Returns the verified encrypted-envelope context. 208 #[must_use] 209 pub const fn encryption_context(&self) -> MycNip46EncryptionContext { 210 self.encryption_context 211 } 212 213 /// Returns the bounded ciphertext for the later governed decryption boundary. 214 #[must_use] 215 pub fn encrypted_content(&self) -> &str { 216 &self.encrypted_content 217 } 218 } 219 220 impl fmt::Debug for MycVerifiedNip46Event { 221 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 222 formatter 223 .debug_struct("MycVerifiedNip46Event") 224 .field("wire_bytes", &self.original.len()) 225 .field("ciphertext_bytes", &self.encrypted_content.len()) 226 .field("authored_at_unix_seconds", &self.authored_at_unix_seconds) 227 .field("encryption_context", &self.encryption_context) 228 .field("identity", &"[redacted]") 229 .finish() 230 } 231 } 232 233 /// One exact typed NIP-46 request decoded from admitted plaintext. 234 pub struct MycVerifiedNip46Request { 235 request_id: MycNip46RequestId, 236 method: Box<str>, 237 canonical_request: Box<[u8]>, 238 parameter_count: usize, 239 parameter_bytes: usize, 240 } 241 242 impl MycVerifiedNip46Request { 243 /// Returns the validated request identity. 244 #[must_use] 245 pub const fn request_id(&self) -> &MycNip46RequestId { 246 &self.request_id 247 } 248 249 /// Returns the canonical typed method spelling. 250 #[must_use] 251 pub fn method(&self) -> &str { 252 &self.method 253 } 254 255 /// Returns the typed parameter count without exposing parameters. 256 #[must_use] 257 pub const fn parameter_count(&self) -> usize { 258 self.parameter_count 259 } 260 261 /// Returns the aggregate typed parameter byte count. 262 #[must_use] 263 pub const fn parameter_bytes(&self) -> usize { 264 self.parameter_bytes 265 } 266 267 pub(crate) fn canonical_request(&self) -> &[u8] { 268 &self.canonical_request 269 } 270 } 271 272 impl fmt::Debug for MycVerifiedNip46Request { 273 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 274 formatter 275 .debug_struct("MycVerifiedNip46Request") 276 .field("method", &"[redacted]") 277 .field("canonical_bytes", &self.canonical_request.len()) 278 .field("parameter_count", &self.parameter_count) 279 .field("parameter_bytes", &self.parameter_bytes) 280 .field("identity", &"[redacted]") 281 .finish() 282 } 283 } 284 285 /// Verifies one already bounded encrypted NIP-46 event without decrypting it. 286 pub fn verify_myc_nip46_event( 287 bounded: MycBoundedNip46Event, 288 expected_transport: &MycProviderBinding, 289 observed_at: MycNip46ObservedAtUnixSeconds, 290 authored_time_policy: MycNip46AuthoredTimePolicy, 291 ) -> Result<MycVerifiedNip46Event, MycNip46VerificationError> { 292 if expected_transport.role() != MycProviderRole::Transport { 293 return Err(verification_error( 294 MycNip46VerificationErrorKind::InvalidTransportBinding, 295 )); 296 } 297 let expected_receiver = expected_transport.expected_identity(); 298 let (original, encrypted_content) = bounded.into_verification_parts(); 299 let canonical: CanonicalEventFields<'_> = serde_json::from_slice(&original) 300 .map_err(|_| verification_error(MycNip46VerificationErrorKind::MalformedEvent))?; 301 let event: Event = serde_json::from_slice(&original) 302 .map_err(|_| verification_error(MycNip46VerificationErrorKind::MalformedEvent))?; 303 304 if canonical.id != event.id.to_hex() 305 || canonical.pubkey != event.pubkey.to_hex() 306 || canonical.sig != event.sig.to_string() 307 || canonical.created_at != event.created_at.as_secs() 308 || canonical.kind != u64::from(event.kind.as_u16()) 309 || canonical.content != event.content 310 { 311 return Err(verification_error( 312 MycNip46VerificationErrorKind::NonCanonicalEvent, 313 )); 314 } 315 if event.kind != Kind::Custom(RPC_KIND) { 316 return Err(verification_error( 317 MycNip46VerificationErrorKind::InvalidKind, 318 )); 319 } 320 let client_public_key = MycNip46ClientPublicKey::new(&event.pubkey.to_hex()) 321 .map_err(|_| verification_error(MycNip46VerificationErrorKind::InvalidSender))?; 322 if event.pubkey.to_hex() == expected_receiver.as_hex() { 323 return Err(verification_error( 324 MycNip46VerificationErrorKind::InvalidSender, 325 )); 326 } 327 let expected_recipient = ["p", expected_receiver.as_hex()]; 328 if event.tags.len() != 1 329 || event.tags.as_slice()[0] 330 .as_slice() 331 .iter() 332 .map(String::as_str) 333 .ne(expected_recipient) 334 { 335 return Err(verification_error( 336 MycNip46VerificationErrorKind::InvalidReceiver, 337 )); 338 } 339 if !event.verify_id() { 340 return Err(verification_error( 341 MycNip46VerificationErrorKind::InvalidEventId, 342 )); 343 } 344 if !event.verify_signature() { 345 return Err(verification_error( 346 MycNip46VerificationErrorKind::InvalidSignature, 347 )); 348 } 349 verify_authored_time( 350 event.created_at.as_secs(), 351 observed_at.get(), 352 authored_time_policy, 353 )?; 354 let encryption_context = verify_encryption_context(&encrypted_content)?; 355 356 Ok(MycVerifiedNip46Event { 357 original, 358 encrypted_content, 359 event_id: MycNip46EventId::from_bytes(event.id.to_bytes()), 360 client_public_key, 361 receiver_public_key: expected_receiver.clone(), 362 authored_at_unix_seconds: event.created_at.as_secs(), 363 encryption_context, 364 }) 365 } 366 367 /// Decodes one already bounded plaintext request through the canonical NIP-46 model. 368 pub fn verify_myc_nip46_request( 369 bounded: MycBoundedNip46Request, 370 ) -> Result<MycVerifiedNip46Request, MycNip46VerificationError> { 371 let (plaintext, parameter_count, parameter_bytes) = bounded.into_verification_parts(); 372 let message: RequestMessage = serde_json::from_slice(&plaintext) 373 .map_err(|_| verification_error(MycNip46VerificationErrorKind::InvalidTypedRequest))?; 374 let request_id = message 375 .request_id() 376 .map_err(|_| verification_error(MycNip46VerificationErrorKind::InvalidTypedRequest))?; 377 let request_id = MycNip46RequestId::new(request_id.as_str()) 378 .map_err(|_| verification_error(MycNip46VerificationErrorKind::InvalidTypedRequest))?; 379 let method = message.payload().method().to_string(); 380 if method.is_empty() || method.len() > METHOD_MAX_BYTES { 381 return Err(verification_error( 382 MycNip46VerificationErrorKind::InvalidTypedRequest, 383 )); 384 } 385 let canonical_request = serde_json::to_vec(&message) 386 .map_err(|_| verification_error(MycNip46VerificationErrorKind::InvalidTypedRequest))?; 387 388 Ok(MycVerifiedNip46Request { 389 request_id, 390 method: method.into_boxed_str(), 391 canonical_request: canonical_request.into_boxed_slice(), 392 parameter_count, 393 parameter_bytes, 394 }) 395 } 396 397 #[derive(Deserialize)] 398 #[serde(deny_unknown_fields)] 399 struct CanonicalEventFields<'a> { 400 id: &'a str, 401 pubkey: &'a str, 402 created_at: u64, 403 kind: u64, 404 #[serde(rename = "tags")] 405 _tags: serde::de::IgnoredAny, 406 content: &'a str, 407 sig: &'a str, 408 } 409 410 fn verify_authored_time( 411 authored_at: u64, 412 observed_at: u64, 413 policy: MycNip46AuthoredTimePolicy, 414 ) -> Result<(), MycNip46VerificationError> { 415 let earliest = observed_at.saturating_sub(policy.maximum_past_seconds()); 416 let latest = observed_at.saturating_add(policy.maximum_future_seconds()); 417 if !(earliest..=latest).contains(&authored_at) { 418 return Err(verification_error( 419 MycNip46VerificationErrorKind::AuthoredTimeRejected, 420 )); 421 } 422 Ok(()) 423 } 424 425 fn verify_encryption_context( 426 ciphertext: &str, 427 ) -> Result<MycNip46EncryptionContext, MycNip46VerificationError> { 428 if ciphertext.contains("?iv=") { 429 verify_nip04_envelope(ciphertext)?; 430 Ok(MycNip46EncryptionContext::Nip04) 431 } else { 432 verify_nip44_envelope(ciphertext)?; 433 Ok(MycNip46EncryptionContext::Nip44V2) 434 } 435 } 436 437 fn verify_nip04_envelope(ciphertext: &str) -> Result<(), MycNip46VerificationError> { 438 let (encrypted, iv) = ciphertext 439 .split_once("?iv=") 440 .ok_or_else(|| verification_error(MycNip46VerificationErrorKind::InvalidNip04Envelope))?; 441 if encrypted.is_empty() || iv.is_empty() || iv.contains("?iv=") { 442 return Err(verification_error( 443 MycNip46VerificationErrorKind::InvalidNip04Envelope, 444 )); 445 } 446 let encrypted = decode_canonical_base64( 447 encrypted, 448 MycNip46VerificationErrorKind::InvalidNip04Envelope, 449 )?; 450 let iv = decode_canonical_base64(iv, MycNip46VerificationErrorKind::InvalidNip04Envelope)?; 451 if encrypted.is_empty() 452 || !encrypted.len().is_multiple_of(NIP04_BLOCK_BYTES) 453 || iv.len() != NIP04_IV_BYTES 454 { 455 return Err(verification_error( 456 MycNip46VerificationErrorKind::InvalidNip04Envelope, 457 )); 458 } 459 Ok(()) 460 } 461 462 fn verify_nip44_envelope(ciphertext: &str) -> Result<(), MycNip46VerificationError> { 463 let decoded = decode_canonical_base64( 464 ciphertext, 465 MycNip46VerificationErrorKind::InvalidNip44Envelope, 466 )?; 467 if decoded.first() != Some(&NIP44_VERSION_V2) { 468 return Err(verification_error( 469 MycNip46VerificationErrorKind::InvalidNip44Envelope, 470 )); 471 } 472 let overhead = 1usize 473 .checked_add(NIP44_NONCE_BYTES) 474 .and_then(|value| value.checked_add(NIP44_LENGTH_PREFIX_BYTES)) 475 .and_then(|value| value.checked_add(NIP44_HMAC_BYTES)) 476 .expect("fixed NIP-44 overhead fits usize"); 477 let padded = decoded 478 .len() 479 .checked_sub(overhead) 480 .ok_or_else(|| verification_error(MycNip46VerificationErrorKind::InvalidNip44Envelope))?; 481 if !is_valid_nip44_padding_length(padded) { 482 return Err(verification_error( 483 MycNip46VerificationErrorKind::InvalidNip44Envelope, 484 )); 485 } 486 Ok(()) 487 } 488 489 fn decode_canonical_base64( 490 encoded: &str, 491 kind: MycNip46VerificationErrorKind, 492 ) -> Result<Vec<u8>, MycNip46VerificationError> { 493 let decoded = BASE64_STANDARD 494 .decode(encoded) 495 .map_err(|_| verification_error(kind))?; 496 if BASE64_STANDARD.encode(&decoded) != encoded { 497 return Err(verification_error(kind)); 498 } 499 Ok(decoded) 500 } 501 502 fn is_valid_nip44_padding_length(length: usize) -> bool { 503 if !(NIP44_MIN_PADDED_BYTES..=NIP44_MAX_PADDED_BYTES).contains(&length) { 504 return false; 505 } 506 if length <= 256 { 507 return length.is_multiple_of(32); 508 } 509 let Some(next_power) = length.checked_next_power_of_two() else { 510 return false; 511 }; 512 let chunk = next_power / 8; 513 length.is_multiple_of(chunk) 514 } 515 516 const fn verification_error(kind: MycNip46VerificationErrorKind) -> MycNip46VerificationError { 517 MycNip46VerificationError { kind } 518 } 519 520 #[cfg(test)] 521 mod tests { 522 use super::*; 523 524 #[test] 525 fn authored_time_boundaries_are_inclusive_and_checked() { 526 let policy = MycNip46AuthoredTimePolicy::new(10, 2).expect("policy"); 527 assert!(verify_authored_time(90, 100, policy).is_ok()); 528 assert!(verify_authored_time(102, 100, policy).is_ok()); 529 assert_eq!( 530 verify_authored_time(89, 100, policy).unwrap_err().kind(), 531 MycNip46VerificationErrorKind::AuthoredTimeRejected 532 ); 533 assert_eq!( 534 verify_authored_time(103, 100, policy).unwrap_err().kind(), 535 MycNip46VerificationErrorKind::AuthoredTimeRejected 536 ); 537 } 538 539 #[test] 540 fn every_protocol_padding_boundary_is_closed() { 541 for valid in [32, 64, 96, 128, 160, 192, 224, 256, 320, 384, 65_536] { 542 assert!(is_valid_nip44_padding_length(valid)); 543 } 544 for invalid in [0, 31, 33, 257, 287, 288, 65_535, 65_537] { 545 assert!(!is_valid_nip44_padding_length(invalid)); 546 } 547 } 548 549 #[test] 550 fn every_error_is_fixed_and_source_free() { 551 for kind in [ 552 MycNip46VerificationErrorKind::InvalidObservationTime, 553 MycNip46VerificationErrorKind::InvalidTimePolicy, 554 MycNip46VerificationErrorKind::InvalidTransportBinding, 555 MycNip46VerificationErrorKind::MalformedEvent, 556 MycNip46VerificationErrorKind::NonCanonicalEvent, 557 MycNip46VerificationErrorKind::InvalidKind, 558 MycNip46VerificationErrorKind::InvalidSender, 559 MycNip46VerificationErrorKind::InvalidReceiver, 560 MycNip46VerificationErrorKind::InvalidEventId, 561 MycNip46VerificationErrorKind::InvalidSignature, 562 MycNip46VerificationErrorKind::AuthoredTimeRejected, 563 MycNip46VerificationErrorKind::InvalidNip04Envelope, 564 MycNip46VerificationErrorKind::InvalidNip44Envelope, 565 MycNip46VerificationErrorKind::InvalidTypedRequest, 566 ] { 567 let error = verification_error(kind); 568 assert_eq!(error.kind(), kind); 569 assert!(!error.to_string().is_empty()); 570 assert!(error.source().is_none()); 571 } 572 } 573 }