Мы запилили iOS-мессенджер с UIN-ами как в ICQ, шифрованием уровня Signal и Bluetooth-меш-чатом. Без телефона, без почты. Открытая бета.

Втроём 18 месяцев пилили RCQ -iOS-мессенджер. Сегодня в открытой TestFlight-бете, исходники iOS открыли на GitHub под AGPL. И мы, кажется, немного перегнули с фичами.

С чего всё началось

У современных мессенджеров есть одна общая проблема -они хотят твой телефон. Telegram, WhatsApp, Signal -все требуют SMS-подтверждение. Это не приватно: номер сразу связывает тебя с реальной личностью, оператором, государством, рекламной сетью.

Нам захотелось мессенджер где регистрация -это придумать ник и получить UIN. Как в ICQ 2002. Никаких телефонов, никакой почты, никогда.

Так начался RCQ.

Криптография

В основе -libsignal от Signal Foundation. Та же криптобиблиотека что использует сам Signal: X3DH для key exchange, Double Ratchet для forward secrecy, Sealed Sender для скрытия отправителя от сервера.

Мы вшили libsignal как Swift Package в iOS-клиент с предсобранным xcframework. Чтобы это работало, пришлось патчить три вещи: отключить full-LTO (иначе линкер падал), убрать OPENSSL_SMALL флаг (несовместим с iOS), и снять -fembed-bitcode (Apple его deprecated, но libsignal всё ещё пытался использовать).

Sealed Sender работает поверх libsignal Stage 3, мы сделали так чтобы коэкзистировать с v=1 (raw ECIES, было до libsignal). На wire-формате есть поле `v` которое диспетчит между путями. Сервер видит ТОЛЬКО опэйковые блобы и UIN получателя -ни отправителя, ни содержимого. Никакой metadata leakage.

Один из самых неприятных багов: dual-decrypt hazard. У RCQ два таргета -основное приложение и Notification Service Extension (NSE), который декодирует push-нотификации в фоне чтобы показать предпросмотр. Оба шарят libsignal session SQLite через App Group.

Если NSE декодит сообщение для push, а потом основное приложение опять декодит то же сообщение когда пользователь открывает чат Double Ratchet проворачивается дважды, второй декрипт падает молча, сообщение исчезает из чата.

Решение: PushDecryptCache. NSE декодит, пишет plaintext в App Group файл-кэш, MessageService.ingest проверяет кэш перед вызовом crypto.decrypt, consume-on-read. Долго отлавливали потому что failure был silent -сообщение не появлялось без ошибок и логов.

Что выросло из «простого мессенджера»

Чаты: - 1-на-1 и групповые с E2EE - Голосовые сообщения, фотки, видео, ответы, реакции, истории - Disappearing messages с TTL на тред

Звонки: - 1-на-1 голос и видео через WebRTC + CallKit (нативно как обычный iOS звонок) - Audio Rooms -групповые голос/видео с mesh-роутингом до 8 человек, всё E2EE

Анонимные режимы: - **Random Chat** -случайные собеседники, как Chatroulette только E2EE и с reporting/safety controls - **Radio Chat** локальный mesh-чат через Bluetooth и Wi-Fi-Direct, работает БЕЗ интернета вообще. В метро, на даче без связи, между этажами в большом здании, на фестивалях. До 8 пиров, шифрование на эфемерных ключах - **People Nearby** -opt-in геолокация в радиусе километра, анонимные ники, локальный чат района

Экономика и косметика

В какой-то момент стало ясно: мессенджер без сетевого эффекта мёртв. Если у тебя пять контактов и никто из них не использует RCQ -ты тоже не будешь.

Чтобы создать retention без рекламы и без IAP, мы добавили внутреннюю экономику. Только виртуальная валюта -никаких реальных денег, никакого IAP, ничего нельзя купить за рубли.

Из лутбоксов выпадают: - Анимированные питомцы (10 видов разной редкости) - Пакеты смайликов (включая ностальгический Колобок) - Voice packs -звуки нотификаций для конкретных контактов

Питомец показывается рядом с твоим ником в чатах, играх, везде. Это твоя identity-cosmetic.

Прокачка питомцев -хардкорная Russian roulette. Шансы успеха по уровням: +1 это 100%, +2 это 90%, +3 это 80%, +4 это 65%, +5 это 50%, +6 это 35%, +7 это 25%, +8 это 15%, +9 это 8%. Провалил -питомец сжигается. Полностью. Окончательно.

P(common +0 → +9 жив) ≈ 0.0246%. То есть нужно примерно 4070 commons чтобы получить одного +9. +9 легендарка это статус-флекс по дизайну, не realistically achievable. Намеренный хардкор.

P2P торговля и маркетплейс: игроки могут торговать предметы и UIN-ы друг с другом, выставлять на маркете, или поучаствовать в часовых аукционах премиум коротких UIN-ов.

Игры

К моменту когда у нас была валюта, кошелёк и игроки -стало нечем заняться кроме чата. Мы добавили мини-игры:

- **Crash** -provably-fair multiplier-game. Все ставят, multiplier растёт, в случайный момент крашится. Кто кэшаутнул вовремя -забрал × multiplier. Кто не успел -потерял ставку. Multiplayer, видишь чужие ставки и кэшауты. - **Hi-Lo** -solo guess-the-next-card chain. - **Limbo** --single-shot multiplier-target. - **UIN Auction** -раз в час сервер выставляет премиум короткий UIN, игроки бьются ставками, soft-close в последние 30 секунд. - **Pet Hunt** -пассивный idle + 3 ежедневных voluntary-risk охоты. Снаряжённый питомец фармит жетоны и гемы 24/7, накопление capped at 24 часа. Можешь отправить питомца на охоту в одну из трёх зон: Forest (безопасно), Mountain (5% шанс смерти на провале), Cave (25%). Сильнее питомец -выше награда, ниже риск.

Всё на виртуальной валюте. Никаких IAP. Никакого crypto. Просто внутриигровые жетоны и гемы.

Почему open source

iOS-клиент: github.com/rcq-messenger/rcq-ios

AGPL-3.0 потому что libsignal сам под AGPL -линкуя его, наш клиент обязан быть AGPL-совместимым. Same path что и Signal.

Зачем открыли: чтобы любой мог проверить что в крипто-слое нет дыр. Это privacy-проект, доверие к нему = доверие к коду. Без open source это просто маркетинговое заявление.

Backend пока закрыт -там ничего критичного для приватности (он только опэйковые блобы передаёт благодаря sealed sender), но открывать планируем.

Стек

iOS: - Swift 5.9 + SwiftUI (iOS 16+) - libsignal v0.93.1 (vendored) - WebRTC для звонков и audio rooms - MultipeerConnectivity для Radio Chat - CallKit + PushKit + APNs для звонков и push - Программная CoreData (без .xcdatamodeld) для локального хранения сообщений

Backend: - FastAPI + SQLAlchemy async - PostgreSQL 16 (prod) / SQLite (dev) - Single-worker uvicorn (нужен для in-memory state) - Caddy reverse proxy с Let's Encrypt - coturn локально для WebRTC NAT traversal

Хост: DigitalOcean droplet, Frankfurt.

Что мы хотим от тестеров

Открытая бета сейчас -capped at 5000 тестеров. На текущем стеке (single-worker uvicorn) держит около 300-500 одновременных подключений комфортно. Нужен реальный нагрузочный тест.

Что нужно: 1. Найти что ломается. Любые баги, странности, edge case'ы. В приложении есть Bug Bounty surface -пишите там или DM 2. UX обратная связь. Что не очевидно, где запутались 3. Идеи. Если кажется что чего-то не хватает

Что не нужно: - Спам "Signal лучше" -мы знаем - Жалобы на gambling-flavor в играх -это намеренно, на виртуальной валюте, никто реальных денег не тратит - Просьбы добавить Android -да, скоро, не сейчас

Если хочешь подключиться шире

Если кому-то близка идея и хочется участвовать в проекте сверх просто тестера -фидбек, идеи, код, дизайн, UX research, локализации, что угодно -мы открыты к таким коллаборациям. Бус-фактор 1 это не то с чем мы хотим долго жить.

Пишите в комментариях, в issue на GitHub.

Ссылки

- Сайт: https://rcq.app - TestFlight: https://testflight.apple.com/join/GH4y15S5 - iOS source: https://github.com/rcq-messenger/rcq-ios (AGPL-3.0)

Спасибо что дочитали.

6
1
1
5 комментариев