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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user