Loading...
正在加载...
请稍候

Berty/Wesh协议详解:原理、架构与设计思想

✨步子哥 (steper) 2025年09月25日 01:58
Berty/Wesh协议详解:原理、架构与设计思想

Berty/Wesh协议详解

原理、架构与设计思想

info Wesh协议概述

Wesh协议是一个点对点的安全通信协议,最初被称为Berty协议。该协议在属于同一账户的设备之间提供安全通信,在一对一对话中提供联系人之间的通信,以及在多成员群组中提供多个用户之间的通信。Wesh协议采用分布式和异步的方式实现这些功能,无论是否有互联网访问,都可以使用IPFS和BLE等直接传输方式进行通信。

Wesh协议的核心特点包括:

  • 端到端加密:所有交换的消息都经过端到端加密
  • 完美前向保密性:即使密钥泄露,过去的通信内容也不会被解密
  • 去中心化:不依赖中央服务器,难以被关闭或监控
  • 离线通信:通过直接传输方式(如蓝牙低功耗)实现无互联网连接时的通信
  • 多设备支持:同一账户可在多个设备上使用,并保持数据同步
警告:协议的实现仍在进行中。本文档中描述的某些功能尚未实现。该协议尚未经过彻底审计,一些内容可能会随着时间和反馈而变化。

layers 协议栈详解

IPFS

IPFS(InterPlanetary File System)是一个用于存储和共享数据的分布式文件系统的点对点网络。Wesh协议使用IPFS进行即时消息传输,这提供了两个主要优势:

  • 几乎不可能阻止或关闭:任何人都可以在几秒钟内在其计算机上启动一个节点,而同一局域网内的两个节点仍然能够在没有互联网连接的情况下进行通信
  • 难以监控:没有中央服务器可供监视,也没有中央目录可供破坏,因此元数据收集大大减少

然而,使用IPFS也带来了一些技术限制:

  • 内容可用性:由于没有中央存储,所有消息都存储在用户设备上,无法访问存储在离线设备上的内容
  • 异步性:没有中央服务器来管理时间线,因此时间戳除了用于非关键任务外不能用于其他目的
  • 权威性:没有中央权威来仲裁操作及由此产生的任何冲突,也无法管理用户身份和权限
注意:目前,IPFS并非以隐私为中心,并且它是一个点对点网络,任何对等方都可以将peerID解析为其关联的公网IP地址。在协议层面,通过定期轮换用户的peerID、轮换对等方用于会面和相互同步的碰头点,以及使用户共享的数据之间难以建立关联关系来缓解这个问题。

汇合点

汇合点是一个在点对点网络上的易变地址,两个设备可以在那里相遇。对等方可以在给定的汇合点上注册它们的对等方ID,并/或获取已注册对等方的列表。通过这种方式,需要连接在一起在对话中交换消息的对等方,可以找到彼此。

在Wesh协议中,一个会晤点的地址由两个值生成:

  • 一个资源ID
  • 一个基于时间生成的令牌,该令牌由32字节的种子生成

基于时间生成令牌的过程遵循RFC 6238的核心原则。以下是生成会晤点的代码示例:

Go
func rendezvousPoint(id, seed []byte, date time.Time) []byte {
    buf := make([]byte, 32)
    mac := hmac.New(sha256.New, seed)
    binary.BigEndian.PutUint64(buf, uint64(date.Unix()))

    mac.Write(buf)
    sum := mac.Sum(nil)

    rendezvousPoint := sha256.Sum256(append(id, sum...))

    return rendezvousPoint[:]
}

Wesh协议中有两种类型的汇合点:

  • 公共会面点:用于账户接收联系人请求。此处使用的资源ID是账户ID,种子可以由用户任意更新,因此可以撤销向仅拥有先前种子的用户发送联系人请求的能力
  • 群组会面点:用于群组内交换消息。此处使用的资源ID是群组ID,种子不能更改

该协议依赖于三种不同的会面点系统:

  • DHT基础:完全分布式,几乎不可能被关闭,可以在没有互联网访问的情况下运行,但可能较慢
  • 去中心化服务器:非点对点/分布式,比DHT更容易关闭,离线时无法访问但速度更快
  • 本地记录:与蓝牙低功耗等直接传输方式结合使用,几乎能即时生效,但会引发隐私问题

直接传输

当没有互联网连接时,在一定的物理距离限制下,仍然可以通过直接传输进行通信。这些传输直接与IPFS集成,更具体地说是与其网络层:libp2p集成。

这些直接传输基于:

  • Android设备的Android Nearby
  • iOS设备的Multipeer Connectivity
  • 蓝牙低功耗(BLE)进行跨操作系统通信

通过Android Nearby和Multipeer Connectivity,消息可以通过Wi-Fi直连进行交换,这比通过BLE要快得多,也更可靠。无需访问互联网也能完全使用Wesh协议:创建账户,添加联系人,加入对话并发送消息,只要蓝牙范围内有Wesh用户即可。

无冲突复制数据类型(CRDT)

由于可以通过直接传输进行在线和离线通信,因此需要一种方法来保持所有消息之间的连贯性和顺序,尤其是在有多参与者参与的对话中。这个问题的解决方案是冲突复制数据类型(CRDT),这是一种允许对数据进行一致排序的数据结构,用于分布式系统上的消息。Wesh依赖于OrbitDB,该协议实现了CRDT。

CRDT提供乐观复制和强最终一致性,这确保了一旦同步,每个节点都将拥有相同版本的消息列表。每条消息都链接到其父消息,即当前时刻连接在一起的节点中某一方发送的最后一条消息。当在线版本和离线版本的对话同步时会出现问题:某些消息链接到相同的父消息,链表变成一个有向无环图。

OrbitDB通过使用兰伯特时钟实现这一点:每条消息将包含一个兰伯特时钟,合并后的列表将根据其值排序。兰伯特时钟是一个包含两个字段的结构体:一个身份公钥和一个计数器,该计数器为关联用户/身份发布的每条消息递增。

Go
type lamportClock struct {
    time int
    id   crypto.PublicKey
}

比较函数非常简单,它首先会检查计数器值之间的距离,如果没有,则会检查身份公钥之间的字典序距离:

Go
func compareClock(a, b lamportClock) int {
    dist := a.time - b.time

    if dist == 0 {
        dist = comparePubKey(a.id, b.id) // Returns lexicographic distance
    }

    return dist
}

account_circle 账户系统

账户创建

为了使用Wesh协议,用户必须创建一个账户。账户创建不需要任何个人信息。在整个Wesh协议中,所有密钥对都将使用X25519进行加密和Ed25519进行签名。

创建账户步骤:

  1. 生成账户ID密钥对。此操作不会重复。该密钥对是账户的身份,因此无法更改。
  2. 生成别名密钥对。操作不会重复。有关别名密钥对的更多详细信息,请参阅别名身份部分。
  3. 在用于创建账户的设备上生成设备ID密钥对。此操作将在每台新设备上重复进行。该密钥对是设备的身份。
  4. 生成公共RDV种子。RDV种子用于生成RDV点以接收联系人请求。此操作可以随时重复。

由于没有中央目录,创建账户和发送/接收联系人请求不需要访问互联网。如果两个用户离线创建账户,然后通过直接传输连接,他们将交换他们的公共会晤点(用于联系人请求),因此将能够互加为联系人。

注意:在本文中,"ID"始终表示密钥对中的公钥。

连接设备

在Wesh协议中,用户可以在同一个账号下使用多个设备,这意味着这些设备需要相互链接,以便同步账号的联系人列表、群组列表、设置等。要在设备A上向现有账号添加设备B,将遵循以下链接步骤:

  1. 第一步是在新设备B上生成一个设备ID密钥对。
  2. 然后设备A需要生成一个包含A的peerID的邀请,例如,以URL或二维码的形式。
  3. 设备B必须扫描A提供的二维码或遵循URL来获取A的peerID,与A建立连接,然后向A发送包含B设备ID的连接请求,启动握手过程。

设备A可以向设备B发送三种不同类型的挑战:

  • 二维码:B必须显示一个包含其设备ID指纹的二维码,用户需使用设备A扫描此二维码。如果二维码与A先前收到的ID匹配,则连接成功。如果设备A具备正常工作的摄像头,则应优先使用此挑战,因为它既更安全也更方便用户。
  • PIN:必须显示一个用户需要在设备B上输入的PIN码。然后B使用其设备ID向A发送PIN码的签名。最后,A使用B的设备ID验证PIN码的签名,链接即成功。这种挑战方式安全但用户不便。
  • Fingerprint:B和A必须显示B的ID指纹,然后用户需要手动验证两个指纹是否相同,并使用复选框确认。这种挑战方式安全性较低,因为用户可以在不仔细阅读指纹的情况下确认其相等性,因此不应向用户提出,除非他们希望自动化此过程。
