A PLC Mirror written in Rust
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}