···2233DID Placeholder is a cryptographic, strongly-consistent, and recoverable [DID](https://www.w3.org/TR/did-core/) method.
4455-### ⚠ README️ out of date ⚠️
66-75## Motivation
8697We introduced DID Placeholder because we weren't totally satisfied with any of the existing DID methods.
108We wanted a strongly consistent, highly available, recoverable, and cryptographically secure method with cheap and fast propagation of updates.
1191212-We cheekily titled the method "Placeholder", because we _don't_ want it to stick around. We're actively hoping to replace it with something less centralized.
1313-We expect a method to emerge that fits the bill within the next few years, likely a permissioned DID consortium.
1010+We titled the method "Placeholder", because we _don't_ want it to stick around in its current form. We're actively hoping to replace it with or evolve it into something less centralized - likely a permissioned DID consortium.
14111512## How it works
1616-This is not a fully-expressive DID format.
1717-Though it adheres to the DID spec, it is domain-specific and only allows for representing specific data types in a specific manner.
1818-There is the possibility that it could be extended to be more general in the future.
1313+Each DID document can be described by a JSON object of the following format:
1414+```ts
1515+type DocumentData = {
1616+ did: string
1717+ rotationKeys: string[]
1818+ verificationMethods: Record<string, string>
1919+ alsoKnownAs: string[]
2020+ services: Record<string, Service>
2121+}
19222020-Each DID document is made up of just four pieces of data (for now):
2121-- `signingKey`
2222-- `recoveryKey`
2323-- `handle`
2424-- `atpPds` (Personal Data Server for the related AT Protocol repository)
2323+type Service = {
2424+ type: string
2525+ endpoint: string
2626+}
2727+```
25282626-DID documents are derived from a log of signed operations, ordered by the PLC server.
2929+Keys are notated using [did:key](https://w3c-ccg.github.io/did-method-key/) and only secp256k1 and NIST P-256 are currently supported.
27302828-There are 5 operations that can be found in each log: `create`, `rotate_signing_key`, `rotate_recovery_key`, `update_handle`, and `update_atp_pds`.
3131+This data is a succinct format that can be directly translated to a valid DID document.
29323030-Each operation is of the shape:
3333+Each operation fully attests the current state of the document data. It also includes a reference to the previous operation in the log using a sha256 [CID](https://github.com/multiformats/cid). Each operation also includes a `base64url` encoded signature of the cbor-encoded operation from a valid rotation key.
3434+3535+An operation is of the shape:
3136```ts
3237type Operation = {
3333- type: string // operation type
3434- prev: CID | null // pointer to the CID of the previous operation in the log
3535- sig: string // base64url encoded signature of the operation
3636- ... // other operation-specific data
3838+ type: 'plc_operation',
3939+ rotationKeys: string[]
4040+ verificationMethods: Record<string, string>
4141+ alsoKnownAs: string[]
4242+ services: Record<string, Service>
4343+ prev: CID | null // null if genesis operation
4444+ sig: string
3745}
3846```
39474040-Each operation contains a reference the the immediately preceding operation in the log and is signed by either the `signingKey` or the `recoveryKey`.
4141-4242-The DID itself is derived from the sha256 hash of the first operation in the log.
4343-It is then base32 encoded and truncated to 24 chars.
4848+The DID itself is derived from the sha256 hash of the first operation in the log. It is then base32 encoded and truncated to 24 chars.
44494550To illustrate:
4651`did:plc:${base32Encode(sha256(createOp)).slice(0,24)}`
47524853Operations are verified, ordered and made available by the PLC server.
49545050-The PLC server is constrained in it's capabilities.
5155The operation logs are fully self-certifying, with the exception of their ordering.
52565357Therefore, the PLC server's attacks are limited to:
5458- Denial of service: rejecting valid operations, or refusing to serve some information about the DID
5559- Misordering: In the event of a fork in DID document history, the server could choose to serve the "wrong" fork
56605757-### Signing and Recovery Keys
6161+### DID Rotation & Account Recovery
6262+6363+Any key specified in `rotationKeys` has the ability to sign operations for the DID document.
6464+6565+These keys are solely a PLC concept and are _not_ included in the DID document.
6666+6767+Keys are listed in the document data in order of descending authority.
6868+6969+The PLC server provides a 72hr window during which a higher authority key can "rewrite" history.
7070+7171+To do so, that key must sign a new operation that points to the CID of the last "valid" operation - ie the fork point. This operation will be accepted as long as it was within 72hrs of the pointed to operation & the key that signed it is at a lower index in the `rotationKeys` array than the key that signed the to-be-invalidated operation
58725959-Both the `signingKey` and the `recoveryKey` are permissioned to make changes to the DID document.
6060-However, these keys are not equal.
61736262-As can be seen in the example document (below), only the `signingKey` is granted the ability to make assertions and invoke/delegate capabilities.
7474+### Resolution
63756464-The recovery key on the other hand is capable of performing a "recovery operation"
7676+Dids are resolved by making a GET request to `https://plc.directory/:did`
65776666-### Account Recovery
7878+In addition, you may resolve the constituent data by making a request to `https://plc.directory/:did/data`
67796868-The PLC server provides a 72hr window during which the `recoveryKey` can "rewrite" history.
8080+### Auditability
69817070-This is to be used in adversarial situations in which a user's `signingKey` leaks or is being held by some custodian who turns out to be a bad actor.
8282+As an additional check against the PLC server and to promote resiliency, the entire database of PLC is auditable.
71837272-In a situation such as this, the `recoveryKey` may be used to rotate both the `signingKey` and `recoveryKey`.
8484+The audit history of a given DID (complete with timestamps & invalidated forked histories) can be found at: `https://plc.directory/:did/log/audit`
73857474-If a user wishes to recover from this situation, they sign a new operation rotating the `signingKey` to a key that they hold and set the `prev` of that operation to point to the most recent pre-attack operation.
8686+The entire history of PLC operations may be downloaded as a paginated series of jsonlines at `https://plc.directory/export`
75877688## Example
77897878-Consider the following operation log:
7990```ts
8080-[
8181- {
8282- type: 'create',
8383- signingKey: 'did:key:zDnaejYFhgFiVF89LhJ4UipACLKuqo6PteZf8eKDVKeExXUPk',
8484- recoveryKey: 'did:key:zDnaeSezF2TgCD71b5DiiFyhHQwKAfsBVqTTHRMvP597Z5Ztn',
8585- handle: 'alice.example.com',
8686- service: 'https://example.com',
8787- prev: null,
8888- sig: 'vi6JAl5W4FfyViD5_BKL9p0rbI3MxTWuh0g_egTFAjtf7gwoSfSe1O3qMOEUPX6QH3H0Q9M4y7gOLGblWkEwfQ'
9191+// note: we use shorthand for keys for ease of reference, but consider them valid did:keys
9292+9393+// Genesis operation
9494+const genesisOp = {
9595+ type: 'plc_operation',
9696+ verificationMethods: {
9797+ atproto:"did:key:zSigningKey"
9898+ },
9999+ rotationKeys: [
100100+ "did:key:zRecoveryKey",
101101+ "did:key:zRotationKey"
102102+ ],
103103+ alsoKnownAs: [
104104+ "at://alice.test"
105105+ ],
106106+ services: {
107107+ atproto_pds: {
108108+ type: "AtprotoPersonalDataServer",
109109+ endpoint: "https://example.test"
110110+ }
111111+ },
112112+ prev: null,
113113+ sig: 'sig_from_did:key:zRotationKey'
114114+}
115115+116116+// Operation to update recovery key
117117+const updateKeys = {
118118+ type: 'plc_operation',
119119+ verificationMethods: {
120120+ atproto:"did:key:zSigningKey"
121121+ },
122122+ rotationKeys: [
123123+ "did:key:zNewRecoveryKey",
124124+ "did:key:zRotationKey"
125125+ ],
126126+ alsoKnownAs: [
127127+ "at://alice.test"
128128+ ],
129129+ services: {
130130+ atproto_pds: {
131131+ type: "AtprotoPersonalDataServer",
132132+ endpoint: "https://example.test"
133133+ }
89134 },
9090- {
9191- type: 'update_handle',
9292- handle: 'ali.example2.com',
9393- prev: 'bafyreih2gihqzgq5qd6uqktyfpyxqxvpdnrpu2qunnkaxugbyquxumisuq',
9494- sig: 'KL98ORpGmAJTqDsC9mWAYbhoDIv_-eZ3Nv0YqiPkbgx0ra96gYa3fQhIpZVxXFyNbu_4Y3JhPCvyJb8yDMe9Sg'
135135+ prev: CID(genesisOp),
136136+ sig: 'sig_from_did:key:zRotationKey'
137137+}
138138+139139+// Invalid operation that will be rejected
140140+// because did:key:zAttackerKey is not listed in rotationKeys
141141+const invalidUpdate = {
142142+ type: 'plc_operation',
143143+ verificationMethods: {
144144+ atproto:"did:key:zAttackerKey"
95145 },
9696- {
9797- type: 'update_atp_pds',
9898- service: 'https://example2.com',
9999- prev: 'bafyreickw7v7mwncrganw645agsmwjciolknt4f6f5an5wt3nrjepqaoiu',
100100- sig: 'AS-APea3xxR5-sq2i5v9IOsgbM5G5qAnB92tExZ8Z4vEy_GQbV8jmfY7zTx76P88AVXInZsO6yWX4UO7_xAIfg'
146146+ rotationKeys: [
147147+ "did:key:zAttackerKey"
148148+ ],
149149+ alsoKnownAs: [
150150+ "at://bob.test"
151151+ ],
152152+ services: {
153153+ atproto_pds: {
154154+ type: "AtprotoPersonalDataServer",
155155+ endpoint: "https://example.test"
156156+ }
101157 },
102102- {
103103- type: 'rotate_signing_key',
104104- key: 'did:key:zDnaeh9v2RmcMo13Du2d6pjUf5bZwtauYxj3n9dYjw4EZUAR7',
105105- prev: 'bafyreictfsrkdt5azni355vapqka5a7erqjsa3vv7iaf52yjlqqbzkwgga',
106106- sig: 'VvcCoYVDluLZghv3i6ARyk1r7m1M32BPryJlTma1HTOx2CdbmIOUkVUbFa2LWi571fe-2yjTWY0IEAKfRiPAZg'
158158+ prev: CID(updateKeys),
159159+ sig: 'sig_from_did:key:zAttackerKey'
160160+}
161161+162162+// Valid recovery operation that "undoes" updateKeys
163163+const recoveryOp = {
164164+ type: 'plc_operation',
165165+ verificationMethods: {
166166+ atproto:"did:key:zSigningKey"
107167 },
108108- {
109109- type: 'rotate_recovery_key',
110110- key: 'did:key:zDnaedvvAsDE6H3BDdBejpx9ve2Tz95cymyCAKF66JbyMh1Lt',
111111- prev: 'bafyreiazzldal6642usrcowrpztb5gjb73qla343ifnt5dfbxz4swmf5vi',
112112- sig: 'Um1GVZZT9JgB2SKEbwoF4_Sip05QjH7r_g-Hcx7lIY-OhIg88ZKcN_N4TgzljgBGwe6qZb0u_0Vaq0c-S2WSDg'
113113- }
114114-]
168168+ rotationKeys: [
169169+ "did:key:zRecoveryKey"
170170+ ],
171171+ alsoKnownAs: [
172172+ "at://alice.test"
173173+ ],
174174+ services: {
175175+ atproto_pds: {
176176+ type: "AtprotoPersonalDataServer",
177177+ endpoint: "https://example.test"
178178+ }
179179+ },
180180+ prev: CID(genesisOp),
181181+ sig: 'sig_from_did:key:zRecoveryKey'
182182+}
115183```
116184117117-The log produces the following document data:
185185+## Presentation as DID Document
186186+187187+The following data:
188188+118189```ts
119190{
120191 did: 'did:plc:7iza6de2dwap2sbkpav7c6c6',
121121- signingKey: 'did:key:zDnaeh9v2RmcMo13Du2d6pjUf5bZwtauYxj3n9dYjw4EZUAR7',
122122- recoveryKey: 'did:key:zDnaedvvAsDE6H3BDdBejpx9ve2Tz95cymyCAKF66JbyMh1Lt',
123123- handle: 'ali.example2.com',
124124- atpPds: 'https://example2.com'
192192+ verificationMethods: {
193193+ atproto: 'did:key:zDnaeh9v2RmcMo13Du2d6pjUf5bZwtauYxj3n9dYjw4EZUAR7'
194194+ },
195195+ rotationKeys: [
196196+ 'did:key:zDnaedvvAsDE6H3BDdBejpx9ve2Tz95cymyCAKF66JbyMh1Lt',
197197+ 'did:key:zDnaeh9v2RmcMo13Du2d6pjUf5bZwtauYxj3n9dYjw4EZUAR7'
198198+ ],
199199+ alsoKnownAs: [
200200+ 'at://alice.test'
201201+ ],
202202+ services: {
203203+ atproto_pds: {
204204+ type: "AtprotoPersonalDataServer",
205205+ endpoint: "https://example.test"
206206+ }
207207+ }
125208}
126209```
127210128128-And the following DID document:
211211+Will be presented as the following DID document:
212212+129213```ts
130214{
131215 '@context': [
···133217 'https://w3id.org/security/suites/ecdsa-2019/v1'
134218 ],
135219 id: 'did:plc:7iza6de2dwap2sbkpav7c6c6',
136136- alsoKnownAs: [ 'https://ali.example2.com' ],
220220+ alsoKnownAs: [ 'at://alice.test' ],
137221 verificationMethod: [
138222 {
139139- id: 'did:plc:7iza6de2dwap2sbkpav7c6c6#signingKey',
223223+ id: '#atproto',
140224 type: 'EcdsaSecp256r1VerificationKey2019',
141225 controller: 'did:plc:7iza6de2dwap2sbkpav7c6c6',
142226 publicKeyMultibase: 'zSSa7w8s5aApu6td45gWTAAFkqCnaWY6ZsJ8DpyzDdYmVy4fARKqbn5F1UYBUMeVvYTBsoSoLvZnPdjd3pVHbmAHP'
143143- },
144144- {
145145- id: 'did:plc:7iza6de2dwap2sbkpav7c6c6#recoveryKey',
146146- type: 'EcdsaSecp256r1VerificationKey2019',
147147- controller: 'did:plc:7iza6de2dwap2sbkpav7c6c6',
148148- publicKeyMultibase: 'zRV2EDDvop2r2aKWTcCtei3NvuNEnR5ucTVd9U4CSCnJEiha2QFyTjdxoFZ6629iHxhmTModThGQzX1495ZS6iD4V'
149227 }
150228 ],
151151- assertionMethod: [ 'did:plc:7iza6de2dwap2sbkpav7c6c6#signingKey' ],
152152- capabilityInvocation: [ 'did:plc:7iza6de2dwap2sbkpav7c6c6#signingKey' ],
153153- capabilityDelegation: [ 'did:plc:7iza6de2dwap2sbkpav7c6c6#signingKey' ],
154229 service: [
155230 {
156156- id: 'did:plc:7iza6de2dwap2sbkpav7c6c6#atpPds',
157157- type: 'AtpPersonalDataServer',
231231+ id: '#atproto_pds',
232232+ type: 'AtprotoPersonalDataServer',
158233 serviceEndpoint: 'https://example2.com'
159234 }
160235 ]