局限性:
  • 设备吊销:请注意,无法吊销设备。一旦设备被链接,它就拥有与其他所有设备相同的信息和密钥。因此,它具备与其他所有设备相同的功能,例如链接其他设备、发送消息、加入群组或添加联系人。与账户链接的设备之间不存在层级关系。
  • 设备同步:由于Wesh是一种异步协议,两个设备需要同时在线才能同步。然而,可以使用复制设备来缓解这个问题,这些设备的唯一目的是提供内容的高可用性。这些设备无法解密消息,它们所能做的就是验证消息的真实性。

添加联系人

如果账户A想要与账户B开始一对一对话,它必须先添加B为联系人。A需要向B发送一个联系人请求,B必须接受该请求后对话才能开始。

联系请求

当账户A(请求者)想要将账户B(响应者)添加到其联系人列表时,它需要知道响应者的公共会面点。这个会面点是由RDV种子和账户ID派生出来的。因此,响应者首先需要将他的RDV种子和账户ID分享给请求者,以便后者可以计算RDV点。这些信息可以通过不同的方式发送:通过消息发送的URL、响应者设备上显示的二维码并由请求者的智能手机扫描,等等……

响应者可以随时更新他们的RDV种子。如果它这样做,请求者将无法再发送联系人请求,除非响应者共享他们新的RDV种子。响应者还可以通过从其公共汇合点注销设备来完全禁用传入的联系人请求。

握手

以下是请求者向响应者发送联系人请求时发生的握手过程:

  1. 请求者你好:请求者将其临时公钥发送给响应者。临时密钥仅用于一次握手,然后被丢弃。它们保证了消息的新鲜性,以防止重放攻击。
  2. 响应者你好:响应者将其临时公钥发送给请求者。现在请求者和响应者都能够计算两个共享密钥。
  3. 请求者认证:请求者发送一个包含其ID公钥签名的秘密盒子,他们使用ID公钥来验证自己的身份。密钥盒被密封,并且在这个步骤中,请求者也证明了他们知道响应者的ID。
  4. 响应者接受:响应者发送一个包含其ID公钥签名的秘密盒子。该秘密盒子用新的秘密密封,这证明了响应者已经有效接收并解密了前一条消息。
  5. 请求者确认:为了通知响应者,请求者在验证响应者接受的内容时没有遇到任何错误,请求者发送一个确认。完成这一步后,双方都认为握手有效。
安全性:
  • 中间人攻击:握手过程不会受到中间人攻击的威胁。实际上,请求者已经通过可信方式(例如直接扫描响应者的设备上的二维码)知道了响应者的身份。为了验证彼此的身份,请求者和响应者可以稍后见面并检查他们共享密钥的指纹。
  • DDoS攻击:如果响应者拒绝接触请求,他们可以阻止请求者的ID,使其无法再与对方进行握手。如果响应者收到的接触请求过多,他们也可以更改RDV Seed,并使用旧的RDV节点变得无法联系。
  • 重放攻击:使用临时密钥对可以保证攻击者无法重用先前窃取的消息来欺骗请求者或响应者。
局限性:设备可用性:请注意,Handshake是Wesh协议中唯一的同步操作:为了成功,两台设备都需要同时在线,通常情况下,如果请求者扫描了响应者设备上的二维码就会这样,但如果请求者点击了通过消息收到的URL,也可能不会这样。如果响应者的所有设备都不可用,请求者必须等待并在稍后发起握手。

group 群组系统

概念

该协议强烈基于群组的概念。群组是一个逻辑结构,成员及其设备连接到其中交换消息和元数据。元数据种类繁多,其中一些用于通知群组有新成员或新成员的设备加入了群组,另一些则用于成员之间交换加密密钥等。消息和元数据通过OrbitDB提供的两个不可变日志进行交换。

组结构

一个组分为两个日志:消息日志和元数据日志。

  • 消息日志:包含群组内所有交换的消息。群组成员可以选择只下载部分消息日志(例如仅最后1000条消息)。此外,由于对称密钥旋转协议,成员无法解密在他们到达之前发送的消息。
  • 元数据日志:包含群组的所有元数据。由于它包含重要信息,群组成员应当下载整个元数据日志。秘密信息在这个日志上交换。新成员的加入也会在这个日志上宣布,所以如果新成员没有下载整个元数据日志,他们将不知道完整的成员列表,因此他们将无法与他们交换秘密信息,从而无法解密他们的消息。

群组类型

在Wesh协议中,有三种不同的群组类型:账户群组、联系人群组和多成员群组。群组成员是群组中的Wesh用户(一个账户)。群组对于Wesh协议中的通信至关重要,它们拥有自己的密钥和秘密信息,并在所有群组成员之间共享:

  • 群组秘密:群组秘密是一个对称密钥,用于加密/解密群组负载。
  • 群组ID密钥对:群组ID用于派生群组RDV点。私钥仅用于生成群组密钥签名并签名创建者成员ID,然后被丢弃(仅限多成员群组)
  • 附件密钥:附件密钥是对称密钥,用于加密/解密附加到消息的文件的内容ID。附件密钥不用于加密文件。
  • 群组密钥签名:由群组ID私钥对群组密钥的签名(仅用于多成员群组)

账户群组

账户组是由所有连接到同一账户的设备组成的群组。同一账户下的设备需要在一个私密群组中才能相互通信并共享账户信息,例如发送和接收的消息、加入的群组、添加的联系人的信息等……账户组仅由一个群组成员组成,即拥有所有设备的账户。每次有新设备连接到账户时,它都会加入账户组。账户组的密钥和密钥秘密在账户创建时随机生成,与多成员群组的方式相同。

联系组

一个联系组是由恰好两名互为联系人的组成员组成的群组。当一名账户将另一名账户添加为联系人时,联系组即被创建。群组密钥、群组ID和附件密钥由发送联系人请求的一方使用X25519密钥协商协议生成。

设计考虑:如果密钥的生成方式与多成员群组和账户-群组相同(即随机生成),设备之间的同步不足可能导致为同一对联系人创建多个不同的联系人组。假设联系人组的密钥由群组创建者随机生成。Alice使用设备A1向Bob发送联系人请求,Bob使用设备B1接受该联系人请求。现在A1和B1处于一个具有随机密钥的联系人组中,A1与B1共享了这些密钥。然后A1和B1下线,A2和B2上线。A2并不知道Alice的联系人请求已被Bob接受,因此它再次发送该请求,而B2由于不知道B1已经接受了联系人请求,因此接受了这个新的联系人请求。再次,A2生成随机密钥并与B2共享,现在他们处于一个新的联系人组中,由于密钥不同,这个联系人组与第一个联系人组是不同的。如果密钥是从Alice和Bob的账户ID密钥对中确定性地生成的,这种情况就可以避免。

多成员群组

一个多成员群组是由若干个群组成员组成的,这些成员可能是彼此的联系人,也可能不是。多成员群组的一个特点是,用户在群组中不会使用他们的账户ID,而是会使用一个特定于该群组的成员ID(由群组ID和一些秘密账户派生而来)。同样地,他们的设备会使用一个成员设备ID(随机生成)。因此,知道彼此账户ID(即联系人)的用户在多成员群组中无法互相识别,除非他们愿意这样做(参见别名身份)。

多成员群组的密钥和秘密在群组创建者创建群组时随机生成。一旦秘密生成,群组创建者会在元数据日志上发布一个初始化成员条目。如上图所示,初始化成员条目由一个用群组密钥封存的密钥盒组成,其中包含以下元素:

  • 成员ID公钥
  • 成员ID公钥由群组ID私钥签名

在将此条目发布到元数据日志后,群组创建者丢弃群组ID私钥。群组中的所有成员具有相同的状态,除了可以用此初始化成员条目识别的群组创建者。但它并没有赋予他们比其他成员更多的权利或能力。

别名身份

在多成员组中,用户不会在组中使用他们的账户ID,而是会使用一个特定于该组的成员ID(由组ID和某个账户的秘密派生而来)。同样地,他们的设备将使用一个成员设备ID(随机生成)。因此,知道某人账户ID的用户将无法在多成员组中识别他们。但是,如果他们希望被联系人识别,他们可以向组共享一个别名条目,该条目由别名解析器和别名证明组成:

  • 别名解析器:别名公钥和组ID的HMAC。
  • 别名证明:由别名私钥对别名解析器签名的证明。

别名密钥对在账户创建时生成,一旦联系人请求被接受,别名公钥就会与联系人共享。通过别名条目,群组中知道某人别名公钥的每个人都能识别他们的成员ID。当用户加入一个新的多成员群组时,它会为每个联系人计算别名解析器,以便每当有其他成员披露别名条目时,匹配过程都能即时完成。

注意:在多成员群组中披露别名条目后,无法撤销。别名条目只能用于识别特定多成员群组中的成员,因为别名解析器是从群组ID派生出来的。

