集隐私通信、移动钱包、链上朋友圈和红包功能一体的社交应用ComingChat

ComingChat是在Sui上构建的社交平台,为用户提供加密聊天功能,并利用Signal加密协议安全发送数字资产。

集隐私通信、移动钱包、链上朋友圈和红包功能一体的社交应用ComingChat

ComingChat是在Sui网络上构建的功能众多的去中心化社交平台,其中加密聊天功能为用户提供了安全的沟通方式。该功能利用了Signal加密协议,这是一种在Signal、WhatsApp和Skype等应用中广受欢迎的开源软件协议。

ComingChat在Sui上提供了全面的生活体验。它将ChatGPT增强的生产力、社交互动与全链钱包相结合,为用户提供了广泛的功能和应用。

除了其可信和可验证的特性外,ComingChat选择Signal协议作为其加密通信的基础,这与Sui网络的账户系统完全兼容。

聊天模式

ComingChat的聊天模块为用户提供了三种不同的模式,以满足其不同的隐私和安全需求。这些模式包括:

  • 私密聊天:此模式在两个用户之间提供一对一的通信。Signal协议对这些对话进行加密,保持其机密性。
  • 加密群聊:此模式允许群体进行通信,同时保持较高水平的安全性和隐私性。与私密聊天类似,Signal协议对加密群聊中的消息进行加密。
  • 非加密群聊:在此模式中,消息没有加密,但该群组支持最多1000人,易于设置和管理,但对消息内容的保护水平较低。

Signal协议的实施发生在ComingChat的聊天基础设施中,该基础设施使用了Decentralized Moments(Dmens)协议。该协议支持常见的聊天功能,如发帖、点赞和回复,还集成了红包功能,使用户可以向彼此发送tokens或NFTs。

实施加密

ComingChat的加密聊天功能基于Signal协议的双棘轮算法,该算法提供端到端加密,确保用户之间的通信安全。

构建加密聊天功能需要以下步骤:

  1. 实现双棘轮算法,为消息启用端到端加密,确保只有预期的接收者能够解密和阅读这些消息。
  2. 将加密的消息存储在Sui网络上,以确保数据的完整性和安全性。
  3. 允许用户交换公钥以进行安全通信,并使用扩展三椭圆曲线Diffie-Hellman(X3DH)密钥协议建立安全会话。
ComingChat聊天架构图
ComingChat的加密聊天架构允许用户选择私密聊天,发送方将发送给对方的消息进行加密,接收方接收后解密这些消息,以便可以阅读它们。

双棘轮算法

双棘轮算法被两个参与方用于基于共享密钥交换加密消息。通常,这些参与方会使用诸如X3DH之类的密钥协议来协商共享密钥。在达成协议后,这些参与方将使用双棘轮来发送和接收加密消息。

参与方为每个双棘轮消息派生新的密钥,以便无法从后来的密钥中计算出之前的密钥。参与方还会将Diffie-Hellman公共值附加到其消息中。Diffie-Hellman计算的结果被混入派生密钥中,以便无法从之前的密钥中计算出后来的密钥。这些特性在某种程度上保护了在某方密钥遭受威胁的情况下之前或之后的加密消息。

增强隐私的红包功能

“红包”这一安全在线交易术语源来自于中国节假日和特殊场合向他人赠送现金的实际做法,通常使用红色信封。ComingChat使用这个术语来表示加密聊天功能中的安全消息数据包。

Sui Move 的语言变体允许我们在开发红包合约时与 Core 存在一些独特的差异。尤其是, 需要对交易状态同步返回和入口函数参数进行编程,前者有助于对聊天信息进行排序,后者需要对象 ID。Move Move

  • Sui红包不具有增量的红包ID redPacketId,但它们具有红包对象ID redPacket ObjectId,因为Sui的数据模型要求所有对象都有一个ID。
  • 在发出打开/关闭交易后,Sui红包无需异步获取状态;红包状态可以根据交易返回数据中的事件进行更新。
  • 服务器端节点需要异步获取用户创建的红包的创建状态,因为该用户的节点可能与服务器端节点不同。

下面的核心合同代码显示了我们如何考虑Move 上Sui 的独特能力和要求。核心合同代码 Config 对象包含了发送者、接收者和管理员的地址,同时定义了交易费用。 RedPacketInfo 对象包括coin额度、发送的token以及接收者的地址。 RedPacketEvent 对象用于跟踪token额度。

