Relation ONE IM 介绍

Relation ONE IM是一个去中心化的隐私聊天协议。

Relation ONE IM真正实现了去中心化和隐私性。

它主要有四部分组成(见图 1-1)

  • Client: 是一个前端应用程序,帮助用户加密/解密消息、发送消息至Proxy。并且需要调用Relation Protocol的标准接口来进行好友和群组管理。

  • Proxy: 是一个简单的后端程序,仅用于接收Client发来的消息,存储消息至Arweave并且推送至消息接收者。

  • Decentralized Storage(Arweave): 用于永久化存储加密的消息。

  • 合约:一系列符合Relation Protocol的合约(Relation Name Service、Follow、DAO),用于存储用户信息、Follow信息、DAO信息。

1. 消息发送主要流程

流程见图 1-1

  1. 发消息的用户使用钱包连接至Client

  2. 在Client端使用Lit Protocol对消息进行加密

  3. Client将加密消息发生至Proxy

  4. Proxy将消息保存至永久化存储

  5. Proxy通过websocket将消息推送至需要接收消息的Client

  6. 收消息用户在Client端使用Lit Protocol对消息进行解密

2. 身份

每一个以太坊的公私钥对就是一个身份。可以使用MetaMask等钱包来连接Client。

以太坊地址相当于用户的id,聊天的时候在消息体中指定接收人的地址,Proxy会将消息推送至目标地址登录的Client。

私钥用于对消息体进行签名,保证了消息是由该地址发出去的,并且不能被任何人修改。

该身份也用于登录Lit Protocol,用来在Client中加密或解密消息。

该身份需要在Relation Name Service注册一个SBT Token作为隐私聊天的准入条件。

2.1. Relation Name Service

Relation Name Service是由Relation部署的一个符合Relation Protocol的Name Service标准的合约。

Relation Name Service作为隐私聊天的准入条件,使用隐私聊天之前需要在Relation Name Service合约注册,用户将会获得一个SBT Token。

在聊天时,从Relation Name Service合约中获取对方的Name和头像等信息,可以更方便的我们找到和认出好友。

Relation Name Service合约主要提供以下接口:

  • 注册:register(address owner, string calldata name, bool reverseRecord) external returns (uint)

  • 设置头像:setProfileURI(string memory profileURI) external

  • 获取用户名: nameOf(address addr) external view returns (string memory)

  • 获取头像:profileURI(address addr) external view returns (string memory)

3. Client

Client是一个前端应用程序,可以是web应用、Chrome插件、桌面应用程序,移动应用程序等,在Relation ONE中是一个Chrome 插件

用户在登录到Client后,Client会通过WebSocket连接至Proxy。当有人给自己发消息时,Proxy主动会推送消息到Client,Client使用Lit对消息解密后就可以看到明文了。

用户在发消息前,Client按照消息结构组装消息体,使用Lit对消息体进行加密后再发送至Proxy。

由于消息是在Client进行加密和解密的,保证了数据的隐私性。

Client还需通过调用合约来管理好友群组

4. Proxy

Relation ONE Proxy是一个后端应用程序,提供了一套REST Api接口(用于接收Client发送的消息、修改状态等)和一个WebSocket端口(用于推送消息至Client)。

Proxy会将Client发过来的消息存储至Arweave,由于所有消息都记录在去中心化存储中,因此Client并不是强依赖于某一个Proxy。即使Proxy宕机,Client在更换Proxy后,消息数据依然不会丢失。

4.1. 接收/发送消息

Client通过REST Api接口将(加密)消息发送至Proxy,Proxy会根据永久化存储策略将消息存储至Arweave,同时通过WebSocket将消息推送至接收者登录的Client上。

4.2. 状态保存

未读消息数、屏蔽群消息、置顶消息等状态信息,由Proxy记录在自己的数据库中,用户连接到不同的Proxy时未读消息数不共享。

4.3. 消息永久化存储

Proxy会将消息打包上传至Arweave,每个消息包里包含1条或多条消息。每个包上传至Arweave时,都会打一个tag,指向前一个交易的Hash。