message 消息加密与交换

加密

在Wesh协议中,所有通信均使用对称密钥旋转机制进行端到端加密。每次用户想要向某人发送消息时,都会使用HKDF从其链密钥中导出消息密钥。HKDF每次导出后也会更新链密钥。消息密钥随后用于加密消息,且不会重复用于加密其他消息。

每个群组成员的设备拥有不同的链密钥。群组ID包含在HKDF的参数中,以使导出的密钥具有上下文相关性。对话开始时,成员们与其他参与者共享其设备的链密钥。为了解密其他参与者发送的消息,他们必须遵循相同的过程,并从链密钥中导出消息密钥。发送方每收到一条消息的HKDF密钥。

Go
func deriveNextKeys(currChainKey [32]byte, salt [64]byte, groupID []byte)
   (nextChainKey, nextMsgKey [32]byte) {
    // Salt length must be equal to hash length (64 bytes for sha256)
    hash := sha256.New

    // Generate Pseudo Random Key using currChainKey as IKM and salt
    prk := hkdf.Extract(hash, currChainKey[:], salt[:])
    // Expand using extracted prk and groupID as info (kind of namespace)
    kdf := hkdf.Expand(hash, prk, groupID)

    // Generate next chain and message keys
    io.ReadFull(kdf, nextChainKey[:])
    io.ReadFull(kdf, nextMsgKey[:])

    return nextChainKey, nextMsgKey
}

加入群组

要与其他设备(或用户)通信,设备(或用户)必须加入一个群组。只有拥有邀请函,设备(或用户)才能加入群组。

邀请

一个邀请由群组ID、群组密钥、群组密钥签名和附件密钥组成。因此,邀请可以由群组的任何成员创建。通过邀请,Wesh用户可以计算汇合点,该组的标识符,它源自组ID。

一旦RDV点计算完成,用户便能够下载群组的元数据日志,并使用群组密钥解密其部分条目。他将会获得所有群组成员的列表,这对于与他们会话并能够解密他们的消息至关重要。

新成员加入

一旦用户收到加入多成员群的邀请并下载了元数据日志,他们必须宣布自己的加入。为此,他们需要在元数据日志上为每个设备发布一个成员条目。一个成员条目由一个用群组密钥密封的秘密盒子组成,其中包含以下元素:

  • 使用成员ID密钥对组ID和成员设备ID公钥进行签名
  • 成员ID公钥用于验证签名
  • 设备ID公钥用于验证新成员

现在新成员宣布到达后,需要与其他成员交换他们的链密钥。为此,对于他们的每台设备,他们会在每个已在群组中的成员的元数据日志上发布一个秘密条目。一个秘密入口由一个用群组密钥封印的秘密盒子组成,其中包含以下元素:

  • 发送设备ID公钥
  • 接收成员ID公钥以便群组内的每个人都知道秘密条目是写给谁的
  • 一个用发送者和接收者都能计算的共享秘密封印的秘密盒子,包含发送者的链密钥及其当前计数器

这个操作是双向的,一旦一个成员在元数据日志上获取了一个秘密条目,他们也会发布一个针对新成员的秘密条目。最终,每个人都拥有新成员的链密钥,新成员也拥有群组中每个成员的当前链密钥。

交换消息

现在群组中的每个成员都拥有新成员的链密钥,新成员也拥有群组中每个成员的链密钥,每个人都能够发送和接收消息。为此,他们必须首先按照对称棘轮协议从他们的链密钥中导出消息密钥。

想要向群组发送消息的成员必须在消息日志上发布一个消息条目。一个秘密条目由一个用群组秘密封印的秘密盒子组成,其中包含以下元素:

  • 一个用消息密钥封印的秘密盒子,包含消息
  • 用设备ID私钥对这个秘密盒子的签名
  • 用于验证上述签名并识别发送者的设备ID公钥
  • 对应于消息密钥的计数器

一旦成员从消息日志中获取了消息条目,他们必须使用发送者的链密钥解密它。首先,他们需要旋转发送者的链密钥,直到达到消息计数器。然后,他们需要使用消息密钥来解密消息。一旦消息密钥被使用,它就会被丢弃,因为它只能解密一条消息。

安全性:前向保密性:新成员无法解密在他们到达之前发送的消息,因为对称棘轮协议提供了前向保密性。实际上,一旦链密钥用HKDF导出,就不可能将其恢复到之前的状态,因此无法获得比当前链密钥更旧的消息密钥。
局限性:
  • 邀请过期:如前所述,由于Wesh协议的异步性质,其中没有过期时间。因此邀请不会过期,不是指定性的,可以多次使用。此外,与账户RDV点不同,群组RDV点不能由成员随意更新,因此不能用于逃避不受欢迎的到达。
  • 成员移除:成员不能从群组中移除,因为这涉及更新所有秘密,包括成员的链密钥,同时被禁止的成员不得知道交换的新秘密,并且一些成员可能长时间未与群组的其余部分同步。对用户来说,最简单、最不容易出错且更清晰的方法,只是简单地创建一个没有不受欢迎成员的新群组。成员可以自愿离开群组,但对于其他人来说,没有密码学保证成员已经有效离开群组并且不再拥有秘密。
  • 可扩展性:由于新成员必须为群组的每个成员发布一个秘密条目,然后必须从群组的每个成员接收一个秘密条目,随着群组成员越来越多,多成员群组中的新到达可能成为一个非常昂贵的过程。因此,可能需要在多成员群组中设置成员限制,以确保使用Wesh协议的应用程序的有效运行。
  • 后泄露保密性:目前,Wesh协议中没有后泄露保密性,原因与没有成员移除相同。然而,可以更新所有成员的链密钥,例如每发送一百条消息,以减轻最终未被注意到的泄露。

cloud_queue 高可用性解决方案

由于Wesh协议中没有中央服务器,消息和文件只存储在用户设备上。因此,如果某个设备拥有某些信息并且处于离线状态,其他设备将无法获取这些信息。例如,如果用户使用设备A添加了一个联系人,然后将设备A离线并使用设备B,设备B将不会知道这个新联系人,也无法与其通信。

为了缓解这个问题并提供高可用性,用户可以使用以下配置之一设置专用设备:机器人账户、链接设备、复制设备和复制服务器。

机器人账户

机器人账户是在专用设备(例如服务器)上创建的账户,并作为联系人添加到用户账户中。用户必须手动将机器人联系人添加到其所有多成员群组中。由于机器人账户与用户账户没有任何区别,这个账户可以执行用户账户可以执行的所有操作,包括发送和读取消息。机器人账户仅为多成员群组提供高可用性,因为它不能被添加到联系人群组中。

链接设备

链接设备可以专用于复制,但这只是一个使用上的差异,没有任何东西将这个设备与链接到账户的其他设备区分开来,这意味着它们可以读取和发送消息,以及加入群组或将新设备链接到账户。链接设备为账户所属的每个群组提供高可用性。

复制设备

复制设备是链接到账户的专用设备,但不会获取所有账户秘密,并且无法发送联系人请求或链接新设备。它会自动添加到账户所属的所有群组中,但它不会获得任何群组秘密,除了群组ID公钥、群组签名和附件密钥。因此,它无法读取消息,也无法发送消息。它所能做的就是存储消息并使用群组ID公钥验证其真实性,以避免存储来自群组外部的垃圾邮件。它还需要附件密钥来解密附件cID并存储附件。

复制服务器

复制服务器基本上是一个复制设备,它不链接到用户账户,而是由第三方拥有并提供。任何人都可以将现有的复制服务器添加到群组(通过API)以提供高可用性。与复制设备一样,复制服务器在被添加到群组时只获得群组ID公钥、群组签名和附件密钥,因此无法解密消息。

vpn_key 密码学基础

密钥类型

Wesh协议中使用的所有密钥对都是X25519用于加密和Ed25519用于签名,主要有两个原因:

  • 这些密钥对在相同安全级别下比RSA密钥对更小,这意味着存储的数据更少,通过网络发送的负载更小。
  • 椭圆曲线密码学也比RSA算法更快,特别是在私钥操作上,这意味着CPU消耗更少,因此在移动设备上电池寿命更长。

Golang包

Wesh协议中使用的大多数加密库都是包含在标准Go库中的包:

  • crypto/sha256
  • crypto/rand
  • x/crypto/nacl/box
  • x/crypto/nacl/secretbox
  • x/crypto/hkdf
  • x/crypto/ed25519

Wesh协议中使用的唯一非标准包是以下两个,尽管它们是由专家编写的并经过社区的广泛审查:

  • libp2p/go-libp2p-core/crypto
  • agl/ed25519/extra25519

直接传输的特殊性

