provider_verification.rs (56774B)
1 //! Independent semantic verification for untrusted signer-provider responses. 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, UnsignedEvent}; 8 use zeroize::Zeroizing; 9 10 use crate::provider_contract::MYC_PROVIDER_NIP44_PLAINTEXT_MAX_BYTES; 11 use crate::provider_local_signer::{ 12 LocalSignerUntrustedParts, MYC_LOCAL_SIGNER_TRANSPORT_CONTRACT_VERSION, 13 MycLocalSignerUntrustedResponse, ProtectedWireHex, WireCapability, WireProviderInstance, 14 WireProviderResult, WireRole, 15 }; 16 use crate::{ 17 MYC_PROVIDER_INPUT_MAX_BYTES, MYC_PROVIDER_OUTPUT_MAX_BYTES, MycProviderBinding, 18 MycProviderCapability, MycProviderCapabilitySet, MycProviderCorrelationId, 19 MycProviderInstanceId, MycProviderKind, MycProviderNip44Version, MycProviderOperation, 20 MycProviderOperationId, MycProviderPublicIdentity, MycProviderRole, 21 }; 22 23 const NIP44_V2_VERSION: u8 = 2; 24 const NIP44_V2_FIXED_PAYLOAD_BYTES: usize = 67; 25 26 /// Positive injected completion-observation time for provider verification. 27 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 28 pub struct MycProviderResponseObservedAtUnixMs(u64); 29 30 impl MycProviderResponseObservedAtUnixMs { 31 /// Validates a positive UTC millisecond value representable by governed time types. 32 pub fn new(value: u64) -> Result<Self, MycProviderVerificationError> { 33 if value == 0 || i64::try_from(value).is_err() { 34 return Err(verification_error( 35 MycProviderVerificationErrorKind::InvalidObservationTime, 36 )); 37 } 38 Ok(Self(value)) 39 } 40 41 /// Returns the exact injected value. 42 #[must_use] 43 pub const fn get(self) -> u64 { 44 self.0 45 } 46 } 47 48 /// Stable source-free provider-verification failure classification. 49 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 50 pub enum MycProviderVerificationErrorKind { 51 InvalidObservationTime, 52 InvalidBinding, 53 LateResponse, 54 ResponseBinding, 55 ResultShape, 56 Identity, 57 Capability, 58 Size, 59 Event, 60 Nip04, 61 Nip44, 62 } 63 64 impl MycProviderVerificationErrorKind { 65 /// Returns the stable machine-facing safe code. 66 #[must_use] 67 pub const fn code(self) -> &'static str { 68 match self { 69 Self::InvalidObservationTime => "provider_observation_time_invalid", 70 Self::InvalidBinding => "provider_verification_binding_invalid", 71 Self::LateResponse => "provider_response_late", 72 Self::ResponseBinding => "provider_response_binding_invalid", 73 Self::ResultShape => "provider_result_shape_invalid", 74 Self::Identity => "provider_result_identity_invalid", 75 Self::Capability => "provider_result_capability_invalid", 76 Self::Size => "provider_result_size_invalid", 77 Self::Event => "provider_signed_event_invalid", 78 Self::Nip04 => "provider_nip04_result_invalid", 79 Self::Nip44 => "provider_nip44_result_invalid", 80 } 81 } 82 83 const fn message(self) -> &'static str { 84 match self { 85 Self::InvalidObservationTime => "provider observation time is invalid", 86 Self::InvalidBinding => "provider verification binding is invalid", 87 Self::LateResponse => "provider response missed its deadline", 88 Self::ResponseBinding => "provider response binding is invalid", 89 Self::ResultShape => "provider result shape is invalid", 90 Self::Identity => "provider result identity is invalid", 91 Self::Capability => "provider result capability is invalid", 92 Self::Size => "provider result size is invalid", 93 Self::Event => "provider signed event is invalid", 94 Self::Nip04 => "provider NIP-04 result is invalid", 95 Self::Nip44 => "provider NIP-44 result is invalid", 96 } 97 } 98 } 99 100 /// One source-free provider-verification failure. 101 #[derive(Clone, Copy, PartialEq, Eq)] 102 pub struct MycProviderVerificationError { 103 kind: MycProviderVerificationErrorKind, 104 } 105 106 impl MycProviderVerificationError { 107 /// Returns the stable failure kind. 108 #[must_use] 109 pub const fn kind(self) -> MycProviderVerificationErrorKind { 110 self.kind 111 } 112 113 /// Returns the stable machine-facing safe code. 114 #[must_use] 115 pub const fn code(self) -> &'static str { 116 self.kind.code() 117 } 118 } 119 120 impl fmt::Debug for MycProviderVerificationError { 121 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 122 formatter 123 .debug_struct("MycProviderVerificationError") 124 .field("kind", &self.kind) 125 .finish() 126 } 127 } 128 129 impl fmt::Display for MycProviderVerificationError { 130 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 131 formatter.write_str(self.kind.message()) 132 } 133 } 134 135 impl Error for MycProviderVerificationError {} 136 137 const fn verification_error( 138 kind: MycProviderVerificationErrorKind, 139 ) -> MycProviderVerificationError { 140 MycProviderVerificationError { kind } 141 } 142 143 enum VerifiedProviderResult { 144 Describe { 145 public_identity: MycProviderPublicIdentity, 146 protocol_version: u32, 147 capabilities: MycProviderCapabilitySet, 148 maximum_request_bytes: u64, 149 }, 150 PublicIdentity(MycProviderPublicIdentity), 151 SignedEvent(Zeroizing<Vec<u8>>), 152 Protected { 153 payload: Zeroizing<Vec<u8>>, 154 nip44_version: Option<MycProviderNip44Version>, 155 }, 156 } 157 158 /// One fully correlated, deadline-admitted, independently verified provider result. 159 /// 160 /// This value proves only local semantic admission. It is not publication, 161 /// durable commit evidence, or proof that a cancelled provider call had no effect. 162 pub struct MycVerifiedProviderResponse { 163 operation_id: MycProviderOperationId, 164 correlation_id: MycProviderCorrelationId, 165 instance: MycProviderInstanceId, 166 role: MycProviderRole, 167 capability: MycProviderCapability, 168 operation_binding: [u8; 32], 169 result: VerifiedProviderResult, 170 } 171 172 impl MycVerifiedProviderResponse { 173 /// Returns the exact verified operation identity. 174 #[must_use] 175 pub const fn operation_id(&self) -> MycProviderOperationId { 176 self.operation_id 177 } 178 179 /// Returns the exact verified correlation identity. 180 #[must_use] 181 pub const fn correlation_id(&self) -> MycProviderCorrelationId { 182 self.correlation_id 183 } 184 185 /// Returns the exact verified provider instance. 186 #[must_use] 187 pub const fn instance(&self) -> MycProviderInstanceId { 188 self.instance 189 } 190 191 /// Returns the exact verified provider role. 192 #[must_use] 193 pub const fn role(&self) -> MycProviderRole { 194 self.role 195 } 196 197 /// Returns the exact verified capability. 198 #[must_use] 199 pub const fn capability(&self) -> MycProviderCapability { 200 self.capability 201 } 202 203 pub(crate) fn matches_operation(&self, operation: &MycProviderOperation) -> bool { 204 self.operation_binding == operation.binding_digest() 205 } 206 207 /// Returns a verified public identity for describe/public-identity results. 208 #[must_use] 209 pub const fn public_identity(&self) -> Option<&MycProviderPublicIdentity> { 210 match &self.result { 211 VerifiedProviderResult::Describe { 212 public_identity, .. 213 } 214 | VerifiedProviderResult::PublicIdentity(public_identity) => Some(public_identity), 215 VerifiedProviderResult::SignedEvent(_) | VerifiedProviderResult::Protected { .. } => { 216 None 217 } 218 } 219 } 220 221 /// Returns the verified protocol version for a describe result. 222 #[must_use] 223 pub const fn protocol_version(&self) -> Option<u32> { 224 match &self.result { 225 VerifiedProviderResult::Describe { 226 protocol_version, .. 227 } => Some(*protocol_version), 228 _ => None, 229 } 230 } 231 232 /// Returns the verified capability set for a describe result. 233 #[must_use] 234 pub const fn capabilities(&self) -> Option<MycProviderCapabilitySet> { 235 match &self.result { 236 VerifiedProviderResult::Describe { capabilities, .. } => Some(*capabilities), 237 _ => None, 238 } 239 } 240 241 /// Returns the verified provider request bound for a describe result. 242 #[must_use] 243 pub const fn maximum_request_bytes(&self) -> Option<u64> { 244 match &self.result { 245 VerifiedProviderResult::Describe { 246 maximum_request_bytes, 247 .. 248 } => Some(*maximum_request_bytes), 249 _ => None, 250 } 251 } 252 253 /// Returns the exact canonical independently verified signed-event bytes. 254 /// 255 /// These are the bytes returned by the provider after canonical-form, 256 /// field, author, event-ID, and signature verification. Consumers that 257 /// persist or publish the result must retain these exact bytes. 258 #[must_use] 259 pub fn signed_event_bytes(&self) -> Option<&[u8]> { 260 match &self.result { 261 VerifiedProviderResult::SignedEvent(bytes) => Some(bytes), 262 _ => None, 263 } 264 } 265 266 /// Returns the verified protected operation output, if applicable. 267 #[must_use] 268 pub fn protected_payload(&self) -> Option<&[u8]> { 269 match &self.result { 270 VerifiedProviderResult::Protected { payload, .. } => Some(payload), 271 _ => None, 272 } 273 } 274 275 /// Returns the exact verified NIP-44 version, if applicable. 276 #[must_use] 277 pub const fn nip44_version(&self) -> Option<MycProviderNip44Version> { 278 match &self.result { 279 VerifiedProviderResult::Protected { nip44_version, .. } => *nip44_version, 280 _ => None, 281 } 282 } 283 } 284 285 impl fmt::Debug for MycVerifiedProviderResponse { 286 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 287 formatter 288 .debug_struct("MycVerifiedProviderResponse") 289 .field("operation_id", &"[redacted]") 290 .field("correlation_id", &"[redacted]") 291 .field("instance", &self.instance) 292 .field("role", &self.role) 293 .field("capability", &self.capability) 294 .field("result", &"[redacted]") 295 .finish() 296 } 297 } 298 299 impl MycLocalSignerUntrustedResponse { 300 /// Independently verifies this untrusted response against its original call. 301 pub fn verify( 302 self, 303 binding: &MycProviderBinding, 304 operation: &MycProviderOperation, 305 observed_at: MycProviderResponseObservedAtUnixMs, 306 ) -> Result<MycVerifiedProviderResponse, MycProviderVerificationError> { 307 verify_response(binding, operation, observed_at, self.into_parts()) 308 } 309 } 310 311 pub(crate) fn verify_encrypted_provider_response( 312 binding: &MycProviderBinding, 313 operation: &MycProviderOperation, 314 observed_at: MycProviderResponseObservedAtUnixMs, 315 result: WireProviderResult, 316 ) -> Result<MycVerifiedProviderResponse, MycProviderVerificationError> { 317 if binding.kind() != MycProviderKind::EncryptedFile 318 || operation.provider() != MycProviderKind::EncryptedFile 319 || operation.role() != binding.role() 320 || operation.instance() != binding.instance() 321 || operation.expected_identity() != binding.expected_identity() 322 || !binding 323 .required_capabilities() 324 .contains(operation.input().capability()) 325 { 326 return Err(verification_error( 327 MycProviderVerificationErrorKind::InvalidBinding, 328 )); 329 } 330 if observed_at.get() > operation.deadline().get() { 331 return Err(verification_error( 332 MycProviderVerificationErrorKind::LateResponse, 333 )); 334 } 335 let result = verify_result(binding, operation, result)?; 336 Ok(MycVerifiedProviderResponse { 337 operation_id: operation.operation_id(), 338 correlation_id: operation.correlation_id(), 339 instance: operation.instance(), 340 role: operation.role(), 341 capability: operation.input().capability(), 342 operation_binding: operation.binding_digest(), 343 result, 344 }) 345 } 346 347 fn verify_response( 348 binding: &MycProviderBinding, 349 operation: &MycProviderOperation, 350 observed_at: MycProviderResponseObservedAtUnixMs, 351 parts: LocalSignerUntrustedParts, 352 ) -> Result<MycVerifiedProviderResponse, MycProviderVerificationError> { 353 if binding.kind() != MycProviderKind::LocalSigner 354 || operation.provider() != MycProviderKind::LocalSigner 355 || operation.role() != binding.role() 356 || operation.instance() != binding.instance() 357 || operation.expected_identity() != binding.expected_identity() 358 || !binding 359 .required_capabilities() 360 .contains(operation.input().capability()) 361 { 362 return Err(verification_error( 363 MycProviderVerificationErrorKind::InvalidBinding, 364 )); 365 } 366 if observed_at.get() > operation.deadline().get() { 367 return Err(verification_error( 368 MycProviderVerificationErrorKind::LateResponse, 369 )); 370 } 371 372 let expected_operation_id = hex::encode(operation.operation_id().as_bytes()); 373 let expected_correlation_id = hex::encode(operation.correlation_id().as_bytes()); 374 let response = parts.response; 375 if response.contract_version != MYC_LOCAL_SIGNER_TRANSPORT_CONTRACT_VERSION 376 || response.provider_instance != WireProviderInstance::from(operation.instance()) 377 || response.role != WireRole::from(operation.role()) 378 || response.operation_id != expected_operation_id 379 || response.correlation_id != expected_correlation_id 380 || parts.outer_correlation_id.as_ref() != expected_correlation_id 381 || response.absolute_deadline_unix_ms != operation.deadline().get() 382 || response.expected_identity != operation.expected_identity().as_hex() 383 || response.capability != WireCapability::from(operation.input().capability()) 384 { 385 return Err(verification_error( 386 MycProviderVerificationErrorKind::ResponseBinding, 387 )); 388 } 389 390 let result = verify_result(binding, operation, response.result)?; 391 Ok(MycVerifiedProviderResponse { 392 operation_id: operation.operation_id(), 393 correlation_id: operation.correlation_id(), 394 instance: operation.instance(), 395 role: operation.role(), 396 capability: operation.input().capability(), 397 operation_binding: operation.binding_digest(), 398 result, 399 }) 400 } 401 402 #[cfg(test)] 403 pub(crate) fn verify_protected_response_for_test( 404 binding: &MycProviderBinding, 405 operation: &MycProviderOperation, 406 payload: &[u8], 407 ) -> Result<MycVerifiedProviderResponse, MycProviderVerificationError> { 408 if operation.role() != binding.role() 409 || operation.instance() != binding.instance() 410 || operation.provider() != binding.kind() 411 || operation.expected_identity() != binding.expected_identity() 412 { 413 return Err(verification_error( 414 MycProviderVerificationErrorKind::InvalidBinding, 415 )); 416 } 417 if payload.len() > MYC_PROVIDER_OUTPUT_MAX_BYTES { 418 return Err(verification_error(MycProviderVerificationErrorKind::Size)); 419 } 420 let payload = Zeroizing::new(payload.to_vec()); 421 let result = match operation.input().capability() { 422 MycProviderCapability::Nip04Decrypt => verify_nip04_decrypt_result(operation, payload)?, 423 MycProviderCapability::Nip44Decrypt => verify_nip44_decrypt_result(operation, payload)?, 424 _ => return Err(result_shape()), 425 }; 426 Ok(MycVerifiedProviderResponse { 427 operation_id: operation.operation_id(), 428 correlation_id: operation.correlation_id(), 429 instance: operation.instance(), 430 role: operation.role(), 431 capability: operation.input().capability(), 432 operation_binding: operation.binding_digest(), 433 result, 434 }) 435 } 436 437 fn verify_result( 438 binding: &MycProviderBinding, 439 operation: &MycProviderOperation, 440 result: WireProviderResult, 441 ) -> Result<VerifiedProviderResult, MycProviderVerificationError> { 442 match (operation.input().capability(), result) { 443 ( 444 MycProviderCapability::Describe, 445 WireProviderResult::Describe { 446 public_identity, 447 protocol_version, 448 capabilities, 449 maximum_request_bytes, 450 }, 451 ) => verify_describe( 452 binding, 453 public_identity, 454 protocol_version, 455 capabilities, 456 maximum_request_bytes, 457 ), 458 ( 459 MycProviderCapability::PublicIdentity, 460 WireProviderResult::PublicIdentity { public_identity }, 461 ) => { 462 verify_identity(operation.expected_identity(), &public_identity)?; 463 Ok(VerifiedProviderResult::PublicIdentity( 464 operation.expected_identity().clone(), 465 )) 466 } 467 (MycProviderCapability::SignEvent, WireProviderResult::SignEvent { payload_hex }) => { 468 verify_signed_event(operation, decode_payload(payload_hex)?) 469 } 470 ( 471 MycProviderCapability::Nip04Encrypt, 472 WireProviderResult::Nip04Encrypt { peer, payload_hex }, 473 ) => { 474 verify_peer(operation, &peer)?; 475 let payload = decode_payload(payload_hex)?; 476 let expected_plaintext = operation.input().bytes().ok_or_else(result_shape)?; 477 verify_nip04_ciphertext(&payload, Some(expected_plaintext.len()))?; 478 Ok(VerifiedProviderResult::Protected { 479 payload, 480 nip44_version: None, 481 }) 482 } 483 ( 484 MycProviderCapability::Nip04Decrypt, 485 WireProviderResult::Nip04Decrypt { peer, payload_hex }, 486 ) => { 487 verify_peer(operation, &peer)?; 488 verify_nip04_decrypt_result(operation, decode_payload_allow_empty(payload_hex)?) 489 } 490 ( 491 MycProviderCapability::Nip44Encrypt, 492 WireProviderResult::Nip44Encrypt { 493 peer, 494 version, 495 payload_hex, 496 }, 497 ) => { 498 verify_peer(operation, &peer)?; 499 let requested_version = operation.input().nip44_version().ok_or_else(result_shape)?; 500 if version != requested_version.as_u8() { 501 return Err(verification_error(MycProviderVerificationErrorKind::Nip44)); 502 } 503 let plaintext = operation.input().bytes().ok_or_else(result_shape)?; 504 let payload = decode_payload(payload_hex)?; 505 verify_nip44_ciphertext(&payload, Some(plaintext.len()), requested_version)?; 506 Ok(VerifiedProviderResult::Protected { 507 payload, 508 nip44_version: Some(requested_version), 509 }) 510 } 511 ( 512 MycProviderCapability::Nip44Decrypt, 513 WireProviderResult::Nip44Decrypt { 514 peer, 515 version, 516 payload_hex, 517 }, 518 ) => { 519 verify_peer(operation, &peer)?; 520 let requested_version = operation.input().nip44_version().ok_or_else(result_shape)?; 521 if version != requested_version.as_u8() { 522 return Err(verification_error(MycProviderVerificationErrorKind::Nip44)); 523 } 524 verify_nip44_decrypt_result(operation, decode_payload(payload_hex)?) 525 } 526 _ => Err(result_shape()), 527 } 528 } 529 530 fn verify_nip04_decrypt_result( 531 operation: &MycProviderOperation, 532 payload: Zeroizing<Vec<u8>>, 533 ) -> Result<VerifiedProviderResult, MycProviderVerificationError> { 534 let input = operation.input().bytes().ok_or_else(result_shape)?; 535 let ciphertext_length = verify_nip04_ciphertext(input, None)?; 536 if payload.len() >= ciphertext_length { 537 return Err(verification_error(MycProviderVerificationErrorKind::Nip04)); 538 } 539 Ok(VerifiedProviderResult::Protected { 540 payload, 541 nip44_version: None, 542 }) 543 } 544 545 fn verify_nip44_decrypt_result( 546 operation: &MycProviderOperation, 547 payload: Zeroizing<Vec<u8>>, 548 ) -> Result<VerifiedProviderResult, MycProviderVerificationError> { 549 let requested_version = operation.input().nip44_version().ok_or_else(result_shape)?; 550 let input = operation.input().bytes().ok_or_else(result_shape)?; 551 let padded_length = verify_nip44_ciphertext(input, None, requested_version)?; 552 if payload.len() > padded_length || payload.len() > MYC_PROVIDER_NIP44_PLAINTEXT_MAX_BYTES { 553 return Err(verification_error(MycProviderVerificationErrorKind::Nip44)); 554 } 555 Ok(VerifiedProviderResult::Protected { 556 payload, 557 nip44_version: Some(requested_version), 558 }) 559 } 560 561 fn verify_describe( 562 binding: &MycProviderBinding, 563 public_identity: String, 564 protocol_version: u32, 565 capabilities: Vec<WireCapability>, 566 maximum_request_bytes: u64, 567 ) -> Result<VerifiedProviderResult, MycProviderVerificationError> { 568 verify_identity(binding.expected_identity(), &public_identity)?; 569 if protocol_version != MYC_LOCAL_SIGNER_TRANSPORT_CONTRACT_VERSION { 570 return Err(verification_error( 571 MycProviderVerificationErrorKind::ResponseBinding, 572 )); 573 } 574 if capabilities.is_empty() || capabilities.len() > 7 { 575 return Err(verification_error( 576 MycProviderVerificationErrorKind::Capability, 577 )); 578 } 579 let capabilities: Vec<MycProviderCapability> = capabilities 580 .into_iter() 581 .map(MycProviderCapability::from) 582 .collect(); 583 let capabilities = MycProviderCapabilitySet::new(&capabilities) 584 .map_err(|_| verification_error(MycProviderVerificationErrorKind::Capability))?; 585 if binding 586 .required_capabilities() 587 .iter() 588 .any(|capability| !capabilities.contains(capability)) 589 { 590 return Err(verification_error( 591 MycProviderVerificationErrorKind::Capability, 592 )); 593 } 594 let expected_maximum = match binding.kind() { 595 MycProviderKind::EncryptedFile => MYC_PROVIDER_INPUT_MAX_BYTES as u64, 596 MycProviderKind::LocalSigner => binding 597 .local_signer_limits() 598 .ok_or_else(|| verification_error(MycProviderVerificationErrorKind::InvalidBinding))? 599 .request_max_bytes(), 600 }; 601 if maximum_request_bytes != expected_maximum { 602 return Err(verification_error(MycProviderVerificationErrorKind::Size)); 603 } 604 Ok(VerifiedProviderResult::Describe { 605 public_identity: binding.expected_identity().clone(), 606 protocol_version, 607 capabilities, 608 maximum_request_bytes, 609 }) 610 } 611 612 fn verify_identity( 613 expected: &MycProviderPublicIdentity, 614 actual: &str, 615 ) -> Result<(), MycProviderVerificationError> { 616 if actual != expected.as_hex() { 617 return Err(verification_error( 618 MycProviderVerificationErrorKind::Identity, 619 )); 620 } 621 Ok(()) 622 } 623 624 fn verify_peer( 625 operation: &MycProviderOperation, 626 actual: &str, 627 ) -> Result<(), MycProviderVerificationError> { 628 if operation 629 .input() 630 .peer() 631 .map(MycProviderPublicIdentity::as_hex) 632 != Some(actual) 633 { 634 return Err(verification_error( 635 MycProviderVerificationErrorKind::ResponseBinding, 636 )); 637 } 638 Ok(()) 639 } 640 641 fn decode_payload( 642 payload: ProtectedWireHex, 643 ) -> Result<Zeroizing<Vec<u8>>, MycProviderVerificationError> { 644 let payload = payload 645 .into_bytes() 646 .map_err(|_| verification_error(MycProviderVerificationErrorKind::ResultShape))?; 647 if payload.is_empty() || payload.len() > MYC_PROVIDER_OUTPUT_MAX_BYTES { 648 return Err(verification_error(MycProviderVerificationErrorKind::Size)); 649 } 650 Ok(payload) 651 } 652 653 fn decode_payload_allow_empty( 654 payload: ProtectedWireHex, 655 ) -> Result<Zeroizing<Vec<u8>>, MycProviderVerificationError> { 656 let payload = payload 657 .into_bytes() 658 .map_err(|_| verification_error(MycProviderVerificationErrorKind::ResultShape))?; 659 if payload.len() > MYC_PROVIDER_OUTPUT_MAX_BYTES { 660 return Err(verification_error(MycProviderVerificationErrorKind::Size)); 661 } 662 Ok(payload) 663 } 664 665 fn verify_signed_event( 666 operation: &MycProviderOperation, 667 payload: Zeroizing<Vec<u8>>, 668 ) -> Result<VerifiedProviderResult, MycProviderVerificationError> { 669 let unsigned_bytes = operation.input().bytes().ok_or_else(result_shape)?; 670 let mut unsigned: UnsignedEvent = serde_json::from_slice(unsigned_bytes) 671 .map_err(|_| verification_error(MycProviderVerificationErrorKind::Event))?; 672 let canonical_unsigned = serde_json::to_vec(&unsigned) 673 .map_err(|_| verification_error(MycProviderVerificationErrorKind::Event))?; 674 if canonical_unsigned != unsigned_bytes 675 || unsigned.verify_id().is_err() 676 || unsigned.pubkey.to_hex() != operation.expected_identity().as_hex() 677 { 678 return Err(verification_error(MycProviderVerificationErrorKind::Event)); 679 } 680 let expected_id = unsigned.id(); 681 let event: Event = serde_json::from_slice(&payload) 682 .map_err(|_| verification_error(MycProviderVerificationErrorKind::Event))?; 683 let canonical_event = serde_json::to_vec(&event) 684 .map_err(|_| verification_error(MycProviderVerificationErrorKind::Event))?; 685 if canonical_event.as_slice() != payload.as_slice() 686 || event.pubkey != unsigned.pubkey 687 || event.created_at != unsigned.created_at 688 || event.kind != unsigned.kind 689 || event.tags != unsigned.tags 690 || event.content != unsigned.content 691 || event.id != expected_id 692 || event.verify().is_err() 693 { 694 return Err(verification_error(MycProviderVerificationErrorKind::Event)); 695 } 696 Ok(VerifiedProviderResult::SignedEvent(payload)) 697 } 698 699 fn verify_nip04_ciphertext( 700 payload: &[u8], 701 plaintext_length: Option<usize>, 702 ) -> Result<usize, MycProviderVerificationError> { 703 let rendered = core::str::from_utf8(payload) 704 .map_err(|_| verification_error(MycProviderVerificationErrorKind::Nip04))?; 705 let (ciphertext, iv) = rendered 706 .split_once("?iv=") 707 .ok_or_else(|| verification_error(MycProviderVerificationErrorKind::Nip04))?; 708 if ciphertext.is_empty() || iv.is_empty() || iv.contains("?iv=") { 709 return Err(verification_error(MycProviderVerificationErrorKind::Nip04)); 710 } 711 let ciphertext = decode_canonical_base64(ciphertext, MycProviderVerificationErrorKind::Nip04)?; 712 let iv = decode_canonical_base64(iv, MycProviderVerificationErrorKind::Nip04)?; 713 if iv.len() != 16 || ciphertext.is_empty() || !ciphertext.len().is_multiple_of(16) { 714 return Err(verification_error(MycProviderVerificationErrorKind::Nip04)); 715 } 716 if let Some(length) = plaintext_length { 717 let expected = length 718 .checked_div(16) 719 .and_then(|blocks| blocks.checked_add(1)) 720 .and_then(|blocks| blocks.checked_mul(16)) 721 .ok_or_else(|| verification_error(MycProviderVerificationErrorKind::Size))?; 722 if ciphertext.len() != expected { 723 return Err(verification_error(MycProviderVerificationErrorKind::Nip04)); 724 } 725 } 726 Ok(ciphertext.len()) 727 } 728 729 fn verify_nip44_ciphertext( 730 payload: &[u8], 731 plaintext_length: Option<usize>, 732 version: MycProviderNip44Version, 733 ) -> Result<usize, MycProviderVerificationError> { 734 let rendered = core::str::from_utf8(payload) 735 .map_err(|_| verification_error(MycProviderVerificationErrorKind::Nip44))?; 736 let decoded = decode_canonical_base64(rendered, MycProviderVerificationErrorKind::Nip44)?; 737 if version.as_u8() != NIP44_V2_VERSION 738 || decoded.first().copied() != Some(version.as_u8()) 739 || decoded.len() < NIP44_V2_FIXED_PAYLOAD_BYTES + 32 740 { 741 return Err(verification_error(MycProviderVerificationErrorKind::Nip44)); 742 } 743 let padded = decoded 744 .len() 745 .checked_sub(NIP44_V2_FIXED_PAYLOAD_BYTES) 746 .ok_or_else(|| verification_error(MycProviderVerificationErrorKind::Nip44))?; 747 if let Some(length) = plaintext_length { 748 let expected = nip44_padding_length(length) 749 .and_then(|value| NIP44_V2_FIXED_PAYLOAD_BYTES.checked_add(value)) 750 .ok_or_else(|| verification_error(MycProviderVerificationErrorKind::Nip44))?; 751 if decoded.len() != expected { 752 return Err(verification_error(MycProviderVerificationErrorKind::Nip44)); 753 } 754 } else if !is_valid_nip44_padding_length(padded) { 755 return Err(verification_error(MycProviderVerificationErrorKind::Nip44)); 756 } 757 Ok(padded) 758 } 759 760 fn decode_canonical_base64( 761 value: &str, 762 kind: MycProviderVerificationErrorKind, 763 ) -> Result<Vec<u8>, MycProviderVerificationError> { 764 let decoded = BASE64_STANDARD 765 .decode(value) 766 .map_err(|_| verification_error(kind))?; 767 if BASE64_STANDARD.encode(&decoded) != value { 768 return Err(verification_error(kind)); 769 } 770 Ok(decoded) 771 } 772 773 fn nip44_padding_length(length: usize) -> Option<usize> { 774 if length == 0 || length > MYC_PROVIDER_NIP44_PLAINTEXT_MAX_BYTES { 775 return None; 776 } 777 if length <= 32 { 778 return Some(32); 779 } 780 let next_power = length.checked_next_power_of_two()?; 781 let chunk = if next_power <= 256 { 782 32 783 } else { 784 next_power.checked_div(8)? 785 }; 786 chunk.checked_mul(length.checked_sub(1)?.checked_div(chunk)?.checked_add(1)?) 787 } 788 789 fn is_valid_nip44_padding_length(padded: usize) -> bool { 790 if !(32..=65_536).contains(&padded) { 791 return false; 792 } 793 if padded <= 256 { 794 return padded.is_multiple_of(32); 795 } 796 let Some(next_power) = padded.checked_next_power_of_two() else { 797 return false; 798 }; 799 let chunk = next_power / 8; 800 padded.is_multiple_of(chunk) 801 } 802 803 const fn result_shape() -> MycProviderVerificationError { 804 verification_error(MycProviderVerificationErrorKind::ResultShape) 805 } 806 807 impl From<WireCapability> for MycProviderCapability { 808 fn from(capability: WireCapability) -> Self { 809 match capability { 810 WireCapability::Describe => Self::Describe, 811 WireCapability::PublicIdentity => Self::PublicIdentity, 812 WireCapability::SignEvent => Self::SignEvent, 813 WireCapability::Nip04Encrypt => Self::Nip04Encrypt, 814 WireCapability::Nip04Decrypt => Self::Nip04Decrypt, 815 WireCapability::Nip44Encrypt => Self::Nip44Encrypt, 816 WireCapability::Nip44Decrypt => Self::Nip44Decrypt, 817 } 818 } 819 } 820 821 #[cfg(test)] 822 mod tests { 823 use std::error::Error as _; 824 use std::path::Path; 825 826 use nostr::nips::{nip04, nip44}; 827 use nostr::{Keys, Kind, SecretKey, Tag, Timestamp}; 828 829 use crate::provider_local_signer::{LocalSignerResponse, MycLocalSignerUntrustedResponse}; 830 use crate::{ 831 MycConfigProfile, MycProviderCorrelationId, MycProviderDeadlineUnixMs, 832 MycProviderOperationId, MycProviderOperationInput, parse_myc_config_v1, 833 }; 834 835 use super::*; 836 837 const CONFIG: &str = include_str!("../contracts/services_hardening/config.v1.example.toml"); 838 839 fn keys(seed: u8) -> Keys { 840 Keys::new(SecretKey::from_slice(&[seed; 32]).expect("test secret")) 841 } 842 843 fn binding(identity: &Keys) -> MycProviderBinding { 844 let source = CONFIG 845 .replace( 846 "/run/radroots/services/myc/primary/user-signer.sock", 847 Path::new("/run/test-verifier.sock") 848 .to_str() 849 .expect("test socket"), 850 ) 851 .replace( 852 "2222222222222222222222222222222222222222222222222222222222222222", 853 &identity.public_key().to_hex(), 854 ); 855 parse_myc_config_v1(source.as_bytes(), MycConfigProfile::RepoLocal) 856 .expect("test config") 857 .provider_contract() 858 .binding(MycProviderRole::User) 859 .expect("user binding") 860 .clone() 861 } 862 863 fn encrypted_transport_binding() -> MycProviderBinding { 864 parse_myc_config_v1(CONFIG.as_bytes(), MycConfigProfile::RepoLocal) 865 .expect("test config") 866 .provider_contract() 867 .binding(MycProviderRole::Transport) 868 .expect("transport binding") 869 .clone() 870 } 871 872 fn make_operation( 873 binding: &MycProviderBinding, 874 seed: u8, 875 input: MycProviderOperationInput, 876 ) -> MycProviderOperation { 877 MycProviderOperation::new( 878 binding, 879 MycProviderOperationId::from_bytes([seed; 32]), 880 MycProviderCorrelationId::from_bytes([seed.wrapping_add(1); 32]), 881 MycProviderDeadlineUnixMs::new(2_000_000_000_000).expect("deadline"), 882 input, 883 ) 884 .expect("operation") 885 } 886 887 fn parts( 888 operation: &MycProviderOperation, 889 result: WireProviderResult, 890 ) -> LocalSignerUntrustedParts { 891 let operation_id = hex::encode(operation.operation_id().as_bytes()); 892 let correlation_id = hex::encode(operation.correlation_id().as_bytes()); 893 LocalSignerUntrustedParts { 894 outer_correlation_id: correlation_id.clone().into(), 895 response: LocalSignerResponse { 896 contract_version: MYC_LOCAL_SIGNER_TRANSPORT_CONTRACT_VERSION, 897 provider_instance: operation.instance().into(), 898 role: operation.role().into(), 899 operation_id, 900 correlation_id, 901 absolute_deadline_unix_ms: operation.deadline().get(), 902 expected_identity: operation.expected_identity().as_hex().to_owned(), 903 capability: operation.input().capability().into(), 904 result, 905 }, 906 } 907 } 908 909 fn observed() -> MycProviderResponseObservedAtUnixMs { 910 MycProviderResponseObservedAtUnixMs::new(1_999_999_999_999).expect("observed") 911 } 912 913 fn public_identity_result(operation: &MycProviderOperation) -> WireProviderResult { 914 WireProviderResult::PublicIdentity { 915 public_identity: operation.expected_identity().as_hex().to_owned(), 916 } 917 } 918 919 fn verify( 920 binding: &MycProviderBinding, 921 operation: &MycProviderOperation, 922 result: WireProviderResult, 923 ) -> Result<MycVerifiedProviderResponse, MycProviderVerificationError> { 924 MycLocalSignerUntrustedResponse::from_parts(parts(operation, result)).verify( 925 binding, 926 operation, 927 observed(), 928 ) 929 } 930 931 fn assert_binding_rejected( 932 binding: &MycProviderBinding, 933 operation: &MycProviderOperation, 934 mutate: impl FnOnce(&mut LocalSignerUntrustedParts), 935 ) { 936 let mut untrusted = parts(operation, public_identity_result(operation)); 937 mutate(&mut untrusted); 938 let error = MycLocalSignerUntrustedResponse::from_parts(untrusted) 939 .verify(binding, operation, observed()) 940 .expect_err("binding mismatch"); 941 assert_eq!( 942 error.kind(), 943 MycProviderVerificationErrorKind::ResponseBinding 944 ); 945 } 946 947 #[test] 948 fn observed_time_and_errors_are_bounded_source_free_and_redacted() { 949 assert!(MycProviderResponseObservedAtUnixMs::new(0).is_err()); 950 assert!(MycProviderResponseObservedAtUnixMs::new(i64::MAX as u64).is_ok()); 951 assert!(MycProviderResponseObservedAtUnixMs::new(i64::MAX as u64 + 1).is_err()); 952 for kind in [ 953 MycProviderVerificationErrorKind::InvalidObservationTime, 954 MycProviderVerificationErrorKind::InvalidBinding, 955 MycProviderVerificationErrorKind::LateResponse, 956 MycProviderVerificationErrorKind::ResponseBinding, 957 MycProviderVerificationErrorKind::ResultShape, 958 MycProviderVerificationErrorKind::Identity, 959 MycProviderVerificationErrorKind::Capability, 960 MycProviderVerificationErrorKind::Size, 961 MycProviderVerificationErrorKind::Event, 962 MycProviderVerificationErrorKind::Nip04, 963 MycProviderVerificationErrorKind::Nip44, 964 ] { 965 let error = verification_error(kind); 966 assert_eq!(error.kind(), kind); 967 assert!(!error.code().is_empty()); 968 assert!(!error.to_string().is_empty()); 969 assert!(error.source().is_none()); 970 } 971 } 972 973 #[test] 974 fn every_outer_and_inner_binding_must_match_the_original_operation() { 975 let identity = keys(1); 976 let binding = binding(&identity); 977 let operation = make_operation(&binding, 3, MycProviderOperationInput::public_identity()); 978 let verified = verify(&binding, &operation, public_identity_result(&operation)) 979 .expect("verified identity"); 980 assert_eq!(verified.operation_id(), operation.operation_id()); 981 assert_eq!(verified.correlation_id(), operation.correlation_id()); 982 assert_eq!(verified.role(), MycProviderRole::User); 983 assert_eq!(verified.instance(), MycProviderInstanceId::User); 984 assert_eq!( 985 verified.public_identity(), 986 Some(operation.expected_identity()) 987 ); 988 989 let error = MycLocalSignerUntrustedResponse::from_parts(parts( 990 &operation, 991 public_identity_result(&operation), 992 )) 993 .verify(&encrypted_transport_binding(), &operation, observed()) 994 .expect_err("wrong configured binding"); 995 assert_eq!( 996 error.kind(), 997 MycProviderVerificationErrorKind::InvalidBinding 998 ); 999 1000 assert_binding_rejected(&binding, &operation, |parts| { 1001 parts.outer_correlation_id = "00".into(); 1002 }); 1003 assert_binding_rejected(&binding, &operation, |parts| { 1004 parts.response.contract_version = 2; 1005 }); 1006 assert_binding_rejected(&binding, &operation, |parts| { 1007 parts.response.operation_id = "00".into(); 1008 }); 1009 assert_binding_rejected(&binding, &operation, |parts| { 1010 parts.response.correlation_id = "00".into(); 1011 }); 1012 assert_binding_rejected(&binding, &operation, |parts| { 1013 parts.response.absolute_deadline_unix_ms -= 1; 1014 }); 1015 assert_binding_rejected(&binding, &operation, |parts| { 1016 parts.response.expected_identity = keys(2).public_key().to_hex(); 1017 }); 1018 assert_binding_rejected(&binding, &operation, |parts| { 1019 parts.response.capability = WireCapability::Describe; 1020 }); 1021 assert_binding_rejected(&binding, &operation, |parts| { 1022 parts.response.role = WireRole::Transport; 1023 }); 1024 assert_binding_rejected(&binding, &operation, |parts| { 1025 parts.response.provider_instance = WireProviderInstance::Transport; 1026 }); 1027 1028 let late = MycProviderResponseObservedAtUnixMs::new(operation.deadline().get() + 1) 1029 .expect("late observation"); 1030 let error = MycLocalSignerUntrustedResponse::from_parts(parts( 1031 &operation, 1032 public_identity_result(&operation), 1033 )) 1034 .verify(&binding, &operation, late) 1035 .expect_err("late result"); 1036 assert_eq!(error.kind(), MycProviderVerificationErrorKind::LateResponse); 1037 1038 let exact = MycProviderResponseObservedAtUnixMs::new(operation.deadline().get()) 1039 .expect("exact deadline"); 1040 MycLocalSignerUntrustedResponse::from_parts(parts( 1041 &operation, 1042 public_identity_result(&operation), 1043 )) 1044 .verify(&binding, &operation, exact) 1045 .expect("exact deadline admitted"); 1046 } 1047 1048 #[test] 1049 fn describe_requires_exact_identity_protocol_capabilities_and_size() { 1050 let identity = keys(1); 1051 let binding = binding(&identity); 1052 let operation = make_operation(&binding, 4, MycProviderOperationInput::describe()); 1053 let all = [ 1054 WireCapability::Describe, 1055 WireCapability::PublicIdentity, 1056 WireCapability::SignEvent, 1057 WireCapability::Nip04Encrypt, 1058 WireCapability::Nip04Decrypt, 1059 WireCapability::Nip44Encrypt, 1060 WireCapability::Nip44Decrypt, 1061 ]; 1062 let result = || WireProviderResult::Describe { 1063 public_identity: operation.expected_identity().as_hex().to_owned(), 1064 protocol_version: 1, 1065 capabilities: all.to_vec(), 1066 maximum_request_bytes: binding 1067 .local_signer_limits() 1068 .expect("limits") 1069 .request_max_bytes(), 1070 }; 1071 let verified = verify(&binding, &operation, result()).expect("describe"); 1072 assert_eq!(verified.protocol_version(), Some(1)); 1073 assert_eq!(verified.capabilities().expect("capabilities").len(), 7); 1074 assert_eq!(verified.maximum_request_bytes(), Some(65_536)); 1075 1076 let duplicate = WireProviderResult::Describe { 1077 public_identity: operation.expected_identity().as_hex().to_owned(), 1078 protocol_version: 1, 1079 capabilities: vec![WireCapability::Describe, WireCapability::Describe], 1080 maximum_request_bytes: 65_536, 1081 }; 1082 assert_eq!( 1083 verify(&binding, &operation, duplicate).unwrap_err().kind(), 1084 MycProviderVerificationErrorKind::Capability 1085 ); 1086 let missing = WireProviderResult::Describe { 1087 public_identity: operation.expected_identity().as_hex().to_owned(), 1088 protocol_version: 1, 1089 capabilities: vec![WireCapability::Describe, WireCapability::PublicIdentity], 1090 maximum_request_bytes: 65_536, 1091 }; 1092 assert_eq!( 1093 verify(&binding, &operation, missing).unwrap_err().kind(), 1094 MycProviderVerificationErrorKind::Capability 1095 ); 1096 let wrong_size = WireProviderResult::Describe { 1097 public_identity: operation.expected_identity().as_hex().to_owned(), 1098 protocol_version: 1, 1099 capabilities: all.to_vec(), 1100 maximum_request_bytes: 65_535, 1101 }; 1102 assert_eq!( 1103 verify(&binding, &operation, wrong_size).unwrap_err().kind(), 1104 MycProviderVerificationErrorKind::Size 1105 ); 1106 let wrong_protocol = WireProviderResult::Describe { 1107 public_identity: operation.expected_identity().as_hex().to_owned(), 1108 protocol_version: 2, 1109 capabilities: all.to_vec(), 1110 maximum_request_bytes: 65_536, 1111 }; 1112 assert_eq!( 1113 verify(&binding, &operation, wrong_protocol) 1114 .unwrap_err() 1115 .kind(), 1116 MycProviderVerificationErrorKind::ResponseBinding 1117 ); 1118 let wrong_identity = WireProviderResult::Describe { 1119 public_identity: keys(2).public_key().to_hex(), 1120 protocol_version: 1, 1121 capabilities: all.to_vec(), 1122 maximum_request_bytes: 65_536, 1123 }; 1124 assert_eq!( 1125 verify(&binding, &operation, wrong_identity) 1126 .unwrap_err() 1127 .kind(), 1128 MycProviderVerificationErrorKind::Identity 1129 ); 1130 } 1131 1132 #[test] 1133 fn signed_event_must_match_every_canonical_unsigned_field_and_signature() { 1134 let identity = keys(1); 1135 let binding = binding(&identity); 1136 let unsigned = UnsignedEvent::new( 1137 identity.public_key(), 1138 Timestamp::from_secs(1_700_000_000), 1139 Kind::TextNote, 1140 Vec::<Tag>::new(), 1141 "signed content", 1142 ); 1143 let unsigned_bytes = serde_json::to_vec(&unsigned).expect("unsigned JSON"); 1144 let operation = make_operation( 1145 &binding, 1146 5, 1147 MycProviderOperationInput::sign_event(&unsigned_bytes).expect("sign input"), 1148 ); 1149 let event = unsigned 1150 .clone() 1151 .sign_with_keys(&identity) 1152 .expect("signed event"); 1153 let event_bytes = serde_json::to_vec(&event).expect("event JSON"); 1154 let verified = verify( 1155 &binding, 1156 &operation, 1157 WireProviderResult::SignEvent { 1158 payload_hex: ProtectedWireHex::from_bytes(&event_bytes), 1159 }, 1160 ) 1161 .expect("verified event"); 1162 assert_eq!(verified.signed_event_bytes(), Some(event_bytes.as_slice())); 1163 assert!(!format!("{verified:?}").contains("signed content")); 1164 1165 let altered = UnsignedEvent::new( 1166 identity.public_key(), 1167 Timestamp::from_secs(1_700_000_000), 1168 Kind::TextNote, 1169 Vec::<Tag>::new(), 1170 "altered content", 1171 ) 1172 .sign_with_keys(&identity) 1173 .expect("altered event"); 1174 let error = verify( 1175 &binding, 1176 &operation, 1177 WireProviderResult::SignEvent { 1178 payload_hex: ProtectedWireHex::from_bytes( 1179 &serde_json::to_vec(&altered).expect("altered JSON"), 1180 ), 1181 }, 1182 ) 1183 .expect_err("altered fields"); 1184 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Event); 1185 1186 let other_identity = keys(2); 1187 let wrong_author = UnsignedEvent::new( 1188 other_identity.public_key(), 1189 Timestamp::from_secs(1_700_000_000), 1190 Kind::TextNote, 1191 Vec::<Tag>::new(), 1192 "signed content", 1193 ) 1194 .sign_with_keys(&other_identity) 1195 .expect("wrong-author event"); 1196 let error = verify( 1197 &binding, 1198 &operation, 1199 WireProviderResult::SignEvent { 1200 payload_hex: ProtectedWireHex::from_bytes( 1201 &serde_json::to_vec(&wrong_author).expect("wrong-author JSON"), 1202 ), 1203 }, 1204 ) 1205 .expect_err("wrong author"); 1206 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Event); 1207 1208 let mut wrong_id = event.clone(); 1209 wrong_id.id = nostr::EventId::all_zeros(); 1210 let error = verify( 1211 &binding, 1212 &operation, 1213 WireProviderResult::SignEvent { 1214 payload_hex: ProtectedWireHex::from_bytes( 1215 &serde_json::to_vec(&wrong_id).expect("wrong-id JSON"), 1216 ), 1217 }, 1218 ) 1219 .expect_err("wrong event ID"); 1220 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Event); 1221 1222 let mut wrong_signature = event.clone(); 1223 wrong_signature.sig = altered.sig; 1224 let error = verify( 1225 &binding, 1226 &operation, 1227 WireProviderResult::SignEvent { 1228 payload_hex: ProtectedWireHex::from_bytes( 1229 &serde_json::to_vec(&wrong_signature).expect("wrong-signature JSON"), 1230 ), 1231 }, 1232 ) 1233 .expect_err("wrong signature"); 1234 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Event); 1235 1236 let noncanonical_input = format!(" {}", String::from_utf8(unsigned_bytes).unwrap()); 1237 let noncanonical_operation = make_operation( 1238 &binding, 1239 6, 1240 MycProviderOperationInput::sign_event(noncanonical_input.as_bytes()) 1241 .expect("bounded input"), 1242 ); 1243 let error = verify( 1244 &binding, 1245 &noncanonical_operation, 1246 WireProviderResult::SignEvent { 1247 payload_hex: ProtectedWireHex::from_bytes(&event_bytes), 1248 }, 1249 ) 1250 .expect_err("noncanonical unsigned event"); 1251 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Event); 1252 } 1253 1254 #[test] 1255 fn nip04_direction_peer_shape_and_padding_are_independently_bound() { 1256 let identity = keys(1); 1257 let peer = keys(2); 1258 let binding = binding(&identity); 1259 let peer_identity = 1260 MycProviderPublicIdentity::new(&peer.public_key().to_hex()).expect("peer"); 1261 let plaintext = b"nip04 plaintext"; 1262 let ciphertext = nip04::encrypt( 1263 identity.secret_key(), 1264 &peer.public_key(), 1265 plaintext.as_slice(), 1266 ) 1267 .expect("nip04 ciphertext"); 1268 let encrypt = make_operation( 1269 &binding, 1270 7, 1271 MycProviderOperationInput::nip04_encrypt(peer_identity.clone(), plaintext) 1272 .expect("encrypt input"), 1273 ); 1274 let verified = verify( 1275 &binding, 1276 &encrypt, 1277 WireProviderResult::Nip04Encrypt { 1278 peer: peer.public_key().to_hex(), 1279 payload_hex: ProtectedWireHex::from_bytes(ciphertext.as_bytes()), 1280 }, 1281 ) 1282 .expect("nip04 encrypt"); 1283 assert_eq!(verified.protected_payload(), Some(ciphertext.as_bytes())); 1284 1285 let error = verify( 1286 &binding, 1287 &encrypt, 1288 WireProviderResult::Nip04Encrypt { 1289 peer: keys(3).public_key().to_hex(), 1290 payload_hex: ProtectedWireHex::from_bytes(ciphertext.as_bytes()), 1291 }, 1292 ) 1293 .expect_err("wrong peer"); 1294 assert_eq!( 1295 error.kind(), 1296 MycProviderVerificationErrorKind::ResponseBinding 1297 ); 1298 let error = verify( 1299 &binding, 1300 &encrypt, 1301 WireProviderResult::Nip04Encrypt { 1302 peer: peer.public_key().to_hex(), 1303 payload_hex: ProtectedWireHex::from_bytes(b"not-ciphertext"), 1304 }, 1305 ) 1306 .expect_err("malformed ciphertext"); 1307 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Nip04); 1308 1309 let decrypt = make_operation( 1310 &binding, 1311 8, 1312 MycProviderOperationInput::nip04_decrypt(peer_identity, ciphertext.as_bytes()) 1313 .expect("decrypt input"), 1314 ); 1315 let verified = verify( 1316 &binding, 1317 &decrypt, 1318 WireProviderResult::Nip04Decrypt { 1319 peer: peer.public_key().to_hex(), 1320 payload_hex: ProtectedWireHex::from_bytes(plaintext), 1321 }, 1322 ) 1323 .expect("nip04 decrypt"); 1324 assert_eq!(verified.protected_payload(), Some(plaintext.as_slice())); 1325 1326 let error = verify( 1327 &binding, 1328 &decrypt, 1329 WireProviderResult::Nip04Decrypt { 1330 peer: peer.public_key().to_hex(), 1331 payload_hex: ProtectedWireHex::from_bytes(&[0; 32]), 1332 }, 1333 ) 1334 .expect_err("plaintext cannot fill the complete ciphertext block capacity"); 1335 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Nip04); 1336 } 1337 1338 #[test] 1339 fn nip44_direction_peer_version_shape_and_padding_are_independently_bound() { 1340 let identity = keys(1); 1341 let peer = keys(2); 1342 let binding = binding(&identity); 1343 let peer_identity = 1344 MycProviderPublicIdentity::new(&peer.public_key().to_hex()).expect("peer"); 1345 let plaintext = b"nip44 plaintext"; 1346 assert!( 1347 MycProviderOperationInput::nip44_encrypt( 1348 peer_identity.clone(), 1349 MycProviderNip44Version::V2, 1350 b"" 1351 ) 1352 .is_err() 1353 ); 1354 let ciphertext = nip44::encrypt( 1355 identity.secret_key(), 1356 &peer.public_key(), 1357 plaintext.as_slice(), 1358 nip44::Version::V2, 1359 ) 1360 .expect("nip44 ciphertext"); 1361 let encrypt = make_operation( 1362 &binding, 1363 9, 1364 MycProviderOperationInput::nip44_encrypt( 1365 peer_identity.clone(), 1366 MycProviderNip44Version::V2, 1367 plaintext, 1368 ) 1369 .expect("encrypt input"), 1370 ); 1371 let verified = verify( 1372 &binding, 1373 &encrypt, 1374 WireProviderResult::Nip44Encrypt { 1375 peer: peer.public_key().to_hex(), 1376 version: 2, 1377 payload_hex: ProtectedWireHex::from_bytes(ciphertext.as_bytes()), 1378 }, 1379 ) 1380 .expect("nip44 encrypt"); 1381 assert_eq!(verified.nip44_version(), Some(MycProviderNip44Version::V2)); 1382 1383 let error = verify( 1384 &binding, 1385 &encrypt, 1386 WireProviderResult::Nip44Encrypt { 1387 peer: peer.public_key().to_hex(), 1388 version: 3, 1389 payload_hex: ProtectedWireHex::from_bytes(ciphertext.as_bytes()), 1390 }, 1391 ) 1392 .expect_err("wrong version"); 1393 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Nip44); 1394 1395 let error = verify( 1396 &binding, 1397 &encrypt, 1398 WireProviderResult::Nip44Encrypt { 1399 peer: keys(3).public_key().to_hex(), 1400 version: 2, 1401 payload_hex: ProtectedWireHex::from_bytes(ciphertext.as_bytes()), 1402 }, 1403 ) 1404 .expect_err("wrong peer"); 1405 assert_eq!( 1406 error.kind(), 1407 MycProviderVerificationErrorKind::ResponseBinding 1408 ); 1409 let error = verify( 1410 &binding, 1411 &encrypt, 1412 WireProviderResult::Nip44Encrypt { 1413 peer: peer.public_key().to_hex(), 1414 version: 2, 1415 payload_hex: ProtectedWireHex::from_bytes(b"not-base64"), 1416 }, 1417 ) 1418 .expect_err("malformed ciphertext"); 1419 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Nip44); 1420 1421 let decrypt = make_operation( 1422 &binding, 1423 10, 1424 MycProviderOperationInput::nip44_decrypt( 1425 peer_identity, 1426 MycProviderNip44Version::V2, 1427 ciphertext.as_bytes(), 1428 ) 1429 .expect("decrypt input"), 1430 ); 1431 let verified = verify( 1432 &binding, 1433 &decrypt, 1434 WireProviderResult::Nip44Decrypt { 1435 peer: peer.public_key().to_hex(), 1436 version: 2, 1437 payload_hex: ProtectedWireHex::from_bytes(plaintext), 1438 }, 1439 ) 1440 .expect("nip44 decrypt"); 1441 assert_eq!(verified.protected_payload(), Some(plaintext.as_slice())); 1442 1443 let error = verify( 1444 &binding, 1445 &decrypt, 1446 WireProviderResult::Nip44Decrypt { 1447 peer: peer.public_key().to_hex(), 1448 version: 2, 1449 payload_hex: ProtectedWireHex::from_bytes(&[0; 33]), 1450 }, 1451 ) 1452 .expect_err("plaintext cannot exceed the ciphertext padding capacity"); 1453 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Nip44); 1454 } 1455 1456 #[test] 1457 fn wrong_result_shape_and_semantic_output_overflow_fail_before_exposure() { 1458 let identity = keys(1); 1459 let binding = binding(&identity); 1460 let operation = make_operation(&binding, 11, MycProviderOperationInput::public_identity()); 1461 let error = verify( 1462 &binding, 1463 &operation, 1464 WireProviderResult::SignEvent { 1465 payload_hex: ProtectedWireHex::from_bytes(b"event"), 1466 }, 1467 ) 1468 .expect_err("wrong shape"); 1469 assert_eq!(error.kind(), MycProviderVerificationErrorKind::ResultShape); 1470 1471 let peer = keys(2); 1472 let peer_identity = 1473 MycProviderPublicIdentity::new(&peer.public_key().to_hex()).expect("peer"); 1474 let decrypt = make_operation( 1475 &binding, 1476 12, 1477 MycProviderOperationInput::nip04_decrypt( 1478 peer_identity, 1479 nip04::encrypt(identity.secret_key(), &peer.public_key(), "input") 1480 .expect("ciphertext") 1481 .as_bytes(), 1482 ) 1483 .expect("decrypt input"), 1484 ); 1485 let oversized = vec![7_u8; MYC_PROVIDER_OUTPUT_MAX_BYTES + 1]; 1486 let error = verify( 1487 &binding, 1488 &decrypt, 1489 WireProviderResult::Nip04Decrypt { 1490 peer: peer.public_key().to_hex(), 1491 payload_hex: ProtectedWireHex::from_bytes(&oversized), 1492 }, 1493 ) 1494 .expect_err("oversized output"); 1495 assert_eq!(error.kind(), MycProviderVerificationErrorKind::Size); 1496 } 1497 1498 #[test] 1499 fn nip44_padding_model_matches_all_exact_protocol_boundaries() { 1500 for (length, padded) in [ 1501 (1, 32), 1502 (32, 32), 1503 (33, 64), 1504 (256, 256), 1505 (257, 320), 1506 (MYC_PROVIDER_NIP44_PLAINTEXT_MAX_BYTES, 65_536), 1507 ] { 1508 assert_eq!(nip44_padding_length(length), Some(padded)); 1509 assert!(is_valid_nip44_padding_length(padded)); 1510 } 1511 assert_eq!(nip44_padding_length(0), None); 1512 assert_eq!( 1513 nip44_padding_length(MYC_PROVIDER_NIP44_PLAINTEXT_MAX_BYTES + 1), 1514 None 1515 ); 1516 for invalid in [0, 31, 33, 288, 65_537] { 1517 assert!(!is_valid_nip44_padding_length(invalid)); 1518 } 1519 } 1520 }