永久化存储消息有三个策略:

  1. 每12小时将消息打包上传到Arweave上。

  2. 如果12小时内没有消息,将不会打包,然后重新计时12小时。

  3. 如果没有到定时任务触发的时候,消息的大小超过了100MB,立即将消息打包上传到Arweave。

4.4. REST接口

Proxy需提供以下接口

接口描述Http Method接口地址Request BodyResponse Body

发送消息

POST

/message/send

查询聊天内容

GET

/message/list?limit=${limit}&offset=${offset}

标记为已读

POST

/message/read?messageId=${messageId}

未读消息数

GET

/message/unreadCount

Long

聊天列表

GET

/chats/list?limit=${limit}&offset=${offset}

5. 关系管理

5.1. 好友管理

使用符合Relation Protocol的Follow合约来管理好友。

每个身份都会通过FollowRegister创建一个Follow合约(见图 1-4),当AlicefollowBob的时候,只用调用Bob的Follow合约follow()方法即可。

  • 好友列表:通过FollowRegister遍历出所有的Follow合约,再遍历出每个用户的follow信息。

  • 关注:调用对方合约的follow()方法。

  • 取关:调用对方合约的unfollow()方法。

5.2. 群组管理

使用符合Relation Protocol的DAO合约来管理群聊,该合约中所有持有Semantic SBT的地址即为群成员。

Client通过调用标准的DAO接口来进行成员管理。

例如:发起人通过DaoRegister创建一个DAO合约。通过isFreeJoin()方法设置群聊是否可以自由加入。通过调用join()方法来加入群聊,通过调用remove(address addr)方法离开群聊。(见图1-5)

  • 创建群聊:在Client端调用DaoRegister合约的deployDaoContract(address owner,string memory name) external returns (uint256)方法创建一个新的群聊(见图 1-5)。

  • 加入群聊:如果isFreeJoin()设置为True。可以通过join()方法自由加入;如果为False需要管理员通过addMember(address[] memory addr) external来添加成员。

  • 离开群聊:自己调用remove(address addr)方法离开群聊。或者管理员调用remove(address addr)方法移除某个成员。

  • 设置群公告:群公告由群主向群里发送一条特殊的消息,发送时将kind设置为3

  • 群名称:通过name()方法获取群名称。

  • 群头像:通过daoURI()方法获取群头像。

  • 群成员:合约里所有持有SBT Token的地址即为群成员。

6. 聊天

6.1. 点对点聊天

可以与好友进行聊天,也可以给非好友的以太坊地址发送消息。具体步骤:

Client(A)使用Lit对消息的payload进行加密(accessCondition为接收方的以太坊地址),receiver设置为对方的以太坊地址,按照消息结构组装成消息体后发送至Proxy。Proxy会将消息推送至接收方地址登录的Client(B)。

6.2. 群聊

发送群消息时,用户A登录到Client(A)设置Lit对消息的payload进行加密(accessCondition为群成员可见),receiver设置为群聊的合约地址,按照消息结构组装成消息体后发送至Proxy。Proxy会将消息推送至每个群成员所在的Client(C)、Client(D)、Client(E)……

6.3. 加密/解密

Client会集成Lit Protocol SDK,每条消息都在发送者的CLient加密,在接收者的Client解密。

如图 1-6,Alice要给Bob发送一条加密消息:Hi

  1. Alice的Client会使用Lit SDK在本地对Hi进行加密,得到消息密文encryptedString和秘钥symmetricKey

const messageToEncrypt = "Hi";
// 1. Encryption
const { encryptedString, symmetricKey } = await LitJsSdk.encryptString(messageToEncrypt);
  1. Alice的Client设置accessControlConditions为Bob的地址可见,并将accessControlConditions和秘钥symmetricKey上传到Lit的节点

const accessControlConditions = [
    {
      "contractAddress": "",
      "standardContractType": "",
      "chain": "ethereum",
      "method": "",
      "parameters": [
        ":userAddress",
      ],
      "returnValueTest": {
        "comparator": "=",
        //Bob's wallet address
        "value": "0x50e2dac5e78B5905CB09495547452cEE64426db2"
      }
    }
];
// 2. Saving the Encrypted Content to the Lit Nodes
const encryptedSymmetricKey = await litNodeClient.saveEncryptionKey({
  accessControlConditions,
  symmetricKey,
  authSig,
  chain,
});
  1. Alice的Client组装Message,并发送至Proxy。

