# Прозрачность счётчика пользователей Версия: **2026-05-19** ## Что мы доказываем Число «Уникальных пользователей» на `montana.quest/vpn/` — **не выдумано**. ## Что мы НЕ публикуем IP-адреса пользователей. Это PII (персональные данные) — публиковать их незаконно (GDPR / 152-ФЗ) и аморально. ## Как доказательство работает 1. На узле Helsinki в `/var/lib/montana-vpn-stats/unique-ips.txt` накапливается список IP, когда-либо подключавшихся к VPN (из xray access.log). Файл root:root 600, не доступен публично. 2. Каждую минуту скрипт `/opt/montana-vpn-stats/transparency-sign.py`: - читает список IP - сортирует - вычисляет `sha256(joined-with-LF)` → **merkle_root** - подписывает snapshot `{ts_utc, unique_users, merkle_root, algorithm}` ключом ed25519 - публикует: - `https://montana.quest/vpn/transparency.json` — последний snapshot - `https://montana.quest/vpn/transparency-log.txt` — append-only лог всех snapshot'ов 3. Публичный ключ подписи: `d9a8bf07871d35c8e85f7de4a9b62896c330ba0987732468515c7bda8bb4adde` (ed25519). Лежит здесь и в репозитории. Приватный ключ — только на узле, 600 root. ## Что может проверить аудитор без доступа к серверу | Свойство | Как проверить | |---|---| | **Подпись валидна** | `verify-transparency.sh` проверяет ed25519 над snapshot | | **Публичный ключ не сменили** | сравнить с публикуемым в этом репозитории | | **Число не подкручивается вниз** | `transparency-log.txt` — append-only, аудитор скачивает последние N снимков, проверяет монотонность | | **Snapshot timestamp реальный** | сравнить `ts_utc` с моментом скачивания (расхождение ≤ 60s) | | **Один и тот же merkle root = одни и те же IPы** | повторный merkle root между snapshots = новых IP нет | ## Что аудитор НЕ может узнать - Конкретные IP пользователей (они зашифрованы в merkle root). - Страну/время подключения отдельного пользователя. Чтобы доказать что конкретный IP в множестве — нужен сам IP (известен только владельцу) + полный список (raw `unique-ips.txt` доступен только под NDA на аудит, не публично). ## Запуск проверки ```bash bash scripts/verify-transparency.sh ``` Скрипт: 1. Скачивает `https://montana.quest/vpn/transparency.json`. 2. Проверяет ed25519-подпись с известного `EXPECTED_PUBKEY`. 3. Скачивает `https://montana.quest/vpn/transparency-log.txt`. 4. Проверяет монотонность роста за последние 50 снимков. 5. Выводит зелёные галки и текущее число. ## Ограничения текущей реализации 1. **Anti-tamper только в пределах подписи.** Атакующий с доступом к privateKey может выдавать любое число. Защита: ключ 600 root + сервер изолирован. 2. **Нет on-chain anchor.** При желании можно anchored daily snapshot в Bitcoin через OpenTimestamps — это даст proof что snapshot существовал не позднее timestamp'а в чейне. Roadmap. 3. **IP = пользователь — приближение.** Один человек с двух IP считается как 2; несколько человек за одним NAT-IP считаются как 1. Это **верхняя оценка уникальных** для NAT и **нижняя** для multi-device. Точнее без trackable user-ID (что = приватность ↓). 4. **Уязвимость к ротации файла.** Если `unique-ips.txt` корраптится — счётчик откатывается. Защита: append-only лог сохраняет историю. ## Полный список секретов - `/etc/montana/transparency-privkey` (Moscow, 600 root) — ed25519 приватный ключ подписи. - Только этот хост может подписывать. При компрометации — генерация нового keypair, обновление публичного ключа в репо + публикация incident notice. ## Roadmap - **P1:** Bitcoin OpenTimestamps anchoring каждый день в 00:00 UTC. - **P2:** Migration на cryptographic accumulator (RSA / Merkle tree) для proof-of-membership без раскрытия списка. - **P3:** Multi-party signing (2 of 3 узлов подписывают) — устраняет single point of trust на Moscow privateKey.