Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

Persisted formats

Versioned V1/V2 encrypted-envelope and Recovery Kit formats, authenticated metadata, encoding rules, and parser rejection behavior.

All persisted structures are strict UTF-8 JSON. Binary fields use unpadded base64url and timestamps use UTC RFC 3339. Unknown fields and unsupported versions are rejected.

PersistedFormatSupport from @ownfold/core declares the versions each release reads and writes. pnpm test:compatibility checks that declaration against immutable V1 and V2 cryptographic fixtures. See Versioning and upgrades for the removal and migration policy.

PersistedCryptoCapabilities.current separately declares reader and writer suite identifiers for every persisted cryptographic format. Its strict V1 document can be exchanged through a host-owned rollout system, and checkCompatibility proves both releases can read the other’s writer. This supports bridge releases for future algorithm changes without enabling automatic downgrade. See ADR 0010.

Encrypted envelope V2

New encryption uses ownfold.encrypted-envelope V2. It retains the V1 field layout and XChaCha20-Poly1305-IETF primitives, but separates the two authenticated-data purposes:

  • Content authenticates the format, version, suite, vault ID, record context, encoding, and content nonce.
  • Data-key wrapping authenticates those fields plus the root-key version.

The content tag deliberately does not depend on the wrapped-data-key object or root-key version. Root-key rotation can therefore authenticate and unwrap the random record key with the old root, wrap it with the new root and a fresh nonce, and leave the content nonce and ciphertext unchanged. Changing any routing context or key version still makes one of the authenticated operations fail.

Encrypted envelope V1 compatibility

ownfold.encrypted-envelope V1 uses a random 256-bit data key per record. The data key is wrapped by the root key. Both operations use XChaCha20-Poly1305-IETF with independent random 192-bit nonces.

Wrapping additional data authenticates the format, version, suite, vault ID, key version, record context, content encoding, and content nonce. Content additional data authenticates the same values plus the wrapped-data-key object.

V1 remains parseable and decryptable. Because its content tag depends on the wrapped key, rotating a V1 record requires a one-time browser-side migration: authenticate and decrypt the content, use a fresh content nonce, emit V2, and erase temporary plaintext buffers. No server receives plaintext. CryptoEngine.rewrapRecordDataKey performs this migration. V2 inputs take the fast path and retain their exact content object.

The compatibility suite creates actual V1 records with withEnvelopeVersionForTesting(createSodiumCryptoEngine(), 1), rotates them through the browser client, and verifies the resulting V2 record against its original context and plaintext. The version override is exported only from @ownfold/crypto/testing; application code should always write the engine’s current default version.

See ADR 0008: Rotation-friendly envelope for the protocol rationale and compatibility decision.

Recovery Kit V1

ownfold.recovery-kit V1 derives a 256-bit wrapping key with Argon2id13 using a 16-byte salt, three operations, and 64 MiB memory. XChaCha20-Poly1305-IETF encrypts the root key. All public kit fields are authenticated.

Password bytes are exact UTF-8. Ownfold does not trim or normalize them. A valid-looking kit whose authentication fails may have the wrong secret or may have been modified; the SDK does not claim to distinguish those cases.

Device envelope V1

ownfold.device-envelope version 1 uses X25519-HKDF-SHA-256+A256GCM. It contains the device and vault identifiers, key version, creation time, recipient and ephemeral X25519 public keys, HKDF salt, AES-GCM nonce, and the encrypted 32-byte root key. Binary fields use unpadded base64url.

Every field except encryption.wrappedRootKey is canonicalized as authenticated additional data. The HKDF info separately binds the derivation purpose, format, version, device, vault, key version, and both public keys. Changing ownership or key-routing metadata makes decryption fail.

Protected device private key V1

ownfold.protected-device-private-key V1 is local-only and never crosses the configured transport. It contains an X25519 private key encrypted with AES-256-GCM under a non-extractable browser CryptoKey. Its authenticated data binds the storage purpose, format, version, algorithm, X25519 public key, encryption algorithm, and nonce. The AES key and encrypted private-key bytes are stored in IndexedDB; raw X25519 private bytes exist only briefly during device operations and are zeroed on a best-effort basis.

The parser also accepts the earlier direct non-extractable X25519 CryptoKey representation for upgrade compatibility. New writes always use the protected V1 structure because WebKit does not reliably restore direct X25519 keys from IndexedDB.

Pairing offer V1

ownfold.pairing-offer version 1 is an expiring public structure containing the pairing request, vault, and device identifiers; device label; X25519 public key; creation time; and expiry. It contains no ciphertext or secret material. The complete strict JSON structure is transferred out of band and compared exactly by the approving browser before it creates a device envelope.

Server-side pairing state is a discriminated union: pending contains only the offer and revision; approved additionally requires a device envelope and approval time; cancelled requires a cancellation time. Database constraints and parsers reject mixed or incomplete states.

Last updated on August 4, 2026

Was this page helpful?