Version: 1.1 Go Version: 1.26+
- System Overview
- Package Structure
- Core Components
- Protocol Design
- Key Management
- Security Architecture
- Performance Considerations
- API Reference
Quantum-Go is a quantum-resistant VPN encryption library implementing the Cascaded Hybrid KEM (CH-KEM) protocol. It provides post-quantum security through ML-KEM-1024 while maintaining classical security guarantees through X25519.
- Defense in Depth: Multiple cryptographic layers ensure security if one is compromised
- Minimal Dependencies: Uses Go standard library where possible
- Constant-Time Operations: Cryptographic operations avoid timing side-channels
- Clear Abstractions: Separate concerns between crypto, protocol, and transport layers
The protocol draws on published post-quantum cryptography research - including work on PQ-WireGuard variants, KEM binding properties, and formal verification methodologies - while maintaining an original architecture based on TLS 1.3-structured handshakes rather than the Noise Framework. See Design Influences for detailed attribution.
| Component | Technology |
|---|---|
| Language | Go 1.26+ |
| Post-Quantum KEM | ML-KEM-1024 (crypto/mlkem) |
| Classical ECDH | X25519 (crypto/ecdh) |
| KDF | SHAKE-256 (golang.org/x/crypto/sha3) |
| AEAD | AES-256-GCM, ChaCha20-Poly1305 |
quantum-go/
├── cmd/
│ └── quantum-vpn/ # CLI application
│ └── main.go
├── pkg/
│ ├── chkem/ # Cascaded Hybrid KEM
│ │ ├── chkem.go # Core implementation
│ │ └── chkem_test.go # Unit tests
│ ├── tunnel/ # VPN tunnel
│ │ ├── session.go # Session management
│ │ ├── handshake.go # Key exchange protocol
│ │ ├── transport.go # Encrypted transport
│ │ └── tunnel_test.go # Tests
│ ├── crypto/ # Cryptographic primitives
│ │ ├── mlkem.go # ML-KEM-1024 wrapper
│ │ ├── x25519.go # X25519 ECDH
│ │ ├── kdf.go # Key derivation
│ │ ├── aead.go # Authenticated encryption
│ │ ├── random.go # Secure random
│ │ └── crypto_test.go # Tests
│ └── protocol/ # Wire protocol
│ ├── version.go # Protocol versioning
│ ├── messages.go # Message types
│ └── codec.go # Serialization
├── internal/
│ ├── constants/ # Security parameters
│ │ └── constants.go
│ └── errors/ # Custom errors
│ └── errors.go
├── docs/ # Documentation
├── test/ # Integration & benchmarks
└── go.mod
The Cascaded Hybrid KEM combines X25519 and ML-KEM-1024.
// Generate a new key pair
kp, err := chkem.GenerateKeyPair()
// Encapsulate (sender side)
ciphertext, sharedSecret, err := chkem.Encapsulate(recipientPublicKey)
// Decapsulate (recipient side)
sharedSecret, err := chkem.Decapsulate(ciphertext, keyPair)Data Flow:
Encapsulation:
┌─────────────────────────────────────────────────────────────┐
│ │
│ Recipient's Public Key ──┬──> X25519 DH ──> K_x │
│ │ │
│ Ephemeral X25519 Key ────┘ │
│ │
│ Recipient's ML-KEM Key ──────> ML-KEM Encaps ──> K_m │
│ │
│ Transcript = H(pk || ct) │
│ │
│ K_final = SHAKE-256(K_x || K_m || transcript) │
│ │
└─────────────────────────────────────────────────────────────┘
The tunnel package provides secure communication channels.
Session States:
SessionStateNew ─────> SessionStateHandshaking ─────> SessionStateEstablished
│ │ │
│ │ ├───> SessionStateRekeying
│ │ │ │
│ ▼ │ │
└─────────────> SessionStateClosed <───────────────────┴─────────┘
Components:
| Component | Responsibility |
|---|---|
| Session | State, encryption keys, statistics |
| Handshake | CH-KEM key exchange state machine |
| Transport | Encrypted message send/receive |
Low-level cryptographic operations wrapped with consistent error handling.
| Function | Description |
|---|---|
GenerateMLKEMKeyPair() |
Generate ML-KEM-1024 key pair |
MLKEMEncapsulate() |
Encapsulate shared secret |
MLKEMDecapsulate() |
Decapsulate shared secret |
GenerateX25519KeyPair() |
Generate X25519 key pair |
X25519() |
Compute ECDH shared secret |
DeriveKey() |
SHAKE-256 key derivation |
NewAEAD() |
Create AEAD cipher |
Initiator Responder
│ │
│───────── ClientHello ───────────────>│
│ • Protocol version │
│ • Random (32B) │
│ • CH-KEM public key (1600B) │
│ • Cipher suites │
│ │
│<──────── ServerHello ────────────────│
│ • Protocol version │
│ • Random (32B) │
│ • Session ID (16B) │
│ • CH-KEM ciphertext (1600B) │
│ • Selected cipher suite │
│ │
│ [Both derive shared secret K] │
│ │
│───────── ClientFinished ────────────>│
│ • Encrypted(verify_data) │
│ • verify_data = KDF(K, transcript) │
│ │
│<──────── ServerFinished ─────────────│
│ • Encrypted(verify_data) │
│ • verify_data = KDF(K, transcript) │
│ │
│═══════ Tunnel Established ═══════════│
All messages follow this structure:
┌──────────┬──────────────┬───────────────────┐
│ Type │ Length │ Payload │
│ (1 byte) │ (4 bytes BE) │ (variable) │
└──────────┴──────────────┴───────────────────┘
Message Types:
| Type | Value | Description |
|---|---|---|
| ClientHello | 0x01 | Initiate handshake |
| ServerHello | 0x02 | Respond to handshake |
| ClientFinished | 0x03 | Client confirmation |
| ServerFinished | 0x04 | Server confirmation |
| Data | 0x10 | Encrypted payload |
| Rekey | 0x11 | Key rotation (AEAD-encrypted payload) |
| Ping | 0x12 | Keepalive request |
| Pong | 0x13 | Keepalive response |
| Close | 0x14 | Graceful close |
| Alert | 0xF0 | Error condition |
Master Secret (32B)
│
├──> SHAKE-256("CH-KEM-VPN-Handshake") ──> Handshake Keys
│
├──> SHAKE-256("CH-KEM-VPN-Traffic") ──> Traffic Keys
│
├──> SHAKE-256("CH-KEM-VPN-Rekey") ──> Ratcheted Rekey Secret
│ input: [old_master_secret || fresh_KEM_secret]
│
└──> SHAKE-256("CH-KEM-VPN-ClientFinished/ServerFinished")
input: [shared_secret || transcript]
output: verify_data (32B)
Session Resumption Key Derivation (PSK + ECDHE):
Ticket Secret (PSK, 32B)
│
└──> Fresh CH-KEM Exchange ──> Fresh Secret (32B)
│
└──> SHAKE-256("CH-KEM-VPN-Resumption")
input: [PSK || Fresh Secret]
output: Resumed Master Secret (32B)
CH-KEM Key Pair (Long-term)
│
└──> Session Master Secret (per session)
│
├──> Initiator Write Key (32B)
├──> Responder Write Key (32B)
├──> Initiator Write IV (12B)
└──> Responder Write IV (12B)
Sessions automatically rekey when:
- Nonce counter approaches 2^28 (90% of 2^28)
- Bytes transmitted exceed 1 GB
- Session duration exceeds 1 hour
The rekey protocol performs a fresh CH-KEM exchange and ratchets the new secret by mixing the current master secret with the fresh KEM output:
new_master = SHAKE-256("CH-KEM-VPN-Rekey", [old_master || fresh_KEM_secret])
This ensures forward secrecy: compromise of a single rekey does not expose prior traffic, and the fresh KEM exchange prevents future traffic from being compromised even if the current master secret leaks.
All sensitive key material is zeroized when:
- Session closes
- Keys are rotated
- Handshake completes (intermediate keys)
Zeroization uses runtime.KeepAlive to prevent the compiler from eliminating
zeroing loops as dead stores. Constant-time comparison uses crypto/subtle from
the Go standard library.
// Example zeroization
func (s *Session) Close() {
crypto.Zeroize(s.masterSecret)
s.LocalKeyPair.Zeroize()
}Note: Go 1.26 introduces the experimental
runtime/secretpackage for hardware-backed secure erasure of cryptographic temporaries. Future versions may adopt this for stronger guarantees on supported platforms (amd64/arm64 Linux).
| Threat | Mitigation |
|---|---|
| Quantum computer | ML-KEM-1024 post-quantum security |
| Classical cryptanalysis | X25519 hybrid approach |
| Harvest-now-decrypt-later | Immediate quantum resistance |
| Man-in-the-middle | Transcript binding, authentication |
| Replay attacks | Sequence numbers, sliding window |
| Side-channel attacks | Constant-time crypto, Go std lib |
| Nonce reuse | Counter-based nonces, rekey triggers |
┌─────────────────────────────────────────────────────┐
│ Replay Window (64 packets) │
│ │
│ HighSeq: 150 │
│ Window: [87-150] │
│ Bitmap: 0b11111111...1111 (packets 87-150 seen) │
│ │
│ Seq 85: REJECT (too old) │
│ Seq 120: REJECT (already seen) │
│ Seq 151: ACCEPT (new highest) │
│ Seq 145: ACCEPT (in window, not seen) │
└─────────────────────────────────────────────────────┘
Errors are designed to prevent information leakage:
- Decryption failures return generic
ErrAuthenticationFailed - Invalid ciphertexts trigger implicit rejection in ML-KEM
- Timing is constant regardless of error type
Measured on Apple M1 Pro, Go 1.26, single-threaded:
| Operation | Time | Throughput |
|---|---|---|
| CH-KEM KeyGen | ~99us | ~10,100 ops/s |
| CH-KEM Encapsulate | ~106us | ~9,400 ops/s |
| CH-KEM Decapsulate | ~84us | ~12,000 ops/s |
| AES-256-GCM Encrypt (1400B) | ~567ns | ~2.5 GB/s |
| AES-256-GCM Encrypt (1KB) | ~398ns | ~2.6 GB/s |
| AES-256-GCM Encrypt (64KB) | ~17.7us | ~3.7 GB/s |
| Session Encrypt (1400B) | ~652ns | ~2.1 GB/s |
| Full Handshake (over net.Pipe) | ~487us | ~2,050 handshakes/s |
| Component | Allocation |
|---|---|
| CH-KEM public key | 1600 bytes |
| CH-KEM ciphertext | 1600 bytes |
| Session state | ~10 KB |
| Per-packet overhead | ~40 bytes |
- Key Reuse: Generate CH-KEM keys once, use for multiple sessions
- Connection Pooling: Reuse established sessions when possible
- Cipher Suite: ChaCha20-Poly1305 may be faster without AES-NI
- Buffer Reuse: Use sync.Pool for message buffers
package main
import (
"github.com/sara-star-quant/quantum-go/pkg/tunnel"
)
func main() {
// Server
listener, _ := tunnel.Listen("tcp", ":8443")
defer listener.Close()
go func() {
for {
conn, _ := listener.Accept()
go handleClient(conn)
}
}()
// Client
client, _ := tunnel.Dial("tcp", "localhost:8443")
defer client.Close()
client.Send([]byte("Hello, quantum-resistant world!"))
}
func handleClient(t *tunnel.Tunnel) {
defer t.Close()
for {
data, err := t.Receive()
if err != nil {
return
}
// Process data...
}
}// Create session
session, err := tunnel.NewSession(tunnel.RoleInitiator)
// Check state
if session.State() == tunnel.SessionStateEstablished {
// Ready for data
}
// Get statistics
stats := session.Stats()
fmt.Printf("Sent: %d bytes, Received: %d bytes\n",
stats.BytesSent, stats.BytesReceived)
// Close session
session.Close()config := tunnel.TransportConfig{
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
// ObserverFactory: func(session *tunnel.Session) tunnel.Observer { ... },
// RateLimitObserver: metrics.NewRateLimitObserver(collector, metrics.GetLogger()),
}
transport, err := tunnel.NewTransport(session, conn, config)Document Version: 1.2 Last Updated: 2026-03-13