use crate::utils::*; use chrono::{DateTime, Utc}; use ipld_core::cid::Cid; use schemars::schema::Schema; use schemars::{JsonSchema, SchemaGenerator}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::collections::HashMap; use std::fmt::{Debug, Display}; const DID_DOC_JSONLD_CTX: &[&str] = &[ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1", "https://w3id.org/security/suites/secp256k1-2019/v1", ]; #[derive(Debug, JsonSchema, Deserialize, Serialize)] #[serde(tag = "type")] pub enum PlcOperationType { #[serde(rename = "create")] Create(PlcCreate), #[serde(rename = "plc_tombstone")] Tombstone(PlcTombstone), #[serde(rename = "plc_operation")] Operation(PlcOperation), } #[derive(Debug, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PlcCreate { pub sig: String, pub prev: Option, pub signing_key: String, pub recovery_key: String, pub handle: String, pub service: String, } #[derive(Debug, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PlcTombstone { pub sig: String, pub prev: Option, } #[derive(Debug, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PlcOperation { pub sig: String, pub prev: Option, pub rotation_keys: Vec, pub verification_methods: HashMap, pub also_known_as: Vec, pub services: HashMap, } #[derive(Debug, JsonSchema, Deserialize, Serialize)] pub struct PlcService { #[serde(rename = "type")] pub ty: String, pub endpoint: String, } #[derive(Debug, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PlcEntry { pub did: String, pub operation: PlcOperationType, pub cid: JsonCid, pub nullified: bool, pub created_at: DateTime, } #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, Deserialize, Serialize)] pub struct JsonCid( #[serde(deserialize_with = "cid_de_string", serialize_with = "cid_ser_string")] pub Cid, ); // the default cid debugger outputs a byte array, which just sucks to read impl Debug for JsonCid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.0, f) } } impl Display for JsonCid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.0, f) } } impl JsonSchema for JsonCid { fn schema_name() -> String { "Cid".to_string() } fn schema_id() -> Cow<'static, str> { Cow::Borrowed(concat!(module_path!(), "::Cid")) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { String::json_schema(generator) } } #[derive(Debug, JsonSchema, Serialize)] #[serde(rename_all = "camelCase")] pub struct DidDocument { // I hate json-ld. #[serde(rename = "@context")] context: &'static [&'static str], pub id: String, pub also_known_as: Vec, pub verification_method: Vec, pub service: Vec, } impl DidDocument { pub fn from_create(did: &str, op: PlcCreate) -> Self { DidDocument { context: DID_DOC_JSONLD_CTX, id: did.to_string(), also_known_as: vec![op.handle], verification_method: vec![DidVerificationMethod { id: format!("{did}:#atproto"), ty: "Multikey".to_string(), controller: did.to_string(), public_key_multibase: op.signing_key[8..].to_string(), }], service: vec![DidService { id: "#atproto_pds".to_string(), ty: "AtprotoPersonalDataServer".to_string(), service_endpoint: op.service, }], } } pub fn from_plc_op(did: &str, op: PlcOperation) -> Self { let verification_method = op .verification_methods .into_iter() .map(|(service, did_key)| DidVerificationMethod { id: format!("{did}#{service}"), ty: "Multikey".to_string(), controller: did.to_string(), public_key_multibase: did_key[8..].to_string(), }) .collect(); let service = op .services .into_iter() .map(|(id, service)| DidService { id: format!("#{id}"), ty: service.ty, service_endpoint: service.endpoint, }) .collect(); DidDocument { context: DID_DOC_JSONLD_CTX, id: did.to_string(), also_known_as: op.also_known_as, verification_method, service, } } } #[derive(Debug, JsonSchema, Serialize)] #[serde(rename_all = "camelCase")] pub struct DidVerificationMethod { pub id: String, #[serde(rename = "type")] pub ty: String, pub controller: String, pub public_key_multibase: String, } #[derive(Debug, JsonSchema, Serialize)] #[serde(rename_all = "camelCase")] pub struct DidService { pub id: String, #[serde(rename = "type")] pub ty: String, pub service_endpoint: String, }