§ Decentralized Web Node

Specification Status: Draft

Latest Draft: identity.foundation/decentralized-web-node/spec

Previous Draft: 0.0.1-predraft

Companion Guide v0.0.1

Chairs
Andor Kesselman
Liran Cohen
Editors:
Daniel Buchner (Block)
Tobias Looker (Mattr)
Contributors:
Henry Tsai (Microsoft)
XinAn Xu (Microsoft)
Moe Jangda (Block)
Participate:
GitHub repo
File a bug
Commit history

§ Abstract

Most digital activities between people, organizations, devices, and other entities require the exchange of messages and data. For entities to exchange messages and data for credential, app, or service flows, they need an interface through which to store, discover, and fetch data related to the flows and experiences they are participating in. A Decentralized Web Node (DWN) is a data storage and message relay mechanism entities can use to locate public or private permissioned data related to a given Decentralized Identifier (DID). Decentralized Web Nodes are a mesh-like datastore construction that enable an entity to operate multiple nodes that sync to the same state across one another, enabling the owning entity to secure, manage, and transact their data with others without reliance on location or provider-specific infrastructure, interfaces, or routing mechanisms.

§ Status of This Document

Decentralized Web Node is a DRAFT specification under development within the Decentralized Identity Foundation (DIF). It is an active work item of the Secure Data Storage Working Group at DIF. It incorporates requirements and learnings from related work of many active industry players into a shared specification that meets the collective needs of the community.

The specification will be updated to incorporate feedback, from DIF members and the wider community, with a reference implementation being developed within DIF that exercises the features and requirements defined here. We encourage reviewers to submit GitHub Issues as the means by which to communicate feedback and contributions.

§ Terminology

Decentralized Web Node
A decentralized personal and application data storage and message relay node, as defined in the DIF Decentralized Web Node specification. Users may have multiple Nodes that replicate their data between them.
Decentralized Identifiers
Unique ID URI string and PKI metadata document format for describing the cryptographic keys and other fundamental PKI values linked to a unique, user-controlled, self-sovereign identifier in a target system (e.g., blockchain, distributed ledger).

§ Topology

§ Technical Stack

Decentralized Web Nodes are comprised of the following component layers, each of which is defined in this specification to ensure multiple Decentralized Web Node implementations can be used together and operate as a single logical unit for users.

DID Authentication
Access & Authorization
Interface Definitions
Interface-Specific Processing
Object Format
Object Signing / Encryption
IPLD Multiformats

§ Protocols

Protocols are used to describe common rules that DWNs will follow when dealing with specific types and structures of data. Through Protocol Definitions a DWN Owner can define how a protocol should behave.

The definition defines types, as well as their structure allowing for hierarchial relationships for data types, as well as roles, object capabilities, and enforced limitations such as data payload size.

This will promote interoperability between users and apps, avoiding bespoke implementation details and interactivity often needed within traditional application development.

In this example, a DWN owner can write images and metadata to their own DWN. Some of the rules the protocol enforces:

  1. Images must have the schema https://example.com/schemas/image.
  2. Images must have a dataFormat of image/png, image/jpeg or image/gif.
  3. Images cannot exceed a size of 500MB.
  4. Image Metadata must have a parent Image.
  5. Image Metadata must have the schema https://example.com/schemas/metadata.
  6. Image Metadata must have a dataFormat of application/json.
  7. Image Metadata cannot exceed a size of 100KB.
EXAMPLE
{
  "protocol": "https://example.com/protocol/image-storage",
  "types": {
    "image": {
      "schema": "https://example.com/schemas/image",
      "dataFormats": [
        "image/png",
        "image/jpeg",
        "image/gif"
      ]
    },
    "metadata": {
      "schema": "https://example.com/schemas/metadata",
      "dataFormats": [
        "application/json"
      ]
    }
  },
  "structure": {
    "image": {
      "$size": {
        "max": 500000000
      },
      "metadata": {
        "$size": {
          "max": 100000
        }
      }
    }
  }
}

In this example, we extend the simpler protocol with additional actors via roles. The protocol follows the same rules as the Image Storage protocol with the following additions:

  1. An Owner can assign a viewer role to an external actor.
  2. A Viewer must have the schema https://example.com/schemas/viewer.
  3. A Viewer must have the dataFormat of application/json.
  4. Only the DWN Owner can add a Viewer.
  5. A Viewer can read and query Images as well as Image Metadata.
  6. An Owner can assign a writer role to an external actor.
  7. A Writer must have the schema https://example.com/schemas/writer.
  8. A Writer must have the dataFormat of application/json.
  9. A Writer can create Images.
  10. A Writer can delete or update Images which they authored.
  11. The Author of an Image can create Image Metadata for that Image.
  12. The Author of an Image can delete or update the Image Metadata which they authored.
