Core Architecture of the croc Project: Secure P2P File Transfer in Go
The croc file transfer tool centers around a monolithic Client struct in src/croc/croc.go that orchestrates secure peer-to-peer transfers through PAKE key exchange, relay-assisted TCP connections, and AES-GCM encryption, enabling both CLI and programmatic library usage.
The croc project is a cross-platform, peer-to-peer file transfer tool written in Go that eliminates the need for server-side infrastructure while maintaining end-to-end encryption. Understanding the core architecture of the croc project reveals how it balances simplicity with security, using a centralized relay architecture for NAT traversal while keeping data transfer direct between peers. At its heart, a single Client object manages the entire lifecycle of a transfer, from discovery and handshake to encrypted data streaming and reconnection logic.
The Central Client Architecture
The architecture revolves entirely around the Client struct defined in src/croc/croc.go. This object encapsulates all runtime state including connection handles, file metadata, progress tracking, and configuration options.
The Client implements two high-level workflows:
Send– Initiates transfers by gathering file metadata, establishing secure connections, and streaming data chunks.Receive– Listens for incoming connections, performs handshake verification, and writes received chunks to disk.
Both workflows share the same internal state machine, allowing croc to function as either a sender or receiver without separate binaries. The Options struct (defined in the same file) captures all user-configurable parameters including relay addresses, encryption settings, compression flags, and bandwidth throttling.
Connection Layer: TCP and Communication Abstractions
croc abstracts network complexity through two distinct layers. The TCP layer (src/tcp/tcp.go) manages low-level socket connections to public relays or locally-started relays, handling multiplexed streams and reconnection logic through functions like ConnectToTCPServer.
Above this sits the comm.Comm abstraction (src/comm/comm.go), a thin wrapper that provides:
- Message framing – Ensures complete JSON message delivery over TCP streams.
- Heartbeat handling – Maintains connection viability during idle periods.
- Send/Receive primitives –
comm.Sendandcomm.Receivemethods used throughout the codebase.
This layered approach allows the higher-level Client to focus on transfer logic while the lower layers handle network resilience.
Security Architecture: PAKE and Encryption
Security is implemented through a two-phase cryptographic handshake. First, the PAKE (Password-Authenticated Key Exchange) handshake establishes a shared secret without transmitting the password itself. This occurs in croc.go through senderWaitForHandshake on the sender side and corresponding receiver logic.
Once the PAKE exchange completes, all subsequent communication uses AES-GCM symmetric encryption implemented in src/crypt/crypt.go. The crypt.Encrypt and crypt.Decrypt functions protect file chunks and control messages, ensuring that even if relay servers are compromised, transferred data remains inaccessible.
Message Protocol and File Handling
The message package (src/message/message.go) defines the JSON-encoded protocol used after encryption. Key message types include:
- PAKE messages – Exchange public parameters for key derivation.
- File metadata –
FileInfostructs containing name, size, hash, permissions, and symlink targets. - Data chunks – Binary payloads with sequence identifiers.
- Acknowledgments – Receipt confirmations for flow control.
Before transfer begins, GetFilesInfo in croc.go walks source paths, applies .gitignore rules, handles exact-path exclusions, and optionally creates zip archives for folders. This preprocessing ensures the receiver knows exactly what to expect before bytes flow.
Relay and Discovery Mechanisms
croc solves NAT traversal through a hybrid approach. By default, it connects to public relay servers, but it can also spawn local relays for LAN transfers using src/webrelay/webrelay.go. The local relay advertises its presence via peerdiscovery, allowing devices on the same network to connect directly without internet bandwidth.
If LAN discovery fails, the client falls back to the public relay specified in Options.RelayAddress. The webrelay implementation handles HTTP signaling for the optional web UI (getcroc.com), though this is separate from the core TCP relay functionality.
Resilience and State Management
Transfer reliability relies on sophisticated reconnection logic. The transferWithReconnect function in croc.go automatically attempts reconnection to the same or alternate relays upon network interruption, preserving transfer state including progress bars and file positions.
Additional utilities in src/utils/utils.go provide:
- Rate limiting – Throttling bandwidth usage.
- Progress tracking – Visual feedback via progress bars.
- QR-code generation – For easy mobile device pairing.
- Clipboard integration – Automatic secret copying.
Using croc: CLI and Library Patterns
The architecture supports two primary usage modes: command-line interface and Go library integration.
Command-Line Usage
The most common interaction occurs through the CLI defined in main.go:
# Sender side – transfer a directory
croc send /path/to/myfolder
# Receiver side – receive the files (copy the secret printed by the sender)
croc receive
Programmatic Usage as a Library
For embedded applications, import the croc package directly:
package main
import (
"fmt"
"github.com/schollz/croc/v10/src/croc"
"github.com/schollz/croc/v10/src/models"
)
func main() {
// Create a client with custom options
opts := croc.Options{
IsSender: true,
SharedSecret: "demo-secret-1234",
RelayAddress: models.DEFAULT_RELAY,
RelayPassword: models.DEFAULT_PASSPHRASE,
Debug: true,
}
cl, err := croc.New(opts)
if err != nil {
panic(err)
}
// Gather file information
files, empty, total, err := croc.GetFilesInfo([]string{"example.txt"}, false, false, nil)
if err != nil {
panic(err)
}
// Start the transfer
if err = cl.Send(files, empty, total); err != nil {
fmt.Println("transfer failed:", err)
} else {
fmt.Println("transfer completed")
}
}
Receiving Programmatically
package main
import (
"fmt"
"github.com/schollz/croc/v10/src/croc"
"github.com/schollz/croc/v10/src/models"
)
func main() {
// Receiver options – use the same secret as the sender
opts := croc.Options{
IsSender: false,
SharedSecret: "demo-secret-1234",
RelayAddress: models.DEFAULT_RELAY,
RelayPassword: models.DEFAULT_PASSPHRASE,
}
cl, err := croc.New(opts)
if err != nil {
panic(err)
}
// Start receiving
if err = cl.Receive(); err != nil {
fmt.Println("receive failed:", err)
} else {
fmt.Println("receive completed")
}
}
Summary
- The core architecture of the croc project centers on a single
Clientstruct insrc/croc/croc.gothat manages both sending and receiving workflows. - TCP connections (
src/tcp/tcp.go) and thecomm.Commwrapper (src/comm/comm.go) provide reliable transport with automatic reconnection support. - PAKE key exchange and AES-GCM encryption (
src/crypt/crypt.go) secure all transfers without requiring certificate infrastructure. - The JSON message protocol (
src/message/message.go) handles metadata exchange and chunked data transmission. - Hybrid relay architecture supports both public relays and local LAN discovery via
src/webrelay/webrelay.go. - The codebase functions as both a standalone CLI and an importable Go library, with all state contained within the
Clientobject.
Frequently Asked Questions
How does croc establish secure connections without prior key exchange?
croc uses PAKE (Password-Authenticated Key Exchange) to derive encryption keys from the user-provided shared secret. As implemented in src/croc/croc.go, the PAKE handshake allows both parties to generate identical session keys (kA/kB) without transmitting the actual password across the network, preventing man-in-the-middle attacks even when using public relay servers.
What happens if the network connection drops during a file transfer?
The transferWithReconnect function in src/croc/croc.go automatically attempts to re-establish the TCP connection to the same or alternative relays. This reconnection logic preserves the transfer state, allowing transfers to resume from the last acknowledged chunk rather than restarting from the beginning.
Can croc be integrated into existing Go applications as a library?
Yes. The Client struct in src/croc/croc.go is designed for programmatic use through the croc.New() constructor and Send() / Receive() methods. Applications can configure transfer behavior via the Options struct, including custom relay addresses, debug logging, and compression settings without invoking the CLI.
How does croc handle large files or directories efficiently?
Before transfer, GetFilesInfo in src/croc/croc.go enumerates all files and breaks them into chunks defined in src/models/constants.go. The message protocol (src/message/message.go) streams these chunks with flow control via acknowledgments, while TCP multiplexing in src/tcp/tcp.go maintains concurrent channels for data and control signals.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →