@voideddev/e2ee-client
v0.3.0
Published
Browser-based E2EE client library for true end-to-end encryption including keys, crypto, compression, and local storage adapters.
Maintainers
Readme
voideddev E2EE Client Library
A comprehensive browser-based end-to-end encryption library that provides true client-side encryption with advanced security features, automatic compression, and user-friendly key management UI components.
⚡ Powered by Rust + WebAssembly - Cryptographic operations can run through Rust-compiled WASM for enhanced performance, with automatic fallback to Web Crypto API.
📋 Table of Contents
- 🚀 Features
- 📦 Installation
- 🔧 Core Components
- 🎯 Usage Patterns
- 🔒 Security Features
- 📊 Performance Features
- 🧪 Testing and Validation
- 🔧 Integration Examples
- 🚨 Error Handling
- Build and Publishing Workflow
- 📚 API Reference
- 📋 Complete Function Response Structures
- 🤝 Contributing
- 📄 License
- 🆘 Support
🚀 Features
- Rust + WASM Core: Optional WebAssembly backend powered by Rust for maximum performance
- True E2EE: All encryption/decryption happens in the browser - your server never sees keys
- Advanced Security: Digital signatures, forward secrecy, key agreement, and identity verification
- Automatic Compression: Intelligent compression with Brotli and Gzip support
- Large File Support: Automatic chunking with client-side limits up to 32 GiB
- Benchmarks: Built-in benchmarking helpers for compression and pipeline
- Key Management UI: Built-in export/import modals with QR code support
- Multiple Storage Options: IndexedDB, custom storage adapters
- Framework Agnostic: Works with React, Vue, Svelte, or vanilla JavaScript
- Signal-Level Security: High-iteration key derivation, safety numbers, and fingerprint verification
- Key Rotation: Support for secure key rotation with migration workflows
- Passkey-Derived Keys (0.3.0): Deterministic key derivation from passkey material
- X25519 Key Sharing (0.3.0): Secure key exchange and device transfer helpers
🎯 Quick Start
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
npm install @voideddev/e2ee-client🔧 Core Components
1. Main Client (VoidedE2EEClient)
The primary encryption client that handles all cryptographic operations, compression, optional signatures, optional forward secrecy, and automatic chunking of large payloads.
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 authenticityenableForwardSecrecy: Enable ephemeral keys for forward secrecyenableChunking: Enable automatic chunking for large datachunkSize: 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)
2. Crypto Service (CryptoService)
Handles all cryptographic operations using Web Crypto API.
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 keygenerateSigningKeyPair(): Generate ECDSA key pair for signaturesgenerateKeyAgreementKeyPair(): Generate ECDH key pair for key agreementderiveKeyFromPassword(): PBKDF2-based key derivation from data (deterministic keys for cloud deployments)deriveSharedKey(): ECDH key agreementgetKeyFingerprint(): Generate key fingerprint for verificationgetSafetyNumbers(): Generate human-readable safety numbers
3. Compression Module (compression.ts)
Provides intelligent compression with automatic and explicit algorithm selection.
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
4. Key Manager (KeyManager)
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.
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 } | nullKey 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
5. Storage Service (StorageService)
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:
// 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:
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<string | null> {
/* ... */
},
async setKey(keyId: string, key: string): Promise<void> {
/* ... */
},
async removeKey(keyId: string): Promise<void> {
/* ... */
},
// ... 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 keysetKey(keyId, key): Store encryption keyremoveKey(keyId): Delete encryption keygetMigrationState(keyId): Get migration statussetMigrationState(keyId, state): Store migration stategetKeyPair(keyId, type): Retrieve key pairssetKeyPair(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
6. Key UI Components (key-ui.ts)
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
qrcodelibrary - 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:
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
qrcodelibrary 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:
/* 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:
/* 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 */
}7. Hash Service (HashService)
Comprehensive hashing utilities for data integrity and verification.
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 hashgenerateHashWithSalt(data, salt, algorithm): Generate salted hashcompareHash(data, hash, algorithm): Compare data with hashhashWithHighIterations(data, salt): High-iteration PBKDF2 hashingverifyWithHighIterations(data, hash, salt): Verify high-iteration hashverifyWithSalt(data, hash, salt, algorithm): Verify salted hashgenerateRandomHash(algorithm): Generate random hashgenerateFingerprint(data, length): Generate key fingerprintgenerateSafetyNumbers(data, groupSize): Generate safety numbersgenerateRandomSalt(length): Generate random saltsecureWipe(buffer): Securely wipe buffer from memory
8. Passkey + Key Sharing (PasskeyKeyDeriver, KeySharing) - 0.3.0
Additive key-management APIs for deterministic passkey-derived keys and X25519-based sharing.
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
Understanding the Different Flows
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.
1. Full Pipeline Encryption (Recommended)
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'2. Individual Components (Advanced Usage)
For advanced users who need fine-grained control over the encryption process:
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. Advanced Security Features
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"4. Key Management
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)
}5. Key Agreement (Secure Key Exchange)
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"6. Identity Verification
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);7. UI Integration
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 (
<div>
<button onClick={() => keyExport?.show()}>Export Key</button>
<button onClick={() => keyImport?.show()}>Import Key</button>
<button onClick={() => client.rotateKey()}>Rotate Key</button>
</div>
);
}8. Large File Handling (Chunked vs Non-Chunked)
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",
});9. Custom Storage
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
High-Iteration Key Derivation
- Uses PBKDF2 with configurable iterations (default: 100,000)
- OWASP-compliant security parameters
- Salt generation and management
Hashing Types and Purposes
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.
Digital Signatures
- 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
Forward Secrecy
- Ephemeral key generation for each message
- ECDH key agreement for shared secrets
- Perfect forward secrecy implementation
Identity Verification
- Key fingerprints for identity verification
- Safety numbers (like Signal) for human verification
- Timing-safe comparisons to prevent attacks
Key Rotation
- 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
Large File Security
- 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
chunkSizeandminChunkThreshold
Benchmarks
You can run quick in-browser benchmarks to gauge performance:
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
Compression
- 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
Chunking
- 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
Caching
- Key caching for performance
- Secure memory management
- Automatic cache invalidation
🧪 Testing and Validation
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
React Integration
import React, { useEffect, useState } from "react";
import {
VoidedE2EEClient,
createKeyExport,
createKeyImport,
} from "@voideddev/e2ee-client";
function EncryptionApp() {
const [client, setClient] = useState<VoidedE2EEClient | null>(null);
const [keyExport, setKeyExport] = useState<any>(null);
const [keyImport, setKeyImport] = useState<any>(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 (
<div>
<button onClick={() => keyExport?.show()}>Export Key</button>
<button onClick={() => keyImport?.show()}>Import Key</button>
<button onClick={() => handleEncrypt("test data")}>Encrypt</button>
</div>
);
}Vue Integration
<template>
<div>
<button @click="exportKey">Export Key</button>
<button @click="importKey">Import Key</button>
<button @click="encryptData">Encrypt</button>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import {
VoidedE2EEClient,
createKeyExport,
createKeyImport,
} from "@voideddev/e2ee-client";
const client = ref(null);
const keyExport = ref(null);
const keyImport = ref(null);
onMounted(() => {
client.value = new VoidedE2EEClient();
keyExport.value = createKeyExport(client.value);
keyImport.value = createKeyImport(client.value);
});
const exportKey = () => keyExport.value?.show();
const importKey = () => keyImport.value?.show();
const encryptData = async () => {
const encrypted = await client.value.encrypt("test data");
console.log("Encrypted:", encrypted);
};
</script>Svelte Integration
<script>
import { onMount } from 'svelte';
import { VoidedE2EEClient, createKeyExport, createKeyImport } from '@voideddev/e2ee-client';
let client, keyExport, keyImport;
onMount(() => {
client = new VoidedE2EEClient();
keyExport = createKeyExport(client);
keyImport = createKeyImport(client);
});
function exportKey() {
keyExport?.show();
}
function importKey() {
keyImport?.show();
}
async function encryptData() {
const encrypted = await client.encrypt('test data');
console.log('Encrypted:', encrypted);
}
</script>
<div>
<button on:click={exportKey}>Export Key</button>
<button on:click={importKey}>Import Key</button>
<button on:click={encryptData}>Encrypt</button>
</div>🚨 Error Handling
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) viawasm-pack - Copies generated artifacts into
packages/e2ee-client/wasm - Generates/updates
wasm/init.js
- Builds Rust WASM (
npm run copy:wasm- Only copies already-built artifacts from
crates/voided-wasm/pkgintopackages/e2ee-client/wasm - Use when Rust/WASM is already built and you just need to sync package artifacts
- Only copies already-built artifacts from
npm run test- Runs Jest suite
npm run build- Builds
dist/bundles and types
- Builds
Recommended publish validation:
npm run build:wasmnpm run copy:wasmnpm run testnpm run buildnpm pack --dry-runnpm publish
Note: The repo pins Rust toolchain in crates/rust-toolchain.toml to 1.93.0.
📚 API Reference
Quick Reference: EncryptedBlob Shapes
Non-chunked (small/medium data):
{
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):
{
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';
}Main Functions
encrypt(data): Encrypt data with all featuresdecrypt(blob): Decrypt encrypted blobexportKey(): Export current key as stringimportKey(keyString): Import key from stringrotateKey(options): Rotate encryption keyderiveKeyFromPassword(options): Derive key from dataderiveAndSetPDK(credentialPublicKey, credentialId): Derive and set active key from passkey materialgetKeyFingerprint(): Get key fingerprintgetSafetyNumbers(): Get safety numbersgenerateSigningKeys(): Generate signing key pairgenerateAgreementKeys(): Generate agreement key pairperformKeyAgreement(publicKey): Perform key agreement
Classes
VoidedE2EEClient: Main encryption clientCryptoService: Cryptographic operationsKeyManager: Key lifecycle managementStorageService: Storage abstractionIndexedDBStorage: Built-in IndexedDB storageVoidedKeyExport: Key export UI componentVoidedKeyImport: Key import UI componentHashService: Hashing utilitiesPasskeyKeyDeriver: Passkey-derived key helperKeySharing: X25519 key sharing/transfer helper
Interfaces
E2EEConfig: Client configurationRotationOptions: Key rotation optionsMigrationState: Migration statusE2EEStorage: Storage interfaceEncryptedBlob: Encrypted data structureEncryptedChunk: Chunked data structureKeyDerivationOptions: Data derivation optionsKeyVerificationResult: Identity verification resultKeyExportOptions: Export UI optionsKeyImportOptions: Import UI options
void // No return valueclearCachedKey Response:
void // No return valuehasKey Response:
true; // boolean indicating if key existsgetMigrationStatus Response:
{
isActive: true,
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
lastProgress: 0.75,
createdAt: Date
} | nullfinalizeMigration Response:
void // No return valuegetCurrentKeyVersion Response:
2; // number representing current key versiongetMigrationInfo Response:
{
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
createdAt: Date
} | nullCompression Functions
compress Response:
// 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:
// 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 dataanalyzeCompression Response:
// 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:
Uint8Array; // Converted string to bytesuint8ArrayToString Response:
"string"; // Converted bytes to stringCryptoService Methods
generateKey Response:
CryptoKey; // Web Crypto API key objectgenerateSigningKeyPair Response:
{
publicKey: CryptoKey,
privateKey: CryptoKey
}generateKeyAgreementKeyPair Response:
{
publicKey: CryptoKey,
privateKey: CryptoKey
}deriveKeyFromPassword Response:
CryptoKey; // Derived AES-GCM keyderiveSharedKey Response:
CryptoKey; // Shared AES-GCM keygenerateSalt Response:
Uint8Array; // Random salt bytessignData Response:
ArrayBuffer; // ECDSA signatureverifySignature Response:
true; // boolean indicating signature validitygetKeyFingerprint Response:
"<64-hex>"; // 64-character hex fingerprintgetSafetyNumbers Response:
"123 456 789 012 345"; // Human-readable safety numbersencrypt Response:
ArrayBuffer; // Encrypted datadecrypt Response:
Uint8Array; // Decrypted dataexportKey Response:
"base64_key_string";exportPublicKey Response:
"base64_public_key_string";importKey Response:
CryptoKey; // Imported key objectimportPublicKey Response:
CryptoKey; // Imported public key objectvalidateKeyFormat Response:
true; // boolean indicating valid formatsecureWipe Response:
void // No return value, buffer is wipedKeyManager Methods
getCurrentKey Response:
CryptoKey; // Current encryption keygetKeyForDecryption Response:
CryptoKey; // Key for decryption (current or legacy)setKey Response:
void // No return valueforceRotate Response:
"base64_new_key_string";startMigration Response:
"base64_new_key_string";finalizeMigration Response:
void // No return valuedeleteKey Response:
void // No return valuehasKey Response:
true; // boolean indicating if key existsgetCurrentKeyVersion Response:
2; // number representing current key versiongetMigrationStatus Response:
{
isActive: true,
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
lastProgress: 0.75,
createdAt: Date
} | nullgetLegacyKey Response:
CryptoKey | null; // Legacy key during migrationclearCache Response:
void // No return valueHashService Methods
generateHash Response:
// Example: generateHash("Hello, World!", "sha256")
"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"; // Hex string hashgenerateHashWithSalt Response:
// Example: generateHashWithSalt("Hello, World!", "mysalt", "sha256")
"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"; // Hex string salted hashcompareHash Response:
// Example: compareHash("Hello, World!", "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f", "sha256")
true; // boolean indicating hash matchhashWithHighIterations Response:
// Example: hashWithHighIterations("Hello, World!")
{
hash: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
salt: "a1b2c3d4e5f67890" // Generated salt
}verifyWithHighIterations Response:
true; // boolean indicating verification successverifyWithSalt Response:
true; // boolean indicating verification successgenerateRandomHash Response:
"a1b2c3d4e5f6..."; // Hex string random hashgenerateFingerprint Response:
"<64-hex>"; // 64-character hex fingerprintgenerateSafetyNumbers Response:
"123 456 789 012 345"; // Human-readable safety numbersgenerateRandomSalt Response:
"a1b2c3d4e5f6..."; // Hex string random saltsecureWipe Response:
void // No return value, buffer is wipedKey UI Methods
VoidedKeyExport.show Response:
void // No return value, shows modalVoidedKeyExport.hide Response:
void // No return value, hides modalVoidedKeyImport.show Response:
void // No return value, shows modalVoidedKeyImport.hide Response:
void // No return value, hides modalcreateKeyExport Response:
VoidedKeyExport; // Key export UI component instancecreateKeyImport Response:
VoidedKeyImport; // Key import UI component instanceStorage Methods
IndexedDBStorage.getKey Response:
"base64_key_string" | null;IndexedDBStorage.setKey Response:
void // No return valueIndexedDBStorage.removeKey Response:
void // No return valueIndexedDBStorage.getMigrationState Response:
{
isActive: true,
oldKeyVersion: 1,
newKeyVersion: 2,
cutoffTime: Date,
lastProgress: 0.75,
createdAt: Date
} | nullIndexedDBStorage.setMigrationState Response:
void // No return valueIndexedDBStorage.removeMigrationState Response:
void // No return valueIndexedDBStorage.getKeyPair Response:
"base64_key_pair_string" | null;IndexedDBStorage.setKeyPair Response:
void // No return valueIndexedDBStorage.removeKeyPair Response:
void // No return valueConvenience Functions
encrypt (convenience) Response:
{
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:
"decrypted_string";exportKey (convenience) Response:
"base64_exported_key_string";importKey (convenience) Response:
void // No return valuerotateKey (convenience) Response:
"base64_new_key_string";deriveKeyFromPassword (convenience) Response:
void // No return valuegetKeyFingerprint (convenience) Response:
"a1b2c3d4";getSafetyNumbers (convenience) Response:
"123 456 789 012 345";🤝 Contributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
📄 License
MIT License - see LICENSE file for details.
🆘 Support
For issues, questions, or contributions, please open an issue on the GitHub repository.
