docs: rename for doc-structure redesign — DESIGN / CRYPTO / docs/FORMATS

Mechanical renames only; no content changes. Tracked as renames so
git blame / git log --follow survive intact.

- ARCHITECTURE.md → DESIGN.md (top-level system tour)
- docs/ARCHITECTURE.md → docs/CRYPTO.md (crypto pipeline)
- FORMATS.md → docs/FORMATS.md (wire formats; aligns with docs/ layout)

Spec: docs/superpowers/specs/2026-05-30-doc-structure-redesign-design.md
This commit is contained in:
adlee-was-taken
2026-05-30 15:29:12 -04:00
parent 9ffb0f108b
commit 36a59cd564
3 changed files with 0 additions and 0 deletions

276
docs/CRYPTO.md Normal file
View File

@@ -0,0 +1,276 @@
# Relicario — Architecture
## System Overview
```
┌──────────────────────────────────────────────────────────────────┐
│ CLIENT DEVICE (trusted) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Reference │ │ Passphrase │ │ relicario-cli │ │
│ │ JPEG │ │ (typed) │ │ or browser ext │ │
│ │ (on disk) │ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ ▼ │ │ │
│ ┌──────────────┐ │ │ │
│ │ imgsecret │ │ │ │
│ │ ::extract() │ │ │ │
│ └──────┬───────┘ │ │ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌──────────────────────────────┐ │ │
│ │ Argon2id KDF │ │ │
│ │ password = passphrase ‖ │ │ │
│ │ image_secret │ │ │
│ │ salt = vault_salt │ │ │
│ │ → master_key (32 bytes) │ │ │
│ └──────────────┬───────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────────────────────┐ │ │
│ │ XChaCha20-Poly1305 │◄──────────────────┘ │
│ │ encrypt / decrypt │ │
│ │ (192-bit nonce, 256-bit │ │
│ │ key, 128-bit auth tag) │ │
│ └──────────────┬───────────────┘ │
│ │ │
└─────────────────┼──────────────────────────────────────────────────┘
│ git push / pull (HTTPS or SSH)
┌──────────────────────────────────────────────────────────────────┐
│ GIT SERVER (untrusted) │
│ │
│ relicario-vault.git/ │
│ ├── manifest.enc ← opaque ciphertext │
│ ├── settings.enc ← opaque ciphertext │
│ ├── items/ │
│ │ ├── a1b2c3d4e5f6a7b8.enc ← opaque ciphertext │
│ │ └── … │
│ ├── attachments/ │
│ │ └── <item-id>/<aid>.enc ← opaque ciphertext │
│ └── .relicario/ │
│ ├── salt ← 32 bytes (not secret) │
│ ├── params.json ← KDF params (not secret) │
│ ├── devices.json ← device public keys (not secret) │
│ └── revoked.json ← revoked device records (not secret) │
│ │
│ The server sees NOTHING useful. No keys, no plaintext, │
│ no metadata about what's inside. │
└──────────────────────────────────────────────────────────────────┘
```
## Vault Creation Flow
```
User provides: System generates:
├── carrier JPEG (any photo) ├── image_secret (256-bit random)
└── passphrase (memorized) └── vault_salt (256-bit random)
┌──────────────────┐
carrier JPEG ──────►│ imgsecret │──────► reference.jpg
image_secret ──────►│ ::embed() │ (looks like a normal photo,
│ DCT stego │ carries hidden secret)
└──────────────────┘
┌──────────────────┐
passphrase ────────►│ │
│ Argon2id │──────► master_key (32 bytes)
image_secret ──────►│ (64 MiB, 3 it) │ (held in memory only)
vault_salt ────────►│ │
└──────────────────┘
┌──────────────────┐
master_key ────────►│ XChaCha20- │──────► manifest.enc
empty manifest ────►│ Poly1305 │ settings.enc
default settings ──►│ encrypt (×2) │ (parallel artifacts;
└──────────────────┘ independent nonces)
┌──────────────────┐
│ git init │──────► vault repo
│ git commit │ (ready to push)
└──────────────────┘
```
Item creation, the typed-item envelope (`Item` + per-type `ItemCore`),
attachment encryption, and field-history tracking are not shown above —
they are described in [`crates/relicario-core/ARCHITECTURE.md`](../crates/relicario-core/ARCHITECTURE.md).
The flow above covers only the crypto-pipeline shape that vault init
establishes; the per-item lifecycle reuses the same `master_key` +
XChaCha20-Poly1305 primitives against `items/<id>.enc` and
`attachments/<item-id>/<aid>.enc`.
## Unlock Flow (every vault operation)
```
┌──────────────────┐
reference.jpg ─────►│ imgsecret │──────► image_secret
│ ::extract() │ (256 bits)
└──────────────────┘
┌──────────────────┐
passphrase ────────►│ │
image_secret ──────►│ Argon2id │──────► master_key
vault_salt ────────►│ │
└──────────────────┘
┌──────────────────┐
master_key ────────►│ XChaCha20 │──────► plaintext entries
*.enc files ───────►│ ::decrypt() │ (in memory only)
└──────────────────┘
```
## imgsecret DCT Embedding
```
Input JPEG
┌──────────────────┐
│ Decode to RGB │
│ Extract Y │
│ (luminance) │
└────────┬─────────┘
┌──────────────────────────────┐
│ Full Image │
│ ┌────────────────────────┐ │
│ │ 15% ┌────────────┐ │ │
│ │margin│ │15% │ │
│ │ │ Central │mar-│ │
│ │ │ 70% │gin │ │
│ │ │ EMBEDDING │ │ │
│ │ │ REGION │ │ │
│ │ └────────────┘ │ │
│ │ 15% margin │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
┌──────────────────┐
│ Divide into │
│ 8×8 blocks │
│ Apply 2D DCT │
└────────┬─────────┘
┌──────────────────┐
│ For each │
│ selected block: │
│ │
│ QIM embed bits │
│ in zig-zag │
│ positions 6-17 │
│ (mid-frequency) │
│ │
│ Repeat secret │
│ MIN_COPIES (5) │
│ to 50 times, │
│ by capacity │
└────────┬─────────┘
┌──────────────────┐
│ Inverse DCT │
│ Reconstruct RGB │
│ Save JPEG (Q92) │
└──────────────────┘
Output: reference.jpg
(visually identical,
carries 256-bit secret)
```
The redundancy count is chosen at embed time based on available DCT capacity: `num_copies = (total_blocks / BLOCKS_PER_COPY).min(50)`, with `BLOCKS_PER_COPY = 22` and a floor of `MIN_COPIES = 5` (`crates/relicario-core/src/imgsecret.rs:78,530-537`). Images that cannot fit at least 5 copies are rejected before embed. Majority voting across these copies at extract time requires ≥ 60 % confidence per bit.
## Extraction (with crop recovery)
```
Input JPEG (possibly re-encoded or cropped)
┌─────────────────────────┐
│ Try canonical alignment │──── Success ──► image_secret
│ (offset 0,0) │
└─────────┬───────────────┘
│ Fail
┌─────────────────────────┐
│ Search crop offsets │
│ dx, dy: -15% to +15% │
│ step: 8 pixels │
│ (~16,800 candidates) │──── Success ──► image_secret
│ │
│ For each: extract bits, │
│ majority vote, check │
│ confidence ≥ 60% │
└─────────┬───────────────┘
│ All fail
ExtractionFailed error
```
## Encrypted File Format
```
┌─────────┬────────────────────────┬──────────────────┬──────────────────┐
│ version │ nonce │ ciphertext │ auth tag │
│ 1 byte │ 24 bytes │ N bytes │ 16 bytes │
│ 0x02 │ random per write │ XChaCha20 stream │ Poly1305 MAC │
└─────────┴────────────────────────┴──────────────────┴──────────────────┘
```
`VERSION_BYTE = 0x02` (`crates/relicario-core/src/crypto.rs:59`). Blobs starting with any other byte are rejected with `UnsupportedFormatVersion { found, expected: 0x02 }`. The legacy `0x01` format from the pre-typed-items era is no longer supported.
## Crate Architecture
```
┌────────────────────────────────────────────────────────────┐
│ relicario-cli │
│ Filesystem, git (shelling out), terminal I/O, clipboard │
│ │
│ Depends on: relicario-core, clap, anyhow, rpassword, arboard │
└──────────────────────┬─────────────────────────────────────┘
│ uses
┌────────────────────────────────────────────────────────────┐
│ relicario-core │
│ Platform-agnostic: bytes in, bytes out │
│ No filesystem, no network, no git │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌────────────┐ │
│ │ crypto │ │ imgsecret│ │ item + │ │ vault │ │
│ │ │ │ │ │ types │ │ │ │
│ │ KDF │ │ DCT │ │ Item │ │ encrypt_ │ │
│ │ encrypt │ │ embed │ │ Manifest│ │ item() │ │
│ │ decrypt │ │ extract │ │ Settings│ │ decrypt_ │ │
│ │ │ │ QIM │ │ Backup │ │ manifest() │ │
│ │ │ │ │ │ Device │ │ ... │ │
│ └──────────┘ └──────────┘ └─────────┘ └────────────┘ │
│ │
│ Consumed by: relicario-cli, relicario-wasm (extension), │
│ relicario-server (pre-receive hook). │
│ Future: JNI/Swift wrappers for Android/iOS. │
└────────────────────────────────────────────────────────────┘
```
## Entropy at Each Attack Scenario
```
Server breach only: ████████████████████████████ 256+ bits (infeasible)
passphrase + image_secret
Server + stolen image: ████░░░░░░░░░░░░░░░░░░░░░░ ~51 bits (4 diceware words)
passphrase through Argon2id ~7 million years
Shoulder-surfed passphrase: ████████████████████████████ 256 bits (infeasible)
image_secret
Stolen device: ████░░░░░░░░░░░░░░░░░░░░░░ ~51 bits (4 diceware words)
passphrase through Argon2id ~7 million years
Both factors compromised: game over (same as every password manager)
```