const message = {
  ...
  "sender": "Alice's wallet address",
  "receiver": "Bob's wallet address",
  "payload": "${encryptedString}",
  "encryptedSymmetricKey": "${encryptedSymmetricKey}",
  ...
}
  1. Proxy将消息转发至Bob的Client。

  2. Bob接收到Proxy推送的Message后,去Lit节点校验通过后拿到加密秘钥_symmetricKey

// 5. Decrypt it
// <String> toDecrypt
const toDecrypt = LitJsSdk.uint8arrayToString(encryptedSymmetricKey, 'base16');

// <Uint8Array(32)> _symmetricKey 
const _symmetricKey = await litNodeClient.getEncryptionKey({
    accessControlConditions,
    toDecrypt,
    chain,
    authSig
})
  1. Bob在本地对消息解密:

// <String> decryptedString
let decryptedString;

try{
    // 得到原文:Hi
    decryptedString = await LitJsSdk.decryptString(
        encryptedString,
        _symmetricKey
    );
}catch(e){
    console.log(e);
}

7. 消息定义

7.1. Message结构

{
  "id": "${messageId}",
  "encryptedBy": "Lit",
  "sender": "${senderWalletAddress}",
  "receiver": "${receiverWalletAddress/receiverDaoContractAddress}",
  "timestamp": ${the message create timestamp in millisecond},
  "kind": ${kind},
  "tags": [
    ["quoteId", "${messageId of the quoted message}"],
    ["mentionedUsers", "${The wallet address of the mentioned user}"]
  ],
  "type": "${TEXT/CARD/NFT/IMAGE/EMOJI/BATCH_TRANSFER/ANNOUNCEMENT}",
  "payload": "${message encrypted by Lit}",
  "encryptedSymmetricKey": "${a hex string that LIT will use to decrypt payload as long as you satisfy the conditions}",
  "delegationMessage": "授权 ${地址} 代理签名,创建时间:${}, 过期时间:${}",
  "delegationSig": "${私钥对${delegationMessage}的签名}",
  "sig": "${授权私钥对${messageId}的签名}"
}
  • messageId生成方法: 组装下面格式的json字符串,计算其Keccak256

[
  <encryptedBy>,
  <sender>,
  <receiver>,
  <timestamp>,
  <kind>,
  <tags>,
  <payload>,
  <delegationMessage>,
  <delegationSig>
]
  • kind

    • 0: p2p message。

    • 1: group message。

    • 2: dao message。

    • 3: announcement。

    • 4: hide message。

  • Session Key:

在客户端生成一个公私钥对作为Session Key,把Session Key的公钥组装成delegationMessage,使用用户的私钥对delegationMessage进行签名,得到delegationSig

用户发消息时,使用Session Key的私钥对消息进行签名,并且带上delegationMessagedelegationSig,通过这个delegationChain,就能验证消息是由用户本人签名的。

  • sig生成方法:

通过Session Key的私钥对messageId进行签名即得到sig

7.2. 消息包

1条或多条Message组成的数组称为一个消息包(Messages),它是以下格式:

Messages = [Message,Message,...] 

7.3. Payload

  • 文本消息

{
  //  ...
  "type": "TEXT",
  "payload": "Hi"
  //  ...
}
  • 卡片消息

{
  //  ...
  "type": "CARD",
  "payload": "type:href;action:shareFavorite;p1(Text):https%3A%2F%2Fspace.id%2Fvoyage,p2(Text):,p3(Text):SPACE ID,p4(Text):https%3A%2F%2Fspace.id%2Ffavicon.ico"
  //type:${href};action:${shareFavorite};p1(Text):${URL},p2(Text):,p3(Text):${title},p4(Text):${icon}
  //  ...
}
  • NFT

{
  //  ...
  "type": "NFT",
  "payload": "type:image;action:nftImage;p1(Text):https%3A%2F%2F3fypb-gqaaa-aaaag-aaedq-cai.ic1.io%2Fnft%2Fpolygon%2F0x4b5c095380e8e5f016a70a28af570c3bca93b811%2F25908.jpeg,p2(Text):polygon"
  //type:image;action:nftImage;p1(Text):${imageURL},p2(Text):${chainName}
  //  ...
}
  • 本地图片