EXAMPLE
{
  "protocol": "https://example.com/protocol/image-sharing",
  "types": {
    "image": {
      "schema": "https://example.com/schemas/image",
      "dataFormats": [
        "image/png",
        "image/jpeg",
        "image/gif"
      ]
    },
    "metadata": {
      "schema": "https://example.com/schemas/metadata",
      "dataFormats": [
        "application/json"
      ]
    },
    "viewer": {
      "schema": "https://example.com/schemas/viewer",
      "dataFormats": [
        "application/json"
      ]
    },
    "writer": {
      "schema": "https://example.com/schemas/writer",
      "dataFormats": [
        "application/json"
      ]
    }
  },
  "structure": {
    "viewer": {
      "$role": true
    },
    "writer": {
      "$role": true
    },
    "image": {
      "$size": {
        "max": 500000000
      },
      "$actions": [
        {
          "role": "viewer",
          "can": [
            "read",
            "query"
          ]
        },
        {
          "role": "writer",
          "can": [
            "create",
            "update",
            "delete"
          ]
        }
      ],
      "metadata": {
        "$size": {
          "max": 100000
        },
        "$actions": [
          {
            "role": "viewer",
            "can": [
              "read",
              "query"
            ]
          },
          {
            "who": "author",
            "of": "image",
            "can": [
              "create",
              "update",
              "delete"
            ]
          }
        ]
      }
    }
  }
}

§ Service Endpoints

The following DID Document Service Endpoint entries MUST be present in the DID Document of a target DID for resolution to properly locate the URI for addressing a DID owner’s Decentralized Web Nodes:

{
  "id": "did:example:alice",
  "service": [{
    "id": "#dwn",
    "type": "DecentralizedWebNode",
    "enc": [
      "#dwn-enc"
    ],
    "sig": [
      "#dwn-sig"
    ],
    "serviceEndpoint": [
      "did:example:host",
      "https://dwn.example.com"
    ]
  }]
}

§ Messages

WARNING

All references to this section are out of date and will need to be updated.

All Decentralized Web Node messaging is transacted via Messages JSON objects. These objects contain message execution parameters, authorization material, authorization signatures, and signing/encryption information. For various purposes Messages rely on IPLD CIDs and DAG APIs.

{  // Request Object
  "messages": [  // Message Objects
    {
      "recordId": GENERATED_CID_STRING,
      "descriptor": {
        "interface": INTERFACE_STRING,
        "method": METHOD_STRING,
        "dataCid": DATA_CID_STRING,
        "dataFormat": DATA_FORMAT_STRING,
      }
    },
    {...}
  ]
}

Messages objects MUST be composed as follows:

In order to enable data replication features for a Decentralized Web Node, all Messages MUST be committed to an IPLD DAG in a tree allocated to the DID of the owner after all subtrees are composed and committed. The top-level of Message objects MUST be committed as a DAG CBOR encoded object.

NOTE

Individual Interface methods may describe additional properties that the descriptor object MUST or MAY contain, which are detailed in the Interfaces section of the specification.

§ Message Authorization

Some messages may require authorization material for processing them in accordance with the permissions a Decentralized Web Node owner has specified. If a message requires authorization it MUST include an authorization property with a value that is a [RFC7515] General JSON Web Signature (JWS), constructed as follows:

{  // Request Object
  "messages": [  // Message Objects
      "data": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
      "recordId": "b65b7r8n7bewv5w6eb7r8n7t78yj7hbevsv567n8r77bv65b7e6vwvd67b6",
      "descriptor": {
        "interface": "Records",
        "method": "Write",
        "schema": "https://schema.org/SocialMediaPosting",
        "dataCid": CID(data),
        "dateCreated": 123456789,
        "dataFormat": "application/json"
      },
      "attestation": {
        "payload": "89f5hw458fhw958fq094j9jdq0943j58jfq09j49j40f5qj30jf",
        "signatures": [{
          "protected": "4d093qj5h3f9j204fq8h5398hf9j24f5q9h83402048h453q",
          "signature": "49jq984h97qh3a49j98cq5h38j09jq9853h409jjq09h5q9j4"
        }]
      },
      "authorization": {
        "payload": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
        "signatures": [{
          "protected": "f454w56e57r68jrhe56gw45gw35w65w4f5i54c85j84wh5jj8h5",
          "signature": "5678nr67e56g45wf546786n9t78r67e45657bern797t8r6e5"
        }]
      }
    },
    {...}
  ]
}

§ Raw Data

If there is no need or desire to sign or encrypt the content of a message (i.e. public repudiable data), the message descriptor object is the only property required in a Message (with any method-specific properties required). An optional data property may be passed at the Message level that contains the data associated with the message (when data is desired or required to be present for a given method invocation).

{ // Message
  "data": BASE64URL_STRING,
  "recordId": "b65b7r8n7bewv5w6eb7r8n7t78yj7hbevsv567n8r77bv65b7e6vwvd67b6",
  "descriptor": {
    "interface": "Records",
    "method": "Write",
    "schema": "https://schema.org/InviteAction",
    "dataCid": CID(data),
    "dateCreated": 123456789,
    "dataFormat": "application/json"
  }
}

§ Signed Data

If the object is to be attested by a signer (e.g the Node owner via signature with their DID key), the object MUST contain the following additional properties to produce a [RFC7515] General JSON Web Signature (JWS):

