# Protocol Versioning in Celld's Body-Bound HMAC Authentication

> Understand protocol versioning in celld's body-bound HMAC authentication. Securely evolve algorithms and maintain peer relationships by embedding version identifiers in signed payloads.

- Repository: [Deno/celld](https://github.com/denoland/celld)
- Tags: deep-dive
- Published: 2026-08-09

---

**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`](https://github.com/denoland/celld/blob/main/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`](https://github.com/denoland/celld/blob/main/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:

```rust
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:

```rust
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`](https://github.com/denoland/celld/blob/main/protocol.rs) and HMAC logic in [`peer_auth.rs`](https://github.com/denoland/celld/blob/main/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`](https://github.com/denoland/celld/blob/main/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`](https://github.com/denoland/celld/blob/main/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`](https://github.com/denoland/celld/blob/main/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.