本文档包含 Ceramic 协议的技术规范。有关 Ceramic 网络的详细介绍,请参见 Ceramic 概述。

索引

协议概述文档生命周期创建文档更新文档查找文档文档标识符文档日志区块链锚定冲突解决文档记录文档类型Ceramic 文档类型更新规则文档更新与传播查询未来的改进实施情况 

协议概述

Ceramic 是一种分散的协议,可以在全球公共网络上创建可变的、防篡改的智能文档。智能文档是通过数字签名、文档更新规则和区块链锚定的结合来实现的。Ceramic 文档是一种灵活的数据结构,可以用来表示各种各样的东西,如自我主权身份、特定的数据或内容、媒体文档、模式、数据访问控制策略、可验证的凭据、多方之间的协议等。该协议不依赖任何特定的区块链系统,而是利用 IPLD 将文档更改成编码为哈希链接的数据结构,称为默克尔 DAG(有向无环图),并将这些更新锚定到区块链,该区块链为给定文档的内容提供严格的排序。因此,Ceramic 可以被概念化为单个“文档链”的网络。验证特定文档的状态,只需要用户同步给定文档的数据,用户不需要像所有保持全局状态的区块链网络(即比特币、以太坊)那样同步整个网络的状态。Ceramic 网络中的一个节点因此可以选择只查询相关文档。这意味着没有全局的文档账簿。

 

Ceramic 文档由一个仅追加的记录日志组成,由一个分散标识符(DID) 签名进行更新,根据一组状态转换规则进行验证,然后锚定在区块链上。Doctypes 指定了管理文档有效更新(状态转换)的显式规则。这些可能是直接事件,如所有者的签名,也可能是间接事件,如另一个文档状态的变化。Doctype 允许 Ceramic 节点以分散的方式管理和验证给定文档的状态,而不需要集中的服务器或中间商。当对特定文档进行有效更新时,该节点随后将更新消息传递给网络的其他部分,对该文档感兴趣的任何节点将在其节点上更新给定文档的状态。因此,任何节点都可以接收对文档的查询,并返回正确的状态,即使它不维护文档本身的状态。该协议对于哪个区块链用于锚定是不可知的,开发人员可以选择将他们的文档锚定在他们选择的区块链上。

文档生命周期

Ceramic 网络中的每个文档都是独立的,所有文档的生命周期都可以用同样的方式进行广义描述。文档可以发生三个主要动作:创建、更新和查找。

 

 

创建文档

要创建文档,将创建一个包含文档初始内容的 IPLD 对象。这称为创世记录,其 CID 用作文档标识符(docId)。根据文档类型,可能会有关于创世记录的有效内容的约束。

更新文档

为了更新文档,需要添加两条记录。这些更新被添加到文档日志中,这是一个记录的链接列表。首先,创建一个包含内容更新的签名记录并添加到日志中。创建签名记录后,该记录的 CID 将被发送到一个锚定服务,该服务将一组文档更新批量发送到默克树中,并将根哈希提交到区块链进行最终确认。锚定服务然后返回给定 CID 的锚定记录,该记录被添加到文档日志中。现已对文档进行了完全更新,节点现在将包含 docId 和锚定记录的 CID 的更新消息发布到 Ceramic pubsub topic。

查找文档

要查找文档,需要 docId。一旦知道 docId,节点可以通过向 Ceramic pubsub topic 发送查找请求来查找文档。具有该文档状态的其他节点将使用他们所知道的最新记录 CID 进行响应。进行查找的节点现在解析它接收的所有 CID 的文档日志(在大多数情况下,这将只是一个CID)。如果有任何冲突,它们将通过冲突解决机制来解决。一旦解决了任何冲突,签名记录的所有更新都将应用于创世记录,并找到文档的最新状态。

 

文档标识符

在 Ceramic 中,每个文档都有一个唯一的标识符(DocID)。这是文档的永久标识符,永远不会更改。标识符编码在 CIP-59 中定义。当编码为字符串时,DocID 通常会在协议处理程序的前面:

ceramic://

 

例如,docId 可能看起来像这样:

ceramic://kjzl6fddub9hxf2q312a5qjt9ra3oyzb7lthsrtwhne0wu54iuvj852bw9wxfvs

