Vollcrypt is a cryptography library — not a product. It provides the cryptographic primitives needed to build secure end-to-end encrypted (E2EE) messaging systems, file transfer tools, or any application that needs modern, post-quantum-ready cryptography.
The same Rust core is compiled to three targets:
| Target | Package | Use Case |
|---|---|---|
| Node.js (native) | @vollcrypt/node |
NestJS, Express, server-side |
| WebAssembly | @vollcrypt/wasm |
React, Next.js, browser |
| Rust | vollcrypt-core |
Direct Rust integration |
- Why Vollcrypt
- Security Properties
- What It Provides
- Installation
- Quick Start
- API Reference
- License Configuration
- Full E2EE Flow Example
- Building From Source
- Architecture
- Security Considerations
- Licensing
- Contributing
Most cryptography libraries give you low-level primitives and leave you to wire them together correctly. Vollcrypt goes one step further: it provides the higher-level building blocks — hybrid KEM handshakes, time-windowed forward secrecy, post-compromise security ratchets, sealed sender flows, key verification codes — while keeping each primitive independently accessible and testable.
What makes it different:
- Hybrid post-quantum KEM — X25519 combined with ML-KEM-768 (NIST FIPS 203). Breaking one does not break the session.
- Post-Compromise Security — Unlike systems that only offer forward secrecy, Vollcrypt implements a ratchet mechanism. If a session key is compromised, the system heals itself within a configurable number of messages.
- Sealed Sender — Sender identity is encrypted inside the message. The server routes messages without knowing who sent them.
- Key Transparency — Every public key publication is recorded in an append-only, hash-linked, Ed25519-signed log. Key changes cannot be silently backdated.
- Verification Codes — Users can confirm each other's keys out of band using short numeric or emoji codes, defeating MITM attacks at the human layer.
- Single core, three targets — The same Rust logic runs in Node.js, WebAssembly, and native Rust. No divergence between environments.
| Property | Mechanism | Guarantee |
|---|---|---|
| Confidentiality | AES-256-GCM | Messages cannot be read without the session key |
| Integrity | AES-256-GCM auth tag + Transcript hash | Messages cannot be modified or reordered without detection |
| Forward Secrecy | Time-windowed WindowKey (HKDF-derived per window) | Compromising a current key does not expose past messages |
| Post-Compromise Security | Ephemeral X25519 PCS ratchet | System recovers from key compromise within N messages |
| Quantum Resistance | X25519 + ML-KEM-768 hybrid KEM | Session establishment resists both classical and quantum attacks |
| Sender Authenticity | Ed25519 signature on KEM ciphertext | The server cannot substitute a different sender's key |
| Sender Privacy | Sealed Sender (ephemeral ECDH + AES-GCM) | The server routes messages without knowing the sender's identity |
| Key Authenticity | Key Transparency log (Ed25519-signed hash chain) | Key changes are auditable and cannot be silently backdated |
| MITM Detection | Verification codes (SHA-256 of public key pair) | Users can confirm keys out of band |
Algorithms used:
| Purpose | Algorithm | Standard |
|---|---|---|
| Symmetric encryption | AES-256-GCM | NIST SP 800-38D |
| Classical key exchange | X25519 ECDH | RFC 7748 |
| Post-quantum KEM | ML-KEM-768 | NIST FIPS 203 |
| Digital signatures | Ed25519 | RFC 8032 |
| Key derivation | HKDF-SHA256 | RFC 5869 |
| Password derivation | PBKDF2-SHA256 (100 000 iterations) | RFC 8018 |
| Key wrapping | AES-256-KW | RFC 3394 |
| Recovery phrase | BIP-39 (24 words, 256-bit entropy) | BIP-39 |
Algorithms explicitly excluded: RSA, ECDSA, AES-CBC, AES-ECB, MD5, SHA-1, DH under 2048 bits.
- Ed25519 keypair generation, signing, and verification
- X25519 keypair generation and ECDH
- ML-KEM-768 keypair generation, encapsulation, and decapsulation
- AES-256-GCM encryption and decryption with optional AAD
- HKDF-SHA256 and PBKDF2-SHA256 key derivation
- AES-256-KW key wrapping and unwrapping
- BIP-39 mnemonic generation and seed derivation
- Hybrid KEM handshake (X25519 + ML-KEM-768)
- Authenticated KEM (hybrid KEM + Ed25519 signature — closes MITM on key exchange)
- Time-windowed Session Root Key → WindowKey derivation chain
- Binary envelope packing:
[4B window_index][12B IV][32B AAD hash][ciphertext][16B auth tag] - PCS ratchet (ephemeral X25519 ratchet for post-compromise recovery)
Create a .env file using the template below:
VOLLCRYPT_LICENSE_KEY=
VOLLCRYPT_LICENSE_SERVER=https://api.vollcrypt.com
VOLLCRYPT_LICENSE_REPORT_INTERVAL_MS=3600000
VOLLCRYPT_LICENSE_OFFLINE_FALLBACK=true
The Node.js package reads these values on startup and will validate the license automatically.
- Transcript hashing (SHA-256 chain over message sequence — detects reordering and deletion)
- Sealed sender (sender identity hidden from server)
- Key Transparency log (append-only, Ed25519-signed, hash-linked)
- Verification codes (numeric and emoji, derived from public key pair + conversation ID)
- Device registry with revocation
npm install @vollcrypt/nodePrebuilt native binaries are provided for:
- Linux x64 (
linux-x64-gnu) - macOS x64 (
darwin-x64) - Windows x64 (
win32-x64-msvc)
npm install @vollcrypt/wasm# Cargo.toml
[dependencies]
vollcrypt-core = { git = "https://github.com/BeratVural/vollcrypt.git" }Or in a workspace:
vollcrypt-core = { path = "../vollcrypt/core" }import {
generateEd25519Keypair,
generateX25519Keypair,
encryptAesGcm,
decryptAesGcm,
} from '@vollcrypt/node';
import crypto from 'crypto';
// Identity keypair
const [identitySecret, identityPublic] = generateEd25519Keypair();
// Session key (in practice, derived via KEM handshake)
const sessionKey = crypto.randomBytes(32);
const plaintext = Buffer.from('Hello, Vollcrypt');
// Encrypt
const ciphertext = encryptAesGcm(sessionKey, plaintext, null);
// Decrypt
const decrypted = decryptAesGcm(sessionKey, ciphertext, null);
console.log(decrypted.toString()); // Hello, Vollcryptimport init, {
generateEd25519Keypair,
encryptAesGcm,
decryptAesGcm,
} from '@vollcrypt/wasm';
await init();
const [identitySecret, identityPublic] = generateEd25519Keypair();
const sessionKey = crypto.getRandomValues(new Uint8Array(32));
const plaintext = new TextEncoder().encode('Hello, Vollcrypt');
const ciphertext = encryptAesGcm(sessionKey, plaintext, null);
const decrypted = decryptAesGcm(sessionKey, ciphertext, null);
console.log(new TextDecoder().decode(decrypted));use vollcrypt_core::{
generate_ed25519_keypair,
encrypt_aes256gcm,
decrypt_aes256gcm,
};
let (_secret, _public) = generate_ed25519_keypair().unwrap();
let key = [0u8; 32]; // use OsRng in production
let plaintext = b"Hello, Vollcrypt";
let ciphertext = encrypt_aes256gcm(&key, plaintext, None).unwrap();
let decrypted = decrypt_aes256gcm(&key, &ciphertext, None).unwrap();
assert_eq!(plaintext, decrypted.as_slice());All examples below use the Node.js binding. The WASM binding exposes identical function names with snake_case convention. The Rust API mirrors the same logic in the vollcrypt_core crate.
Generates a new Ed25519 keypair. Use for user and device identity, message signing, and Key Transparency log entries.
const [secretKey, publicKey] = generateEd25519Keypair();
// secretKey: Buffer (32 bytes) — keep private, never transmit
// publicKey: Buffer (32 bytes) — safe to publishSigns a message with an Ed25519 private key.
const message = Buffer.from('data to sign');
const signature = signMessage(secretKey, message);
// signature: Buffer (64 bytes)Verifies an Ed25519 signature. Returns true only if the signature is valid for the given key and message.
const valid = verifySignature(publicKey, message, signature);Generates a new X25519 keypair. Use for ECDH key exchange and session establishment.
const [x25519Secret, x25519Public] = generateX25519Keypair();Generates a new ML-KEM-768 keypair (NIST FIPS 203).
const [encapsKey, decapsKey] = generateMlKem768Keypair();
// encapsKey: Buffer — share with peers for encapsulation
// decapsKey: Buffer — keep privatePerforms a hybrid KEM encapsulation combining X25519 ECDH and ML-KEM-768. Produces a shared secret that is secure as long as either the classical or the post-quantum component is unbroken.
const [ciphertext, sharedSecret] = hybridKemEncapsulate(
bobX25519Public,
bobMlkemEncapsKey,
);
// ciphertext: Buffer — send to recipient
// sharedSecret: Buffer (32 bytes) — use to derive session keysDecapsulates a hybrid KEM ciphertext. Produces the same shared secret as the sender.
const sharedSecret = hybridKemDecapsulate(
myX25519Secret,
receivedCiphertext,
myMlkemDecapsKey,
);authenticatedKemEncapsulate(recipientX25519Pub, recipientMlkemPub, senderIdentitySk) → [ciphertext, sharedSecret]
Performs a hybrid KEM encapsulation and signs the ciphertext with the sender's Ed25519 identity key. The recipient can verify that the ciphertext was produced by the claimed sender and was not substituted by the server.
const [authCiphertext, sharedSecret] = authenticatedKemEncapsulate(
bobX25519Public,
bobMlkemEncapsKey,
aliceIdentitySecret,
);authenticatedKemDecapsulate(ciphertext, ourX25519Secret, ourMlkemDecapsKey, senderIdentityPk) → sharedSecret
Verifies the sender's signature before decapsulating. Throws if the signature is invalid.
const sharedSecret = authenticatedKemDecapsulate(
receivedAuthCiphertext,
myX25519Secret,
myMlkemDecapsKey,
aliceIdentityPublic, // obtained from Key Transparency log or server
);Why authenticated KEM matters: Without it, the server can replace Bob's public key with its own, read the message, and re-encrypt it for Bob. The sender's signature over the KEM ciphertext closes this attack.
Encrypts using AES-256-GCM. The IV is generated internally using the OS CSPRNG and prepended to the output. The optional aad (additional authenticated data) is authenticated but not encrypted — any modification is detected at decryption.
const aad = Buffer.from(`${messageId}:${senderId}`);
const ciphertext = encryptAesGcm(sessionKey, plaintext, aad);Decrypts and verifies an AES-256-GCM ciphertext. Throws if the authentication tag or AAD does not match.
const plaintext = decryptAesGcm(sessionKey, ciphertext, aad);Never reuse IVs. The library generates a fresh random IV for every
encryptAesGcmcall. Do not attempt to pass IVs manually — there is no API for it.
Derives a key using HKDF-SHA256. Use distinct info strings for each purpose to ensure cryptographic domain separation.
const sessionRootKey = deriveHkdf(
sharedSecret,
chatId,
Buffer.from('vollchat-srk-v1'),
32,
);
const windowKey = deriveHkdf(
sessionRootKey,
Buffer.from(windowIndex.toString()),
Buffer.from('vollchat-window-key-v1'),
32,
);Derives a key from a password using PBKDF2-SHA256. Default: 100 000 iterations. Use for password-protected key storage.
const wrappingKey = derivePbkdf2(
Buffer.from(userPassword),
salt, // random 16-byte salt, stored alongside wrapped key
100_000,
32,
);Convenience function. Derives a Session Root Key from a hybrid KEM shared secret and a conversation identifier. Equivalent to calling deriveHkdf with the context string vollchat-srk-v1.
Derives a time-window-specific encryption key from the Session Root Key. Window index is typically Math.floor(Date.now() / 1000 / WINDOW_SIZE_SECONDS).
const WINDOW_SIZE_SECONDS = 3600; // 1 hour
const windowIndex = Math.floor(Date.now() / 1000 / WINDOW_SIZE_SECONDS);
const windowKey = deriveWindowKey(sessionRootKey, windowIndex);Wraps a key using AES-256-KW (RFC 3394). Use to store sensitive keys (DEK, SRK) encrypted under a password-derived wrapping key.
const wrappedDek = wrapKey(dataEncryptionKey, wrappingKey);
// Store wrappedDek in IndexedDB or server — safe to store, cannot be used without wrappingKeyUnwraps a key wrapped with wrapKey. Throws if the wrapping key is incorrect.
const dataEncryptionKey = unwrapKey(wrappedDek, wrappingKey);Generates a 24-word BIP-39 mnemonic phrase (256-bit entropy). Use as a paper key for disaster recovery.
const mnemonic = generateBip39Mnemonic();
// "abandon ability able about above ..."
// Store physically — never digitally in plaintextDerives a 64-byte seed from a BIP-39 mnemonic. Use to reconstruct the master key hierarchy during account recovery.
const seed = bip39MnemonicToSeed(mnemonic, '');Generates an ephemeral X25519 keypair for a PCS ratchet step. The private key never leaves the WASM boundary or the Rust core — only the public key is exposed.
const ratchetKp = generateRatchetKeypair();
// ratchetKp.public_key — send to peer
// Private key is used internally via compute_ratchet()Performs one PCS ratchet step. Derives a new Session Root Key that neither party can compute from the old SRK alone. The old SRK should be zeroized after this call.
const MESSAGE_RATCHET_INTERVAL = 50;
if (messageCount % MESSAGE_RATCHET_INTERVAL === 0) {
const newSrk = ratchetKp.computeRatchet(
currentSrk,
theirLatestRatchetPublic,
chatId,
ratchetStep,
);
currentSrk.fill(0); // zeroize old SRK
currentSrk = newSrk;
ratchetStep += 1;
}Returns true if a ratchet step should be performed given the current state.
const needsRatchet = shouldRatchet(messageCount, windowChanged, 50, true);Transcript hashing maintains a running SHA-256 hash chain over the message sequence. Reordering, deleting, or replaying any message breaks the chain and causes decryption or verification to fail.
// Initialize at session start
let chainState = transcriptNew(Buffer.from(conversationId));
// After encrypting each message
const msgHash = transcriptComputeMessageHash(messageId, senderId, timestamp, ciphertext);
chainState = transcriptUpdate(chainState, msgHash);
// To verify both parties are in sync (e.g., during key verification)
const inSync = transcriptVerifySync(myChainState, theirChainState);Sealed sender hides the sender's identity from the server. The server sees only the recipient — the sender identity is encrypted inside the message payload using an ephemeral ECDH key.
Encrypts senderId together with content. The ephemeral key changes for every call so sealed packets cannot be correlated by the server.
const sealed = sealMessage(
bobX25519Public,
Buffer.from('alice@example.com'),
encryptedMessagePayload,
);
// sealed: Buffer — send to server with only { to: bobId, payload: sealed }Decrypts the sealed packet. Throws if the packet was tampered with or decrypted with the wrong key.
const [senderId, content] = unsealMessage(sealed, myX25519Secret);
console.log(senderId.toString()); // alice@example.comVerification codes let users confirm each other's public keys through a separate channel (in person, phone call, another app). If the codes match, no MITM substitution occurred.
The code is derived from both users' Ed25519 public keys and the conversation identifier. It is symmetric — Alice and Bob arrive at the same code regardless of which order they pass the keys.
const result = JSON.parse(generateVerificationCode(
alicePublicKey,
bobPublicKey,
Buffer.from('conv-alice-bob-001'),
));
console.log(result.numeric.formatted);
// "25437 81920 34521 09876 54321 12345 67890 24680 13579 86420 11223 34455"
console.log(result.emoji.formatted);
// "🔥💧🌊⚡🎯 🦋🌸🍀🌙☀️ 🎵🎸🎹🎺🎻 🦁🐯🐻🦊🐺"MITM detection:
// Alice computes
const aliceCode = generateVerificationCode(alicePublic, bobPublic, convId);
// Bob computes (order does not matter — result is the same)
const bobCode = generateVerificationCode(bobPublic, alicePublic, convId);
// Compare out of band (phone, in person)
const safe = verifyFingerprintsMatch(
Buffer.from(JSON.parse(aliceCode).fingerprint),
Buffer.from(JSON.parse(bobCode).fingerprint),
);
// true → keys match, no MITM
// false → keys differ, do not trust this sessionThe Key Transparency log is an append-only, Ed25519-signed, hash-linked record of every public key publication. No entry can be silently modified or deleted — any change breaks the hash chain and is detected during verifyChain.
import { keyLogCreateEntry, keyLogVerifyChain } from '@vollcrypt/node';
const GENESIS_HASH = Buffer.alloc(32, 0);
// First entry — Alice publishes her key
const entry0 = JSON.parse(keyLogCreateEntry(
Buffer.from('alice@example.com'),
alicePublicKey,
Math.floor(Date.now() / 1000),
GENESIS_HASH,
1, // action: 1=Add, 2=Update, 3=Revoke
aliceIdentitySecret,
));
// Key rotation — Alice updates her key
const entry1 = JSON.parse(keyLogCreateEntry(
Buffer.from('alice@example.com'),
newAlicePublicKey,
Math.floor(Date.now() / 1000),
Buffer.from(entry0.hash), // prev_entry_hash
2, // action: Update
aliceIdentitySecret,
));
const log = [entry0, entry1];const valid = keyLogVerifyChain(JSON.stringify(log));
// Throws with { atIndex, reason } if any entry has a broken chain or invalid signature// Get Alice's current active key
const currentKey = keyLogCurrentKey(
JSON.stringify(log),
Buffer.from('alice@example.com'),
);
// Get the key that was valid at a specific point in time
// (for verifying historical messages)
const historicalKey = keyLogKeyAtTimestamp(
JSON.stringify(log),
Buffer.from('alice@example.com'),
messageTimestamp,
);import { addDevice, revokeDevice, isDeviceRevoked } from '@vollcrypt/node';
// Register a new device
addDevice(registry, {
deviceId: 'device-uuid-001',
publicKey: deviceEd25519Public,
addedAt: Math.floor(Date.now() / 1000),
});
// Revoke a lost or compromised device
revokeDevice(registry, 'device-uuid-001');
// Check before accepting a signed message
if (isDeviceRevoked(registry, signingDeviceId)) {
throw new Error('Message signed by revoked device');
}The following shows a complete session between Alice and Bob using the authenticated KEM handshake, time-windowed encryption, sealed sender, and transcript hashing. See vollcrypt-example/src/09_full_flow.ts for the runnable version.
// ─── 1. Key Generation ────────────────────────────────────────────────────
const [aliceIdSk, aliceIdPk] = generateEd25519Keypair();
const [aliceX25519Sk, aliceX25519Pk] = generateX25519Keypair();
const [aliceMlkemEncaps, aliceMlkemDecaps] = generateMlKem768Keypair();
const [bobIdSk, bobIdPk] = generateEd25519Keypair();
const [bobX25519Sk, bobX25519Pk] = generateX25519Keypair();
const [bobMlkemEncaps, bobMlkemDecaps] = generateMlKem768Keypair();
// ─── 2. Authenticated KEM Handshake ──────────────────────────────────────
const conversationId = Buffer.from('conv-alice-bob-001');
// Alice encapsulates and signs
const [authCiphertext, aliceSharedSecret] = authenticatedKemEncapsulate(
bobX25519Pk, bobMlkemEncaps, aliceIdSk,
);
// Bob verifies Alice's signature and decapsulates
const bobSharedSecret = authenticatedKemDecapsulate(
authCiphertext, bobX25519Sk, bobMlkemDecaps, aliceIdPk,
);
// aliceSharedSecret === bobSharedSecret
// ─── 3. Session Root Key and WindowKey Derivation ────────────────────────
const srk = deriveSrk(aliceSharedSecret, conversationId);
const WINDOW = 3600;
const windowIndex = Math.floor(Date.now() / 1000 / WINDOW);
const windowKey = deriveWindowKey(srk, windowIndex);
// ─── 4. Transcript Initialization ────────────────────────────────────────
let aliceChain = transcriptNew(conversationId);
let bobChain = transcriptNew(conversationId);
// ─── 5. Alice Sends a Message (Sealed Sender) ────────────────────────────
const messageId = Buffer.from('msg-001');
const senderId = Buffer.from('alice@example.com');
const timestamp = Math.floor(Date.now() / 1000);
const aad = Buffer.concat([messageId, senderId, Buffer.from(timestamp.toString())]);
const plaintext = Buffer.from('Hello Bob');
const ciphertext = encryptAesGcm(windowKey, plaintext, aad);
const sealed = sealMessage(bobX25519Pk, senderId, ciphertext);
// Alice updates her transcript
const msgHash = transcriptComputeMessageHash(messageId, senderId, timestamp, ciphertext);
aliceChain = transcriptUpdate(aliceChain, msgHash);
// ─── 6. Bob Receives and Decrypts ────────────────────────────────────────
const [revealedSender, revealedCiphertext] = unsealMessage(sealed, bobX25519Sk);
const bobWindowKey = deriveWindowKey(
deriveSrk(bobSharedSecret, conversationId),
windowIndex,
);
const decrypted = decryptAesGcm(bobWindowKey, revealedCiphertext, aad);
bobChain = transcriptUpdate(bobChain, msgHash);
console.log(decrypted.toString()); // Hello Bob
console.log(revealedSender.toString()); // alice@example.com
console.log(transcriptVerifySync(aliceChain, bobChain)); // true
// ─── 7. Key Verification (Out of Band) ───────────────────────────────────
const aliceCode = generateVerificationCode(aliceIdPk, bobIdPk, conversationId);
const bobCode = generateVerificationCode(bobIdPk, aliceIdPk, conversationId);
// Alice and Bob compare these codes over a phone call| Tool | Version | Purpose |
|---|---|---|
| Rust | stable (≥ 1.76) | Core and bindings |
| wasm-pack | latest | WASM build |
| Node.js | ≥ 18 | Node.js binding and examples |
| npm | ≥ 9 | Package management |
# Clone
git clone https://github.com/BeratVural/vollcrypt.git
cd vollcrypt
# Run all tests
cargo test --workspace
# Check formatting and lints
cargo fmt --all -- --check
cargo clippy --workspace -- -D warnings
# Build Node.js native addon
cd node && npm install && npm run build && cd ..
# Build WebAssembly package
cd wasm && wasm-pack build --target web --out-dir pkg && cd ..
# Run usage examples
cd vollcrypt-example && npm install
npx ts-node src/09_full_flow.tsvollcrypt/
├── core/ Rust cryptographic core (no I/O, no_std compatible)
│ └── src/
│ ├── symmetric.rs AES-256-GCM encryption / decryption
│ ├── pqc.rs ML-KEM-768 + Hybrid KEM + Authenticated KEM
│ ├── keys.rs Ed25519 and X25519 keypair operations
│ ├── kdf.rs HKDF, PBKDF2, SRK and WindowKey derivation
│ ├── ratchet.rs PCS ratchet (post-compromise security)
│ ├── transcript.rs Message hash chain (session integrity)
│ ├── sealed_sender.rs Sender privacy layer
│ ├── verification.rs Key verification codes (numeric + emoji)
│ ├── key_log.rs Key Transparency log (hash-linked, signed)
│ ├── envelope.rs Binary message envelope packing
│ ├── wrap.rs AES-256-KW key wrapping
│ ├── bip39.rs BIP-39 mnemonic generation and seed derivation
│ └── device.rs Device registry and revocation
├── node/ N-API native binding (@vollcrypt/node)
├── wasm/ wasm-bindgen WebAssembly binding (@vollcrypt/wasm)
├── packages/
│ └── license-server/ License validation and MAU tracking server
└── vollcrypt-example/ Runnable usage examples (01 through 10)
Every AES-256-GCM ciphertext is packed into a standard binary envelope:
┌──────────────┬──────────────┬──────────────────┬──────────────┬──────────────────┐
│ Window Index │ IV │ AAD Hash │ Ciphertext │ Auth Tag │
│ 4 bytes │ 12 bytes │ 32 bytes │ variable │ 16 bytes │
│ (BE uint32) │ (OsRng) │ SHA-256(AAD) │ AES-256-GCM │ GCM MAC │
└──────────────┴──────────────┴──────────────────┴──────────────┴──────────────────┘
The window index tells the recipient which WindowKey to derive. The AAD hash ensures the message cannot be moved to a different conversation or attributed to a different sender.
- Generates IVs internally using
OsRng— you cannot pass an IV manually - Zeroizes sensitive memory (
zeroizecrate) after use in all Rust code - Uses
subtle::ConstantTimeEqfor all security-sensitive comparisons - Enforces distinct HKDF context strings for every key derivation purpose
Do not store raw key bytes in persistent memory in JavaScript. Import DEK and SRK bytes into SubtleCrypto with extractable: false immediately after receiving them from the WASM layer, then zero the raw buffer:
const dekCryptoKey = await crypto.subtle.importKey(
'raw', rawDekBuffer, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'],
);
new Uint8Array(rawDekBuffer).fill(0); // zero immediately after importDo not store raw keys in React state, Redux, or localStorage. A key stored in JavaScript memory is accessible to any XSS payload on the page.
Verify keys out of band. The library provides verification codes, but humans must compare them. An unverified session is authenticated only against the server's honesty.
Rotate keys after compromise. Key Transparency logs key changes, but it does not automatically rotate keys. Your application must trigger a new authenticated KEM handshake after device compromise or revocation.
Vollcrypt is dual-licensed under the GNU General Public License v3.0 (for open source projects) and a Commercial License (for proprietary projects).
Note
Current Status: Free Early Access
While the code includes methods like initializeLicense and references VOLLCRYPT_LICENSE_KEY, our commercial licensing infrastructure is currently under development.
At this time, Vollcrypt is completely free to use without any Monthly Active User (MAU) limits or license key requirements. You do not need to configure a license key to use the library in production today.
Once our licensing system is finalized and active, we will officially announce the pricing tiers and provide a smooth transition period for all our early adopters. For inquiries, you can reach out to Berat Vural at berat.vural.tr@gmail.com or connect on LinkedIn.
Contributions are welcome. Before opening a pull request, please read CONTRIBUTING.md.
All contributors must sign the Contributor License Agreement (CLA) before their first pull request is merged. This allows Vollcrypt to be offered under both the GPL and a commercial license.
Security issues: Please do not open public GitHub issues for security vulnerabilities. Follow the process in SECURITY.md.
Built with Rust · Powered by RustCrypto and dalek-cryptography