Zero-Dependency Encryption: X25519 + AES-GCM

Zero-Dependency Encryption: X25519 + AES-GCM

Every encryption library is a dependency. Every dependency is an attack surface. When OpenSSL disclosed Heartbleed in 2014, it affected an estimated 17% of all TLS servers on the internet -- not because of a flaw in the cryptographic algorithms, but because of a buffer over-read in a library that virtually every project imported without auditing. The xz Utils backdoor in 2024 demonstrated that even compression libraries can become supply chain weapons when a determined attacker gains commit access.

For AI agents, the stakes are higher. Agents exchange model parameters, training data, task instructions, and coordination messages. They often run unattended, without a human to notice when something looks wrong. If the encryption library they depend on is compromised, every message between every agent in the fleet is exposed -- silently, at scale.

Pilot Protocol takes a different approach: zero external cryptographic dependencies. The entire encryption stack -- key exchange, symmetric encryption, nonce generation, and authenticated encryption -- uses Go's standard library exclusively. No OpenSSL. No libsodium. No BoringSSL. No third-party modules. The audit surface is Go's crypto package, maintained by the Go team and reviewed by Google's security engineers.

This article walks through the complete encryption implementation: how X25519 key exchange establishes shared secrets, how AES-256-GCM provides authenticated encryption, how nonces are managed to prevent replay attacks, and how all of this fits into Pilot Protocol's tunnel layer.

Why Zero-Dependency Cryptography Matters

The argument for zero-dependency encryption is not about reinventing the wheel. It is about minimizing the supply chain attack surface while using battle-tested algorithms that already ship with the language runtime.

Supply Chain Risk

Every go get or npm install pulls code from a public repository into your build. Most developers never read that code. Most security teams never audit it. A single compromised dependency -- even a transitive one -- can inject a backdoor into every binary that imports it.

Go's standard library is different. It ships with the compiler. It is versioned with the language. It is maintained by a known team with a strong security track record. When you use crypto/ecdh and crypto/aes, you are using the same code that secures Go's own TLS implementation. There is no external supply chain to compromise.

Binary Size and Audit Surface

External crypto libraries often pull in hundreds of thousands of lines of code. OpenSSL alone is over 500,000 lines of C. Auditing this is a multi-year, multi-million-dollar effort that most organizations never complete.

Go's crypto/ecdh package for X25519 is roughly 300 lines of Go. The crypto/aes and crypto/cipher packages for AES-GCM add another ~1,500 lines. The total audit surface for Pilot's encryption is under 2,000 lines of well-documented, type-safe Go code. A single security engineer can review it in a day.

Reproducible Builds

With zero external dependencies, the encryption code is fully determined by the Go version. Every developer, CI system, and deployment pipeline that uses the same Go version produces identical binaries. There is no version skew, no dependency resolution ambiguity, and no risk of pulling a compromised version from a package registry.

Practical impact: Pilot Protocol's entire binary -- including the daemon, tunnel layer, encryption, NAT traversal, and CLI -- compiles to a single static binary with no dynamic library dependencies. You can copy it to any Linux, macOS, or Windows machine and run it. No apt install, no brew install, no runtime dependencies to manage.

X25519 Key Exchange: Ephemeral Shared Secrets

X25519 (RFC 7748) is an elliptic curve Diffie-Hellman function on Curve25519. It allows two parties to derive a shared secret from their respective key pairs without ever transmitting the secret itself. The shared secret is then used to derive the AES-256 encryption key.

How It Works

When two Pilot agents establish a tunnel, each side generates an ephemeral X25519 key pair. "Ephemeral" means the keys are created fresh for every tunnel establishment. Even if an attacker captures and decrypts one session's traffic (by somehow obtaining the shared secret), they cannot decrypt any other session because every session uses different keys. This property is called forward secrecy.

// Generate ephemeral X25519 key pair using Go's crypto/ecdh
curve := ecdh.X25519()

// Generate private key (random 32 bytes)
privateKey, err := curve.GenerateKey(rand.Reader)
// privateKey.PublicKey() returns the 32-byte public key

// Send public key to peer, receive peer's public key
// Both sides now have: their own private key + peer's public key

// Derive shared secret using ECDH
sharedSecret, err := privateKey.ECDH(peerPublicKey)
// sharedSecret is 32 bytes -- used as the AES-256 key

The mathematics of elliptic curve Diffie-Hellman guarantee that both sides derive the same shared secret, even though neither transmitted their private key. An eavesdropper who captures both public keys cannot derive the shared secret without solving the elliptic curve discrete logarithm problem, which is computationally infeasible.

Key Exchange on the Wire

Pilot transmits public keys using PILK frames (magic bytes 0x50494C4B -- "Pilot Key exchange"). The exchange adds a single round-trip to tunnel establishment:

# Key exchange wire format

