Главная страница в мобильном и веб приложении
Главная страница в мобильном и веб приложении

Я открываю ленту и ищу что-то живое среди рекламы и хлама. Потом комментарии, где непонятно, сколько аккаунтов вообще люди. Потом чат, который может прочитать кто-то третий, а я об этом не узнаю: разработчик, который продаёт базу, старший брат или алгоритм, которому переписку отдали "для безопасности".

Я устал. И я собираю то, чего мне в этом не хватает: соцсеть, в которой спокойно находиться, свободно говорить и которую нельзя забрать, оставив людей ни с чем. Дальше не обещания, а то, что уже лежит в открытом репозитории.

Веб: https://social.flora-s.net/

Мобильное приложение: https://social.flora-s.net/download

Гитхаб: https://github.com/effka11/Flora.Ecosystem
⠀

Что уже можно открыть

Flora – открытая некоммерческая социальная экосистема. Код сообщества под AGPL-3.0-only, стек – Rust и TypeScript: HTTP-хост flora-api, веб на Next.js 16, мобильный клиент на Expo, данные в PostgreSQL. Это модульный монолит и отдельные продукты рядом: соцсеть, шифрование, лента, поиск, кодеки. Социальная сеть их собирает, они от неё не зависят.

Сейчас альфа, и ей уже можно пользоваться. После входа есть лента, посты, комментарии, сообщества, подписки, сообщения, музыка и поиск. Рекламы, платного продвижения и платной косметики статуса нет. Брендовые посты, если появляются, идут по правилам обычных и помечаются. Выключатель "убрать брендированное" я ещё разрабатываю, в настройках его пока нет.

Я выпускаю версии, читаю обратную связь и помогаю тем, кто заходит. Помощь с разработкой тоже принимаю.

Поднять локально: Node.js 20.19 или новее, Rust, Docker, PostgreSQL. Дальше npm install и скрипты из репозитория: база, API на порту 5290, веб на порту 3000.
⠀

Сквозное шифрование

Основа переписки – свой протокол FSCP, версия v1. Сообщение закрывается на устройстве. Сервер хранит шифртекст, публичные ключи, запечатанный бэкап ключей, параметры KDF, список устройств и служебное: кто, когда, в каком диалоге, какого размера. Plaintext, приватных ключей и ключа "для поддержки" у него нет. Чужой человек с дампом базы текст не прочитает.

На каждое сообщение генерируется свой 32-байтовый messageKey. Тело (текст или описание вложений) шифруется XChaCha20-Poly1305. Для каждого получателя, включая ваши собственные устройства, делается отдельный конверт: эфемерный X25519, HKDF-SHA-256, затем тем же XChaCha20-Poly1305 заворачивается только messageKey. Конверт подписывается Ed25519. На этом слое AES-GCM запрещён. Байты файла – отдельный объект AES-GCM, а ключ файла лежит внутри уже закрытого тела.

Второе устройство в таких мессенджерах обычно ломает схему: либо сидишь на одном, либо таскаешь ключи руками. Во Flora вы входите в аккаунт. Бэкап ключей скачивается и открывается паролем прямо на этом устройстве. Это отдельный Argon2id, не хеш пароля, которым вы логинитесь. На десктопе параметры такие: 64 МиБ памяти, 3 прохода, parallelism 1. Соль и параметры лежат рядом с шифртекстом и секретом не являются. Само устройство становится доверенным после открытия бэкапа и отдельного подтверждения.

Сброс почты старый бэкап не открывает. Историю после смены пароля поднимает фраза восстановления из 12 слов (проверяльщика фразы на сервере нет) или другое доверенное устройство. Если пропали пароль, фраза и все доверенные устройства, эпоха остаётся закрытой. Так и задумано.

Группа в текущей версии – тот же конверт, разосланный каждому участнику, до 128 человек, включая отправителя. Общего группового секрета нет. Сервер видит название и состав и не видит текст.

Дальше границы v1, без них описание было бы рекламой.

Подпись сервер проверяет публичным ключом из того же конверта. Она держит пассивную подмену шифртекста. Активный сервер может подменить и ключ, и подпись. Защита от этого сейчас – первое доверие к ключу и число безопасности, отпечаток идентификационных ключей двух сторон. Прозрачности ключей нет.

Каждое сообщение получает новый эфемерный ключ, и это ещё не Double Ratchet. Долгосрочный ключ соглашения устройства переиспользуется: кто его украл, раскроет messageKey конвертов, завёрнутых на этот ключ. Post-compromise security, одноразовых pre-key и X3DH в v1 нет. Гибрид X25519 с ML-KEM-768 есть в черновике v2 и на провод не вышел.

Криптографические жалобы (franking) в коде есть: можно доказать содержимое одного сообщения, не открывая серверу весь чат. В проде тег не выпускается.

Обзор угроз и параметры бэкапа – в e2e-security.md.
⠀

Живучесть

