A PLC Mirror written in Rust
at main 5.3 kB view raw
1use crate::utils::*; 2use chrono::{DateTime, Utc}; 3use ipld_core::cid::Cid; 4use schemars::schema::Schema; 5use schemars::{JsonSchema, SchemaGenerator}; 6use serde::{Deserialize, Serialize}; 7use std::borrow::Cow; 8use std::collections::HashMap; 9use std::fmt::{Debug, Display}; 10 11const DID_DOC_JSONLD_CTX: &[&str] = &[ 12 "https://www.w3.org/ns/did/v1", 13 "https://w3id.org/security/multikey/v1", 14 "https://w3id.org/security/suites/secp256k1-2019/v1", 15]; 16 17#[derive(Debug, JsonSchema, Deserialize, Serialize)] 18#[serde(tag = "type")] 19pub enum PlcOperationType { 20 #[serde(rename = "create")] 21 Create(PlcCreate), 22 #[serde(rename = "plc_tombstone")] 23 Tombstone(PlcTombstone), 24 #[serde(rename = "plc_operation")] 25 Operation(PlcOperation), 26} 27 28#[derive(Debug, JsonSchema, Deserialize, Serialize)] 29#[serde(rename_all = "camelCase")] 30pub struct PlcCreate { 31 pub sig: String, 32 pub prev: Option<JsonCid>, 33 pub signing_key: String, 34 pub recovery_key: String, 35 pub handle: String, 36 pub service: String, 37} 38 39#[derive(Debug, JsonSchema, Deserialize, Serialize)] 40#[serde(rename_all = "camelCase")] 41pub struct PlcTombstone { 42 pub sig: String, 43 pub prev: Option<JsonCid>, 44} 45 46#[derive(Debug, JsonSchema, Deserialize, Serialize)] 47#[serde(rename_all = "camelCase")] 48pub struct PlcOperation { 49 pub sig: String, 50 pub prev: Option<JsonCid>, 51 pub rotation_keys: Vec<String>, 52 pub verification_methods: HashMap<String, String>, 53 pub also_known_as: Vec<String>, 54 pub services: HashMap<String, PlcService>, 55} 56 57#[derive(Debug, JsonSchema, Deserialize, Serialize)] 58pub struct PlcService { 59 #[serde(rename = "type")] 60 pub ty: String, 61 pub endpoint: String, 62} 63 64#[derive(Debug, JsonSchema, Deserialize, Serialize)] 65#[serde(rename_all = "camelCase")] 66pub struct PlcEntry { 67 pub did: String, 68 pub operation: PlcOperationType, 69 pub cid: JsonCid, 70 pub nullified: bool, 71 pub created_at: DateTime<Utc>, 72} 73 74#[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, Deserialize, Serialize)] 75pub struct JsonCid( 76 #[serde(deserialize_with = "cid_de_string", serialize_with = "cid_ser_string")] pub Cid, 77); 78 79// the default cid debugger outputs a byte array, which just sucks to read 80impl Debug for JsonCid { 81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 82 Display::fmt(&self.0, f) 83 } 84} 85 86impl Display for JsonCid { 87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 88 Display::fmt(&self.0, f) 89 } 90} 91 92impl JsonSchema for JsonCid { 93 fn schema_name() -> String { 94 "Cid".to_string() 95 } 96 97 fn schema_id() -> Cow<'static, str> { 98 Cow::Borrowed(concat!(module_path!(), "::Cid")) 99 } 100 101 fn json_schema(generator: &mut SchemaGenerator) -> Schema { 102 String::json_schema(generator) 103 } 104} 105 106#[derive(Debug, JsonSchema, Serialize)] 107#[serde(rename_all = "camelCase")] 108pub struct DidDocument { 109 // I hate json-ld. 110 #[serde(rename = "@context")] 111 context: &'static [&'static str], 112 113 pub id: String, 114 pub also_known_as: Vec<String>, 115 pub verification_method: Vec<DidVerificationMethod>, 116 pub service: Vec<DidService>, 117} 118 119impl DidDocument { 120 pub fn from_create(did: &str, op: PlcCreate) -> Self { 121 DidDocument { 122 context: DID_DOC_JSONLD_CTX, 123 id: did.to_string(), 124 also_known_as: vec![op.handle], 125 verification_method: vec![DidVerificationMethod { 126 id: format!("{did}:#atproto"), 127 ty: "Multikey".to_string(), 128 controller: did.to_string(), 129 public_key_multibase: op.signing_key[8..].to_string(), 130 }], 131 service: vec![DidService { 132 id: "#atproto_pds".to_string(), 133 ty: "AtprotoPersonalDataServer".to_string(), 134 service_endpoint: op.service, 135 }], 136 } 137 } 138 139 pub fn from_plc_op(did: &str, op: PlcOperation) -> Self { 140 let verification_method = op 141 .verification_methods 142 .into_iter() 143 .map(|(service, did_key)| DidVerificationMethod { 144 id: format!("{did}#{service}"), 145 ty: "Multikey".to_string(), 146 controller: did.to_string(), 147 public_key_multibase: did_key[8..].to_string(), 148 }) 149 .collect(); 150 151 let service = op 152 .services 153 .into_iter() 154 .map(|(id, service)| DidService { 155 id: format!("#{id}"), 156 ty: service.ty, 157 service_endpoint: service.endpoint, 158 }) 159 .collect(); 160 161 DidDocument { 162 context: DID_DOC_JSONLD_CTX, 163 id: did.to_string(), 164 also_known_as: op.also_known_as, 165 verification_method, 166 service, 167 } 168 } 169} 170 171#[derive(Debug, JsonSchema, Serialize)] 172#[serde(rename_all = "camelCase")] 173pub struct DidVerificationMethod { 174 pub id: String, 175 #[serde(rename = "type")] 176 pub ty: String, 177 pub controller: String, 178 pub public_key_multibase: String, 179} 180 181#[derive(Debug, JsonSchema, Serialize)] 182#[serde(rename_all = "camelCase")] 183pub struct DidService { 184 pub id: String, 185 #[serde(rename = "type")] 186 pub ty: String, 187 pub service_endpoint: String, 188}