myc

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

services_hardening_nip46_work.rs (4469B)


      1 #![forbid(unsafe_code)]
      2 
      3 use serde_json::{Value, json};
      4 
      5 const CONTRACT: &str = include_str!("../contracts/services_hardening/nip46_work.v1.json");
      6 const SOURCE: &str = include_str!("../src/nip46_work.rs");
      7 
      8 #[test]
      9 fn machine_contract_freezes_decryption_method_and_work_authority() {
     10     let contract: Value = serde_json::from_str(CONTRACT).expect("work contract");
     11     assert_eq!(contract["schema"], "radroots.myc.nip46-work.v1");
     12     assert_eq!(contract["contract_version"], 1);
     13     assert_eq!(
     14         contract["decryption"]["operation_id_domain"],
     15         "radroots.myc.nip46.decrypt.operation.v1\\0"
     16     );
     17     assert_eq!(
     18         contract["decryption"]["correlation_id_domain"],
     19         "radroots.myc.nip46.decrypt.correlation.v1\\0"
     20     );
     21     assert_eq!(
     22         contract["decryption"]["provider_result_binding"]["domain"],
     23         "radroots.myc.provider.operation_binding.v1\\0"
     24     );
     25     assert_eq!(
     26         contract["decryption"]["provider_result_binding"]["field_order"],
     27         json!([
     28             "role",
     29             "instance",
     30             "provider",
     31             "operation_id",
     32             "correlation_id",
     33             "absolute_deadline_unix_ms",
     34             "expected_identity",
     35             "capability",
     36             "peer_or_empty",
     37             "nip44_version_or_zero",
     38             "input_or_empty"
     39         ])
     40     );
     41     assert_eq!(
     42         contract["method_inventory"],
     43         json!([
     44             "connect",
     45             "get_public_key",
     46             "get_session_capability",
     47             "sign_event",
     48             "nip04_encrypt",
     49             "nip04_decrypt",
     50             "nip44_encrypt",
     51             "nip44_decrypt",
     52             "ping",
     53             "switch_relays",
     54             "logout"
     55         ])
     56     );
     57     assert_eq!(contract["custom_methods"], "unsupported_v1");
     58     assert_eq!(contract["work_classes"]["provider"]["role"], "user");
     59     assert_eq!(
     60         contract["work_classes"]["provider"]["sign_event_input"],
     61         json!([
     62             "canonical_unsigned_json",
     63             "valid_optional_event_id",
     64             "exact_configured_user_author"
     65         ])
     66     );
     67     assert_eq!(
     68         contract["authorization"]["required_permission_api"],
     69         "radroots_nostr_connect::server::required_permission"
     70     );
     71     assert_eq!(contract["sqlite"]["transaction_parameter"], "forbidden");
     72     assert_eq!(
     73         contract["nonclaims"],
     74         json!([
     75             "provider_execution",
     76             "response_commit",
     77             "outbox_creation",
     78             "relay_publication"
     79         ])
     80     );
     81 }
     82 
     83 #[test]
     84 fn implementation_uses_the_shared_permission_truth_and_has_no_external_authority() {
     85     for required in [
     86         "server::required_permission",
     87         "required_permission(&request)",
     88         "record.matches_request(&signer_request)",
     89         "response.matches_operation(&self.operation)",
     90         "MycProviderRole::Transport",
     91         "MycProviderRole::User",
     92         "Request::Custom { .. }",
     93         "MycNip46WorkErrorKind::UnsupportedMethod",
     94     ] {
     95         assert!(
     96             SOURCE.contains(required),
     97             "missing Step 145 binding `{required}`"
     98         );
     99     }
    100 
    101     for forbidden in [
    102         "sqlx::",
    103         "ServiceSqlite",
    104         "StateRepository",
    105         "StateHost",
    106         "ServiceSqliteTransaction",
    107         "tokio::spawn",
    108         "std::time::SystemTime",
    109         "Timestamp::now",
    110         "rand::",
    111         "getrandom",
    112         "RelayPool",
    113         ".execute(",
    114         ".publish(",
    115     ] {
    116         assert!(
    117             !SOURCE.contains(forbidden),
    118             "Step 145 work gained forbidden authority `{forbidden}`"
    119         );
    120     }
    121 }
    122 
    123 #[test]
    124 fn public_work_values_are_sealed_and_redacted() {
    125     for required in [
    126         "pub struct MycNip46DecryptWork {",
    127         "pub struct MycDecryptedNip46Request {",
    128         "pub struct MycPreparedNip46Request {",
    129         "pub struct MycNip46Work {",
    130         "MycDecryptedNip46Request([redacted])",
    131         ".field(\"request\", &\"[redacted]\")",
    132         "impl Error for MycNip46WorkError {}",
    133     ] {
    134         assert!(
    135             SOURCE.contains(required),
    136             "missing sealed boundary `{required}`"
    137         );
    138     }
    139     for forbidden in [
    140         "pub replay:",
    141         "pub signer_request:",
    142         "pub request:",
    143         "pub payload:",
    144         "fn source(",
    145         "impl Serialize for MycNip46Work",
    146     ] {
    147         assert!(
    148             !SOURCE.contains(forbidden),
    149             "public work leak `{forbidden}`"
    150         );
    151     }
    152 }