Cercano
Мессенджер, в котором сервер не участник разговора. iOS и Android, свой крипто-слой, самостоятельный хостинг звонков.
Сообщения шифруются на устройстве ключом каждого чата и остаются зашифрованными в пути и в хранении. Звонки идут peer-to-peer через TURN-сервер, который проект держит сам, а не арендует у кого-то.
- Год
- 2026
- Роль
- Продуктовый дизайн, дизайн-система, фронтенд на React Native
- Стек
- React Native · Rust (Axum) · PostgreSQL · WebRTC
- Статус
- В разработке, iOS и Android
Проблема
У шифрования нет интерфейса. Ни одна из гарантий продукта не видна на экране, а приватное приложение, которое ощущается как приватное приложение, удаляют через неделю. Работа была в том, чтобы собрать вещь, которая ведёт себя как обычный мессенджер (быстрый, знакомый, прощающий), пока каждое обещание под ней настоящее.
Чтоясделал
Вход, у которого нечего украсть
Номер телефона и шестизначный код. Ни пароля, ни почты для ссылки сброса, ни социального логина, отдающего вас третьей стороне в момент прихода. При первом запуске устройство генерирует собственную пару ключей X25519: публичная половина регистрируется, чтобы люди могли до вас достучаться, приватная никогда не покидает телефон. На сервере нет аккаунта, который можно выманить, потому что важная часть на сервере никогда не была.
Чат, который ничего не выдаёт
Ответ цитатой, правка отправленного, пересылка, копирование, закрепление, удаление и шесть реакций по долгому нажатию. Доставка в трёх состояниях, отправлено, доставлено и прочитано, индикатор набора живёт на сокете. Ничто из этого не фича безопасности. Это список обычных вещей, отсутствие которых и заставляет заметить, что в руках продукт для безопасности, а мессенджер, который никто не держит открытым, не защищает никого.
Один тап, и всего нет
Красная кнопка в списке чатов стирает чаты, медиа и локальное хранилище, разлогинивает устройство и в тот же момент вычищает серверную сторону аккаунта. Диалога подтверждения нет: ситуация, ради которой существует кнопка, не та, где переспрашивают. Вы заранее выбираете, что из трёх она делает, а после открывается экран приветствия, тот же, что на телефоне, где приложения никогда не было.

Второй код, другая реальность
Задайте код-обманку рядом с настоящим. Настоящий открывает приложение как есть; обманка открывает его с пустым списком чатов и недосягаемым настоящим контентом. Ни скрытого меню, ни долгого нажатия, ни жеста, который мог бы выдать. Для читающего через плечо это приложение, которым вы почти не пользуетесь. Отрицание, которое не требует от человека с телефоном в руках понимать это слово.
Голосовые, которые не несут волну
Держите для записи, отпустите для отправки. Волна рисует себя, пока вы говорите, и ещё раз при проигрывании, на 1x, 1.25x, 1.5x или 2x. Форма не записывается: она выводится из id сообщения, так что каждое устройство рисует одинаковую картинку, а волна никогда не передаётся и не хранится. Сам звук идёт через то же шифрование, что и текст.
Звонки, где посередине никого
Аудио и видео один на один, с картинкой в картинке, переворотом камеры, мьютом и динамиком внутри приложения, а не на откуп системе. Групповые звонки до четырёх человек в mesh: каждый участник соединён напрямую с каждым. Сервер знает, кто в звонке, и ничего о том, что внутри, а когда сеть отказывает прямому маршруту, релеем служит TURN-сервер, который проект держит сам.

Замкнутый круг
Ни каталога, ни юзернейма для поиска, ни незнакомцев. Люди приходят по скану вашего кода или через номер, уже записанный в телефоне. Группы несут роли админов и общую галерею, а групповое шифрование использует тот же ключ чата, зашифрованный отдельно для каждого участника. У продукта без публичного поиска нет проблемы спама, к которой пришлось бы дособирать защиту.
Сообщения со сроком годности
Автоудаление ставится на чат, а не разом на всё приложение: выключено, пять минут, час, день, неделя. Сервер сам выбрасывает истёкшее, не дожидаясь, пока клиент вернётся и спросит, и с другого телефона оно исчезает в тот же момент. Именно последнее делает это фичей, а не уборкой собственного экрана.

Обе темы нарисованы, а не выведены
Тёмная - истинный чёрный, который OLED-панель вообще не зажигает. Светлая инвертирует пузыри: исходящее становится чёрным по белому. Обе нарисованы, ни одна не сгенерирована из другой, и приложение следует за системой, пока не сказано иначе. Всё остальное - оттенки серого нарочно: красный значит опасность, зелёный значит онлайн, и третьего цвета, размывающего оба, нет. Текст масштабируется на 1.0x, 1.15x и 1.3x через собственный компонент приложения, а не платформенный, так что лейаут сам решает, как перетекает текст на треть длиннее.

Айдентика, которая переживает 24 пикселя
Знак - кот, свернувшийся в C, проверен на чёрном, тёмно-сером, белом и светло-сером, с минимальными размерами до 24 пикселей и иконкой приложения, нарисованной на той же сетке.
Подкапотом
Криптография намеренно скучная
Обмен ключами - X25519, сообщения - AES-256-GCM со свежим nonce на каждое, бэкап выводит ключ через PBKDF2 над SHA-256 на ста тысячах итераций. Всё через @noble/curves, @noble/ciphers и @noble/hashes, а не через реализацию, написанную здесь. Интересная криптография - способ получить продукт с интересной уязвимостью.
Схема, которая не может прочитать собственные строки
Две таблицы существуют только чтобы возить ключи: одна держит публичный ключ каждого пользователя, другая - ключ чата, зашифрованный отдельно для каждого участника. Колонка content у сообщений держит шифротекст, так что нет запроса, возвращающего читаемый текст, включая написанные случайно.
Клиент пересобран с Flutter на React Native
Первый клиент был полным Flutter-приложением: около шестидесяти экранов со своим шифрованием, сокетом, звонками, уведомлениями и сервисом обновлений, вплоть до применения бинарных патчей на Dart. Миграцию решил веб: Flutter-сборка для веба была недостаточно хороша для веб-клиента, который нужен продукту, а держать рядом второй, отдельно написанный клиент стоило дороже переписывания.
Rust на сервере и сокет для живой половины
Axum на Tokio, SQLx сверяет каждый запрос с настоящей схемой на этапе компиляции. Сокет несёт то, что должно быть мгновенным: доставку, набор, присутствие и сигналинг звонков; HTTP несёт остальное. Процесс, который держит чужой шифротекст, - не то место, где хочется узнать о баге памяти.
Обновления, которые не ждут ревью стора
JavaScript уходит по воздуху бандлом; нативные изменения уходят бинарными APK-патчами на bsdiff, так что пользователи качают разницу, а не всё приложение. Мессенджер, который не может выкатить фикс за часы, имеет проблему безопасности, которую не может закрыть.
Девять языков, заложенные с самого начала
Английский, русский, испанский, немецкий, французский, китайский, японский, корейский и арабский живут в словарях i18next, а лейаут собран так, чтобы пережить текст на 40% длиннее английского и язык, идущий в другую сторону. Вшивать это в готовый дизайн значит перерисовывать каждый экран.