ceramicnetwork / ceramicnetwork/CIPs

StreamType: DIDPublish

Open
#105 4 comments 0 reactions 1 assignee Claimed by @oed View on GitHub
Cat: Standards Type: RFC
Dominant language
HTML
Stars
84
Forks
25
PR merge metrics
No merged PRs in 30d

Description

```
cip: 105
title: StreamType: DIDPublish
author: Joel Thorstensson (@oed)
discussions-to: https://github.com/ceramicnetwork/CIP/issues/105
status: Draft
category: Standards
type: RFC
created: 2021-06-27
requires: 5
```

## Simple Summary

DIDPublish is a powerful StreamType which enables a DID to publish any structured content to a global decentralized context.

## Abstract

The DIDPublish stream type makes it possible for a DID to publish any structured content which can be represented by a single [CID](https://github.com/multiformats/cid). The stream itself is ephemeral, i.e. the commit log is not persisted, this means that it is fast to sync since there is no history that needs to be verified. The content a DID publishes can be associated with some metadata information (e.g. `family`, `tags`) and the relevant DID(s). Any observer that only knows the DID and metadata can resolve the published CID.

## Motivation

Traditionally Ceramic streams have relied on an append-only data structure where all history is present and conflicts are resolved though the *earliest anchor rule*. This works great when there is a need for strong verifiability of the history of the log or as a primitive for secure key revocation, but it requires the entire log to be processed in order to get the latest state. In a lot of use cases however, we just want to find the current state as fast as possible and we might not care so much about the history of the stream. This is where the DIDPublish stream type comes in handy. DIDPublish uses a *largest nonce rule* to determine which signed commit is canonical. This reduces the need to keep the entire history of events. Instead DIDPublish simply refers to one CID which can include any content that is possible to represent as IPLD.

### Fast sync

When syncing a DIDPublish stream we simply query the network for the latest *tip* given the StreamID. The response can either be a *signed commit* or an *anchor commit*. In the first case we simply verify that the commit was signed by a *VerificationMethod* in the latest version of the controller DID document. In the second case we verify the anchor commit, then verify that the signed commit was signed by a *VerificationMethod* in the DID document which was valid at the timestamp retrived from the anchor commit. If there is a conflict, i.e. we get multiple *tips*, then we simply choose the valid tip with the largest *nonce* in the signed commit.

### Any content

Using the DIDPublish stream a user can publish any content that can be represented with a CID. Essentially any content representable as IPLD. Below some examples are listed.

#### IPFS

IPFS uses IPLD as it's internal data structure, so any file/data on IPFS can be published. In particular the UNIX-fs subsystem of IPFS might be of particular interest here.

#### Blockchain data

The blockchains of Ethereum, Bitcoin, Zcash, and Filecoin are representable as IPLD and can thus be published using DIDPublish. We could for example publish a reference to a specific block or transaction. For Filecoin specifically it might be interesting to publish information about a particular storage deal.

#### P2P databases

DIDPublish can also be used to publish the latest tip of existing p2p databases that utilize IPFS. Examples here include **Textile threads**, **Fission WINFS**, **Peergos**, and **OrbitDB**.

### StreamTypes based on DIDPublish

It is also possible to create new StreamTypes that build on top of the DIDPublish primitive. For example it may be interesting to create a stream where the history can be dynamically loaded.

## Specification

#### StreamID code: `0xXX` (to be assigned)

### Commit Formats

#### Genesis commit

The genesis commit has to be a *dag-cbor* IPLD object with the following structure:

```ipldsch
type GenesisCommit struct {
controllers [String]
family optional String
tags optional [String]
}
```

#### Signed commits

A signed commit has to be a JWS with the following structure:

```jsonc
{
payload:
signatures: [{
protected: {
kid: ,
url: ,
nonce:
},
signature:
}]
}
```

The three properties in the protected header are required.

- `kid` - the DID URL of the DID which signed the JWS
Has to include the `versionId` or `versionTime` query parameter. This is to indicate which version of the DID document was active when the signature was made.
- `url` - the Ceramic url for the stream that is being updated
The format for this would be `ceramic://`
- `nonce` - a nonce to indicate the priority of this commit (bytestring, encoded as `base64url`)
When comparing two different signed commits, the one with the largest nonce will get priority.

Note that multiple singatures from different DIDs MAY be included in the `signatures` array.

### State Transitions

#### Applying the Genesis Commit

When the genesis commit is applied a `StreamState` object is created that looks like this:

```js
{
type: 0xXX, // to be assinged
metadata: {
controllers: genesisCommit.controllers,
family: genesisCommit.family,
tags: genesisCommit.tags
},
content: null
signature: SignatureStatus.GENESIS,
anchorStatus: AnchorStatus.NOT_REQUESTED,
log: [new CID(genesisCommit)]
}
```

In addition to this, the following should be verified:

- `controllers` is an array of DID strings
- If `family` is defined it should be a string
- If `tags` is defined it should be an array of strings

#### Applying a Signed Commit

When a signed commit is applied the `StreamState` object should be modified as stated below:

```js
state.content = new CID(signedCommit.payload)
state.signature = SignatureStatus.SIGNED
state.anchorStatus = AnchorStatus.NOT_REQUESTED // or AnchorStatus.REQUESTED
state.log = [new CID(genesisCommit), new CID(signedCommit)]
```

In addition the following should be validated:

* The `url` of the protected header should be equal to the StreamID of the current stream

- The **JWS** should be valid and signed by the DID(s) in the `controllers` array
- The **JWS** should include a `kid` property in it's header which specifies the `keyFragment` as well as the `version-id`/`versionTime` of the public key that was used to sign the commit

#### Applying an Anchor Commit

When an anchor commit is applied the `StreamState` object should be modified as stated below:

```js
state.anchorStatus = AnchorStatus.ANCHORED
state.anchorProof = anchorCommit.proof
state.log.push(new CID(anchorCommit))
```

In addition to this, the following should be verified:

* the timestamp from the *anchorProof* should be between the `updated` and `nextUpdate` properties from the [didDocumentMetadata](https://w3c.github.io/did-core/#did-resolution-metadata) of the resolved DID document from the `kid`(s) that signed the JWS in the previous step

### Conflict resolution

This stream type defines a new conflict resolution rule called: **largest nonce rule**. If there are two conflicting valid *tips* this rule simply picks the *tip* which includes the larger *nonce* in the signed commit. In the edge case where there are two *signed commits* with the same *nonce*, the one that was anchored earliest is selected.

## Rationale

The DIDPublish stream type only stores the most recent signed commit and one anchor commit on top of that. This makes sync and verification time fast. The signed commit is a JWS which is stored in IPFS using *dag-jose*, but unlike the TileDocument stream type the JWS contains a `url` property with the StreamID (as oppsed to an `id` property for tiles). This makes it possible for the CID in the JWS to point to content directly.

## Implementation

No implementation yet.

## Security Considerations

DIDPublish requires support for configurable conflict resolution logic in Ceramic. TileDocument and Caip10 Link both use the *earlies anchor rule*, but in this CIP we introduced the new *largest nonce rule*. Since we only verify the genesis + signed + anchor record for DIDPublish it means that we can't rotate the owner DID. Instead DIDPublish streams are always tied to a specific DID. By default no history is keep in this stream type, this can be useful if you want your users to be able to delete data. However, if you want to preserve auditability of all events a different stream type should be used.

## Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.