Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

Crypto engine reference

Reference for the production sodium engine, opaque key handles, algorithms, persisted formats, worker isolation, and deterministic test helpers.

@ownfold/crypto supplies the production CryptoEngine. It performs cryptographic operations but does not decide user identity, store vault state, make network requests, or render UI.

pnpm add @ownfold/core@beta @ownfold/crypto@beta

createSodiumCryptoEngine()

import { createSodiumCryptoEngine } from "@ownfold/crypto"

const cryptoEngine = createSodiumCryptoEngine()
const rootKey = await cryptoEngine.createRootKey()
if (rootKey.status === "error") return rootKey

The factory is synchronous; the returned engine initializes its crypto runtime lazily when an operation first needs it. Every fallible operation returns Result.

PropType
createRootKey() => Promise<Result$1<RootKeyHandle, CryptoEngineError>>
Type() => Promise<Result$1<RootKeyHandle, CryptoEngineError>>
encryptRecord(input: EncryptRecordInput) => Promise<Result$1<EncryptedEnvelopeValue, CryptoError>>
Type(input: EncryptRecordInput) => Promise<Result$1<EncryptedEnvelopeValue, CryptoError>>
decryptRecord(input: DecryptRecordInput) => Promise<Result$1<Uint8Array<ArrayBufferLike>, CryptoError>>
Type(input: DecryptRecordInput) => Promise<Result$1<Uint8Array<ArrayBufferLike>, CryptoError>>
rewrapRecordDataKey(input: RewrapRecordDataKeyInput) => Promise<Result$1<EncryptedEnvelopeV2, CryptoError>>
Type(input: RewrapRecordDataKeyInput) => Promise<Result$1<EncryptedEnvelopeV2, CryptoError>>
createRecoveryKit(input: CreateRecoveryKitInput) => Promise<Result$1<RecoveryKitV1, CryptoError>>
Type(input: CreateRecoveryKitInput) => Promise<Result$1<RecoveryKitV1, CryptoError>>
openRecoveryKit(input: OpenRecoveryKitInput) => Promise<Result$1<RootKeyHandle, CryptoError>>
Type(input: OpenRecoveryKitInput) => Promise<Result$1<RootKeyHandle, CryptoError>>
createDeviceKey() => Promise<Result$1<DeviceKeyHandle, CryptoEngineError>>
Type() => Promise<Result$1<DeviceKeyHandle, CryptoEngineError>>
wrapRootKeyForDevice(input: WrapRootKeyForDeviceInput) => Promise<Result$1<DeviceEnvelopeV1, CryptoError>>
Type(input: WrapRootKeyForDeviceInput) => Promise<Result$1<DeviceEnvelopeV1, CryptoError>>
openDeviceEnvelope(input: OpenDeviceEnvelopeInput) => Promise<Result$1<RootKeyHandle, CryptoError>>
Type(input: OpenDeviceEnvelopeInput) => Promise<Result$1<RootKeyHandle, CryptoError>>
compareRootKeys(left: RootKeyHandle, right: RootKeyHandle) => Promise<Result$1<boolean, CryptoEngineError>>
Type(left: RootKeyHandle, right: RootKeyHandle) => Promise<Result$1<boolean, CryptoEngineError>>
destroyRootKey(key: RootKeyHandle) => void
Type(key: RootKeyHandle) => void

Key handles

RootKeyHandle and DeviceKeyHandle deliberately expose identity metadata, not private key bytes. The engine is the only component allowed to dereference their private material.

const created = await cryptoEngine.createRootKey()
if (created.status === "error") return created

try {
  // Pass the handle back to engine operations.
} finally {
  cryptoEngine.destroyRootKey(created.value)
}

After destroyRootKey, the handle reports destroyed: true and must not produce a successful operation. Destroying a handle reduces key lifetime; it does not erase ciphertext, Recovery Kits, or device envelopes.

Record encryption

PropType
rootKeyRootKeyHandle
TypeRootKeyHandle
vaultIdstring
Typestring
keyVersionnumber
Typenumber
contextRecordContext
TypeRecordContext
encoding"json" | "binary"
Type"json" | "binary"
plaintextUint8Array
TypeUint8Array
envelopeVersion?1 | 2
Type1 | 2

encryptRecord:

  1. generates a fresh record data key;
  2. encrypts plaintext with XChaCha20-Poly1305;
  3. authenticates vault ID, key version, encoding, namespace, record ID, and owner ID;
  4. wraps the data key under the root key; and
  5. returns a versioned envelope.
