Important links:
This specification should serve as a detailed description of the Nyvo archive format. It is currently incomplete and should therefore not be your single source of information about the format.
In the following, the Nyvo archive format will be called “Nyvo” for simplicity.
Current specification version: 1
It is recommended to use the one of the following file extensions for Nyvo:
.nyvo - This should be used most of the time for consistency purposes..n0 - Alternative extension, may not be recognized correctly by some OS or userspace applications..nyv - For compability only, as some software requires the file extension not to be longer than 3 bytes.Nyvo uses Little-endian (LE) byte order everywhere without exceptions.
Also, Nyvo makes extensive use of variable-length integers (unsigned LEB128 encoding), which are also used in the RAR 5.0 archive format and a wide range of other projects, including WebAssembly, LLVM and Android.
In the following (and the reference implementation), this kind of variable-length unsigned integers
is referred to as vu8. The theoretical limit of this encoding is infinite, but the reference implementation
cannot handle more bits than the target architecture supports (e.g. 64 bits for amd64/x86_64 or other 64-bit
architectures, 32 bits for i386/x86_32 and so on) due to internal memory adressing by the Rust programming language.
[!CAUTION]
Archives with variable integers larger than platform word size may cause unexpected errors.
As this encoding is widely used, it is considered trivial and will not be documented here.
| Section | Notes |
|---|---|
| Magic value | always the first 8 bytes |
| Archive metadata | |
| Encryption methods | |
| Store methods |
After these headers, one of the three headers may follow:
Parsing should end if EOF is reached. This allows append operations.
Always the first 8 bytes of the archive. This should be used to detect the Nyvo format itself when checking the archive type.
| Type | Name | Content | Notes |
|---|---|---|---|
u8[8] |
magic value | a8 28 4e 79 76 6f 28 a8 |
escaped string:\xa8\x28Nyvo\x28\xa8 |
| Type | Name | Content | Notes |
|---|---|---|---|
vu8 |
Format version | Spec version -1 | 0 for Nyvo v1 |
vu8 |
Encryption method count | Number of entries in the encryption methods header | |
vu8 |
Store method count | Number of entries in the store methods header |
This is an array of encryption methods with the length stored in the archive metadata header.
An entry of this header is structured in this format:
| Type | Name | Content | Notes |
|---|---|---|---|
vu8 |
Encryption algorithm | Encryption algorithm used for content encryption | |
vu8 |
Key derivation memory | Memory cost for Argon2id key derivation | |
vu8 |
Key derivation iterations | Iteration cost for Argon2id key derivation | |
vu8 |
Key derivation parallelism | Parallelism cost for Argon2id key derivation | |
u8[32] |
Salt | Key derivation salt | |
vu8 |
KEK count | Key encryption key count | |
| Key[] | Keys | Key encryption keys |
[!IMPORTANT]
Key derivation fields may not exceed 32 bits.
| ID | Name |
|---|---|
0 |
AES-256-GCM-SIV |
More will be added in the future. For now, only AES-256-GCM-SIV is recommended and supported.
| Type | Name | Content | Notes |
|---|---|---|---|
u8[12] |
Nonce | DEK cipher nonce | |
u8[48] |
DEK | Data encryption key cipher | 32-byte AES-256 key + 16-byte GCM-SIV tag |
The DEK will always be encrypted with AES-256-GCM-SIV, no matter what the “Encryption algorithm” fields says.
Content decryption steps:
This is an array of store methods with the length stored in the archive metadata header.
An entry of this header is structured in this format:
| Type | Name | Content | Notes |
|---|---|---|---|
vu8 |
Encryption method | ID of the encryption method to use + 1 | 0 for no encryption, 1 for encryption using the first method, … |
vu8 |
Compression algorithm | Compression algorithm used for content compression | 0 for no compression |
| ID | Name |
|---|---|
0 |
None (don’t compress) |
1 |
Zstandard |
More will be added in the future.
| Type | Name | Content | Notes |
|---|---|---|---|
bool |
Is index | true if the following content is an index |
Highest bit of byte 0 |
| Store option reference | Store option | ID of the store option used for following content | Lower 7 bits of byte 0 |
optional vu8 |
Store option ID | Custom store option ID | Only present if Store option = Custom (1) |
vu8 |
Length | Length of the following content in bytes | |
u8[16] |
Checksum | BLAKE3-256 Checksum of the next Length bytes |
7-bit enum
| Value | Name | Notes |
|---|---|---|
0 |
Default | Store option 0 if defined, else format default |
1 |
Custom | References a custom store option ID in a vu8 |
2 |
Copy | Reuses the store option the content before used. If not used before, Default (0). |
3 |
Increment | Increments the store option the content before used by 1. If not used before, Default (0). |
4 |
Decrement | Decrements the store option the content before used by 1. If not used before, Default (0). |
This header can be compressed and encrypted according to its corresponding store option.
Because file directories are structured like trees, the index header is structured like a tree as well. This makes a little harder to parse than other archive formats, because an entry only contains parts of its path to save space.
| Type | Name | Content | Notes |
|---|---|---|---|
bool |
This header can be compressed and encrypted according to its corresponding store option.
| Type | Name | Content | Notes |
|---|---|---|---|
u8[] |
Content | raw file contents |
// TODO: complete this spec