Post-quantum cryptography workspace for messaging, files, WebAssembly, desktop, and database security.
High-performance, chunk-based End-to-End Encrypted (E2EE) file container engine for Node.js, WebAssembly, and Rust.
Vollcrypt Files is designed for local file encryption, cloud object storage, and secure shared-file access. It processes large files incrementally without loading them fully into memory, but it is not a real-time network, audio, or video streaming protocol.
This module provides high-performance chunked file encryption, cryptographic access control, and chunk integrity verification for large encrypted file containers.
This package is dual-licensed under:
Vollcrypt Files is intended for:
Vollcrypt Files does not provide real-time transport encryption for live network streams, audio calls, or video calls.
Those use cases require different security properties such as frame ordering, packet-loss tolerance, replay windows, rekeying, jitter handling, and low-latency authentication. They are intended to be handled by separate Vollcrypt protocol profiles such as:
@vollcrypt/streaming (planned profile; not published)@vollcrypt/voice (planned profile; not published)Vollcrypt Files focuses on encrypted file containers for local storage, cloud storage, and secure file sharing.
Node.js native binding:
npm install @vollcrypt/files-node
Browser WebAssembly binding:
npm install @vollcrypt/files-wasm
import {
generateDek,
generateFileId,
encryptFilePipelinedAsync,
decryptFilePipelinedAsync
} from "@vollcrypt/files-node";
const dek = generateDek();
const fileId = generateFileId();
// Asynchronously encrypt a file using 4 parallel thread workers
const header = await encryptFilePipelinedAsync(
"./input.txt",
"./input.enc",
dek,
fileId,
65536, // 64 KB chunk size
[], // wraps
0, // mode (0 = Password)
4, // thread workers
null // optional signInfo
);
// Asynchronously decrypt the file
await decryptFilePipelinedAsync(
"./input.enc",
"./input.dec",
dek,
4 // thread workers
);
import init, { generateDek, generateFileId } from "@vollcrypt/files-wasm";
async function run() {
await init();
const dek = generateDek();
const fileId = generateFileId();
console.log("DEK generated:", dek);
}
run();
Vollcrypt Files operates on a chunk-by-chunk file container model. The format is optimized for large local files, cloud-stored encrypted objects, random-access reads, and secure shared-file opening.
Below is the block layout visualizing the relationship between the File Header, chunk envelopes, the Merkle Tree, and out-of-order seekability:
graph TD
classDef file fill:#eee,stroke:#333,stroke-width:1px;
classDef chunk fill:#df5c3f,stroke:#333,stroke-width:1px,color:#fff;
classDef node fill:#99c2ff,stroke:#333,stroke-width:1px;
classDef root fill:#4b0082,stroke:#333,stroke-width:1px,color:#fff;
subgraph MerkleTree ["Merkle Tree Integrity Chain (SHA-256)"]
Root["Merkle Root (Header offset 44)"]:::root
H_12["Node H(1..2)"]:::node
H_34["Node H(3..4)"]:::node
Tag1["Leaf 1: LeafHashV1(Chunk 1)"]:::node
Tag2["Leaf 2: LeafHashV1(Chunk 2)"]:::node
Tag3["Leaf 3: LeafHashV1(Chunk 3)"]:::node
Tag4["Leaf 4: LeafHashV1(Chunk 4)"]:::node
Root --> H_12
Root --> H_34
H_12 --> Tag1
H_12 --> Tag2
H_34 --> Tag3
H_34 --> Tag4
end
subgraph FileStorage ["Encrypted Chunk Envelopes on Disk"]
Header["Header (VOLLVALT Magic, DEK wraps, Merkle Root)"]:::file
Chunk1["Chunk 1 Envelope (Index 0, IV1, Ciphertext1, Auth Tag 1)"]:::chunk
Chunk2["Chunk 2 Envelope (Index 1, IV2, Ciphertext2, Auth Tag 2)"]:::chunk
Chunk3["Chunk 3 Envelope (Index 2, IV3, Ciphertext3, Auth Tag 3)"]:::chunk
Chunk4["Chunk 4 Envelope (Index 3, IV4, Ciphertext4, Auth Tag 4)"]:::chunk
end
Tag1 -.- Chunk1
Tag2 -.- Chunk2
Tag3 -.- Chunk3
Tag4 -.- Chunk4
+-----------------------------------------------------------+
| FILE CONTAINER |
+-----------------------------------------------------------+
| Header: |
| - Magic Bytes ("VOLLVALT") |
| - Version & Container Flags |
| - File ID & Merkle Root |
| - Wrap Table & Extension Table |
| - Signature (Optional, v2 only) |
+-----------------------------------------------------------+
| Chunk Envelopes: |
| +-----------------------------------------------------+ |
| | Chunk 1: Index (4B) | IV (12B) | Cipher | Tag (16B) | |
| +-----------------------------------------------------+ |
| | Chunk 2: Index (4B) | IV (12B) | Cipher | Tag (16B) | |
| +-----------------------------------------------------+ |
| | ... | |
| +-----------------------------------------------------+ |
+-----------------------------------------------------------+
| Merkle Root in Header |
| /\ |
| / \ |
| / \ |
| / \ |
| /\ /\ |
| / \ / \ |
| / \ / \ |
| Leaf 1 Leaf 2 Leaf 3 Leaf 4 |
+-----------------------------------------------------------+
| Leaf 1: LeafHashV1(Chunk 1) |
| LeafHashV1 = SHA-256("vollcrypt-file-merkle-leaf-v1" || |
| file_id || index || len || IV1 || Tag1) |
+-----------------------------------------------------------+
Vollcrypt Files supports multiple ways to wrap and protect the file-specific Data Encryption Key (DEK):
[!NOTE] New containers SHOULD use Argon2id. PBKDF2-based wrapping SHOULD only be used when compatibility with constrained or legacy environments is required.
[!NOTE] File Container Write Model Vollcrypt Files stores the Merkle Root in the file header. During encryption, implementations may either:
- write a placeholder header, encrypt chunks sequentially, compute the Merkle Root, and rewrite the header on seekable outputs such as local files; or
- build the encrypted container in a temporary file and write the final header once the Merkle Root is known.
This design is intentional for encrypted file containers. Vollcrypt Files is not intended to be used as a live real-time transport stream protocol.
To support multi-member groups:
Genesis, AddMember, and RemoveMember.graph LR
classDef gen fill:#4b0082,stroke:#333,stroke-width:1px,color:#fff;
classDef op fill:#99c2ff,stroke:#333,stroke-width:1px;
Genesis["Genesis Block<br/>(prev_hash: [0;32])<br/>Signed by Founder"]:::gen
Add["AddMember Block<br/>(prev_hash: H(Genesis))<br/>Signed by Admin"]:::op
Remove["RemoveMember Block<br/>(prev_hash: H(Add))<br/>Signed by Admin"]:::op
Genesis --> Add
Add --> Remove
Vollcrypt group revocation has multiple modes:
For large groups, manifest size and verification cost grow with the number of operations and members. Applications targeting very large groups should consider checkpointing, manifest compaction, or epoch snapshots.
.vproof sidecar file. The sidecar file hash MUST be bound to the main container header.To maximize CPU and NVMe SSD throughput, Vollcrypt Files implements bounded-memory, parallel pipelined file encryption and decryption:
num_workers * 2 chunks, strictly capping heap usage to $O(\text{num_workers} \times \text{chunk_size})$ regardless of file size.WrapTable containing all WrapEntry records protecting the DEK is permanently cleared (purged).ContainerSealed error."vollcrypt-file-sealed-marker-v1") which is written to the header’s SignedMetadata extension. This prevents attackers from stripping the sealed marker to make the container look unsealed.Purge mode. This mode actively zeroizes the entire ciphertext body of the file container before truncating the file to the header size, ensuring both keys and encrypted blocks are destroyed.Shield framework enforces strict integrity policy validation rules over the file container. It classifies and intercepts potential tampering vectors before raw data is decrypted or processed.Optional (for backward-compatibility).ReleaseMode::Verified) uses a double-pass decryption system. It verifies the complete Merkle root and chunk tag chain over the entire container before releasing any plaintext bytes to the output. If any bit-flip or chunk reordering is detected, exactly zero bytes of plaintext are written, preventing Chosen-Ciphertext Attacks (CCA2) on release.ReleaseMode::Streaming begins writing decrypted chunks to the output stream immediately. If a mismatch is encountered mid-stream, it aborts decryption and returns an integrity error, preventing further read amplification.subtle::ConstantTimeEq).The header contains critical file metadata and the wraps protecting the DEK. All multibyte integers are written in Big-Endian (BE) format.
Implementations MUST NOT assume a fixed small header size. The header is length-prefixed and may grow with the number of recipients, group metadata, and extensions.
| Offset | Length | Type | Description |
|---|---|---|---|
| 0 | 8 | Bytes | Magic Bytes (VOLLVALT) |
| 8 | 1 | u8 | Format Version (1 = Unsigned, 2 = Signed Classical, 3 = Signed Post-Quantum Hybrid) |
| 9 | 1 | u8 | Container Mode (e.g. 0 = Password, 1 = Recipient, 2 = Group) |
| 10 | 1 | u8 | Cipher Suite ID |
| 11 | 16 | Bytes | File ID |
| 27 | 4 | u32 BE | Chunk Size (Max 16 MB) |
| 31 | 8 | u64 BE | Plaintext Size |
| 39 | 32 | Bytes | Merkle Root |
| 71 | 1 | u8 | Wrap Count |
| 72 | 1 | u8 | Hash Algorithm (0 = SHA-256, 1 = BLAKE3) |
| 73 | 3 | Bytes | Reserved |
| 76 | 4 | u32 BE | Variable Length (Length of wrap entries table) |
| 80 | Var | Structs | Wrap Table (concatenated WrapEntry records) |
| 80 + Var | 4 | u32 BE | Metadata Length (v2/v3 only) |
| 84 + Var | Var | Struct | Signed Metadata (v2/v3 only) |
| 84 + Var + MetVar | Var | Signature | Signature Table (v2: 64B Ed25519, v3: Ed25519 + ML-DSA-65 Hybrid Signature) |
The container mode determines how the file is accessed (Password, Recipient, or Group). Supported access methods are determined by the list of WrapEntry records.
Variable Length is the total byte length of all concatenated WrapEntry records. Parsers MUST reject headers where the sum of parsed wrap entry sizes does not exactly equal Variable Length.
Each wrap entry starts with a 1-byte wrap_type and a 2-byte BE payload_len. payload_len excludes the 3-byte entry prefix (wrap_type || payload_len). Therefore, the total serialized size of a wrap entry is 3 + payload_len.
0..1: Wrap Type (0x00)1..3: Payload Length (0x003C - 60 bytes)3..7: Iterations (u32 BE, typically 600,000)7..23: Salt (16 bytes)23..63: Wrapped DEK (40 bytes AES-KW)0..1: Wrap Type (0x01)1..3: Payload Length (0x0044 - 68 bytes)3..7: Memory Cost (u32 BE)7..11: Time Cost (u32 BE)11..15: Parallelism Cost (u32 BE)15..31: Salt (16 bytes)31..71: Wrapped DEK (40 bytes AES-KW)0..1: Wrap Type (0x03)1..3: Payload Length (0x003C - 60 bytes)3..19: Group ID (16 bytes)19..23: Group Key Version (u32 BE)23..63: Wrapped DEK (40 bytes AES-KW)0..1: Wrap Type (0x04)1..3: Payload Length (0x049C - 1180 bytes)3..19: Recipient ID (16 bytes)19..23: Recipient Key Version / Wrap Context Version (u32 BE)23..55: X25519 Ephemeral Public Key (32 bytes)55..1143: ML-KEM-768 Ciphertext (1088 bytes)1143..1183: Wrapped Key (40 bytes AES-KW)0..1: Wrap Type (0x05)1..3: Payload Length (0x003A - 58 bytes)3..4: Threshold t (1 byte, 1..=255)4..5: Total Shares n (1 byte, 1..=255)5..21: Share Set ID (16 bytes)21..61: Wrapped DEK (40 bytes AES-KW under the derived KEK)Reconstructing the 32-byte Threshold Master Secret (TMS) requires at least t valid shares. The KEK is derived via HKDF-SHA256:
Info: "vollcrypt-file-threshold-kek-v1" |
share_set_id (16 bytes) |
t (1 byte) |
n (1 byte) |
cipher_suite_id (1 byte) |
Shares are exported externally as portable strings in the format vcs_<base64url(payload)> (without padding), where the 59-byte payload consists of:
0..4: Version / domain tag VCS\x01 ([0x56, 0x43, 0x53, 0x01])4..20: Share Set ID (16 bytes)20..21: Threshold t (1 byte)21..22: Total Shares n (1 byte)22..23: Coordinate x (1 byte, 1..=255)23..55: Share Value y (32 bytes)55..59: Checksum (4 bytes, first 4 bytes of SHA-256 over payload[0..55])For direct recipient wrapping, this field represents the recipient key version. For group-mediated recipient wrapping, a separate group-mediated wrap profile SHOULD be used. (Type 2 is unsupported/legacy).
Implementations MUST:
header_len, wrap_count, and wrap_table_len disagree.A container with zero wraps may be parsed for inspection, but it is not decryptable through normal APIs. Normal encrypted containers MUST contain at least one valid WrapEntry. Zero-wrap containers MAY be used to represent key-shredded files whose ciphertext remains stored but whose DEK can no longer be recovered.
Each encrypted chunk is stored as a sequential binary chunk envelope:
| Offset | Length | Type | Description |
|---|---|---|---|
| 0 | 4 | u32 BE | Chunk Index (0-based) |
| 4 | 12 | Bytes | IV / Nonce (12 bytes) |
| 16 | Var | Bytes | Ciphertext (Plaintext size) |
| 16 + Var | 16 | Bytes | AES-256-GCM Authentication Tag |
Zeroize and ZeroizeOnDrop traits to ensure they are scrubbed from memory immediately after use.unsafe code. Node.js and WebAssembly bindings are thin wrappers around the safe Rust core.For chunk i, implementations derive separate AEAD key and IV material using domain-separated HKDF labels.
chunk_key_i = HKDF-SHA256(
ikm = DEK,
salt = file_id,
info = "vollcrypt-file-chunk-key-v1" || chunk_index_u32_be,
length = 32
)
chunk_iv_i = HKDF-SHA256(
ikm = DEK,
salt = file_id,
info = "vollcrypt-file-chunk-iv-v1" || chunk_index_u32_be,
length = 12
)
The same (chunk_key, chunk_iv) pair MUST never be reused for different plaintext chunks.
hybrid_secret = x25519_shared_secret || ml_kem_shared_secret
KEK = HKDF-SHA256(
ikm = hybrid_secret,
salt = file_id,
info =
"vollcrypt-file-hybrid-kem-v1" ||
recipient_id[16] ||
recipient_key_version_u32_be ||
kem_suite_id ||
cipher_suite_id,
length = 32
)
Each AES-256-GCM chunk encryption authenticates the following associated data:
AAD_FileChunk_V1 =
"vollcrypt-file-chunk-aad-v1" ||
header_hash[32] ||
file_id[16] ||
chunk_index_u32_be ||
chunk_size_u32_be ||
plaintext_size_u64_be ||
chunk_plaintext_len_u32_be
Implementations MUST reject chunks if AEAD authentication fails. The header hash is derived as:
header_hash = SHA-256(canonical_header_without_mutable_fields)
To prevent malicious storage servers from replacing, reordering, or swapping chunk envelopes, Vollcrypt Files constructs a Merkle Tree over canonical chunk leaf hashes.
For format version 1, each leaf is computed using SHA-256 by default:
LeafHashV1_Sha256 =
SHA-256(
"vollcrypt-file-merkle-leaf-v1" ||
file_id[16] ||
chunk_index_u32_be ||
chunk_plaintext_len_u32_be ||
iv[12] ||
auth_tag[16]
)
For the optional BLAKE3 high-performance profile, LeafHashV1 and internal Merkle tree nodes are computed using BLAKE3:
LeafHashV1_Blake3 =
BLAKE3(
"vollcrypt-file-merkle-leaf-v1" ||
file_id[16] ||
chunk_index_u32_be ||
chunk_plaintext_len_u32_be ||
iv[12] ||
auth_tag[16]
)
The ciphertext payload is intentionally excluded from the Merkle leaf because AES-256-GCM already authenticates the ciphertext through the authentication tag.
Rust-owned secret material is zeroized using Zeroize and ZeroizeOnDrop.
When using Node.js or WebAssembly bindings, JavaScript runtimes may copy secrets in ways that cannot be fully zeroized by the native library. Callers SHOULD avoid immutable strings for passwords and SHOULD clear user-owned Uint8Array / Buffer values after use.
This example uses the published low-level exports from @vollcrypt/files-node. The readHeaderPrefix, FileHandle, and MerkleProofProvider helpers are application-owned I/O abstractions.
import {
HeaderClass,
decryptChunk,
verifyMerkleProof,
chunkLeafHash
} from '@vollcrypt/files-node';
async function seekAndDecryptChunk(
file: FileHandle,
targetByteOffset: number,
dek: Uint8Array,
proofProvider: MerkleProofProvider
): Promise<Buffer> {
const headerPrefix = await readHeaderPrefix(file, 16 * 1024 * 1024);
const { header, headerLen } = HeaderClass.parse(headerPrefix);
const chunkSize = header.chunkSize;
const plaintextLength = header.plaintextSize;
const fileId = header.fileId;
const merkleRoot = header.merkleRoot;
const chunkIndex = Math.floor(targetByteOffset / chunkSize);
const totalChunks = Math.ceil(plaintextLength / chunkSize);
if (chunkIndex >= totalChunks) {
throw new Error("Target offset exceeds file size");
}
const isLastChunk = chunkIndex === totalChunks - 1;
const chunkPlaintextLen = isLastChunk
? (plaintextLength % chunkSize || chunkSize)
: chunkSize;
const envelopeSize = 32 + chunkPlaintextLen;
const targetEnvelopeDiskPos = headerLen + chunkIndex * (32 + chunkSize);
const envelopeBuffer = Buffer.alloc(envelopeSize);
await file.read(envelopeBuffer, 0, envelopeSize, targetEnvelopeDiskPos);
const parsedIndex = envelopeBuffer.readUInt32BE(0);
if (parsedIndex !== chunkIndex) {
throw new Error(`Chunk index mismatch: expected ${chunkIndex}, got ${parsedIndex}`);
}
const envelope = {
chunkIndex: parsedIndex,
iv: envelopeBuffer.subarray(4, 16),
ciphertext: envelopeBuffer.subarray(16, 16 + chunkPlaintextLen),
tag: envelopeBuffer.subarray(16 + chunkPlaintextLen, envelopeSize)
};
const leafHash = chunkLeafHash(envelope);
const proof = await proofProvider.getProof(chunkIndex);
if (!verifyMerkleProof(leafHash, chunkIndex, totalChunks, proof, merkleRoot)) {
throw new Error(`Security exception: chunk ${chunkIndex} failed Merkle validation`);
}
return decryptChunk(dek, fileId, chunkIndex, envelope);
}
generateDek(): Generate a cryptographically secure 32-byte Data Encryption Key.generateFileId(): Generate a cryptographically secure 16-byte File ID.generateSalt(): Generate a cryptographically secure 16-byte Salt.generateGk(): Generate a cryptographically secure 32-byte Group Key.encryptChunk(dek, file_id, chunkIndex, plaintext): Encrypt a single block of plaintext.decryptChunk(dek, file_id, chunkIndex, envelope): Decrypt a single chunk envelope.encryptFilePipelinedAsync(sourcePath, destPath, dek, fileId, chunkSize, wraps, mode, numWorkers, signInfo, writeMode): Asynchronously encrypts a file from disk using parallel thread workers (Zero-Copy V8 heap footprint).decryptFilePipelinedAsync(sourcePath, destPath, dek, numWorkers, shield): Asynchronously decrypts a file from disk using parallel thread workers with an optional ShieldPolicy (Zero-Copy V8 heap footprint).wrapDekWithPassword(dek, password, kdf): Wrap a DEK with a password.unwrapDekWithPassword(wrapEntry, password): Unwrap a password-wrapped DEK.generateRecipientKeypair(): Generate an ML-KEM-768 + X25519 keypair.wrapKeyToRecipient(key, recipientId, gkVersion, recipientPk): Encrypt a key to an asymmetric recipient.unwrapKeyWithRecipientKey(wrapEntry, recipientSk): Decrypt a key using recipient secret key.wrapDekForGroup(dek, groupId, gkVersion, gk): Wrap the DEK with the Group Key.unwrapDekWithGroupKey(wrapEntry, gk): Unwrap a GroupWrap entry using the Group Key.ed25519KeypairGenerate(): Generate a signing keypair.ed25519Sign(sk, message): Sign a message.ed25519Verify(pk, message, signature): Verify a signature.sealContainer(path, options) / sealContainer(container_bytes, options): Irreversibly seal a container by purging the wrap table. options specifies mode (“seal” or “purge”), reason, and optional signInfo for signed v2/v3 containers.isSealed(header): Returns true if the container header has an empty wrap table (meaning it is sealed).inspectSealedContainer(path) / inspectSealedContainer(container_bytes): Returns structural metadata for a sealed container (mode, reason, timestamp, and ciphertext availability).verifyContainer(path, policy) / verifyContainer(container_bytes, policy): Evaluates a container’s header, wraps, signature, chunk tags, and Merkle tree against a ShieldPolicy and returns a string ShieldReport.const { sealContainer } = require("@vollcrypt/files-node");
// Irreversibly seal the container and sign the sealed marker with the owner's signing key
await sealContainer("container.dat", {
mode: "seal",
reason: "GDPR right to be forgotten request",
signInfo: {
kind: "plain",
signerPk: ownerPublicKey,
signerSk: ownerPrivateKey,
keyLogId: keyLogId,
timestamp: Math.floor(Date.now() / 1000)
}
});
const { verifyContainer, decryptFilePipelinedAsync } = require("@vollcrypt/files-node");
const policy = {
releaseMode: "verified", // "verified" (double-pass fail-closed) or "streaming" (fail mid-stream)
signature: "required", // "required" (must have valid owner signature) or "optional"
rollbackPin: 5, // enforce manifest epoch >= 5
founderAnchor: true, // verify founder anchor matches genesis
onTamper: "abort" // "abort", "report", or "recover"
};
// Check integrity upfront
const report = verifyContainer("container.dat", policy);
if (report === "ContainerSealed") {
console.log("Container is sealed.");
} else if (report !== "Success") {
console.error("Integrity check failed: " + report);
} else {
// Safe to decrypt under policy
await decryptFilePipelinedAsync("container.dat", "plaintext.txt", dek, 4, policy);
}
@vollcrypt/files-node: Node.js native binding for disk and low-level file-container APIs.@vollcrypt/files-wasm: Browser WebAssembly binding with bounded in-memory file APIs.vollcrypt-files-core: Rust workspace crate used by the native bindings and desktop application; it is not published to npm.@vollcrypt/messages-node and @vollcrypt/messages-wasm: separate encrypted-messaging packages.cd vollcrypt-files/node
npm install
npm run build:debug
npm test
By default, the WebAssembly module compiles with 128-bit SIMD acceleration enabled (target-feature=+simd128).
To compile:
cd vollcrypt-files/wasm
npm install
npm run build
npm test
To compile a portable fallback build without SIMD features, override the target flags:
RUSTFLAGS="" npm run build
Vollcrypt Files has undergone targeted performance optimizations to achieve peak single-core throughput and resolve encryption/decryption asymmetry:
file_id || chunk_index || chunk_plaintext_len || iv || tag according to LeafHashV1), avoiding double-pass processing (AES-GCM + SHA-256) of full file contents.OsRng in the encryption loop with a 44-byte HKDF expansion to derive both chunk subkeys and IVs deterministically.+simd128 target feature flag, allowing Rust cryptographic primitives to run with SIMD parallel hardware instructions directly inside modern browsers.x86-64-v3, allowing optional native overrides (RUSTFLAGS="-C target-cpu=native") to fully unlock hardware acceleration (AVX2, AES-NI, SHA-NI).| Metric | Balanced Profile (256MB) | Max Profile (1GB) | Detail | | — | — | — | — | | Throughput | 3.56 GB/s | 3.31 GB/s | Aggregate gigabytes per second | | Cycles/Byte | 0.97 | 1.04 | CPU clock cycles per byte encrypted | | Instructions/Byte | 1.21 | 1.30 | CPU instructions executed per byte | | Allocations/Chunk | 0 | 0 | Number of heap allocations per chunk | | Bytes Copied/Byte Encrypted | 1.0 | 1.0 | Total buffer copy amplification ratio | | Worker Idle Time | 56.5% | 81.3% | Time workers spent waiting for queue | | Queue Wait Time | 11.3% | 15.0% | Average time chunks spent in queue | | I/O Wait Time | 45.2% | 65.1% | Average time spent in disk/stream I/O | | Merkle Time / Total | 0.01% | 0.00% | Percentage of time spent in Merkle tree | | HKDF Time / Total | 0.02% | 0.00% | Percentage of time spent in HKDF subkeys | | AEAD Time / Total | 43.44% | 18.65% | Percentage of time spent in AEAD crypto | | Energy Estimate | 21.05 J/GB | 22.65 J/GB | Estimated energy consumption per GB | | Time to First Verified Plaintext | 0.172 ms | 1.668 ms | Latency to verify and decrypt chunk 0 |
| Operation | Input Size | Latency (median) | Latency (p99) | Throughput | | — | — | — | — | — | | encrypt_chunk | 4 KB | 4.30 μs | 28.50 μs | 908.43 MB/s | | decrypt_chunk | 4 KB | 3.60 μs | 4.40 μs | 1085.07 MB/s | | encrypt_chunk | 64 KB | 36.90 μs | 66.80 μs | 1693.77 MB/s | | decrypt_chunk | 64 KB | 37.10 μs | 56.60 μs | 1684.64 MB/s | | encrypt_chunk | 1 MB | 691.60 μs | 778.90 μs | 1445.92 MB/s | | decrypt_chunk | 1 MB | 675.50 μs | 723.30 μs | 1480.38 MB/s | | encrypt_chunk | 4 MB | 2586.30 μs | 2970.20 μs | 1546.61 MB/s | | decrypt_chunk | 4 MB | 2641.20 μs | 2651.90 μs | 1514.46 MB/s | | encrypt_chunk | 16 MB | 10425.80 μs | 11724.60 μs | 1534.65 MB/s | | decrypt_chunk | 16 MB | 10564.10 μs | 10630.60 μs | 1514.56 MB/s |
All baseline timings measured dynamically on the same AMD Ryzen 5 7500F test system:
| Metric | Balanced Profile (256MB, 1MB chunk) | Max Profile (1GB, 8MB chunk) | Detail | | — | — | — | — | | Throughput | 0.71 GB/s | 0.74 GB/s | Aggregate gigabytes per second | | Cycles/Byte | 0.55 | 0.53 | CPU clock cycles per byte encrypted | | Instructions/Byte | 0.69 | 0.66 | CPU instructions executed per byte | | Allocations/Chunk | 0 | 0 | Number of heap allocations per chunk | | Bytes Copied/Byte Encrypted | 1.0 | 1.0 | Total buffer copy amplification ratio | | Cache Misses/GB | N/A | N/A | Modeled cache misses per gigabyte | | Branch Misses/GB | N/A | N/A | Modeled branch mispredictions per gigabyte | | Worker Idle Time | 86.4% | 92.8% | Time workers spent waiting for queue | | Queue Wait Time | 15.0% | 15.0% | Average time chunks spent in queue | | I/O Wait Time | 69.2% | 74.3% | Average time spent in disk/stream I/O | | Merkle Time / Total | 0.20% | 0.01% | Percentage of time spent in Merkle tree | | HKDF Time / Total | 0.57% | 0.04% | Percentage of time spent in HKDF subkeys | | AEAD Time / Total | 12.99% | 7.14% | Percentage of time spent in AEAD crypto | | Energy Estimate | 134.21 J/GB | 128.59 J/GB | Estimated energy consumption per GB | | Time to First Verified Plaintext | 0.682 ms | 4.769 ms | Latency to verify and decrypt chunk 0 |
| Operation | Input Size | Latency (median) | Latency (p99) | Throughput |
| — | — | — | — | — |
| encrypt_chunk | 4 KB | 72.37 μs | 90.15 μs | 53.98 MB/s |
| decrypt_chunk | 4 KB | 80.88 μs | 336.33 μs | 48.30 MB/s |
| encrypt_chunk | 64 KB | 182.53 μs | 209.93 μs | 342.41 MB/s |
| decrypt_chunk | 64 KB | 168.51 μs | 376.97 μs | 370.89 MB/s |
| encrypt_chunk | 1 MB | 1173.14 μs | 1754.10 μs | 852.42 MB/s |
| decrypt_chunk | 1 MB | 1171.14 μs | 1198.10 μs | 853.87 MB/s |
| encrypt_chunk | 4 MB | 4509.95 μs | 4652.09 μs | 886.93 MB/s |
| decrypt_chunk | 4 MB | 4650.11 μs | 4795.83 μs | 860.19 MB/s |
| encrypt_chunk | 16 MB | 17892.73 μs | 19608.63 μs | 894.22 MB/s |
| decrypt_chunk | 16 MB | 18179.62 μs | 21220.74 μs | 880.11 MB/s |
All baseline timings measured dynamically on the same Intel Core i5-12450H test system:
Vollcrypt Files includes a dedicated benchmark and resource monitoring harness binary named vollcrypt. You can use this CLI to run automated suites, sweep configurations, profile specific parameters, and inspect real-time CPU/RAM/Disk stats:
# Run the full automated suite (generates markdown files under reports/)
cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --suite auto
# Profile specific configurations with JSON output
cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --profile balanced --json
# Profile max configuration and compare against local OpenSSL/Age baselines
cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --profile max --compare
# Sweep chunk sizes (from 4 KB to 16 MB)
cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --sweep chunk-size
# Sweep worker threads to evaluate parallel scaling
cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --sweep workers
The current test suite includes stress, fuzzing, tampering, replay, forgery-resistance, and safe-default policy tests.
vollcrypt-files-stress (16/16 pass)-- -D warnings on all target formats.