Technical

How this works in detail

For anyone who wants the specifics: formats, parameters, constructions — and the places where we deliberately promise nothing.

1. The container

A vault file is a plaintext but MAC-protected header and an encrypted body. The header carries only what deriving the key needs: the salt, the KDF parameters and the kinds of factor. Everything else — entries, notes, photos and the access conditions — sits in the body.

Up to version 3 the coordinates and the opening date sat in the plaintext header. For an app whose purpose is to hide where valuables are, that was the product giving away its own subject. Since version 4 they are in the body, and the consequence is deliberate: conditions can only be read after decryption.

The file always holds two slots. A second slot that was never set up is filled with random data of the same length and is indistinguishable from a real one — so the file does not reveal whether a second vault exists.

ParameterValue
version4
AEADAES-256-GCM
KDFArgon2id (libsodium), opsLimit 2, memLimit 64 MiB, p = 1
KDF (Altbestand)PBKDF2-HMAC-SHA256, 600 000
HKDFHKDF-SHA256, reine Dart-Implementierung
Slots2 (immer beide vorhanden)
Wraps je Slot2 — Faktorpfad, Notfallpfad

2. From password to key

Argon2id turns the factors into a base key. Argon2id is memory-hard: 64 MiB per attempt hurts an attacker with special hardware far more than compute rounds alone. On a 2024 device an attempt costs about a second; PBKDF2 at a comparably defensible round count measured several seconds to over twenty, because Android offers no native path for it.

The base key does not decrypt anything directly. Encryption uses a random body key, wrapped twice: once under the factor path, once under the recovery path. Where a path is unused, a decoy of identical length stands in its place. Two consequences: the expensive Argon2id runs once no matter how many location cells are tried, and the header does not betray which kind of vault you are holding.

Files as part of the key go through Shamir over GF(28) with the Rijndael polynomial 0x11B: either all N in a fixed order, or any K of them as a fallback.

3. Place as a key factor

The place is not a question the app asks; it is part of the key schedule. The position is quantised onto a grid, the cell is hashed into an identifier, and that identifier enters the derivation of the path key as the salt:

masterKey = HKDF(baseKey, salt = cell id)

Outside the cell the key therefore does not exist — there is nothing for a modified app or a spoofed location to bypass. The vault is bound to exactly one cell; the search radius only says how many neighbouring cells the client tries, and so costs time rather than safety.

ParameterValue
Raster50 m, metrisch quantisiert
ZellkennungSHA-256 über tva:geo:v1:…
AbleitungHKDF-SHA256, salt = Zellkennung
Suchradius150 / 300 / 600 / 1200 m
Kandidaten25 / 121 / 529 / 2209 Zellen
Kosten (razr 50 ultra)72 / 162 / 220 / 385 ms
Gespeichertnichts — weder Koordinaten noch Zelle noch Radius

4. Time capsules

A time capsule is an implementation of beattime-seal-v1. The note's key is split by Shamir; the beacon shares are encrypted with identity-based encryption (Boneh–Franklin over BLS12-381) to an identity derived from the opening moment. The matching "private" half is the signature the beacon publishes at that moment — before then it does not exist, not even for the operator.

The pairing arithmetic lives in a dedicated Rust library on ark-bls12-381. The widely used tlock crate is not used: it hardcodes a 16-byte sigma, because all it ever encrypts is an age file key. The reference draws sigma at the length of the message — at the 32 bytes sealed here the two produce different ciphertexts, and neither opens the other's.

Nothing is allocated across the C boundary that anyone would have to free: both results have a length the caller can compute in advance, the caller provides the buffer, and every entry point catches panics.

ParameterValue
Formatbeattime-seal-v1
Hauptgeheimnis32 B, Shamir über GF(28), Polynom 0x11B
NutzlastAES-256-GCM-STREAM, Segment 65 536 B
NoncePräfix 7 B ‖ Zähler 4 B BE ‖ last 1 B
AADSHA-256 des kanonischen Kopfs
SchlüsselHKDF-SHA256, info = beattime-seal-v1|aead
IBEBoneh–Franklin über BLS12-381
Kurvenschemabls-unchained-g1-rfc9380
ChiffratU 96 B (G2) ‖ V 32 B ‖ W 32 B
Beaconsdrand quicknet + BeatTime-Schlüsselserver
Profilstandard — 2 von 3

5. Proof of time for photos

A photo taken inside the app can be given a proof of existence. What travels is the SHA-256 checksum, never the picture. The service places the value in a Merkle leaf; once the week closes, the root is signed with Ed25519. The app stores the checksum, the time, the inclusion proof and the signature, and recomputes the proof offline: photo to leaf, leaf to root, signature against the public key.

This is proof pointing backwards — "this existed by then" — and therefore explicitly not a time lock. A time lock needs the opposite direction, and that is what the capsules are for.

6. How this is verified

A format that only talks to itself proves nothing. Three checks run against foreign code, one per layer:

On top of that, tests on real hardware: that the arm64 library loads inside the package, that a place-bound capsule does not open in another city, and what all of it costs — a pairing lands at roughly 10 ms.

7. What is not claimed here