// Copyright 2022-2023 ComingChat Authors. Licensed under Apache-2.0 License.
module rp::red_packet {
	…
	struct Config has key {
	id: UID, 
	admin: address, 
	beneficiary: address, 
	owner: address, 
	count: u64, 
	fees: Bag
}

struct RedPacketInfo<phantom CoinType> has key,store {
	id: UID, 
	remain_coin: Balance<CoinType>, 
	remain count: u64, 
	beneficiary: address
}

// Event emitted when created/opened/closed a red packet.
struct RedPacketEvent has copy, drop {
	id: ID, 
	event_type: u8, 
	remain_count: u64, 
	remain balance: u64

// One-Time-Witness for the module.
struct RED_PACKET has drop {}

fun init (
	otw: RED_PACKET, 
	ct: &mut TxContext
) {
		…
}

public entry fun create<CoinType> (
	config: &mut Config,
	coins: vector<Coin<CoinType>>, 
	count: u64, 
	total_balance: u64, 
	ctx: &mut TxContext
) {
	// 1. check args
	…

}

public entry fun open<CoinType> (
	info: &mut RedPacketInfo<CoinType>, 
	lucky_accounts: vector<address>, 
	balances: vector<u64>, 
	ct: &mut TXContext
) {
…
}

public entry fun close<CoinType> (
	info: RedPacketInfo CoinType>, 
	ctx: &mut TxContext
) {
…
}

public entry fun withdraw<CoinType> (
	config: Smut Config, 
	ctx: &mut TxContext
) {
…
}

在提交Sui红包的打开/关闭交易时,交易结果将直接在响应中获得,并更新数据库和缓存状态,无需从浏览器中异步获取交易状态。

在用户创建红包后,系统会异步查询创建交易的状态,并基于事件获取红包数据,包括金额、数量和红包ID。

ComingChat后端架构图
在ComingChat app中,将“创建红包”事件发送给Sui网络作为智能合约,并根据接收者的操作处理该合约的状态。

红包状态变更

在ComingChat发送Sui交易后,它直接获取交易结果,无需进行异步任务来更新打开/关闭状态,因此:

  • 在触发打开条件后,管理员调用打开交易并根据打开交易状态直接将记录设置为成功。
  • 在触发关闭条件后,管理员调用关闭交易并根据关闭交易状态将其直接设置为已关闭或关闭失败。

如果打开/关闭交易失败,需要记录该失败以防止交易重试,这会产生额外的gas费。

红包交易流程图
ComingChat会监控交易状态,要么关闭成功的交易,要么确认失败的状态并停止自动重试,以避免不必要的gas费。

Dmens协议

ComingChat在Sui上构建了Decentralized Moments(Dmens)协议作为SDK,提供用户身份识别、内容共享和价值共享等功能。该协议使用Sui来管理用户数据和内容,并使用SUI支付gas费。用户可以创建个人资料、发布内容、关注其他用户并与他们互动。该协议还允许用户将其创建的内容转化为独特的NFT,并针对不同的情境发行不同类型的NFT。

这些情境包括:

  1. KOL向粉丝发行有价值的NFT,以增加粉丝的参与、忠诚度和收入。
  2. 项目发行权益证明NFT,用于运营活动,以增加用户参与度、忠诚度,并促进生态系统的发展。
  3. 内容创作者通过付费NFT模型将其内容变现,实现更好的内容变现和更多的收入。
  4. 艺术家将他们的数字艺术品转化为NFT,并将其出售给收藏家或投资者。

Dmens架构

在ComingChat中设计了Dmens以支持公共和私密聊天功能。总体而言,当用户创建一条消息(可以是新消息或回复消息)时,它会在Sui上启动ComingChat的聊天功能。ComingChat使用GraphQL来查询链下存储的用户资料,并使用Dmens索引器模块确保消息被正确排序。

Sui和dmens分度器之间的流程图
Dmens架构使用了Sui、GraphQL和Dmens索引器来处理用户操作,例如创建个人资料或发布新消息。在这里,GraphQL充当了存储资料的数据库查询工具。

在下面的智能合约代码中,定义了 Chat 对象,该对象允许用户发布消息、转发其他消息、点赞消息以及其他典型的聊天功能。

//chat.move
module chat::chat {
	/// Sui Chat NFT (i.e., a post, retweet, like, chat message etc).
	struct Chat has key, store {
		id: UID,
		// The ID of the chat app.
		app_id: address,
		// Post's text.
		text: String,
		// Set if referencing an another object (i.e., due to a Like, Retweet, Reply etc).
		// We allow referencing any object type, not only Chat NFTs.
		ref id: Option<address>,
		// app-specific metadata. We do not enforce a metadata format and delegate this to app layer.
		metadata: vector<u8>,
	}

	/// Simple Chat.text getter.
	public fun text (chat: &Chat): String {
		chat.text
	}

	/// Mint (post) a Chat object.
	fun post internal (
		app_id: address, 
		text: vector<u8>, 
		ref_id: Option<address>, 
		metadata: vector<u8>, 
		ctx: &mut TxContext,
	) {
		…
	}


	/// Mint (post) a Chat object without referencing another object.
	public entry fun post (
		app_identifier: address, 
		text: vector<u8>, 
		metadata: vector<u8>, 
		ctx: &mut IxContext,
	) {
		post_internal(app_identifier, text, option::none (), metadata, ctx);
	}

