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 }