Agent A                                          Agent B
   |                                                |
   |--- PILK frame [A's 32-byte public key] ------->|
   |                                                |
   |<--- PILK frame [B's 32-byte public key] -------|
   |                                                |
   |  Both sides compute:                           |
   |  sharedSecret = ECDH(myPrivate, peerPublic)    |
   |                                                |
   |  Both derive the same 32-byte AES-256 key      |
   |                                                |
   |<========= PILS encrypted traffic ==============>|

# PILK = 0x50494C4B ("Pilot Key"), 4 bytes magic + 32 bytes public key
# Total key exchange overhead: 72 bytes (36 bytes per direction)

The entire key exchange adds approximately 0.3ms to connection setup time. This is measured on cross-region connections between GCP VMs. On local networks, key exchange completes in under 0.1ms. The cost is paid once per tunnel, not per packet.

AES-256-GCM: Authenticated Encryption

After key exchange, all tunnel frames are encrypted with AES-256-GCM (RFC 5288). GCM (Galois/Counter Mode) is an authenticated encryption mode that provides both confidentiality (the data is encrypted) and integrity (any modification is detected). It is the same cipher suite used by TLS 1.3 for HTTPS traffic worldwide.

Encryption in Go

The implementation uses Go's crypto/aes package for the AES block cipher and crypto/cipher for the GCM mode. Here is the simplified flow:

// Create AES cipher from 32-byte shared secret
block, err := aes.NewCipher(sharedSecret)

// Wrap in GCM mode for authenticated encryption
aesGCM, err := cipher.NewGCM(block)

// Encrypt a packet (Seal)
nonce := generateNonce() // 12 bytes: [4-byte random prefix][8-byte counter]
ciphertext := aesGCM.Seal(nil, nonce, plaintext, nil)
// ciphertext includes a 16-byte authentication tag appended automatically

// Decrypt a packet (Open)
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
// Returns error if authentication tag does not match
// (packet was modified in transit or nonce was wrong)

The Seal function encrypts the plaintext and appends a 16-byte GCM authentication tag. The Open function verifies the tag before decrypting. If even a single bit of the ciphertext or nonce has been modified, Open returns an error and the packet is discarded. There is no "decrypt anyway" option -- integrity verification is mandatory.

Encrypted Frame Format

Encrypted tunnel frames use PILS magic bytes (0x50494C53 -- "Pilot Secure") to distinguish them from plaintext PILT frames:

# PILT frame (plaintext, used before key exchange)
50 49 4C 54    # Magic: "PILT"
XX XX          # Frame length
[pilot packet] # 34-byte header + payload (plaintext)

# PILS frame (encrypted, used after key exchange)
50 49 4C 53    # Magic: "PILS"
[12 bytes]     # Nonce (4-byte random prefix + 8-byte counter)
[N bytes]      # AES-256-GCM ciphertext
[16 bytes]     # GCM authentication tag (appended by Seal)

# Overhead per packet: 4 (magic) + 12 (nonce) + 16 (auth tag) = 32 bytes

The 32-byte overhead per packet is modest. For a typical agent message of 1,024 bytes, encryption adds 3.1% overhead. For larger payloads near the MTU limit, overhead drops below 2%. The per-packet encryption time is approximately 5 microseconds on modern hardware -- negligible compared to network latency.

Nonce Management: Preventing Replay and Reuse

AES-GCM has a well-known weakness: if the same nonce is ever used twice with the same key, the encryption is broken. An attacker who captures two ciphertexts encrypted with the same nonce and key can XOR them together to cancel out the keystream and recover both plaintexts. This is not a theoretical concern -- it is a practical attack that has been demonstrated against real systems.

Pilot Protocol prevents nonce reuse with a two-part nonce construction:

ComponentSizePurpose
Random prefix4 bytesUnique per connection, generated from crypto/rand
Counter8 bytesIncremented for every packet, starts at 0
// Nonce construction
nonce := make([]byte, 12)            // 12-byte GCM nonce
copy(nonce[0:4], connectionPrefix)   // 4 bytes: random, unique per connection
binary.BigEndian.PutUint64(nonce[4:12], counter) // 8 bytes: monotonic counter
counter++

// With 4-byte random prefix:
//   - Different connections almost certainly use different prefixes
//   - Same connection never reuses a counter value
//   - Even if two connections share a key (they don't), nonces differ

The random prefix is generated once when the secure connection is established. It ensures that even if two connections between the same agents somehow derived the same shared secret (impossible with ephemeral keys, but defense-in-depth), they would still use different nonces. The 8-byte counter supports 2^64 packets per connection -- enough for continuous transmission at gigabit speeds for millions of years.

Why Not Just Use a Counter?

A simple counter starting at 0 would be unique within a single connection, but it creates a risk across connections. If an agent reconnects and re-derives the same key (e.g., due to a PRNG failure), counter 0 would be reused with the same key. The random prefix eliminates this risk by adding 32 bits of randomness to every nonce. Combined with ephemeral X25519 keys (which ensure different shared secrets per connection), nonce reuse is effectively impossible.

The Complete Handshake Flow

Here is the full sequence when two Pilot agents establish an encrypted tunnel, from first contact to encrypted data flow:

# Complete encrypted tunnel establishment

Agent A                                          Agent B
   |                                                |
   |  1. Generate ephemeral X25519 key pair          |  1. Generate ephemeral X25519 key pair
   |     privA, pubA = X25519.GenerateKey()         |     privB, pubB = X25519.GenerateKey()
   |                                                |
   |  2. Exchange public keys via PILK frames        |
   |--- PILK [pubA, 32 bytes] --------------------->|
   |<--- PILK [pubB, 32 bytes] ---------------------|
   |                                                |
   |  3. Derive shared secret (both sides independently)
   |     secret = ECDH(privA, pubB)                 |     secret = ECDH(privB, pubA)
   |     // Both compute the same 32-byte secret   |
   |                                                |
   |  4. Create AES-256-GCM cipher                  |
   |     block = AES(secret)                        |     block = AES(secret)
   |     gcm = GCM(block)                           |     gcm = GCM(block)
   |                                                |
   |  5. Generate random nonce prefix                |
   |     prefixA = rand(4 bytes)                    |     prefixB = rand(4 bytes)
   |                                                |
   |  6. Encrypted communication begins              |
   |=== PILS [nonceA | Seal(pilot_packet)] ========>|
   |<=== PILS [nonceB | Seal(pilot_packet)] ========|
   |                                                |
   |  All subsequent traffic is AES-256-GCM encrypted
   |  Authentication tags prevent tampering
   |  Counter nonces prevent replay

The entire handshake adds one round-trip (~0.3ms on typical networks). After the handshake, every packet is encrypted and authenticated with ~5 microseconds of overhead. The agent developer never calls any encryption API -- the daemon handles it automatically when connections target port 443 or when encrypt-by-default is enabled.

Encrypt by Default: Port 443

Pilot Protocol designates port 443 as the secure port. Any connection to port 443 automatically performs X25519 key exchange and enables AES-256-GCM encryption. No configuration needed, no flags to remember, no TLS certificates to provision.

# Connecting to port 443 enables encryption automatically
$ pilotctl connect data-agent --port 443 --message "sensitive payload"
Key exchange: X25519 (0.28ms)
Cipher: AES-256-GCM
Nonce prefix: a3b2c1d0
Message encrypted and delivered

# Port 80 for non-sensitive traffic (still within the overlay)
$ pilotctl connect status-monitor --port 80 --message "heartbeat"
Connected (plaintext within tunnel)
Message delivered

The encrypt-by-default design means that developers who use port 443 get end-to-end encryption without writing any cryptographic code. The Go standard library handles the heavy lifting. The daemon handles the key exchange. The developer handles the application logic.

Performance Characteristics

Encryption overhead is measured at two levels: the one-time cost of key exchange, and the per-packet cost of AES-GCM operations.

OperationTimeNotes
X25519 key generation~0.05msOne per tunnel establishment
ECDH shared secret derivation~0.12msOne per tunnel establishment
PILK frame round-trip~0.15ms (LAN) / ~40ms (WAN)Network-dominated, not CPU
Total key exchange~0.3ms (LAN) / ~40ms (WAN)One-time cost per tunnel
AES-GCM Seal (encrypt)~5 microseconds / 1KBPer-packet, hardware-accelerated (AES-NI)
AES-GCM Open (decrypt)~5 microseconds / 1KBPer-packet, includes auth tag verification
Per-packet overhead32 bytes4 (magic) + 12 (nonce) + 16 (auth tag)

On modern CPUs with AES-NI hardware acceleration (virtually all x86 processors since 2010 and ARM processors since ARMv8), AES-GCM runs at memory bandwidth speeds. The 5-microsecond per-packet cost is negligible compared to network transmission time, which is typically measured in milliseconds. Even on a Raspberry Pi without AES-NI, per-packet encryption completes in under 50 microseconds.

The bottom line: encryption adds effectively zero observable latency to agent communication. The key exchange adds one network round-trip. Per-packet encryption is invisible at the application layer.

Why Not Just Use TLS?

TLS is the obvious choice for encryption, and it is an excellent protocol. But TLS was designed for TCP connections between a client and a server, not for UDP-based overlay networks between peers. Using TLS in Pilot would introduce several problems:

Pilot's encryption is narrowly scoped: X25519 for key exchange, AES-256-GCM for symmetric encryption, nothing else. There is no certificate parsing, no cipher suite negotiation, no protocol version fallback. This narrow scope is the point -- fewer features means fewer bugs, fewer CVEs, and a smaller attack surface.

Security Properties Summary

For how encryption integrates with Pilot's trust model and Ed25519 identity system, see Secure AI Agent Communication With Zero Trust. For the full protocol architecture including addressing, transport, and services, see How Pilot Protocol Works. For real-world applications of this encryption in sensitive environments, see Secure Research Collaboration: Share Models, Not Data and Building a Private Agent Network for Your Company.

Try Pilot Protocol

End-to-end encryption for AI agents with zero external dependencies. X25519 key exchange, AES-256-GCM authenticated encryption, and forward secrecy -- all built on Go's standard library.

View on GitHub