const encrypted = await cryptoEngine.encryptRecord({
  rootKey,
  vaultId: "vault_01",
  keyVersion: 1,
  context: {
    namespace: "documents",
    recordId: "doc_01",
    ownerId: "user_01",
  },
  encoding: "binary",
  plaintext: bytes,
})

decryptRecord requires the exact expected context and returns fresh Uint8Array plaintext only after format, context, and AEAD authentication succeed.

Data-key rewrapping

PropType
oldRootKeyRootKeyHandle
TypeRootKeyHandle
newRootKeyRootKeyHandle
TypeRootKeyHandle
envelopeEncryptedEnvelopeValue
TypeEncryptedEnvelopeValue
newKeyVersionnumber
Typenumber

rewrapRecordDataKey opens only the wrapped record data key under the old root key and wraps it under the new root key. It returns a V2 envelope with the existing ciphertext and authenticated context. Application plaintext is never decrypted during rotation.

Recovery Kits

PropType
rootKeyRootKeyHandle
TypeRootKeyHandle
vaultIdstring
Typestring
keyVersionnumber
Typenumber
createdAtstring
Typestring
secretUint8Array
TypeUint8Array

createRecoveryKit derives a wrapping key from the supplied secret using the parameters embedded in the kit and encrypts the root key locally. openRecoveryKit validates the structure, derives the same key, and returns an opaque root-key handle only after authentication succeeds.

Recovery failures intentionally do not reveal whether the secret, ciphertext, or authenticated metadata was wrong. Never upload the secret or file to diagnose them.

Device keys and envelopes

Method Purpose
createDeviceKey() Generates a local X25519 identity and returns an opaque private-key handle plus public key.
wrapRootKeyForDevice(input) Creates a recipient-bound, versioned device envelope.
openDeviceEnvelope(input) Opens an envelope only with the matching device private key.
compareRootKeys(left, right) Constant-behavior equality check used by recovery verification.
PropType
rootKeyRootKeyHandle
TypeRootKeyHandle
deviceIdstring
Typestring
vaultIdstring
Typestring
keyVersionnumber
Typenumber
createdAtstring
Typestring
recipientPublicKeystring
Typestring

Device public keys and encrypted envelopes may cross the network. Device private keys and opened root handles must remain in local protected storage or memory.

Algorithms and format versions

Artifact Current suite Read versions Write version
Record envelope XCHACHA20-POLY1305-IETF 1, 2 2
Recovery Kit ARGON2ID13(3,67108864,32)+XCHACHA20-POLY1305-IETF 1 1
Device envelope X25519-HKDF-SHA-256+A256GCM 1 1
Protected device key X25519+A256GCM 1 1
Pairing offer Structured public metadata 1 1

The persisted value carries its own format, version, algorithm identifiers, nonce, salt, and authenticated context. Do not strip or replace those fields in database mappings.

Browser worker entry point

The worker entry point belongs to @ownfold/browser/worker, but it exposes the same production engine through a message boundary.

import { exposeOwnfoldCryptoWorker } from "@ownfold/browser/worker"

exposeOwnfoldCryptoWorker()
import { createVaultClient, createWorkerCryptoEngine } from "@ownfold/browser"

const worker = new Worker(
  new URL("../ownfold-crypto.worker.ts", import.meta.url),
  { type: "module" },
)

export const vault = createVaultClient({
  cryptoEngine: createWorkerCryptoEngine({ worker }),
  transport,
})

Worker isolation reduces accidental main-thread key exposure. Same-origin script compromise can still call an unlocked worker through the application, so keep CSP, dependency integrity, and automatic locking controls.

Testing entry point

Import deterministic helpers only from @ownfold/crypto/testing.

Export Behavior
createDeterministicCryptoEngine(values) Consumes caller-provided byte arrays as deterministic randomness.
withEnvelopeVersionForTesting(engine, version) Forces record writes to envelope V1 or V2 for compatibility tests.
import {
  createDeterministicCryptoEngine,
  withEnvelopeVersionForTesting,
} from "@ownfold/crypto/testing"

Never ship the testing entry point in production. Exhausted or incorrectly sized deterministic input produces a typed crypto failure; it never falls back to insecure randomness.

Error surface

Operations return CryptoError, which includes invalid input, corrupted/authentication failures, unavailable key versions, Recovery Kit failures, and runtime failures. Handle them as values and destroy transient handles in cleanup paths. See Errors.

Last updated on August 4, 2026

Was this page helpful?