{
  //  ...
  "type": "IMAGE",
  "payload": "type:image;action:localImage;p1(Text):https%3A%2F%2F3fypb-gqaaa-aaaag-aaedq-cai.ic1.io%2Fim%2Fimage%2Fnymb5-kqaaa-aaaaj-4thiq-cai%2F2f3537d1a2474f2fb92cb5f7de2ed70a.png,p2(Text):im/image/nymb5-kqaaa-aaaaj-4thiq-cai/2f3537d1a2474f2fb92cb5f7de2ed70a.png"
  //type:image;action:localImage;p1(Text):${imageURL},p2(Text):${s3ObjectKey}
  //  ...
}
  • 表情包

{
  //  ...
  "type": "EMOJI",
  "payload": "type:image;action:customEmoji;p1(Text):https%3A%2F%2F3fypb-gqaaa-aaaag-aaedq-cai.ic1.io%2Fim%2Femoji%2Flov6e-qqaaa-aaaaj-xykxq-cai%2Fac1dc2bf6bad48aeb1a502c0a28ee942.jpg,p2(Text):im/emoji/lov6e-qqaaa-aaaaj-xykxq-cai/ac1dc2bf6bad48aeb1a502c0a28ee942.jpg"
  //type:image;action:customEmoji;p1(Text):${imageURL},p2(Text):${s3ObjectKey}
  //  ...
}
  • 群转账

{
  //  ...
  "type": "BATCH_TRANSFER",
  "payload": "0xd84ed2b4deacbad8a568747fea45f3fc7f2d204595101f99f43006712f3ff290::USDT::20.0000::0x969f85053b44d7eCE8108094420Aba45149b7A3a::qzoo5-ryaaa-aaaaj-xs6ma-cai"
  //payload: 0xbaf915e778b044dbf62974d17bd2ab8875bccce8bd02c40dfc241d2893755851::USDT::5.0000::aukee-3yaaa-aaaah-qc3ya-cai::4xtgo-viaaa-aaaaj-aavyq-cai,ms5ob-iqaaa-aaaaj-aa2za-cai,kppoo-ozrgz-rtkn3-cgqyq-cai,meohx-szymu-ytezr-ugiza-cai,oazqd-ndeg4-ytqyj-zga2q-cai
  //payload: 0x71de31f73f34fc9af20f7ce3b35ec4e56bf25af476f99cb376779761c2fe91fa::USDT::1.0000::aukee-3yaaa-aaaah-qc3ya-cai::7zshb-ryaaa-aaaaj-aag3a-cai
  //  ...
} 
  • 公告

{
  //  ...
  "type": "TEXT",
  "payload": "It's an announcement"
  //  ...
}

7.4. Chat结构

{
  "id": "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5",
  "name": "test-group",
  "avatar": "",
  "channelType": "GROUP",
  "unreadMessageCount": 0,
  "type": "TEXT",
  "payload": "the message payload",
  "lastPostAt": "1665219499000"
}

8. 消息存储

使用永久存储Arweave来存储加密消息,保证了数据的可靠性和去中心化。

MessageProxy打包成Messages,上传至rweave上。每个Transaction都被打上一个tag,指向前一个Transaction的Hash。可以方便的根据最新Hash找到所有历史消息。

Messages = [Message, Message, Message]

9. 合约

NameDocsMumbaiGoerlizkSync Era Testnet

Relation Name Service

0x04C23dDfE813d8D208bc8120b73F8EF30f423850

0xe4AA1f1E5be0A4E7F80CAC26C2Db1611C3E70c41

-

FollowRegister

0xA0cECa90d7A2033f5162dc73391cdB72272456E7

-

0x734dE4f1b3a1c0619ECB80e3a676a6cFabe72Adc

DaoRegister

0x153C3eF051C7665c9A9c1a718bF12bC8EE6b5115

-

0x7229Dc0117E8484D61137B7304b02b163beC912c

Last updated