Celld Fleet Communication Security Model: A Technical Deep Dive
Celld operates on a Zero-Trust, mutual-authentication security model built on Ed25519 identity keys, Noise XK handshake, and AEAD encryption with token-based revocation.
This article explains how the denoland/celld distributed system protects inter-node communication. The security architecture ensures that every fleet member cryptographically proves its identity before exchanging data, with no implicit trust granted based on network location.
Zero-Trust Foundations
Celld rejects perimeter-based security. Every cell—the fundamental unit in a celld fleet—mustauthenticate itself to every peer it contacts. The model rests on three pillars: persistent identity, short-lived authorization, and session-based encryption.
The threat model assumes compromised nodes, passive eavesdroppers, and active man-in-the-middle attackers. Countermeasures are enforced at the protocol layer, making them independent of transport mechanism.
Identity: Ed25519 Long-Term Keys
Each cell generates a persistent Ed25519 key pair during initialization. These keys serve as the cell's identity throughout its lifecycle.
In crates/celld/peer_auth.rs, the PeerAuth struct loads and manages these keys:
// crates/celld/peer_auth.rs
pub struct PeerAuth {
key_pair: Ed25519KeyPair,
public_key: PublicKey,
}
The private key never leaves the node. The public key is registered with the fleet leader to establish initial trust.
Key Generation Example
// Deno/TypeScript using the Web Crypto API
const keyPair = await crypto.subtle.generateKey(
{ name: "ED25519", namedCurve: "ED25519" },
true,
["sign", "verify"],
);
await Deno.writeTextFile("node_private.key",
await crypto.subtle.exportKey("pkcs8", keyPair.privateKey));
await Deno.writeTextFile("node_public.key",
await crypto.subtle.exportKey("spki", keyPair.publicKey));
Authorization: Leader-Signed Peer Tokens
Authorization in celld's security model uses time-bound, cryptographically signed tokens. When a node joins the fleet, the leader inspects its public key and issues a PeerToken if the node is permitted access.
The token structure is defined in crates/celld/peer.rs:
// crates/celld/peer.rs
pub struct PeerToken {
pub payload: PeerTokenPayload,
pub signature: Signature,
}
pub struct PeerTokenPayload {
pub pub_key: PublicKey,
pub expires: SystemTime,
}
Token creation in peer_auth.rs binds the peer's public key to an expiration timestamp:
// crates/celld/peer_auth.rs (simplified)
pub fn sign_peer_token(
priv_key: &Ed25519KeyPair,
peer_pub: &PublicKey,
expires: SystemTime,
) -> PeerToken {
let payload = PeerTokenPayload {
pub_key: peer_pub.clone(),
expires,
};
let sig = priv_key.sign(&bincode::serialize(&payload).unwrap());
PeerToken { payload, signature: sig }
}
The leader's signature proves token authenticity. The expiration limits the window of compromise if a token is exfiltrated.
Key Exchange: Noise XK Handshake
Celld implements the Noise XK pattern for authenticated key exchange. This pattern provides:
- Mutual authentication: Both peers verify each other's identity
- Forward secrecy: Session keys cannot be recovered from long-term keys
- Noise properties: No unauthenticated cleartext, identity hiding for the initiator
The handshake executes in crates/logic/peer.rs:
// crates/logic/peer.rs (excerpt)
let mut handshake = NoiseHandshake::new_xk(
&local_static_key,
&remote_static_key, // from PeerToken
&mut rng,
);
let msg1 = handshake.write_message(&[])?; // first handshake message
socket.send(msg1).await?;
let msg2 = socket.recv().await?;
handshake.read_message(&msg2)?;
let (cipher, _handshake_state) = handshake.into_transport_mode()?;
XK pattern specifics:
- X: Static key transmitted to initiator (leader knows peer's key from token)
- K: Responder knows initiator's static key in advance
After into_transport_mode(), all communication uses the derived session keys.
Encryption: ChaCha20-Poly1305 AEAD
Transport-layer security in celld uses ChaCha20-Poly1305 AEAD for every frame. This provides:
- Confidentiality: 256-bit symmetric encryption
- Integrity: 128-bit authentication tag
- Replay protection: Sequential nonces derived from session state
The implementation lives in crates/celld/protocol.rs. Each encrypted frame includes a nonce, ciphertext, and authentication tag. The nonce space is partitioned to prevent collisions between sender and receiver.
Revocation and Lifecycle Management
Fleet-wide security policies are enforced through the ownership store. The leader maintains the ownership_store.rs registry of authorized peers:
// crates/celld/ownership_store.rs (simplified)
pub fn is_authorised(&self, peer_id: &PeerId) -> bool {
self.active_peers.contains_key(peer_id)
}
pub fn revoke(&mut self, peer_id: &PeerId) {
self.active_peers.remove(peer_id);
}
When revoke() removes a peer, all active connections to that peer are terminated. The cryptographic binding of session keys to the revoked identity prevents reconnection without a fresh, valid token.
Transport Agnosticism
The celld security model operates over any reliable byte stream. The same handshake and encryption layer functions identically across:
- WebSockets
- HTTP/2 streams
- Raw TCP connections
This is enabled by the abstraction in crates/celld/runtime.rs, which wires the security layer into the chosen transport without protocol modification.
Security Guarantees Summary
| Property | Mechanism | Source File |
|---|---|---|
| Identity authenticity | Ed25519 signatures | peer_auth.rs |
| Authorization validity | Leader-signed PeerToken with expiration |
peer.rs |
| Session key establishment | Noise XK handshake (Curve25519 ECDH) | logic/peer.rs |
| Data confidentiality | ChaCha20-Poly1305 AEAD | protocol.rs |
| Peer eviction | ownership_store removal + connection drop |
ownership_store.rs |
Summary
- Zero-Trust architecture: No implicit trust based on network position; every peer cryptographically authenticates
- Ed25519 identity keys: Long-term, persistent node identifiers stored in
peer_auth.rs - Token-based authorization: Leader-issued, time-bound
PeerTokengrants fleet membership - Noise XK handshake: Mutual authentication with forward secrecy via Curve25519 ECDH
- AEAD encryption: ChaCha20-Poly1305 protects all transported data with integrity and replay resistance
- Immediate revocation: Leader-controlled
ownership_storeenables instant peer removal
Frequently Asked Questions
How does celld prevent a compromised node from impersonating others?
Each node's identity is tied to its private Ed25519 key, which never leaves the node. The Noise XK handshake requires proof of possession of this key. A compromised node can only impersonate itself—it cannot forge signatures for other nodes' public keys. The protocol binding in logic/peer.rs cryptographically locks sessions to the proven identity.
What happens when a node fails to present a valid peer token?
The Noise handshake aborts before any application data flows. The initiator sends its static key and token signature in the first message; the responder verifies the leader's signature on the token and checks expiration. If verification fails, the handshake state machine in logic/peer.rs returns an error and the underlying connection closes.
Can the leader read all fleet communication?
No. The leader signs authorization tokens but does not possess session keys. The Noise XK handshake derives shared secrets directly between communicating peers using ephemeral Curve25519 key exchanges. The leader's role is limited to identity verification and access control, not traffic decryption.
How does celld handle token expiration during long-lived connections?
Session keys outlive tokens but new connections require fresh tokens. The AEAD session established by the Noise handshake remains valid indefinitely for that specific connection pair. However, if the connection drops and reconnection is attempted, the peer must present a current, unexpired token. This balances operational continuity with revocation granularity.
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 →