Skip to content

Vault and Secret Sharing

Yura Filatov edited this page Jun 27, 2026 · 1 revision

Vault & Secret Sharing

Occulta Vault stores encrypted entries (label + content). Each entry has its own randomly generated per-entry key (PEK) — the vault master key only wraps the PEK, not the content directly. Recovery can be scoped per entry: different entries can have different trustees and different thresholds.


Encryption Model

vault key (SE-derived)
  └── encryptedEntryKey  →  per-entry key (PEK, 32 random bytes)
        ├── encryptedLabel    (AES-GCM, AAD = entry.aad())
        └── encryptedContent  (AES-GCM, AAD = entry.aad())

Shamir's Secret Sharing

The PEK is split using SSS over GF(2⁸) (Lagrange interpolation, field polynomial 0x11B). A threshold-of-n split produces n shards; any k ≥ threshold reconstruct the PEK. With fewer than k shards an attacker learns nothing about the PEK — information-theoretic security, not computational.

Splitting is per-entry. Different entries use independent random polynomials; shards from different distributions are incompatible and fail GCM authentication if mixed.


Shard Signing

Each shard is wrapped in a SignedAttribute(category: .shard) signed by the owner's SE identity key. The v2 signing payload binds:

"occulta-signed-attribute-v2" ∥ attrID ∥ "shard" ∥ entryID
∥ createdAt UInt64 BE ∥ expiry flag ∥ shardBytes

entryID binding prevents a shard from a previous PEK generation from being accepted against a rotated entry. createdAt/expiresAt in the payload prevent a trustee from extending validity by editing stored JSON — any modification invalidates the ECDSA signature.

On a new device the owner's SE key is gone; signature verification is skipped. GCM authentication on reconstruction substitutes as the integrity check.


Shard Delivery

Shards are delivered as .occ bundles using longTermFallback + mandatory ML-KEM. A contact without ML-KEM key material cannot be selected as a trustee. This gates every shard bundle behind a session key that requires breaking ML-KEM-1024 in addition to P-256 — the only practical defence against harvest-now-decrypt-later attacks.


What a Trustee Stores

A trustee stores a CustodyShard row containing only:

  • A random per-row id (plaintext SwiftData key).
  • An encryptedPayload blob — AES-GCM seal of { ownerKeyFingerprint, ownerContactIdentifier, signedAttribute } under the shard custody key, AAD = id.

No plaintext column links a shard to a specific contact. Cold-disk forensics learns "N shards stored" — nothing about ownership, timing, or which entries are covered.


Shard Custody Key

A dedicated SE key (tag "shard.custody.occulta", device-unlock level, no biometric) is used exclusively for shard operations. Two symmetric keys are HKDF-derived from it:

Symmetric key HKDF info Seals
Shard custody key Occulta-v1-shard-custody-2026 CustodyShard
Recovery buffer key Occulta-v1-recovery-buffer-2026 ReconstructShard

Device-unlock (not biometric) allows shard bundles to be stored automatically on receipt without requiring the user to open the app.


Reconstruction

  1. Trustees detect Alice's key change during proximity exchange and auto-return shards via .handback operations in the next outbound bundle.
  2. Alice's app buffers arriving shards in ReconstructShard (encrypted under the recovery buffer key).
  3. When ≥ k shards are buffered, tryFinalizeReconstruction runs automatically.
  4. Old device: verify each shard's ECDSA signature. New device: skip, rely on GCM.
  5. ShamirSecretSharing.reconstruct(shares:) → 32-byte PEK.
  6. AES.GCM.open(encryptedContent, using: PEK) — success confirms shard integrity.
  7. Re-wrap PEK under current vault key; zero PEK bytes immediately.

Threat Model

Threat Defence
< k shards Information-theoretically zero leakage
Stale shards (old PEK) GCM decryption fails immediately
Tampered shard ECDSA fails (normal); GCM fails on new device
Shard mixing across entries GCM authentication rejects incompatible shares
HNDL (quantum adversary archives bundles) Mandatory ML-KEM-1024 in session key
Trustee device loss Owner detects fingerprint change → marks shard .lost; UI prompts redistribution

Recovery Health Monitoring

The Vault tab surfaces three readiness signals inline.

Per-entry key (PEK) health — entries whose shard distribution has fallen below threshold (trustees lost devices, shards revoked) appear in a "Needs Attention" section with a critical or degraded label. Only active (.pending / .confirmed) shards count toward coverage.

Backup encryption key (BEK) status — a permanent row in the vault list tracks whether the BEK has been distributed and how many trustees have confirmed receipt. Vault export is gated on reaching threshold confirmation.

Backup staleness — three conditions invalidate an existing backup and surface as tappable rows: new entries added since last export, BEK rotated (existing backup permanently unrestorable), or trustee set changed. Each routes directly to the export flow.


Shard Indicator

Every entry in the vault list carries a glyph in its trailing position. When no shard distribution has been configured the glyph is grayscale and dimmed — a passive cue that the entry's encryption key has no recovery path. Once any distribution is marked for delivery the glyph switches to full colour with a soft purple glow.

Clone this wiki locally