Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StreamType: DIDPublish #105

Open
oed opened this issue Jun 27, 2021 · 4 comments
Open

StreamType: DIDPublish #105

oed opened this issue Jun 27, 2021 · 4 comments

Comments

@oed
Copy link
Member

oed commented Jun 27, 2021

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. 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:

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:

{
  payload: <cid-bytes>
  signatures: [{
    protected: {
      kid: <did-url>,
      url: <ceramic-streamid-url>,
      nonce: <stream-nonce>
    },
    signature: <signature-bytes>
  }]
}

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://<streamid>
  • 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:

{
  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:

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:

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 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.

@stbrody
Copy link
Contributor

stbrody commented Feb 8, 2022

what does code in the GenesisCommit represent?

@oed
Copy link
Member Author

oed commented Feb 9, 2022

It is meant to represent the concept of "stream code" basically the state transition logic of a stream. For now it's not used in DID Publish.

Edit: removed it for now.

@codynhat
Copy link

codynhat commented Dec 2, 2022

Hey @oed. What is the status of this? I assume this is not a priority with all the ComposeDB work going on.

If I was interested in implementing this StreamType, would that be practical? Is there a process in place for someone to contribute a new StreamType?

@oed
Copy link
Member Author

oed commented Dec 2, 2022

Right now this is not being prioritized. Over the next year the 3Box Labs team will put focus on building a node that only does event streaming. The idea is that various streamtypes could more easily be built on top of that core primitive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants