Protocol Versioning in Celld's Body-Bound HMAC Authentication

Protocol versioning in celld's body-bound HMAC authentication allows distributed nodes to negotiate cryptographic capabilities by embedding version identifiers directly into signed payloads, enabling secure algorithm evolution without breaking existing peer relationships.

The denoland/celld repository implements a distributed networking protocol where every inter-node request is protected by cryptographic authentication. By binding the protocol version to the HMAC calculation, the system ensures that peers can verify request integrity using version-specific routines while maintaining compatibility across software updates.

What Is Body-Bound HMAC Authentication?

Body-bound HMAC authentication ensures that every request's payload and metadata are cryptographically signed using a shared secret key. In crates/celld/peer_auth.rs, the implementation uses the hmac crate with SHA-256 to generate a 32-byte authentication tag over the complete request body. This design guarantees that any tampering with the request body—whether the payload or critical metadata—will be detected during verification.

The HMAC calculation incorporates not just the raw bytes of the body, but also the protocol version identifier, binding the authentication to a specific version of the wire format.

How Protocol Versioning Works in Celld

The versioning logic resides in crates/celld/protocol.rs, where the ProtocolVersion enum defines supported versions of the authentication protocol. Each outgoing request includes the version identifier both in the request headers and as the first byte of the signed payload.

When a node receives a request, it performs the following verification sequence:

  1. Extracts the protocol version from the request header.
  2. Selects the appropriate verification routine matching that version.
  3. Computes the HMAC over the version byte concatenated with the request body using the algorithm defined for that version.
  4. Compares the computed tag against the provided authentication tag.

If the version is unknown or unsupported, the request is rejected immediately before any application-level processing occurs.

Implementing Versioned HMAC in Rust

The following examples demonstrate how celld generates and verifies versioned requests using the ProtocolVersion enum and HmacKey struct.

Generating a signed request:

use celld::protocol::{ProtocolVersion, SignedRequest};
use celld::peer_auth::HmacKey;
use hmac::{Hmac, Mac};
use sha2::Sha256;

// Select the protocol version
let version = ProtocolVersion::V1;

// Prepare request body
let body = b"{\"action\":\"ping\"}";

// Initialize 32-byte HMAC key
let key = HmacKey::from_bytes(b"super_secret_shared_key_32bytes_____");

// Compute HMAC over version + body
let mut mac = Hmac::<Sha256>::new_from_slice(&key.0).expect("32-byte key");
mac.update(&[version as u8]);
mac.update(body);
let tag = mac.finalize().into_bytes();

// Construct signed request
let signed_req = SignedRequest {
    version,
    body: body.to_vec(),
    tag: tag.to_vec(),
};

Verifying an incoming request:

use celld::protocol::{ProtocolVersion, SignedRequest};
use celld::peer_auth::HmacKey;
use hmac::{Hmac, Mac};
use sha2::Sha256;

fn verify_request(req: SignedRequest, key: HmacKey) -> Result<(), &'static str> {
    match req.version {
        ProtocolVersion::V1 => {
            let mut mac = Hmac::<Sha256>::new_from_slice(&key.0)
                .map_err(|_| "invalid key length")?;
            mac.update(&[req.version as u8]);
            mac.update(&req.body);
            mac.verify_slice(&req.tag)
                .map_err(|_| "HMAC verification failed")
        }
        _ => Err("unsupported protocol version"),
    }
}

Security Benefits of Protocol Versioning

Embedding version information into the HMAC payload provides critical security and operational advantages:

  • Forward compatibility: Newer nodes can process requests from older clients by detecting the legacy version identifier and executing the appropriate verification path.
  • Backward compatibility: Older nodes reject unknown version numbers gracefully, preventing silent failures when encountering newer protocol features.
  • Algorithm agility: Security upgrades—such as transitioning to a stronger hash function or modified key derivation—can be introduced by incrementing the version number, forcing clients to adopt stronger algorithms while maintaining support for legacy versions during transition periods.
  • Graceful deprecation: When a version is deemed insecure, servers can explicitly reject it, compelling clients to upgrade without service disruption.

Summary

  • Protocol versioning in celld binds the version identifier directly into the HMAC calculation, preventing downgrade attacks.
  • The ProtocolVersion enum in protocol.rs and HMAC logic in peer_auth.rs enable version-specific verification routines.
  • Version negotiation occurs before application processing, with explicit rejection of unsupported versions.
  • This architecture supports cryptographic agility, allowing the system to migrate from SHA-256 to future algorithms without breaking existing peer relationships.

Frequently Asked Questions

How does celld prevent version downgrade attacks?

The protocol version byte is prepended to the request body before HMAC calculation in peer_auth.rs, making the version an integral part of the signed data. An attacker cannot strip or modify the version header without invalidating the authentication tag, as the verification routine expects the version byte to be present in the signed payload.

What happens when a celld node receives a request with an unknown protocol version?

The node immediately rejects the request with an error indicating an unsupported protocol version. This check occurs in the verification logic before any body parsing or application logic executes, ensuring that nodes never process requests using unrecognized cryptographic algorithms.

Can celld support multiple HMAC algorithms simultaneously?

Yes. The version-based dispatch pattern shown in protocol.rs allows each ProtocolVersion variant to specify its own hash algorithm and verification routine. While the current implementation uses SHA-256 for V1, future versions could implement SHA-3 or BLAKE3 by adding new enum variants and their corresponding verification paths.

Where is the HMAC key stored and validated in the celld codebase?

The HmacKey struct defined in crates/celld/peer_auth.rs enforces a fixed 32-byte key length using Hmac::<Sha256>::new_from_slice(). Keys are typically derived from shared secrets established during peer handshakes and must be exactly 32 bytes to satisfy the SHA-256 block size requirements.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →