myc

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

services_hardening_provider_verification.rs (5909B)


      1 #![forbid(unsafe_code)]
      2 
      3 use serde_json::json;
      4 
      5 const CONTRACT: &str =
      6     include_str!("../contracts/services_hardening/provider_verification.v1.json");
      7 const LIB_SOURCE: &str = include_str!("../src/lib.rs");
      8 const VERIFICATION_SOURCE: &str = include_str!("../src/provider_verification.rs");
      9 const TRANSPORT_SOURCE: &str = include_str!("../src/provider_local_signer.rs");
     10 
     11 #[test]
     12 fn machine_contract_freezes_independent_provider_verification() {
     13     let actual: serde_json::Value = serde_json::from_str(CONTRACT).expect("contract JSON");
     14     assert_eq!(
     15         actual,
     16         json!({
     17             "schema": "radroots.myc.provider-verification",
     18             "schema_version": 1,
     19             "provider_contract_version": 1,
     20             "local_signer_transport_contract_version": 1,
     21             "observation_time": {
     22                 "source": "injected",
     23                 "minimum_unix_ms": 1,
     24                 "maximum_unix_ms": 9223372036854775807_i64
     25             },
     26             "verification_order": [
     27                 "configured_binding", "absolute_deadline", "outer_correlation",
     28                 "response_envelope_binding", "result_shape", "semantic_result"
     29             ],
     30             "response_binding": [
     31                 "contract_version", "provider_instance", "role", "operation_id",
     32                 "correlation_id", "absolute_deadline_unix_ms", "expected_identity",
     33                 "capability"
     34             ],
     35             "describe": {
     36                 "identity": "exact_expected",
     37                 "protocol_version": 1,
     38                 "capabilities": "closed_unique_contains_role_requirements",
     39                 "maximum_request_bytes": "exact_configured_limit"
     40             },
     41             "public_identity": "exact_expected",
     42             "sign_event": [
     43                 "canonical_unsigned_json", "exact_unsigned_fields", "exact_expected_author",
     44                 "computed_event_id", "valid_schnorr_signature", "canonical_signed_json",
     45                 "retain_exact_verified_bytes"
     46             ],
     47             "nip04": {
     48                 "direction": "exact_result_tag",
     49                 "peer": "exact_echo",
     50                 "ciphertext": "canonical_base64_aes_cbc_shape",
     51                 "encrypt_length": "exact_plaintext_padding_length",
     52                 "decrypt_length": "less_than_ciphertext_block_capacity"
     53             },
     54             "nip44": {
     55                 "direction": "exact_result_tag",
     56                 "peer": "exact_echo",
     57                 "version": 2,
     58                 "plaintext_max_bytes": 65_408,
     59                 "ciphertext": "canonical_base64_v2_shape",
     60                 "encrypt_length": "exact_v2_plaintext_padding_length",
     61                 "decrypt_length": "bounded_by_ciphertext_padding_capacity"
     62             },
     63             "semantic_output_max_bytes": 1_048_576,
     64             "verified_result_sealed": true,
     65             "verified_result_serializable": false,
     66             "safe_errors_source_free": true,
     67             "late_result_usable": false,
     68             "ambiguous_result_is_publication": false,
     69             "signing_is_publication": false,
     70             "database_transaction_allowed": false,
     71             "provider_execution_allowed": false
     72         })
     73     );
     74 }
     75 
     76 #[test]
     77 fn implementation_rebinds_every_response_before_semantic_use() {
     78     for required in [
     79         "observed_at.get() > operation.deadline().get()",
     80         "parts.outer_correlation_id.as_ref() != expected_correlation_id",
     81         "response.operation_id != expected_operation_id",
     82         "response.correlation_id != expected_correlation_id",
     83         "response.expected_identity != operation.expected_identity().as_hex()",
     84         "response.capability != WireCapability::from(operation.input().capability())",
     85         "verify_peer(operation, &peer)?",
     86         "event.verify().is_err()",
     87         "canonical_event.as_slice() != payload.as_slice()",
     88         "VerifiedProviderResult::SignedEvent(payload)",
     89         "verify_nip04_ciphertext",
     90         "verify_nip44_ciphertext",
     91     ] {
     92         assert!(
     93             VERIFICATION_SOURCE.contains(required),
     94             "missing verifier {required}"
     95         );
     96     }
     97     assert!(TRANSPORT_SOURCE.contains("pub(crate) struct LocalSignerUntrustedParts"));
     98 }
     99 
    100 #[test]
    101 fn verified_boundary_is_sealed_redacted_and_not_publication() {
    102     for required in [
    103         "pub struct MycVerifiedProviderResponse",
    104         "pub struct MycProviderResponseObservedAtUnixMs",
    105         "pub enum MycProviderVerificationErrorKind",
    106         "pub struct MycProviderVerificationError",
    107         ".field(\"result\", &\"[redacted]\")",
    108         "impl Error for MycProviderVerificationError {}",
    109     ] {
    110         assert!(VERIFICATION_SOURCE.contains(required));
    111     }
    112     for forbidden in [
    113         "pub result:",
    114         "pub payload:",
    115         "pub outer_correlation_id:",
    116         "pub fn into_inner",
    117         "pub fn raw_response",
    118         "impl Serialize for MycVerifiedProviderResponse",
    119         "pub const fn signed_event",
    120         "ServiceSqliteTransaction",
    121         "publish(",
    122         "nostr_sdk",
    123     ] {
    124         assert!(!VERIFICATION_SOURCE.contains(forbidden));
    125     }
    126     assert!(LIB_SOURCE.contains("mod provider_verification;"));
    127     assert!(!LIB_SOURCE.contains("pub mod provider_verification"));
    128 }
    129 
    130 #[test]
    131 fn peer_and_nip44_version_are_mandatory_response_evidence() {
    132     let response = TRANSPORT_SOURCE
    133         .split("pub(crate) enum WireProviderResult")
    134         .nth(1)
    135         .expect("response result enum");
    136     for variant in [
    137         "Nip04Encrypt",
    138         "Nip04Decrypt",
    139         "Nip44Encrypt",
    140         "Nip44Decrypt",
    141     ] {
    142         assert!(
    143             response.contains(&format!("{variant} {{\n        peer: String,")),
    144             "missing peer echo on {variant}"
    145         );
    146     }
    147     assert!(VERIFICATION_SOURCE.contains("version != requested_version.as_u8()"));
    148     assert!(VERIFICATION_SOURCE.contains("decoded.first().copied() != Some(version.as_u8())"));
    149 }