LVE is a browser-native encrypted file format — WebAuthn PRF + AES-256-GCM + AES-KW. Files are passkey-locked: only a registered WebAuthn credential with PRF support can decrypt them. There is no password fallback by design.
| Browser | Minimum version |
|---|---|
| Chrome / Edge | 118+ |
| Safari | 17.5+ |
Requires: Web Crypto API (SubtleCrypto) + WebAuthn PRF extension.
Node.js 18+ is supported for crypto operations, but passkey APIs are browser-only.
plaintext
└─ AES-256-GCM (random DEK + 12-byte IV)
└─ DEK wrapped with AES-KW per credential
└─ KEK = HKDF-SHA-256(PRF output, wrapSalt, fileId)
└─ PRF output = WebAuthn assertion on fileId
Each authorized credential gets its own wrapped_dek entry in the file header.
The ciphertext is encrypted once; only the wrapped DEK copies differ per recipient.
import {
checkPasskeyCapabilities,
registerCredential,
authenticateWithPrf,
createLveFile,
LvePrfNotSupportedError,
} from "@lve/sdk";
// 1. Check browser support
const caps = await checkPasskeyCapabilities();
if (!caps.prfSupported) {
throw new LvePrfNotSupportedError("PRF not available — use Chrome 118+");
}
// 2. Register a passkey (skip if the user already has one)
const cred = await registerCredential({
rpId: window.location.hostname,
rpName: "My App",
userId: crypto.randomUUID().slice(0, 16),
userName: "user@example.com",
userDisplayName: "User",
});
// 3. Authenticate to get the PRF output bound to a specific fileId
const fileId = crypto.randomUUID();
const authResult = await authenticateWithPrf({
rpId: window.location.hostname,
fileId,
});
// 4. Encrypt
const lveBytes = await createLveFile({
plaintext: new TextEncoder().encode("secret content"),
rpId: window.location.hostname,
fileId,
credentials: [{
credentialId: authResult.credentialId,
prfOutput: authResult.prfOutput,
}],
});
import { exportMetadata, authenticateWithPrf, openLveFile } from "@lve/sdk";
// 1. Read public metadata — no passkey needed
const meta = exportMetadata(lveBytes);
console.log(`${meta.file_id} — ${meta.recipient_count} authorized credential(s)`);
// 2. Authenticate to get PRF output, bound to this file's ID
const authResult = await authenticateWithPrf({
rpId: window.location.hostname,
fileId: meta.file_id,
});
// 3. Decrypt
const { plaintext } = await openLveFile(lveBytes, {
rpId: window.location.hostname,
credentials: [{
credentialId: authResult.credentialId,
prfOutput: authResult.prfOutput,
}],
});
console.log(new TextDecoder().decode(plaintext));
import { addAuthorizedCredential } from "@lve/sdk";
// Alice (already authorized) grants access to Bob
const aliceAuth = await authenticateWithPrf({ rpId, fileId: meta.file_id });
const bobReg = await registerCredential({ rpId, rpName: "My App", ... });
const updatedLve = await addAuthorizedCredential(lveBytes, {
existingCredentialId: aliceAuth.credentialId,
existingPrfOutput: aliceAuth.prfOutput,
newCredentialId: bobReg.credentialId,
newPrfOutput: bobReg.prfOutput!,
});
// updatedLve is openable by both Alice and Bob
All errors extend LveError.
import {
openLveFile, LveError,
LvePrfNotSupportedError, LveWrongCredentialError,
LveTamperedError, LveInvalidFormatError, LveWrongOriginError,
} from "@lve/sdk";
try {
const { plaintext } = await openLveFile(lveBytes, { rpId, credentials });
} catch (err) {
if (err instanceof LvePrfNotSupportedError) {
// Browser or authenticator does not support PRF — no fallback
showError("Use Chrome 118+ or Safari 17.5+");
} else if (err instanceof LveWrongCredentialError) {
// This passkey is not authorized, or wrong PRF output
showError("Wrong passkey");
} else if (err instanceof LveTamperedError) {
// AES-GCM auth tag mismatch — file was modified
showError("File integrity check failed");
} else if (err instanceof LveInvalidFormatError) {
// Not a .lve file (wrong magic bytes, truncated, etc.)
showError("Not a valid .lve file");
} else if (err instanceof LveWrongOriginError) {
// File was created for a different rp_id (different app/domain)
showError("File belongs to a different app");
} else if (err instanceof LveError) {
showError(err.message);
} else {
throw err;
}
}
| Error class | When thrown |
|---|---|
LveError |
Base — catch all LVE errors |
LvePrfNotSupportedError |
PRF unavailable; no password fallback |
LveWrongCredentialError |
Passkey not authorized or wrong PRF output |
LveTamperedError |
Ciphertext or AAD-covered header was modified |
LveUnsupportedVersionError |
Format version > 1 not supported by this SDK |
LveInvalidFormatError |
Not a valid .lve binary |
LveWrongOriginError |
File bound to a different rp_id |