CIP-7: CAIP-10 Link Source

Author Joel Thorstensson, Michael Sena
Status Final
Category RFC
Created 2020-07-24
Requires 5

Simple Summary

This CIP describes the CAIP-10 Link stream type which is used to create a public link between a blockchain account (formatted as a CAIP-10 accountId) and a DID. This enables the owner of an account to associate it with a DID.


The CAIP-10 Link stream type enables the linking of a blockchain account to a DID. Blockchain accounts are represented using CAIP-10 accountIds, which is a cross-chain standard for referencing blockchain accounts. The stream type allows anyone with knowledge of a particular CAIP-10 accountId to look up which DID it is associated with using the Ceramic network. To make such a lookup, anyone can simply recreate the genesis event, which only contains the CAIP-10 accountId as unique data, to get the streamId of the particular stream and then look it up in the network.


While it’s always possible to associate a blockchain account with some external identifier (such as a DID) by submitting a transaction on-chain, doing so can be expensive due to transaction costs and likely needs to be done in a single registry so lookups can occur. Ceramic provides a better infrastructure for making these public associations.

In many cases, blockchain accounts are just simple cryptographic key pairs and can create an association with a DID only using a signature. The CAIP-10 Link stream type provides this with the addition of strict ordering, which allows an account to be securely relinked to a different DID at a later point in time.

For smart contract based accounts, the association can happen in different ways depending on the implementation of the contract. At times this can happen on-chain or off-chain. A particular example is ERC1271 which is a standard for contract based “signatures”. CAIP-10 Link can be extended to support any number of these use cases.


Below the event formats and state transitions of the CAIP-10 Link stream type are specified. Together they should include all information needed to implement this stream type in Ceramic.

StreamID code: 0x01

event formats

The InitEvent is stored in IPLD using the dag-cbor codec. It has the following required properties:

  • owners - one blockchain account encoded as CAIP-10

No other properties are allowed to be defined.

DataEvents of a CAIP-10 Link stream are also encoded using dag-cbor. Other than that they conform to the standard event format. The content of the data property should be a proof as returned by the 3id-blockchain-utils library. This library has a general structure for creating and verifying “link proofs” for any blockchain.

Note that because the signature in included in the proof object, the rest of the DataEvent is not authenticated. This could pose a potential problem, but can be mitigated if the proof timestamp is verified as described below.

State transitions

Below the state transition logic for the three different supported events is described.

Applying the InitEvent

When the InitEvent is applied a StreamState object is created that looks like this:

  metadata: {
    owners: initEvent.owners
  content: null,
  signature: SignatureStatus.GENESIS,
  anchorStatus: AnchorStatus.NOT_REQUESTED,
  log: [new CID(initEvent)]

In addition to this, the following should be verified:

  • owners is a CAIP-10 AccountId

Applying a DataEvent

When a DataEvent is applied the StreamState object should be modified in the following way: = {
  content: verifiedProof.DID
state.signature = SignatureStatus.SIGNED
state.anchorStatus = AnchorStatus.NOT_REQUESTED
state.log.push(new CID(dataEvent))

Where verifiedProof.DID is the DID contained in the proof object from In addition it needs to be verified that this proof was signed by the CAIP-10 accountId that is represented in owners. If the variable state.anchorProof is present the implementation MUST also verify that proof.timestamp is larger than state.anchorProof.blockTimestamp. For an example of how this verification can take place have a look in the 3id-blockchain-utils library.

Applying an TimeEvent

When an TimeEvent is applied the StreamState object should be modified in the following way:

state.anchorStatus = AnchorStatus.ANCHORED
state.anchorProof = timeEvent.proof
state.content =
state.metadata = Object.assign(state.metadata,
state.log.push(new CID(timeEvent))

No additional validation needs to happen at this point in the state transition function.


When resolving an caip10-link stream that is linked to a 3ID, the result looks like the examples below.

  metadata: {
    owners: ["0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb@eip155:1"]
  content: "did:3:bafyreiecedg6ipyvwdwycdjiakhj5hiuuutxlvywtkvckwvsnu6pjbwxae"
  metadata: {
    owners: ["5hmuyxw9xdgbpptgypokw4thfyoe3ryenebr381z9iaegmfy@polkadot:b0a8d493285c2df73290dfb7e61f870f"],
  content: "did:3:bafyreiecedg6ipyvwdwycdjiakhj5hiuuutxlvywtkvckwvsnu6pjbwxae"
  metadata: {
  	owners: ["cosmos1t2uflqwqe0fsj0shcfkrvpukewcw40yjj6hdc0@cosmos:cosmoshub-3"],
  content: "did:3:bafyreiecedg6ipyvwdwycdjiakhj5hiuuutxlvywtkvckwvsnu6pjbwxae"
  metadata: {
  	owners: ["128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6@bip122:000000000019d6689c085ae165831e93"],
  content: "did:3:bafyreiecedg6ipyvwdwycdjiakhj5hiuuutxlvywtkvckwvsnu6pjbwxae"


The CAIP-10 Link allows blockchain accounts to be linked to DIDs by signing a simple message. The signature is only over this message itself and not the entire DataEvent. This provides less strong security guarantees and instead opts for user experience, since the user will be able to read the message and have a better understanding of what is going on. The use of CAIP-10 future proofs this stream type by allowing any blockchain account to be linked to a DID by extending the implementation of the stream type with additional verification methods.


A reference implementation of the CAIP-10 Link stream type is provided in js-ceramic. (see the stream-caip10-link package).

Security Considerations

Since the entire payload of the DataEvent is not included in the payload of the signature used for this stream type, care needs to be taken so that the stream can not be attacked by a replay attack. For example, lets imagine that account 0xabc123@eip155:1 is first linked to did:3:A, then after that is updated to link to did:3:B. A malicious attacker could now take the proof from the first linkage and update the stream with a new DataEvent which points to the latest update but contains the first proof link object. The account would now be linked to did:3:A once again which is not desirable.

To circumvent this, each proof contains a timestamp which is the unix timestamp of when the message was signed. If we simply verify that this timestamp is larger than the timestamp of the most recent TimeEvent we can be sure that this is not a reply attack. Further attacks could possibly include modification of the refs property, but this could in the worst case just make syncing of the stream slower.

Copyright and related rights waived via CC0.


Please cite this document as:

Joel Thorstensson, Michael Sena, "CIP-7: CAIP-10 Link," Ceramic Improvement Proposals, no. 7, July 2020. [Online serial]. Available: