ledvar-rs
The Rust reference implementation of the Ledvar protocol.
Documentation: ledvar.org
Ledvar represents the state of a system as a canonical, content-addressed tree and compares two such trees to reveal — exactly and reproducibly — what changed. This workspace is the reference implementation: a small library, an optional companion, and a CLI. It is domain-blind (security, cloud, anything that can be a tree) and dependency-light in the core.
Crates
| Crate | Kind | What it is |
|---|---|---|
ledvar-core |
lib | The protocol core: data model, canonical hashing, well-formedness. Deps: serde, sha2. |
ledvar-diff |
lib | The optional companion: StateStatus + the comparison + result types. |
ledvar |
bin | The CLI: validate, hash, diff, canon, schema. |
The three are versioned lock-step.
Build & install
Requirements: a Rust toolchain (stable, 1.96+). No system dependencies — the whole
workspace is pure Rust (nothing to apt install); cargo fetches all crate dependencies.
The core depends only on serde and sha2; yaml is the only optional feature
(see Format support).
CLI
Input and output are independent axes (--in / --out) — e.g. read JSON,
write YAML. --in auto detects by file extension, and the hashing is
format-independent.
Format support (v0.1)
| Format | read (--in) |
write (--out) |
notes |
|---|---|---|---|
json |
✅ | ✅ | always available; the canonical interchange format |
yaml |
✅ | ✅ | opt-in: build with --features yaml |
xml |
⛔ not implemented | ⛔ not implemented | |
proto |
⛔ not implemented | ⛔ not implemented |
YAML is opt-in behind the
yamlCargo feature — for two reasons. The default build is JSON-only. (1) No unsafe/C in the default build: the archived/unmaintainedserde_yamlwas dropped; with the feature, YAML uses the maintainedserde_yaml_ng, which pullsunsafe-libyaml, so it stays out of the default. (2) YAML would coerce string values: YAML 1.1 turns bareno/yes/on/off/true/… into booleans, but every Ledvar value is a string (SPEC §4.4) — an unquotedoffin a config value would silently becomefalseand change its hash. If you enable YAML, quote any value that could be read as a bool/number/null. Selecting--in/--out yamlwithout the feature returns a clear error telling you to rebuild with--features yaml. JSON is the canonical data format.How this reference reads YAML: it deserializes plain scalars as raw text —
0600stays"0600",4.50stays"4.50", and barenull/~become the literal strings — so no value is corrupted on input. A different YAML reader that resolves scalar types by the YAML core schema before mapping to strings could reject the very same document this one accepts. That is a difference in input acceptance, never in the hash (identical accepted input always hashes identically), and quoting your values sidesteps it entirely.
XML and Protocol Buffers are NOT implemented yet — only the format dispatch exists. Selecting them returns a clear "not implemented yet" error. They are deferred until a real consumer needs them: adding a serde-native format (like YAML) is one match arm, but XML and proto need a custom mapping and a defined snapshot/diff representation first.
Library
use Node;
use diff;
let id = node.identity_key?; // SHA-256 of the canonical path
let ch = node.content_hash?; // SHA-256 of the canonical content
let result = diff?;
// Every hashing entry point validates first (SPEC §9): ill-formed input
// yields an `Err`, never a hash.
Design notes
docs/canonical-hashing.md— why hashing is hand-rolled and streaming (and the one known caveat about non-BMP characters).
Conformance
conformance/ holds golden vectors mirrored from the protocol repo (including the cross-domain and
node-astral* code-point-vs-UTF-16 vectors). The tests assert that every hash and StateStatus
matches them exactly:
Keep the mirror honest with conformance/sync-check.sh — run it to verify (exit 1 on drift), or
--write to copy the vectors from the protocol repo. Point it at the protocol with LEDVAR_PROTOCOL_DIR
when it isn't checked out beside this repo.
Interim. This copy (and the sync script) are temporary. After launch, once the protocol's conformance vectors are published on crates.io, this directory is removed and the vectors are consumed from that crate instead — single source of truth, no manual sync.
Verifying a release
Every release binary ships with two companion files: a .sha256 and a .asc. They answer different
questions, and only one of them protects you from an attacker.
| File | Proves |
|---|---|
.sha256 |
integrity — the download did not get corrupted on the way |
.asc |
authenticity — the file is genuinely the maintainer's and nobody swapped it |
A checksum alone protects against nothing hostile: whoever can replace the binary can replace the checksum next to it. The signature can't be forged without the private key.
# 1. import the project's public key — ONCE
# from the site: https://ledvar.org/ledvar-public-key.asc
# or the protocol repo: https://github.com/ledvar/ledvar/blob/main/ledvar-public-key.asc
# 2. confirm you imported the RIGHT key (compare against the fingerprint published on both)
# 750F 73CA 0EC5 9FB3 D945 CA12 911C CB7A 80D3 49FB
# 3. verify the file you downloaded
Expected: Good signature from "Maykon Luiz Matos Araújo <maykon.lma@gmail.com>".
Step 2 is not optional. A signature only means something once you know the key is the right one — otherwise an attacker hands you a fake binary and a fake key that signs it perfectly. That is why the fingerprint is published in two independent places: compromising one is not enough.
Releases are signed locally — the private key never reaches CI — and are immutable once published: a release that is live can never have its binaries replaced afterwards.