Дальше то, из-за чего проект переживает давление.

Код сообщества можно читать, проверять, запускать у себя и улучшать вкладом. Если вы отдаёте изменённый сервис по сети, исходники этой версии обязаны остаться открытыми. Поэтому открытую редакцию нельзя тихо закрыть и унести. Я включу рекламу, забью или пропаду – люди поднимут ту же соцсеть под другим именем и останутся там. Чем живее проект, тем труднее забрать его насовсем. Уничтожить проект целиком можно только уничтожив все его копии, по сути как книгу. А рукописи, как говорится, не горят.

Коммерческая лицензия на тот же код есть, у того же правообладателя: закрытая встройка, OEM, SLA. Она не отменяет AGPL для пользовательской сети и не продаёт внимание людей внутри Flora. Она нужна для тех, кто хочет встроить код Flora в закрытый продукт.
⠀

Рекомендации

Рекомендации –  FIRA. Алгоритм открыт, его можно прочитать и настроить. Хронологическая вкладка "Подписки" идёт мимо формулы. Свои посты закрепляются сверху и не участвуют в скоре.

Живой скор ленты сейчас такой. Коэффициент тематического сходства равен нулю, поэтому интересы профиля на ранг не влияют. Остаются популярность и то, что сделали люди, на которых вы подписаны:

Score = 0.70 × GlobalRelevance + 0.30 × SocialProximity

GlobalRelevance = viral × exp(−λ × ageHours) viral = engagement / ln(followers + 2)

engagement = ln(likes48h + 1) × 1.0 + ln(comments48h + 1) × 2.0 + ln(reposts48h + 1) × 2.5 + ln(views48h + 1) × 0.01

SocialProximity = ln(followedLikers + 1) × 3 + repostBoost repostBoost = 1.5 × min(ln(followedReposters + 1), 3)


repostBoost равен нулю, если среди ваших подписок репост никто не сделал. При настройках по умолчанию скор совпадает с эталонными векторами. Код скорера –  products/FIRA/crates/fira-core.

Пул, из которого это считается: ваши подписки, авторы второй степени (на кого подписаны ваши подписки), тренды, сообщества, в которых вы состоите, и примесь случайных публичных постов. Коллаборативной фильтрации "похожие на вас люди" в ленте нет, это v3.

В настройках ленты уже крутятся живые ручки:

  • свежесть – λ 0.10 / 0.05 / 0.025 (свежее, баланс, популярное);

  • сколько впускать нового –  0 / 8% / 15% / 25%;

  • один автор подряд – не больше 1, не больше 2, или без лимита;

  • репосты и посты сообществ можно выключить;

  • уже виденное можно оставить, опустить (множитель 0.3) или убрать;

  • "не интересно" убирает пост и умножает автора на 0.5, не больше трёх раз.

Скрытый человек пропадает из рекомендаций. На вкладке подписок его посты остаются: она хронологическая и от скора не зависит.

Свои направления интересов я ещё довожу. Кнопки, которой брендированный контент отключается целиком, опять же, пока нет. Алгоритм слушает те настройки, которые перечислены выше.
⠀

Свой медиакодек

Я написал фотокодек FRC-I на Rust. Формат заморожен, bitstream v12, ядро без unsafe, один код собирается и на сервер, и в браузер, и в мобильное приложение.

С JPEG спека сравнивает версию v8, Kodak, PSNR: около −29% BD-rate, то есть меньше бит при той же PSNR, кодирование 11,8 Мп/с против примерно 9,2 у JPEG. Сравнение текущего v12 с JPEG XL на том же Kodak по SSIMULACRA2 другое: у FRC-I ещё примерно на 33,6% больше бит, чем у cjxl -e 7. Кодирование при этом быстрее: 13,8 Мп/с против примерно 7,9. Плотнее обычного JPEG на опубликованном PSNR-бенче – да. Обгонять JPEG XL я не заявляю.

В постах картинка пишется парой. Основной файл – FRC-I (для постов качество 75, для аватаров 85), рядом lossless WebP. Браузер по умолчанию получает WebP. FRC-I отдаётся, когда клиент просит image/x-flora-frc-i. В чате клиент кодирует FRC-I и запасной JPEG, PNG или WebP, шифрует каждый вариант и уходит на сервер уже шифртекст

Аудиокодек FRC-A и видеокодек FRC-V лежат в том же семействе и остаются черновиками. В пользовательской сети звук пока AAC, видео пока AV1. Свой аудиопрогон я не выигрывал у Opus, AAC, Vorbis и MP3. Это в спеке написано прямо.
⠀

Кого я жду

Людей внутри почти нет. Работы много, она продолжается, и мне нужны те, кто зайдёт. Напишите, сделайте пост, почитайте, поругайтесь по делу. Предметную критику я люблю, в т.ч. там, где текст выше расходится с тем, что вы увидите в спеке или в клиенте. Буду рад каждому живому человеку.