{ // Message
  "recordId": "b65b7r8n7bewv5w6eb7r8n7t78yj7hbevsv567n8r77bv65b7e6vwvd67b6",
  "descriptor": {
    "interface": "Records",
    "method": "Write",
    "schema": "https://schema.org/InviteAction",
    "dataCid": CID(data),
    "dateCreated": 123456789,
    "dataFormat": "application/json"
  },
  "attestation": {
    "payload": "89f5hw458fhw958fq094j9jdq0943j58jfq09j49j40f5qj30jf",
    "signatures": [{
      "protected": "4d093qj5h3f9j204fq8h5398hf9j24f5q9h83402048h453q",
      "signature": "49jq984h97qh3a49j98cq5h38j09jq9853h409jjq09h5q9j4"
    }]
  }
  ...
}

The message generating party MUST construct the signed message object as follows:

  1. The Message object MUST contain an attestation property, and its value MUST be a General object representation of a [RFC7515] JSON Web Signature composed as follows:
    • The object must include a payload property, and its value must be the stringified Version 1 CID of the DAG CBOR encoded descriptor object, whose composition is defined in the Message Descriptor section of this specification.
    • The object MUST include a protected property, and its value must be an object composed of the following values:
      • The object MUST include an alg property, and its value MUST be the string representing the algorithm used to verify the signature (as defined by the [RFC7515] JSON Web Signature specification).
      • The object MUST include a kid property, and its value MUST be a DID URL string identifying the key to be used in verifying the signature.
    • The object MUST include a signature property, and its value must be a signature string produced by signing the protected and payload values, in accordance with the [RFC7515] JSON Web Signature specification.

§ Response Objects

Responses from Interface method invocations are JSON objects that MUST be constructed as follows:

  1. The object MAY have a status property if an error is produced from a general request-related issue, and if present its value MUST be an object composed of the following properties:
    • The status object MUST have a code property, and its value MUST be an integer set to the HTTP Status Code appropriate for the status of the response.
    • The status object MAY have a detail property, and if present its value MUST be a string that describes a terse summary of the status. It is recommended that the implementer set the message text to the standard title of the HTTP Status Code, when a title/message has already been defined for that code.
  2. The object MAY have a replies property, and if present its value MUST be an array containing Message Result Objects for all messages that were included in the initiating request object. The Message Result Objects MUST be put in the index order that matches the index of each result’s corresponding request message. Message Result Objects are constructed as follows:
    1. The object MUST have a status property, and its value MUST be an object composed of the following properties:
      • The status object MUST have a code property, and its value MUST be an integer set to the HTTP Status Code appropriate for the status of the response.
      • The status object MAY have a detail property, and if present its value MUST be a string that describes a terse summary of the status. It is recommended that the implementer set the message text to the standard title of the HTTP Status Code, when a title/message has already been defined for that code.
    2. The object MAY have a entries property if the message request was successful. If present, its value MUST be the resulting message entries returned from the invocation of the corresponding message.

§ Request-Level Status Coding

If any of the scenarios described in this section are encountered during general message processing, the implementation must include a request-level status property, and its value must be an object as defined below.

Target DID not found

If the DID targeted by a request object is not found within the Decentralized Web Node, the implementation MUST produce a request-level status with the code 404, and SHOULD use Target DID not found within the Decentralized Web Node as the status detail value.

Response Example:

EXAMPLE
{
  "status": {
    "code": 404,
    "detail": "Target DID not found within the Decentralized Web Node"
  }
}

General request-level processing errors

If a general request-level error in processing occurs that is not covered by one of the specific status cases above and prevent the implementation from correctly evaluating the request, the implementation MUST produce a request-level status with the code 500, and SHOULD use The request cannot not be processed as the status detail value.

Response Example:

EXAMPLE
{
  "status": {
    "code": 500,
    "detail": "The request could not be processed correctly"
  }
}

§ Message-Level Status Coding

If any of the scenarios described in this section are encountered during the processing of an individual message, the implementation must include a message-level status property, and its value must be an object as defined below.

Message succeeded for query/read-type interface that expects results

If a message is processed correctly and a set of result entries is expected, the implementation MUST include a message-level status object with its code property set to 200, and SHOULD use The message was successfully processed as the status detail value.

NOTE

If no results are found, the status remains 200, and the implementation MUST return an empty entries array.

Request Example:

{  // Request Object
  "messages": [  // Message Objects
    {
      "descriptor": {
        "interface": "Records",
        "method": "Query",
        "schema": "https://schema.org/SocialMediaPosting"
      }
    },
    ...
  ]
}

Response Example:

EXAMPLE
{
  "replies": [
    {
      "status": { "code": 200, "detail": "OK" },
      "entries": [...]
    }
  ]
}

Improperly constructed message

If a message is malformed or constructed with invalid properties/values, the implementation MUST include a message-level status object with its code property set to 400, and SHOULD use The message was malformed or improperly constructed as the status detail value.

Request Example:

{  // Request Object
  "messages": [  // Message Objects
    {
      "descriptorization": {
        "interface": "Records",
        "method": "Query",
        "schemata": "https://schema.org/SocialMediaPosting"
      }
    }
  ]
}

Response Example:

EXAMPLE
{
  "replies": [
    {
      "status": { "code": 400, "detail": "The message was malformed or improperly constructed" }
    }
  ]
}

Message failed authorization requirements