当Wesh用户通过直接传输连接到另一个Wesh用户时,他们首先要做的是发送他们正在监听的所有RDV点列表:他们的公共RDV点(用于联系人请求)以及他们所属的所有群组的RDV点。如果第二个用户已经在其中一些RDV点上监听,他们将能够使用直接传输在这些群组中进行通信。

例如,如果Alice和Bob是联系人,他们都将监听他们联系人群组的RDV点,因此当Alice将她所有的RDV点发送给Bob时,他将看到他们有一个共同的RDV点,他将能够在他们的联系人群组中向她发送消息。这对于多成员群组也是有效的。

因此,直接传输不像IPFS那样使用DHT,这是一种同步通信协议,因为两个设备需要相互连接才能交换消息。除此之外,Wesh协议的工作方式完全相同。

警告:如果Alice将她所有的RDV点发送给Bob,他可能会推断出她在多成员群组中的真实身份。Bob确实会知道他和Alice所属的所有群组,并且可能能够将不同群组中的不同成员ID与Alice联系起来。

讨论回复

3 条回复
✨步子哥 (steper) #1
09-25 02:04
OrbitDB在Berty/Wesh协议中的应用

OrbitDB在Berty/Wesh协议中的应用

分布式数据库与无冲突复制数据类型详解

storage OrbitDB概述

OrbitDB是一个无服务器、分布式、点对点的数据库,它使用IPFS作为其数据存储层,并利用Libp2p Pubsub自动与对等方同步数据库。OrbitDB是一个最终一致的数据库,使用Merkle-CRDTs(无冲突复制数据类型)进行无冲突的数据库写入和合并,使其成为p2p和去中心化应用、区块链应用和本地优先Web应用的优秀选择。

OrbitDB的核心特点包括:

  • 分布式:数据存储在多个节点上,没有单点故障
  • 点对点:节点之间直接通信,无需中央服务器
  • 无服务器:不需要中央服务器来协调或存储数据
  • 最终一致性:所有节点最终会达到一致的状态
  • 无冲突合并:使用CRDTs确保数据合并时不会产生冲突
JavaScript
// 创建OrbitDB实例
import { createLibp2p } from 'libp2p'
import { createHelia } from 'helia'
import { createOrbitDB } from '@orbitdb/core'

const libp2p = await createLibp2p({ /* Libp2p options */ })
const ipfs = await createHelia({ libp2p }) // Helia是存储和网络通信所必需的
const orbitdb = await createOrbitDB({ ipfs })

// 创建数据库
const mydb = await orbitdb.open('mydb')
console.log(mydb.address) // /orbitdb/zdpuAuK3BHpS7NvMBivynypqciYCuy2UW77XYBPUYRnLjnw13

// 添加数据
await mydb.add("hello world!")

architecture OrbitDB架构

OrbitDB的架构由多个组件组成,每个组件负责特定的功能。这些组件共同工作,以提供一个分布式、无冲突的数据库系统。

核心组件

  • IPFS:作为底层存储和网络层,提供数据存储和点对点通信能力
  • Libp2p:提供点对点网络协议栈,包括节点发现、数据传输和Pubsub功能
  • OrbitDB Core:提供数据库的核心功能,包括数据库创建、打开和同步
  • 数据存储类型:提供不同类型的数据库,如日志、键值存储、文档存储等
  • 访问控制器:管理对数据库的访问权限
  • 身份系统:提供节点身份验证和签名功能

数据存储类型

OrbitDB提供了多种数据存储类型,以满足不同的应用需求:

  • Log:仅追加的日志,可以按时间顺序存储和检索数据
  • Feed:类似Log,但支持多个写入者
  • KeyValue:简单的键值存储
  • KeyValueIndexed:带索引的键值存储,支持更复杂的查询
  • Documents:类似MongoDB的文档存储,支持JSON文档
  • Events:事件日志,支持发布/订阅模式
注意:在Berty/Wesh协议中,主要使用了OrbitDB的Log数据存储类型来存储消息和元数据日志。消息日志包含群组内所有交换的消息,而元数据日志包含群组的所有元数据,如成员信息、密钥交换等。

sync CRDTs技术详解

CRDTs(Conflict-free Replicated Data Types,无冲突复制数据类型)是一种数据结构,它简化了分布式数据存储系统和多用户应用程序。在许多系统中,某些数据的副本需要存储在多台计算机上(称为副本)。CRDTs的主要目标是确保即使在多个副本上并发修改数据,也能在没有冲突的情况下合并这些修改。

CRDTs的基本原理

CRDTs基于两个核心概念:

  • 状态:数据在某个时间点的完整表示
  • 操作:对数据进行的修改

根据实现方式的不同,CRDTs可以分为两类:

  • 基于状态的CRDTs(CvRDT):副本之间交换完整的状态,并通过合并函数确保一致性
  • 基于操作的CRDTs(CmRDT):副本之间交换操作,并通过确保操作的因果传递和确定性执行来保证一致性

CRDTs的优势

CRDTs在分布式系统中具有以下优势:

  • 自动冲突解决:不需要复杂的冲突解决算法,数据合并是确定性的
  • 高可用性:即使网络分区,副本仍然可以接受更新
  • 最终一致性:所有副本最终会达到一致的状态
  • 低延迟:不需要等待其他副本的确认,可以立即应用更新

Merkle-CRDTs

OrbitDB使用Merkle-CRDTs,这是一种结合了Merkle树和CRDTs的技术。Merkle树是一种哈希树,可以高效地验证数据完整性。在Merkle-CRDTs中,每个操作都包含一个Merkle证明,可以验证操作的有效性和完整性。

Go
// 兰伯特时钟示例,用于在CRDTs中确定操作顺序
type lamportClock struct {
    time int
    id   crypto.PublicKey
}

// 比较函数,用于确定两个操作的顺序
func compareClock(a, b lamportClock) int {
    dist := a.time - b.time

    if dist == 0 {
        dist = comparePubKey(a.id, b.id) // 返回字典序距离
    }

    return dist
}
注意:在Berty/Wesh协议中,OrbitDB使用兰伯特时钟(Lamport Clock)来实现CRDTs。兰伯特时钟是一个包含两个字段的结构体:一个身份公钥和一个计数器,该计数器为关联用户/身份发布的每条消息递增。这确保了即使在网络分区的情况下,消息也能被正确排序。

integration_instructions OrbitDB在Berty/Wesh协议中的应用

在Berty/Wesh协议中,OrbitDB扮演着关键角色,它提供了分布式数据存储和同步功能,使得协议能够在没有中央服务器的情况下实现消息的可靠传递和存储。

消息和元数据存储

在Wesh协议中,群组分为两个日志:消息日志和元数据日志。这两个日志都使用OrbitDB的Log数据存储类型来实现:

  • 消息日志:包含群组内所有交换的消息。群组成员可以选择只下载部分消息日志(例如仅最后1000条消息)。此外,由于对称密钥旋转协议,成员无法解密在他们到达之前发送的消息。
  • 元数据日志:包含群组的所有元数据。由于它包含重要信息,群组成员应当下载整个元数据日志。秘密信息在这个日志上交换。新成员的加入也会在这个日志上宣布,所以如果新成员没有下载整个元数据日志,他们将不知道完整的成员列表,因此他们将无法与他们交换秘密信息,从而无法解密他们的消息。

消息排序和冲突解决

由于可以通过直接传输进行在线和离线通信,因此需要一种方法来保持所有消息之间的连贯性和顺序,尤其是在有多参与者参与的对话中。OrbitDB的CRDTs特性确保了这一点:

  • 每条消息都链接到其父消息,即当前时刻连接在一起的节点中某一方发送的最后一条消息
  • 当在线版本和离线版本的对话同步时,某些消息可能链接到相同的父消息,链表变成一个有向无环图
  • OrbitDB通过使用兰伯特时钟实现消息的排序和合并,确保所有节点最终拥有相同版本的消息列表

群组管理

OrbitDB还用于群组管理,包括:

  • 成员管理:新成员加入时,会在元数据日志上发布成员条目,包含成员ID公钥和设备ID公钥
  • 密钥交换:成员之间通过元数据日志交换链密钥,以便能够解密彼此的消息
  • 别名身份:在多成员群组中,用户可以共享别名条目,以便被联系人识别
Go
// 在Berty/Wesh协议中使用OrbitDB的示例
// 初始化OrbitDB实例
orbitdb, err := orbitdb.NewOrbitDB(ctx, ipfs, &orbitdb.NewOrbitDBOptions{
    Directory: &dbPath,
})

// 打开群组的元数据日志
metadataStore, err := orbitdb.Open(ctx, group.MetadataLogAddr, &orbitdb.OpenOptions{})
if err != nil {
    return nil, err
}

// 打开群组的消息日志
messageStore, err := orbitdb.Open(ctx, group.MessageLogAddr, &orbitdb.OpenOptions{})
if err != nil {
    return nil, err
}

