Every archive is a database.
zx: versioned, searchable and verifiable archives, in pure Dart.
zx keeps every version of your files: updates are appended, earlier states stay readable by date, and identical data is stored once. Any file is found in microseconds by SHA-256 or by similarity, and a SQL database lives inside the archive next to the files. Versions can be signed with NOSTR keys, so anyone can verify who wrote them and that nothing changed since, and a README inside the archive describes its contents. Compression goes well beyond the usual formats when there is time for it: built on the context mixing models of paq8px and cmix, zcm level 9 makes a 6.8 MB mixed corpus 33% smaller than xz -9e and 7z PPMd (1.71 MB against 2.57 and 2.55 MB), with 39 to 40% saved on source code and executables and 43 to 46% on photographs and audio, at a few KB/s; level 1 runs at 1.1 MB/s. And it still reads and writes every 7z, zip, rar and tar you have. Pure Dart: a library, a command line and a file explorer for desktop and Android.
Overview
zx also reads and writes the established formats (7z, zip, RAR5, tar and the compressed tars, zpaq) and reads older RAR versions, disk images and firmware. Its LZMA, LZMA2 and PPMd coders are ported from the public domain LZMA SDK and produce output byte-identical to the reference implementation. The library is pure Dart, with no FFI and no native libraries, so the same code runs as the command line program, as a library in Dart and Flutter applications, and in the desktop and Android app.
The .zx format in detail:
- Generations. Updates are appended and earlier bytes are never rewritten, as in zpaq. Any earlier state can be read back by number or by date; content-defined deduplication stores identical chunks once across files and generations.
- An embedded database. SQL in the SQLite dialect, key-value stores and time series are stored in the archive itself, versioned with it.
- Content lookup. Each file carries its SHA-256 and a TLSH digest, so exact and approximate matches are index lookups.
- Context mixing compression (zcm), from the models of zpaq, paq8px and cmix, with deterministic arithmetic.
- Optional signed generations with NOSTR keys, checkable without the archive's password.
The test suite compares results against the reference tools wherever they exist: 7z, xz, zip and unzip, tar, gzip, bzip2, rar and unrar, arj, lhasa, zpaq, zpaqfranz, sqlite3, mksquashfs, xorriso, the mkfs tools and paq8px.
Install
zx consists of two programs: zx, the command line, and
zx-gui, the desktop application. On Android the application is
distributed as zx.apk. To inspect an archive without installing
anything, zx online runs the same library in the browser,
read only.
Download
Intel zx-macos-x64
Each file is a single executable with no runtime to install. Rename it to
zx (zx.exe on Windows) and place it on the PATH; on Linux and
macOS make it executable with chmod +x zx. The macOS builds are not
notarized: remove the quarantine flag with xattr -d com.apple.quarantine zx.
All builds: releases.
Other ways to install
| Target | Method |
|---|---|
| Any platform with the Dart SDK | dart pub global activate --source git https://github.com/x1watt/zx |
| Linux desktop | The Debian package (tool/build_deb.sh) or tool/install_linux.sh; both install zx and zx-gui. |
| Android, from source | tool/build_apk.sh builds zx.apk (--split: one APK per ABI). |
Command line
Usage: zx <command> [<switches>...] <archive> [<files>...].
zx without arguments prints every command and switch.
| Command | Function |
|---|---|
a, u | Add files; update changed and new files |
x, e | Extract with and without paths |
l, t | List; test integrity |
d, rn | Delete; rename inside the archive |
h, i, b | Hashes of files; supported formats; benchmark |
sql | SQL on the database of a .zx archive, or an interactive shell |
seal | Show, check and sign the seals of a .zx archive |
readme | Print or check the README of an archive or of a folder in it |
| Switch | Function |
|---|---|
-snest | List, test or extract through nested archives and disk images as one tree |
-mversion=N, -mversion=DATE | Read a .zx or zpaq archive as it was at a generation or date |
-mtimeline=PATH | Every version of one file, with dates |
-mcompact[=N] | Rewrite a .zx archive keeping the last N generations |
-m0=zcm:auto -mtime=T -mmem=M | The strongest zcm level that fits a time and memory budget |
-mvdir=DIR[:SIZE|:full] | Write volumes to several folders or disks in order |
-msign=KEY | Sign the new generation with a NOSTR key |
zx a -mx9 -psecret -mhe backup.7z ~/docs # 7z, LZMA2 level 9, encrypted file names zx x release.tar.xz -oout # tar.xz extracted in one pass zx a -v700m -mrr=5% big.rar data/ # RAR5 volumes with a recovery record zx a backup.zx ~/docs # first generation; later runs store changes only zx x backup.zx -mversion=2026-09-01 -oold # the archive as of a date zx x -snest -snld20 firmware.pak -oout # the root file system inside a firmware image zx sql backup.zx "SELECT path, size FROM zx_files ORDER BY size DESC LIMIT 10"
Every switch: docs/cli.md.
Formats
Formats are detected by content, not by file name.
| Access | Formats |
|---|---|
| Read and write | zx, 7z, zip and jar (ZipCrypto, WinZip AES, Zip64), RAR5, tar, tar.gz, tar.bz2, tar.xz, gzip, bzip2, xz, lzma, zpaq, ARJ, LHA |
| Read | RAR 1.5 to 7, ISO 9660, UDF, MBR, GPT, FAT, ext2/3/4, SquashFS, cramfs, JFFS2, UBI, UBIFS, cpio, U-Boot images, device trees, Reolink firmware, split volumes |
The .zx format
A .zx file is a header, a sequence of blocks, and an index with a
footer; each update appends further blocks, a new index and a new footer. Every
block names the codec chain that produced it, and the file states the oldest
version of zx that can decode it, so new codecs do not require a new format and an
older reader refuses a file it can not read instead of misreading it. The complete
specification is docs/zx-format.md.
- Generations
- Earlier bytes are never rewritten. A reader opens the last valid footer, so an
update interrupted by a crash leaves the previous generation intact. Any
generation can be listed or extracted;
-mcompactreclaims space. - Deduplication
- Content-defined chunks (64 KiB average) are stored once across files and generations. A second version of a source tree added to an archive of the first costs 0.31 MB instead of 4.76 MB; the chunk index of a 100 GiB archive needs 13 MB of memory.
- Parallel coding
- Blocks are coded by worker isolates within a memory budget, with output identical to the single-threaded coder: 2.9 times faster on 4 threads than on one (LZMA2 level 5, 40 MB of C sources in 2 MiB blocks).
- Content hashes
- SHA-256 per file in a sorted table, verified on extraction and test, and a TLSH digest per file for similarity search.
- Encryption
- AES-256-CTR with HMAC-SHA-256 per block (encrypt then MAC), keys from scrypt; file names and sizes are encrypted by default.
- Volumes
- Volumes of different sizes over several folders or disks, with budgets or until a disk is full; earlier volumes are not needed to append.
- READMEs
- A
README.mdin the archive, or in one of its folders, is shown by the app as the description of that folder. Images and linked files must be entries of the archive; links to external sites are permitted, fetching external resources is not (zx readme -checkreports violations).
Compression: zcm
zcm is a family of context mixing compressors built from the models of zpaq,
paq8px and cmix: data type detection per block (text, x86 code, images including
headerless raw ones, audio), an English dictionary transform, word, match, sparse,
image and audio models, two-layer mixers with SSE stages, PPMd and DMC predictors
gated by measured gain, and cmix's LSTM byte mixer at the top level. The
arithmetic is deterministic, so every machine decodes the same bytes.
-m0=zcm:auto selects the strongest level that fits a given time and
memory budget on the machine at hand.
| Compressor | Output bytes |
|---|---|
| 7z, PPMd | 384,433 |
| xz -9e | 381,240 |
| zpaq, method 5 | 357,160 |
| zcm level 1 | 366,552 |
| zcm level 3 | 350,935 |
| zcm level 5 | 341,206 |
| zcm level 9 | 331,324 |
| paq8px -8 (C++, 2.4 GB of memory) | 330,801 |
Input: a 700,986 byte corpus of text, source code, an x86 binary and a firmware slice. Method and further corpora: docs/performance.md.
| File (6,758,876 byte corpus) | xz -9e | 7z PPMd -mx9 | zcm level 9 | Smaller than xz |
|---|---|---|---|---|
| Book, English text | 223,640 | 180,645 | 148,830 | 33% |
| C++ source | 146,500 | 141,442 | 88,714 | 39% |
| x86-64 executable | 478,896 | 410,356 | 289,481 | 40% |
| Firmware | 558,976 | 609,164 | 527,057 | 6% |
| Photograph, PPM | 176,456 | 193,652 | 95,018 | 46% |
| Photograph, BMP | 241,872 | 264,029 | 136,644 | 44% |
| Grayscale image | 56,364 | 55,477 | 30,701 | 46% |
| Music, 16-bit stereo WAV | 668,704 | 679,210 | 379,683 | 43% |
| Voice, 8-bit WAV | 22,480 | 17,484 | 15,424 | 31% |
| Total | 2,573,888 | 2,551,459 | 1,711,552 | 33% |
Measured with zcm stream version 2 at 9 KB/s; the current version 3 was equal or smaller on the small corpus and has not been measured on this one yet. The corpus (a Project Gutenberg book, the paq8px sources, photographs, synthesized audio, a firmware slice) is described in docs/performance.md.
| Data type | zcm level 9 relative to paq8px -8 |
|---|---|
| English text | 0.7 to 5% smaller |
| Source code (C++, Dart) | 0.5 to 1% smaller |
| Grayscale image | 0.05% larger |
| Color photograph (PPM, BMP) | 0.45 to 3.45% larger |
| 16-bit stereo audio | 0.96% larger |
| x86 binary | 2.6% larger |
| Level | Speed (AOT) | Memory | Models |
|---|---|---|---|
| 1 | 1.2 MB/s | 40 MiB | Fastest level; smaller than xz -9e |
| 3 | 200 KB/s | 30 MiB | Smaller than zpaq method 5 |
| 5 | 85 KB/s | 35 MiB | Data type detection, paq8 models |
| 7 | 10 KB/s | 170 MiB | paq8px text, image and audio models |
| 9 | 4 to 6 KB/s | 400 MiB | All models, PPMd and DMC, gain gating |
| cmix | 2 to 3 KB/s | per budget | Level 9 with cmix's LSTM byte mixer |
Database: zxdb
A .zx archive can hold a database next to its files: SQL tables in
the SQLite dialect (cross-checked against sqlite3 by differential fuzzing),
key-value stores with expiry, partitioned time series and full-text search. Pages
are compressed with the archive's codecs. Each commit is a generation of the
archive, so every table can be read as of an earlier commit
(AS OF '2026-09-01'). Read-only system tables expose the archive's
files, generations and similarity search to SQL.
SELECT path, size FROM zx_files WHERE sha256 = x'9f86d081...';
SELECT path, distance FROM similar('photos/IMG_0042.jpg', 20);
SELECT * FROM zx_files AS OF '2026-09-01';
CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT) WITH (compression = 'max');
CREATE KV STORE settings WITH (ttl = '30d');
CREATE TIMESERIES logs (ts DATETIME, level TEXT, msg TEXT)
PARTITION BY DAY RETENTION '400d';
| Measurement | zxdb | sqlite3 |
|---|---|---|
| Point select, 100k rows | 4.8 us | 11.7 us |
| File size, 20k JSON records (2.4 MiB) | 158 KiB | 2,680 KiB |
| File size, 1M log lines | 17.8 MiB | 153.9 MiB |
| Key-value get, cached | 1.4 us | |
| Key-value put, random keys into a 1M-key tree | 161k/s | |
| SHA-256 lookup | 2 us | |
| 20 nearest files by TLSH among 1M | < 15 ms |
The sizes are at compression level max. SQL reference:
docs/zxdb-sql.md.
Signed generations
Whoever holds a file can modify it; signing can not prevent that, but it can make
any modification detectable. When sealing is enabled, each generation of a
.zx archive ends with a seal: a SHA-256 over the stored bytes of the
generation and over its index, chained to the seal of the previous generation and
signed with a NOSTR key (BIP-340 Schnorr over secp256k1). Because the hashes cover
the stored bytes, the seals of an encrypted archive can be verified without its
password.
The seal also carries the archive's roles: one admin and any number of maintainers, identified by their npubs and readable by anyone. The admin adds and removes maintainers, decides whether they may sign, and can transfer the admin role to a new key, which must countersign its acceptance. A reader replays the role history offline and checks each generation against the roles in force when it was written; generations signed by a maintainer before their removal remain valid.
zx a -msign=@admin.nsec book.zx chapters/ # new archive; the key becomes the admin zx seal -sign=@admin.nsec -add=npub1... book.zx # add a maintainer zx u -msign=@maintainer.nsec book.zx chapters/ # an update signed by the maintainer zx seal -full book.zx # verify every byte and every signature
A modified byte, a forged generation, a removed seal, a signature by a key that
may not sign and an unauthorized change of roles are all reported. Two limits are
inherent to any scheme of this kind: the verification establishes which npub
signed, not who controls it; and a truncated copy that ends at an earlier, validly
sealed generation can only be recognized against an independent record of the
latest one. Sealing is optional, can be enabled on an existing archive, and has no
effect on archives that do not use it. Keys are passed as a file or through the
ZX_NSEC environment variable, not on the command line.
| Cost | Measured |
|---|---|
| Write time, LZMA2 level 5, signed relative to unsigned | +0.4% |
| Write time, stored (no compression) | +4.4% |
| Opening and listing a sealed archive | unchanged |
| Quick check (indexes, signatures, roles) | 10 ms |
| Full check of a 41 MB archive | 0.33 s |
| One signature; one verification | 2.3 ms; 1.6 ms |
The block workers hash the payloads in parallel, so the cost appears only where the coder is faster than SHA-256. Specification: section 17 of docs/zx-format.md.
Desktop and Android application
zx-gui is a file manager built with Flutter on the same library. It
opens archives, nested archives, disk images and firmware as folders: copying out
extracts, pasting in adds. For .zx archives it lists the generations,
shows the archive's README, provides a view of the embedded database (tables,
key-value stores, time series, queries, file metadata, similarity search) and
reports the state of the seals. All archive work runs in background isolates.
On Linux it registers file associations and an "Extract to folder" entry for Nautilus and Thunar; on Android it supports the system's storage access, SD and USB volumes, opening from and sharing to other applications. Details: docs/app.md.
zx online is the same application in the browser, read only: the library compiled to WebAssembly runs in a worker. Local files are read in place and never uploaded. An archive given by URL is read with HTTP range requests when the server permits cross-origin access, so listing a large archive transfers its first and last blocks rather than the whole file; otherwise it can be downloaded into browser storage. Requires a browser with WebAssembly garbage collection (Chrome 119, Firefox 120, Safari 18.2).
Archives can be linked by pasting their address after the page's:
online/https://example.org/archive.zx, optionally followed by
#path=PATH to open a folder or select a file, and
#theme=green (or dark, light,
orange, eighties) to choose the theme. The Share link button produces
the link of the current view.
Credits and license
zx is distributed under the BSD 3-clause license. It is derived from, or follows, the work of Igor Pavlov (7-Zip and the LZMA SDK), Matt Mahoney (zpaq, paq8, lpaq), the paq8px authors, Byron Knoll (cmix), Dmitry Shkarin (PPMd), Jean-loup Gailly and Mark Adler (zlib), Julian Seward (bzip2), Tim Kientzle and the libarchive contributors, Nicholas Waples (rardecode), Simon Howard (lhasa), Franco Corbelli (zpaqfranz), Wei Dai (SHA-256), the TLSH and BLAKE2 authors, and Pieter Wuille, Jonas Nick and Tim Ruffing (BIP-340). Each source file names its origins. 7-Zip is a registered trademark of Igor Pavlov; zx is not affiliated with or endorsed by him.