Files
relicario/docs/ARCHITECTURE.md
adlee-was-taken 900ccf1cf4 docs: refresh README, ARCHITECTURE, overview for current state
Apply trivial-fix findings from the 2026-05-02 doc audit:
- README: items/ vs entries/, settings.enc + attachments/ +
  revoked.json in vault layout, full crate tree (relicario-wasm
  + relicario-server + typed-items modules), 16-char hex IDs,
  roadmap reflects shipped trains
- ARCHITECTURE.md: git-server box reflects items/ + 16-char IDs;
  relicario-core inner box lists typed-items modules
- architecture/overview.md: ID width / 128-bit AttachmentId

8 deeper findings still proposed for v0.5.0 release prep.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 16:04:02 -04:00

261 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 │
└──────────────────┘
┌──────────────────┐
│ git init │──────► vault repo
│ git commit │ (ready to push)
└──────────────────┘
```
## 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 positions │
│ 4-15 (mid-freq) │
│ │
│ Repeat secret │
│ 20+ times │
└────────┬─────────┘
┌──────────────────┐
│ Inverse DCT │
│ Reconstruct RGB │
│ Save JPEG (Q92) │
└──────────────────┘
Output: reference.jpg
(visually identical,
carries 256-bit secret)
```
## 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 │
│ 0x01 │ random per write │ XChaCha20 stream │ Poly1305 MAC │
└─────────┴────────────────────────┴──────────────────┴──────────────────┘
```
## 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)
```