// 添加新成员到元数据日志
memberEntry := &protocol.MemberEntry{
    DevicePK: devicePK,
    MemberPK: memberPK,
}

memberEntryBytes, err := proto.Marshal(memberEntry)
if err != nil {
    return err
}

_, err = metadataStore.Add(ctx, memberEntryBytes, &orbitdb.WriteOptions{})
if err != nil {
    return err
}
注意:在Berty/Wesh协议中,OrbitDB的日志是加密的。只有拥有正确密钥的群组成员才能解密日志中的内容。这确保了消息的机密性和完整性。

security OrbitDB的安全性和隐私保护

在Berty/Wesh协议中,OrbitDB不仅提供了分布式数据存储和同步功能,还通过多种机制确保数据的安全性和用户隐私。

数据加密

OrbitDB中的所有数据都经过加密处理:

  • 端到端加密:所有消息和元数据都使用群组密钥进行加密,只有群组成员才能解密
  • 密钥轮换:使用对称密钥旋转机制,每次发送消息时都会从链密钥中导出新的消息密钥,确保前向保密性
  • 签名验证:所有条目都经过签名验证,确保数据的完整性和来源的真实性

身份保护

OrbitDB通过多种机制保护用户身份:

  • 匿名性:在多成员群组中,用户使用特定于群组的成员ID,而不是账户ID,使得其他成员无法轻易识别其真实身份
  • 别名系统:用户可以选择性地披露别名,允许被特定联系人识别,同时保持对其他成员的匿名性
  • 密钥分离:不同类型的密钥用于不同目的,如账户密钥、设备密钥、群组密钥等,减少了密钥泄露的风险

元数据保护

OrbitDB还采取措施保护元数据:

  • 最小化元数据:只收集必要的元数据,减少可被用于分析的信息
  • 定期轮换:定期轮换用户的peerID和会面点,使得难以建立关联关系
  • 本地存储:所有数据都存储在用户设备上,没有中央服务器可供监视
注意:尽管OrbitDB和Berty/Wesh协议采取了多种安全措施,但用户仍需注意,完全的匿名性和隐私保护是困难的。特别是,IPFS并非以隐私为中心,任何对等方都可以将peerID解析为其关联的公网IP地址。协议通过定期轮换用户的peerID和会面点来缓解这个问题,但这并不能完全解决隐私问题。

compare_arrows OrbitDB与其他分布式数据库的比较

OrbitDB是众多分布式数据库中的一种,但它有一些独特的特点,使其特别适合Berty/Wesh协议的需求。

与中心化数据库的比较

中心化数据库

  • 单点故障风险
  • 需要中央服务器
  • 易于审查和关闭
  • 依赖互联网连接
  • 集中式元数据收集

OrbitDB

  • 无单点故障
  • 无需中央服务器
  • 难以审查和关闭
  • 支持离线操作
  • 最小化元数据收集

与其他分布式数据库的比较

与其他分布式数据库相比,OrbitDB具有以下优势:

  • 与IPFS深度集成:OrbitDB使用IPFS作为底层存储和网络层,提供了强大的点对点网络能力
  • 内置CRDTs支持:OrbitDB内置了对CRDTs的支持,使得数据合并更加简单和可靠
  • 多种数据存储类型:OrbitDB提供了多种数据存储类型,满足不同的应用需求
  • 灵活的访问控制:OrbitDB提供了灵活的访问控制机制,可以精细控制谁可以访问和修改数据
  • 轻量级:OrbitDB设计为轻量级,适合在资源受限的环境(如移动设备)中运行

OrbitDB的局限性

尽管OrbitDB有许多优势,但它也有一些局限性:

  • 性能:由于需要维护多个副本和进行加密操作,OrbitDB的性能可能不如中心化数据库
  • 存储效率:由于数据存储在多个节点上,OrbitDB的存储效率较低
  • 复杂性:分布式系统的复杂性使得开发和调试更加困难
  • 成熟度:相比一些成熟的数据库系统,OrbitDB相对较新,可能存在一些未发现的问题
注意:在Berty/Wesh协议中,OrbitDB的局限性通过其他机制得到了缓解。例如,性能问题通过优化数据结构和减少同步频率来缓解;存储效率问题通过允许用户选择只同步部分数据来缓解;复杂性问题通过提供清晰的API和文档来缓解。

trending_up OrbitDB的未来发展

OrbitDB作为一个活跃的开源项目,正在不断发展和改进。以下是一些可能的发展方向,这些发展将进一步增强OrbitDB在Berty/Wesh协议中的应用。

性能优化

性能是OrbitDB持续关注的重点,未来的优化可能包括:

  • 更高效的CRDTs算法:研究和实现更高效的CRDTs算法,减少计算和存储开销
  • 增量同步:实现更精细的增量同步机制,减少网络带宽使用
  • 索引优化:改进索引机制,提高查询性能
  • 缓存策略:实现更智能的缓存策略,减少磁盘I/O

隐私增强

隐私是Berty/Wesh协议的核心关注点,OrbitDB的隐私增强可能包括:

  • 更强的匿名性:集成I2P、Tor或类似网络,提供更强的匿名性
  • 元数据保护:进一步减少可被用于分析的元数据
  • 差分隐私:引入差分隐私技术,保护用户数据
  • 零知识证明:集成零知识证明技术,允许验证数据而不泄露内容

功能扩展

OrbitDB的功能扩展可能包括:

  • 更多数据存储类型:支持更多种类的数据存储类型,如图数据库、时间序列数据库等
  • 更灵活的访问控制:实现更细粒度的访问控制机制
  • 跨链互操作:支持与不同区块链的互操作
  • 智能合约集成:集成智能合约功能,实现更复杂的业务逻辑

生态系统发展

OrbitDB的生态系统发展可能包括:

  • 更多语言支持:支持更多编程语言,扩大应用范围
  • 更好的开发工具:提供更好的开发工具,简化开发流程
  • 更多应用案例:探索更多应用场景,促进技术成熟
  • 社区建设:建设活跃的社区,促进知识共享和协作
注意:OrbitDB的发展将直接影响Berty/Wesh协议的未来。随着OrbitDB的改进,Berty/Wesh协议也将受益于更好的性能、更强的隐私保护和更丰富的功能。反过来,Berty/Wesh协议的使用也将为OrbitDB提供宝贵的反馈和用例,促进其进一步发展。
✨步子哥 (steper) #2
09-25 02:10
IPFS的pubsub协议与Gossipsub v1.1

IPFS的pubsub协议与Gossipsub v1.1

原理、特点、安全性改进及其在Berty/Wesh协议中的应用

hub IPFS的pubsub协议概述

IPFS的pubsub(发布/订阅)协议是一种允许节点之间进行消息广播和事件通知的机制。根据目前公开的信息,IPFS的pubsub协议并没有被废弃,而是经历了协议升级和强化,其替代品是Gossipsub v1.1。

Pubsub协议在分布式系统中扮演着重要角色,它允许节点:

  • 订阅特定主题:节点可以订阅一个或多个主题,只接收与这些主题相关的消息
  • 发布消息:节点可以向特定主题发布消息,所有订阅该主题的节点都会收到
  • 实时通信:支持节点间的实时消息传递,无需轮询
  • 去中心化:没有中央服务器,消息在网络中通过节点间传播
注意:在Berty/Wesh协议中,pubsub机制被用于节点间的消息同步和事件通知,特别是在没有互联网连接的情况下,通过直接传输方式(如蓝牙低功耗)实现设备间的通信。

history Pubsub协议的演进

IPFS的pubsub协议经历了多次演进,从最初的Floodsub到现在的Gossipsub v1.1,每一次演进都带来了性能和安全性上的提升。

Floodsub(泛洪广播)

Floodsub是IPFS最早的pubsub实现,其工作原理非常简单:

  • 当一个节点收到消息时,它会将消息转发给所有已连接的节点(除了消息来源节点)
  • 这种"泛洪"式的传播方式确保消息能够快速到达网络中的所有节点
  • 然而,这种方法效率低下,会产生大量冗余消息,不适合大规模网络

Gossipsub

Gossipsub是对Floodsub的重大改进,它引入了更高效的消息传播机制:

  • Mesh拓扑:节点维护一个特定主题的连接子集(称为"mesh"),而不是连接到所有节点
  • Gossip传播:使用 gossip 协议来传播消息,减少冗余流量
  • 可扩展性:相比Floodsub,Gossipsub能够支持更大规模的网络

Gossipsub v1.1

Gossipsub v1.1是当前版本的pubsub协议实现,它在原有基础上进一步增强了安全性:

  • 防御女巫攻击:通过评分机制限制恶意节点的影响
  • 防御日蚀攻击:通过维护多样化的连接,防止恶意节点隔离目标节点
  • 防御垃圾信息攻击:通过验证和过滤机制,减少垃圾消息的传播
重要澄清:如果你听说IPFS的pubsub被"废弃",那可能是指早期实验性的Floodsub或旧版pubsub实现被Gossipsub替代,而不是整个pubsub功能被移除。Gossipsub v1.1目前是活跃维护中的协议,而非废弃状态。

architecture Gossipsub v1.1的工作原理

Gossipsub v1.1是libp2p提供的pubsub协议实现,它通过一系列精心设计的机制实现了高效、安全、可扩展的消息传播。

核心概念

  • 主题(Topics):消息的逻辑分类,节点可以订阅一个或多个主题
  • Mesh:针对每个主题,节点维护一个连接子集,形成网状拓扑结构
  • Fanout:对于节点已发布但尚未订阅的主题,维护一个临时的连接列表
  • Graft/Prune:动态调整mesh结构的机制,用于优化网络拓扑

消息传播流程

Gossipsub v1.1的消息传播流程如下:

  1. 节点A向主题T发布消息M
  2. 节点A将消息M发送给它在主题T的mesh中的所有邻居节点
  3. 收到消息M的节点验证消息的有效性(如签名、格式等)
  4. 验证通过后,节点将消息M转发给它在主题T的mesh中的部分邻居节点(不是全部)
  5. 同时,节点会随机选择一些不在mesh中的节点,将消息M发送给它们(称为"gossip")
  6. 这个过程持续进行,直到消息M传播到网络中的所有相关节点

评分机制

Gossipsub v1.1引入了节点评分机制,用于识别和限制恶意节点:

  • 每个节点维护一个对其他节点的评分
  • 节点的行为会影响其评分,例如:
    • 有效消息会增加评分
    • 无效消息会降低评分
    • 违反协议的行为会大幅降低评分
  • 评分低的节点会被限制或从mesh中移除
  • 评分机制是防御女巫攻击和垃圾信息攻击的关键
Go
// Gossipsub评分机制的简化示例
type PeerScore struct {
    score float64
    topics map[string]*TopicScore
}

// 更新节点评分
func (ps *PeerScore) Update(topic string, valid bool) {
    topicScore, ok := ps.topics[topic]
    if !ok {
        topicScore = &TopicScore{score: 0}
        ps.topics[topic] = topicScore
    }
    
    if valid {
        // 有效消息增加评分
        topicScore.score += 1.0
    } else {
        // 无效消息降低评分
        topicScore.score -= 10.0
    }
    
    // 更新总体评分
    ps.score = 0
    for _, ts := range ps.topics {
        ps.score += ts.score
    }
}

security Gossipsub v1.1的安全性改进

Gossipsub v1.1相比早期版本,在安全性方面有显著改进,使其更适合用于区块链等对安全性和可靠性要求较高的场景。

防御女巫攻击(Sybil Attack)

女巫攻击是指攻击者通过创建大量虚假身份(节点)来控制或影响网络。Gossipsub v1.1通过以下机制防御女巫攻击:

  • 评分机制:新节点初始评分较低,需要通过发送有效消息逐步建立信誉
  • 连接限制:限制单个IP地址或子网的连接数量
  • 行为验证:要求节点遵循协议规范,异常行为会导致评分下降
  • 动态mesh调整:定期移除低评分节点,引入高评分节点

防御日蚀攻击(Eclipse Attack)

日蚀攻击是指攻击者通过控制目标节点的所有连接,将其与网络隔离。Gossipsub v1.1通过以下机制防御日蚀攻击:

  • 连接多样性:鼓励节点与不同网络、不同地区的节点建立连接
  • 主动发现:节点定期发现新节点,避免仅依赖固定连接
  • 连接验证:定期验证连接的有效性,移除无响应的连接
  • 透明度:节点可以查看其连接情况,检测是否被隔离

防御垃圾信息攻击(Spam Attack)

垃圾信息攻击是指攻击者发送大量无用或恶意消息,消耗网络资源。Gossipsub v1.1通过以下机制防御垃圾信息攻击:

  • 消息验证:严格验证消息格式、签名和内容
  • 速率限制:限制单个节点发送消息的速率
  • 评分惩罚:发送无效消息的节点会受到评分惩罚
  • 消息优先级:根据消息来源和内容设置优先级,优先处理高优先级消息
注意:尽管Gossipsub v1.1引入了多种安全机制,但没有任何系统能够完全免疫所有攻击。在实际应用中,还需要结合其他安全措施,如加密、身份验证等,来构建更安全的系统。

compare Pubsub协议比较

为了更好地理解Gossipsub v1.1的优势,我们可以将其与其他pubsub协议进行比较。

特性 Floodsub Gossipsub Gossipsub v1.1
消息传播效率 低(大量冗余消息) 中(使用gossip机制) 高(优化的gossip机制)
可扩展性 差(不适合大规模网络) 中(支持中等规模网络) 好(支持大规模网络)
防御女巫攻击 有限 强(评分机制)
防御日蚀攻击 有限 强(连接多样性)
防御垃圾信息攻击 有限 强(验证和过滤)
资源消耗 高(大量冗余流量) 低(优化的消息传播)
实现复杂度 高(多种安全机制)

适用场景

不同的pubsub协议适用于不同的场景:

  • Floodsub:适用于小型网络或测试环境,实现简单但效率低下
  • Gossipsub:适用于中等规模网络,平衡了效率和实现复杂度
  • Gossipsub v1.1:适用于大规模网络和对安全性要求高的场景,如区块链网络(Filecoin、Ethereum 2.0)等
注意:在Berty/Wesh协议中,Gossipsub v1.1被用于节点间的消息同步和事件通知,特别是在多设备同步和群组通信场景中。其安全性和效率特性使其成为Berty/Wesh协议的理想选择。

integration_instructions Gossipsub v1.1在Berty/Wesh协议中的应用

在Berty/Wesh协议中,Gossipsub v1.1扮演着关键角色,它提供了高效、安全的消息传播机制,使得协议能够在没有中央服务器的情况下实现节点间的可靠通信。

消息同步

Gossipsub v1.1在Berty/Wesh协议中主要用于消息同步:

  • 群组消息传播:当用户向群组发送消息时,消息通过Gossipsub v1.1传播到群组中的所有成员
  • 元数据同步:群组的元数据(如成员信息、密钥交换等)通过Gossipsub v1.1同步到所有成员
  • 设备同步:同一账户下的多个设备通过Gossipsub v1.1同步消息和状态

事件通知

除了消息同步,Gossipsub v1.1还用于事件通知:

  • 联系人请求:当用户发送联系人请求时,通过Gossipsub v1.1通知目标用户
  • 群组邀请:群组邀请通过Gossipsub v1.1发送给被邀请用户
  • 状态更新:用户状态(如在线/离线)的更新通过Gossipsub v1.1传播

直接传输增强

在Berty/Wesh协议中,Gossipsub v1.1还增强了直接传输功能:

  • 服务发现:通过Gossipsub v1.1发现附近的设备和服务
  • 连接建立:使用Gossipsub v1.1建立和维护设备间的直接连接
  • 数据传输:在直接连接建立后,通过Gossipsub v1.1传输数据
Go
// 在Berty/Wesh协议中使用Gossipsub的简化示例
import (
    "context"
    
    pubsub "github.com/libp2p/go-libp2p-pubsub"
    "github.com/libp2p/go-libp2p-core/host"
)

// 初始化Gossipsub
func setupGossipsub(ctx context.Context, h host.Host) (*pubsub.PubSub, error) {
    // 创建Gossipsub实例
    gossipSub, err := pubsub.NewGossipSub(ctx, h, 
        pubsub.WithMessageSignature(true),
        pubsub.WithStrictSignatureVerification(true),
        pubsub.WithPeerScore(
            // 配置评分参数
            &pubsub.PeerScoreParams{
                // 评分参数配置
            },
            // 评分阈值配置
            &pubsub.PeerScoreThresholds{
                // 阈值配置
            },
        ),
    )
    
    if err != nil {
        return nil, err
    }
    
    return gossipSub, nil
}

// 加入主题并订阅消息
func joinTopic(ctx context.Context, ps *pubsub.PubSub, topicName string) (*pubsub.Topic, *pubsub.Subscription, error) {
    // 加入主题
    topic, err := ps.Join(topicName)
    if err != nil {
        return nil, nil, err
    }
    
    // 订阅主题
    sub, err := topic.Subscribe()
    if err != nil {
        return nil, nil, err
    }
    
    return topic, sub, nil
}
注意:在Berty/Wesh协议中,所有通过Gossipsub v1.1传播的消息都是加密的。只有拥有正确密钥的节点才能解密消息,这确保了通信的机密性和完整性。Gossipsub v1.1本身不提供加密功能,加密是在应用层实现的。

trending_up Gossipsub的未来发展

作为libp2p生态系统的重要组成部分,Gossipsub仍在不断发展和改进。以下是一些可能的发展方向,这些发展将进一步增强Gossipsub在Berty/Wesh协议中的应用。

