# Croc's Relay Communication Protocol Internals: How the PAKE Handshake and Room-Based Pairing Work

> Explore Croc's relay communication protocol internals. Learn how PAKE handshake and room based pairing create secure connections without inspecting payloads.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: internals
- Published: 2026-07-26

---

**Croc's relay communication protocol uses a lightweight TCP server to perform Password-Authenticated Key Exchange (PAKE), derive strong symmetric keys, and pair two clients into an opaque byte-streaming "room" without ever inspecting the transferred payload.**

Croc's relay communication protocol powers the secure file transfer tool in the `schollz/croc` repository by acting as a cryptographic coordinator rather than a data processor. The relay, implemented primarily in [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go), handles client authentication, NAT traversal signaling, and bidirectional byte piping while remaining blind to the actual file contents.

## PAKE Handshake and Initial Connection

When a client connects to the relay, the server initiates a cryptographic handshake in the `clientCommunication` function. The process begins with the client sending its PAKE initiation value **A**, which the relay receives and processes using the SIEC curve.

In [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go) (lines [78-100](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go#L78-L100)), the relay computes its response value **B** using the weak passphrase and the SIEC elliptic curve:

```go
// 1️⃣  Read A from the client
Abytes, err := c.Receive()
...
// 2️⃣  Compute B and send it back
B, err := pake.InitCurve(weakKey, 1, "siec")
...
err = c.Send(B.Bytes())

```

Both endpoints then derive an identical **strongKey** via `B.SessionKey()`, establishing a symmetric encryption key that protects all subsequent handshake messages.

## Password Verification and Session Encryption

After the PAKE exchange, the relay authenticates the client using the derived strong key. The client encrypts its password with `strongKeyForEncryption`, and the server decrypts and validates it against the relay's configured password (default `"pass123"`).

This verification occurs in [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go) (lines [119-133](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go#L119-L133)):

```go
// 3️⃣  Receive encrypted password
passwordBytesEnc, err := c.Receive()
passwordBytes, err := crypt.Decrypt(passwordBytesEnc, strongKeyForEncryption)
if strings.TrimSpace(string(passwordBytes)) != strings.TrimSpace(s.password) {
    // mismatch → send encrypted error and abort
}

```

A mismatch results in immediate termination, while successful validation proceeds to the banner exchange phase.

## Banner Exchange and NAT Traversal

Once authenticated, the relay transmits a banner containing the client's external IP address and available relay ports. This banner enables NAT traversal by informing both peers about network endpoints.

The server constructs the banner in [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go) (lines [137-147](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go#L137-L147)) using the format `banner|||<client-IP>`:

```go
bSend, err := crypt.Encrypt([]byte(banner+"|||"+c.Connection().RemoteAddr().String()),
                           strongKeyForEncryption)
err = c.Send(bSend)

```

This encrypted payload tells the client which ports (`c.Options.RelayPorts`) to use for the actual data transfer and provides the external IP address visible to the other peer.

## Room Creation and Peer Pairing

Following the banner exchange, the client submits a room identifier—a random string that determines pairing. The relay maintains a thread-safe map (`roomMap`) tracking room states.

In [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go) (lines [152-186](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go#L152-L186)), the relay handles room assignment:

```go
roomBytes, err := crypt.Decrypt(enc, strongKeyForEncryption)
room = string(roomBytes)

s.rooms.Lock()
if _, ok := s.rooms.rooms[room]; !ok {
    // first participant – store connection and reply "ok"
    s.rooms.rooms[room] = roomInfo{first: c, opened: time.Now()}
    // …
    return
}
if s.rooms.rooms[room].full {
    // room already has two participants → reject
}

```

When a second client joins an existing room, the relay marks it as **full**, retrieves the first connection, and establishes a full-duplex pipe. The piping logic (lines [238-260](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go#L238-L260)) launches goroutines copying bytes bidirectionally:

```go
otherConnection := s.rooms.rooms[room].first
go func(com1, com2 *comm.Comm) {
    pipe(com1.Connection(), com2.Connection())
}(otherConnection, c)

```

The `pipe` function (lines [87-112](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go#L87-L112)) continues streaming raw bytes until either connection closes.

## Connection Teardown and Cleanup

When a transfer completes or a connection drops, the relay invokes `deleteRoom` to release resources. This function, located at lines [390-403](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go#L390-L403) in [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go), closes both underlying TCP connections and removes the room entry from the map to prevent memory leaks.

## WebRelay Bridge for Browser Clients

For browser-based transfers, Croc includes a WebSocket-to-TCP bridge in [`src/webrelay/webrelay.go`](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go). This component accepts WebSocket connections on `/ws?port=<allowed-port>` and forwards traffic to the upstream TCP relay.

The bridge validates requests (lines [31-45](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go#L31-L45)) and uses `copyStream` (lines [71-89](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go#L71-L89)) to shuttle data bidirectionally between the WebSocket client and the TCP relay:

```go
// Bridge simply forwards bytes; PAKE and encryption happen inside the TCP relay

```

Unlike the TCP relay, the WebRelay performs no cryptographic handshaking—it merely transports the already-encrypted byte stream between the browser and the core relay server.

## Practical Implementation Examples

### Connecting via the Go Library

You can interact with the relay directly using the `tcp` package:

```go
package main

import (
	"log"
	"time"

	"github.com/schollz/croc/v10/src/tcp"
)

func main() {
	// Connect to the public relay (default host & port)
	relay := "croc.schollz.com:9009"
	password := "pass123"
	room := "my‑unique‑room"

	// Establish a secure PAKE‑based connection
	c, banner, ip, err := tcp.ConnectToTCPServer(relay, password, room,
		5*time.Second) // optional timeout
	if err != nil {
		log.Fatalf("relay connect failed: %v", err)
	}
	defer c.Close()

	log.Printf("Connected! Banner: %s, External IP: %s", banner, ip)

	// From here you can send/receive raw bytes – Croc will pipe them to the peer.
	// Example: send a short message
	_, _ = c.Send([]byte("hello from client"))
}

```

The `tcp.ConnectToTCPServer` function (lines [37-49](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go#L37-L49)) handles the complete PAKE handshake, password verification, and room registration automatically.

### Running a Local Relay

For testing or private networks, run a standalone relay:

```bash

# Start a local relay listening on 0.0.0.0:9100

croc relay --host 0.0.0.0 --ports 9100 --relay-password secret

```

The CLI parses these flags and initializes the TCP server via [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) (lines [147-155](https://github.com/schollz/croc/blob/main/src/croc/croc.go#L147-L155)).

### Browser Client via WebRelay

To enable web-based transfers:

```bash

# Serve the web UI + bridge on localhost:9014

croc webrelay --listen-address 127.0.0.1:9014 \
    --relay croc.schollz.com --relay-password pass123

```

Navigate to `http://127.0.0.1:9014` to access the UI, which connects via WebSocket to the bridge at `/ws?port=9009`.

## Key Source Files

The relay protocol spans several critical files in the `schollz/croc` repository:

- **[`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go)** – Core TCP relay server implementing PAKE handshake, password verification, room management, and bidirectional piping.
- **[`src/webrelay/webrelay.go`](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go)** – HTTP server serving the static UI and implementing the WebSocket-to-TCP bridge for browser clients.
- **[`src/comm/comm.go`](https://github.com/schollz/croc/blob/main/src/comm/comm.go)** – Communication wrapper around `net.Conn` providing `Send` and `Receive` primitives used throughout the relay.
- **[`src/crypt/crypt.go`](https://github.com/schollz/croc/blob/main/src/crypt/crypt.go)** – Symmetric encryption utilities (`Encrypt`, `Decrypt`) protecting handshake messages and payloads.
- **[`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go)** – High-level client orchestration coordinating CLI commands with relay connections and data transfer logic.

## Summary

- **PAKE Handshake**: The relay uses `pake.InitCurve` with the SIEC curve to derive strong symmetric keys from weak passwords, implemented in [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go).
- **Password Verification**: Clients encrypt passwords using the PAKE-derived key; the relay validates against its configured secret before allowing room access.
- **Room-Based Pairing**: The relay maintains a `roomMap` where the first connecting client waits until a second client joins the same room, at which point it creates a full-duplex byte pipe.
- **Opaque Relay**: The server never interprets transferred data—it only encrypts handshakes and pipes raw bytes between paired connections.
- **WebSocket Bridge**: Browser clients connect through [`src/webrelay/webrelay.go`](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go), which forwards WebSocket traffic to the TCP relay without performing cryptographic operations.

## Frequently Asked Questions

### What encryption algorithm does Croc's relay use for the initial handshake?

Croc's relay communication protocol uses Password-Authenticated Key Exchange (PAKE) with the SIEC (Simplified Isogeny Elliptic Curve) implementation. The `pake.InitCurve` function generates ephemeral public keys that allow both parties to derive an identical strong symmetric key without transmitting the actual password over the network.

### How does the relay handle NAT traversal between two peers?

During the banner exchange phase, the relay transmits the client's external IP address (extracted from `c.Connection().RemoteAddr()`) back to the client encrypted within the banner payload. This allows both peers to discover their public-facing endpoints and coordinate direct connections if possible, while falling back to the relay's byte-piping mechanism when necessary.

### What happens if a third client tries to join an existing room?

According to the room management logic in [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go), the relay checks if a room is already marked as `full` when a new connection attempts to join. If two participants already occupy the room, the relay rejects the third connection attempt, ensuring that only two endpoints can communicate through any given room identifier.

### Does the WebRelay bridge perform any encryption or authentication?

No. The WebRelay bridge in [`src/webrelay/webrelay.go`](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go) acts as a transparent proxy that merely forwards bytes between WebSocket clients and the upstream TCP relay. All PAKE handshakes, password verification, and payload encryption occur within the TCP relay itself ([`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go)), meaning the bridge handles only pre-encrypted traffic.