	/// Mint (post) a Chat object and reference another object (i.e., to simulate retweet, reply, like, attach).
	/// TODO: Using address as app_identifier & 'ref_identifier type, because we cannot pass 'ID' to entry functions. Using vector<u8>' for text instead of String' for the same reason.
	public entry fun post_with_ref 
		app_identifier: address, 
		text: vector<u8>, 
		ref_identifier: address, 
		metadata: vector<u8>, 
		ctx: &mut TxContext,
	) {
		post_internal(app_identifier, text, some (ref_identifier), metadata, ctx);
	}

	/// Burn a Chat object.
	public entry fun burn (chat: Chat) {
		let Chat { id, app_id: _, text: _ , ref_id: _, metadata: _ } = chat;
		object::delete (id);
	}
}

上面代码片段中的Chat结构表示聊天消息。它有包括 ref_id在内的ID字段,允许聊天消息转发、回复或点赞另一条消息,这在代码中表示为一个对象。实际的聊天消息是结构中的文本字符串。

  post internal 函数创建了一条新的聊天消息,因为它是用于在模块内部调用的,它被标记为”internal”。由此函数创建的对象具有ID字段和实际消息的文本字符串。 ref_id 允许它引用另一个对象,例如作为对现有聊天的回复或点赞。

类似地,还有 post public entry 函数,它调用 post internal 来创建新的聊天。然而,它将 ref_id 置为none,因为此函数旨在供人们发起新的聊天。

Dmens索引器结构设计

ComingChat的加密聊天模块使用Redis(一个开源的流式数据库)作为链下存储。它处理消息队列,确保聊天消息以有序方式显示。

对于Redis流,首先初始化客户端。

func (r *BaseRedisCustomer) InitCustomer ( ) error {
	...
}

Redis将数据存储在内存中,因此需要适当和定期地修剪队列数据。在下面的代码片段中,定义了一个修剪队列的函数。

func (r *BaseRedisCustomer) TrimQueueList (ct context.Context) {
	r.wg.Add (1)
	defer funct( ) { 
		r.wg.Done ( )
	} ( )
	for {
		select {
		case <-ctx.Done ( ) :
			return
	}
	…
}

下面的代码片段中的监听器代码通过合约地址过滤事务。下面的函数是将传统的链下存储与Web3 app集成的很好示例。

func (1 *ListenLastIxByCycle) cycleFetchTransactionNum(ct context. Context, tx chan<-TxDigest) {
	var (
		cursor *types.TransactionDigest
	)
	…
}

下面的代码片段将每个新的事务摘要推送到一个队列中,名为 transaction-analyze.

rpip.Evalsha (
	r.Context ()、 
	r.script["pushNewT×DigestToStream" ]、
	[ ]string{topic, fmt.Sprintf(PrefixChainLastDigest, chain, packageId) }、
	"数据"、 
	preDigest、 
	digest、

	)

ComingChat使用Lua服务器端脚本将多个Redis命令组合在一起,确保事务摘要的连续性。

local lastDigest = redis.call( 'get', KEYS[2])
本地 result = false
if (lastDigest ~= false) and (lastDigest == ARG[2])) or ((lastDigest == false) and (ARGVI 21 == ')) then
	redis.call('xadd', KEYS[1], '*', ARGV[1], ARGV[3]) 
	redis.call ('set', KEYS[2], ARGV[3])
结束
返回 true

索引器在接收到用户提交的每条消息时经历以下过程:

定时

  1. 查询所有消费者的故障消息,从queue_message表中查询。
  2. 根据主题重新消费。如果工作超过重新消费的阈值,必须停止并手动访问。

消费者

  • 分析交易
    1.查询受此交易影响的对象,但不包括货币对象
    2.将受影响的对象推送到队列中
  • 对象更新
    1.获取对象详细信息,并在object_list表上创建或更新它
    2.过滤个人资料对象
    3.过滤调用ChatGPT的推文
  • 解码个人资料对象
    1.解码个人资料对象
  • GPT回复
    1.获取Dmens推文内容,并使用正则表达式匹配GPT机器人地址

结束语

加密聊天在Signal、WhatsApp和微信等应用中已经被证明非常受欢迎。ComingChat中的这一功能与基于Sui社交平台的现有构想非常契合。加密为用户提供了隐私保护,确保不良行为者无法窃听他们的对话。加密聊天还与Sui的功能相吻合,为用户的在线生活提供了一个独立且安全的平台。

ComingChat的技术实现利用了值得信赖的Signal协议的双棘轮算法,展示了现有技术如何应用于Web3平台。本文介绍的Dmens、红包和聊天机器人等高级功能的加入,可以在ComingChat提供了丰富的用户体验。