Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Project516/p2p-chat/llms.txt

Use this file to discover all available pages before exploring further.

Before any messages are exchanged, both peers in p2p-chat perform a Diffie-Hellman key exchange to derive a shared 32-byte key. This key is never transmitted over the network — each side computes it independently from their own private key and the other peer’s public key. The exchange happens once per connection, and the resulting key is used for all message encryption during that session.

How the key exchange works

Both peers run the same ExchangeKeys function. There is no separate client-side or server-side logic for the crypto — the code path is identical regardless of which peer initiated the connection.
func ExchangeKeys(r io.Reader, w io.Writer) (*[32]byte, error) {
    pub, priv, err := box.GenerateKey(rand.Reader)
    // send public key, read peer's public key, derive shared key
    box.Precompute(&sharedKey, &peerPub, priv)
    fmt.Println("Encryption established.")
    return &sharedKey, nil
}
1

Generate an ephemeral key pair

Each peer generates a fresh Curve25519 key pair using box.GenerateKey. The randomness comes from crypto/rand, the operating system’s cryptographically secure random source. Both the public and private keys are 32 bytes.
pub, priv, err := box.GenerateKey(rand.Reader)
2

Send the local public key

The local public key (32 bytes) is written directly to the connection. No framing or encoding is applied — it is a raw byte sequence.
_, err = w.Write(pub[:])
3

Read the peer's public key

The peer’s public key is read from the connection using io.ReadFull, which blocks until exactly 32 bytes have been received. This ensures the key is complete before proceeding.
var peerPub [32]byte
_, err = io.ReadFull(r, peerPub[:])
4

Derive the shared key

box.Precompute performs the X25519 Diffie-Hellman operation: it multiplies the peer’s public key by the local private key to produce a 32-byte shared secret. Both peers arrive at the same value without ever sending the secret over the wire.
var sharedKey [32]byte
box.Precompute(&sharedKey, &peerPub, priv)
On success, "Encryption established." is printed to stdout.

Ephemeral keys

The key pair generated in step 1 exists only for the duration of the ExchangeKeys call. It is never written to disk, never logged, and never reused. Each new connection produces a completely independent key pair.
Ephemeral keys are generated from crypto/rand, which reads from /dev/urandom (or the equivalent OS source) on Linux. This is appropriate for cryptographic key material.

The shared key

After the exchange completes, ExchangeKeys returns a pointer to a 32-byte array. This value is the session key — it is passed to crypto.Encrypt and crypto.Decrypt for every message sent or received on that connection. The shared key is computed identically by both peers:
  • Peer A computes: DH(A_private, B_public)
  • Peer B computes: DH(B_private, A_public)
Because Curve25519 is commutative, both expressions produce the same 32-byte value.

Forward secrecy

Because keys are generated fresh for every connection and never persisted, a compromise of one session’s key material does not affect any other session — past or future.
If an attacker records encrypted traffic today and later obtains the shared key for that session (through compromise of a peer’s memory, for example), they can decrypt only that session. They cannot decrypt any other session because each session uses an independent ephemeral key pair.
Long-lived keys — keys stored on disk and reused across connections — do not provide forward secrecy. If such a key is compromised, all past sessions encrypted with it are retroactively exposed. Generating fresh keys per connection eliminates this risk.

Limitations

p2p-chat does not verify the identity of the peer during the key exchange. There is no PKI, no certificate chain, and no fingerprint comparison mechanism. The protocol follows a trust-on-first-use (TOFU) model.
A network-level attacker positioned between the two peers before the first connection could perform a man-in-the-middle attack: substituting their own public key for each peer’s actual public key, deriving two separate shared keys, and relaying (and reading) all traffic. p2p-chat provides no protection against this attack.
Because both peers run the same ExchangeKeys code with no client/server distinction, there is no structural way to assign a persistent identity to either side. Adding identity verification would require an out-of-band mechanism — such as comparing key fingerprints over a separate channel, or integrating a PKI.

Message encryption

How the shared key is used to encrypt and authenticate every message with XSalsa20-Poly1305.

Build docs developers (and LLMs) love