Status: Accepted Date: 2026-02-27 Authors: ruv.io, RuVector Architecture Team Deciders: Architecture Review Board SDK: Claude-Flow Relates to: ADR-029 (RVF Canonical Format), ADR-042 (Security-RVF-AIDefence-TEE)
The RVF wire format (rvf-wire) uses XXH3-128 as the sole content hash for all
segment integrity verification. The checksum_algo field in the 64-byte segment
header supports three values:
| Algo | Name | Status |
|---|---|---|
| 0 | CRC32C | Deprecated — silently upgraded to XXH3-128 |
| 1 | XXH3-128 | Active — used for all operations |
| 2 | SHAKE-256 | Declared in enum but never implemented |
A comprehensive review of rvf-wire/src/hash.rs, rvf-types/src/checksum.rs,
and the graph shard module (ruvector-graph/src/distributed/shard.rs) identified
six issues:
-
Non-constant-time hash comparison (P1):
verify_content_hashuses==on byte arrays. While XXH3-128 is not cryptographic, a timing side-channel could reveal partial hash values to an attacker probing segment files over a network interface. For defense-in-depth, verification should use constant-time comparison. -
SHAKE-256 declared but unimplemented (P2):
ChecksumAlgo::Shake256(algo=2) exists in the enum and is accepted byTryFrom<u8>, butcompute_content_hashignores the algo parameter entirely — all paths route to XXH3-128. A writer could setchecksum_algo=2in the header and it would silently verify against XXH3-128, creating a false sense of cryptographic integrity. -
Algo parameter ignored (P2):
compute_content_hash(_algo, data)discards the algorithm selector. If a future writer uses algo=2, the verifier cannot detect the mismatch. -
No keyed/HMAC hash option (P3): The current scheme provides integrity (accidental corruption detection) but not authentication. For federated transfer scenarios (ADR-057), a keyed hash is needed to prevent a man-in-the-middle from replacing segment payloads while recomputing the hash.
-
Dead CRC32C dependency (P3):
crc32c = "0.6"remains inCargo.tomleven though CRC32C is fully deprecated. Thecompute_crc32candcompute_crc32c_hashfunctions are dead code. -
Graph shard uses XXH3-64 (P3):
ruvector-graphshard routing usesxxh3_64()(64-bit). With 2^32 nodes the birthday bound gives ~50% collision probability for shard assignment. This is acceptable for current scale but noted for future growth.
XXH3-128 on 1 MB payload: ~50 GB/s on AVX2 hardware (dominated by memory bandwidth). No performance regression is expected from the changes below since the hash function itself is not modified.
Replace the == comparison in verify_content_hash with a constant-time
byte-equality check using subtle::ConstantTimeEq. This eliminates the timing
side-channel at negligible cost (~2 ns overhead for 16 bytes).
Implement compute_shake256_128(data) using the sha3 crate (already a
dependency of rvf-crypto). Route algo=2 in compute_content_hash to this
implementation. This makes the ChecksumAlgo::Shake256 enum variant truthful.
SHAKE-256 truncated to 128 bits provides:
- 128-bit collision resistance (same as XXH3-128)
- Post-quantum preimage resistance (vs XXH3's ~0 bits)
- ~300 MB/s throughput (vs XXH3's ~50 GB/s) — acceptable for security-sensitive segments where correctness matters more than speed
Make compute_content_hash dispatch on the algo value:
- 0 → XXH3-128 (CRC32C upgrade, backward compatible)
- 1 → XXH3-128
- 2 → SHAKE-256 (first 128 bits)
- other → XXH3-128 (fallback)
Remove compute_crc32c(), compute_crc32c_hash(), and the crc32c dependency.
Retain the Crc32c = 0 enum variant for backward-compatible header parsing.
Reserve checksum_algo=3 for HMAC-SHAKE-256 (keyed integrity). Implementation
is deferred to a follow-up PR as it requires key management infrastructure.
Add the enum variant now so the wire format is forward-compatible.
- Eliminates timing side-channel in hash verification
- SHAKE-256 segments can now be written and verified correctly
- Dead code removed, smaller dependency tree
- Wire format is forward-compatible with keyed hashing
subtlecrate added as a dependency (~10 KB, widely audited)sha3crate added torvf-wire(already inrvf-crypto)- Writers that relied on the silent algo-mismatch behavior will now produce SHAKE-256 hashes when they set algo=2 (breaking change for any such writers, but none are known to exist)
- The
subtlecrate's constant-time guarantees depend on the compiler not optimizing away the timing-safe operations. Rust'ssubtlev2.6+ uses inline-asm barriers on supported platforms.
- Add
subtleandsha3dependencies torvf-wire/Cargo.toml - Remove
crc32cdependency and dead CRC32C functions - Implement
compute_shake256_128()inhash.rs - Update
compute_content_hash()to dispatch on algo - Update
verify_content_hash()to usesubtle::ConstantTimeEq - Add
HmacShake256 = 3toChecksumAlgoenum (reserved, no impl yet) - Update tests and benchmarks
- Verify existing tests pass (no behavioral change for algo=0 and algo=1)
ADR-075 documents the integration of rvf-crypto (including the SHAKE-256 functions hardened by this ADR) into the Shared Brain server. The brain server's verify.rs module previously used inline sha3::Shake256 calls; it now delegates to rvf_crypto::shake256_256() for content hashing and rvf_crypto::create_witness_chain() / rvf_crypto::verify_witness_chain() for tamper-evident audit trails. This ensures that the constant-time comparison and proper algo dispatch implemented here are used consistently across the stack.
See: ADR-075 — Wire Full RVF AGI Stack into mcp-brain-server