Files
relicario/docs/ARCHITECTURE.md
adlee-was-taken 76d092d4f6 docs(architecture): note settings.enc + typed items in vault-creation flow (audit F10)
The Vault Creation Flow ASCII showed only manifest.enc as init's
encrypted artifact; cmd_init has been writing settings.enc in parallel
since the VaultSettings rollout. Update the encrypt step to show both
artifacts side-by-side with independent nonces.

Below the ASCII, add a short pointer noting that the per-item lifecycle
(typed-item envelope, attachment encryption, field-history) lives in
crates/relicario-core/ARCHITECTURE.md and reuses the same master_key +
XChaCha20-Poly1305 primitives. The doc-audit framing is "this top-level
doc could just point at the per-crate docs" — taking that trim path.

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

17 KiB
Raw Permalink Blame History

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. 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 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)