性能优化

性能是Gossipsub持续关注的重点,未来的优化可能包括:

  • 更高效的消息传播算法:研究和实现更高效的消息传播算法,减少冗余流量
  • 自适应mesh调整:根据网络条件动态调整mesh结构,优化消息传播路径
  • 并行处理:利用多核处理器的优势,实现消息的并行处理
  • 缓存优化:优化消息缓存机制,减少内存使用

安全性增强

安全性是Gossipsub v1.1的核心关注点,未来的增强可能包括:

  • 更先进的评分机制:引入机器学习等技术,实现更智能的节点评分
  • 隐私保护:增强对节点身份和通信内容的保护
  • 抗量子计算:研究抗量子计算的加密算法,应对未来的安全挑战
  • 形式化验证:通过形式化验证确保协议的安全性和正确性

功能扩展

Gossipsub的功能扩展可能包括:

  • 消息优先级:实现更精细的消息优先级机制,确保重要消息优先传递
  • 服务质量(QoS):支持不同级别的服务质量,满足不同应用的需求
  • 跨协议互操作:支持与其他pubsub协议的互操作,扩大应用范围
  • 可编程性:提供可编程接口,允许开发者自定义消息处理逻辑

生态系统发展

Gossipsub的生态系统发展可能包括:

  • 更多语言支持:支持更多编程语言,扩大应用范围
  • 更好的开发工具:提供更好的开发工具,简化开发流程
  • 更多应用案例:探索更多应用场景,促进技术成熟
  • 社区建设:建设活跃的社区,促进知识共享和协作
注意:Gossipsub的发展将直接影响Berty/Wesh协议的未来。随着Gossipsub的改进,Berty/Wesh协议也将受益于更好的性能、更强的安全性和更丰富的功能。反过来,Berty/Wesh协议的使用也将为Gossipsub提供宝贵的反馈和用例,促进其进一步发展。
✨步子哥 (steper) #3
09-25 02:12
Gossipsub协议的Go语言实现

Gossipsub协议的Go语言实现

深入解析 libp2p/go-libp2p-pubsub 库

code 库概述

目前最主流、最权威的 Gossipsub 协议 Go 语言实现是 github.com/libp2p/go-libp2p-pubsub。这是由 Protocol Labs 维护的官方库,属于 libp2p 项目的一部分,提供了完整的 Gossipsub 协议实现。

该库支持多个版本的 Gossipsub 协议:

  • Gossipsub v1.0:基础版,提供了高效的 pubsub 功能
  • Gossipsub v1.1:增强安全性版本,增加了对女巫攻击、日蚀攻击和垃圾信息攻击的防御机制
  • Gossipsub v2.0:实验性优化版本,旨在减少重复消息,提高网络效率
注意:在 Berty/Wesh 协议中,主要使用 Gossipsub v1.1 版本,因为它在效率和安全性之间取得了良好的平衡,特别适合去中心化通信场景。

download 安装与配置

安装方式

使用 Go 模块系统安装 go-libp2p-pubsub 库非常简单:

Bash
go get github.com/libp2p/go-libp2p-pubsub

基本配置

在项目中导入并初始化 Gossipsub 实例:

Go
import (
    "context"
    "github.com/libp2p/go-libp2p"
    pubsub "github.com/libp2p/go-libp2p-pubsub"
)

// 创建 libp2p 主机
ctx := context.Background()
host, err := libp2p.New()
if err != nil {
    panic(err)
}

// 创建 Gossipsub 实例
ps, err := pubsub.NewGossipSub(ctx, host)
if err != nil {
    panic(err)
}

高级配置

go-libp2p-pubsub 提供了丰富的配置选项,可以根据应用需求进行定制:

Go
// 创建带有高级配置的 Gossipsub 实例
ps, err := pubsub.NewGossipSub(ctx, host,
    // 启用消息签名
    pubsub.WithMessageSignature(true),
    
    // 启用严格签名验证
    pubsub.WithStrictSignatureVerification(true),
    
    // 配置对等节点评分机制
    pubsub.WithPeerScore(
        &pubsub.PeerScoreParams{
            // 评分参数配置
            AppSpecificScore: func(p peer.ID) float64 {
                // 自定义评分函数
                return 0
            },
            AppSpecificWeight: 1.0,
            IPColocationFactorWeight: -35.31,
            IPColocationFactorThreshold: 10,
            BehaviourPenaltyWeight: -15.92,
            BehaviourPenaltyThreshold: 6,
            DecayInterval: 12 * time.Second,
            DecayToZero: 0.01,
            RetainScore: 10 * time.Minute,
        },
        &pubsub.PeerScoreThresholds{
            // 评分阈值配置
            GossipThreshold: -100,
            PublishThreshold: -200,
            GraylistThreshold: -400,
            AcceptPXThreshold: 100,
            OpportunisticGraftThreshold: 5,
        },
    ),
    
    // 配置其他选项
    pubsub.WithFloodPublish(true),
    pubsub.WithSeenMessagesTTL(2*time.Minute),
)
注意:高级配置选项需要根据具体应用场景进行调整。不正确的配置可能导致性能下降或安全风险。在生产环境中使用前,建议进行充分测试。

integration_instructions 使用示例

基本发布/订阅

以下是一个简单的发布/订阅示例,展示了如何使用 go-libp2p-pubsub 实现基本的 pubsub 功能:

Go
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/libp2p/go-libp2p"
    "github.com/libp2p/go-libp2p-core/peer"
    pubsub "github.com/libp2p/go-libp2p-pubsub"
)

func main() {
    ctx := context.Background()
    
    // 创建 libp2p 主机
    host, err := libp2p.New()
    if err != nil {
        panic(err)
    }
    
    // 创建 Gossipsub 实例
    ps, err := pubsub.NewGossipSub(ctx, host)
    if err != nil {
        panic(err)
    }
    
    // 定义主题名称
    topicName := "example-topic"
    
    // 加入主题
    topic, err := ps.Join(topicName)
    if err != nil {
        panic(err)
    }
    
    // 订阅主题
    sub, err := topic.Subscribe()
    if err != nil {
        panic(err)
    }
    
    // 启动消息处理协程
    go handleMessages(sub)
    
    // 发布消息
    publishMessages(ctx, topic)
    
    // 等待程序退出
    select {}
}

// 处理接收到的消息
func handleMessages(sub *pubsub.Subscription) {
    for {
        msg, err := sub.Next(context.Background())
        if err != nil {
            fmt.Println("Error reading from subscription:", err)
            continue
        }
        
        fmt.Printf("Received message from %s: %s\n", msg.ReceivedFrom, string(msg.Data))
    }
}

// 发布消息
func publishMessages(ctx context.Context, topic *pubsub.Topic) {
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()
    
    for i := 0; ; i++ {
        select {
        case <-ticker.C:
            message := fmt.Sprintf("Message #%d", i)
            err := topic.Publish(ctx, []byte(message))
            if err != nil {
                fmt.Println("Error publishing message:", err)
            } else {
                fmt.Println("Published message:", message)
            }
        case <-ctx.Done():
            return
        }
    }
}

在 Berty/Wesh 协议中的使用

在 Berty/Wesh 协议中,go-libp2p-pubsub 被用于实现群组通信和设备同步。以下是一个简化的示例,展示了如何在 Berty/Wesh 协议中使用 Gossipsub:

Go
package berty

import (
    "context"
    "crypto/rand"
    "encoding/hex"
    
    "github.com/libp2p/go-libp2p-core/crypto"
    "github.com/libp2p/go-libp2p-core/peer"
    pubsub "github.com/libp2p/go-libp2p-pubsub"
)

// Group 表示一个 Berty/Wesh 群组
type Group struct {
    ID        string
    SecretKey  crypto.PrivKey
    PublicKey crypto.PubKey
    Topic     *pubsub.Topic
    Sub       *pubsub.Subscription
    PubSub    *pubsub.PubSub
}

// NewGroup 创建一个新的群组
func NewGroup(ctx context.Context, ps *pubsub.PubSub) (*Group, error) {
    // 生成群组密钥对
    privKey, pubKey, err := crypto.GenerateEd25519Key(rand.Reader)
    if err != nil {
        return nil, err
    }
    
    // 生成群组ID
    pubKeyBytes, err := pubKey.Raw()
    if err != nil {
        return nil, err
    }
    groupID := hex.EncodeToString(pubKeyBytes)
    
    // 加入主题
    topicName := "/berty/group/" + groupID
    topic, err := ps.Join(topicName)
    if err != nil {
        return nil, err
    }
    
    // 订阅主题
    sub, err := topic.Subscribe()
    if err != nil {
        return nil, err
    }
    
    return &Group{
        ID:        groupID,
        SecretKey:  privKey,
        PublicKey: pubKey,
        Topic:     topic,
        Sub:       sub,
        PubSub:    ps,
    }, nil
}

