ledvar-core 0.1.0

Reference implementation of the Ledvar protocol: data model, canonical content-addressed hashing, and well-formedness.
Documentation

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.

cargo build --release                                 # build the workspace
cargo install --path crates/ledvar                    # install the `ledvar` CLI (JSON only)
cargo install --path crates/ledvar --features yaml    # ...with YAML support

The core depends only on serde and sha2; yaml is the only optional feature (see Format support).

CLI

cargo run -p ledvar -- validate snapshot.json
cargo run -p ledvar -- hash     snapshot.json
cargo run -p ledvar -- diff     previous.json current.json              # human-readable (git-style + - ~)
cargo run -p ledvar -- diff     current.json  --baseline                # cold start: every node is Baseline
cargo run -p ledvar --features yaml -- diff previous.json current.json --out yaml   # YAML needs the feature
cargo run -p ledvar -- canon    snapshot.json                          # the exact bytes that get hashed
cargo run -p ledvar -- schema --out json                               # reference schema (or --out proto)

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 yaml Cargo feature — for two reasons. The default build is JSON-only. (1) No unsafe/C in the default build: the archived/unmaintained serde_yaml was dropped; with the feature, YAML uses the maintained serde_yaml_ng, which pulls unsafe-libyaml, so it stays out of the default. (2) YAML would coerce string values: YAML 1.1 turns bare no/yes/on/off/true/… into booleans, but every Ledvar value is a string (SPEC §4.4) — an unquoted off in a config value would silently become false and change its hash. If you enable YAML, quote any value that could be read as a bool/number/null. Selecting --in/--out yaml without 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 text0600 stays "0600", 4.50 stays "4.50", and bare null/~ 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 ledvar_core::Node;
use ledvar_diff::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(Some(&previous), &current)?;
// 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:

cargo test

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
gpg --import ledvar-public-key.asc

# 2. confirm you imported the RIGHT key (compare against the fingerprint published on both)
gpg --fingerprint 911CCB7A80D349FB
#   750F 73CA 0EC5 9FB3 D945  CA12 911C CB7A 80D3 49FB

# 3. verify the file you downloaded
gpg --verify ledvar-v0.1.0-x86_64-unknown-linux-musl.tar.gz.asc \
             ledvar-v0.1.0-x86_64-unknown-linux-musl.tar.gz

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.

License

Apache-2.0. See LICENSE and NOTICE.