If a message fails to meet authorization requirements during processing, the implementation MUST include a message-level status object with its code property set to 401, and SHOULD use The message failed authorization requirements as the status detail value.

Request Example:

{  // Request Object
  "messages": [  // Message Objects
    { // Message
      "descriptor": {
        "interface": "Records",
        "method": "Write",
        "recordId": "b6464162-84af-4aab-aff5-f1f8438dfc1e",
        "dataCid": CID(data),
        "dateCreated": 123456789,
        "schema": "https://schema.org/SocialMediaPosting",
        "dataFormat": "application/json"
      }

      ^  `authorization` PROPERTY MISSING
    }
  ]
}

Response Example:

EXAMPLE
{
  "replies": [
    {
      "status": { "code": 401, "detail": "OK" }
    }
  ]
}

Interface is not implemented

If a message attempts to invoke an interface method that is not the implementation does not support, the implementation MUST include a message-level status object with its code property set to 501, and SHOULD use The interface method is not implemented as the status detail value.

Request Example:

{  // Request Object
  "messages": [  // Message Objects
    { // Message
      "descriptor": {
        "interface": "Records",
        "method": "Write",
        "recordId": "b6464162-84af-4aab-aff5-f1f8438dfc1e",
        "dataCid": CID(data),
        "schema": "https://schema.org/LikeAction",
        "dataFormat": "application/json"
      }
    }
  ]
}

Response Example:

EXAMPLE
{
  "replies": [
    {
      "status": {
        "code": 501,
        "detail": "The interface method is not implemented"
      }
    }
  ]
}

Resource consumption limit exceeded

If the DWeb Node instance receiving the request has determined that the rate of resource consumption has exceeded its tolerances and cannot process the request, the instance MUST respond with the following status entry:

Response Example:

EXAMPLE
{
  "replies": [
    {
      "status": {
        "code": 429,
        "detail": "Resource consumption has exceeded tolerances"
      }
    }
  ]
}

§ Interfaces

§ Records

To maximize decentralized app and service interoperability, the Records interface of Decentralized Web Nodes provides a mechanism to store data relative to shared schemas. By storing data in accordance with a given schema, which may be well-known in a given vertical or industry, apps and services can leverage the same datasets across one another, enabling a cohesive, cross-platform, cross-device, cross-app experience for users.

§ RecordsRead

RecordsRead messages are JSON objects that include general Message Descriptor properties and the following additional properties, which MUST be composed as follows:

A reference of the json schema can be found in the schemas directory of the specification.

EXAMPLE
{
	"descriptor": {
		"recordId": "b65b7r8n7bewv5w6eb7r8n7t78yj7hbevsv567n8r77bv65b7e6vwvd67b6",
		"messageTimestamp": "2002-10-02T10:00:00-05:00Z",
		"method": "Read",
		"interface": "Records"
	}
}
EXAMPLE
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://identity.foundation/dwn/json-schemas/records-read.json",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "descriptor"
  ],
  "properties": {
    "authorization": {
      "$ref": "https://identity.foundation/dwn/json-schemas/general-jws.json"
    },
    "descriptor": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "interface",
        "method",
        "messageTimestamp",
        "recordId"
      ],
      "properties": {
        "interface": {
          "enum": [
            "Records"
          ],
          "type": "string"
        },
        "method": {
          "enum": [
            "Read"
          ],
          "type": "string"
        },
        "messageTimestamp": {
          "type": "string"
        },
        "recordId": {
          "type": "string"
        }
      }
    }
  }
}

§ RecordsQuery

RecordsQuery messages are JSON objects that include general Message Descriptor properties and the following additional properties, which must be composed as follows:

::: Get a single object by its ID reference:

{ // Message
  "descriptor": {
    "interface": "Records",
    "method": "Query",
    "filter": {
      "recordId": "b6464162-84af-4aab-aff5-f1f8438dfc1e"
    }
  }
}

::: Get an object of a given schema type:

{ // Message
  "descriptor": {
    "interface": "Records",
    "method": "Query",
    "filter": {
      "schema": "https://schema.org/MusicPlaylist"
    }
  }
}

::: Get all objects of a given schema type:

{ // Message
  "descriptor": {
    "interface": "Records",
    "method": "Query",
    "dateSort": "createdDescending",
    "filter": {
      "dataFormat": "image/gif"
    }
  }
}
EXAMPLE
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://identity.foundation/dwn/json-schemas/records-query.json",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "descriptor"
  ],
  "properties": {
    "authorization": {
      "$ref": "https://identity.foundation/dwn/json-schemas/general-jws.json"
    },
    "descriptor": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "interface",
        "method",
        "messageTimestamp",
        "filter"
      ],
      "properties": {
        "interface": {
          "enum": [
            "Records"
          ],
          "type": "string"
        },
        "method": {
          "enum": [
            "Query"
          ],
          "type": "string"
        },
        "messageTimestamp": {
          "$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
        },
        "filter": {
          "type": "object",
          "minProperties": 1,
          "additionalProperties": false,
          "properties": {
            "protocol": {
              "type": "string"
            },
            "attester": {
              "$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/did"
            },
            "recipient": {
              "$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/did"
            },
            "contextId": {
              "type": "string"
            },
            "schema": {
              "type": "string"
            },
            "recordId": {
              "type": "string"
            },
            "parentId": {
              "type": "string"
            },
            "dataFormat": {
              "type": "string"
            },
            "dateCreated": {
              "type": "object",
              "minProperties": 1,
              "additionalProperties": false,
              "properties": {
                "from": {
                  "$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
                },
                "to": {
                  "$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
                }
              }
            }
          }
        },
        "dateSort": {
          "enum": [
            "createdAscending",
            "createdDescending",
            "publishedAscending",
            "publishedDescending"
          ],
          "type": "string"
        }
      }
    }
  }
}