文档版本

每次将文档锚定在具有锚记录的区块链上时,都会创建文档的新版本。文档的每个版本都可以通过使用以下格式来引用:

ceramic://?version=

文档日志

Ceramic 文档由一个仅追加的日志组成,该日志可以简化为单个 json 对象。日志中的每个记录都是一个 IPLD 对象,可以由其 CID 引用。由于 CID 是唯一的标识符,根据对象的内容,我们可以创建一个链表,其中每个记录都包含指向日志中前一个条目的上一个指针。这使得日志成为记录的不可变历史。然而,日志可能有不同的分支。为了处理这一点,使用了通过区块链锚定的冲突解决策略。日志中的每个记录都是特定类型的:创世纪、签名或锚定。创世记录是文档的第一个记录。签名记录包含对文档的更新。锚定记录将文档更新锚定到区块链,形成 exlpicit 版本。这些记录的结构将在下面的文档记录部分中描述。

区块链锚定

文档更新使用 IPLD 编码的默克树锚定到区块链。这棵默克树的根放在区块链上,树的每一片叶子都包含文档更新的哈希。这意味着来自许多不同文档的大量文档更新只能使用一个事务锚定到区块链。由于默克树是使用 IPLD 编码的,特定文档更新的见证(默克证明)可以通过 IPFS 网络高效同步。在大多数情况下,这种锚定服务将由第三方运行,该第三方聚合来自许多用户的许多文档更新,但是如果用户愿意,他们当然可以选择锚定自己的记录。在用户直接锚定自己的更新的情况下,他们可能不使用默克树。

在许多情况下,单个用户可能会更新少量文档。他们不必发送自己的区块链交易,而是可以将文档更新发送到锚定服务。该服务接收来自多个不同用户的锚定请求,并定期将这些请求批量发送到单个事务中。任何人都可以运行该服务,并且可以配置在 Ceramic 节点中使用的锚定服务。不同的服务可能提供自定义的锚定间隔,或者锚定到不同的区块链。根据给定文档的上下文和用例,一个区块链可能比另一个区块链更受欢迎。

冲突解决

如果特定文档日志有两个不同的分支,规范分支通过简单地查看冲突的锚记录来确定哪个更新最先发生。当两个锚在同一个区块链上时,使用锚的区块高度;如果锚在不同的区块链上,则使用区块时间戳。

需要注意的是,更新可能有较早的锚记录,但不遵循给定文档类型的更新规则。因此,在正确应用更新规则后,将应用冲突解决方案。由于所有文档类型都需要签名更新,恶意行为者需要访问文档创建者的私钥,才能创建更有效的冲突分支。

数据扣留攻击

对这种冲突解决系统的一种建议攻击是数据扣留攻击。在这种情况下,用户创建一个文档,进行两次冲突的更新,并将其中一次更新早于另一次,但只发布后来锚定的更新的数据。现在,对文档的后续更新将在第二次发布的更新之上进行。每个观察者都将接受这些更新为有效,因为他们没有看到第一次更新。但是,如果用户稍后发布了早期更新的数据,文档将返回到此更新,对文档进行的所有其他更新将无效。

这本质上是一种双重支出攻击,这是区块链解决的问题。然而,由于身份只有一个所有者,即用户,这就没什么问题了。在这种情况下,“双重支出”将导致用户丢失其身份上积累的所有历史和关联,他们自然不愿意这样做。同样,对于多方签名的 Tile 文档,由于所有各方都需要签署所有更新,他们都需要意识到数据扣留攻击。

就组织身份而言,这更像是一个问题,例如,如果组织的旧管理员想制造麻烦。这可以通过引入“重锚”来解决,这种锚更依赖于一些链上机制。例如,控制身份的智能合同或 DAO。

两个锚,一样的区块高度

虽然不太可能,但从技术上讲,两个文档更新以相同的区块高度锚定是可能的。如果发生这种情况,协议只需选择二进制格式中 CID 最小的更新记录。

文档记录

记录是 Ceramic 文档的基本构建块。记录是一个 IPLD 对象,包含一些数据和该数据的证明。每个记录类型都提供了一种验证其证明的方法。证明可以有许多形式,但最常见的例子是签名和区块链锚。下面最常见的记录类型是用 IPLD 架构描述的预期数据格式定义的。

创世记录

创世记录是文档的第一个记录。该记录的 CID 用于创建文档的持久 DocId ,这是一个不 可变的永久链接,用于识别特定文档。下面概述的模式描述了任何文档类型的创世记录的一般形式。它有三个主要属性,doctype, header, 和 data。doctype 是描述该文档文档类型的字符串。header 是一个属性,包括关于文档的附加元数据。最后,data 属性包含创建时文档的内容。

type GenesisHeader struct { owners [String] schema optional String tags optional [String] unique optional String}type GenesisRecord struct { doctype String header GenesisHeader data Any}

 

需要由所有文档类型定义的所需属性。但是如何使用其中的数据取决于特定的文档类型。

doctype - 包含给定 doctype 名称的字符串owners - 定义文档所有者的字符串数组data - 用于生成此文档内容的数据

 

上面有一些可选的属性在 Ceramic 协议中有特殊用途。

schema - 表示包含模式的文档的 Ceramic 文档的字符串。由特定的文档类型使用模式来验证文档内容tags - 可用于对给定文档进行分类的字符串数组。Ceramic 网络中的锚和索引服务将使用此属性unique - 如果存在,应包含一个随机字符串,允许文档获得唯一的 DocId,即使它与另一个文档具有完全相同的内容

签名记录

签名记录允许更新文档。它包含签名证明(例如地址链接)或包装在其中(例如使用 dag-jose 的文档类型)。签名记录包含指向前一条记录的指针,作为数据对文档的更新,以及编码的签名。签名的验证方式取决于使用的文档类型。

type SignedHeader struct { owners optional [String] schema optional String tags optional [String]}type SignedRecord struct { id Link prev Link refs [Link] header optional SignedHeader data Any}

 

需要由所有签名记录定义的所需属性:

id - 指向创世记录的 IPLD 链接prev - 到上一条记录的 IPLD 链接refs - IPLD 链接的数组(参见参考链接)data - 用于更新文档内容的数据,例如补丁对象

 

标头中的三个可选属性用于特定目的:

owners - 应包括更新文档的所有者,例如密钥轮换schema - 应包含以更新文档使用的模式tags - 应包括更新文档的标签

 

锚点记录

锚点记录只是证明 prev 属性的 CID 锚定在区块链上。此记录的格式可以在下面看到。proof 属性包含 AnchorProof 的 CID。此证明元数据对象由锚定在同一区块链上同一默克尔树中的所有文档更新共享。path 属性是通往包含有 CID 的默克尔树叶子的唯一路径,该CID 也包含在 prev 属性中。

 

锚点记录:

type MerkleNode struct { L Link R Link}type AnchorProof struct { chainId String blockNumber Int blockTimestamp Int txHash Link root Link}type AnchorRecord struct { id Link prev Link refs [Link] proof &AnchorProof path String

 

除了上面定义的 id、prev 和 ref 之外,我们还有以下属性:

chainId - 表示特定区块链的 CAIP-2 字符串blockNumber - 包含事务的区块数量blockTimestamp - 包含事务的区块时间戳txHash - 区块链交易的 CID,例如 eth-txroot - 到默克节点的 IPLD 链接path - 表示从 root SignedRecord 的路径,也由 prev引用

 

请注意,为了方便起见,将 block Number 和 block Timestamp 添加到此对象中,但需要验证这些数字。txHash 包括含有根 CID 的区块链交易的 CID。使用此 tx 哈希,可以使用外部区块链 api 来验证证明的信息。最后,根属性包含 IPLD 默克树根的 CID。

 

下面描述了锚点记录的图形表示以及它在包含其证明的默克树中的路径。默克根和 IPLD 对象都由上面定义的默克节点表示。

 

 

要验证特定的锚点记录,使用以下算法:

使用证明 CID 从 ipfs 获取证明元数据对象从 root 获取所有数据,并遵循路径中的所有链接验证路径的结尾与上一个相同使用 txHash 从给定区块链获取交易数据验证 block Number 和 block Timestamp 是否正确

使用 IPLD 存储锚点证明和默克树的一个很好的特性是,共享相同证明对象并在默克树中具有相似路径的文档最终将协作存储其证明的部分数据。

其他记录类型

将来可能会有新的记录类型由额外的 CIP 定义。这些可能包括像 DAO 记录这样的东西,理论上它可以作为锚和签名记录。如果定义了新的记录类型,它需要在每个 Doctype 中得到明确的支持。

参考链接

已签名和锚点记录中的 refs 属性包含组成该文档的记录列表。它被用作加快文档日志同步过程的一种方式。为了实现这一目标,refs 属性根据它们与当前节点的对数距离指向一组以前的记录。有关如何实现这一点,请参阅 ipfs-log-source。

文档类型

每个 Ceramic 文档都必须指定一个文档类型(doctype)。doctype 描述了应用于文档更新功能的状态转换规则以及文档中数据的格式。

 

Ceramic 文档类型

Ceramic 目前有两种主要的文档类型,但如果需要,将来还可以添加更多。

Tile-用于标识符、内容、媒体、模式、元数据、访问控制等的通用文档。CAIP-10 link-从区块链地址到 DID 的链接

 

更新规则

每个 doctype 需要指定什么构成有效的 updates/state 转换和有效的日志记录序列的规则。doctype 还可以为给定文档的内容指定所需的数据格式。例如,CAIP-10 link doctype 只允许一个 DID 作为其内容。

 

文档更新及传播

给定一个 docId(例如 /ceramic/3id/),可以通过与 Ceramic 网络中的其他节点通信来检索完整日志。这是通过使用 libp2p pubsub 在对等节点之间共享文档日志的更新来实现的。Ceramic 网络中的所有节点都加入了/ceramicpubsub 话题。当节点对文档进行更改时,新的head(最近的记录 CID)将与 docId 一起在 Pub/Sub 话题上共享。对该文档感兴趣的对等体看到此消息,使用 IPFS 获取新记录,并将日志应用于文档。如果节点获得两个或多个冲突 head,则使用冲突解决机制。

 

更新消息格式:

 

查询

节点可以向其他节点查询特定文档的最新 head。为此,它们向 Pub/Sub 话题发送  REQUEST  消息。看到此 消息的其他节点将使用 RESPONSE 消息进行响应。

请求消息格式:

 

如果一个已经下线的节点重新联机,它必须对所有相关文档发出请求,以确保它保持所有更新。

 

锚元数据消息

当节点从锚点服务请求锚点时,它将收到关于该服务打算何时将文档更新锚点在区块链上的元数据。然而,对同一文档感兴趣的其他节点将不知道这些额外的元数据信息。为了与其他节点共享这些信息, ANCHOR_META 消息用于告诉其他节点已经向特定的锚点服务发出了请求。感兴趣的节点可以查询给定的锚点服务来检索元数据信息。

 

消息类型

 

 

未来改进

拥有一个所有文档都共享的 Pub/Sub 话题的主要原因是为了更容易地创建一个连接良好的网络。这样做的好处是,您可以从对同一文档感兴趣的节点获得更新,即使没有直接连接到它们。这种方法的主要缺点是可扩展性。一旦网络增长,Pub/Sub中的文档和消息量对许多节点来说将会非常大。为了解决这个问题,可以使用基于 docId 的 namespace-ing 将文档分成多个不同的空间。具体情况还不确定。

Pub/Sub 方法的一个潜在问题是某种形式的 DoS。当节点对特定文档发出请求时,恶意行为者可能会发送许多与请求文档不对应的 head。这将导致请求节点不得不进行大量计算,以确保所有接收到的 head 实际上都是不正确的。解决这个问题有几种不同的方法。一种是使用针锋相对的系统,在这种系统中,节点与发送许多不正确响应的节点断开连接。如果许多用户这样做,这应该在恶意节点开始执行攻击时有效地阻止它们。另一种方法是在响应中包含零知识证明,证明消息中的 CID 确实对应于正确的文档。

 

实用情况

目前,有一个Typecript(js-Ceramic )实现可用。如果您有兴趣用另一种语言提供实现,请联系我们,我们将协助您开始!

Ceramic JavaScript客户端