Skip to content

Who sees what

For each participant below: what it can learn, and which data structure it learns it from.

This is what a node keeps about a session:

type Session struct {
UUID string // Xray VLESS UUID
Email string // Xray account name
ExpiresAt time.Time
CreatedAt time.Time
Flow string
}

There is no wallet field. A node is never sent a wallet; the protocol has nowhere to put one.

That matters because the first node sees your IP for as long as the session lasts. It could tie a wallet to the place you connect from only if it had one, and it has none.

Email is not an email address. It is built from the session:

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

Eight characters of a random UUID. Xray wants accounts to have names; that is the name.

What a node does know is the address that connected to it and the address it passes traffic on to. In a chain those two facts sit with different nodes:

  • the first node knows your IP and the next hop, not the destination;
  • the last node knows the destination and the previous hop, not your IP;
  • middle nodes know neither.

On a single hop both halves meet at one node. Keeping them apart takes two parties.

No traffic goes through a Control Plane. It issues sessions and receives reports; the data goes from client to node directly.

This is what it keeps about a session:

type SessionBinding struct {
CheckpointPublicKey []byte // ed25519, 32 bytes
PermissionDigest []byte // one-way, or nil
PermissionWindow uint64 // the window that permission belongs to
}

The key that may report on the session, and the permission the session runs on. The record is deleted when the session ends and otherwise expires 48 hours after it was last written. A renewal writes it again.

The two tiers differ in what the connection holds:

Wallet accessAnonymous access
WalletAddressthe client’s addressempty
Anonymousfalsetrue
Proof of entitlementa wallet signaturea ZK proof
Checkpoints signed withthe wallet’s secp256k1 keya one-off ed25519 key

In the anonymous tier a Control Plane knows that someone in the subscriber set opened a session. Who, the proof does not say. What stops the same proof being used twice is a nullifier, and a nullifier is tied to no address.

In neither tier does it learn where you go. It plays no part in carrying traffic and is never sent a destination.

It does see how much. Every checkpoint carries the bytes moved each way since the session started, signed by the client, because an interval with almost no traffic is not credited. In the wallet tier that is a volume per wallet. In the anonymous tier it is a volume per session, and the session belongs to nobody in particular.

Like any server, a Control Plane can see the address a connection comes from. A session for the first hop has to be asked for before there is a tunnel to ask through, so the client connects to its Control Plane directly, or through that Control Plane’s front, which runs on the same host. Either way, that host sees your IP. With wallet access, the same connection also carries your wallet. With anonymous access, it carries a session that belongs to nobody in particular.

The star program does not read a client’s address: no session record, report or log line holds it. That describes what the code does, and it does not limit what an operator can see. A reverse proxy in front of the Control Plane, or the host itself, can record connections, and the protocol cannot prevent that. So count the Control Plane of your first hop among the parties that may learn your IP.

Once the first hop is up, Wanderer sends that session’s reports through the tunnel, so they arrive from the node’s address. On a route of several hops, each later session is asked for through the hops already up. That hop’s Control Plane sees the hop before it; your address never reaches it.

A nullifier stops one entitlement being used twice. It is in no contract; it stays with the Control Plane.

Its scope is built from three numbers:

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

The Control Plane’s own id is in there, so the same subscriber has different nullifiers at different Control Planes. Two operators comparing lists find nothing in common.

The price is that the quota is counted per Control Plane rather than across the network. Permissions are what count network-wide, and they are shared on purpose — see anonymous access.

This is what lands in EpochAttestations:

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

A node, a Control Plane, an interval, a session hash, a permission digest and the node’s signature. No client, no destination, no volume.

From the chain you can learn that node 42 served so many seconds in an epoch. You cannot learn for whom.

Also public: the Control Plane list in CPEndpointRegistry, the Star registry with owners, signers and roles, and the commitment list in SubscriberRegistry. A commitment is a hash, and membership is proven without saying which one is yours.

Node addresses are not on chain at all. A client learns a node’s address from the Control Plane along with its session, and a Control Plane hands addresses out only to owners, against a signature.

Someone between you and the first node sees a TLS connection. The transport is VLESS over REALITY, as raw TCP with XTLS-Vision or as xHTTP, and the handshake looks like a visit to a real third-party site, because the certificate in it belongs to that site.

Visible: that a connection exists, how long it lasts, and how many bytes cross it.

Not visible: what is inside, and where you go after the first hop.

  • The whole route. The client assembles its chain on its own machine. Nobody receives it as a list; each party knows only its neighbour.
  • Volume on chain. The network pays in seconds. Byte counts stay with the Control Plane that received the checkpoints.
  • A path from the chain back to a client. A recorded interval names its session by the hash of its UUID and its permission by a digest. Neither appears anywhere next to a wallet.

Wallet-tier signatures travel within the federation. For a neighbouring Control Plane to check somebody else’s checkpoints, it receives the client’s signature, and in the wallet tier that recovers to an address. The anonymous tier is not affected: its signatures come from one-off keys tied to nothing.

A node reports its own way out. warp, direct or custom is the node’s statement about itself.

Registering for anonymous access is public. The Registered event ties a wallet to a commitment in the set. Activation can register in the same transaction, and Wanderer always does, so the event says little more than that the wallet holds an Orbit. Which sessions are theirs stays hidden.

The first node, and the Control Plane of the first hop, can see your IP. At any number of hops. The node sees it for the whole session; the Control Plane sees it when the session is asked for. See where requests come from.

Timing is not hidden. If the subscriber set is small, connections and disconnections are easy to match up. Check SubscriberRegistry.totalCommitments() before relying on it.