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 }