@ursalock/crypto
Low-level crypto functions used internally by @ursalock/client. Most users only need deriveVaultKeys and bytesToBase64.
deriveVaultKeys
Section titled “deriveVaultKeys”Derive vault-specific encryption, HMAC, and index keys from a master key via HKDF.
import { deriveVaultKeys } from "@ursalock/crypto";
const masterKey = base64urlToBytes(cipherJwk.k); // 32 bytes from passkey PRFconst keys = await deriveVaultKeys(masterKey, vaultUid);
keys.encryptionKey; // Uint8Array(32) — AES-256-GCM encryptionkeys.hmacKey; // Uint8Array(32) — HMAC-SHA256 integritykeys.indexKey; // Uint8Array(32) — deterministic document indexingParameters:
masterKey: Uint8Array— 32-byte master key (from passkey CipherJWK)vaultUid: string— Vault UID (used as HKDF context for key separation)
Returns: VaultKeys — { encryptionKey, hmacKey, indexKey } (all 32-byte Uint8Arrays)
This is the key function for setting up a DocumentClient. Each vault gets unique derived keys, so compromising one vault’s keys doesn’t affect others.
bytesToBase64 / base64ToBytes
Section titled “bytesToBase64 / base64ToBytes”Convert between Uint8Array and base64 strings.
import { bytesToBase64, base64ToBytes } from "@ursalock/crypto";
const b64 = bytesToBase64(keys.encryptionKey);// => "aGVsbG8gd29ybGQ..." (standard base64)
const bytes = base64ToBytes(b64);// => Uint8Array(32)Use bytesToBase64 when you need to share derived keys with an agent (e.g. via the Agent Access UI).
encrypt / decrypt
Section titled “encrypt / decrypt”AES-256-GCM encryption using Web Crypto API. Used internally by DocumentClient.
import { encrypt, decrypt } from "@ursalock/crypto";
// Encryptconst plaintext = new TextEncoder().encode(JSON.stringify(data));const { combined } = await encrypt(plaintext, key); // combined = iv + ciphertext
// Decryptconst decrypted = await decrypt(combined, key);const data = JSON.parse(new TextDecoder().decode(decrypted));- 256-bit key, 96-bit IV (NIST recommended for GCM)
- 128-bit auth tag (included in ciphertext by Web Crypto)
encryptWithJwk / decryptWithJwk
Section titled “encryptWithJwk / decryptWithJwk”Encrypt/decrypt directly with a CipherJWK (used for local storage encryption).
import { encryptWithJwk, decryptWithJwk } from "@ursalock/crypto";import type { CipherJWK } from "@ursalock/crypto";
const encrypted = await encryptWithJwk(plaintext, cipherJwk);const decrypted = await decryptWithJwk(encrypted.combined, cipherJwk);computeHmac / verifyHmac
Section titled “computeHmac / verifyHmac”HMAC-SHA256 for data integrity verification (Encrypt-then-MAC pattern).
import { computeHmac, verifyHmac } from "@ursalock/crypto";
const tag = await computeHmac(data, hmacKey); // hex stringconst valid = await verifyHmac(data, hmacKey, tag); // booleanUsed by the sync engine to detect server-side tampering of encrypted blobs.
Raw HKDF-SHA256 derivation.
import { hkdf } from "@ursalock/crypto";
const derived = await hkdf(inputKey, salt, info, length);// => Uint8Array of `length` bytesderiveVaultKeys is a higher-level wrapper around this.
import type { CipherJWK, // JWK with 'k' field containing AES key VaultKeys, // { encryptionKey, hmacKey, indexKey } EncryptedPayload // { iv, ciphertext, combined }} from "@ursalock/crypto";