myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

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 }