138 lines
7.4 KiB
Markdown
138 lines
7.4 KiB
Markdown
# Single Source of Truth — VPN-сеть Montana
|
||
|
||
Версия: **2026-05-19**
|
||
|
||
## Принцип
|
||
|
||
Вся канонически верифицируемая правда о состоянии VPN-сети живёт в **одном append-only signed event log**:
|
||
|
||
```
|
||
https://montana.quest/vpn/events.jsonl
|
||
```
|
||
|
||
Каждая строка — событие, индивидуально подписанное **ed25519**:
|
||
|
||
```jsonl
|
||
{"data":{...},"seq":N,"sig":"ed25519:...","ts":"2026-05-19T19:00:00Z","type":"node_register"}
|
||
```
|
||
|
||
**Свойства:**
|
||
- **Append-only:** запись только в конец, прошлые события не переписываются.
|
||
- **Подписанные:** каждое событие проверяется публичным ключом `d9a8bf07871d35c8e85f7de4a9b62896c330ba0987732468515c7bda8bb4adde`.
|
||
- **Монотонный seq:** счётчик никогда не убывает, гэпы запрещены.
|
||
- **Pure-function state:** state = replay(events).
|
||
|
||
## Типы событий
|
||
|
||
| type | data | когда |
|
||
|---|---|---|
|
||
| `node_register` | `{alias, ip, country, label}` | admin регистрирует узел через /vpn/node/register |
|
||
| `node_deregister` | `{alias, ip}` | узел остановлен (xray ExecStopPost) |
|
||
| `node_online` | `{alias, ip}` | orchestrator watchdog: probe OK после offline |
|
||
| `node_offline` | `{alias, ip}` | orchestrator watchdog: 2 probe fails подряд |
|
||
| `unique_user` | `{ip_hash}` | новый sha256(client_IP) в логе xray |
|
||
| `metric` | произвольное | периодические метрики (нагрузка, throughput) |
|
||
| `checkpoint` | `{seq_range, merkle}` | каждые 1000 событий (план) |
|
||
|
||
## Derived state
|
||
|
||
`https://montana.quest/vpn/state.json` — кешированный результат `build_state(events)`. Любой может перепостроить и сравнить.
|
||
|
||
```json
|
||
{
|
||
"head_seq": 1502,
|
||
"head_ts": "2026-05-19T19:25:30Z",
|
||
"built_at": "...",
|
||
"nodes": {
|
||
"helsinki": {"online": true, "ip": "91.132.142.42", "country": "FI", ...},
|
||
"frankfurt": {"online": true, ...},
|
||
"newyork": {"online": true, ...}
|
||
},
|
||
"unique_users": 957,
|
||
"unique_users_merkle": "...",
|
||
"pubkey_hex": "d9a8bf07...",
|
||
"event_log_url": "https://montana.quest/vpn/events.jsonl"
|
||
}
|
||
```
|
||
|
||
`state.json` rebuilds каждые 30 секунд через `montana-state-builder.timer` на Moscow.
|
||
|
||
## Что НЕ публикуется
|
||
|
||
- IP-адреса пользователей (это PII). В события `unique_user` идёт **только sha256 IP** — не обратимо без brute-force на 32-битное IPv4 пространство (~4 сек на rainbow tabe).
|
||
- Чтобы усложнить brute-force, в roadmap — добавить **secret salt** с публикацией с задержкой 7 дней.
|
||
|
||
## Архитектура emitters
|
||
|
||
```
|
||
┌─────────────────────────┐
|
||
│ /opt/montana-vpn-stats │
|
||
│ /event_log.py │
|
||
│ (sign + append) │
|
||
└────────────┬────────────┘
|
||
│
|
||
┌────────────────────────────┼────────────────────────────┐
|
||
│ │ │
|
||
orchestrator watchdog xray-stats collector admin /register
|
||
(online/offline transitions) (new unique IPs) (manual events)
|
||
│ │ │
|
||
└────────────────────────────▼────────────────────────────┘
|
||
│
|
||
/var/www/montana_quest/vpn/events.jsonl
|
||
│
|
||
▼
|
||
montana-state-builder.timer (30s)
|
||
│
|
||
/var/www/montana_quest/vpn/state.json
|
||
│
|
||
▼
|
||
сайт + /vpn/sub + /vpn/node/uptime (читают state.json)
|
||
```
|
||
|
||
## Гарантии
|
||
|
||
| Гарантия | Как доказывается |
|
||
|---|---|
|
||
| Число уникальных пользователей не подделано | каждое событие `unique_user` подписано; state.json содержит merkle root; replay воспроизводит число |
|
||
| Узел не «фейково онлайн» | переходы online↔offline — отдельные подписанные события с timestamp |
|
||
| История не переписана | append-only; seq монотонный без гэпов; любой имеет полный лог |
|
||
| Нельзя backdate | timestamp на каждом событии; запланировано daily anchoring через OpenTimestamps |
|
||
|
||
## Что НЕ гарантируется (пока)
|
||
|
||
- **Forced inclusion:** мы можем замедлить добавление события в лог. Защита: автоматический rebuild каждые 30 секунд.
|
||
- **Privatekey compromise:** если ключ утечёт, можно подделать события. Защита: privateKey 600 root на Moscow.
|
||
- **Tampering архивных событий:** теоретически root доступом на Moscow можно переписать `events.jsonl`. **Защита roadmap:** ежедневный snapshot хеша всего лога в Bitcoin через OpenTimestamps — нельзя backdate.
|
||
|
||
## Аудит
|
||
|
||
Любой может проверить single-source-of-truth:
|
||
|
||
```bash
|
||
bash Montana/Node/External-Audit/scripts/verify-state.sh
|
||
```
|
||
|
||
Этот скрипт:
|
||
1. Скачивает `events.jsonl` и `state.json`.
|
||
2. Проверяет ed25519-подпись каждого события.
|
||
3. Проверяет монотонность seq без гэпов.
|
||
4. Replay events до `head_seq` из state.json.
|
||
5. Сравнивает результат replay со state.json: `head_seq`, `unique_users`, `merkle root`, `nodes online/offline`.
|
||
|
||
Если хотя бы один тест провалится — мы лжём.
|
||
|
||
## Сейчас зелёные тесты
|
||
|
||
```
|
||
✓ все 1531 events подписаны валидно, seq непрерывен
|
||
✓ replay matches claimed state: head_seq=1502, nodes=3, unique_users=957
|
||
=== ВСЕ ПРОВЕРКИ ЗЕЛЁНЫЕ ===
|
||
```
|
||
|
||
## Roadmap
|
||
|
||
- **P1:** daily checkpoint в Bitcoin через OpenTimestamps. Дописать `transparency.md`.
|
||
- **P2:** мульти-эмиттер — каждый узел независимо подписывает свои события + threshold-signature (2 of 3). Устраняет single trust на Moscow privateKey.
|
||
- **P3:** GraphQL-like derivation API — клиенты запрашивают subset state через `?fields=...`.
|
||
- **P4:** Public mirror лога на IPFS/GitHub raw — анти-цензура.
|