Docs for the Rust version; benchmarks; build.sh fixes

README, CLAUDE.md, man pages, the user guide and its screenshot script
now describe the Rust build and the intentional behaviour changes, which
docs/MIGRATION.md lists. Go and Rust micro-benchmarks for the same
operations, and scripts/bench.sh for end-to-end comparison. build.sh
exports its tree root to the per-target jobs and keeps logs out of the
output directory; riscv64 musl builds use getrandom's raw syscall
backend, since zig's musl getrandom never reaches the kernel there.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andy Dixon
2026-10-08 11:50:55 +01:00
co-authored by Claude
parent cddde5ca52
commit 53968f8822
18 changed files with 659 additions and 109 deletions
+66 -44
View File
@@ -4,71 +4,93 @@ This file provides guidance to Claude Code when working with this repository.
## Overview
vault is a post-quantum encrypted file vault: one `package main` at the repo
root that builds a single `vault` binary. Module `github.com/andydixon/vault`.
vault is a post-quantum encrypted file vault: one Rust package (`Cargo.toml`
at the root, toolchain pinned in `rust-toolchain.toml`) that builds a single
`vault` binary. The library (`src/lib.rs`) exists so tests and benchmarks can
reach the same code; `src/main.rs` only calls `cli::main`.
- `main.go` - CLI: `genkey`, `init`, `add` (file, directory
or URL), `migrate` (add, then delete originals), `mirror`, `absorb` (tar
on stdin, `absorb.go`), `ls` (one folder level), `list` (recursive),
`get` (`-r` for a folder), `mv`/`cp` (files or folders; `transfers`
resolves names like mv(1), copies share objects), `rm` (`-r` for a
folder, asks unless `-f`), `unlock`/`lock`.
- `agent*.go` - `vault unlock` re-executes itself as a hidden `__agent`
process holding the suite ID and seed (pipe in, mlocked, undumpable) and
serving them on a Unix socket in a 0700 per-user dir until a deadline
(fixed, or with `--idle` reset on each use). `keyIdentity` asks it first
unless `$VAULT_PASSPHRASE` is set.
- `keyfile.go` - key file with two passphrase slots (Argon2id +
XChaCha20-Poly1305 sealing a suite ID and 32-byte master seed). The second
slot is either a decoy identity or random bytes; slot order is random.
- `store.go` - on-disk vault: `records/` holds fixed-size encrypted catalogue
records, `objects/` holds file bodies cut into 4 MiB chunks (last one
bucket-padded) whose IDs are HMAC'd from a per-record seed. Both are
sharded into 256 `xx/` subdirectories by ID prefix. Records a key cannot
open are skipped, not treated as errors.
- `lock*.go` - vault lock on `DIR/.lock`: fcntl on unix, LockFileEx on
Windows, an exclusive lock file elsewhere. Writers lock exclusively,
readers shared, taken after the passphrase prompt.
- `docs/guide` - LaTeX user guide (`build-pdf.sh` via Tectonic; `shots.py`
re-shoots screenshots from the real binary). Keep it in step with the CLI.
- `packaging/repo.sh [TAG]` - builds dixon-vault .deb/.rpm/Arch packages
from a tag with nfpm and publishes them, signed, to the apt/dnf/pacman
repo at repo.dixon.cx (same script, other project block, in ../mapsize).
- `build.sh` - cross-compiles every `go tool dist list` target except
iOS and android/386, amd64, arm (cgo-only).
- `internal/pqcrypt` - file encryption (header + STREAM ChaCha20-Poly1305),
suite registry, hybrid FrodoKEM-640 + ML-KEM-768 default suite.
- `internal/format`, `internal/stream`, `internal/term` - header encoding,
chunked payload encryption, terminal passphrase prompts.
- `src/cli.rs` - commands and Go-`flag`-compatible parsing: `genkey`,
`init`, `add` (file, directory or URL), `migrate`, `mirror`, `absorb`,
`ls`/`list`, `get` (`-r`), `mv`/`cp` (`transfers` resolves names like
mv(1)), `rm` (`-r`, asks unless `-f`), `unlock`/`lock`, hidden `__agent`.
- `src/agent.rs` - the unlock agent: `unlock` re-executes the binary as
`__agent` (passphrase variables stripped, secret over a pipe); it hardens
itself (`platform::memory`) before reading the key, serves it on a Unix
socket in a 0700 per-user dir until a deadline (fixed or `--idle`).
Wire-compatible with the Go agent. Not available on Windows.
- `src/keyfile.rs` - key file: two Argon2id/XChaCha20-Poly1305 slots, decoy
or random bytes in the second, random order, both always tried.
- `src/crypto/` - `frodo.rs` (FrodoKEM-640-SHAKE ported from CIRCL v1.6.4),
`kem.rs` (hybrid suite 0x0001 with RustCrypto ml-kem, `Identity`),
`header.rs` (Fury header format), `fury.rs` (file encryption), `stream.rs`
(STREAM, including random access).
- `src/store/` - on-disk vault: `records/`, `objects/` sharded by ID prefix,
catalogue, add/remove/copy/extract, filler; `types.rs` holds the validated
types (`VaultName`, `Id`, `ObjectSeed`, `Record`) and record JSON.
- `src/pipeline.rs` - the one bounded worker pool and the eight reusable
object buffers; coordinators run on the caller's thread.
- `src/platform/` - all `unsafe` and OS specifics: `fs.rs` (no-clobber
publish, `Dir` confined extraction, epoch times, dir sync), `lock.rs`
(fcntl / LockFileEx / lock file), `memory.rs` (mlock, undumpable),
`term.rs` (prompts).
- `src/net/` - `add URL` and `mirror` with limits; `url.rs` (Go `net/url`
semantics), `html.rs` (tokenizer and link rewriting).
- `src/absorb.rs` - tar (optionally gzipped) from stdin, streamed in.
- `src/secret.rs`, `src/rng.rs`, `src/util.rs`, `src/error.rs` - secret
buffers, randomness (seeded only under `cfg(test)`), Go-compatible
parsing/printing helpers, the error type.
- `oracle/go` - the frozen Go implementation (test-only reference, see
`oracle/README.md`); `oracle/gen-vectors.sh` writes `tests/vectors`.
- `tests/` - CLI and interoperability tests against the real binaries;
`fuzz/` - cargo-fuzz targets; `benches/micro.rs` - micro-benchmarks.
- `docs/` - man pages, `COMPATIBILITY.md`, `MIGRATION.md`, `PLATFORMS.md`,
`BENCHMARKS.md`, and the LaTeX guide (`docs/guide`, `build-pdf.sh` via
Tectonic; `shots.py` re-shoots screenshots from the real binary). Keep
them in step with the CLI.
- `build.sh` - cross-builds every supported target (see `PLATFORMS.md`).
- `packaging/package.sh` builds and inspects dixon-vault packages locally;
`packaging/repo.sh [TAG]` signs and publishes them to repo.dixon.cx.
## Commands
```sh
go test ./...
go build .
./build.sh # all platforms, into ./dist
TARGETS="linux/amd64" ./build.sh # a subset
cargo test --locked # needs go on PATH for interop tests
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo fmt --check
cargo build --release --locked
./build.sh # all buildable targets, into ./dist
TARGETS="linux/amd64" ./build.sh # a subset
cargo deny --locked check # advisories and licences
(cd fuzz && cargo +nightly fuzz run header)
```
End-to-end check:
```sh
export VAULT_PASSPHRASE=test VAULT_KEY=$PWD/k VAULT_DIR=$PWD/s
./vault genkey && ./vault init && ./vault add README.md docs && ./vault list
target/release/vault genkey && target/release/vault init && target/release/vault add README.md docs && target/release/vault list
```
## Invariants
- The on-disk formats are frozen and shared with the Go version; check any
change against `docs/COMPATIBILITY.md` and the oracle vectors.
- Never write a seed or private key unencrypted (the agent gets it by pipe).
- Keep suite IDs, stanza tags, HKDF info strings and `fileKeySize` (32) stable.
- Keep suite IDs, stanza tags, HKDF info strings and the 32-byte file key stable.
- Every ciphertext's and shard directory's mtime is pinned to the epoch;
records stay one fixed size; objects stay at most 4 MiB and bucket-padded.
These hide metadata from the storage host.
- An object's plaintext starts with its own ID, binding it to its record
and its position in the file.
- `get` publishes output only after every object has authenticated.
- `get` publishes output only after every object has authenticated, with a
no-replace primitive; never fall back to check-then-rename.
- Decoy deniability: both key slots stay the same size and are both always
tried; `init` always writes filler; foreign records are skipped silently.
- Every command that changes the vault holds the exclusive lock.
- Keep dependencies pure Go and CGO_ENABLED=0 builds working.
- Every command that changes the vault holds the exclusive lock (POSIX
fcntl locks, compatible with Go's).
- Crash-safe order: objects before records, new records before removing old
ones, record removal (synced) before object removal.
- Secrets live in `SecretArray`/`SecretVec`/`LockedBuf`: no Clone, Debug or
serialisation. Production randomness is the OS only.
- `unsafe` only in `src/platform`, each block with a SAFETY comment.
- Diagnostics go to standard error, lower-case, no trailing full stops.