§ RecordsWrite

RecordsWrite messages are JSON objects that include general Message Descriptor properties and the following additional properties, which must be composed as follows:

{ // Message
  "recordId": "b65b7r8n7bewv5w6eb7r8n7t78yj7hbevsv567n8r77bv65b7e6vwvd67b6",
  "descriptor": { // Message Descriptor
    "parentId": CID(PREVIOUS_DESCRIPTOR),
    "dataCid": CID(data),
    "dateCreated": 123456789,
    "published": true,
    "encryption": "jwe",
    "interface": "Records",
    "method": "Write",
    "schema": "https://schema.org/SocialMediaPosting",
    "commitStrategy": "json-merge",
    "dataFormat": DATA_FORMAT
  }
}

§ RecordsSubscribe

TODO

TODO

§ RecordsDelete

RecordsDelete messages are JSON objects that include general Message Descriptor properties and the following additional properties, which must be composed as follows:

::: Sample Records Delete

{ 
  "descriptor": { 
    "recordId": "b65b7r8n7bewv5w6eb7r8n7t78yj7hbevsv567n8r77bv65b7e6vwvd67b6",
    "messageTimestamp": 2002-10-02T10:00:00-05:00Z",
    "interface": "Records",
    "method": "Delete"
  }
}
EXAMPLE
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://identity.foundation/dwn/json-schemas/records-delete.json",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "authorization",
    "descriptor"
  ],
  "properties": {
    "authorization": {
      "$ref": "https://identity.foundation/dwn/json-schemas/general-jws.json"
    },
    "descriptor": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "interface",
        "method",
        "messageTimestamp",
        "recordId"
      ],
      "properties": {
        "interface": {
          "enum": [
            "Records"
          ],
          "type": "string"
        },
        "method": {
          "enum": [
            "Delete"
          ],
          "type": "string"
        },
        "messageTimestamp": {
          "type": "string"
        },
        "recordId": {
          "type": "string"
        }
      }
    }
  }
}

§ Computed Context IDs

TODO

Detail how IDs are computed for record contexts.

§ Retained Record Processing

Retained messages in the Records interface are those that may be stored against the specific record they are associated with. Within the Records interface the RecordsWrite, RecordsCommit, RecordsDelete messages are among the set that may be retained to determine the history and current data state of a record. A conforming implementation MUST perform the following steps to process retained messages:

§ If the message is a RecordsWrite:
  1. Generate the message’s Entry ID by performing the Record ID Generation Process.
    • IF the generated Entry ID matches the recordId value of the message –
    • ELSE the message may be an overwriting entry for the record; continue processing.
  2. If a message is not the Initial Entry, its descriptor MUST contain a parentId to determine the entry’s position in the record’s lineage. If a parentId is present proceed with processing, else discard the record and cease processing.
  3. Ensure all immutable values from the Initial Entry remained unchanged if present in the inbound message. If any have been mutated, discard the message and cease processing.
  4. Retrieve the Latest Checkpoint Entry, which will be either the Initial Entry or the latest RecordsDelete, and compare the parentId value of the inbound message to the Entry ID of the Latest Checkpoint Entry derived from running the Record ID Generation Process on it. If the values match, proceed with processing, if the values do not match discard the message and cease processing.
  5. If an existing RecordsWrite entry linked to the Latest Checkpoint Entry is not present and the dateCreated value of the inbound message is greater than the Latest Checkpoint Entry, store the message as the Latest Entry and cease processing, else discard the inbound message and cease processing.
  6. If an exiting RecordsWrite entry linked to the Latest Checkpoint Entry is present all of the following conditions must be true:
    • The dateCreated value of the inbound message is greater than the existing RecordsWrite, or if the dateCreated values are the same, the Entry ID of the inbound message is greater than the existing entry when the Entry IDs of the two are compared lexicographically.
  7. If all of the following conditions for Step 6 are true, store the inbound message as the Latest Entry and discard the existing RecordsWrite entry that was attached to the Latest Checkpoint Entry.
§ If the message is a RecordsDelete:
  1. Ensure the record specified by the inbound message’s recordId exists. If it does not, discard the message and cease processing.
  2. Ensure all immutable values from the Initial Entry remained unchanged if present in the inbound message. If any have been mutated, discard the message and cease processing.
  3. Fetch the active RecordsDelete entry that exists for the record. If no such entry is present, proceed to the next step. If an active RecordsDelete entry for the record is present, the dateCreated value of the inbound message MUST be greater than the active RecordsDelete entry; if it is not, discard the message and cease processing.
  4. Store the message as the Latest Checkpoint Entry, delete all messages back to the Initial Entry, including their data, and cease processing.

§ Protocols

DWeb Nodes are designed to act the substrate upon which a wide variety of decentralized applications and services can be written. With an interface like Records alone, a DWeb Node owner and those they permission can write isolated records, but that alone is not enough to support and facilitate decentralized apps. Protocols introduces a mechanism for declaratively encoding an app or service’s underlying protocol rules, including segmentation of records, relationships between records, data-level requirements, and constraints on how participants interact with a protocol. With the DWeb Node Protocols mechanism, one can model the underpinning protocols for a vast array of use cases in a way that enables interop-by-default between app implementations that ride on top of them.

§ ProtocolsConfigure

ProtocolsConfigure messages are JSON objects that include general Message Descriptor properties and the following additional properties, which must be composed as follows:

{
  "interface": "Protocols", // required
  "method": "Configure", // required
  "protocolVersion": "1.0.0", // required
  "definition": { PROTOCOL_DEFINITION_OBJ }, // optional
}

§ Protocol Definitions

Protocol Definition objects are declarative rules within ProtocolConfigure messages that specify the types, relationships, and interactions that are permitted under a given protocol installed in a DWeb Node. Inbound callers who wish to interact with a protocol must adhere to these rules, which DWeb Nodes enforce.

{
  "interface": "Protocols",
  "method": "Configure",
  "definition": {
    "protocol": "https://decentralized-social-example.org/protocol/",
    "published": true,
    "types": {
      "post": {
        "schema": "https://decentralized-social-example.org/schemas/post",
        "dataFormat": ["application/json"],
      },
      "reply": {
        "schema": "https://decentralized-social-example.org/schemas/reply",
        "dataFormat": ["application/json"],
      },
      "image": {
        "dataFormat": ["image/jpeg", "image/png", "image/gif"],
      }
    },
    "structure": {
      "post": {
        "$actions": [{
          "who": "anyone",
          "can": "read",
        }],
        "reply": {
          "$actions":[{
            "who": "anyone",
            "can": "write",
          }],
          "image": {
            "$actions": [{
              "who": "anyone",
              "can": "read",
            },{
              "who": "author",
              "of": "reply",
              "can": "write",
            }]
          }
        },
        "image": {
          "$actions":[{
            "who": "anyone",
            "can": "read",
          }, {
            "who": "author",
            "of": "post",
            "can": "write",
          }]
        }
      }
    }
  }
}
§ Processing Instructions

When processing a ProtocolsConfigure message, a conforming implementation MUST perform the following steps:

  1. If the message has a lastConfiguration property, ensure the referenced CID value links to a valid previous configuration for the specified protocol + version;
  2. If the message: 2a. Does not contain a protocolDefinition property, process the configuration as if the protocol + version is closed for interaction. 2b. Does contain a protocolDefinition property, perform any indexing, setup, or optimization processes required to begin enforcing it within the implementation.
  3. Store the configuration.

§ ProtocolsQuery

The ProtocolsQuery interface method allows an outside entity to query for any active protocols the owner has an active configuration for.

{
  "interface": "Protocols",
  "method": "Query",
  "filter": {
    "protocol": "identity.foundation/protocols/credential-issuance",
    "versions": ["1.0.0", "2.0.0"]
  }
}

§ Decentralized Web Node Protocol Languague

Please see the following JSON Schema to describe the DWN Protocol Language:

::: Protocol Definition Structure – JSON Schema

{
  "$id": "https://identity.foundation/dwn/json-schemas/protocol-definition.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "protocol",
    "published",
    "types",
    "structure"
  ],
  "properties": {
    "protocol": {
      "type": "string"
    },
    "published": {
      "type": "boolean"
    },
    "types": {
      "type": "object",
      "patternProperties": {
        ".*": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "schema": {
              "type": "string"
            },
            "dataFormats": {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "structure": {
      "type": "object",
      "patternProperties": {
        ".*": {
          "$ref": "https://identity.foundation/dwn/json-schemas/protocol-rule-set.json"
        }
      }
    }
}

::: Protocol Rule Set – JSON Schema

{
  "$id": "https://identity.foundation/dwn/json-schemas/protocol-rule-set.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "$encryption": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "rootKeyId": {
          "type": "string"
        },
        "publicKeyJwk": {
          "$ref": "https://identity.foundation/dwn/json-schemas/public-jwk.json"
        }
      }
    },
    "$actions": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "oneOf": [
          {
            "required": [
              "who",
              "can"
            ],
            "additionalProperties": false,
            "properties": {
              "who": {
                "type": "string",
                "enum": [
                  "anyone",
                  "author",
                  "recipient"
                ]
              },
              "of": {
                "type": "string"
              },
              "can": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "string",
                  "enum": [
                    "co-delete",
                    "co-update",
                    "create",
                    "delete",
                    "read",
                    "update"
                  ]
                }
              }
            }
          },
          {
            "required": [
              "role",
              "can"
            ],
            "properties": {
              "role": {
                "$comment": "Must be the protocol path of a role record type",
                "type": "string"
              },
              "can": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "string",
                  "enum": [
                    "co-delete",
                    "co-update",
                    "create",
                    "delete",
                    "query",
                    "subscribe",
                    "read",
                    "update"
                  ]
                }
              }
            }
          }
        ]
      }
    },
    "$role": {
      "$comment": "When `true`, this turns a record into `role` that may be used within a context/sub-context",
      "type": "boolean"
    },
    "$size": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "min": {
          "type": "number",
          "minimum": 0
        },
        "max": {
          "type": "number",
          "minimum": 0
        }
      }
    }
  },
  "patternProperties": {
    "^[^$].*": {
      "$ref": "https://identity.foundation/dwn/json-schemas/protocol-rule-set.json"
    }
  }
}

