Мы запилили 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)
Спасибо что дочитали.