@vollcrypt/files-wasm 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,891 @@
1
+ # Vollcrypt Files
2
+
3
+ High-performance, chunk-based End-to-End Encrypted (E2EE) file container engine for Node.js, WebAssembly, and Rust.
4
+
5
+ ---
6
+
7
+ 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.
8
+
9
+ This module provides high-performance chunked file encryption, cryptographic access control, and chunk integrity verification for large encrypted file containers.
10
+
11
+ ## License
12
+
13
+ This package is dual-licensed under:
14
+ - GPL-3.0-only (for open-source distribution) — see the [LICENSE-GPL](LICENSE-GPL) file.
15
+ - Commercial License (for proprietary software integrations) — see the [LICENSE-COMMERCIAL.md](LICENSE-COMMERCIAL.md) file.
16
+
17
+ ---
18
+
19
+ ## Use Cases & Non-Goals
20
+
21
+ ### Use Cases
22
+
23
+ Vollcrypt Files is intended for:
24
+ - Encrypting local files before storage.
25
+ - Storing encrypted files in untrusted cloud storage.
26
+ - Sharing encrypted files with multiple recipients.
27
+ - Opening shared encrypted files securely.
28
+ - Random-access reads over cloud range requests.
29
+ - Group-based file access with key rotation and revocation semantics.
30
+
31
+ ### Non-Goals
32
+
33
+ Vollcrypt Files does not provide real-time transport encryption for live network streams, audio calls, or video calls.
34
+
35
+ 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:
36
+ - **[Vollcrypt Messages Module Documentation (README-messages.md)](README-messages.md)**
37
+ - `@vollcrypt/streaming`
38
+ - `@vollcrypt/voice`
39
+
40
+ Vollcrypt Files focuses on encrypted file containers for local storage, cloud storage, and secure file sharing.
41
+
42
+ ---
43
+
44
+ ## Quick Start
45
+
46
+ ### High-Level API Usage
47
+
48
+ ```typescript
49
+ import { files } from '@vollcrypt/files';
50
+
51
+ // Encrypt a file using a password or recipient keys
52
+ await files.encryptFile({
53
+ input: 'report.pdf',
54
+ output: 'report.pdf.voll',
55
+ password: 'my-secure-password',
56
+ recipients: [aliceRecipientId],
57
+ });
58
+
59
+ // Decrypt a file using a password
60
+ await files.decryptFile({
61
+ input: 'report.pdf.voll',
62
+ output: 'report.pdf',
63
+ password: 'my-secure-password',
64
+ });
65
+
66
+ // Open a shared file using a recipient private key
67
+ await files.openSharedFile({
68
+ input: 'report.pdf.voll',
69
+ recipientKey: myRecipientKey,
70
+ });
71
+ ```
72
+
73
+ ### Advanced Asynchronous Pipelined File API (Zero-Copy)
74
+
75
+ ```typescript
76
+ import {
77
+ generateDek,
78
+ generateFileId,
79
+ encryptFilePipelinedAsync,
80
+ decryptFilePipelinedAsync
81
+ } from "@vollcrypt/files-node";
82
+
83
+ const dek = generateDek();
84
+ const fileId = generateFileId();
85
+
86
+ // Asynchronously encrypt a file using 4 parallel thread workers
87
+ const header = await encryptFilePipelinedAsync(
88
+ "./input.txt",
89
+ "./input.enc",
90
+ dek,
91
+ fileId,
92
+ 65536, // 64 KB chunk size
93
+ [], // wraps
94
+ 0, // mode (0 = Password)
95
+ 4, // thread workers
96
+ null // optional signInfo
97
+ );
98
+
99
+ // Asynchronously decrypt the file
100
+ await decryptFilePipelinedAsync(
101
+ "./input.enc",
102
+ "./input.dec",
103
+ dek,
104
+ 4 // thread workers
105
+ );
106
+ ```
107
+
108
+ ### WebAssembly (Browser) Integration
109
+
110
+ ```javascript
111
+ import init, { generateDek, generateFileId } from "./pkg/vollcrypt_file_wasm.js";
112
+
113
+ async function run() {
114
+ await init();
115
+
116
+ const dek = generateDek();
117
+ const fileId = generateFileId();
118
+ console.log("DEK generated:", dek);
119
+ }
120
+ run();
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Architecture and File Container Design
126
+
127
+ 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.
128
+
129
+ ### Container Block Layout
130
+ Below is the block layout visualizing the relationship between the File Header, chunk envelopes, the Merkle Tree, and out-of-order seekability:
131
+
132
+ ```mermaid
133
+ graph TD
134
+ classDef file fill:#eee,stroke:#333,stroke-width:1px;
135
+ classDef chunk fill:#df5c3f,stroke:#333,stroke-width:1px,color:#fff;
136
+ classDef node fill:#99c2ff,stroke:#333,stroke-width:1px;
137
+ classDef root fill:#4b0082,stroke:#333,stroke-width:1px,color:#fff;
138
+
139
+ subgraph MerkleTree ["Merkle Tree Integrity Chain (SHA-256)"]
140
+ Root["Merkle Root (Header offset 44)"]:::root
141
+ H_12["Node H(1..2)"]:::node
142
+ H_34["Node H(3..4)"]:::node
143
+ Tag1["Leaf 1: LeafHashV1(Chunk 1)"]:::node
144
+ Tag2["Leaf 2: LeafHashV1(Chunk 2)"]:::node
145
+ Tag3["Leaf 3: LeafHashV1(Chunk 3)"]:::node
146
+ Tag4["Leaf 4: LeafHashV1(Chunk 4)"]:::node
147
+
148
+ Root --> H_12
149
+ Root --> H_34
150
+ H_12 --> Tag1
151
+ H_12 --> Tag2
152
+ H_34 --> Tag3
153
+ H_34 --> Tag4
154
+ end
155
+
156
+ subgraph FileStorage ["Encrypted Chunk Envelopes on Disk"]
157
+ Header["Header (VOLLVALT Magic, DEK wraps, Merkle Root)"]:::file
158
+ Chunk1["Chunk 1 Envelope (Index 0, IV1, Ciphertext1, Auth Tag 1)"]:::chunk
159
+ Chunk2["Chunk 2 Envelope (Index 1, IV2, Ciphertext2, Auth Tag 2)"]:::chunk
160
+ Chunk3["Chunk 3 Envelope (Index 2, IV3, Ciphertext3, Auth Tag 3)"]:::chunk
161
+ Chunk4["Chunk 4 Envelope (Index 3, IV4, Ciphertext4, Auth Tag 4)"]:::chunk
162
+ end
163
+
164
+ Tag1 -.- Chunk1
165
+ Tag2 -.- Chunk2
166
+ Tag3 -.- Chunk3
167
+ Tag4 -.- Chunk4
168
+ ```
169
+
170
+ ### Visual Layout Diagram
171
+ ```
172
+ +-----------------------------------------------------------+
173
+ | FILE CONTAINER |
174
+ +-----------------------------------------------------------+
175
+ | Header: |
176
+ | - Magic Bytes ("VOLLVALT") |
177
+ | - Version & Container Flags |
178
+ | - File ID & Merkle Root |
179
+ | - Wrap Table & Extension Table |
180
+ | - Signature (Optional, v2 only) |
181
+ +-----------------------------------------------------------+
182
+ | Chunk Envelopes: |
183
+ | +-----------------------------------------------------+ |
184
+ | | Chunk 1: Index (4B) | IV (12B) | Cipher | Tag (16B) | |
185
+ | +-----------------------------------------------------+ |
186
+ | | Chunk 2: Index (4B) | IV (12B) | Cipher | Tag (16B) | |
187
+ | +-----------------------------------------------------+ |
188
+ | | ... | |
189
+ | +-----------------------------------------------------+ |
190
+ +-----------------------------------------------------------+
191
+ | Merkle Root in Header |
192
+ | /\ |
193
+ | / \ |
194
+ | / \ |
195
+ | / \ |
196
+ | /\ /\ |
197
+ | / \ / \ |
198
+ | / \ / \ |
199
+ | Leaf 1 Leaf 2 Leaf 3 Leaf 4 |
200
+ +-----------------------------------------------------------+
201
+ | Leaf 1: LeafHashV1(Chunk 1) |
202
+ | LeafHashV1 = SHA-256("vollcrypt-file-merkle-leaf-v1" || |
203
+ | file_id || index || len || IV1 || Tag1) |
204
+ +-----------------------------------------------------------+
205
+ ```
206
+
207
+ ### Key Capabilities
208
+
209
+ #### 1. Multi-Mode Key Wrapping
210
+ Vollcrypt Files supports multiple ways to wrap and protect the file-specific Data Encryption Key (DEK):
211
+ * **Password-Based Wrapping:** Derives a Key Encryption Key (KEK) using Argon2id by default. PBKDF2-SHA256 is supported only for compatibility and legacy profiles. The DEK is wrapped using AES-256 Key Wrap (AES-KW).
212
+ * **Asymmetric Recipient Wrapping:** Uses a Post-Quantum Hybrid Key Encapsulation Mechanism (X25519 + ML-KEM-768) to encapsulate the DEK. The KEK is derived via HKDF-SHA256 using the classical and post-quantum shared secrets.
213
+ * **Group Wrapping:** Supports encrypting the DEK under a symmetric Group Key (GK), which is itself managed and rotated through a signed, hash-linked Group Manifest.
214
+
215
+ > [!NOTE]
216
+ > New containers SHOULD use Argon2id. PBKDF2-based wrapping SHOULD only be used when compatibility with constrained or legacy environments is required.
217
+
218
+ #### 2. Chunked File Container Engine
219
+ * **Chunk-Based Encryption:** Files are split into standard chunks (default: 1,048,576 bytes / 1 MiB). Each chunk is encrypted independently using AES-256-GCM.
220
+ * **Cryptographic Domain Separation:** Rather than using the DEK directly, each chunk is encrypted using a unique subkey derived via HKDF-SHA256 from the DEK, the 16-byte random file ID, and the chunk index.
221
+ * **Out-of-Order Decryption:** Allows instant random-access seeking. Any chunk can be decrypted independently given its index, without decrypting preceding chunks.
222
+ * **Chunk Size Limits:** Minimum: 4 KiB, Typical: 1 MiB to 16 MiB. A ceiling check of 16 MB is enforced during header parsing.
223
+ * **Sequential Full-File Decryption:** The decryption engine can consume encrypted container bytes sequentially for full-file decryption, without requiring random seeks during normal full-file reads.
224
+
225
+ > [!NOTE]
226
+ > **File Container Write Model**
227
+ > Vollcrypt Files stores the Merkle Root in the file header. During encryption, implementations may either:
228
+ > 1. write a placeholder header, encrypt chunks sequentially, compute the Merkle Root, and rewrite the header on seekable outputs such as local files; or
229
+ > 2. build the encrypted container in a temporary file and write the final header once the Merkle Root is known.
230
+ >
231
+ > This design is intentional for encrypted file containers. Vollcrypt Files is not intended to be used as a live real-time transport stream protocol.
232
+
233
+ #### 3. Signed, Hash-Linked Group Manifest
234
+ To support multi-member groups:
235
+ * **Operation Log:** The manifest records the lifecycle of the group through operations: `Genesis`, `AddMember`, and `RemoveMember`.
236
+ * **Ed25519 Signatures:** Every operation in the log must be signed by the group's founder/admin.
237
+ * **Cryptographic Chaining:** Each operation contains the SHA-256 hash of the complete preceding operation, forming an immutable hash chain starting from the Genesis block.
238
+
239
+ ```mermaid
240
+ graph LR
241
+ classDef gen fill:#4b0082,stroke:#333,stroke-width:1px,color:#fff;
242
+ classDef op fill:#99c2ff,stroke:#333,stroke-width:1px;
243
+
244
+ Genesis["Genesis Block<br/>(prev_hash: [0;32])<br/>Signed by Founder"]:::gen
245
+ Add["AddMember Block<br/>(prev_hash: H(Genesis))<br/>Signed by Admin"]:::op
246
+ Remove["RemoveMember Block<br/>(prev_hash: H(Add))<br/>Signed by Admin"]:::op
247
+
248
+ Genesis --> Add
249
+ Add --> Remove
250
+ ```
251
+
252
+ ##### Revocation & Manifest Limits
253
+
254
+ ###### Revocation Model
255
+ Vollcrypt group revocation has multiple modes:
256
+ 1. **Lazy Revocation:** Removed members stop receiving future group keys. Historical files may remain decryptable if the removed member previously cached the required keys.
257
+ 2. **Forward-Only Revocation:** New files are encrypted under a new group key epoch. Old files are not automatically re-encrypted.
258
+ 3. **Strict Revocation:** Existing files are rewrapped or re-encrypted under a new key epoch. This is more expensive but prevents removed members from opening files.
259
+
260
+ ###### Manifest Scaling
261
+ 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.
262
+
263
+ #### 4. Merkle Tree Integrity Verification
264
+ * **Chunk-Substitution Protection:** To prevent malicious storage servers from replacing, reordering, or swapping chunk envelopes, a Merkle Tree is constructed over the authentication tags of all chunk envelopes.
265
+ * **Merkle Proofs:** Individual chunks can be verified for integrity by validating their chunk leaf hash and associated Merkle proof against the trusted root hash stored in the file header.
266
+ * **Merkle Proof Storage Modes:** Vollcrypt Files supports the following verification modes:
267
+ 1. **Full-file verification:** The reader verifies the complete container by processing all chunk tags and recomputing the Merkle Root. No external proof storage is required.
268
+ 2. **Embedded proof index:** Merkle proofs or proof indexes are stored inside the encrypted container metadata. This mode is suitable for self-contained shared files and cloud range reads.
269
+ 3. **Sidecar proof file:** Proof metadata is stored in a separate `.vproof` sidecar file. The sidecar file hash MUST be bound to the main container header.
270
+ 4. **Remote metadata service:** Proofs may be fetched from a metadata service. The service is not trusted; all proofs MUST verify against the Merkle Root stored in the signed/trusted file header.
271
+
272
+ #### 5. Bounded-Memory Parallelism
273
+ To maximize CPU and NVMe SSD throughput, Vollcrypt Files implements bounded-memory, parallel pipelined file encryption and decryption:
274
+ * **Bounded Memory Consumption:** Employs bounded channels to buffer at most `num_workers * 2` chunks, strictly capping heap usage to $O(\text{num\_workers} \times \text{chunk\_size})$ regardless of file size.
275
+ * **Out-of-order Processing with Sequential Write:** Crypto worker threads encrypt/decrypt chunks concurrently out-of-order, while a re-ordering buffer sequentially writes them to the target file.
276
+
277
+ #### 6. Sovereign Sealing & Crypto-Shredding
278
+ * **Irreversible Key Erasure:** Sovereign sealing is designed for strict compliance and data lifecycle governance. When a container is sealed, the `WrapTable` containing all `WrapEntry` records protecting the DEK is permanently cleared (purged).
279
+ * **Decryption Blocked:** Once sealed, the Data Encryption Key (DEK) is destroyed inside the file container, making decryption mathematically impossible unless the caller retains a backup copy of the DEK externally. Standard decryption routines will immediately reject sealed containers with a `ContainerSealed` error.
280
+ * **Signed Sealed Markers:** For signed formats (v2 and v3 containers), the owner signs a domain-separated sealed marker (`"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.
281
+ * **Crypto-Shredding (Purge Mode):** In addition to erasing wraps, Vollcrypt Files supports `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.
282
+
283
+ #### 7. Shield Integrity Policy
284
+ * **Tamper-Reactive Security:** The `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.
285
+ * **Signature Enforcement:** Enforces the presence of valid owner signatures on v2/v3 containers. Standard unsigned files or legacy v1 headers are rejected unless the signature policy is explicitly set to `Optional` (for backward-compatibility).
286
+ * **Verified Release Mode:** The default policy (`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.
287
+ * **Streaming Release Mode:** For large-file performance, `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.
288
+ * **Constant-Time Comparisons:** To eliminate side-channel timing analysis vectors, all Merkle root comparisons inside the Shield verification routines are performed using constant-time equality primitives (`subtle::ConstantTimeEq`).
289
+
290
+ ---
291
+
292
+ ## Technical Specifications
293
+
294
+ ### Cryptographic Algorithms
295
+
296
+ - **Symmetric Encryption**: AES-256-GCM
297
+ - **Key Wrapping**: AES-256-Key-Wrap (AES-KW)
298
+ - **Key Derivation (KDF)**: Argon2id (default) or PBKDF2 (SHA-256) (legacy compatibility)
299
+ - **Asymmetric Exchange (Hybrid KEM)**: ML-KEM-768 (Kyber) combined with X25519
300
+ - **Signatures**: Ed25519 (RFC 8032)
301
+ - **Integrity**: Merkle Tree leaf hashing over chunk metadata and authentication tags (SHA-256 by default, with optional BLAKE3 support for high-performance profiles)
302
+
303
+ ### File Header Binary Layout
304
+ The header contains critical file metadata and the wraps protecting the DEK. All multibyte integers are written in Big-Endian (BE) format.
305
+
306
+ 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.
307
+
308
+ | Offset | Length | Type | Description |
309
+ | :--- | :--- | :--- | :--- |
310
+ | 0 | 8 | Bytes | Magic Bytes (`VOLLVALT`) |
311
+ | 8 | 1 | u8 | Format Version (1 = Unsigned, 2 = Signed Classical, 3 = Signed Post-Quantum Hybrid) |
312
+ | 9 | 1 | u8 | Container Mode (e.g. 0 = Password, 1 = Recipient, 2 = Group) |
313
+ | 10 | 1 | u8 | Cipher Suite ID |
314
+ | 11 | 16 | Bytes | File ID |
315
+ | 27 | 4 | u32 BE | Chunk Size (Max 16 MB) |
316
+ | 31 | 8 | u64 BE | Plaintext Size |
317
+ | 39 | 32 | Bytes | Merkle Root |
318
+ | 71 | 1 | u8 | Wrap Count |
319
+ | 72 | 1 | u8 | Hash Algorithm (0 = SHA-256, 1 = BLAKE3) |
320
+ | 73 | 3 | Bytes | Reserved |
321
+ | 76 | 4 | u32 BE | Variable Length (Length of wrap entries table) |
322
+ | 80 | Var | Structs | Wrap Table (concatenated WrapEntry records) |
323
+ | 80 + Var | 4 | u32 BE | Metadata Length (v2/v3 only) |
324
+ | 84 + Var | Var | Struct | Signed Metadata (v2/v3 only) |
325
+ | 84 + Var + MetVar | Var | Signature | Signature Table (v2: 64B Ed25519, v3: Ed25519 + ML-DSA-65 Hybrid Signature) |
326
+
327
+ #### Container Flags
328
+ The container mode determines how the file is accessed (Password, Recipient, or Group). Supported access methods are determined by the list of `WrapEntry` records.
329
+
330
+ #### Variable Length
331
+ `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`.
332
+
333
+ ### Wrap Entry Binary Layouts
334
+ 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`.
335
+
336
+ #### Type 0: Password PBKDF2 (Payload Length = 60)
337
+ * `0..1`: Wrap Type (0x00)
338
+ * `1..3`: Payload Length (0x003C - 60 bytes)
339
+ * `3..7`: Iterations (u32 BE, typically 600,000)
340
+ * `7..23`: Salt (16 bytes)
341
+ * `23..63`: Wrapped DEK (40 bytes AES-KW)
342
+ * *Total Serialization Size:* 63 bytes.
343
+
344
+ #### Type 1: Password Argon2id (Payload Length = 68)
345
+ * `0..1`: Wrap Type (0x01)
346
+ * `1..3`: Payload Length (0x0044 - 68 bytes)
347
+ * `3..7`: Memory Cost (u32 BE)
348
+ * `7..11`: Time Cost (u32 BE)
349
+ * `11..15`: Parallelism Cost (u32 BE)
350
+ * `15..31`: Salt (16 bytes)
351
+ * `31..71`: Wrapped DEK (40 bytes AES-KW)
352
+ * *Total Serialization Size:* 71 bytes.
353
+
354
+ #### Type 3: Group Wrap (Payload Length = 60)
355
+ * `0..1`: Wrap Type (0x03)
356
+ * `1..3`: Payload Length (0x003C - 60 bytes)
357
+ * `3..19`: Group ID (16 bytes)
358
+ * `19..23`: Group Key Version (u32 BE)
359
+ * `23..63`: Wrapped DEK (40 bytes AES-KW)
360
+ * *Total Serialization Size:* 63 bytes.
361
+
362
+ #### Type 4: Hybrid KEM (Payload Length = 1180)
363
+ * `0..1`: Wrap Type (0x04)
364
+ * `1..3`: Payload Length (0x049C - 1180 bytes)
365
+ * `3..19`: Recipient ID (16 bytes)
366
+ * `19..23`: Recipient Key Version / Wrap Context Version (u32 BE)
367
+ * `23..55`: X25519 Ephemeral Public Key (32 bytes)
368
+ * `55..1143`: ML-KEM-768 Ciphertext (1088 bytes)
369
+ * `1143..1183`: Wrapped Key (40 bytes AES-KW)
370
+ * *Total Serialization Size:* 1183 bytes.
371
+
372
+ #### Type 5: Threshold SSS Wrap (Payload Length = 58)
373
+ * `0..1`: Wrap Type (0x05)
374
+ * `1..3`: Payload Length (0x003A - 58 bytes)
375
+ * `3..4`: Threshold `t` (1 byte, `1..=255`)
376
+ * `4..5`: Total Shares `n` (1 byte, `1..=255`)
377
+ * `5..21`: Share Set ID (16 bytes)
378
+ * `21..61`: Wrapped DEK (40 bytes AES-KW under the derived KEK)
379
+ * *Total Serialization Size:* 61 bytes.
380
+
381
+ Reconstructing the 32-byte Threshold Master Secret (TMS) requires at least `t` valid shares. The KEK is derived via HKDF-SHA256:
382
+ * **IKM:** Reconstructed TMS (32 bytes)
383
+ * **Salt:** File ID (16 bytes)
384
+ * **Info:** `"vollcrypt-file-threshold-kek-v1"` || `share_set_id` (16 bytes) || `t` (1 byte) || `n` (1 byte) || `cipher_suite_id` (1 byte)
385
+ * **Length:** 32 bytes
386
+
387
+ Shares are exported externally as portable strings in the format `vcs_<base64url(payload)>` (without padding), where the 59-byte `payload` consists of:
388
+ * `0..4`: Version / domain tag `VCS\x01` (`[0x56, 0x43, 0x53, 0x01]`)
389
+ * `4..20`: Share Set ID (16 bytes)
390
+ * `20..21`: Threshold `t` (1 byte)
391
+ * `21..22`: Total Shares `n` (1 byte)
392
+ * `22..23`: Coordinate `x` (1 byte, `1..=255`)
393
+ * `23..55`: Share Value `y` (32 bytes)
394
+ * `55..59`: Checksum (4 bytes, first 4 bytes of SHA-256 over `payload[0..55]`)
395
+
396
+ 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).
397
+
398
+ ### Canonical Encoding and Parser Rules
399
+ Implementations MUST:
400
+ - Parse all multibyte integers as Big-Endian.
401
+ - Reject non-canonical header encodings.
402
+ - Reject duplicate critical extensions.
403
+ - Reject headers where `header_len`, `wrap_count`, and `wrap_table_len` disagree.
404
+ - Reject unknown critical extensions.
405
+ - Reject trailing bytes inside the declared header region.
406
+ - Reject chunk envelopes whose encoded chunk index does not match their expected position.
407
+ - Reject containers with zero valid wraps unless explicitly opened in a shredded/deleted-key inspection mode.
408
+
409
+ 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.
410
+
411
+ ### Chunk Envelope Binary Layout
412
+ Each encrypted chunk is stored as a sequential binary chunk envelope:
413
+
414
+ | Offset | Length | Type | Description |
415
+ | :--- | :--- | :--- | :--- |
416
+ | 0 | 4 | u32 BE | Chunk Index (0-based) |
417
+ | 4 | 12 | Bytes | IV / Nonce (12 bytes) |
418
+ | 16 | Var | Bytes | Ciphertext (Plaintext size) |
419
+ | 16 + Var | 16 | Bytes | AES-256-GCM Authentication Tag |
420
+
421
+ ---
422
+
423
+ ## Technical Foundations
424
+
425
+ ### Cryptographic Security Policies
426
+ 1. **Memory Protection:** All sensitive keying materials (including Key Encryption Keys, ephemeral Diffie-Hellman secrets, and recipient secret keys) implement the `Zeroize` and `ZeroizeOnDrop` traits to ensure they are scrubbed from memory immediately after use.
427
+ 2. **No Unsafe Code:** The Rust cryptographic core is implemented without `unsafe` code. Node.js and WebAssembly bindings are thin wrappers around the safe Rust core.
428
+
429
+ ### Chunk Key and IV Derivation
430
+ For chunk `i`, implementations derive separate AEAD key and IV material using domain-separated HKDF labels.
431
+ ```
432
+ chunk_key_i = HKDF-SHA256(
433
+ ikm = DEK,
434
+ salt = file_id,
435
+ info = "vollcrypt-file-chunk-key-v1" || chunk_index_u32_be,
436
+ length = 32
437
+ )
438
+
439
+ chunk_iv_i = HKDF-SHA256(
440
+ ikm = DEK,
441
+ salt = file_id,
442
+ info = "vollcrypt-file-chunk-iv-v1" || chunk_index_u32_be,
443
+ length = 12
444
+ )
445
+ ```
446
+ The same `(chunk_key, chunk_iv)` pair MUST never be reused for different plaintext chunks.
447
+
448
+ ### Hybrid KEM KEK Derivation
449
+ ```
450
+ hybrid_secret = x25519_shared_secret || ml_kem_shared_secret
451
+
452
+ KEK = HKDF-SHA256(
453
+ ikm = hybrid_secret,
454
+ salt = file_id,
455
+ info =
456
+ "vollcrypt-file-hybrid-kem-v1" ||
457
+ recipient_id[16] ||
458
+ recipient_key_version_u32_be ||
459
+ kem_suite_id ||
460
+ cipher_suite_id,
461
+ length = 32
462
+ )
463
+ ```
464
+
465
+ ### Chunk AEAD Associated Data
466
+ Each AES-256-GCM chunk encryption authenticates the following associated data:
467
+ ```
468
+ AAD_FileChunk_V1 =
469
+ "vollcrypt-file-chunk-aad-v1" ||
470
+ header_hash[32] ||
471
+ file_id[16] ||
472
+ chunk_index_u32_be ||
473
+ chunk_size_u32_be ||
474
+ plaintext_size_u64_be ||
475
+ chunk_plaintext_len_u32_be
476
+ ```
477
+ Implementations MUST reject chunks if AEAD authentication fails. The header hash is derived as:
478
+ ```
479
+ header_hash = SHA-256(canonical_header_without_mutable_fields)
480
+ ```
481
+
482
+ ### Merkle Tree Integrity Verification
483
+ To prevent malicious storage servers from replacing, reordering, or swapping chunk envelopes, Vollcrypt Files constructs a Merkle Tree over canonical chunk leaf hashes.
484
+
485
+ For format version 1, each leaf is computed using SHA-256 by default:
486
+ ```
487
+ LeafHashV1_Sha256 =
488
+ SHA-256(
489
+ "vollcrypt-file-merkle-leaf-v1" ||
490
+ file_id[16] ||
491
+ chunk_index_u32_be ||
492
+ chunk_plaintext_len_u32_be ||
493
+ iv[12] ||
494
+ auth_tag[16]
495
+ )
496
+ ```
497
+
498
+ For the optional BLAKE3 high-performance profile, `LeafHashV1` and internal Merkle tree nodes are computed using BLAKE3:
499
+ ```
500
+ LeafHashV1_Blake3 =
501
+ BLAKE3(
502
+ "vollcrypt-file-merkle-leaf-v1" ||
503
+ file_id[16] ||
504
+ chunk_index_u32_be ||
505
+ chunk_plaintext_len_u32_be ||
506
+ iv[12] ||
507
+ auth_tag[16]
508
+ )
509
+ ```
510
+ The ciphertext payload is intentionally excluded from the Merkle leaf because AES-256-GCM already authenticates the ciphertext through the authentication tag.
511
+
512
+ ### Memory Zeroization and JS/WASM Runtimes
513
+ Rust-owned secret material is zeroized using `Zeroize` and `ZeroizeOnDrop`.
514
+
515
+ 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.
516
+
517
+ ---
518
+
519
+ ## Programmatic Integration Examples
520
+
521
+ ### Out-of-Order Seek & Verify
522
+ This TypeScript pseudocode details how an integrator reads any random chunk offset from an encrypted file, verifies its Merkle proof, and decrypts it independently.
523
+
524
+ ```ts
525
+ import {
526
+ Header,
527
+ decryptChunk,
528
+ verifyMerkleProof,
529
+ chunkLeafHash
530
+ } from '@vollcrypt/files';
531
+
532
+ async function seekAndDecryptChunk(
533
+ file: FileHandle,
534
+ targetByteOffset: number,
535
+ keyHandle: VollcryptFileKey,
536
+ proofProvider: MerkleProofProvider
537
+ ): Promise<Buffer> {
538
+ const { header, headerLen } = await Header.readFrom(file, {
539
+ maxHeaderSize: 16 * 1024 * 1024
540
+ });
541
+
542
+ const chunkSize = header.chunkSize;
543
+ const plaintextLength = header.plaintextSize;
544
+ const fileId = header.fileId;
545
+ const merkleRoot = header.merkleRoot;
546
+
547
+ const chunkIndex = Math.floor(targetByteOffset / chunkSize);
548
+ const totalChunks = Math.ceil(plaintextLength / chunkSize);
549
+
550
+ if (chunkIndex >= totalChunks) {
551
+ throw new Error("Target offset exceeds file size");
552
+ }
553
+
554
+ const isLastChunk = chunkIndex === totalChunks - 1;
555
+ const chunkPlaintextLen = isLastChunk
556
+ ? (plaintextLength % chunkSize || chunkSize)
557
+ : chunkSize;
558
+
559
+ const envelopeSize = 32 + chunkPlaintextLen;
560
+ const targetEnvelopeDiskPos = headerLen + chunkIndex * (32 + chunkSize);
561
+
562
+ const envelopeBuffer = Buffer.alloc(envelopeSize);
563
+ await file.read(envelopeBuffer, 0, envelopeSize, targetEnvelopeDiskPos);
564
+
565
+ const parsedIndex = envelopeBuffer.readUInt32BE(0);
566
+
567
+ if (parsedIndex !== chunkIndex) {
568
+ throw new Error(`Chunk index mismatch: expected ${chunkIndex}, got ${parsedIndex}`);
569
+ }
570
+
571
+ const iv = envelopeBuffer.subarray(4, 16);
572
+ const ciphertext = envelopeBuffer.subarray(16, 16 + chunkPlaintextLen);
573
+ const authTag = envelopeBuffer.subarray(16 + chunkPlaintextLen, envelopeSize);
574
+
575
+ const leafHash = chunkLeafHash({
576
+ fileId,
577
+ chunkIndex,
578
+ chunkPlaintextLen,
579
+ iv,
580
+ tag: authTag
581
+ });
582
+
583
+ const proof = await proofProvider.getProof(chunkIndex);
584
+
585
+ if (!verifyMerkleProof(leafHash, proof, merkleRoot, chunkIndex, totalChunks)) {
586
+ throw new Error(`Security Exception: Chunk ${chunkIndex} failed Merkle validation`);
587
+ }
588
+
589
+ return decryptChunk(keyHandle, fileId, chunkIndex, {
590
+ chunkIndex,
591
+ iv,
592
+ ciphertext,
593
+ tag: authTag
594
+ });
595
+ }
596
+ ```
597
+
598
+ ### Low-Level Random-Access Parsing Example
599
+ ```typescript
600
+ const { header, headerLen } = await Header.readFrom(file, {
601
+ maxHeaderSize: 16 * 1024 * 1024
602
+ });
603
+
604
+ const keyHandle = await files.openKeyFromPassword(password, header);
605
+
606
+ // Fetch a single chunk out-of-order
607
+ const chunkIndex = 42;
608
+ const envelope = await fetchChunkEnvelope(file, header, chunkIndex);
609
+
610
+ // Validate and check parsed index matches the expectation
611
+ if (envelope.chunkIndex !== chunkIndex) {
612
+ throw new Error(`Chunk index mismatch: expected ${chunkIndex}, got ${envelope.chunkIndex}`);
613
+ }
614
+
615
+ const plaintext = await files.decryptChunk(envelope, keyHandle);
616
+
617
+ // Verify leaf integrity locally
618
+ const leafHash = chunkLeafHash({
619
+ fileId: header.fileId,
620
+ chunkIndex: envelope.chunkIndex,
621
+ chunkPlaintextLen: plaintext.length,
622
+ iv: envelope.iv,
623
+ tag: envelope.tag
624
+ });
625
+ assert.ok(verifyMerkleProof(leafHash, chunkIndex, totalChunks, proof, header.merkleRoot));
626
+
627
+ keyHandle.destroy();
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Advanced API Reference (Bindings)
633
+ - `generateDek()`: Generate a cryptographically secure 32-byte Data Encryption Key.
634
+ - `generateFileId()`: Generate a cryptographically secure 16-byte File ID.
635
+ - `generateSalt()`: Generate a cryptographically secure 16-byte Salt.
636
+ - `generateGk()`: Generate a cryptographically secure 32-byte Group Key.
637
+ - `encryptChunk(dek, file_id, chunkIndex, plaintext)`: Encrypt a single block of plaintext.
638
+ - `decryptChunk(dek, file_id, chunkIndex, envelope)`: Decrypt a single chunk envelope.
639
+ - `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).
640
+ - `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).
641
+ - `wrapDekWithPassword(dek, password, kdf)`: Wrap a DEK with a password.
642
+ - `unwrapDekWithPassword(wrapEntry, password)`: Unwrap a password-wrapped DEK.
643
+ - `generateRecipientKeypair()`: Generate an ML-KEM-768 + X25519 keypair.
644
+ - `wrapKeyToRecipient(key, recipientId, gkVersion, recipientPk)`: Encrypt a key to an asymmetric recipient.
645
+ - `unwrapKeyWithRecipientKey(wrapEntry, recipientSk)`: Decrypt a key using recipient secret key.
646
+ - `wrapDekForGroup(dek, groupId, gkVersion, gk)`: Wrap the DEK with the Group Key.
647
+ - `unwrapDekWithGroupKey(wrapEntry, gk)`: Unwrap a GroupWrap entry using the Group Key.
648
+ - `ed25519KeypairGenerate()`: Generate a signing keypair.
649
+ - `ed25519Sign(sk, message)`: Sign a message.
650
+ - `ed25519Verify(pk, message, signature)`: Verify a signature.
651
+ - `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.
652
+ - `isSealed(header)`: Returns `true` if the container header has an empty wrap table (meaning it is sealed).
653
+ - `inspectSealedContainer(path)` / `inspectSealedContainer(container_bytes)`: Returns structural metadata for a sealed container (mode, reason, timestamp, and ciphertext availability).
654
+ - `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`.
655
+
656
+ ### Sovereign Sealing & Shield Policy API Examples
657
+
658
+ #### Sealing a Container (Node.js)
659
+ ```javascript
660
+ const { sealContainer } = require("@vollcrypt/files-node");
661
+
662
+ // Irreversibly seal the container and sign the sealed marker with the owner's signing key
663
+ await sealContainer("container.dat", {
664
+ mode: "seal",
665
+ reason: "GDPR right to be forgotten request",
666
+ signInfo: {
667
+ kind: "plain",
668
+ signerPk: ownerPublicKey,
669
+ signerSk: ownerPrivateKey,
670
+ keyLogId: keyLogId,
671
+ timestamp: Math.floor(Date.now() / 1000)
672
+ }
673
+ });
674
+ ```
675
+
676
+ #### Verifying a Container with Shield Integrity Policy
677
+ ```javascript
678
+ const { verifyContainer, decryptFilePipelinedAsync } = require("@vollcrypt/files-node");
679
+
680
+ const policy = {
681
+ releaseMode: "verified", // "verified" (double-pass fail-closed) or "streaming" (fail mid-stream)
682
+ signature: "required", // "required" (must have valid owner signature) or "optional"
683
+ rollbackPin: 5, // enforce manifest epoch >= 5
684
+ founderAnchor: true, // verify founder anchor matches genesis
685
+ onTamper: "abort" // "abort", "report", or "recover"
686
+ };
687
+
688
+ // Check integrity upfront
689
+ const report = verifyContainer("container.dat", policy);
690
+ if (report === "ContainerSealed") {
691
+ console.log("Container is sealed.");
692
+ } else if (report !== "Success") {
693
+ console.error("Integrity check failed: " + report);
694
+ } else {
695
+ // Safe to decrypt under policy
696
+ await decryptFilePipelinedAsync("container.dat", "plaintext.txt", dek, 4, policy);
697
+ }
698
+ ```
699
+
700
+ ---
701
+
702
+ ## Package Layout
703
+
704
+ - `@vollcrypt/core`: shared cryptographic primitives, suite IDs, KDFs, AEAD wrappers, encoders, and error types.
705
+ - `@vollcrypt/files`: encrypted file container format for local files, cloud objects, and shared files.
706
+ - `@vollcrypt/messages`: message encryption profile.
707
+ - `@vollcrypt/streaming`: future real-time stream encryption profile.
708
+ - `@vollcrypt/voice`: future low-latency voice/media encryption profile.
709
+ - `vollcrypt`: optional high-level meta-package.
710
+
711
+ ---
712
+
713
+ ## Building and Testing
714
+
715
+ ### Build Node.js Crate
716
+ ```bash
717
+ cd vollcrypt-files/node
718
+ npm install
719
+ npm run build:debug
720
+ npm test
721
+ ```
722
+
723
+ ### Build WebAssembly Crate
724
+ By default, the WebAssembly module compiles with 128-bit SIMD acceleration enabled (`target-feature=+simd128`).
725
+ To compile:
726
+ ```bash
727
+ cd vollcrypt-files/wasm
728
+ npm install
729
+ npm run build
730
+ npm test
731
+ ```
732
+ To compile a portable fallback build without SIMD features, override the target flags:
733
+ ```bash
734
+ RUSTFLAGS="" npm run build
735
+ ```
736
+
737
+ ---
738
+
739
+ ## Performance & Optimizations
740
+
741
+ Vollcrypt Files has undergone targeted performance optimizations to achieve peak single-core throughput and resolve encryption/decryption asymmetry:
742
+
743
+ - **Merkle Leaf Hash Optimization:** Omits ciphertext payload from Merkle tree leaf hashing (only hashing `file_id || chunk_index || chunk_plaintext_len || iv || tag` according to `LeafHashV1`), avoiding double-pass processing (AES-GCM + SHA-256) of full file contents.
744
+ - **Deterministic IV Derivation:** Eliminates system-call overhead by replacing `OsRng` in the encryption loop with a 44-byte HKDF expansion to derive both chunk subkeys and IVs deterministically.
745
+ - **Optional BLAKE3 Hashing Profile:** Supports swapping SHA-256 for BLAKE3 within the Merkle tree verification process, yielding massive speedups on systems without hardware-accelerated SHA-NI instructions.
746
+ - **WebAssembly 128-bit SIMD Acceleration:** Compiles the browser WebAssembly package with the `+simd128` target feature flag, allowing Rust cryptographic primitives to run with SIMD parallel hardware instructions directly inside modern browsers.
747
+ - **Architecture-Specific Speedups:** Set default compilation profile targeting `x86-64-v3`, allowing optional native overrides (`RUSTFLAGS="-C target-cpu=native"`) to fully unlock hardware acceleration (AVX2, AES-NI, SHA-NI).
748
+
749
+ ### Benchmark Results (AMD Ryzen 5 7500F @ 3.70 GHz)
750
+
751
+ #### Device Profile for Tests:
752
+ - **CPU:** AMD Ryzen 5 7500F @ 3.70 GHz (6 physical cores, 12 logical threads)
753
+ - **GPU:** NVIDIA GeForce GTX 1660 SUPER
754
+ - **Disk:** D:\ [HDD] (734.0 GB free / 931.5 GB total); C:\ [SSD] (27.1 GB free / 465.1 GB total)
755
+ - **RAM Utilized:** Min 34.9%, Max 53.9%, Avg 40.6%
756
+ - **CPU Utilized:** Min 6.0%, Max 76.0%, Avg 21.7%
757
+
758
+ #### Pipelined Performance Metrics Suite
759
+ | Metric | Balanced Profile (256MB) | Max Profile (1GB) | Detail |
760
+ | --- | --- | --- | --- |
761
+ | Throughput | 3.56 GB/s | 3.31 GB/s | Aggregate gigabytes per second |
762
+ | Cycles/Byte | 0.97 | 1.04 | CPU clock cycles per byte encrypted |
763
+ | Instructions/Byte | 1.21 | 1.30 | CPU instructions executed per byte |
764
+ | Allocations/Chunk | 0 | 0 | Number of heap allocations per chunk |
765
+ | Bytes Copied/Byte Encrypted | 1.0 | 1.0 | Total buffer copy amplification ratio |
766
+ | Worker Idle Time | 56.5% | 81.3% | Time workers spent waiting for queue |
767
+ | Queue Wait Time | 11.3% | 15.0% | Average time chunks spent in queue |
768
+ | I/O Wait Time | 45.2% | 65.1% | Average time spent in disk/stream I/O |
769
+ | Merkle Time / Total | 0.01% | 0.00% | Percentage of time spent in Merkle tree |
770
+ | HKDF Time / Total | 0.02% | 0.00% | Percentage of time spent in HKDF subkeys |
771
+ | AEAD Time / Total | 43.44% | 18.65% | Percentage of time spent in AEAD crypto |
772
+ | Energy Estimate | 21.05 J/GB | 22.65 J/GB | Estimated energy consumption per GB |
773
+ | Time to First Verified Plaintext | 0.172 ms | 1.668 ms | Latency to verify and decrypt chunk 0 |
774
+
775
+ #### Chunk Latency & Throughput (Single-Core)
776
+ | Operation | Input Size | Latency (median) | Latency (p99) | Throughput |
777
+ | --- | --- | --- | --- | --- |
778
+ | encrypt_chunk | 4 KB | 4.30 μs | 28.50 μs | 908.43 MB/s |
779
+ | decrypt_chunk | 4 KB | 3.60 μs | 4.40 μs | 1085.07 MB/s |
780
+ | encrypt_chunk | 64 KB | 36.90 μs | 66.80 μs | 1693.77 MB/s |
781
+ | decrypt_chunk | 64 KB | 37.10 μs | 56.60 μs | 1684.64 MB/s |
782
+ | encrypt_chunk | 1 MB | 691.60 μs | 778.90 μs | 1445.92 MB/s |
783
+ | decrypt_chunk | 1 MB | 675.50 μs | 723.30 μs | 1480.38 MB/s |
784
+ | encrypt_chunk | 4 MB | 2586.30 μs | 2970.20 μs | 1546.61 MB/s |
785
+ | decrypt_chunk | 4 MB | 2641.20 μs | 2651.90 μs | 1514.46 MB/s |
786
+ | encrypt_chunk | 16 MB | 10425.80 μs | 11724.60 μs | 1534.65 MB/s |
787
+ | decrypt_chunk | 16 MB | 10564.10 μs | 10630.60 μs | 1514.56 MB/s |
788
+
789
+ #### Competitor Comparison (1 GB Single-Threaded & Multi-Threaded)
790
+ All baseline timings measured dynamically on the same AMD Ryzen 5 7500F test system:
791
+ - **Vollcrypt File (Single-Core):** 0.72 s (measured)
792
+ - **Vollcrypt File (All-Cores):** 0.14 s (measured)
793
+ - **OpenSSL CLI Baseline:** 0.75 s (measured on device)
794
+ - **Age Baseline:** 1.57 s (measured on device)
795
+
796
+ ### Benchmark Results (Intel Core i5-12450H)
797
+
798
+ #### Device Profile for Tests:
799
+ - **CPU:** 12th Gen Intel(R) Core(TM) i5-12450H (8 physical cores, 12 logical threads)
800
+ - **GPU:** Intel Corporation Alder Lake-P GT1 [UHD Graphics]
801
+ - **Disk:** / [SSD]
802
+ - **RAM Utilized:** Min 27.5%, Max 70.8%, Avg 35.9%
803
+ - **CPU Utilized:** Min 5.4%, Max 66.0%, Avg 12.1%
804
+
805
+ #### Pipelined Performance Metrics Suite
806
+ | Metric | Balanced Profile (256MB, 1MB chunk) | Max Profile (1GB, 8MB chunk) | Detail |
807
+ | --- | --- | --- | --- |
808
+ | Throughput | 0.71 GB/s | 0.74 GB/s | Aggregate gigabytes per second |
809
+ | Cycles/Byte | 0.55 | 0.53 | CPU clock cycles per byte encrypted |
810
+ | Instructions/Byte | 0.69 | 0.66 | CPU instructions executed per byte |
811
+ | Allocations/Chunk | 0 | 0 | Number of heap allocations per chunk |
812
+ | Bytes Copied/Byte Encrypted | 1.0 | 1.0 | Total buffer copy amplification ratio |
813
+ | Cache Misses/GB | N/A | N/A | Modeled cache misses per gigabyte |
814
+ | Branch Misses/GB | N/A | N/A | Modeled branch mispredictions per gigabyte |
815
+ | Worker Idle Time | 86.4% | 92.8% | Time workers spent waiting for queue |
816
+ | Queue Wait Time | 15.0% | 15.0% | Average time chunks spent in queue |
817
+ | I/O Wait Time | 69.2% | 74.3% | Average time spent in disk/stream I/O |
818
+ | Merkle Time / Total | 0.20% | 0.01% | Percentage of time spent in Merkle tree |
819
+ | HKDF Time / Total | 0.57% | 0.04% | Percentage of time spent in HKDF subkeys |
820
+ | AEAD Time / Total | 12.99% | 7.14% | Percentage of time spent in AEAD crypto |
821
+ | Energy Estimate | 134.21 J/GB | 128.59 J/GB | Estimated energy consumption per GB |
822
+ | Time to First Verified Plaintext | 0.682 ms | 4.769 ms | Latency to verify and decrypt chunk 0 |
823
+
824
+ #### Chunk Latency & Throughput (Single-Core)
825
+ | Operation | Input Size | Latency (median) | Latency (p99) | Throughput |
826
+ | --- | --- | --- | --- | --- |
827
+ | `encrypt_chunk` | 4 KB | 72.37 μs | 90.15 μs | 53.98 MB/s |
828
+ | `decrypt_chunk` | 4 KB | 80.88 μs | 336.33 μs | 48.30 MB/s |
829
+ | `encrypt_chunk` | 64 KB | 182.53 μs | 209.93 μs | 342.41 MB/s |
830
+ | `decrypt_chunk` | 64 KB | 168.51 μs | 376.97 μs | 370.89 MB/s |
831
+ | `encrypt_chunk` | 1 MB | 1173.14 μs | 1754.10 μs | 852.42 MB/s |
832
+ | `decrypt_chunk` | 1 MB | 1171.14 μs | 1198.10 μs | 853.87 MB/s |
833
+ | `encrypt_chunk` | 4 MB | 4509.95 μs | 4652.09 μs | 886.93 MB/s |
834
+ | `decrypt_chunk` | 4 MB | 4650.11 μs | 4795.83 μs | 860.19 MB/s |
835
+ | `encrypt_chunk` | 16 MB | 17892.73 μs | 19608.63 μs | 894.22 MB/s |
836
+ | `decrypt_chunk` | 16 MB | 18179.62 μs | 21220.74 μs | 880.11 MB/s |
837
+
838
+ #### Competitor Comparison (1 GB Single-Threaded)
839
+ All baseline timings measured dynamically on the same Intel Core i5-12450H test system:
840
+ - **Vollcrypt File:** 1.21 s (measured)
841
+ - **OpenSSL Baseline:** 1.20 s (measured on device)
842
+ - **Age Baseline:** 2.51 s (measured on device)
843
+
844
+ ### Benchmark CLI
845
+
846
+ 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:
847
+
848
+ ```bash
849
+ # Run the full automated suite (generates markdown files under reports/)
850
+ cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --suite auto
851
+
852
+ # Profile specific configurations with JSON output
853
+ cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --profile balanced --json
854
+
855
+ # Profile max configuration and compare against local OpenSSL/Age baselines
856
+ cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --profile max --compare
857
+
858
+ # Sweep chunk sizes (from 4 KB to 16 MB)
859
+ cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --sweep chunk-size
860
+
861
+ # Sweep worker threads to evaluate parallel scaling
862
+ cargo run --release -p vollcrypt-files-bench --bin vollcrypt -- bench --sweep workers
863
+ ```
864
+
865
+ ### Test & Security Scorecard
866
+
867
+ The current test suite includes stress, fuzzing, tampering, replay, forgery-resistance, and safe-default policy tests.
868
+
869
+ - **Stress Tests:** `vollcrypt-files-stress` (16/16 pass)
870
+ - **Hardening Coverages:**
871
+ - **Bit-flip Resistance:** Flip every bit in ciphertext chunk (8448 flips tested, 0 decrypted/accepted).
872
+ - **Tag Forgery Resistance:** Random tag insertion (100000 tries, 0 accepted).
873
+ - **Header Tampering Matrix:** Tamper magic, version, file_id (27 fields, 27 rejected).
874
+ - **Replay Attack Resistance:** IV uniqueness & cross-file substitution (0 replayed).
875
+ - **Timing Side Channels:** Constant-time password unwrap check (Median delta: 0.0000 μs).
876
+ - **Manifest Authority:** Unauthorized signature injection (1 forgery, 0 accepted).
877
+ - **Signed Header Replay:** Replaying v2 signature on fake file (1 replay, 0 accepted).
878
+ - **Safe-Default Verification (Section I):**
879
+ - **default_fail_closed:** Assures fail-closed default policy on recipient/group modes.
880
+ - **mandatory_rollback_pin:** Enforces minimum epoch pinning.
881
+ - **mandatory_founder_anchor:** Rejects manifests with invalid founder anchors.
882
+ - **verified_no_release_on_failure:** Double-pass verified mode releases exactly 0 bytes on tampering.
883
+ - **kdf_error_propagates_no_zero_key:** No insecure fallback key usage.
884
+ - **chunk_index_overflow_cap:** Prevents u32 chunk index overflows.
885
+ - **Sovereign Sealing & Crypto-Shredding (Section J):**
886
+ - **sovereign_sealed_fail_closed:** Assures fail-closed behavior for sealed containers under standard decryption paths.
887
+ - **sealed_marker_tamper:** Detects stripping or altering the owner-signed sealed marker.
888
+ - **Shield Integrity Policy (Section K):**
889
+ - **shield_verified_fail_closed:** Ensures any bit-flip or out-of-order chunk releases exactly 0 plaintext bytes in verified release mode.
890
+ - **shield_timing_constant:** Verifies Merkle root cryptographic comparisons are side-channel timing-constant.
891
+ - **Linter:** 100% clean Clippy builds under `-- -D warnings` on all target formats.