§ Permissions

The Permissions interface provides a mechanism for external entities to request access to various data and functionality provided by a Decentralized Web Node. Permissions employ a capabilities-based architecture that allows for DID-based authorization and delegation of authorized capabilities to others, if allowed by the owner of a Decentralized Web Node.

§ PermissionsRequest

PermissionsRequest messages are JSON objects that include general Message Descriptor properties and the following additional properties, which must be composed as follows:

{
  "descriptor": {
  "interface": "Permissions",
  "method": "Request",
    "permissionRequestId": "b6464162-84af-4aab-aff5-f1f8438dfc1e",
    "grantedBy": "did:example:alice",
    "grantedTo": "did:example:bob",
    "description": "Help you create and edit your music playlists",
    "scope": {
      "interface": "Records",
      "method": "Write",
      "schema": "https://schema.org/MusicPlaylist"
    },
    "conditions": {
      "delegation": true,
      "publication": true,
      "sharedAccess": true,
      "encryption": "optional",
      "attestation": "prohibited"
    }
  },
  "authorization": {
    "payload": "89f5hw458fhw958fq094j9jdq0943j58jfq09j49j40f5qj30jf",
    "signatures": [{
      "protected": "4d093qj5h3f9j204fq8h5398hf9j24f5q9h83402048h453q",
      "signature": "49jq984h97qh3a49j98cq5h38j09jq9853h409jjq09h5q9j4"
    }]
  }
}

§ PermissionsGrant

PermissionsGrant messages are JSON objects containing capabilities granted to parties that curtail the scope of permitted activities an invoker can perform. They are generated either in response to a PermissionsRequest message or optimistically by a user agent without an initiating PermissionsRequest. PermissionsGrant messages include general Message Descriptor properties and the following additional properties:

{
  "descriptor": {
    "interface": "Permissions",
    "method": "Grant",
    "permissionGrantId": "f45wve-5b56v5w-5657b4e-56gqf35v",
    "permissionRequestId": "b6464162-84af-4aab-aff5-f1f8438dfc1e",
    "grantedBy": "did:example:bob",
    "grantedTo": "did:example:carol",
    "expiry": 1575606941,
    "delegatedFrom": PARENT_PERMISSION_GRANT,
    "scope": {
      "method": "RecordsWrite",
      "schema": "https://schema.org/MusicPlaylist",
      "recordId": "f45wve-5b56v5w-5657b4e-56gqf35v"
    },
    "conditions": {
      "delegation": true,
      "publication": true,
      "sharedAccess": true,
      "encryption": "optional",
      "attestation": "prohibited"
    }
  },
  "authorization": {
    "payload": "89f5hw458fhw958fq094j9jdq0943j58jfq09j49j40f5qj30jf",
    "signatures": [{
      "protected": "4d093qj5h3f9j204fq8h5398hf9j24f5q9h83402048h453q",
      "signature": "49jq984h97qh3a49j98cq5h38j09jq9853h409jjq09h5q9j4"
    }]
  },
  "encryptionKey": { 
    "protected": ...,
    "recipients": ...,
    "ciphertext": ...,
    "iv": ...,
    "tag": ... 
  }
}
§ Granted Encryption Keys

The encryptionKey attribute of a PermissionsGrant is a [RFC7516] JSON Web Encryption (JWE) object that is composed as follows:

  1. The kid field of the JWE header MUST be a DID URL that identifies the public key type designated for encryption in the DID Document of the PermissionGrant recipient.
  2. The ciphertext field MUST be encrypted with the X25519 public key designated for encryption in the DID Document of the PermissionGrant recipient.
  3. The data encrypted in the object’s ciphertext field MUST be the JSON Web Key (JWK) object representation of a AES-256 symmetric encryption key generated by the owner of the DWeb Node that will be used to encrypt the data transacted in relation to the associated PermissionGrant.
§ Grantor PermissionsGrant Storage

After generating a PermissionsGrant the user agent (e.g. wallet app with access to authoritative keys for a given DID) MUST commit the granted permission object to the Decentralized Web Node of the DID the grant was issued from. This will ensure that the permission is present when addressed in subsequent interface method invocations.

§ Grantee PermissionsGrant Delivery

Once a user agent (e.g. wallet app with access to authoritative keys for a given DID) generates a PermissionsGrant for an entity to permit access to data and functionality, it is the responsibility of the user agent that generated the PermissionsGrant to deliver it to the entity that is the subject. To do this, the user agent MUST generate a Request that includes the PermissionsGrant and send it to the Decentralized Web Node of the subject it has been granted to, in accordance with the Resolution and Request Construction sections of this specification.

