Browser-based E2EE client library for true end-to-end encryption including keys, crypto, compression, and local storage adapters.
npm install @voideddev/e2ee-clienttypescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
// Create client with default settings
const client = new VoidedE2EEClient();
// Encrypt data (full pipeline: compression + encryption + signatures)
const encrypted = await client.encrypt("Hello, World!");
// Non-chunked return shape:
// {
// data: "base64_encrypted_data",
// iv: "base64_iv",
// keyId: "default",
// algorithm: "AES-GCM",
// version: "1.0",
// compression: { algorithm: "brotli" | "gzip" | "none", originalSize: 13, compressedSize: 11 },
// signature?: "base64_signature",
// ephemeralPublicKey?: "base64_public_key",
// textEncoding?: "utf8" | "utf16le"
// }
// Decrypt data
const decrypted = await client.decrypt(encrypted);
console.log(decrypted); // 'Hello, World!'
// Export key for backup
const key = await client.exportKey();
console.log("Backup this key:", key); // "U2FsdGVkX19QYXNzd29yZFRlc3Q="
`
๐ฆ Installation
`bash
npm install @voideddev/e2ee-client
`
๐ง Core Components
$3
The primary encryption client that handles all cryptographic operations, compression,
optional signatures, optional forward secrecy, and automatic chunking of large payloads.
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
// Basic usage
const client = new VoidedE2EEClient();
// Advanced configuration
const client = new VoidedE2EEClient({
keyId: "user123",
autoGenerateKey: true,
enableSignatures: true,
enableForwardSecrecy: true,
enableChunking: true,
chunkSize: 2 1024 1024, // 2MB chunks
minChunkThreshold: 10 1024 1024, // 10MB threshold
});
// Example usage with return values (non-chunked):
const encrypted = await client.encrypt("sensitive data");
// Returns: { data, iv, keyId: 'user123', algorithm: 'AES-GCM', version: '1.0', compression, signature?, ephemeralPublicKey?, textEncoding? }
const decrypted = await client.decrypt(encrypted);
// Returns: "sensitive data"
`
Configuration Options:
- keyId: Unique identifier for the key (default: 'default')
- autoGenerateKey: Auto-generate key if none exists (default: true)
- enableSignatures: Enable digital signatures for authenticity
- enableForwardSecrecy: Enable ephemeral keys for forward secrecy
- enableChunking: Enable automatic chunking for large data
- chunkSize: Size of chunks in bytes (default: 2MB)
- minChunkThreshold: Minimum size to trigger chunking (default: 10MB)
Additional encryption-time options passed to encrypt(data, options):
- compressionAlgorithm: 'auto' | 'gzip' | 'brotli' | 'none' (default: 'auto')
- compressionLevel: number (gzip: 1โ9, brotli: 1โ11; default: 6)
- forceCompression: boolean (override auto-skip logic)
- originalSizeBytes: number (explicit data size for limit enforcement)
- resumeTokenOriginalSize: number (explicit size when resuming)
$3
Handles all cryptographic operations using Web Crypto API.
`typescript
import { CryptoService } from "@voideddev/e2ee-client";
const crypto = new CryptoService();
// Generate encryption key
const key = await crypto.generateKey();
// Returns: CryptoKey (Web Crypto API key object)
// Generate signing key pair
const signingKeyPair = await crypto.generateSigningKeyPair();
// Returns: { publicKey: CryptoKey, privateKey: CryptoKey }
// Generate key agreement key pair
const agreementKeyPair = await crypto.generateKeyAgreementKeyPair();
// Returns: { publicKey: CryptoKey, privateKey: CryptoKey }
// Derive key from data
const derivedKey = await crypto.deriveKeyFromPassword(data, salt, iterations);
// Returns: CryptoKey (Derived AES-GCM key)
// Perform key agreement
const sharedKey = await crypto.deriveSharedKey(privateKey, publicKey);
// Returns: CryptoKey (Shared AES-GCM key)
// Get key fingerprint (SHA-256 hex of key material)
const fingerprint = await crypto.getKeyFingerprint(key);
// Returns: 64-character hex string (e.g., "dffd6021bb2bd5b0af67...")
// Get safety numbers (like Signal)
const safetyNumbers = await crypto.getSafetyNumbers(key);
// Returns: "123 456 789 012 345" (Human-readable safety numbers)
`
Key Functions:
- generateKey(): Generate AES-GCM encryption key
- generateSigningKeyPair(): Generate ECDSA key pair for signatures
- generateKeyAgreementKeyPair(): Generate ECDH key pair for key agreement
- deriveKeyFromPassword(): PBKDF2-based key derivation from data (deterministic keys for cloud deployments)
- deriveSharedKey(): ECDH key agreement
- getKeyFingerprint(): Generate key fingerprint for verification
- getSafetyNumbers(): Generate human-readable safety numbers
$3
Provides intelligent compression with automatic and explicit algorithm selection.
`typescript
import {
compress,
decompress,
analyzeCompression,
} from "@voideddev/e2ee-client";
// Compress with explicit or automatic algorithm selection
const result = await compress(data, {
algorithm: "auto", // 'auto' | 'brotli' | 'gzip' | 'none'
minSizeThreshold: 100, // Skip compression below threshold
compressionLevel: 6, // 1-9 for gzip, 1-11 for brotli
});
// Returns: { compressed: Uint8Array(23) [...], algorithm: "brotli", originalSize: 52, compressedSize: 23, compressionRatio: 0.44 }
// Decompress
const decompressed = await decompress(result.compressed, result.algorithm);
// Returns: Uint8Array(52) [...] (Decompressed data)
// Analyze compression effectiveness
const analysis = await analyzeCompression(data);
// Returns: { originalSize: 52, gzipSize: 25, brotliSize: 23, gzipRatio: 0.48, brotliRatio: 0.44, recommendation: "brotli" }
console.log(Recommendation: ${analysis.recommendation});
console.log(Brotli ratio: ${analysis.brotliRatio});
console.log(Gzip ratio: ${analysis.gzipRatio});
`
Features:
- Automatic algorithm selection (Brotli preferred, Gzip fallback)
- Size-based compression decisions
- Entropy analysis to avoid compressing already-compressed data
- Compression ratio analysis
- Browser-compatible using fflate and brotli-wasm
$3
Manages encryption keys with versioning, rotation, and migration support. Rotation is protected
by an internal lock to prevent concurrent operations. During migration, a legacy key is cached
and used transparently for decryption until finalizeMigration() is called.
Important: The KeyManager uses in-memory caching for performance but relies on the provided StorageService for persistence. Keys are automatically loaded from storage and cached in memory. If you're using a custom storage implementation, ensure it provides reliable persistence.
`typescript
import { KeyManager } from "@voideddev/e2ee-client";
const keyManager = new KeyManager(storage, crypto, keyId);
// Get current key
const key = await keyManager.getCurrentKey();
// Returns: CryptoKey (Current encryption key)
// Force rotate key (delete old, generate new)
const newKey = await keyManager.forceRotate();
// Returns: "base64_new_key_string"
// Start migration (keep old key, generate new)
const newKey = await keyManager.startMigration(cutoffTime);
// Returns: "base64_new_key_string"
// Finalize migration (remove old key)
await keyManager.finalizeMigration();
// Returns: void (No return value)
// Get migration status
const status = await keyManager.getMigrationStatus();
// Returns: { isActive: true, oldKeyVersion: 1, newKeyVersion: 2, cutoffTime: Date, lastProgress: 0.75, createdAt: Date } | null
`
Key Features:
- Automatic key generation and caching
- Key versioning with migration support
- Rotation lock to prevent concurrent operations
- Legacy key support during migration
- Secure key storage and retrieval
- In-memory caching with persistent storage backend
$3
Abstracts storage operations for keys and metadata with robust IndexedDB implementation. The built-in
IndexedDBStorage now also persists signing and agreement key pairs and migration state alongside the main key.
#### Why Frontend Keys?
Client-Side Security Model:
The e2ee-client library operates on a true client-side security model where encryption keys never leave the user's browser. This approach provides several critical security benefits:
- Zero Server Knowledge: Your server never sees encryption keys, eliminating a major attack vector
- User Control: Users maintain complete control over their encryption keys
- Privacy by Design: Even if your server is compromised, encrypted data remains secure
- Compliance: Meets strict privacy requirements (GDPR, HIPAA, etc.)
Key Management Strategy:
- Keys are generated, stored, and managed entirely in the browser
- Users can export/import keys for backup and device migration
- Key rotation and migration happen locally without server involvement
- Multiple key versions can coexist during migration periods
#### IndexedDB Utilization
The library uses IndexedDB as the primary storage mechanism for several reasons:
Why IndexedDB?
- Persistent Storage: Survives browser restarts and device reboots
- Large Capacity: Can store multiple keys, key pairs, and migration states
- Structured Data: Supports complex data structures and relationships
- Security: Sandboxed per origin, isolated from other websites
- Performance: Asynchronous operations don't block the main thread
Database Structure:
`typescript
// IndexedDB Database: 'voideddev-e2ee' (version 3)
{
// Store: 'keys' - Main encryption keys
keys: {
keyPath: 'id',
records: [
{ id: 'user123', key: 'base64_encryption_key' },
{ id: 'default', key: 'base64_encryption_key' }
]
},
// Store: 'migrations' - Key migration states
migrations: {
keyPath: 'id',
records: [
{
id: 'user123',
isActive: true,
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
lastProgress: 0.75,
createdAt: Date
}
]
},
// Store: 'keyPairs' - Signing and agreement keys
keyPairs: {
keyPath: 'id',
records: [
{ id: 'user123-signing', keyPair: 'base64_signing_key_pair' },
{ id: 'user123-agreement', keyPair: 'base64_agreement_key_pair' }
]
}
}
`
Storage Operations:
`typescript
import { StorageService, IndexedDBStorage } from "@voideddev/e2ee-client";
// Use built-in IndexedDB storage
const storage = new StorageService(new IndexedDBStorage());
// Or implement custom storage
const customStorage = {
async getKey(keyId: string): Promise {
/ ... /
},
async setKey(keyId: string, key: string): Promise {
/ ... /
},
async removeKey(keyId: string): Promise {
/ ... /
},
// ... other methods
};
const storage = new StorageService(customStorage);
// Example storage operations with return values:
const key = await storage.getKey("user123");
// Returns: "base64_key_string" | null
await storage.setKey("user123", "base64_key_string");
// Returns: void (No return value)
await storage.removeKey("user123");
// Returns: void (No return value)
`
Storage Interface:
- getKey(keyId): Retrieve encryption key
- setKey(keyId, key): Store encryption key
- removeKey(keyId): Delete encryption key
- getMigrationState(keyId): Get migration status
- setMigrationState(keyId, state): Store migration state
- getKeyPair(keyId, type): Retrieve key pairs
- setKeyPair(keyId, type, keyPair): Store key pairs
IndexedDB Features:
- Automatic Versioning: Database schema upgrades handled automatically
- Error Handling: Graceful fallbacks when IndexedDB is unavailable
- Transaction Safety: ACID-compliant operations for data integrity
- Memory Management: Efficient caching with automatic cleanup
- Cross-Tab Support: Keys accessible across browser tabs/windows
$3
Built-in UI components for key export and import with QR code support and extensive customization options.
#### QR Modal Integration
The library provides sophisticated QR code integration for secure key backup and transfer:
Key Export Modal Features:
- QR Code Generation: Automatic QR code generation using the qrcode library
- Fallback Support: Graceful degradation when QR library is unavailable
- Web Share API: Native sharing capabilities on supported platforms
- Multiple Export Formats: Text, QR code, download, and share options
- Responsive Design: Mobile-friendly interface with touch support
Key Import Modal Features:
- QR Code Scanning: Built-in QR code scanning capability (requires camera access)
- Manual Input: Text area for pasting keys manually
- Validation: Automatic key format validation
- Error Handling: Comprehensive error messages and recovery options
#### Customization Options
The UI components are highly customizable through CSS classes and configuration options:
`typescript
import { createKeyExport, createKeyImport } from "@voideddev/e2ee-client";
// Create export UI with full customization
const keyExport = createKeyExport(client, {
// Display options
showQR: true, // Show QR code
showText: true, // Show text area
showShare: true, // Enable Web Share API
// Content
title: "Backup Your Encryption Key",
// CSS customization classes
className: "my-key-export",
overlayClassName: "my-overlay",
modalClassName: "my-modal",
qrContainerClassName: "my-qr-container",
textAreaClassName: "my-textarea",
buttonClassName: "my-button",
copyButtonClassName: "my-copy-btn",
downloadButtonClassName: "my-download-btn",
shareButtonClassName: "my-share-btn",
closeButtonClassName: "my-close-btn",
warningClassName: "my-warning",
keyIdClassName: "my-key-id",
// Event callbacks
onClose: () => console.log("Modal closed"),
onCopy: () => console.log("Key copied!"),
onDownload: () => console.log("Key downloaded!"),
onShare: () => console.log("Key shared!"),
});
// Returns: VoidedKeyExport (Key export UI component instance)
// Create import UI with customization
const keyImport = createKeyImport(client, {
title: "Import Encryption Key",
showQRScan: true, // Enable QR scanning
// CSS customization classes
className: "my-key-import",
overlayClassName: "my-overlay",
modalClassName: "my-modal",
textAreaClassName: "my-textarea",
buttonClassName: "my-button",
importButtonClassName: "my-import-btn",
scanButtonClassName: "my-scan-btn",
cancelButtonClassName: "my-cancel-btn",
closeButtonClassName: "my-close-btn",
warningClassName: "my-warning",
// Event callbacks
onClose: () => console.log("Modal closed"),
onSuccess: () => console.log("Key imported!"),
onError: (error) => console.error("Import failed:", error),
});
// Returns: VoidedKeyImport (Key import UI component instance)
// Show modals
keyExport.show(); // Returns: void (No return value, shows modal)
keyImport.show(); // Returns: void (No return value, shows modal)
`
#### QR Code Implementation Details
QR Code Generation:
- Uses the qrcode library for high-quality QR codes
- Automatic fallback to text representation if library unavailable
- Configurable QR code size, margin, and colors
- Includes key ID and metadata in QR format
QR Code Scanning:
- Camera-based QR code scanning (requires user permission)
- Automatic key extraction and validation
- Support for multiple QR code formats
- Error handling for invalid or corrupted QR codes
Web Share API Integration:
- Native sharing on mobile devices
- Fallback to clipboard copy if sharing unavailable
- Configurable share content and format
- Cross-platform compatibility
#### CSS Customization
The components use semantic HTML with data attributes for easy styling:
`css
/ Example custom styling /
.my-modal {
background: white;
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
max-width: 500px;
width: 90%;
}
.my-qr-container {
text-align: center;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
margin: 20px 0;
}
.my-button {
background: #007bff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
}
.my-button:hover {
background: #0056b3;
}
.my-warning {
background: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
padding: 12px;
border-radius: 6px;
margin-top: 20px;
}
`
#### Data Attributes for Targeting
Components include data attributes for precise CSS targeting:
`css
/ Target specific elements /
[data-voideddev-component="qr-container"] {
/ QR code container styles /
}
[data-voideddev-component="key-textarea"] {
/ Text area styles /
}
[data-voideddev-action="copy"] {
/ Copy button styles /
}
`
$3
Comprehensive hashing utilities for data integrity and verification.
`typescript
import { hashService } from "@voideddev/e2ee-client";
// Basic hashing
const hash = await hashService.generateHash(data, "sha256");
// Salted hashing
const saltedHash = await hashService.generateHashWithSalt(
data,
"mysalt",
"sha256"
);
// High-iteration hashing (PBKDF2-SHA256)
const { hash: iterHash, salt } = await hashService.hashWithHighIterations(data);
const isValid = await hashService.verifyWithHighIterations(
data,
iterHash,
salt
);
// Verify salted hash
const isValidSalted = await hashService.verifyWithSalt(
data,
saltedHash,
"mysalt",
"sha256"
);
// Fingerprints and safety numbers
const fingerprint = await hashService.generateFingerprint(data);
const safetyNumbers = await hashService.generateSafetyNumbers(data);
// Random utilities
const randomHash = await hashService.generateRandomHash("sha256");
const randomSalt = hashService.generateRandomSalt(32);
`
HashService Methods:
- generateHash(data, algorithm): Generate basic hash
- generateHashWithSalt(data, salt, algorithm): Generate salted hash
- compareHash(data, hash, algorithm): Compare data with hash
- hashWithHighIterations(data, salt): High-iteration PBKDF2 hashing
- verifyWithHighIterations(data, hash, salt): Verify high-iteration hash
- verifyWithSalt(data, hash, salt, algorithm): Verify salted hash
- generateRandomHash(algorithm): Generate random hash
- generateFingerprint(data, length): Generate key fingerprint
- generateSafetyNumbers(data, groupSize): Generate safety numbers
- generateRandomSalt(length): Generate random salt
- secureWipe(buffer): Securely wipe buffer from memory$3
Additive key-management APIs for deterministic passkey-derived keys and X25519-based sharing.
`typescript
import { VoidedE2EEClient, PasskeyKeyDeriver, KeySharing } from "@voideddev/e2ee-client";// 1) Client-level PDK flow
const client = new VoidedE2EEClient({
keyId: "user-123",
autoGenerateKey: false,
pdkConfig: {
salt: "my-app-v1",
appId: "my-app",
cacheKey: true,
},
});
await client.deriveAndSetPDK(credentialPublicKey, credentialId);
// 2) Advanced derivation flow
const deriver = new PasskeyKeyDeriver({
salt: "my-app-v1",
appId: "my-app",
cacheKey: true,
});
const masterPdk = await deriver.deriveKey(credentialPublicKey, credentialId);
const appScopedKey = await deriver.deriveAppKey(masterPdk, "chat");
const identity = await deriver.deriveIdentityKeyPair(masterPdk, "user-123");
// 3) X25519 key sharing flow
const sharing = new KeySharing();
const encryptedKeyBlob = await sharing.encryptKeyForRecipient(
appScopedKey,
senderPrivateKeyBytes, // 32-byte X25519 private key
recipientPublicKeyBytes // 32-byte X25519 public key
);
const recoveredKey = await sharing.decryptKeyFromSender(
encryptedKeyBlob,
recipientPrivateKeyBytes,
senderPublicKeyBytes
);
`New Methods/Capabilities in 0.3.0:
-
VoidedE2EEClient.deriveAndSetPDK(credentialPublicKey, credentialId)
- PasskeyKeyDeriver.deriveKey(...)
- PasskeyKeyDeriver.deriveAppKey(...)
- PasskeyKeyDeriver.deriveIdentityKeyPair(...)
- KeySharing.encryptKeyForRecipient(...)
- KeySharing.decryptKeyFromSender(...)
- KeySharing.deriveTransferKey(...)
- KeySharing.encryptKeyForTransfer(...)
- KeySharing.decryptKeyFromTransfer(...)๐ฏ Usage Patterns
$3
Important: The encrypt() and decrypt() methods in VoidedE2EEClient are full pipeline functions that handle compression, encryption, digital signatures (if enabled), forward secrecy (if enabled), and chunking (if enabled) automatically. These are the primary methods you should use for most encryption/decryption tasks.
The library also provides individual components (CryptoService, compression, HashService) for advanced users who need fine-grained control over the encryption process.
$3
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
const client = new VoidedE2EEClient({
enableSignatures: true,
enableForwardSecrecy: true,
enableChunking: true,
});
// Encrypt data with full pipeline (compression + encryption + signatures + forward secrecy + chunking)
const encrypted = await client.encrypt("sensitive data", {
compressionAlgorithm: "auto",
compressionLevel: 6,
forceCompression: false,
});
// Returns: Non-chunked or chunked shape depending on size (see Large File Handling)
// Decrypt data with full pipeline
const decrypted = await client.decrypt(encrypted);
// Returns: "sensitive data"
console.log(decrypted); // 'sensitive data'
`
$3
For advanced users who need fine-grained control over the encryption process:
`typescript
import { CryptoService } from "@voideddev/e2ee-client";
import { compress, decompress } from "@voideddev/e2ee-client";
import { hashService } from "@voideddev/e2ee-client";
const crypto = new CryptoService();
// Step 1: Compress data
const compressionResult = await compress("sensitive data", {
algorithm: "brotli",
});
// Returns: { compressed: Uint8Array(23) [...], algorithm: "brotli", originalSize: 14, compressedSize: 12, compressionRatio: 0.86 }
// Step 2: Generate encryption key
const key = await crypto.generateKey();
// Returns: CryptoKey (Web Crypto API key object)
// Step 3: Encrypt compressed data
const encrypted = await crypto.encrypt(compressionResult.compressed, key);
// Returns: ArrayBuffer (Encrypted data, IV is prefixed to the ciphertext)
// Step 4: Generate hash for integrity
const hash = await hashService.generateHash("sensitive data");
// Returns: "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f" (Hex string hash)
// For decryption, reverse the process
const decrypted = await crypto.decrypt(encrypted, iv, key);
// Returns: Uint8Array (Decrypted data)
const decompressed = await decompress(decrypted, "brotli");
// Returns: Uint8Array(14) [...] (Decompressed data)
const finalData = new TextDecoder().decode(decompressed);
// Returns: "sensitive data"
`
$3
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
const client = new VoidedE2EEClient({
enableSignatures: true,
enableForwardSecrecy: true,
});
// Derive key from password using PBKDF2 (salt auto-generated if omitted)
await client.deriveKeyFromPassword({
password: "my-secure-data",
iterations: 100000,
});
// Returns: void (No return value, key is stored internally)
// Generate signing keys
const signingPublicKey = await client.generateSigningKeys();
// Returns: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..." (Base64 ECDSA public key)
// Generate agreement keys
const agreementPublicKey = await client.generateAgreementKeys();
// Returns: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..." (Base64 ECDH public key)
// Get identity verification info
const fingerprint = await client.getKeyFingerprint();
// Returns: "a1b2c3d4" (8-character fingerprint for key verification)
const safetyNumbers = await client.getSafetyNumbers();
// Returns: "123 456 789 012 345" (Human-readable safety numbers like Signal)
// Encrypt with all security features
const encrypted = await client.encrypt("maximum security message", {
compressionAlgorithm: "auto",
});
// Returns: Non-chunked or chunked shape depending on size (see Large File Handling)
const decrypted = await client.decrypt(encrypted);
// Returns: "maximum security message"
`
$3
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
const client = new VoidedE2EEClient();
// Export key
const exportedKey = await client.exportKey();
// Returns: "U2FsdGVkX19QYXNzd29yZFRlc3Q=" (Base64 exported key string)
// Import key
await client.importKey(exportedKey);
// Returns: void (No return value)
// Rotate key (force - delete old key)
const newKey = await client.rotateKey({ force: true });
// Returns: "base64_new_key_string"
// Rotate key (migration - keep old key)
const newKey = await client.rotateKey({
migrate: true,
cutoffTime: new Date(),
});
// Returns: "base64_new_key_string"
// Check migration status
const status = await client.getMigrationStatus();
// Returns: { isActive: true, oldKeyVersion: 1, newKeyVersion: 2, cutoffTime: Date, lastProgress: 0.75, createdAt: Date } | null
if (status?.isActive) {
await client.finalizeMigration();
// Returns: void (No return value)
}
`
$3
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
const alice = new VoidedE2EEClient({ keyId: "alice" });
const bob = new VoidedE2EEClient({ keyId: "bob" });
// Generate key agreement keys
const alicePublicKey = await alice.generateAgreementKeys();
// Returns: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..." (Base64 ECDH public key)
const bobPublicKey = await bob.generateAgreementKeys();
// Returns: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..." (Base64 ECDH public key)
// Perform key agreement
await alice.performKeyAgreement(bobPublicKey);
// Returns: void (No return value, shared key is derived internally)
await bob.performKeyAgreement(alicePublicKey);
// Returns: void (No return value, shared key is derived internally)
// Now they share the same encryption key
const message = "Secret message";
const encrypted = await alice.encrypt(message, {
compressionAlgorithm: "auto",
});
// Returns: Non-chunked or chunked shape depending on size (see Large File Handling)
const decrypted = await bob.decrypt(encrypted);
// Returns: "Secret message"
`
$3
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
const alice = new VoidedE2EEClient({ keyId: "alice" });
const bob = new VoidedE2EEClient({ keyId: "bob" });
// Get fingerprints
const aliceFingerprint = await alice.getKeyFingerprint();
// Returns: 64-character hex string fingerprint
const aliceSafetyNumbers = await alice.getSafetyNumbers();
// Returns: "123 456 789 012 345" (Human-readable safety numbers like Signal)
// Verify identity (in real usage, share through secure channel)
const verification = await bob.verifyFingerprint(aliceFingerprint);
// Returns: { fingerprint: string / 64-char hex /, safetyNumbers: "123 456 789 012 345", verified: true }
console.log("Verified:", verification.verified);
`
$3
`typescript
import {
VoidedE2EEClient,
createKeyExport,
createKeyImport,
} from "@voideddev/e2ee-client";
const client = new VoidedE2EEClient();
// Create UI components
const keyExport = createKeyExport(client, {
title: "Backup Your Key",
showQR: true,
onCopy: () => showToast("Key copied!"),
});
// Returns: VoidedKeyExport (Key export UI component instance)
const keyImport = createKeyImport(client, {
onSuccess: () => showToast("Key imported!"),
onError: (error) => showError("Import failed: " + error),
});
// Returns: VoidedKeyImport (Key import UI component instance)
// React component example
function KeyManagement() {
return (
);
}
`
$3
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
const client = new VoidedE2EEClient({
enableChunking: true,
chunkSize: 1024 * 1024, // 1MB chunks
minChunkThreshold: 5 1024 1024, // 5MB threshold
});
// Large data will be automatically chunked
const largeData = "x".repeat(10 1024 1024); // 10MB
const encrypted = await client.encrypt(largeData);
// Chunked return shape:
// {
// keyId: "default",
// algorithm: "AES-GCM",
// version: "1.0",
// compression: { algorithm: 'brotli' | 'gzip' | 'none', originalSize: 10485760, compressedSize: 10485760 },
// chunks: Array<{ data: string; iv: string; index: number; signature?: string }>,
// chunkInfo: { isChunked: true, totalChunks: 10, chunkSize: 1048576 },
// signature?: string,
// ephemeralPublicKey?: string,
// textEncoding?: 'utf8' | 'utf16le'
// }
// Check if data was chunked
if (encrypted.chunkInfo?.isChunked) {
console.log(Data chunked into ${encrypted.chunkInfo.totalChunks} chunks);
}
const decrypted = await client.decrypt(encrypted);
// Returns: "x".repeat(10 1024 1024) (Decrypted large data string)
// Tip: You can fine-tune compression during encryption
const encryptedAuto = await client.encrypt(largeData, {
compressionAlgorithm: "auto",
});
const encryptedNone = await client.encrypt(largeData, {
compressionAlgorithm: "none",
});
`
$3
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
// Implement custom storage (must implement the E2EEStorage interface)
// Note: Avoid localStorage/sessionStorage; use a secure storage backend.
const customStorage = {
async getKey(keyId) {
/ ... return base64 string or null ... /
},
async setKey(keyId, key) {
/ ... persist securely ... /
},
async removeKey(keyId) {
/ ... /
},
async getMigrationState(keyId) {
/ ... return MigrationState or null ... /
},
async setMigrationState(keyId, state) {
/ ... /
},
async removeMigrationState(keyId) {
/ ... /
},
async getKeyPair(keyId, type) {
/ ... /
},
async setKeyPair(keyId, type, keyPair) {
/ ... /
},
async removeKeyPair(keyId, type) {
/ ... /
},
};
const client = new VoidedE2EEClient({ storage: customStorage });
`
๐ Security Features
$3
- Uses PBKDF2 with configurable iterations (default: 100,000)
- OWASP-compliant security parameters
- Salt generation and management
$3
Basic Hashing (generateHash):
- Purpose: Data integrity verification, checksums
- Use case: Verify files haven't been corrupted
- Algorithm: SHA-256, SHA-512
Salted Hashing (generateHashWithSalt):
- Purpose: Data integrity with salt protection
- Use case: Store hashes that need to be verified later
- Algorithm: SHA-256, SHA-512 with salt
High-Iteration Hashing (hashWithHighIterations):
- Purpose: Secure storage of sensitive data
- Use case: Storing hashes that need to be resistant to brute force attacks
- Algorithm: PBKDF2(SHA-256) with 100,000+ iterations
// Note: HMAC utilities are not part of the current API surface.
$3
- Default algorithm: ECDSA P-256 with SHA-256
- Per-chunk and global signature support (when enabled via enableSignatures)
- Automatic signature verification on decryption when signatures are present
$3
- Ephemeral key generation for each message
- ECDH key agreement for shared secrets
- Perfect forward secrecy implementation
$3
- Key fingerprints for identity verification
- Safety numbers (like Signal) for human verification
- Timing-safe comparisons to prevent attacks
$3
- Secure key rotation with migration support
- Time-based cutoff for migration
- Legacy key support during transition (client tries current key, then legacy during active migration)
- Rotation lock prevents concurrent rotations; client waits to avoid races
$3
- Automatic chunking for large files
- Per-chunk signatures and encryption
- Memory-efficient processing
๐ฆ Limits
- Client-side upload limit: 32 GiB per file (enforced via size checks)
- Non-chunked encryption max payload: ~64 MB per AES-GCM operation
- Default chunking: 2 MB chunks; chunking automatically enabled for payloads โฅ 10 MB
- Limits can be tuned via chunkSize and minChunkThreshold
$3
You can run quick in-browser benchmarks to gauge performance:
`ts
import {
benchmarkCompression,
benchmarkEncryption,
benchmarkAll,
} from "@voideddev/e2ee-client/benchmark-all";
const comp = await benchmarkCompression("sample text", 10);
const enc = await benchmarkEncryption("sample text", 10);
const all = await benchmarkAll("sample text", 10);
`
๐ Performance Features
$3
- Automatic or explicit algorithm selection ('auto' | 'brotli' | 'gzip' | 'none')
- Threshold-based compression with minSizeThreshold
- Configurable compressionLevel (gzip: 1โ9, brotli: 1โ11)
- Compression can be forced via forceCompression
$3
- Automatic chunking for large files
- Parallel chunk processing
- Memory-efficient streaming
- Configurable chunk sizes and threshold
- Decryption reassembles compressed bytes, then decompresses once for correctness and speed
$3
- Key caching for performance
- Secure memory management
- Automatic cache invalidation
๐งช Testing and Validation
`typescript
import { VoidedE2EEClient } from "@voideddev/e2ee-client";
// Test encryption round-trip
async function testEncryption() {
const client = new VoidedE2EEClient();
const data = "test data";
const encrypted = await client.encrypt(data);
const decrypted = await client.decrypt(encrypted);
console.log("Round-trip successful:", data === decrypted);
}
// Test key rotation (migration preserves ability to decrypt old data)
async function testKeyRotation() {
const client = new VoidedE2EEClient();
const data = "test data";
const encrypted1 = await client.encrypt(data);
// Use migrate to keep old key available during transition
await client.rotateKey({ migrate: true, cutoffTime: new Date() });
const encrypted2 = await client.encrypt(data);
// Old data should still be decryptable during active migration
const decrypted1 = await client.decrypt(encrypted1);
const decrypted2 = await client.decrypt(encrypted2);
console.log("Rotation test:", decrypted1 === data && decrypted2 === data);
}
`
๐ง Integration Examples
$3
`typescript
import React, { useEffect, useState } from "react";
import {
VoidedE2EEClient,
createKeyExport,
createKeyImport,
} from "@voideddev/e2ee-client";
function EncryptionApp() {
const [client, setClient] = useState(null);
const [keyExport, setKeyExport] = useState(null);
const [keyImport, setKeyImport] = useState(null);
useEffect(() => {
const e2eeClient = new VoidedE2EEClient();
setClient(e2eeClient);
setKeyExport(createKeyExport(e2eeClient));
setKeyImport(createKeyImport(e2eeClient));
}, []);
const handleEncrypt = async (data: string) => {
if (!client) return;
const encrypted = await client.encrypt(data);
console.log("Encrypted:", encrypted);
};
return (
);
}
`
$3
`vue
`
$3
`svelte
`
๐จ Error Handling
`typescript
import {
VoidedE2EEClient,
ValidationError,
CryptoError,
KeyError,
} from "@voideddev/e2ee-client";
const client = new VoidedE2EEClient();
try {
const encrypted = await client.encrypt(data);
} catch (error) {
if (error instanceof ValidationError) {
console.error("Validation error:", error.message);
} else if (error instanceof CryptoError) {
console.error("Crypto error:", error.message);
} else if (error instanceof KeyError) {
console.error("Key error:", error.message);
} else {
console.error("Unknown error:", error);
}
}
`Build and Publishing Workflow
From
packages/e2ee-client:-
npm run build:wasm
- Builds Rust WASM (crates/voided-wasm) via wasm-pack
- Copies generated artifacts into packages/e2ee-client/wasm
- Generates/updates wasm/init.js
- npm run copy:wasm
- Only copies already-built artifacts from crates/voided-wasm/pkg into packages/e2ee-client/wasm
- Use when Rust/WASM is already built and you just need to sync package artifacts
- npm run test
- Runs Jest suite
- npm run build
- Builds dist/ bundles and typesRecommended publish validation:
1.
npm run build:wasm
2. npm run copy:wasm
3. npm run test
4. npm run build
5. npm pack --dry-run
6. npm publishNote: The repo pins Rust toolchain in
crates/rust-toolchain.toml to 1.93.0.๐ API Reference
$3
Non-chunked (small/medium data):
`typescript
{
data: string; // base64
iv: string; // base64
keyId: string;
algorithm: 'AES-GCM';
version: '1.0';
compression: { algorithm: 'gzip' | 'brotli' | 'none'; originalSize: number; compressedSize: number };
signature?: string; // base64
ephemeralPublicKey?: string; // base64
textEncoding?: 'utf8' | 'utf16le';
}
`
Chunked (large data):
`typescript
{
keyId: string;
algorithm: 'AES-GCM';
version: '1.0';
compression: { algorithm: 'gzip' | 'brotli' | 'none'; originalSize: number; compressedSize: number };
chunks: Array<{ data: string; iv: string; index: number; signature?: string }>;
chunkInfo: { isChunked: true; totalChunks: number; chunkSize: number };
signature?: string; // global signature (base64)
ephemeralPublicKey?: string; // base64
textEncoding?: 'utf8' | 'utf16le';
}
`
$3
- encrypt(data): Encrypt data with all features
- decrypt(blob): Decrypt encrypted blob
- exportKey(): Export current key as string
- importKey(keyString): Import key from string
- rotateKey(options): Rotate encryption key
- deriveKeyFromPassword(options): Derive key from data
- deriveAndSetPDK(credentialPublicKey, credentialId): Derive and set active key from passkey material
- getKeyFingerprint(): Get key fingerprint
- getSafetyNumbers(): Get safety numbers
- generateSigningKeys(): Generate signing key pair
- generateAgreementKeys(): Generate agreement key pair
- performKeyAgreement(publicKey): Perform key agreement
$3
- VoidedE2EEClient: Main encryption client
- CryptoService: Cryptographic operations
- KeyManager: Key lifecycle management
- StorageService: Storage abstraction
- IndexedDBStorage: Built-in IndexedDB storage
- VoidedKeyExport: Key export UI component
- VoidedKeyImport: Key import UI component
- HashService: Hashing utilities
- PasskeyKeyDeriver: Passkey-derived key helper
- KeySharing: X25519 key sharing/transfer helper
$3
- E2EEConfig: Client configuration
- RotationOptions: Key rotation options
- MigrationState: Migration status
- E2EEStorage: Storage interface
- EncryptedBlob: Encrypted data structure
- EncryptedChunk: Chunked data structure
- KeyDerivationOptions: Data derivation options
- KeyVerificationResult: Identity verification result
- KeyExportOptions: Export UI options
- KeyImportOptions: Import UI options
`typescript
void // No return value
`
clearCachedKey Response:
`typescript
void // No return value
`
hasKey Response:
`typescript
true; // boolean indicating if key exists
`
getMigrationStatus Response:
`typescript
{
isActive: true,
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
lastProgress: 0.75,
createdAt: Date
} | null
`
finalizeMigration Response:
`typescript
void // No return value
`
getCurrentKeyVersion Response:
`typescript
2; // number representing current key version
`
getMigrationInfo Response:
`typescript
{
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
createdAt: Date
} | null
`
$3
compress Response:
`typescript
// Example: compress("Hello, World! This is a test message for compression.")
{
compressed: Uint8Array(23) [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33, 32, 84, 104, 105, 115, 32, 105, 115, 32, 97], // Compressed data
algorithm: "brotli", // "gzip" | "brotli" | "none"
originalSize: 52,
compressedSize: 23,
compressionRatio: 0.44 // 44% of original size
}
`
decompress Response:
`typescript
// Example: decompress(compressedData, "brotli")
Uint8Array(52)[
(72,
101,
108,
108,
111,
44,
32,
87,
111,
114,
108,
100,
33,
32,
84,
104,
105,
115,
32,
105,
115,
32,
97,
32,
116,
101,
115,
116,
32,
109,
101,
115,
115,
97,
103,
101,
32,
102,
111,
114,
32,
99,
111,
109,
112,
114,
101,
115,
115,
105,
111,
110,
46)
]; // Decompressed data
`
analyzeCompression Response:
`typescript
// Example: analyzeCompression("Hello, World! This is a test message for compression.")
{
originalSize: 52,
gzipSize: 25,
brotliSize: 23,
gzipRatio: 0.48, // 48% of original size
brotliRatio: 0.44, // 44% of original size
recommendation: "brotli" // "gzip" | "brotli" | "none"
}
`
stringToUint8Array Response:
`typescript
Uint8Array; // Converted string to bytes
`
uint8ArrayToString Response:
`typescript
"string"; // Converted bytes to string
`
$3
generateKey Response:
`typescript
CryptoKey; // Web Crypto API key object
`
generateSigningKeyPair Response:
`typescript
{
publicKey: CryptoKey,
privateKey: CryptoKey
}
`
generateKeyAgreementKeyPair Response:
`typescript
{
publicKey: CryptoKey,
privateKey: CryptoKey
}
`
deriveKeyFromPassword Response:
`typescript
CryptoKey; // Derived AES-GCM key
`
deriveSharedKey Response:
`typescript
CryptoKey; // Shared AES-GCM key
`
generateSalt Response:
`typescript
Uint8Array; // Random salt bytes
`
signData Response:
`typescript
ArrayBuffer; // ECDSA signature
`
verifySignature Response:
`typescript
true; // boolean indicating signature validity
`
getKeyFingerprint Response:
`typescript
"<64-hex>"; // 64-character hex fingerprint
`
getSafetyNumbers Response:
`typescript
"123 456 789 012 345"; // Human-readable safety numbers
`
encrypt Response:
`typescript
ArrayBuffer; // Encrypted data
`
decrypt Response:
`typescript
Uint8Array; // Decrypted data
`
exportKey Response:
`typescript
"base64_key_string";
`
exportPublicKey Response:
`typescript
"base64_public_key_string";
`
importKey Response:
`typescript
CryptoKey; // Imported key object
`
importPublicKey Response:
`typescript
CryptoKey; // Imported public key object
`
validateKeyFormat Response:
`typescript
true; // boolean indicating valid format
`
secureWipe Response:
`typescript
void // No return value, buffer is wiped
`
$3
getCurrentKey Response:
`typescript
CryptoKey; // Current encryption key
`
getKeyForDecryption Response:
`typescript
CryptoKey; // Key for decryption (current or legacy)
`
setKey Response:
`typescript
void // No return value
`
forceRotate Response:
`typescript
"base64_new_key_string";
`
startMigration Response:
`typescript
"base64_new_key_string";
`
finalizeMigration Response:
`typescript
void // No return value
`
deleteKey Response:
`typescript
void // No return value
`
hasKey Response:
`typescript
true; // boolean indicating if key exists
`
getCurrentKeyVersion Response:
`typescript
2; // number representing current key version
`
getMigrationStatus Response:
`typescript
{
isActive: true,
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
lastProgress: 0.75,
createdAt: Date
} | null
`
getLegacyKey Response:
`typescript
CryptoKey | null; // Legacy key during migration
`
clearCache Response:
`typescript
void // No return value
`
$3
generateHash Response:
`typescript
// Example: generateHash("Hello, World!", "sha256")
"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"; // Hex string hash
`
generateHashWithSalt Response:
`typescript
// Example: generateHashWithSalt("Hello, World!", "mysalt", "sha256")
"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"; // Hex string salted hash
`
compareHash Response:
`typescript
// Example: compareHash("Hello, World!", "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f", "sha256")
true; // boolean indicating hash match
`
hashWithHighIterations Response:
`typescript
// Example: hashWithHighIterations("Hello, World!")
{
hash: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
salt: "a1b2c3d4e5f67890" // Generated salt
}
`
verifyWithHighIterations Response:
`typescript
true; // boolean indicating verification success
`
verifyWithSalt Response:
`typescript
true; // boolean indicating verification success
`
generateRandomHash Response:
`typescript
"a1b2c3d4e5f6..."; // Hex string random hash
`
generateFingerprint Response:
`typescript
"<64-hex>"; // 64-character hex fingerprint
`
generateSafetyNumbers Response:
`typescript
"123 456 789 012 345"; // Human-readable safety numbers
`
generateRandomSalt Response:
`typescript
"a1b2c3d4e5f6..."; // Hex string random salt
`
secureWipe Response:
`typescript
void // No return value, buffer is wiped
`
$3
VoidedKeyExport.show Response:
`typescript
void // No return value, shows modal
`
VoidedKeyExport.hide Response:
`typescript
void // No return value, hides modal
`
VoidedKeyImport.show Response:
`typescript
void // No return value, shows modal
`
VoidedKeyImport.hide Response:
`typescript
void // No return value, hides modal
`
createKeyExport Response:
`typescript
VoidedKeyExport; // Key export UI component instance
`
createKeyImport Response:
`typescript
VoidedKeyImport; // Key import UI component instance
`
$3
IndexedDBStorage.getKey Response:
`typescript
"base64_key_string" | null;
`
IndexedDBStorage.setKey Response:
`typescript
void // No return value
`
IndexedDBStorage.removeKey Response:
`typescript
void // No return value
`
IndexedDBStorage.getMigrationState Response:
`typescript
{
isActive: true,
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
lastProgress: 0.75,
createdAt: Date
} | null
`
IndexedDBStorage.setMigrationState Response:
`typescript
void // No return value
`
IndexedDBStorage.removeMigrationState Response:
`typescript
void // No return value
`
IndexedDBStorage.getKeyPair Response:
`typescript
"base64_key_pair_string" | null;
`
IndexedDBStorage.setKeyPair Response:
`typescript
void // No return value
`
IndexedDBStorage.removeKeyPair Response:
`typescript
void // No return value
`
$3
encrypt (convenience) Response:
`typescript
{
data?: "base64_encrypted_data",
iv?: "base64_iv",
keyId: "default",
algorithm: "AES-GCM",
version: "1.0",
compression: {
algorithm: "brotli" | "gzip" | "none",
originalSize: 13,
compressedSize: 11
},
chunks?: Array<{ data: string; iv: string; index: number; signature?: string }>,
chunkInfo?: { isChunked: true; totalChunks: number; chunkSize: number },
signature?: string,
ephemeralPublicKey?: string,
textEncoding?: 'utf8' | 'utf16le'
}
`
decrypt (convenience) Response:
`typescript
"decrypted_string";
`
exportKey (convenience) Response:
`typescript
"base64_exported_key_string";
`
importKey (convenience) Response:
`typescript
void // No return value
`
rotateKey (convenience) Response:
`typescript
"base64_new_key_string";
`
deriveKeyFromPassword (convenience) Response:
`typescript
void // No return value
`
getKeyFingerprint (convenience) Response:
`typescript
"a1b2c3d4";
`
getSafetyNumbers (convenience) Response:
`typescript
"123 456 789 012 345";
``