state_request.rs (37892B)
1 //! Durable NIP-46 request identity and idempotent admission. 2 3 use core::fmt; 4 use std::error::Error; 5 6 use nostr::PublicKey; 7 use radroots_service_sqlite::{ 8 ServiceSqliteTransaction, ServiceSqliteTransactionError, ServiceSqliteTransactionErrorKind, 9 }; 10 use sha2::{Digest, Sha256}; 11 use sqlx::Row; 12 13 use crate::{ 14 MycStateRepository, MycStateRepositoryError, MycStateRepositoryErrorKind, 15 state_repository::{PersistedMetadata, RepositoryOperationError, require_expected_metadata}, 16 }; 17 18 /// Maximum UTF-8 byte length of a canonical NIP-46 request identifier. 19 pub const MYC_NIP46_REQUEST_ID_MAX_UTF8_BYTES: usize = 128; 20 21 /// Maximum canonical decrypted request bytes accepted by the state boundary. 22 pub const MYC_NIP46_CANONICAL_REQUEST_MAX_BYTES: usize = 262_144; 23 24 const REQUEST_DIGEST_DOMAIN: &[u8] = b"radroots.myc.nip46.request.v1\0"; 25 const REQUEST_IDENTITY_DOMAIN: &[u8] = b"radroots.myc.nip46.request_identity.v1\0"; 26 const OPERATION_ID_DOMAIN: &[u8] = b"radroots.myc.nip46.operation.v1\0"; 27 const CORRELATION_ID_DOMAIN: &[u8] = b"radroots.myc.nip46.correlation.v1\0"; 28 29 const READ_BY_DEDUP_SQL: &str = r#"SELECT 30 CASE WHEN typeof(r.operation_id) = 'blob' AND length(r.operation_id) = 32 31 THEN r.operation_id ELSE NULL END AS operation_id, 32 CASE WHEN typeof(r.correlation_id) = 'blob' AND length(r.correlation_id) = 32 33 THEN r.correlation_id ELSE NULL END AS correlation_id, 34 CASE WHEN typeof(r.operation_nonce) = 'blob' AND length(r.operation_nonce) = 32 35 THEN r.operation_nonce ELSE NULL END AS operation_nonce, 36 CASE 37 WHEN typeof(r.request_identity_sha256) = 'blob' 38 AND length(r.request_identity_sha256) = 32 39 THEN r.request_identity_sha256 40 ELSE NULL 41 END AS request_identity_sha256, 42 CASE 43 WHEN typeof(selected.request_sha256) = 'blob' 44 AND length(selected.request_sha256) = 32 45 THEN selected.request_sha256 46 ELSE NULL 47 END AS selected_request_sha256, 48 CASE 49 WHEN typeof(request_dedup.request_sha256) = 'blob' 50 AND length(request_dedup.request_sha256) = 32 51 THEN request_dedup.request_sha256 52 ELSE NULL 53 END AS logical_request_sha256, 54 CASE 55 WHEN typeof(request_dedup.operation_id) = 'blob' 56 AND length(request_dedup.operation_id) = 32 57 THEN request_dedup.operation_id 58 ELSE NULL 59 END AS logical_operation_id, 60 CASE 61 WHEN typeof(r.client_public_key) = 'text' 62 AND length(CAST(r.client_public_key AS BLOB)) = 64 63 THEN r.client_public_key 64 ELSE NULL 65 END AS client_public_key, 66 CASE 67 WHEN typeof(r.request_id) = 'text' 68 AND length(CAST(r.request_id AS BLOB)) BETWEEN 1 AND 128 69 THEN r.request_id 70 ELSE NULL 71 END AS request_id, 72 CASE WHEN typeof(r.first_event_id) = 'blob' AND length(r.first_event_id) = 32 73 THEN r.first_event_id ELSE NULL END AS first_event_id, 74 CASE 75 WHEN typeof(r.method) = 'text' 76 AND length(CAST(r.method AS BLOB)) BETWEEN 1 AND 64 77 THEN r.method 78 ELSE NULL 79 END AS method, 80 CASE WHEN typeof(r.request_sha256) = 'blob' AND length(r.request_sha256) = 32 81 THEN r.request_sha256 ELSE NULL END AS request_sha256, 82 CASE 83 WHEN typeof(r.received_at_unix_ms) = 'integer' 84 AND r.received_at_unix_ms BETWEEN 1 AND 9223372036854775807 85 THEN r.received_at_unix_ms 86 ELSE NULL 87 END AS received_at_unix_ms, 88 CASE 89 WHEN typeof(request_dedup.replay_count) = 'integer' 90 AND request_dedup.replay_count BETWEEN 0 AND 9223372036854775807 91 THEN request_dedup.replay_count 92 ELSE NULL 93 END AS replay_count, 94 CASE 95 WHEN typeof(request_dedup.conflict_count) = 'integer' 96 AND request_dedup.conflict_count BETWEEN 0 AND 9223372036854775807 97 THEN request_dedup.conflict_count 98 ELSE NULL 99 END AS conflict_count, 100 CASE 101 WHEN typeof(request_dedup.last_seen_at_unix_ms) = 'integer' 102 AND request_dedup.last_seen_at_unix_ms BETWEEN 1 AND 9223372036854775807 103 THEN request_dedup.last_seen_at_unix_ms 104 ELSE NULL 105 END AS last_seen_at_unix_ms 106 FROM nip46_request_dedup AS selected 107 JOIN nip46_requests AS r ON r.operation_id = selected.operation_id 108 JOIN nip46_request_dedup AS request_dedup 109 ON request_dedup.dedup_kind = 'request' 110 AND request_dedup.identity_sha256 = r.request_identity_sha256 111 WHERE selected.dedup_kind = ? AND selected.identity_sha256 = ? 112 LIMIT 2"#; 113 114 const INSERT_REQUEST_SQL: &str = r#"INSERT INTO nip46_requests ( 115 operation_id, 116 correlation_id, 117 operation_nonce, 118 request_identity_sha256, 119 client_public_key, 120 request_id, 121 first_event_id, 122 method, 123 request_sha256, 124 received_at_unix_ms 125 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#; 126 127 const INSERT_DEDUP_SQL: &str = r#"INSERT INTO nip46_request_dedup ( 128 dedup_kind, 129 identity_sha256, 130 request_sha256, 131 operation_id, 132 replay_count, 133 conflict_count, 134 first_seen_at_unix_ms, 135 last_seen_at_unix_ms 136 ) VALUES (?, ?, ?, ?, ?, 0, ?, ?)"#; 137 138 const RECORD_REPLAY_SQL: &str = r#"UPDATE nip46_request_dedup 139 SET replay_count = replay_count + 1, 140 last_seen_at_unix_ms = MAX(last_seen_at_unix_ms, ?) 141 WHERE dedup_kind = ? 142 AND identity_sha256 = ? 143 AND replay_count < 9223372036854775807"#; 144 145 const RECORD_CONFLICT_SQL: &str = r#"UPDATE nip46_request_dedup 146 SET conflict_count = conflict_count + 1, 147 last_seen_at_unix_ms = MAX(last_seen_at_unix_ms, ?) 148 WHERE dedup_kind = ? 149 AND identity_sha256 = ? 150 AND conflict_count < 9223372036854775807"#; 151 152 /// A bounded canonical NIP-46 request ID. 153 #[derive(Clone, PartialEq, Eq, Hash)] 154 pub struct MycNip46RequestId(Box<str>); 155 156 impl MycNip46RequestId { 157 /// Validates borrowed input before allocating an owned identifier. 158 pub fn new(value: &str) -> Result<Self, MycSignerRequestError> { 159 if value.is_empty() 160 || value.len() > MYC_NIP46_REQUEST_ID_MAX_UTF8_BYTES 161 || value.trim() != value 162 || value.chars().any(char::is_control) 163 { 164 return Err(MycSignerRequestError::new( 165 MycSignerRequestErrorKind::InvalidRequestId, 166 )); 167 } 168 Ok(Self(value.into())) 169 } 170 171 /// Returns the exact canonical request ID. 172 #[must_use] 173 pub fn as_str(&self) -> &str { 174 &self.0 175 } 176 } 177 178 impl fmt::Debug for MycNip46RequestId { 179 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 180 formatter.write_str("MycNip46RequestId([redacted])") 181 } 182 } 183 184 /// One validated NIP-46 client public key. 185 #[derive(Clone, PartialEq, Eq, Hash)] 186 pub struct MycNip46ClientPublicKey(Box<str>); 187 188 impl MycNip46ClientPublicKey { 189 /// Parses one canonical lowercase 32-byte x-only public key. 190 pub fn new(value: &str) -> Result<Self, MycSignerRequestError> { 191 if value.len() != 64 192 || value 193 .bytes() 194 .any(|byte| !byte.is_ascii_hexdigit() || byte.is_ascii_uppercase()) 195 { 196 return Err(MycSignerRequestError::new( 197 MycSignerRequestErrorKind::InvalidClientIdentity, 198 )); 199 } 200 let public_key = PublicKey::from_hex(value).map_err(|_| { 201 MycSignerRequestError::new(MycSignerRequestErrorKind::InvalidClientIdentity) 202 })?; 203 if public_key.to_hex() != value { 204 return Err(MycSignerRequestError::new( 205 MycSignerRequestErrorKind::InvalidClientIdentity, 206 )); 207 } 208 Ok(Self(value.into())) 209 } 210 211 /// Returns the canonical public identity. 212 #[must_use] 213 pub fn as_hex(&self) -> &str { 214 &self.0 215 } 216 } 217 218 impl fmt::Debug for MycNip46ClientPublicKey { 219 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 220 formatter.write_str("MycNip46ClientPublicKey([redacted])") 221 } 222 } 223 224 macro_rules! redacted_digest { 225 ($name:ident) => { 226 #[derive(Clone, Copy, PartialEq, Eq, Hash)] 227 pub struct $name([u8; 32]); 228 229 impl $name { 230 /// Returns the exact identity bytes. 231 #[must_use] 232 pub const fn as_bytes(&self) -> &[u8; 32] { 233 &self.0 234 } 235 } 236 237 impl fmt::Debug for $name { 238 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 239 formatter.write_str(concat!(stringify!($name), "([redacted])")) 240 } 241 } 242 }; 243 } 244 245 redacted_digest!(MycNip46EventId); 246 redacted_digest!(MycSignerRequestDigest); 247 redacted_digest!(MycSignerOperationId); 248 redacted_digest!(MycSignerCorrelationId); 249 250 impl MycSignerOperationId { 251 pub(crate) const fn from_persisted(bytes: [u8; 32]) -> Self { 252 Self(bytes) 253 } 254 } 255 256 /// One-use entropy evidence for a new logical signer operation. 257 /// 258 /// The runtime obtains these bytes from its injected entropy source. Admission 259 /// consumes the value, binds it to the logical request identity, and persists 260 /// it so the resulting operation identity can be revalidated after restart. 261 #[derive(PartialEq, Eq)] 262 pub struct MycSignerOperationNonce([u8; 32]); 263 264 impl MycSignerOperationNonce { 265 /// Wraps exact bytes supplied by the injected entropy boundary. 266 #[must_use] 267 pub const fn from_injected_entropy(bytes: [u8; 32]) -> Self { 268 Self(bytes) 269 } 270 } 271 272 impl fmt::Debug for MycSignerOperationNonce { 273 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 274 formatter.write_str("MycSignerOperationNonce([redacted])") 275 } 276 } 277 278 impl MycNip46EventId { 279 /// Constructs an event identity from an already verified NIP-01 event ID. 280 #[must_use] 281 pub const fn from_bytes(bytes: [u8; 32]) -> Self { 282 Self(bytes) 283 } 284 } 285 286 impl MycSignerRequestDigest { 287 /// Hashes exact canonical decrypted request bytes under the Myc domain. 288 pub fn for_canonical_request(bytes: &[u8]) -> Result<Self, MycSignerRequestError> { 289 if bytes.is_empty() || bytes.len() > MYC_NIP46_CANONICAL_REQUEST_MAX_BYTES { 290 return Err(MycSignerRequestError::new( 291 MycSignerRequestErrorKind::InvalidCanonicalRequest, 292 )); 293 } 294 let mut hasher = Sha256::new(); 295 hasher.update(REQUEST_DIGEST_DOMAIN); 296 hasher.update( 297 u64::try_from(bytes.len()) 298 .expect("bounded request length fits u64") 299 .to_be_bytes(), 300 ); 301 hasher.update(bytes); 302 Ok(Self(hasher.finalize().into())) 303 } 304 } 305 306 /// Exact supported NIP-46 method identity. 307 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 308 pub enum MycSignerRequestMethod { 309 Connect, 310 GetPublicKey, 311 GetSessionCapability, 312 SignEvent, 313 Nip04Encrypt, 314 Nip04Decrypt, 315 Nip44Encrypt, 316 Nip44Decrypt, 317 Ping, 318 SwitchRelays, 319 Logout, 320 } 321 322 impl MycSignerRequestMethod { 323 /// Returns the canonical NIP-46 wire spelling. 324 #[must_use] 325 pub const fn as_str(self) -> &'static str { 326 match self { 327 Self::Connect => "connect", 328 Self::GetPublicKey => "get_public_key", 329 Self::GetSessionCapability => "get_session_capability", 330 Self::SignEvent => "sign_event", 331 Self::Nip04Encrypt => "nip04_encrypt", 332 Self::Nip04Decrypt => "nip04_decrypt", 333 Self::Nip44Encrypt => "nip44_encrypt", 334 Self::Nip44Decrypt => "nip44_decrypt", 335 Self::Ping => "ping", 336 Self::SwitchRelays => "switch_relays", 337 Self::Logout => "logout", 338 } 339 } 340 341 pub(crate) fn parse(value: &str) -> Option<Self> { 342 match value { 343 "connect" => Some(Self::Connect), 344 "get_public_key" => Some(Self::GetPublicKey), 345 "get_session_capability" => Some(Self::GetSessionCapability), 346 "sign_event" => Some(Self::SignEvent), 347 "nip04_encrypt" => Some(Self::Nip04Encrypt), 348 "nip04_decrypt" => Some(Self::Nip04Decrypt), 349 "nip44_encrypt" => Some(Self::Nip44Encrypt), 350 "nip44_decrypt" => Some(Self::Nip44Decrypt), 351 "ping" => Some(Self::Ping), 352 "switch_relays" => Some(Self::SwitchRelays), 353 "logout" => Some(Self::Logout), 354 _ => None, 355 } 356 } 357 } 358 359 /// Positive UTC millisecond instant at which a request reached admission. 360 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 361 pub struct MycRequestReceivedAtUnixMs(u64); 362 363 impl MycRequestReceivedAtUnixMs { 364 /// Validates a positive instant representable by SQLite's signed integer. 365 pub fn new(value: u64) -> Result<Self, MycSignerRequestError> { 366 if value == 0 || i64::try_from(value).is_err() { 367 return Err(MycSignerRequestError::new( 368 MycSignerRequestErrorKind::InvalidReceivedAt, 369 )); 370 } 371 Ok(Self(value)) 372 } 373 374 #[must_use] 375 /// Returns the validated UTC millisecond instant. 376 pub const fn get(self) -> u64 { 377 self.0 378 } 379 380 fn sqlite_value(self) -> i64 { 381 i64::try_from(self.0).expect("validated request time fits SQLite integer") 382 } 383 } 384 385 /// Fully validated request-admission input. 386 #[derive(PartialEq, Eq)] 387 pub struct MycSignerRequest { 388 client_public_key: MycNip46ClientPublicKey, 389 request_id: MycNip46RequestId, 390 event_id: MycNip46EventId, 391 method: MycSignerRequestMethod, 392 request_digest: MycSignerRequestDigest, 393 request_identity: [u8; 32], 394 operation_nonce: MycSignerOperationNonce, 395 received_at: MycRequestReceivedAtUnixMs, 396 } 397 398 impl MycSignerRequest { 399 /// Binds validated input to one caller-supplied injected-entropy value. 400 #[must_use] 401 pub fn new( 402 client_public_key: MycNip46ClientPublicKey, 403 request_id: MycNip46RequestId, 404 event_id: MycNip46EventId, 405 method: MycSignerRequestMethod, 406 request_digest: MycSignerRequestDigest, 407 operation_nonce: MycSignerOperationNonce, 408 received_at: MycRequestReceivedAtUnixMs, 409 ) -> Self { 410 let request_identity = derive_request_identity(&client_public_key, &request_id); 411 Self { 412 client_public_key, 413 request_id, 414 event_id, 415 method, 416 request_digest, 417 request_identity, 418 operation_nonce, 419 received_at, 420 } 421 } 422 423 fn owned(&self) -> Self { 424 Self { 425 client_public_key: self.client_public_key.clone(), 426 request_id: self.request_id.clone(), 427 event_id: self.event_id, 428 method: self.method, 429 request_digest: self.request_digest, 430 request_identity: self.request_identity, 431 operation_nonce: MycSignerOperationNonce(self.operation_nonce.0), 432 received_at: self.received_at, 433 } 434 } 435 436 pub(crate) const fn client_public_key(&self) -> &MycNip46ClientPublicKey { 437 &self.client_public_key 438 } 439 440 pub(crate) const fn request_id(&self) -> &MycNip46RequestId { 441 &self.request_id 442 } 443 444 pub(crate) const fn event_id(&self) -> MycNip46EventId { 445 self.event_id 446 } 447 448 pub(crate) const fn method(&self) -> MycSignerRequestMethod { 449 self.method 450 } 451 452 pub(crate) const fn request_digest(&self) -> MycSignerRequestDigest { 453 self.request_digest 454 } 455 456 fn derived_operation_id(&self) -> MycSignerOperationId { 457 MycSignerOperationId(derive_operation_id( 458 &self.request_identity, 459 &self.operation_nonce, 460 )) 461 } 462 463 fn derived_correlation_id(&self) -> MycSignerCorrelationId { 464 MycSignerCorrelationId(derive_digest( 465 CORRELATION_ID_DOMAIN, 466 self.derived_operation_id().as_bytes(), 467 )) 468 } 469 470 #[cfg(test)] 471 pub(crate) fn admitted_record_for_test(&self) -> MycSignerRequestRecord { 472 MycSignerRequestRecord { 473 operation_id: self.derived_operation_id(), 474 correlation_id: self.derived_correlation_id(), 475 client_public_key: self.client_public_key.clone(), 476 request_id: self.request_id.clone(), 477 first_event_id: self.event_id, 478 method: self.method, 479 request_digest: self.request_digest, 480 received_at: self.received_at, 481 replay_count: 0, 482 conflict_count: 0, 483 last_seen_at: self.received_at, 484 } 485 } 486 } 487 488 impl fmt::Debug for MycSignerRequest { 489 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 490 formatter.write_str("MycSignerRequest([redacted])") 491 } 492 } 493 494 /// Durable state of one accepted logical NIP-46 request. 495 #[derive(Clone, PartialEq, Eq)] 496 pub struct MycSignerRequestRecord { 497 operation_id: MycSignerOperationId, 498 correlation_id: MycSignerCorrelationId, 499 client_public_key: MycNip46ClientPublicKey, 500 request_id: MycNip46RequestId, 501 first_event_id: MycNip46EventId, 502 method: MycSignerRequestMethod, 503 request_digest: MycSignerRequestDigest, 504 received_at: MycRequestReceivedAtUnixMs, 505 replay_count: u64, 506 conflict_count: u64, 507 last_seen_at: MycRequestReceivedAtUnixMs, 508 } 509 510 impl MycSignerRequestRecord { 511 pub(crate) const fn client_public_key(&self) -> &MycNip46ClientPublicKey { 512 &self.client_public_key 513 } 514 515 pub(crate) const fn request_id(&self) -> &MycNip46RequestId { 516 &self.request_id 517 } 518 519 #[must_use] 520 /// Returns the stable logical operation identity. 521 pub const fn operation_id(&self) -> MycSignerOperationId { 522 self.operation_id 523 } 524 525 #[must_use] 526 /// Returns the stable correlation identity. 527 pub const fn correlation_id(&self) -> MycSignerCorrelationId { 528 self.correlation_id 529 } 530 531 #[must_use] 532 /// Returns the closed request method recorded for the operation. 533 pub const fn method(&self) -> MycSignerRequestMethod { 534 self.method 535 } 536 537 #[must_use] 538 /// Returns the number of accepted exact replays. 539 pub const fn replay_count(&self) -> u64 { 540 self.replay_count 541 } 542 543 #[must_use] 544 /// Returns the number of rejected conflicting reuses. 545 pub const fn conflict_count(&self) -> u64 { 546 self.conflict_count 547 } 548 549 pub(crate) fn matches_request(&self, request: &MycSignerRequest) -> bool { 550 self.operation_id == request.derived_operation_id() 551 && self.correlation_id == request.derived_correlation_id() 552 && &self.client_public_key == request.client_public_key() 553 && &self.request_id == request.request_id() 554 && self.first_event_id == request.event_id() 555 && self.method == request.method() 556 && self.request_digest == request.request_digest() 557 && self.received_at == request.received_at 558 } 559 560 pub(crate) const fn received_at(&self) -> MycRequestReceivedAtUnixMs { 561 self.received_at 562 } 563 } 564 565 impl fmt::Debug for MycSignerRequestRecord { 566 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 567 formatter 568 .debug_struct("MycSignerRequestRecord") 569 .field("method", &self.method) 570 .field("replay_count", &self.replay_count) 571 .field("conflict_count", &self.conflict_count) 572 .field("identity", &"[redacted]") 573 .finish() 574 } 575 } 576 577 /// Idempotent result of durable request admission. 578 #[derive(Clone, PartialEq, Eq)] 579 pub enum MycSignerRequestAdmission { 580 Admitted(MycSignerRequestRecord), 581 ExactReplay(MycSignerRequestRecord), 582 ConflictingReuse(MycSignerRequestRecord), 583 } 584 585 impl MycSignerRequestAdmission { 586 #[must_use] 587 /// Returns the durable record associated with the admission outcome. 588 pub const fn record(&self) -> &MycSignerRequestRecord { 589 match self { 590 Self::Admitted(record) | Self::ExactReplay(record) | Self::ConflictingReuse(record) => { 591 record 592 } 593 } 594 } 595 } 596 597 impl fmt::Debug for MycSignerRequestAdmission { 598 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 599 formatter.write_str(match self { 600 Self::Admitted(_) => "MycSignerRequestAdmission::Admitted([redacted])", 601 Self::ExactReplay(_) => "MycSignerRequestAdmission::ExactReplay([redacted])", 602 Self::ConflictingReuse(_) => "MycSignerRequestAdmission::ConflictingReuse([redacted])", 603 }) 604 } 605 } 606 607 /// Stable source-free input failure for request construction. 608 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 609 pub enum MycSignerRequestErrorKind { 610 InvalidRequestId, 611 InvalidClientIdentity, 612 InvalidCanonicalRequest, 613 InvalidReceivedAt, 614 } 615 616 impl MycSignerRequestErrorKind { 617 #[must_use] 618 /// Returns the stable machine-readable classification. 619 pub const fn code(self) -> &'static str { 620 match self { 621 Self::InvalidRequestId => "signer_request_id_invalid", 622 Self::InvalidClientIdentity => "signer_request_client_identity_invalid", 623 Self::InvalidCanonicalRequest => "signer_request_payload_invalid", 624 Self::InvalidReceivedAt => "signer_request_time_invalid", 625 } 626 } 627 } 628 629 #[derive(Clone, Copy, PartialEq, Eq)] 630 pub struct MycSignerRequestError { 631 kind: MycSignerRequestErrorKind, 632 } 633 634 impl MycSignerRequestError { 635 const fn new(kind: MycSignerRequestErrorKind) -> Self { 636 Self { kind } 637 } 638 639 #[must_use] 640 /// Returns the stable failure class. 641 pub const fn kind(self) -> MycSignerRequestErrorKind { 642 self.kind 643 } 644 645 #[must_use] 646 /// Returns the stable machine-readable failure code. 647 pub const fn code(self) -> &'static str { 648 self.kind.code() 649 } 650 } 651 652 impl fmt::Display for MycSignerRequestError { 653 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 654 formatter.write_str(match self.kind { 655 MycSignerRequestErrorKind::InvalidRequestId => "NIP-46 request ID is invalid", 656 MycSignerRequestErrorKind::InvalidClientIdentity => "NIP-46 client identity is invalid", 657 MycSignerRequestErrorKind::InvalidCanonicalRequest => { 658 "canonical NIP-46 request is invalid" 659 } 660 MycSignerRequestErrorKind::InvalidReceivedAt => "NIP-46 request time is invalid", 661 }) 662 } 663 } 664 665 impl fmt::Debug for MycSignerRequestError { 666 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 667 formatter 668 .debug_struct("MycSignerRequestError") 669 .field("kind", &self.kind) 670 .finish() 671 } 672 } 673 674 impl Error for MycSignerRequestError {} 675 676 impl MycStateRepository<'_> { 677 /// Atomically admits a new request, replays an exact request, or records conflict. 678 pub async fn admit_signer_request( 679 &self, 680 request: &MycSignerRequest, 681 ) -> Result<MycSignerRequestAdmission, MycStateRepositoryError> { 682 let request = request.owned(); 683 let expected = PersistedMetadata::from(self.expected()); 684 self.host() 685 .transaction(move |transaction| { 686 Box::pin(async move { 687 require_expected_metadata(transaction, &expected) 688 .await 689 .map_err(|error| match error { 690 RepositoryOperationError::Binding => RequestOperationError::Binding, 691 RepositoryOperationError::Storage => RequestOperationError::Storage, 692 })?; 693 admit_request(transaction, &request).await 694 }) 695 }) 696 .await 697 .map_err(map_transaction_error) 698 } 699 } 700 701 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 702 enum RequestOperationError { 703 Binding, 704 Storage, 705 } 706 707 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 708 enum DedupKind { 709 Request, 710 Event, 711 } 712 713 impl DedupKind { 714 const fn as_str(self) -> &'static str { 715 match self { 716 Self::Request => "request", 717 Self::Event => "event", 718 } 719 } 720 } 721 722 async fn admit_request( 723 transaction: &mut ServiceSqliteTransaction<'_>, 724 request: &MycSignerRequest, 725 ) -> Result<MycSignerRequestAdmission, RequestOperationError> { 726 let request_match = 727 read_by_dedup(transaction, DedupKind::Request, &request.request_identity).await?; 728 let event_match = 729 read_by_dedup(transaction, DedupKind::Event, request.event_id.as_bytes()).await?; 730 731 match (request_match, event_match) { 732 (None, None) => insert_new_request(transaction, request).await, 733 (Some(existing), event) if exact_request(&existing, request) => { 734 if let Some(event) = event { 735 if event.operation_id != existing.operation_id { 736 record_conflict( 737 transaction, 738 DedupKind::Request, 739 &request.request_identity, 740 request.received_at, 741 ) 742 .await?; 743 return read_outcome( 744 transaction, 745 DedupKind::Request, 746 &request.request_identity, 747 MycSignerRequestAdmission::ConflictingReuse, 748 ) 749 .await; 750 } 751 record_replay( 752 transaction, 753 DedupKind::Event, 754 request.event_id.as_bytes(), 755 request.received_at, 756 ) 757 .await?; 758 } else { 759 insert_dedup( 760 transaction, 761 DedupKind::Event, 762 request.event_id.as_bytes(), 763 request.request_digest.as_bytes(), 764 existing.operation_id.as_bytes(), 765 0, 766 request.received_at, 767 ) 768 .await?; 769 } 770 record_replay( 771 transaction, 772 DedupKind::Request, 773 &request.request_identity, 774 request.received_at, 775 ) 776 .await?; 777 read_outcome( 778 transaction, 779 DedupKind::Request, 780 &request.request_identity, 781 MycSignerRequestAdmission::ExactReplay, 782 ) 783 .await 784 } 785 (Some(_), _) => { 786 record_conflict( 787 transaction, 788 DedupKind::Request, 789 &request.request_identity, 790 request.received_at, 791 ) 792 .await?; 793 read_outcome( 794 transaction, 795 DedupKind::Request, 796 &request.request_identity, 797 MycSignerRequestAdmission::ConflictingReuse, 798 ) 799 .await 800 } 801 (None, Some(_)) => { 802 record_conflict( 803 transaction, 804 DedupKind::Event, 805 request.event_id.as_bytes(), 806 request.received_at, 807 ) 808 .await?; 809 let record = read_by_dedup(transaction, DedupKind::Event, request.event_id.as_bytes()) 810 .await? 811 .ok_or(RequestOperationError::Binding)?; 812 Ok(MycSignerRequestAdmission::ConflictingReuse(record)) 813 } 814 } 815 } 816 817 async fn insert_new_request( 818 transaction: &mut ServiceSqliteTransaction<'_>, 819 request: &MycSignerRequest, 820 ) -> Result<MycSignerRequestAdmission, RequestOperationError> { 821 let operation_id = MycSignerOperationId(derive_operation_id( 822 &request.request_identity, 823 &request.operation_nonce, 824 )); 825 let correlation_id = MycSignerCorrelationId(derive_digest( 826 CORRELATION_ID_DOMAIN, 827 operation_id.as_bytes(), 828 )); 829 let result = sqlx::query(INSERT_REQUEST_SQL) 830 .bind(operation_id.as_bytes().as_slice()) 831 .bind(correlation_id.as_bytes().as_slice()) 832 .bind(request.operation_nonce.0.as_slice()) 833 .bind(request.request_identity.as_slice()) 834 .bind(request.client_public_key.as_hex()) 835 .bind(request.request_id.as_str()) 836 .bind(request.event_id.as_bytes().as_slice()) 837 .bind(request.method.as_str()) 838 .bind(request.request_digest.as_bytes().as_slice()) 839 .bind(request.received_at.sqlite_value()) 840 .execute(&mut *transaction) 841 .await 842 .map_err(|_| RequestOperationError::Storage)?; 843 if result.rows_affected() != 1 { 844 return Err(RequestOperationError::Storage); 845 } 846 insert_dedup( 847 transaction, 848 DedupKind::Request, 849 &request.request_identity, 850 request.request_digest.as_bytes(), 851 operation_id.as_bytes(), 852 0, 853 request.received_at, 854 ) 855 .await?; 856 insert_dedup( 857 transaction, 858 DedupKind::Event, 859 request.event_id.as_bytes(), 860 request.request_digest.as_bytes(), 861 operation_id.as_bytes(), 862 0, 863 request.received_at, 864 ) 865 .await?; 866 read_outcome( 867 transaction, 868 DedupKind::Request, 869 &request.request_identity, 870 MycSignerRequestAdmission::Admitted, 871 ) 872 .await 873 } 874 875 async fn insert_dedup( 876 transaction: &mut ServiceSqliteTransaction<'_>, 877 kind: DedupKind, 878 identity: &[u8; 32], 879 request_digest: &[u8; 32], 880 operation_id: &[u8; 32], 881 replay_count: i64, 882 received_at: MycRequestReceivedAtUnixMs, 883 ) -> Result<(), RequestOperationError> { 884 let result = sqlx::query(INSERT_DEDUP_SQL) 885 .bind(kind.as_str()) 886 .bind(identity.as_slice()) 887 .bind(request_digest.as_slice()) 888 .bind(operation_id.as_slice()) 889 .bind(replay_count) 890 .bind(received_at.sqlite_value()) 891 .bind(received_at.sqlite_value()) 892 .execute(&mut *transaction) 893 .await 894 .map_err(|_| RequestOperationError::Storage)?; 895 (result.rows_affected() == 1) 896 .then_some(()) 897 .ok_or(RequestOperationError::Storage) 898 } 899 900 async fn record_replay( 901 transaction: &mut ServiceSqliteTransaction<'_>, 902 kind: DedupKind, 903 identity: &[u8; 32], 904 received_at: MycRequestReceivedAtUnixMs, 905 ) -> Result<(), RequestOperationError> { 906 update_counter(transaction, RECORD_REPLAY_SQL, kind, identity, received_at).await 907 } 908 909 async fn record_conflict( 910 transaction: &mut ServiceSqliteTransaction<'_>, 911 kind: DedupKind, 912 identity: &[u8; 32], 913 received_at: MycRequestReceivedAtUnixMs, 914 ) -> Result<(), RequestOperationError> { 915 update_counter( 916 transaction, 917 RECORD_CONFLICT_SQL, 918 kind, 919 identity, 920 received_at, 921 ) 922 .await 923 } 924 925 async fn update_counter( 926 transaction: &mut ServiceSqliteTransaction<'_>, 927 sql: &'static str, 928 kind: DedupKind, 929 identity: &[u8; 32], 930 received_at: MycRequestReceivedAtUnixMs, 931 ) -> Result<(), RequestOperationError> { 932 let result = sqlx::query(sql) 933 .bind(received_at.sqlite_value()) 934 .bind(kind.as_str()) 935 .bind(identity.as_slice()) 936 .execute(&mut *transaction) 937 .await 938 .map_err(|_| RequestOperationError::Storage)?; 939 (result.rows_affected() == 1) 940 .then_some(()) 941 .ok_or(RequestOperationError::Storage) 942 } 943 944 async fn read_outcome( 945 transaction: &mut ServiceSqliteTransaction<'_>, 946 kind: DedupKind, 947 identity: &[u8; 32], 948 outcome: fn(MycSignerRequestRecord) -> MycSignerRequestAdmission, 949 ) -> Result<MycSignerRequestAdmission, RequestOperationError> { 950 read_by_dedup(transaction, kind, identity) 951 .await? 952 .map(outcome) 953 .ok_or(RequestOperationError::Binding) 954 } 955 956 async fn read_by_dedup( 957 transaction: &mut ServiceSqliteTransaction<'_>, 958 kind: DedupKind, 959 identity: &[u8; 32], 960 ) -> Result<Option<MycSignerRequestRecord>, RequestOperationError> { 961 let rows = sqlx::query(READ_BY_DEDUP_SQL) 962 .bind(kind.as_str()) 963 .bind(identity.as_slice()) 964 .fetch_all(&mut *transaction) 965 .await 966 .map_err(|_| RequestOperationError::Storage)?; 967 if rows.len() > 1 { 968 return Err(RequestOperationError::Binding); 969 } 970 rows.first() 971 .map(|row| parse_record(row, kind, identity)) 972 .transpose() 973 } 974 975 fn parse_record( 976 row: &sqlx::sqlite::SqliteRow, 977 selected_kind: DedupKind, 978 selected_identity: &[u8; 32], 979 ) -> Result<MycSignerRequestRecord, RequestOperationError> { 980 let operation_id = MycSignerOperationId(exact_digest(row, "operation_id")?); 981 let correlation_id = MycSignerCorrelationId(exact_digest(row, "correlation_id")?); 982 let operation_nonce = exact_digest(row, "operation_nonce")?; 983 let request_identity = exact_digest(row, "request_identity_sha256")?; 984 let selected_request_digest = exact_digest(row, "selected_request_sha256")?; 985 let logical_request_digest = exact_digest(row, "logical_request_sha256")?; 986 let logical_operation_id = exact_digest(row, "logical_operation_id")?; 987 let request_digest = exact_digest(row, "request_sha256")?; 988 let client_public_key = MycNip46ClientPublicKey::new(bounded_text(row, "client_public_key")?) 989 .map_err(|_| RequestOperationError::Binding)?; 990 let request_id = MycNip46RequestId::new(bounded_text(row, "request_id")?) 991 .map_err(|_| RequestOperationError::Binding)?; 992 if (selected_kind == DedupKind::Request && selected_identity != &request_identity) 993 || selected_request_digest != request_digest 994 || logical_request_digest != request_digest 995 || logical_operation_id != *operation_id.as_bytes() 996 || derive_request_identity(&client_public_key, &request_id) != request_identity 997 || derive_operation_id(&request_identity, &MycSignerOperationNonce(operation_nonce)) 998 != *operation_id.as_bytes() 999 || derive_digest(CORRELATION_ID_DOMAIN, operation_id.as_bytes()) 1000 != *correlation_id.as_bytes() 1001 { 1002 return Err(RequestOperationError::Binding); 1003 } 1004 let method = MycSignerRequestMethod::parse(bounded_text(row, "method")?) 1005 .ok_or(RequestOperationError::Binding)?; 1006 let received_at = bounded_time(row, "received_at_unix_ms")?; 1007 let last_seen_at = bounded_time(row, "last_seen_at_unix_ms")?; 1008 if last_seen_at < received_at { 1009 return Err(RequestOperationError::Binding); 1010 } 1011 Ok(MycSignerRequestRecord { 1012 operation_id, 1013 correlation_id, 1014 client_public_key, 1015 request_id, 1016 first_event_id: MycNip46EventId(exact_digest(row, "first_event_id")?), 1017 method, 1018 request_digest: MycSignerRequestDigest(request_digest), 1019 received_at, 1020 replay_count: bounded_count(row, "replay_count")?, 1021 conflict_count: bounded_count(row, "conflict_count")?, 1022 last_seen_at, 1023 }) 1024 } 1025 1026 fn exact_request(existing: &MycSignerRequestRecord, request: &MycSignerRequest) -> bool { 1027 existing.client_public_key == request.client_public_key 1028 && existing.request_id == request.request_id 1029 && existing.method == request.method 1030 && existing.request_digest == request.request_digest 1031 } 1032 1033 fn exact_digest( 1034 row: &sqlx::sqlite::SqliteRow, 1035 column: &str, 1036 ) -> Result<[u8; 32], RequestOperationError> { 1037 row.try_get::<Option<Vec<u8>>, _>(column) 1038 .map_err(|_| RequestOperationError::Binding)? 1039 .ok_or(RequestOperationError::Binding)? 1040 .try_into() 1041 .map_err(|_| RequestOperationError::Binding) 1042 } 1043 1044 fn bounded_text<'row>( 1045 row: &'row sqlx::sqlite::SqliteRow, 1046 column: &str, 1047 ) -> Result<&'row str, RequestOperationError> { 1048 row.try_get::<Option<&str>, _>(column) 1049 .map_err(|_| RequestOperationError::Binding)? 1050 .ok_or(RequestOperationError::Binding) 1051 } 1052 1053 fn bounded_time( 1054 row: &sqlx::sqlite::SqliteRow, 1055 column: &str, 1056 ) -> Result<MycRequestReceivedAtUnixMs, RequestOperationError> { 1057 let value = row 1058 .try_get::<i64, _>(column) 1059 .map_err(|_| RequestOperationError::Binding)?; 1060 let value = u64::try_from(value).map_err(|_| RequestOperationError::Binding)?; 1061 MycRequestReceivedAtUnixMs::new(value).map_err(|_| RequestOperationError::Binding) 1062 } 1063 1064 fn bounded_count( 1065 row: &sqlx::sqlite::SqliteRow, 1066 column: &str, 1067 ) -> Result<u64, RequestOperationError> { 1068 let value = row 1069 .try_get::<i64, _>(column) 1070 .map_err(|_| RequestOperationError::Binding)?; 1071 u64::try_from(value).map_err(|_| RequestOperationError::Binding) 1072 } 1073 1074 pub(crate) fn derive_request_identity( 1075 client_public_key: &MycNip46ClientPublicKey, 1076 request_id: &MycNip46RequestId, 1077 ) -> [u8; 32] { 1078 let mut hasher = Sha256::new(); 1079 hasher.update(REQUEST_IDENTITY_DOMAIN); 1080 update_length_prefixed(&mut hasher, client_public_key.as_hex().as_bytes()); 1081 update_length_prefixed(&mut hasher, request_id.as_str().as_bytes()); 1082 hasher.finalize().into() 1083 } 1084 1085 fn derive_digest(domain: &[u8], value: &[u8; 32]) -> [u8; 32] { 1086 let mut hasher = Sha256::new(); 1087 hasher.update(domain); 1088 hasher.update(value); 1089 hasher.finalize().into() 1090 } 1091 1092 fn derive_operation_id(request_identity: &[u8; 32], nonce: &MycSignerOperationNonce) -> [u8; 32] { 1093 let mut hasher = Sha256::new(); 1094 hasher.update(OPERATION_ID_DOMAIN); 1095 hasher.update(request_identity); 1096 hasher.update(nonce.0); 1097 hasher.finalize().into() 1098 } 1099 1100 fn update_length_prefixed(hasher: &mut Sha256, value: &[u8]) { 1101 hasher.update( 1102 u64::try_from(value.len()) 1103 .expect("bounded identity length fits u64") 1104 .to_be_bytes(), 1105 ); 1106 hasher.update(value); 1107 } 1108 1109 fn map_transaction_error( 1110 error: ServiceSqliteTransactionError<RequestOperationError>, 1111 ) -> MycStateRepositoryError { 1112 if error.kind() == ServiceSqliteTransactionErrorKind::CommitOutcomeUnknown { 1113 return MycStateRepositoryError::new(MycStateRepositoryErrorKind::CommitOutcomeUnknown); 1114 } 1115 let kind = match error.operation_error() { 1116 Some(RequestOperationError::Binding) => MycStateRepositoryErrorKind::Binding, 1117 Some(RequestOperationError::Storage) | None => MycStateRepositoryErrorKind::Transaction, 1118 }; 1119 MycStateRepositoryError::new(kind) 1120 }