myc

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

runtime_nip46.rs (29580B)


      1 //! Transaction-free runtime coordination for one admitted NIP-46 event.
      2 
      3 use core::{fmt, str::FromStr as _};
      4 use std::{error::Error, sync::Arc};
      5 
      6 use nostr::{JsonUtil as _, Kind, PublicKey as NostrPublicKey, Tag, Timestamp, UnsignedEvent};
      7 use radroots_identity::PublicKey;
      8 use radroots_nostr_connect::{
      9     message::{RemoteSessionCapability, Response, SignedEvent as ConnectSignedEvent},
     10     permission::Permissions,
     11     uri::RelayUrl,
     12 };
     13 use radroots_service_host::{EntropySource, SystemEntropy, SystemWallClock, WallClock};
     14 use sha2::{Digest, Sha256};
     15 
     16 use crate::state_response::MycNip46PendingResponseCommitRequest;
     17 
     18 use crate::{
     19     MycConfigDocumentV1, MycConnectionAdmissionPolicy, MycConnectionDecision,
     20     MycConnectionDecisionRecord, MycConnectionNonce, MycConnectionPermissionSet,
     21     MycConnectionPolicyGeneration, MycConnectionTimeUnixMs, MycNip46AdmissionLimits,
     22     MycNip46AuthoredTimePolicy, MycNip46CommitRequest, MycNip46EncryptionContext,
     23     MycNip46ObservedAtUnixSeconds, MycNip46ResponseCommitRequest, MycNip46Work, MycNip46WorkKind,
     24     MycProviderCorrelationId, MycProviderDeadlineUnixMs, MycProviderNip44Version,
     25     MycProviderOperation, MycProviderOperationId, MycProviderOperationInput,
     26     MycProviderPublicIdentity, MycProviderResponseObservedAtUnixMs, MycProviderRole,
     27     MycRateRelayId, MycRequestReceivedAtUnixMs, MycSignerOperationNonce, MycSignerRequestAdmission,
     28     MycSignerRequestMethod, MycStateHost, MycTaskCancellation, admit_myc_nip46_event,
     29     prepare_myc_nip46_decrypt_work, prepare_myc_nip46_request, prepare_myc_nip46_work,
     30     provider_executor::MycProviderExecutor, verify_myc_nip46_event,
     31 };
     32 
     33 const RESPONSE_ENCRYPT_OPERATION_DOMAIN: &[u8] =
     34     b"radroots.myc.nip46.response_encrypt.operation.v1\0";
     35 const RESPONSE_ENCRYPT_CORRELATION_DOMAIN: &[u8] =
     36     b"radroots.myc.nip46.response_encrypt.correlation.v1\0";
     37 const RESPONSE_SIGN_OPERATION_DOMAIN: &[u8] = b"radroots.myc.nip46.response_sign.operation.v1\0";
     38 const RESPONSE_SIGN_CORRELATION_DOMAIN: &[u8] =
     39     b"radroots.myc.nip46.response_sign.correlation.v1\0";
     40 const NIP46_RPC_KIND: u16 = 24_133;
     41 
     42 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     43 pub(crate) enum MycNip46DispatchDisposition {
     44     Dropped,
     45     PendingApproval,
     46     Completed,
     47     ExactResponseReplay,
     48 }
     49 
     50 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     51 pub(crate) enum MycNip46DispatchErrorKind {
     52     Provider,
     53     State,
     54     Runtime,
     55 }
     56 
     57 pub(crate) struct MycNip46DispatchError {
     58     kind: MycNip46DispatchErrorKind,
     59 }
     60 
     61 impl MycNip46DispatchError {
     62     pub(crate) const fn kind(&self) -> MycNip46DispatchErrorKind {
     63         self.kind
     64     }
     65 }
     66 
     67 impl fmt::Debug for MycNip46DispatchError {
     68     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     69         formatter
     70             .debug_struct("MycNip46DispatchError")
     71             .field("kind", &self.kind)
     72             .finish()
     73     }
     74 }
     75 
     76 impl fmt::Display for MycNip46DispatchError {
     77     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     78         formatter.write_str("Myc NIP-46 dispatch failed")
     79     }
     80 }
     81 
     82 impl Error for MycNip46DispatchError {}
     83 
     84 const fn dispatch_error(kind: MycNip46DispatchErrorKind) -> MycNip46DispatchError {
     85     MycNip46DispatchError { kind }
     86 }
     87 
     88 pub(crate) struct MycRuntimeNip46Coordinator {
     89     configuration: Arc<MycConfigDocumentV1>,
     90     state: Arc<MycStateHost>,
     91     providers: Arc<MycProviderExecutor>,
     92     limits: MycNip46AdmissionLimits,
     93     authored_time: MycNip46AuthoredTimePolicy,
     94     provider_timeout_ms: u64,
     95     response_relays: Box<[RelayUrl]>,
     96 }
     97 
     98 #[derive(Clone, Copy, PartialEq, Eq)]
     99 pub(crate) struct MycRuntimeNip46AdmissionEvidence {
    100     request_nonce: [u8; 32],
    101     received_at: MycRequestReceivedAtUnixMs,
    102 }
    103 
    104 impl fmt::Debug for MycRuntimeNip46AdmissionEvidence {
    105     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    106         formatter.write_str("MycRuntimeNip46AdmissionEvidence([redacted])")
    107     }
    108 }
    109 
    110 impl MycRuntimeNip46AdmissionEvidence {
    111     pub(crate) fn new(
    112         request_nonce: [u8; 32],
    113         received_at_unix_ms: u64,
    114     ) -> Result<Self, MycNip46DispatchError> {
    115         Ok(Self {
    116             request_nonce,
    117             received_at: MycRequestReceivedAtUnixMs::new(received_at_unix_ms)
    118                 .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?,
    119         })
    120     }
    121 }
    122 
    123 impl MycRuntimeNip46Coordinator {
    124     pub(crate) fn new(
    125         configuration: Arc<MycConfigDocumentV1>,
    126         state: Arc<MycStateHost>,
    127         providers: Arc<MycProviderExecutor>,
    128     ) -> Result<Self, MycNip46DispatchError> {
    129         let limits = MycNip46AdmissionLimits::from_config(&configuration)
    130             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    131         let past =
    132             configuration_integer(&configuration, "/transport/ingress/maximum_past_seconds")?;
    133         let future =
    134             configuration_integer(&configuration, "/transport/ingress/maximum_future_seconds")?;
    135         let authored_time = MycNip46AuthoredTimePolicy::new(past, future)
    136             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    137         let provider_timeout_ms = configuration_integer(
    138             &configuration,
    139             "/transport/ingress/subscription_deadline_ms",
    140         )?;
    141         let response_relays = response_relays(&configuration)?;
    142         Ok(Self {
    143             configuration,
    144             state,
    145             providers,
    146             limits,
    147             authored_time,
    148             provider_timeout_ms,
    149             response_relays,
    150         })
    151     }
    152 
    153     pub(crate) async fn process(
    154         &self,
    155         raw_event: &[u8],
    156         relay_id: MycRateRelayId,
    157         observed_at_unix_ms: u64,
    158         admission_evidence: MycRuntimeNip46AdmissionEvidence,
    159         cancellation: &MycTaskCancellation,
    160     ) -> Result<MycNip46DispatchDisposition, MycNip46DispatchError> {
    161         let observed_seconds = observed_at_unix_ms / 1_000;
    162         let bounded = match admit_myc_nip46_event(self.limits, raw_event) {
    163             Ok(bounded) => bounded,
    164             Err(_) => return Ok(MycNip46DispatchDisposition::Dropped),
    165         };
    166         let observed = match MycNip46ObservedAtUnixSeconds::new(observed_seconds) {
    167             Ok(observed) => observed,
    168             Err(_) => return Ok(MycNip46DispatchDisposition::Dropped),
    169         };
    170         let verified = match verify_myc_nip46_event(
    171             bounded,
    172             self.transport_binding()?,
    173             observed,
    174             self.authored_time,
    175         ) {
    176             Ok(verified) => verified,
    177             Err(_) => return Ok(MycNip46DispatchDisposition::Dropped),
    178         };
    179         let decrypt_deadline = self.provider_deadline()?;
    180         let decrypt = match prepare_myc_nip46_decrypt_work(
    181             verified,
    182             self.transport_binding()?,
    183             decrypt_deadline,
    184         ) {
    185             Ok(work) => work,
    186             Err(_) => return Ok(MycNip46DispatchDisposition::Dropped),
    187         };
    188         let decrypt_response = self
    189             .providers
    190             .execute(
    191                 decrypt.operation().owned_for_runtime(),
    192                 provider_observed_now()?,
    193                 cancellation,
    194             )
    195             .await
    196             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Provider))?;
    197         let decrypted = match decrypt.complete(decrypt_response, self.limits) {
    198             Ok(request) => request,
    199             Err(_) => return Ok(MycNip46DispatchDisposition::Dropped),
    200         };
    201         let received_at = MycConnectionTimeUnixMs::new(admission_evidence.received_at.get())
    202             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    203         let prepared = match prepare_myc_nip46_request(
    204             decrypted,
    205             MycSignerOperationNonce::from_injected_entropy(admission_evidence.request_nonce),
    206             admission_evidence.received_at,
    207         ) {
    208             Ok(request) => request,
    209             Err(_) => return Ok(MycNip46DispatchDisposition::Dropped),
    210         };
    211         let admission = self
    212             .state
    213             .repository()
    214             .admit_signer_request(prepared.signer_request())
    215             .await
    216             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::State))?;
    217         if matches!(admission, MycSignerRequestAdmission::ConflictingReuse(_)) {
    218             return Ok(MycNip46DispatchDisposition::Dropped);
    219         }
    220         if self
    221             .state
    222             .repository()
    223             .read_nip46_response_by_operation(admission.record().operation_id())
    224             .await
    225             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::State))?
    226             .is_some()
    227         {
    228             return Ok(MycNip46DispatchDisposition::ExactResponseReplay);
    229         }
    230 
    231         let connection = if prepared.method() == MycSignerRequestMethod::Connect {
    232             None
    233         } else {
    234             self.state
    235                 .repository()
    236                 .read_active_connection_for_client(
    237                     admission.record().client_public_key(),
    238                     received_at,
    239                 )
    240                 .await
    241                 .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::State))?
    242         };
    243         let provider_deadline = matches!(
    244             prepared.method(),
    245             MycSignerRequestMethod::SignEvent
    246                 | MycSignerRequestMethod::Nip04Encrypt
    247                 | MycSignerRequestMethod::Nip04Decrypt
    248                 | MycSignerRequestMethod::Nip44Encrypt
    249                 | MycSignerRequestMethod::Nip44Decrypt
    250         )
    251         .then(|| self.provider_deadline())
    252         .transpose()?;
    253         let work = match prepare_myc_nip46_work(
    254             prepared,
    255             admission.record().clone(),
    256             connection,
    257             self.configuration.provider_contract(),
    258             received_at,
    259             provider_deadline,
    260         ) {
    261             Ok(work) => work,
    262             Err(_) => return Ok(MycNip46DispatchDisposition::Dropped),
    263         };
    264 
    265         let connect_decision = if work.kind() == MycNip46WorkKind::Connect {
    266             let request = self
    267                 .connection_request(&work, relay_id, received_at)
    268                 .await?;
    269             let admission = self
    270                 .state
    271                 .repository()
    272                 .admit_connection(&request)
    273                 .await
    274                 .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::State))?;
    275             let Some(record) = admission.record().cloned() else {
    276                 return Ok(MycNip46DispatchDisposition::Dropped);
    277             };
    278             if record.decision() == MycConnectionDecision::PendingApproval {
    279                 let committed_at = connection_time_now()?;
    280                 let protocol_response = self.protocol_response(&work, Some(&record), None)?;
    281                 let signed = self
    282                     .signed_protocol_response(&work, protocol_response, committed_at, cancellation)
    283                     .await?;
    284                 let commit = MycNip46PendingResponseCommitRequest::new(
    285                     &work,
    286                     &record,
    287                     &signed.operation,
    288                     &signed.response,
    289                     crate::MycDeliveryTimeUnixMs::new(committed_at.get())
    290                         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?,
    291                 )
    292                 .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    293                 self.state
    294                     .repository()
    295                     .commit_nip46_pending_response(&commit)
    296                     .await
    297                     .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::State))?;
    298                 return Ok(MycNip46DispatchDisposition::PendingApproval);
    299             }
    300             if record.decision() == MycConnectionDecision::Challenged {
    301                 return Ok(MycNip46DispatchDisposition::PendingApproval);
    302             }
    303             Some(record)
    304         } else {
    305             None
    306         };
    307 
    308         let provider_response = if let Some(operation) = work.provider_operation() {
    309             Some(
    310                 self.providers
    311                     .execute(
    312                         operation.owned_for_runtime(),
    313                         provider_observed_now()?,
    314                         cancellation,
    315                     )
    316                     .await
    317                     .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Provider))?,
    318             )
    319         } else {
    320             None
    321         };
    322         let completed_at = connection_time_now()?;
    323         let completion = MycNip46CommitRequest::new(
    324             &work,
    325             connect_decision.as_ref(),
    326             provider_response.as_ref(),
    327             completed_at,
    328         )
    329         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    330         let protocol_response =
    331             self.protocol_response(&work, connect_decision.as_ref(), provider_response.as_ref())?;
    332         let signed = self
    333             .signed_protocol_response(&work, protocol_response, completed_at, cancellation)
    334             .await?;
    335         let commit = MycNip46ResponseCommitRequest::new(
    336             &completion,
    337             &signed.operation,
    338             &signed.response,
    339             crate::MycDeliveryTimeUnixMs::new(completed_at.get())
    340                 .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?,
    341         )
    342         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    343         self.state
    344             .repository()
    345             .commit_nip46_response(&commit)
    346             .await
    347             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::State))?;
    348         Ok(MycNip46DispatchDisposition::Completed)
    349     }
    350 
    351     async fn signed_protocol_response(
    352         &self,
    353         work: &MycNip46Work,
    354         response: Response,
    355         committed_at: MycConnectionTimeUnixMs,
    356         cancellation: &MycTaskCancellation,
    357     ) -> Result<SignedRuntimeResponse, MycNip46DispatchError> {
    358         let envelope = response
    359             .into_envelope(work.request_record().request_id().as_str())
    360             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    361         let plaintext = serde_json::to_vec(&envelope)
    362             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    363         let encrypted = self
    364             .encrypt_response(work, &plaintext, cancellation)
    365             .await?;
    366         self.sign_response(work, &encrypted, committed_at, cancellation)
    367             .await
    368     }
    369 
    370     fn transport_binding(&self) -> Result<&crate::MycProviderBinding, MycNip46DispatchError> {
    371         self.configuration
    372             .provider_contract()
    373             .binding(MycProviderRole::Transport)
    374             .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    375     }
    376 
    377     fn provider_deadline(&self) -> Result<MycProviderDeadlineUnixMs, MycNip46DispatchError> {
    378         wall_time_millis()?
    379             .checked_add(self.provider_timeout_ms)
    380             .and_then(|value| MycProviderDeadlineUnixMs::new(value).ok())
    381             .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    382     }
    383 
    384     async fn connection_request(
    385         &self,
    386         work: &MycNip46Work,
    387         relay_id: MycRateRelayId,
    388         observed_at: MycConnectionTimeUnixMs,
    389     ) -> Result<crate::MycConnectionAdmissionRequest, MycNip46DispatchError> {
    390         let client = work.request_record().client_public_key();
    391         let normalized = self.configuration.normalized();
    392         let contains = |pointer: &str| {
    393             normalized
    394                 .pointer(pointer)
    395                 .and_then(serde_json::Value::as_array)
    396                 .is_some_and(|values| {
    397                     values
    398                         .iter()
    399                         .any(|value| value.as_str() == Some(client.as_hex()))
    400                 })
    401         };
    402         let policy = if contains("/policy/denied_clients") {
    403             MycConnectionAdmissionPolicy::Denied
    404         } else if contains("/policy/trusted_clients") {
    405             MycConnectionAdmissionPolicy::Trusted
    406         } else {
    407             MycConnectionAdmissionPolicy::ExplicitApproval
    408         };
    409         let authorized_until = if policy == MycConnectionAdmissionPolicy::Trusted
    410             && normalized
    411                 .pointer("/policy/challenges/enabled")
    412                 .and_then(serde_json::Value::as_bool)
    413                 == Some(true)
    414         {
    415             let lifetime = configuration_integer(
    416                 &self.configuration,
    417                 "/policy/challenges/authorized_lifetime_ms",
    418             )?;
    419             Some(
    420                 observed_at
    421                     .get()
    422                     .checked_add(lifetime)
    423                     .and_then(|value| MycConnectionTimeUnixMs::new(value).ok())
    424                     .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))?,
    425             )
    426         } else {
    427             None
    428         };
    429         let generation = self
    430             .state
    431             .repository()
    432             .current_configuration_generation()
    433             .await
    434             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::State))?;
    435         let mut nonce = [0_u8; 32];
    436         SystemEntropy
    437             .fill_bytes(&mut nonce)
    438             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    439         work.connection_admission_request(
    440             MycConnectionPolicyGeneration::new(u64::from(generation))
    441                 .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?,
    442             MycConnectionNonce::from_injected_entropy(nonce),
    443             observed_at,
    444             authorized_until,
    445             policy,
    446             relay_id,
    447         )
    448         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    449     }
    450 
    451     fn protocol_response(
    452         &self,
    453         work: &MycNip46Work,
    454         connect: Option<&MycConnectionDecisionRecord>,
    455         provider: Option<&crate::MycVerifiedProviderResponse>,
    456     ) -> Result<Response, MycNip46DispatchError> {
    457         let user = PublicKey::from_hex(
    458             self.configuration
    459                 .provider_contract()
    460                 .binding(MycProviderRole::User)
    461                 .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))?
    462                 .expected_identity()
    463                 .as_hex(),
    464         )
    465         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    466         match work.method() {
    467             MycSignerRequestMethod::Connect => {
    468                 match connect.map(MycConnectionDecisionRecord::decision) {
    469                     Some(MycConnectionDecision::Allowed) => Ok(Response::UserPublicKey(user)),
    470                     Some(MycConnectionDecision::PendingApproval) => Ok(Response::PendingConnection),
    471                     Some(MycConnectionDecision::Denied) => Ok(Response::Error {
    472                         result: None,
    473                         error: "connection_denied".to_owned(),
    474                     }),
    475                     _ => Err(dispatch_error(MycNip46DispatchErrorKind::Runtime)),
    476                 }
    477             }
    478             MycSignerRequestMethod::GetPublicKey => Ok(Response::UserPublicKey(user)),
    479             MycSignerRequestMethod::GetSessionCapability => {
    480                 let permissions = protocol_permissions(
    481                     work.connection()
    482                         .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))?
    483                         .granted_permissions(),
    484                 )?;
    485                 Ok(Response::RemoteSessionCapability(
    486                     RemoteSessionCapability::try_new(
    487                         user,
    488                         self.response_relays.to_vec(),
    489                         permissions,
    490                     )
    491                     .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?,
    492                 ))
    493             }
    494             MycSignerRequestMethod::SignEvent => {
    495                 let bytes = provider
    496                     .and_then(crate::MycVerifiedProviderResponse::signed_event_bytes)
    497                     .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    498                 let json = core::str::from_utf8(bytes)
    499                     .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    500                 Ok(Response::SignedEvent(
    501                     ConnectSignedEvent::from_json(json)
    502                         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?,
    503                 ))
    504             }
    505             MycSignerRequestMethod::Nip04Encrypt => {
    506                 Ok(Response::Nip04Encrypt(protected_text(provider)?))
    507             }
    508             MycSignerRequestMethod::Nip04Decrypt => {
    509                 Ok(Response::Nip04Decrypt(protected_text(provider)?))
    510             }
    511             MycSignerRequestMethod::Nip44Encrypt => {
    512                 Ok(Response::Nip44Encrypt(protected_text(provider)?))
    513             }
    514             MycSignerRequestMethod::Nip44Decrypt => {
    515                 Ok(Response::Nip44Decrypt(protected_text(provider)?))
    516             }
    517             MycSignerRequestMethod::Ping => Ok(Response::Pong),
    518             MycSignerRequestMethod::SwitchRelays => Ok(Response::RelayListUnchanged),
    519             MycSignerRequestMethod::Logout => Ok(Response::LogoutAcknowledged),
    520         }
    521     }
    522 
    523     async fn encrypt_response(
    524         &self,
    525         work: &MycNip46Work,
    526         plaintext: &[u8],
    527         cancellation: &MycTaskCancellation,
    528     ) -> Result<Vec<u8>, MycNip46DispatchError> {
    529         let binding = self
    530             .configuration
    531             .provider_contract()
    532             .binding(MycProviderRole::Transport)
    533             .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    534         let peer =
    535             MycProviderPublicIdentity::new(work.request_record().client_public_key().as_hex())
    536                 .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    537         let input = match work.encryption_context() {
    538             MycNip46EncryptionContext::Nip04 => {
    539                 MycProviderOperationInput::nip04_encrypt(peer, plaintext)
    540             }
    541             MycNip46EncryptionContext::Nip44V2 => MycProviderOperationInput::nip44_encrypt(
    542                 peer,
    543                 MycProviderNip44Version::V2,
    544                 plaintext,
    545             ),
    546         }
    547         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    548         let operation = derived_operation(
    549             binding,
    550             work,
    551             RESPONSE_ENCRYPT_OPERATION_DOMAIN,
    552             RESPONSE_ENCRYPT_CORRELATION_DOMAIN,
    553             self.provider_deadline()?,
    554             input,
    555         )?;
    556         let response = self
    557             .providers
    558             .execute(operation, provider_observed_now()?, cancellation)
    559             .await
    560             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Provider))?;
    561         response
    562             .protected_payload()
    563             .map(<[u8]>::to_vec)
    564             .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Provider))
    565     }
    566 
    567     async fn sign_response(
    568         &self,
    569         work: &MycNip46Work,
    570         ciphertext: &[u8],
    571         completed_at: MycConnectionTimeUnixMs,
    572         cancellation: &MycTaskCancellation,
    573     ) -> Result<SignedRuntimeResponse, MycNip46DispatchError> {
    574         let content = core::str::from_utf8(ciphertext)
    575             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Provider))?;
    576         let transport_binding = self
    577             .configuration
    578             .provider_contract()
    579             .binding(MycProviderRole::Transport)
    580             .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    581         let transport = NostrPublicKey::from_hex(transport_binding.expected_identity().as_hex())
    582             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    583         let client = NostrPublicKey::from_hex(work.request_record().client_public_key().as_hex())
    584             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    585         let unsigned = UnsignedEvent::new(
    586             transport,
    587             Timestamp::from_secs(completed_at.get() / 1_000),
    588             Kind::Custom(NIP46_RPC_KIND),
    589             vec![Tag::public_key(client)],
    590             content,
    591         );
    592         let input = MycProviderOperationInput::sign_event(unsigned.as_json().as_bytes())
    593             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    594         let operation = derived_operation(
    595             transport_binding,
    596             work,
    597             RESPONSE_SIGN_OPERATION_DOMAIN,
    598             RESPONSE_SIGN_CORRELATION_DOMAIN,
    599             self.provider_deadline()?,
    600             input,
    601         )?;
    602         let response = self
    603             .providers
    604             .execute(
    605                 operation.owned_for_runtime(),
    606                 provider_observed_now()?,
    607                 cancellation,
    608             )
    609             .await
    610             .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Provider))?;
    611         Ok(SignedRuntimeResponse {
    612             operation,
    613             response,
    614         })
    615     }
    616 }
    617 
    618 struct SignedRuntimeResponse {
    619     operation: MycProviderOperation,
    620     response: crate::MycVerifiedProviderResponse,
    621 }
    622 
    623 fn derived_operation(
    624     binding: &crate::MycProviderBinding,
    625     work: &MycNip46Work,
    626     operation_domain: &[u8],
    627     correlation_domain: &[u8],
    628     deadline: MycProviderDeadlineUnixMs,
    629     input: MycProviderOperationInput,
    630 ) -> Result<MycProviderOperation, MycNip46DispatchError> {
    631     let identity = work.request_record().operation_id();
    632     MycProviderOperation::new(
    633         binding,
    634         MycProviderOperationId::from_bytes(derived_identifier(
    635             operation_domain,
    636             identity.as_bytes(),
    637         )),
    638         MycProviderCorrelationId::from_bytes(derived_identifier(
    639             correlation_domain,
    640             identity.as_bytes(),
    641         )),
    642         deadline,
    643         input,
    644     )
    645     .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    646 }
    647 
    648 fn derived_identifier(domain: &[u8], operation_id: &[u8; 32]) -> [u8; 32] {
    649     let mut hasher = Sha256::new();
    650     hasher.update(domain);
    651     hasher.update(operation_id);
    652     hasher.finalize().into()
    653 }
    654 
    655 fn protected_text(
    656     provider: Option<&crate::MycVerifiedProviderResponse>,
    657 ) -> Result<String, MycNip46DispatchError> {
    658     provider
    659         .and_then(crate::MycVerifiedProviderResponse::protected_payload)
    660         .and_then(|bytes| core::str::from_utf8(bytes).ok())
    661         .map(str::to_owned)
    662         .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    663 }
    664 
    665 fn protocol_permissions(
    666     permissions: &MycConnectionPermissionSet,
    667 ) -> Result<Permissions, MycNip46DispatchError> {
    668     let value = permissions
    669         .permissions()
    670         .iter()
    671         .map(|permission| permission.code())
    672         .collect::<Vec<_>>()
    673         .join(",");
    674     Permissions::from_str(&value).map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    675 }
    676 
    677 fn response_relays(
    678     configuration: &MycConfigDocumentV1,
    679 ) -> Result<Box<[RelayUrl]>, MycNip46DispatchError> {
    680     let relays = configuration
    681         .normalized()
    682         .pointer("/relays")
    683         .and_then(serde_json::Value::as_array)
    684         .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))?;
    685     relays
    686         .iter()
    687         .filter(|relay| relay.pointer("/write").and_then(serde_json::Value::as_bool) == Some(true))
    688         .map(|relay| {
    689             relay
    690                 .pointer("/url")
    691                 .and_then(serde_json::Value::as_str)
    692                 .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    693                 .and_then(|url| {
    694                     RelayUrl::parse(url)
    695                         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    696                 })
    697         })
    698         .collect::<Result<Vec<_>, _>>()
    699         .map(Vec::into_boxed_slice)
    700 }
    701 
    702 fn wall_time_millis() -> Result<u64, MycNip46DispatchError> {
    703     SystemWallClock
    704         .now_utc()
    705         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))?
    706         .get()
    707         .checked_mul(1_000)
    708         .filter(|value| i64::try_from(*value).is_ok())
    709         .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    710 }
    711 
    712 fn connection_time_now() -> Result<MycConnectionTimeUnixMs, MycNip46DispatchError> {
    713     MycConnectionTimeUnixMs::new(wall_time_millis()?)
    714         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    715 }
    716 
    717 fn provider_observed_now() -> Result<MycProviderResponseObservedAtUnixMs, MycNip46DispatchError> {
    718     MycProviderResponseObservedAtUnixMs::new(wall_time_millis()?)
    719         .map_err(|_| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    720 }
    721 
    722 fn configuration_integer(
    723     configuration: &MycConfigDocumentV1,
    724     pointer: &str,
    725 ) -> Result<u64, MycNip46DispatchError> {
    726     configuration
    727         .normalized()
    728         .pointer(pointer)
    729         .and_then(serde_json::Value::as_u64)
    730         .ok_or_else(|| dispatch_error(MycNip46DispatchErrorKind::Runtime))
    731 }
    732 
    733 #[cfg(test)]
    734 mod tests {
    735     use super::*;
    736 
    737     #[test]
    738     fn admission_evidence_is_copyable_across_exact_runtime_retries() {
    739         let evidence = MycRuntimeNip46AdmissionEvidence::new([0x5a; 32], 1_725_000_000_000)
    740             .expect("admission evidence");
    741         assert_eq!(evidence, evidence);
    742         assert_eq!(evidence.request_nonce, [0x5a; 32]);
    743         assert_eq!(evidence.received_at.get(), 1_725_000_000_000);
    744         assert_eq!(
    745             format!("{evidence:?}"),
    746             "MycRuntimeNip46AdmissionEvidence([redacted])"
    747         );
    748         assert!(MycRuntimeNip46AdmissionEvidence::new([0x5a; 32], 0).is_err());
    749         assert!(MycRuntimeNip46AdmissionEvidence::new([0x5a; 32], u64::MAX).is_err());
    750     }
    751 }