// SendMessage 向群组发送消息
func (g *Group) SendMessage(ctx context.Context, message []byte) error {
    // 在实际应用中,这里应该对消息进行加密
    // 加密后的消息 = Encrypt(message, groupPublicKey)
    
    // 发布消息
    return g.Topic.Publish(ctx, message)
}

// ReceiveMessages 处理群组消息
func (g *Group) ReceiveMessages(ctx context.Context, handler func(peer.ID, []byte)) {
    go func() {
        for {
            msg, err := g.Sub.Next(ctx)
            if err != nil {
                continue
            }
            
            // 在实际应用中,这里应该对消息进行解密
            // 原始消息 = Decrypt(msg.Data, groupSecretKey)
            
            handler(msg.ReceivedFrom, msg.Data)
        }
    }()
}
注意:以上示例是 Berty/Wesh 协议中使用 Gossipsub 的简化版本。实际实现中,还需要考虑消息加密、签名验证、身份管理等多个方面。

verified 特性支持

go-libp2p-pubsub 提供了丰富的特性支持,使其成为构建去中心化应用的理想选择:

security 安全特性

  • 消息签名与验证
  • 对等节点评分机制
  • 防御女巫攻击
  • 防御日蚀攻击
  • 防御垃圾信息攻击

speed 性能特性

  • 高效的消息传播
  • 优化的网络拓扑
  • 自适应 mesh 调整
  • 消息缓存与去重
  • 并行处理支持

settings 功能特性

  • 主题订阅/发布
  • 对等节点发现
  • 事件追踪与调试
  • 自定义评分策略
  • 消息过滤与验证

integration_instructions 集成特性

  • 支持 DHT 发现节点
  • 可穿越 NAT
  • 与其他 libp2p 组件集成
  • 支持多种传输协议
  • 可扩展的插件系统

消息签名与验证

go-libp2p-pubsub 支持消息签名与验证,确保消息的完整性和来源的真实性:

Go
// 启用消息签名
ps, err := pubsub.NewGossipSub(ctx, host,
    pubsub.WithMessageSignature(true),
    pubsub.WithStrictSignatureVerification(true),
)

// 发布签名消息
err = topic.Publish(ctx, []byte("Hello, world!"),
    pubsub.WithSignMessage(true), // 启用消息签名
)

// 验证接收到的消息
msg, err := sub.Next(ctx)
if err != nil {
    return err
}

// 验证消息签名
valid, err := msg.Verify()
if err != nil {
    return err
}

if !valid {
    // 消息签名验证失败
    return fmt.Errorf("invalid message signature")
}

对等节点评分机制

go-libp2p-pubsub 实现了对等节点评分机制,用于防御女巫攻击和垃圾信息攻击:

Go
// 配置对等节点评分机制
ps, err := pubsub.NewGossipSub(ctx, host,
    pubsub.WithPeerScore(
        &pubsub.PeerScoreParams{
            // 应用特定评分函数
            AppSpecificScore: func(p peer.ID) float64 {
                // 根据对等节点的行为返回评分
                // 例如,可以根据历史交互、信誉等因素评分
                return 0
            },
            AppSpecificWeight: 1.0,
            
            // IP 共置惩罚
            IPColocationFactorWeight: -35.31,
            IPColocationFactorThreshold: 10,
            
            // 行为惩罚
            BehaviourPenaltyWeight: -15.92,
            BehaviourPenaltyThreshold: 6,
            
            // 评分衰减参数
            DecayInterval: 12 * time.Second,
            DecayToZero: 0.01,
            RetainScore: 10 * time.Minute,
        },
        &pubsub.PeerScoreThresholds{
            // 评分阈值
            GossipThreshold: -100,
            PublishThreshold: -200,
            GraylistThreshold: -400,
            AcceptPXThreshold: 100,
            OpportunisticGraftThreshold: 5,
        },
    ),
)
注意:对等节点评分机制是防御女巫攻击和垃圾信息攻击的关键。不正确的配置可能导致正常节点被错误地惩罚,或者恶意节点逃脱惩罚。建议根据具体应用场景调整评分参数。

menu_book 文档与资源

官方文档

go-libp2p-pubsub 的官方文档提供了详细的 API 参考和使用指南:

社区资源

除了官方文档,还有许多社区资源可以帮助学习和使用 go-libp2p-pubsub:

相关项目

以下是一些使用 go-libp2p-pubsub 的知名项目,可以作为参考:

提示:学习 go-libp2p-pubsub 的最佳方式是阅读官方文档和示例代码,然后尝试构建简单的应用。遇到问题时,可以在社区资源中寻求帮助。参与社区讨论也是提高技能的好方法。

build 最佳实践与性能优化

最佳实践

使用 go-libp2p-pubsub 时,遵循以下最佳实践可以提高应用的稳定性和性能:

  • 合理配置评分参数:根据应用场景调整对等节点评分参数,平衡安全性和可用性
  • 启用消息签名:在生产环境中启用消息签名和验证,确保消息的完整性和来源的真实性
  • 处理错误和异常:正确处理订阅和发布操作中的错误,避免应用崩溃
  • 限制消息大小:限制消息大小,防止恶意节点发送超大消息导致资源耗尽
  • 使用上下文管理:使用 context.Context 管理操作生命周期,支持优雅关闭

性能优化

以下是一些性能优化技巧,可以提高 go-libp2p-pubsub 的性能:

Go
// 1. 调整 mesh 参数
ps, err := pubsub.NewGossipSub(ctx, host,
    // 设置 mesh 中的目标连接数
    pubsub.WithDegree(10),
    pubsub.WithDegreeLow(5),
    pubsub.WithDegreeHigh(15),
    
    // 设置 fanout 中的目标连接数
    pubsub.WithFanoutTTL(60*time.Second),
    
    // 设置消息缓存时间
    pubsub.WithSeenMessagesTTL(2*time.Minute),
)

// 2. 使用连接池
// go-libp2p-pubsub 内部使用连接池管理连接,可以通过调整连接池参数优化性能
host, err := libp2p.New(
    libp2p.ConnectionManager(lowWater, highWater, gracePeriod),
)

// 3. 限制消息处理速率
// 使用速率限制器控制消息处理速率,防止资源耗尽
limiter := rate.NewLimiter(rate.Limit(1000), 100) // 每秒最多处理1000条消息,突发100条

go func() {
    for {
        msg, err := sub.Next(ctx)
        if err != nil {
            continue
        }
        
        // 等待速率限制器允许
        if err := limiter.Wait(ctx); err != nil {
            continue
        }
        
        // 处理消息
        handleMessage(msg)
    }
}()

// 4. 并行处理消息
// 使用工作池并行处理消息,提高吞吐量
workerCount := runtime.NumCPU()
messageCh := make(chan *pubsub.Message, 100)

// 启动工作池
for i := 0; i < workerCount; i++ {
    go func() {
        for msg := range messageCh {
            handleMessage(msg)
        }
    }()
}

// 从订阅中读取消息并发送到工作池
go func() {
    for {
        msg, err := sub.Next(ctx)
        if err != nil {
            continue
        }
        
        select {
        case messageCh <- msg:
        default:
            // 工作池已满,丢弃消息
        }
    }
}()

调试与监控

go-libp2p-pubsub 提供了丰富的调试和监控功能,可以帮助开发者诊断问题:

Go
// 1. 启用事件追踪
ps, err := pubsub.NewGossipSub(ctx, host,
    pubsub.WithEventTracer(true),
    pubsub.WithTracer(&pubsub.JSONTracer{
        Writer: os.Stdout, // 将事件输出到标准输出
    }),
)

// 2. 监控对等节点评分
// 定期检查对等节点评分,识别异常节点
go func() {
    ticker := time.NewTicker(1 * time.Minute)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            // 获取所有对等节点的评分
            scores := ps.Score()
            
            // 输出评分最低的节点
            var lowestScorePeer peer.ID
            var lowestScore float64 = math.MaxFloat64
            
            for p, score := range scores {
                if score < lowestScore {
                    lowestScore = score
                    lowestScorePeer = p
                }
            }
            
            if lowestScorePeer != "" {
                log.Printf("Lowest score peer: %s, score: %f", lowestScorePeer, lowestScore)
            }
        case <-ctx.Done():
            return
        }
    }
}()

// 3. 监控主题统计信息
// 定期检查主题统计信息,了解网络状况
go func() {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            // 获取主题统计信息
            stats := topic.Stats()
            
            log.Printf("Topic stats: %+v", stats)
        case <-ctx.Done():
            return
        }
    }
}()
注意:调试和监控功能在生产环境中应谨慎使用,因为它们可能会产生大量日志数据,影响性能。建议在生产环境中只启用必要的监控,并设置适当的日志级别。