§ PermissionsRevoke

Revocation of a permission is the act of closing off any additional or invalid invocations of that permission. The Revoke interface method enables revocation of a permission via direct reference to the permission’s ID.

{ // Message
  "descriptor": { // Message Descriptor
    "interface": "Permissions",
    "method": "Revoke",
    "permissionRevokeId": "sdfa4162-84af-4aab-aff5-f1f8438dfc1e",
    "permissionGrantId": "b6464162-84af-4aab-aff5-f1f8438dfc1e"
  }
}

§ PermissionsQuery

The PermissionQuery method exists to facilitate lookup of any retained Permissions objects that exist in a given DID’s DWeb Node instance.

{ // Message
  "descriptor": { // Message Descriptor
    "interface": "Permissions",
    "method": "Query",
    "grantedTo": "did:example:bob"
  }
}

§ Sync

The Sync interface and its methods allow different Decentralized Web Nodes to communicate and sync on the state of the data they contain, including replication of messages and files.

§ Encryption

Each Decentralized Web Node (DWN) supports encryption at the individual message level. Encryption of data is performed using a symmetric key unique to each message. The symmetric key is then encrypted with one or more public keys derived using the HMAC-based Key Derivation Function as specified in [RFC5869] together with one of the key derivation path schemes defined in this specification. Each key derivation path scheme is optimized for a particular usage pattern of the Decentralized Web Node. Application and protocol developers can select schemes that best fit their requirements.

Encrypted messages must include an encryption property. This property contains the encrypted symmetric key(s) and metadata related to asymmetric key derivation. The encryption property allows the recipient, who possesses the corresponding private key, to decrypt the symmetric key and, consequently, the message data itself. For complete details on the encryption property, refer to RecordsWrite.

To enable encryption within a protocol, developers must declare the $encryption property in the protocol’s definition as detailed in Protocol Definitions.

§ Key Derivation Path Schemes

§ Protocol Path Scheme

This scheme enables the owner of a Decentralized Web Node to derive and specify public keys at each level of the protocol path hierarchy, facilitating incoming encrypted communication from others without the need for additional bootstrapping.

Due to the hierarchical nature of the keys, the private key corresponding to any given protocol path can decrypt all records in the descending protocol paths. This feature allows protocol designers to strategically distribute the private key at specific protocol path levels based on their specific requirements.

The hierarchical derivation path segments of this scheme must follow the structure:

["protocolPath", <protocol-url>, <root-level-record-type>, <next-level-record-type>, ...]

§ Protocol Context Scheme

This scheme allows the initiator of a new record context to derive and specify a public key that encrypts all symmetric keys within the context. The holder of the corresponding context derived private key can decrypt all messages within the context.

Under this scheme, a recommended pattern for distributing the private key to a participant is to include the encrypted private key in the participant’s role record itself. This encryption uses the public key declared in the recipient’s own instance of the configuration of the same protocol. This approach ensures that all essential cryptographic materials remain within the record context hierarchy.

The derivation path segments of this scheme must use the structure below:

["protocolContext", <root-context-id>]

§ Supported Encryption Schemes

A conforming implementation MUST be capable of encrypting and decrypting data stored in Decentralized Web Nodes using the following combinations of cryptographic schemes. Each scheme is a pair, wherein the symmetric keys are used to encrypt the data being protected, then subsequently shared with permitted recipients via encryption of the symmetric keys using the asymmetric key of each recipient.

Asymmetric Key Symmetric Key
ECIES-ES256K AES-CTR
X25519 AES-GCM
X25519 XSalsa20-Poly1305

§ Supported Encryption Formats

A conforming implementation MUST be capable of encrypting and decrypting data stored in Decentralized Web Nodes using the following combinations of cryptographic schemes. Each scheme is a pair, wherein the symmetric keys are used to encrypt the data being protected, then subsequently shared with permitted recipients via encryption of the symmetric keys using the asymmetric key of each recipient.

Label Format
jwe AES-GCM
X25519 XSalsa20-Poly1305

§ Normative References

RFC3339
Date and Time on the Internet: Timestamps. G. Klyne; C. Newman; 2002-07. Status: Proposed Standard.
RFC4122
A Universally Unique IDentifier (UUID) URN Namespace. P. Leach; M. Mealling; R. Salz; 2005-07. Status: Proposed Standard.
RFC5869
HMAC-based Extract-and-Expand Key Derivation Function (HKDF). H. Krawczyk; P. Eronen; 2010-05. Status: Informational.
RFC7515
JSON Web Signature (JWS). M. Jones; J. Bradley; N. Sakimura; 2015-05. Status: Proposed Standard.
RFC7516
JSON Web Encryption (JWE). M. Jones; J. Hildebrand; 2015-05. Status: Proposed Standard.
RFC7517
JSON Web Key (JWK). M. Jones; 2015-05. Status: Proposed Standard.
RFC7519
JSON Web Token (JWT). M. Jones; J. Bradley; N. Sakimura; 2015-05. Status: Proposed Standard.

Table of Contents