this repo has no description
1use bspds::plc::{ 2 PlcError, PlcOperation, PlcService, PlcValidationContext, cid_for_cbor, sign_operation, 3 signing_key_to_did_key, validate_plc_operation, validate_plc_operation_for_submission, 4 verify_operation_signature, 5}; 6use k256::ecdsa::SigningKey; 7use serde_json::json; 8use std::collections::HashMap; 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!(matches!(validate_plc_operation(&missing_type), Err(PlcError::InvalidResponse(msg)) if msg.contains("Missing type"))); 36 37 let invalid_type = json!({ "type": "invalid_type", "sig": "test" }); 38 assert!(matches!(validate_plc_operation(&invalid_type), Err(PlcError::InvalidResponse(msg)) if msg.contains("Invalid type"))); 39 40 let missing_sig = json!({ "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, "alsoKnownAs": [], "services": {} }); 41 assert!(matches!(validate_plc_operation(&missing_sig), Err(PlcError::InvalidResponse(msg)) if msg.contains("Missing sig"))); 42 43 let missing_rotation = json!({ "type": "plc_operation", "verificationMethods": {}, "alsoKnownAs": [], "services": {}, "sig": "test" }); 44 assert!(matches!(validate_plc_operation(&missing_rotation), Err(PlcError::InvalidResponse(msg)) if msg.contains("rotationKeys"))); 45 46 let missing_verification = json!({ "type": "plc_operation", "rotationKeys": [], "alsoKnownAs": [], "services": {}, "sig": "test" }); 47 assert!(matches!(validate_plc_operation(&missing_verification), Err(PlcError::InvalidResponse(msg)) if msg.contains("verificationMethods"))); 48 49 let missing_aka = json!({ "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, "services": {}, "sig": "test" }); 50 assert!(matches!(validate_plc_operation(&missing_aka), Err(PlcError::InvalidResponse(msg)) if msg.contains("alsoKnownAs"))); 51 52 let missing_services = json!({ "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, "alsoKnownAs": [], "sig": "test" }); 53 assert!(matches!(validate_plc_operation(&missing_services), Err(PlcError::InvalidResponse(msg)) if msg.contains("services"))); 54 55 assert!(matches!(validate_plc_operation(&json!("not an object")), Err(PlcError::InvalidResponse(_)))); 56} 57 58#[test] 59fn test_plc_submission_validation() { 60 let key = SigningKey::random(&mut rand::thread_rng()); 61 let did_key = signing_key_to_did_key(&key); 62 let server_key = "did:key:zServer123"; 63 64 let base_op = |rotation_key: &str, signing_key: &str, handle: &str, service_type: &str, endpoint: &str| json!({ 65 "type": "plc_operation", 66 "rotationKeys": [rotation_key], 67 "verificationMethods": {"atproto": signing_key}, 68 "alsoKnownAs": [format!("at://{}", handle)], 69 "services": { "atproto_pds": { "type": service_type, "endpoint": endpoint } }, 70 "sig": "test" 71 }); 72 73 let ctx = PlcValidationContext { 74 server_rotation_key: server_key.to_string(), 75 expected_signing_key: did_key.clone(), 76 expected_handle: "test.handle".to_string(), 77 expected_pds_endpoint: "https://pds.example.com".to_string(), 78 }; 79 80 let op = base_op(&did_key, &did_key, "test.handle", "AtprotoPersonalDataServer", "https://pds.example.com"); 81 assert!(matches!(validate_plc_operation_for_submission(&op, &ctx), Err(PlcError::InvalidResponse(msg)) if msg.contains("rotation key"))); 82 83 let ctx_with_user_key = PlcValidationContext { 84 server_rotation_key: did_key.clone(), 85 expected_signing_key: did_key.clone(), 86 expected_handle: "test.handle".to_string(), 87 expected_pds_endpoint: "https://pds.example.com".to_string(), 88 }; 89 90 let wrong_signing = base_op(&did_key, "did:key:zWrongKey", "test.handle", "AtprotoPersonalDataServer", "https://pds.example.com"); 91 assert!(matches!(validate_plc_operation_for_submission(&wrong_signing, &ctx_with_user_key), Err(PlcError::InvalidResponse(msg)) if msg.contains("signing key"))); 92 93 let wrong_handle = base_op(&did_key, &did_key, "wrong.handle", "AtprotoPersonalDataServer", "https://pds.example.com"); 94 assert!(matches!(validate_plc_operation_for_submission(&wrong_handle, &ctx_with_user_key), Err(PlcError::InvalidResponse(msg)) if msg.contains("handle"))); 95 96 let wrong_service_type = base_op(&did_key, &did_key, "test.handle", "WrongServiceType", "https://pds.example.com"); 97 assert!(matches!(validate_plc_operation_for_submission(&wrong_service_type, &ctx_with_user_key), Err(PlcError::InvalidResponse(msg)) if msg.contains("type"))); 98 99 let wrong_endpoint = base_op(&did_key, &did_key, "test.handle", "AtprotoPersonalDataServer", "https://wrong.endpoint.com"); 100 assert!(matches!(validate_plc_operation_for_submission(&wrong_endpoint, &ctx_with_user_key), Err(PlcError::InvalidResponse(msg)) if msg.contains("endpoint"))); 101} 102 103#[test] 104fn test_signature_verification() { 105 let key = SigningKey::random(&mut rand::thread_rng()); 106 let did_key = signing_key_to_did_key(&key); 107 let op = json!({ 108 "type": "plc_operation", "rotationKeys": [did_key.clone()], 109 "verificationMethods": {}, "alsoKnownAs": [], "services": {}, "prev": null 110 }); 111 let signed = sign_operation(&op, &key).unwrap(); 112 let result = verify_operation_signature(&signed, &[did_key.clone()]); 113 assert!(result.is_ok() && result.unwrap()); 114 115 let other_key = SigningKey::random(&mut rand::thread_rng()); 116 let other_did = signing_key_to_did_key(&other_key); 117 let result = verify_operation_signature(&signed, &[other_did]); 118 assert!(result.is_ok() && !result.unwrap()); 119 120 let result = verify_operation_signature(&signed, &["not-a-did-key".to_string()]); 121 assert!(result.is_ok() && !result.unwrap()); 122 123 let missing_sig = json!({ "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, "alsoKnownAs": [], "services": {} }); 124 assert!(matches!(verify_operation_signature(&missing_sig, &[]), Err(PlcError::InvalidResponse(msg)) if msg.contains("sig"))); 125 126 let invalid_base64 = json!({ 127 "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, 128 "alsoKnownAs": [], "services": {}, "sig": "not-valid-base64!!!" 129 }); 130 assert!(matches!(verify_operation_signature(&invalid_base64, &[]), Err(PlcError::InvalidResponse(_)))); 131} 132 133#[test] 134fn test_cid_and_key_utilities() { 135 let value = json!({ "alpha": 1, "beta": 2 }); 136 let cid1 = cid_for_cbor(&value).unwrap(); 137 let cid2 = cid_for_cbor(&value).unwrap(); 138 assert_eq!(cid1, cid2, "CID should be deterministic"); 139 assert!(cid1.starts_with("bafyrei"), "CID should be dag-cbor + sha256"); 140 141 let value2 = json!({ "alpha": 999 }); 142 let cid3 = cid_for_cbor(&value2).unwrap(); 143 assert_ne!(cid1, cid3, "Different data should produce different CIDs"); 144 145 let key = SigningKey::random(&mut rand::thread_rng()); 146 let did = signing_key_to_did_key(&key); 147 assert!(did.starts_with("did:key:z") && did.len() > 50); 148 assert_eq!(did, signing_key_to_did_key(&key), "Same key should produce same did"); 149 150 let key2 = SigningKey::random(&mut rand::thread_rng()); 151 assert_ne!(did, signing_key_to_did_key(&key2), "Different keys should produce different dids"); 152} 153 154#[test] 155fn test_tombstone_operations() { 156 let tombstone = json!({ "type": "plc_tombstone", "prev": "bafyreig6xxxxxyyyyyzzzzzz", "sig": "test" }); 157 assert!(validate_plc_operation(&tombstone).is_ok()); 158 159 let key = SigningKey::random(&mut rand::thread_rng()); 160 let did_key = signing_key_to_did_key(&key); 161 let ctx = PlcValidationContext { 162 server_rotation_key: did_key.clone(), 163 expected_signing_key: did_key, 164 expected_handle: "test.handle".to_string(), 165 expected_pds_endpoint: "https://pds.example.com".to_string(), 166 }; 167 assert!(validate_plc_operation_for_submission(&tombstone, &ctx).is_ok()); 168} 169 170#[test] 171fn test_sign_operation_and_struct() { 172 let key = SigningKey::random(&mut rand::thread_rng()); 173 let op = json!({ 174 "type": "plc_operation", "rotationKeys": [], "verificationMethods": {}, 175 "alsoKnownAs": [], "services": {}, "prev": null, "sig": "old_signature" 176 }); 177 let signed = sign_operation(&op, &key).unwrap(); 178 assert_ne!(signed.get("sig").and_then(|v| v.as_str()).unwrap(), "old_signature"); 179 180 let mut services = HashMap::new(); 181 services.insert("atproto_pds".to_string(), PlcService { 182 service_type: "AtprotoPersonalDataServer".to_string(), 183 endpoint: "https://pds.example.com".to_string(), 184 }); 185 let mut verification_methods = HashMap::new(); 186 verification_methods.insert("atproto".to_string(), "did:key:zTest123".to_string()); 187 let op = PlcOperation { 188 op_type: "plc_operation".to_string(), 189 rotation_keys: vec!["did:key:zTest123".to_string()], 190 verification_methods, 191 also_known_as: vec!["at://test.handle".to_string()], 192 services, 193 prev: None, 194 sig: Some("test".to_string()), 195 }; 196 let json_value = serde_json::to_value(&op).unwrap(); 197 assert_eq!(json_value["type"], "plc_operation"); 198 assert!(json_value["rotationKeys"].is_array()); 199}