this repo has no description
1use k256::ecdsa::SigningKey; 2use serde_json::json; 3use std::collections::HashMap; 4use tranquil_pds::plc::{ 5 PlcError, PlcOperation, PlcService, PlcValidationContext, cid_for_cbor, sign_operation, 6 signing_key_to_did_key, validate_plc_operation, validate_plc_operation_for_submission, 7 verify_operation_signature, 8}; 9 10fn create_valid_operation() -> serde_json::Value { 11 let key = SigningKey::random(&mut rand::thread_rng()); 12 let did_key = signing_key_to_did_key(&key); 13 let op = json!({ 14 "type": "plc_operation", 15 "rotationKeys": [did_key.clone()], 16 "verificationMethods": { "atproto": did_key.clone() }, 17 "alsoKnownAs": ["at://test.handle"], 18 "services": { 19 "atproto_pds": { 20 "type": "AtprotoPersonalDataServer", 21 "endpoint": "https://pds.example.com" 22 } 23 }, 24 "prev": null 25 }); 26 sign_operation(&op, &key).unwrap() 27} 28 29#[test] 30fn test_plc_operation_basic_validation() { 31 let op = create_valid_operation(); 32 assert!(validate_plc_operation(&op).is_ok()); 33 34 let missing_type = json!({ "rotationKeys": [], "verificationMethods": {}, "alsoKnownAs": [], "services": {}, "sig": "test" }); 35 assert!( 36 matches!(validate_plc_operation(&missing_type), Err(PlcError::InvalidResponse(msg)) if msg.contains("Missing type")) 37 ); 38 39 let invalid_type = json!({ "type": "invalid_type", "sig": "test" }); 40 assert!( 41 matches!(validate_plc_operation(&invalid_type), Err(PlcError::InvalidResponse(msg)) if msg.contains("Invalid type")) 42 ); 43 44 let missing_sig = json!({ "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, "alsoKnownAs": [], "services": {} }); 45 assert!( 46 matches!(validate_plc_operation(&missing_sig), Err(PlcError::InvalidResponse(msg)) if msg.contains("Missing sig")) 47 ); 48 49 let missing_rotation = json!({ "type": "plc_operation", "verificationMethods": {}, "alsoKnownAs": [], "services": {}, "sig": "test" }); 50 assert!( 51 matches!(validate_plc_operation(&missing_rotation), Err(PlcError::InvalidResponse(msg)) if msg.contains("rotationKeys")) 52 ); 53 54 let missing_verification = json!({ "type": "plc_operation", "rotationKeys": [], "alsoKnownAs": [], "services": {}, "sig": "test" }); 55 assert!( 56 matches!(validate_plc_operation(&missing_verification), Err(PlcError::InvalidResponse(msg)) if msg.contains("verificationMethods")) 57 ); 58 59 let missing_aka = json!({ "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, "services": {}, "sig": "test" }); 60 assert!( 61 matches!(validate_plc_operation(&missing_aka), Err(PlcError::InvalidResponse(msg)) if msg.contains("alsoKnownAs")) 62 ); 63 64 let missing_services = json!({ "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, "alsoKnownAs": [], "sig": "test" }); 65 assert!( 66 matches!(validate_plc_operation(&missing_services), Err(PlcError::InvalidResponse(msg)) if msg.contains("services")) 67 ); 68 69 assert!(matches!( 70 validate_plc_operation(&json!("not an object")), 71 Err(PlcError::InvalidResponse(_)) 72 )); 73} 74 75#[test] 76fn test_plc_submission_validation() { 77 let key = SigningKey::random(&mut rand::thread_rng()); 78 let did_key = signing_key_to_did_key(&key); 79 let server_key = "did:key:zServer123"; 80 81 let base_op = |rotation_key: &str, 82 signing_key: &str, 83 handle: &str, 84 service_type: &str, 85 endpoint: &str| { 86 json!({ 87 "type": "plc_operation", 88 "rotationKeys": [rotation_key], 89 "verificationMethods": {"atproto": signing_key}, 90 "alsoKnownAs": [format!("at://{}", handle)], 91 "services": { "atproto_pds": { "type": service_type, "endpoint": endpoint } }, 92 "sig": "test" 93 }) 94 }; 95 96 let ctx = PlcValidationContext { 97 server_rotation_key: server_key.to_string(), 98 expected_signing_key: did_key.clone(), 99 expected_handle: "test.handle".to_string(), 100 expected_pds_endpoint: "https://pds.example.com".to_string(), 101 }; 102 103 let op = base_op( 104 &did_key, 105 &did_key, 106 "test.handle", 107 "AtprotoPersonalDataServer", 108 "https://pds.example.com", 109 ); 110 assert!( 111 matches!(validate_plc_operation_for_submission(&op, &ctx), Err(PlcError::InvalidResponse(msg)) if msg.contains("rotation key")) 112 ); 113 114 let ctx_with_user_key = PlcValidationContext { 115 server_rotation_key: did_key.clone(), 116 expected_signing_key: did_key.clone(), 117 expected_handle: "test.handle".to_string(), 118 expected_pds_endpoint: "https://pds.example.com".to_string(), 119 }; 120 121 let wrong_signing = base_op( 122 &did_key, 123 "did:key:zWrongKey", 124 "test.handle", 125 "AtprotoPersonalDataServer", 126 "https://pds.example.com", 127 ); 128 assert!( 129 matches!(validate_plc_operation_for_submission(&wrong_signing, &ctx_with_user_key), Err(PlcError::InvalidResponse(msg)) if msg.contains("signing key")) 130 ); 131 132 let wrong_handle = base_op( 133 &did_key, 134 &did_key, 135 "wrong.handle", 136 "AtprotoPersonalDataServer", 137 "https://pds.example.com", 138 ); 139 assert!( 140 matches!(validate_plc_operation_for_submission(&wrong_handle, &ctx_with_user_key), Err(PlcError::InvalidResponse(msg)) if msg.contains("handle")) 141 ); 142 143 let wrong_service_type = base_op( 144 &did_key, 145 &did_key, 146 "test.handle", 147 "WrongServiceType", 148 "https://pds.example.com", 149 ); 150 assert!( 151 matches!(validate_plc_operation_for_submission(&wrong_service_type, &ctx_with_user_key), Err(PlcError::InvalidResponse(msg)) if msg.contains("type")) 152 ); 153 154 let wrong_endpoint = base_op( 155 &did_key, 156 &did_key, 157 "test.handle", 158 "AtprotoPersonalDataServer", 159 "https://wrong.endpoint.com", 160 ); 161 assert!( 162 matches!(validate_plc_operation_for_submission(&wrong_endpoint, &ctx_with_user_key), Err(PlcError::InvalidResponse(msg)) if msg.contains("endpoint")) 163 ); 164} 165 166#[test] 167fn test_signature_verification() { 168 let key = SigningKey::random(&mut rand::thread_rng()); 169 let did_key = signing_key_to_did_key(&key); 170 let op = json!({ 171 "type": "plc_operation", "rotationKeys": [did_key.clone()], 172 "verificationMethods": {}, "alsoKnownAs": [], "services": {}, "prev": null 173 }); 174 let signed = sign_operation(&op, &key).unwrap(); 175 let result = verify_operation_signature(&signed, &[did_key.clone()]); 176 assert!(result.is_ok() && result.unwrap()); 177 178 let other_key = SigningKey::random(&mut rand::thread_rng()); 179 let other_did = signing_key_to_did_key(&other_key); 180 let result = verify_operation_signature(&signed, &[other_did]); 181 assert!(result.is_ok() && !result.unwrap()); 182 183 let result = verify_operation_signature(&signed, &["not-a-did-key".to_string()]); 184 assert!(result.is_ok() && !result.unwrap()); 185 186 let missing_sig = json!({ "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, "alsoKnownAs": [], "services": {} }); 187 assert!( 188 matches!(verify_operation_signature(&missing_sig, &[]), Err(PlcError::InvalidResponse(msg)) if msg.contains("sig")) 189 ); 190 191 let invalid_base64 = json!({ 192 "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, 193 "alsoKnownAs": [], "services": {}, "sig": "not-valid-base64!!!" 194 }); 195 assert!(matches!( 196 verify_operation_signature(&invalid_base64, &[]), 197 Err(PlcError::InvalidResponse(_)) 198 )); 199} 200 201#[test] 202fn test_cid_and_key_utilities() { 203 let value = json!({ "alpha": 1, "beta": 2 }); 204 let cid1 = cid_for_cbor(&value).unwrap(); 205 let cid2 = cid_for_cbor(&value).unwrap(); 206 assert_eq!(cid1, cid2, "CID should be deterministic"); 207 assert!( 208 cid1.starts_with("bafyrei"), 209 "CID should be dag-cbor + sha256" 210 ); 211 212 let value2 = json!({ "alpha": 999 }); 213 let cid3 = cid_for_cbor(&value2).unwrap(); 214 assert_ne!(cid1, cid3, "Different data should produce different CIDs"); 215 216 let key = SigningKey::random(&mut rand::thread_rng()); 217 let did = signing_key_to_did_key(&key); 218 assert!(did.starts_with("did:key:z") && did.len() > 50); 219 assert_eq!( 220 did, 221 signing_key_to_did_key(&key), 222 "Same key should produce same did" 223 ); 224 225 let key2 = SigningKey::random(&mut rand::thread_rng()); 226 assert_ne!( 227 did, 228 signing_key_to_did_key(&key2), 229 "Different keys should produce different dids" 230 ); 231} 232 233#[test] 234fn test_tombstone_operations() { 235 let tombstone = 236 json!({ "type": "plc_tombstone", "prev": "bafyreig6xxxxxyyyyyzzzzzz", "sig": "test" }); 237 assert!(validate_plc_operation(&tombstone).is_ok()); 238 239 let key = SigningKey::random(&mut rand::thread_rng()); 240 let did_key = signing_key_to_did_key(&key); 241 let ctx = PlcValidationContext { 242 server_rotation_key: did_key.clone(), 243 expected_signing_key: did_key, 244 expected_handle: "test.handle".to_string(), 245 expected_pds_endpoint: "https://pds.example.com".to_string(), 246 }; 247 assert!(validate_plc_operation_for_submission(&tombstone, &ctx).is_ok()); 248} 249 250#[test] 251fn test_sign_operation_and_struct() { 252 let key = SigningKey::random(&mut rand::thread_rng()); 253 let op = json!({ 254 "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, 255 "alsoKnownAs": [], "services": {}, "prev": null, "sig": "old_signature" 256 }); 257 let signed = sign_operation(&op, &key).unwrap(); 258 assert_ne!( 259 signed.get("sig").and_then(|v| v.as_str()).unwrap(), 260 "old_signature" 261 ); 262 263 let mut services = HashMap::new(); 264 services.insert( 265 "atproto_pds".to_string(), 266 PlcService { 267 service_type: "AtprotoPersonalDataServer".to_string(), 268 endpoint: "https://pds.example.com".to_string(), 269 }, 270 ); 271 let mut verification_methods = HashMap::new(); 272 verification_methods.insert("atproto".to_string(), "did:key:zTest123".to_string()); 273 let op = PlcOperation { 274 op_type: "plc_operation".to_string(), 275 rotation_keys: vec!["did:key:zTest123".to_string()], 276 verification_methods, 277 also_known_as: vec!["at://test.handle".to_string()], 278 services, 279 prev: None, 280 sig: Some("test".to_string()), 281 }; 282 let json_value = serde_json::to_value(&op).unwrap(); 283 assert_eq!(json_value["type"], "plc_operation"); 284 assert!(json_value["rotationKeys"].is_array()); 285}