Перейти к содержимому

Кто что видит

Для каждого участника ниже сказано, что он может узнать и из какой структуры данных.

Вот что узел хранит о сессии:

type Session struct {
UUID string // UUID для VLESS в Xray
Email string // имя учётной записи в Xray
ExpiresAt time.Time
CreatedAt time.Time
Flow string
}

Поля для кошелька нет. Кошелёк узлу не передаётся, в протоколе для него просто нет места.

Это важно, потому что первый узел видит ваш IP всё время, пока идёт сессия. Связать кошелёк с местом, откуда вы подключаетесь, он мог бы, только если бы кошелёк у него был, а его нет.

Email — не адрес почты. Он собирается из сессии:

email := fmt.Sprintf("%s@singula", sessionUUID[:8])

Это восемь символов случайного UUID. Xray требует, чтобы у учётной записи было имя, вот оно.

На самом деле узел знает адрес, который к нему подключился, и адрес, куда он передаёт трафик дальше. В цепочке эти два факта оказываются у разных узлов:

  • первый узел знает ваш IP и следующий хоп, но не конечный адрес;
  • последний узел знает конечный адрес и предыдущий хоп, но не ваш IP;
  • средние узлы не знают ни того, ни другого.

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

Через Control Plane трафик не идёт. Он выдаёт сессии и получает отчёты, а данные идут от клиента прямо к узлу.

Вот что он держит о сессии:

type SessionBinding struct {
CheckpointPublicKey []byte // ed25519, 32 байта
PermissionDigest []byte // односторонний, или nil
PermissionWindow uint64 // окно, которому принадлежит это разрешение
}

Это ключ, которым по сессии можно отчитываться, и разрешение, на котором сессия идёт. Запись удаляется, когда сессия заканчивается, а если этого не случилось, истекает через 48 часов после последней записи. Продление записывает её заново.

Два уровня доступа различаются тем, что лежит в соединении:

Доступ с кошелькомАнонимный доступ
WalletAddressадрес клиентапусто
Anonymousfalsetrue
Чем показано правоподпись кошелькаZK-доказательство
Чем подписаны чекпоинтыключ secp256k1 кошелькаодноразовый ключ ed25519

На анонимном уровне Control Plane знает, что сессию открыл кто-то из множества подписчиков. Кто именно, доказательство не говорит. Повторно использовать одно и то же доказательство не даёт нуллификатор, а он ни к какому адресу не привязан.

Куда вы ходите, Control Plane не узнаёт ни на одном уровне. В передаче трафика он не участвует, и адреса назначения ему не отправляются.

А вот сколько — видит. В каждом чекпоинте есть число байт в обе стороны с начала сессии, подписанное клиентом, потому что интервал почти без трафика не засчитывается. На кошельковом уровне это объём на кошелёк. На анонимном — объём на сессию, а сессия не принадлежит никому конкретному.

Как и любой сервер, Control Plane видит адрес, с которого пришло соединение. Сессию для первого хопа приходится просить, пока туннеля, через который можно было бы спросить, ещё нет. Поэтому клиент подключается к своему Control Plane напрямую или через его фронт, который работает на том же хосте. В обоих случаях этот хост видит ваш IP. При доступе с кошельком в том же соединении идёт и кошелёк. При анонимном — сессия, которая никому конкретному не принадлежит.

Программа star адрес клиента не читает: его нет ни в записи о сессии, ни в отчётах, ни в логах. Это описание того, что делает код, и видимость для оператора оно не ограничивает. Обратный прокси перед Control Plane или сам хост могут записывать соединения, и протокол помешать этому не может. Поэтому считайте Control Plane первого хопа одной из сторон, которые могут узнать ваш IP.

Когда первый хоп поднят, Wanderer шлёт отчёты по этой сессии через туннель, и они приходят с адреса узла. На маршруте из нескольких хопов каждая следующая сессия запрашивается через уже поднятые хопы. Control Plane этого хопа видит предыдущий хоп, ваш адрес до него не доходит.

Нуллификатор не даёт использовать одно право дважды. Ни в каком контракте его нет, он хранится у Control Plane.

Его область собирается из трёх чисел:

scope = (cpStarId << 96) | (epoch << 32) | index

В ней есть идентификатор самого Control Plane, поэтому у одного и того же подписчика у разных Control Plane разные нуллификаторы. Два оператора, сравнив свои списки, общих записей не найдут.

Цена этого — квота считается у каждого Control Plane отдельно, а не на всю сеть. Общий счёт на всю сеть ведут разрешения, и общими их сделали намеренно — см. анонимный доступ.

Вот что попадает в EpochAttestations:

struct PushItem {
uint64 node;
uint64 cp;
uint64 fromTs;
uint64 delta;
bytes32 session;
bytes32 permission;
bytes sig;
}

Узел, Control Plane, интервал, хеш сессии, дайджест разрешения и подпись узла. Ни клиента, ни адреса назначения, ни объёма.

Из блокчейна можно узнать, что узел 42 отработал столько-то секунд за эпоху. Для кого — нельзя.

Ещё в открытом доступе: список Control Plane в CPEndpointRegistry, реестр звёзд с владельцами, подписантами и ролями и список коммитментов в SubscriberRegistry. Коммитмент — это хеш, а принадлежность к множеству доказывают, не называя, какой из них ваш.

Адресов узлов в блокчейне нет вообще. Клиент узнаёт адрес узла от Control Plane вместе с сессией, а Control Plane раздаёт адреса только владельцам по подписи.

Тот, кто стоит между вами и первым узлом, видит TLS-соединение. Транспорт — VLESS поверх REALITY, по голому TCP с XTLS-Vision или по xHTTP, и рукопожатие похоже на визит к настоящему стороннему сайту: сертификат в нём принадлежит этому сайту.

Видно: что соединение есть, сколько оно длится и сколько через него проходит байт.

Не видно: что внутри и куда вы идёте после первого хопа.

  • Всего маршрута. Клиент собирает цепочку у себя. Списком её не получает никто, каждый знает только своего соседа.
  • Объёма в блокчейне. Сеть платит за секунды. Счётчики байт остаются у Control Plane, который получил чекпоинты.
  • Пути от блокчейна к клиенту. Записанный интервал называет сессию хешем её UUID, а разрешение — дайджестом. Ни то ни другое нигде не стоит рядом с кошельком.

Подписи кошелькового уровня расходятся по федерации. Чтобы соседний Control Plane мог проверить чужие чекпоинты, он получает подпись клиента, а на кошельковом уровне из неё восстанавливается адрес. Анонимного уровня это не касается: там подписи сделаны одноразовыми ключами, ни к чему не привязанными.

Узел сам говорит, как он выходит в интернет. warp, direct или custom — это утверждение узла о себе.

Регистрация для анонимного доступа публична. Событие Registered связывает кошелёк с коммитментом в множестве. Активация может зарегистрировать его в той же транзакции, и Wanderer всегда так делает, поэтому событие говорит немногим больше, чем то, что у кошелька есть Orbit. Какие сессии его, остаётся скрытым.

Ваш IP видят первый узел и Control Plane первого хопа. Сколько бы хопов ни было. Узел видит его всю сессию, Control Plane — когда у него просят сессию. Подробнее — откуда приходят запросы.

Время не скрыто. Если множество подписчиков маленькое, моменты подключения и отключения легко сопоставить. Прежде чем на него полагаться, посмотрите SubscriberRegistry.totalCommitments().