Zisty 技术说明:加密与隐私
更新日期:2026 年 11 月 2 日 · 对应 iOS 版 5.8.x · English below
这份说明写给懂一点技术、想知道"凭什么相信 Zisty"的人。它把加密设计、服务器能看到什么、数据存在哪里、还有哪些做不到的地方一次讲清楚。每一项都按当前代码核对过;看不到的地方我们直说看不到,做不到的地方也直说做不到。
1. 你需要信任什么 · 2. 身份 · 3. 加好友与核验 · 4. 一对一消息 · 5. 信箱与外层封装 · 6. 群聊 · 7. 图片、视频和文件 · 8. 通话 · 9. 附近直连 · 10. 手机上的数据 · 11. 文件库 · 12. 换手机、恢复包、远程擦除 · 13. 服务器知道什么 · 14. 网络连接 · 15. 算法一览 · 16. 已知局限 · 17. 怎么核对
1. 你需要信任什么
- 不需要信任服务器能保守内容:服务器只拿到密文,没有任何一把能解开消息的钥匙。
- 需要信任 App Store 上的应用确实按本文所说的方式工作。代码目前不公开,也没有做过第三方审计;第 17 节说明你能独立核对哪些部分。
- 需要信任 iOS 本身:钥匙串、文件加密、随机数和加密库(Apple CryptoKit)都来自系统。应用不带任何第三方加密库、统计 SDK 或广告 SDK。
- 服务器运营方(我们)和网络中间方能看到一部分元数据,比如投递时间、大小和 IP。第 13、14 节逐项列出。
2. 身份
第一次打开时,手机在本机生成三把长期密钥:
- X25519:密钥协商;
- Ed25519:签名;
- ML-KEM-768:抗量子的密钥封装(FIPS 203),钥匙串里存的是它的 64 字节种子。
私钥存在系统钥匙串,属性为 AfterFirstUnlockThisDeviceOnly:不进 iCloud 钥匙串,不随备份到别的设备。
你的指纹 = SHA-256(Ed25519 公钥) 的前 128 位,用 32 个十六进制字符表示。指纹就是你在 Zisty 里的全部身份:没有账号、手机号、邮箱,也不读通讯录。
任何名片都带着这三把公钥。收到名片时会检查指纹是否等于其签名公钥的哈希,对不上的名片一律丢弃。
3. 加好友与核验
- 当面添加:两台手机经附近直连交换签名名片,双方在屏幕上比对 30 位安全码,然后确认。安全码 = SHA-256(按字节排序后两把签名公钥的拼接),取前 12 字节,每 2 字节转成一组 5 位数字,共约 96 位。这样加的好友标为"已当面核验"。
- 邀请链接:链接形如
https://zisty.app/i#XXXX-XXXX。8 位邀请码只在 24 小时内有效,用一次就作废。邀请码放在 # 后面,浏览器不会把它发给网页服务器。对方的应用凭码向信箱服务器取回你的签名名片,再把好友请求投到你的公共收件箱。公共收件箱的令牌 = SHA-256("projecty-public-inbox-v1|" + 指纹),好友请求只签名、不加密,因为这时双方还没有共同密钥。
- 邀请链接这一步要信任服务器:理论上,一台恶意服务器可以把邀请码对应到别人的名片。所以远程添加的好友会标为"远程添加 · 未当面核验"。见面时在聊天资料里比对安全码,就能排除这种可能。
- 只有打开"允许远程添加好友"时,你的名片才放在服务器上(昵称、三把公钥、应用版本,不含头像)。关掉就撤回。
4. 一对一消息
4.1 配对密钥
两人之间的静态配对密钥:
HKDF-SHA256(X25519(我的私钥, 对方公钥), salt = 两个指纹排序后拼接, info = "projecty-pairwise-v1"),输出 32 字节。
它是双棘轮的起点,也是外层封装、信箱令牌和附近直连令牌的来源(见第 5、9 节)。
4.2 双棘轮
消息内容走 Signal 式的双棘轮(Double Ratchet)。和 Signal 不同的是没有预密钥服务器:
- 指纹较大的一方作发起方,较小的一方作响应方;
- 响应方的静态 X25519 公钥充当它的第一把棘轮公钥。
各个 KDF:
- 根链:
HKDF-SHA256(salt = 根密钥, IKM = DH 输出, info = "projecty-ratchet-v1"),输出 64 字节,前 32 字节是新根密钥,后 32 字节是新链密钥;
- 消息链:消息密钥 =
HMAC-SHA256(链密钥, 0x01),下一个链密钥 = HMAC-SHA256(链密钥, 0x02);
- 每条消息用 ChaCha20-Poly1305 加密。头部(当前棘轮公钥、上一条链的长度、序号、版本号和所有 ML-KEM 字段)整个作为附加认证数据,删掉或替换任何一个字段都会解密失败。
乱序和重放:
- 乱序到达的消息最多为每条链跳过 200 个消息密钥,最多保存 400 个;
- 重放的旧消息会被认出来,直接丢弃,不会让会话重建。
用过的消息密钥立即删除。会话状态以加密文件保存在本机(见第 10 节)。
4.3 抗量子
双方都是 3.1 以上的 iOS 版时,会话是 X25519 + ML-KEM-768 的混合会话:
- 会话根:发起方向响应方名片里的 ML-KEM 公钥封装一个秘密,
根 = HKDF-SHA256(配对密钥 ‖ ML-KEM 秘密, info = "projecty-ratchet-pq-v1")。1088 字节的封装密文附在发起方的消息头里,直到看到对方第一次回复为止。
- 每一步棘轮(消息头 v4):每条消息都带上发送方当前的临时 ML-KEM 公钥。每次 DH 棘轮时,再向对方最新的 ML-KEM 公钥封装一个新秘密,和 DH 输出一起进入根 KDF:
HKDF-SHA256(salt = 根密钥, IKM = DH ‖ ML-KEM, info = "projecty-ratchet-v2")。
这样,即使将来有人用量子计算机破解了 X25519,也只能拿到过去录下的流量,解不开里面的内容。会话被攻破后能自行恢复安全,这一点在量子攻击者面前同样成立。
消息头的版本号也受认证保护:把 v4 降级成 v3、或把 v3 降级成经典 v2 的消息都会被拒收。
4.4 前向保密从什么时候开始
对方第一次回复之后,每把消息密钥都来自双方的临时密钥,用完即删。这之后,即使有人拿到双方的长期私钥,也解不开之前的消息。
在对方第一次回复之前,你发出的消息只由对方的静态 X25519 和 ML-KEM 密钥保护。所以如果对方的长期私钥日后泄露,这几条消息就能被解开。这一点和不用预密钥的设计取舍有关,我们如实写在这里。
4.5 哪些内容走棘轮
对方是抗量子版本时,除下面这几类外,一切加密内容都走棘轮:消息、动态、名片、回执、通话信令等。
- 附件分块和分块请求:它们本身已经用逐文件密钥加密,而文件密钥在棘轮加密的消息里;
- 附近握手、确认回执、唤醒;
- 好友请求和接受;
- 会话重置。
每个信封的密文都附有发送方的 Ed25519 签名。
5. 信箱与外层封装
两个人不在附近时,消息经信箱服务器中转。
- 外层封装:整个信封(包括发送方指纹、消息种类、签名和已经加密的内容)再用两人的静态配对密钥做一次 ChaCha20-Poly1305。服务器看不到是谁发的,也看不到是哪种消息。
- 收件令牌:
HKDF-SHA256(配对密钥, info = "projecty-mailbox-v1|" + 收件人指纹),32 字节。每对好友每个方向各有一个令牌,没有配对密钥就无法把它和任何人对上。
- 收件人在自己已认证的连接上登记"我收哪些令牌"。发件人投递时不认证:一个只写着令牌和密文的 HTTPS POST。
- 收件人确认收到后,服务器立即删除这封信。没取走的最多保留 5 天。收件人从没连过服务器的,最多保留 2 天。
- 同一份状态(比如资料更新、已读回执)有多份在排队时,服务器只留最新的一份。分组依据是发件方给的一个不透明标签。
外层封装用的是静态配对密钥,本身没有前向保密。它只用来藏住信封的元数据,真正的内容在里面一层,由棘轮保护。
6. 群聊
- 每个群一把 256 位的群密钥,由群主生成。群密钥和完整成员名单经每个人各自的一对一通道(棘轮,抗量子)发给成员。
- 群消息用群密钥做 ChaCha20-Poly1305,附发送者的 Ed25519 签名,再按每个成员分别做第 5 节的外层封装后投递。
- 换钥匙的时机:
- 加人、移出成员、有人退群,都立即换钥匙;被移出或退群的人读不到之后的消息;
- 另外,群主每 24 小时换一次(在下一条消息发出时);
- 每台手机把收到超过 6 天的旧群密钥删掉(服务器上的信最多留 5 天,更早的信不可能再到)。
群聊没有逐条的棘轮。同一把群密钥有效期间的所有群消息,用这把密钥都能解开。所以群聊的前向保密是以天计的,不像一对一那样逐条。
7. 图片、视频和文件
- 小图片和语音直接在消息里,和消息一起加密。
- 大文件:
- 每个文件随机生成一把 256 位文件密钥,按 1 MB 分块,每块 ChaCha20-Poly1305;
- nonce 是块序号,附加认证数据是
blobID/序号,块不能被调换顺序或换到别的文件里;
- 文件密钥、文件名、缩略图和访问码都在加密的消息清单里。
- 服务器上存的是:
- 密文块;
- 上传者指纹、大小、块数、收件人数;
- 访问码的 SHA-256。
下载只凭访问码,不需要身份。所有收件人都取过后,服务器立即删除这个文件;最多保留 5 天。
- 发出的照片去掉位置和其他元数据,只保留方向信息。
8. 语音和视频通话
- 呼叫和接听信令走一对一棘轮(抗量子),双方各带一个 32 字节随机秘密。
- 密钥派生:
- 主密钥 =
HKDF-SHA256(呼叫方秘密 ‖ 接听方秘密, info = "projecty-call-v1");
- 由主密钥再派生:每个方向各一把音频密钥和一把视频密钥,外加一个 16 字节的路由令牌。
- 每个音视频包用 ChaCha20-Poly1305 加密。nonce 是方向加序号,两个方向的 nonce 永不重复;包头作为附加认证数据。
- 线路选择:
- 在同一局域网时,媒体直接走 UDP;
- 否则经服务器的 UDP 转发;
- 再不行就走已有的加密连接。
服务器只看到路由令牌、包的大小和时间。
- 通话不会出现在系统"电话"应用的最近通话里。来电推送见第 13 节。
9. 附近直连
- 在附近时,消息经 Bonjour 发现、走 TCP(普通 Wi-Fi 或点对点 Wi-Fi)直接送达,不经过服务器。蓝牙只用来握手和唤醒对方,不传内容。
- 广播里不出现名字:服务名是随机串,TXT 记录里是每个好友每天一个 8 字节令牌。令牌 =
HKDF-SHA256(配对密钥, info = "bonjour" + 日期 + 广播者指纹)。只有好友认得出是谁;同一对好友两端广播的令牌也不同,旁人没法把两台手机配成一对。
- 消息内容和经信箱时一样,用棘轮加密并签名。
直连链路本身没有再套 TLS,信封也不做第 5 节的外层封装。同一网络里能接入这条连接的设备,能看到:
· 双方的指纹;
· 握手名片里的昵称;
· 消息的种类和大小。
它看不到内容。
10. 手机上的数据
- 应用写到磁盘的一切都加密,包括聊天记录、好友、会话状态、附件和缓存:
- 算法是 ChaCha20-Poly1305,用一把 256 位存储密钥;
- 存储密钥只存在本机钥匙串(
ThisDeviceOnly);
- 数据目录排除在 iCloud 和电脑备份之外。
为了锁屏时也能收消息,文件保护级别是"首次解锁后可读"。
- 保留时间:
- 每个聊天可以设阅后即焚(30 秒)、1、3、5 或 9 小时,从看到的那一刻算起,两边手机同时执行;
- 没看到的消息最多留 5 天;
- 动态可以设 1、3、5 或 9 天。
- 应用锁用系统的面容 ID,应用接触不到生物特征数据。
- 手机上的诊断日志不含消息内容。只有你自己点"上传日志"时才会发出,服务器保留 30 天。
11. 文件库
- 文件库有自己的一对 X25519 密钥:
- 私钥用你的文件库密码派生出的密钥包裹:PBKDF2-HMAC-SHA256,600,000 次迭代,16 字节随机盐,再做 ChaCha20-Poly1305;
- 密码至少 6 位。
- 每个文件随机生成一把文件密钥。文件密钥和文件名、缩略图等元数据一起,用 HPKE(X25519、HKDF-SHA256、ChaCha20-Poly1305)封给文件库的公钥。
- 所以往文件库里存东西不需要密码,但列出或打开都需要。忘了密码,没有任何找回办法,我们也没有。
12. 换手机、恢复包、远程擦除
- 换手机:
- 新手机显示一个二维码,里面是一把临时 X25519 公钥和一个一次性令牌;
- 旧手机扫码后在本地网络直连,把全部数据按块加密传过去,密钥由两把临时密钥派生;
- 新手机确认导入后,旧手机自行清空。同一个身份只会在一台手机上。
- 恢复包:用于手机丢了或坏了的情况。
- 里面有身份密钥、群密钥、好友和群名单,不含消息;
- 用你设的密码加密:PBKDF2-HMAC-SHA256 600,000 次,再做 ChaCha20-Poly1305,文件头作为附加认证数据;密码至少 8 位;
- 存放在哪由你决定。拿到恢复包加密码的人就能冒充你,请像保管密码一样保管它。
- 远程擦除:只有你事先在和他的聊天资料里允许过的好友,才能让你的手机清空。
13. 服务器知道什么
| 服务器保存的 | 保留多久 |
| 每台手机的指纹、签名公钥、首次和最近连接时间 | 60 天没连接就删除 |
| 推送令牌(普通推送和来电推送) | 直到更换或撤回 |
| 每台手机登记的收件令牌列表 | 随好友变动更新 |
| 待取的信:收件人指纹、令牌、密文、大小、时间 | 取走即删,最多 5 天 |
| 附件密文块、上传者指纹、大小、访问码哈希 | 全部收件人取完即删,最多 5 天 |
| 名片(只在开启"允许远程添加好友"时):昵称、公钥、应用版本 | 关闭即撤回 |
| 邀请码 → 指纹 | 24 小时,用一次即作废 |
| 你自己上传的诊断日志(不含内容) | 30 天 |
服务器不知道:
- 消息、文件、通话的内容;
- 文件名、群名、头像;
- 投递者是谁(投递不认证);
- 投递的是哪种消息。
服务器也不会把"谁在线"告诉任何人。
服务器能推断的:
- 一个人登记了多少个收件令牌,大致就是他有多少个好友;
- 每封信的大小和时间;
- 连接和投递时的 IP 地址(只在内存里用来限流,不写进数据库或日志)。
一个刻意观察的运营者,结合 IP 和时间,可以猜测哪两台手机在互相发消息。这是所有中转式通讯都有的元数据问题,我们没有用混淆流量的办法去解决它。
- 推送:
- 普通推送的文字固定为"你有新消息",来电为"你有来电",不含发送者或内容;
- 来电推送还带上收件令牌,以及(不太大时)加密的来电信令;
- Apple 的推送服务能看到推送令牌和推送时间。
- 服务器日志不含内容和 IP,身份一般显示为"-"。
- 备份和备机:
- 每天一份数据库备份,备份前删掉所有信件正文,保留 7 天;
- 东京的冷备机实时接收主库副本,其中包括待取的密文;主库删除的数据,在备机副本里最多再留约 2 小时。
14. 网络连接
- 主服务器在新加坡,有两个入口:
mail.projecty.vip:经 Cloudflare,证书由系统按常规方式校验;
- 直连 IP:只认服务器那把固定的公钥(公钥固定),不信任任何证书机构。
哪条通就用哪条。东京的备机平时只回答"standby",主机出事时才启用。
- 走域名入口时,Cloudflare 会解开 TLS,它看到的和服务器看到的一样:令牌、密文、大小、IP,看不到内容。
- 连上后,服务器发一个 32 字节随机挑战,手机用 Ed25519 身份密钥签名完成认证。附件上传凭认证后发的会话令牌。
15. 算法一览
| 用途 | 算法 |
| 密钥协商 | X25519;ML-KEM-768(混合) |
| 签名 | Ed25519 |
| 对称加密 | ChaCha20-Poly1305(256 位密钥) |
| 密钥派生 | HKDF-SHA256;棘轮消息链 HMAC-SHA256 |
| 文件库 | HPKE(X25519 · HKDF-SHA256 · ChaCha20-Poly1305) |
| 由密码派生密钥 | PBKDF2-HMAC-SHA256,600,000 次 |
| 指纹、安全码、令牌 | SHA-256 / HKDF-SHA256 |
| 实现 | Apple CryptoKit、CommonCrypto(PBKDF2),无第三方加密库 |
16. 已知局限
- 代码不公开,没有第三方安全审计。
- 元数据:服务器和 Cloudflare 能看到大小、时间和 IP,见第 13、14 节。
- 远程添加的好友,在当面比对安全码之前,要信任服务器给的名片是真的。
- 群聊没有逐条棘轮,前向保密以天计。
- 对方第一次回复之前的一对一消息,只由对方的长期密钥保护。
- 附近直连没有外层封装,同一网络里的设备能看到指纹、昵称和消息大小。
- 保留时间是双方应用之间的约定,挡不住截屏、拍屏或被改过的客户端。
- 文件库和恢复包的安全取决于密码强度。PBKDF2 能拖慢猜测,但挡不住弱密码。
- 安卓版只做维护:没有抗量子会话,不支持通话。和安卓好友之间是经典的 X25519 双棘轮。
- 手机本身被解锁并控制时,加密帮不了忙。
17. 怎么核对
- 测试向量:protocol-vectors.json。它是 iOS 和安卓两端每次测试都要逐字节对上的协议向量,用的全是由固定种子生成的测试密钥。里面有:
- 配对密钥、安全码、信箱令牌;
- 签名、AEAD 盒子、附件分块;
- 经典、混合和逐步抗量子三种双棘轮会话的每一步状态;
- 信封的编码。
你可以用自己的实现按本文的公式算一遍,结果应当完全一致。
- 在应用里:聊天资料页能看到和对方的安全码,可以当面比对;同一页也显示这个会话是否已经是抗量子、前向保密的状态。
- 抓包:你只会看到发往上面两个入口的 TLS 流量,以及附近直连的密文。
- 发现问题请写信到
report@projecty.vip。
Zisty Security Overview
Last updated: November 2, 2026 · iOS 5.8.x
This is for people with some technical background who want to know why they should trust Zisty. It covers the cryptography, what the server can and cannot see, where data lives, and what we do not protect against. Every item was checked against the current code.
1. What you have to trust
- Not the server for content: it only ever holds ciphertext and no key that opens a message.
- The App Store app to do what this page says. The code is not public and has not been audited by a third party; section 17 lists what you can check yourself.
- iOS: the Keychain, file protection, randomness and Apple CryptoKit. The app ships no third-party crypto, analytics or advertising code.
- We (the operator) and the network see some metadata, such as times, sizes and IP addresses; sections 13 and 14 list it.
2. Identity
On first launch the phone makes three long-term keys:
- X25519 for key agreement;
- Ed25519 for signing;
- ML-KEM-768 (FIPS 203), stored as its 64-byte seed.
They live in the Keychain as AfterFirstUnlockThisDeviceOnly: they are never in iCloud Keychain and never in a backup restored elsewhere.
Your fingerprint is the first 128 bits of SHA-256(Ed25519 public key). That is your whole identity: no account, phone number, email or contact upload. A card whose fingerprint does not match its signing key is dropped.
3. Adding and verifying friends
- In person: signed cards are exchanged over the nearby link and both people compare a 30-digit safety number. It is SHA-256 over the two signing keys in byte order; the first 12 bytes give six 5-digit groups, about 96 bits. Such friends are marked as verified in person.
- Invite link (
https://zisty.app/i#XXXX-XXXX):
- the 8-character code is valid for 24 hours and works once;
- it sits after the
#, so browsers never send it to a web server;
- the friend's app redeems the code at the mailbox server for your signed card, then drops a signed (not encrypted) friend request in your public inbox. The inbox token is SHA-256("projecty-public-inbox-v1|" + fingerprint).
- The invite step trusts the server: a malicious server could hand out someone else's card for your code. Friends added this way are marked "added remotely, not verified in person". Comparing the safety number later rules this out.
- Your card (nickname, the three public keys, app version, no picture) is on the server only while "Allow remote friend requests" is on.
4. One-to-one messages
4.1 Pairwise key
HKDF-SHA256(X25519(mine, theirs), salt = both fingerprints sorted and joined, info = "projecty-pairwise-v1"), 32 bytes.
It seeds the ratchet, the outer seal, the mailbox tokens and the nearby tokens.
4.2 Double Ratchet
Content uses a Signal-style Double Ratchet without a prekey server:
- the larger fingerprint initiates;
- the responder's static X25519 key is its first ratchet key.
The KDFs:
- root KDF:
HKDF-SHA256(salt = root, IKM = DH, info = "projecty-ratchet-v1"), 64 bytes (the new root, then the new chain);
- chain KDF: message key =
HMAC-SHA256(chain, 0x01), next chain = HMAC-SHA256(chain, 0x02);
- messages are sealed with ChaCha20-Poly1305. The whole header (ratchet key, previous chain length, number, version and every ML-KEM field) is authenticated data.
Out-of-order and replayed messages:
- up to 200 skipped keys per chain, 400 stored;
- replays are recognised and dropped.
Used keys are deleted.
4.3 Post-quantum
Between iOS builds from 3.1 on, sessions are hybrid X25519 + ML-KEM-768:
- Root: the initiator encapsulates to the responder's card key, and
root = HKDF-SHA256(pairwise ‖ ML-KEM secret, info = "projecty-ratchet-pq-v1"). The 1088-byte ciphertext rides in the initiator's headers until the responder has replied.
- Every step (header v4): each message carries the sender's current ephemeral ML-KEM key. Each DH step also encapsulates a fresh secret to the peer's latest key, and both go into
HKDF-SHA256(salt = root, IKM = DH ‖ ML-KEM, info = "projecty-ratchet-v2").
Recorded traffic stays closed even if X25519 falls to a quantum computer, and post-compromise recovery holds against such an attacker too. Version downgrades (v4 → v3, v3 → v2) are rejected.
4.4 When forward secrecy starts
From the peer's first reply on, every message key comes from ephemeral keys and is deleted after use. Messages sent before that reply are protected only by the peer's static X25519 and ML-KEM keys; a later leak of those keys would expose them. This is the cost of having no prekey server.
4.5 What goes through the ratchet
For a post-quantum friend, everything sealed goes through the ratchet: messages, posts, profiles, receipts and call signalling. The exceptions are:
- attachment chunks and chunk requests (already sealed with a per-file key that arrives inside a ratcheted manifest);
- nearby handshakes, acks and wakes;
- friend requests and accepts;
- session resets.
Every envelope's ciphertext is signed with the sender's Ed25519 key.
5. Mailbox and outer seal
- Outer seal: the whole envelope (sender, kind, signature, inner ciphertext) is sealed again with the static pairwise key, so the server sees neither who sent it nor what kind it is.
- Mailbox token:
HKDF-SHA256(pairwise, info = "projecty-mailbox-v1|" + recipient fingerprint), one per friend and direction. It cannot be linked to anyone without the pairwise key.
- Recipients register their tokens on their authenticated connection. Deposits are unauthenticated: an HTTPS POST holding only a token and ciphertext.
- Mail is deleted as soon as the recipient acknowledges it, otherwise after 5 days (2 days if the recipient has never connected). Of several queued copies of the same state (a profile, a read receipt), only the newest is kept, grouped by an opaque tag.
The outer seal uses the static key and has no forward secrecy. It hides metadata only; the content inside is ratcheted.
6. Groups
- Each group has a 256-bit group key made by the owner. It is sent with the full roster to each member over that member's ratcheted (post-quantum) one-to-one channel.
- A group message is sealed with ChaCha20-Poly1305 under the group key, signed by its sender, then outer-sealed per member.
- Key rotation:
- at once when someone is added, removed or leaves; people removed or gone read nothing sent afterwards;
- daily, by the owner, on the next message;
- phones delete group keys older than 6 days.
Groups have no per-message ratchet: one key opens everything sent while it was current, so group forward secrecy is measured in days.
7. Pictures, videos and files
- Small pictures and voice clips travel inside the message.
- Large files:
- a random 256-bit file key, 1 MB chunks, ChaCha20-Poly1305 per chunk;
- the nonce is the chunk index and the authenticated data is
blobID/index, so chunks cannot be reordered or swapped;
- the key, file name, thumbnail and access key travel in the encrypted message.
- The server keeps:
- the ciphertext chunks;
- the uploader's fingerprint, the size and the recipient count;
- SHA-256 of the access key.
Downloads need only the access key, no identity. A file is deleted once every recipient has it, at most after 5 days.
- Photos lose their location and other metadata before sending; orientation is kept.
8. Calls
- Offer and answer go through the ratchet, each carrying a 32-byte random secret.
- Key derivation:
- master =
HKDF-SHA256(offer ‖ answer, info = "projecty-call-v1");
- from the master: per-direction audio and video keys and a 16-byte route token.
- Media packets are sealed with ChaCha20-Poly1305. The nonce is direction plus sequence number; the header is authenticated.
- Paths:
- direct UDP on the same local network;
- otherwise the server's UDP relay;
- failing that, the existing encrypted connection.
The server sees the route token, sizes and timing.
- Calls do not appear in the Phone app's recents.
9. Nearby link
- Nearby messages go over Bonjour and TCP (Wi-Fi or peer-to-peer Wi-Fi), with no server. Bluetooth carries only the handshake and wake-ups.
- Advertisements carry no name, only a daily 8-byte token per friend:
HKDF-SHA256(pairwise, info = "bonjour" + date + advertiser fingerprint). Only that friend can tell whose phone it is, and the two ends of one friendship advertise different tokens.
The link has no TLS and no outer seal. A device on the same network that joins the link can see:
· both fingerprints;
· the nickname in the handshake card;
· message kinds and sizes.
It cannot see content.
10. Data on the phone
- Everything the app writes is encrypted:
- ChaCha20-Poly1305 under a 256-bit storage key;
- the key is in the Keychain (
ThisDeviceOnly);
- the data directory is excluded from iCloud and computer backups.
Files are readable after the first unlock, so messages can arrive while the phone is locked.
- Retention:
- per chat: burn after reading (30 s), 1, 3, 5 or 9 hours from when a message is seen, on both phones;
- unseen messages: 5 days at most;
- posts: 1, 3, 5 or 9 days.
- App Lock uses Face ID; the app never sees biometric data.
- Device logs contain no message content. They leave the phone only when you tap upload, and the server keeps them for 30 days.
11. Vault
- The vault has its own X25519 key pair. The private key is wrapped with PBKDF2-HMAC-SHA256 (600,000 iterations, 16-byte salt) and ChaCha20-Poly1305. The password needs at least 6 characters.
- Each item has a random file key, sealed with its metadata (name, thumbnail) to the vault's public key using HPKE (X25519, HKDF-SHA256, ChaCha20-Poly1305).
- Adding needs no password; listing and opening do. There is no recovery without the password, for anyone.
12. Moving phones, recovery kit, remote wipe
- Move:
- the new phone shows a QR code with an ephemeral X25519 key and a one-time token;
- the old phone connects locally and streams everything, sealed chunk by chunk with a key derived from both ephemeral keys;
- after the import is confirmed, the old phone erases itself. An identity lives on one phone.
- Recovery kit:
- holds the identity keys, group keys, friends and groups, no messages;
- sealed with PBKDF2-HMAC-SHA256 (600,000 iterations) and ChaCha20-Poly1305; the passphrase needs at least 8 characters;
- you keep it wherever you like. Whoever has the kit and its passphrase can be you.
- Remote wipe works only from friends you allowed in advance (in that chat's info page).
13. What the server knows
| Stored | For how long |
| Each phone's fingerprint, signing key, first and last connection | Deleted after 60 days without a connection |
| Push tokens (messages and calls) | Until replaced or withdrawn |
| The mailbox tokens each phone registers | Updated as friends change |
| Waiting mail: recipient fingerprint, token, ciphertext, size, time | Until picked up, 5 days at most |
| Attachment chunks, uploader fingerprint, size, access-key hash | Until every recipient has them, 5 days at most |
| Card (only with remote requests on): nickname, public keys, app version | Until turned off |
| Invite code → fingerprint | 24 hours, single use |
| Diagnostic logs you upload (no content) | 30 days |
The server does not know:
- the content of messages, files or calls;
- file names, group names or pictures;
- who deposited a message (deposits are unauthenticated);
- what kind of message it is.
It never tells anyone who is online.
It can infer:
- roughly how many friends someone has, from the number of tokens they register;
- the size and time of each message;
- IP addresses at connection and deposit (held in memory for rate limiting, never written to the database or logs).
An operator watching closely could guess, from IPs and timing, which two phones talk to each other. Every relayed messenger has this metadata problem; we do not add cover traffic.
- Push:
- the text is fixed ("you have a new message" or "incoming call"), with no sender or content;
- a call push also carries the mailbox token and, when small enough, the encrypted offer;
- Apple sees the push token and the time.
- Server logs hold no content and no IP addresses; identities normally show as "-".
- Backups:
- a daily database copy with all mail bodies removed, kept 7 days;
- the Tokyo standby receives a live replica, including waiting ciphertext; data deleted on the primary can remain there for about 2 hours.
14. Network
- The primary server (Singapore) has two entrances:
mail.projecty.vip through Cloudflare, with the system's normal certificate check;
- a direct IP, trusted only through a pinned server key, with no certificate authority.
The standby in Tokyo answers "standby" until it is promoted.
- On the domain path, Cloudflare terminates TLS and sees what the server sees: tokens, ciphertext, sizes and IPs, never content.
- After connecting, the phone signs a 32-byte server challenge with its Ed25519 key. Uploads use the session token issued after that.
15. Algorithms
| Use | Algorithm |
| Key agreement | X25519; ML-KEM-768 (hybrid) |
| Signatures | Ed25519 |
| Symmetric encryption | ChaCha20-Poly1305 (256-bit keys) |
| Key derivation | HKDF-SHA256; HMAC-SHA256 message chains |
| Vault | HPKE (X25519 · HKDF-SHA256 · ChaCha20-Poly1305) |
| Password-based keys | PBKDF2-HMAC-SHA256, 600,000 iterations |
| Fingerprints, safety numbers, tokens | SHA-256 / HKDF-SHA256 |
| Implementation | Apple CryptoKit, CommonCrypto (PBKDF2); no third-party crypto |
16. Known limits
- The code is not public and has not had a third-party audit.
- Metadata: sizes, times and IPs are visible to the server and to Cloudflare.
- A friend added remotely relies on the server's card until you compare safety numbers.
- Groups have no per-message ratchet; forward secrecy is measured in days.
- One-to-one messages sent before the peer's first reply rest on the peer's long-term keys.
- The nearby link has no outer seal: devices on the same network can see fingerprints, nicknames and sizes.
- Retention is an agreement between the two apps; it cannot stop screenshots, photos of the screen or a modified client.
- The vault and the recovery kit are only as strong as their passwords.
- Android is in maintenance: no post-quantum sessions and no calls. Sessions with Android friends are classic X25519 Double Ratchet.
- Encryption does not help once someone controls your unlocked phone.
17. How to check
- Test vectors: protocol-vectors.json. Both the iOS and the Android test suites check these byte for byte. They use only test keys made from fixed seeds and cover:
- the pairwise key, safety number and mailbox tokens;
- signatures, AEAD boxes and attachment chunks;
- every step of a classic, a hybrid and a per-step post-quantum Double Ratchet session;
- the envelope encoding.
An independent implementation of the formulas above should match them exactly.
- In the app: the chat info page shows the safety number to compare in person, and whether the session is post-quantum and forward secret.
- On the wire: you will see only TLS to the two entrances above, and ciphertext on the nearby link.
- Problems:
report@projecty.vip.