The Connect handshake establishes a session between two MixTransport endpoints. The initiator sends Connect anonymously through the Mix network, the recipient replies through a SURB supplied in that frame, and the initiator establishes the session only after recovering a valid ConnectAck.
The transport implementation is in libp2p_mix_transport/transport.nim. Session-owned received SURBs are stored by libp2p_mix_transport/sessions.nim, and session-specific credential cleanup is implemented in libp2p_mix_transport/reply_credentials.nim.
Why Connect Requires a Round Trip
The initiator already knows the real PeerId of the Mix destination, but that information alone does not show that the destination is reachable through Mix or that it runs MixTransport. connect(destination) therefore does not report success after merely submitting a packet to the first relay.
Instead, the initiator creates a pending session, sends Connect, and waits for a ConnectAck that can only be returned through one of the public SURBs included in Connect. Recovering that reply with the matching private credential shows that a node which received the Connect frame was able to use the supplied return path. Only then does the initiator change its local session from Pending to Established.
The acknowledgement does not reveal the initiator’s authenticated libp2p identity to the recipient. Both frames carry the random session pseudonym generated by the initiator.
Preparing Connect on the Initiator
The public API for this exchange is:
await transport.connect(destinationPeerId)The caller supplies the real PeerId of the remote Mix node. MixTransport constructs MixDestination.exitNode(destinationPeerId) internally because the transport uses exit-equals-destination routing exclusively.
Before sending anything, the initiator checks SessionStore for an existing session registered under that destination. If it finds an established session, it returns the same TransportSession immediately. This preserves the existing sessionId and prevents a repeated connect call from creating a second peer relationship for the same destination.
When several callers request the same destination while its handshake is still pending, ConnectAttemptCoordinator lets every caller wait for the same transport-owned operation. Cancelling one caller does not cancel the handshake while another caller remains. If the final caller is cancelled, the coordinator cancels the now-unobserved handshake and removes the failed pending session through the normal connectInternal cleanup. Mix Transport Implementation Walk Through - Concurrent Connect and Test Injection describes the coordinator, its cancellation paths and the delayed-acknowledgement test seam.
For a new destination, the initiator generates a random PeerId to use as sessionId and adds a pending initiator session to the registry. It then asks its local MixProtocol to create the public SURBs and private reply credentials carried by the handshake.
createConnectFrame asks the actual Protobuf encoder how many serialized SURBs fit in the Sphinx payload. The current frame fits five independent SURBs:
Connect
SURB 1
SURB 2
SURB 3
SURB 4
SURB 5The first two public SURBs are unnumbered response paths for ConnectAck. The remaining three SURBs are numbered session supply with sequences zero through two. Their five private ReplyCredential values remain on the initiator and are registered independently in ReplyCredentialStore under the new sessionId.
The wire decoder requires at least two SURBs because the recipient cannot acknowledge the session without a complete response redundancy batch. The decoder also requires firstSurbSequence when more than two SURBs are present. The initiator registers the numbered suffix before submitting Connect, so acknowledgement and retransmission use the same supply state as later standalone SurbSupply frames.
After the frame passes wire validation and Protobuf encoding, the initiator calls:
mix.send(MixDestination.exitNode(destinationPeerId), MixTransportCodec, payload)Mix routes this ordinary service delivery through the selected relays and terminates the Sphinx path at the destination node.
Receiving Connect on the Recipient
The recipient’s MixProtocol recognizes MixTransportCodec and passes the decoded Mix payload to the delivery handler registered by MixTransport.start. The delivery arrives as an ordinary MixDelivery; it has not travelled through a SURB.
MixTransport decodes the Protobuf frame and handles it as a new session only when its kind is Connect. It then performs the following operations in order:
- It checks that no local session already uses the received
sessionId. - It deserializes the first two public SURBs. If either value is malformed, it rejects the handshake because it cannot return
ConnectAckwith the configured redundancy. - It creates a pending recipient session identified by the received pseudonym and initializes that session’s empty numbered-supply state.
- It independently deserializes every SURB after the first two and accepts each valid value under the sequence carried by
firstSurbSequenceand its position in the suffix. One malformed supply SURB does not discard the other valid entries. - It attaches the resulting absolute supply snapshot to
ConnectAck. - It marks the recipient session established and sends
ConnectAckthrough the first two SURBs.
The session stores individual SURBs. Redundancy is assigned only when a reverse frame is ready to send, so the same queue can serve all stream and session traffic without persistent group boundaries.
Sending ConnectAck Through a Temporary Redundancy Batch
The recipient encodes a ConnectAck containing the same sessionId, no public SURBs or application payload, and the first absolute SURB supply snapshot. With the default recipient capacity of sixteen and three accepted bootstrap SURBs, the snapshot advertises surbSupplyReceiveBase = 3, an empty 256-bit acknowledgement bitmap and surbSupplyLimit = 16. The receive base acknowledges the contiguous bootstrap sequences zero through two, while the limit authorizes sequences three through fifteen.
The recipient submits the encoded bytes separately through both SURBs in the temporary batch:
ConnectAck bytes -> SURB 1 -> return Mix path
-> SURB 2 -> return Mix pathEach call to MixProtocol.sendWithSurb consumes the supplied SURB, even if that send reports an error. sendWithSurbRedundancyBatch therefore tries every SURB in the temporary batch and considers the logical acknowledgement submitted when at least one call succeeds. If every call fails, the recipient removes the newly created session because no acknowledgement was published.
The recipient marks its local session established before submitting the first redundant acknowledgement. This ordering is required because the first copy can reach the initiator while the recipient is still awaiting later sends. Once the initiator observes ConnectAck, it may immediately send OpenStream or Data; the recipient must already accept that session traffic. Complete acknowledgement failure removes the session. Cancellation also removes it because cancellation explicitly terminates the local handshake, regardless of whether an earlier copy escaped.
The three numbered bootstrap SURBs remain in the recipient’s session queue. After the initiator recovers ConnectAck, its supplier removes the acknowledged bootstrap entries from pendingSurbSupply and sends thirteen additional numbered SURBs to fill the advertised capacity. An initiator-originated OpenStream supplies two dedicated response paths and uses any remaining frame space for further numbered session supply when the current credit permits it.
Recovering ConnectAck on the Initiator
Each redundant acknowledgement returns to the initiator as a RawSurbReply. Before Mix attempts its embedded request/reply handling, it offers that reply to the raw reply handler registered by MixTransport.
ReplyCredentialStore uses the reply’s SURBIdentifier to find the corresponding private credential. A successful cryptographic recovery produces the encoded ConnectAck bytes and the sessionId recorded with that individual credential.
MixTransport then decodes the transport frame and verifies that the sessionId inside the frame matches the sessionId associated with the private credential. This prevents a recovered reply registered for one transport session from being dispatched to another session merely because its payload names a different pseudonym.
The reply path first applies the complete supply snapshot. The reply path then accepts ConnectAck only when the matching local session exists, has the Initiator role, and is still Pending. It calls establish, which changes the state to Established and fires the session’s AsyncEvent, and starts the session-owned SURB supplier task. The connect call waiting on the event can now return the established session to its caller while the supplier fills the advertised credit through the forward Mix path.
When a valid acknowledgement is recovered, ReplyCredentialStore removes only the matching credential and records that SURB identifier as retired until its original expiry time. If the second acknowledgement arrives, the store recovers it independently and consumes its own credential. handleReplyFrame observes that the session is no longer pending and ignores the repeated logical ConnectAck. Credentials corresponding to the three numbered bootstrap SURBs held by the recipient remain active until those SURBs are used, expire or the session closes.
Keeping Ordinary Deliveries and SURB Replies Separate
The two frame paths deliberately have different dispatch functions.
Connect is accepted only by the ordinary Mix delivery handler. ConnectAck is accepted only after the raw SURB reply has been recovered with a credential owned by MixTransport. A payload sent as an ordinary forward Mix message therefore cannot mark an initiator session established merely by declaring itself to be ConnectAck.
The implemented stream and supply frames follow the same directional rule. Forward OpenStream, Data, SurbSupply and SurbStatusProbe frames enter through ordinary Mix delivery. Return ConnectAck, StreamAck, StreamReject, Data, ACK and SurbStatus frames enter the initiator through raw SURB reply recovery.
Timeout, Cancellation, and Cleanup
The initiator waits up to thirty seconds by default for its session’s establishment event. Callers can configure this timeout through newMixTransport.
If SURB creation, credential registration, frame encoding, Mix submission, or the acknowledgement wait fails, connect removes the pending session and removes every reply credential registered under its sessionId. Cancellation propagates as CancelledError, but the same deferred cleanup runs before it leaves the operation. This prevents a failed handshake from occupying the destination lookup or leaving credentials that no live session can consume.
removeSession(sessionId) in ReplyCredentialStore first purges all entries whose individual deadline has passed. It then removes the still-active credentials belonging to the failed session and retires their identifiers until their original deadlines. Credentials owned by other active sessions remain registered. One session can own credentials with different deadlines because numbered supply and later status probes create them at different times.
The recipient similarly removes its new session if it receives too few valid SURBs, cannot store them, cannot encode ConnectAck or cannot submit the acknowledgement through at least one selected SURB. The recipient establishes the local state before publication, but complete acknowledgement failure is still a definite rollback condition because no initiator could have acted on the acknowledgement.
Packet Loss
The acknowledgement has two redundant delivery attempts because the recipient forms a temporary batch containing two SURBs. The Connect frame itself is submitted once. If the forward packet is lost, or if both redundant acknowledgements are lost, the initiator reaches its connect timeout and removes the pending session and its credentials.
Connect is not retransmitted. The recipient ignores a Connect whose session ID is already registered rather than sending another acknowledgement. Data and supply retransmission are separate mechanisms and do not repair a lost establishment exchange.