roxify 1.6.5 → 1.6.6
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 +352 -215
- package/dist/cli.js +31 -13
- package/dist/pack.d.ts +1 -0
- package/dist/pack.js +52 -3
- package/dist/utils/decoder.js +10 -21
- package/dist/utils/encoder.js +4 -6
- package/dist/utils/native.js +43 -26
- package/dist/utils/types.d.ts +2 -0
- package/dist/utils/zstd.d.ts +2 -2
- package/dist/utils/zstd.js +15 -6
- package/package.json +7 -6
- package/roxify_native-x86_64-unknown-linux-gnu.node +0 -0
- package/roxify_native.node +0 -0
- package/dist/_esmodule_test.d.ts +0 -1
- package/dist/_esmodule_test.js +0 -1
- package/dist/hybrid-compression.d.ts +0 -25
- package/dist/hybrid-compression.js +0 -90
- package/dist/minpng.d.ts +0 -20
- package/dist/minpng.js +0 -285
- package/dist/rox.exe +0 -0
- package/dist/roxify_native-x86_64-pc-windows-gnu.node +0 -0
- package/dist/roxify_native-x86_64-unknown-linux-gnu.node +0 -0
- package/dist/roxify_native.exe +0 -0
- package/dist/roxify_native.node +0 -0
- package/roxify_native-x86_64-pc-windows-msvc.node +0 -0
package/README.md
CHANGED
|
@@ -1,377 +1,514 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Roxify
|
|
2
2
|
|
|
3
|
-
> Encode binary data into PNG images and decode them back.
|
|
3
|
+
> Encode binary data into PNG images and decode them back, losslessly. Roxify combines native Rust acceleration, multi-threaded Zstd compression, and AES-256-GCM encryption into a single, portable Node.js module.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/roxify)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
---
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- 🚀 **Optimized Compression**: Multi-threaded Zstd compression (level 19) with parallel processing
|
|
12
|
-
- 🔒 **Secure**: AES-256-GCM encryption support with PBKDF2 key derivation
|
|
13
|
-
- 🎨 **Multiple modes**: Compact, chunk, pixel, and screenshot modes
|
|
14
|
-
- 📦 **CLI & API**: Use as command-line tool or JavaScript library
|
|
15
|
-
- 🔄 **Lossless**: Perfect roundtrip encoding/decoding
|
|
16
|
-
- 📖 **Full TSDoc**: Complete TypeScript documentation
|
|
17
|
-
- 🦀 **Rust Powered**: Optional native module for extreme performance (falls back to pure JS)
|
|
10
|
+
## Table of Contents
|
|
18
11
|
|
|
19
|
-
|
|
12
|
+
- [Overview](#overview)
|
|
13
|
+
- [Features](#features)
|
|
14
|
+
- [Benchmarks](#benchmarks)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [CLI Usage](#cli-usage)
|
|
17
|
+
- [JavaScript API](#javascript-api)
|
|
18
|
+
- [Encoding Modes](#encoding-modes)
|
|
19
|
+
- [Encryption](#encryption)
|
|
20
|
+
- [Performance Tuning](#performance-tuning)
|
|
21
|
+
- [Cross-Platform Support](#cross-platform-support)
|
|
22
|
+
- [Building from Source](#building-from-source)
|
|
23
|
+
- [Architecture](#architecture)
|
|
24
|
+
- [Error Handling](#error-handling)
|
|
25
|
+
- [Security Considerations](#security-considerations)
|
|
26
|
+
- [Contributing](#contributing)
|
|
27
|
+
- [License](#license)
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
---
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
## Overview
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
Roxify is a PNG steganography toolkit. It packs arbitrary binary data -- files, directories, or raw buffers -- into standard PNG images that can be shared, uploaded, and stored anywhere images are accepted. The data is compressed with multi-threaded Zstd, optionally encrypted with AES-256-GCM, and embedded in valid PNG structures that survive re-uploads and screenshots.
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
| -------- | ------: | -------: | ---------: | --------: | -----: | ---------: | ------------------------------------------- |
|
|
29
|
-
| 4,000 MB | 731,340 | 3.93 GB | 111.42 MB | **2.8%** | 26.9 s | 149.4 MB/s | gzip: 2.26 GB (57.5%); 7z: 1.87 GB (47.6%) |
|
|
30
|
-
| 1,000 MB | 141,522 | 1.03 GB | 205 MB | **19.4%** | ~6.2 s | ≈170 MB/s | shows benefits for many-small-file datasets |
|
|
35
|
+
The core compression and image-processing logic is written in Rust and exposed to Node.js through N-API. When the native module is unavailable, Roxify falls back to a pure TypeScript implementation transparently.
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
---
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
- Setup: parallel I/O and multithreaded compression on modern SSD-backed systems.
|
|
36
|
-
- Measurements: wall-clock time; throughput = original size / time; comparisons against gzip and 7z with typical defaults.
|
|
37
|
-
- Reproducibility: full benchmark details, commands and raw data are available in `docs/BENCHMARK_FINAL_REPORT.md`.
|
|
39
|
+
## Features
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
- **Native Rust acceleration** via N-API with automatic fallback to pure JavaScript
|
|
42
|
+
- **Multi-threaded Zstd compression** (level 19) with parallel chunk processing via Rayon
|
|
43
|
+
- **AES-256-GCM encryption** with PBKDF2 key derivation (100,000 iterations)
|
|
44
|
+
- **Lossless roundtrip** -- encoded data is recovered byte-for-byte
|
|
45
|
+
- **Directory packing** -- encode entire directory trees into a single PNG
|
|
46
|
+
- **Screenshot reconstitution** -- recover data from photographed or screenshotted PNGs
|
|
47
|
+
- **CLI and programmatic API** -- use from the terminal or import as a library
|
|
48
|
+
- **Cross-platform** -- prebuilt binaries for Linux x64, macOS x64/ARM64, and Windows x64
|
|
49
|
+
- **Full TypeScript support** with exported types and TSDoc annotations
|
|
50
|
+
- **mimalloc allocator** for reduced memory fragmentation under heavy workloads
|
|
40
51
|
|
|
41
|
-
|
|
52
|
+
---
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
- 📗 **[JavaScript SDK](./docs/JAVASCRIPT_SDK.md)** - Programmatic API reference with examples
|
|
45
|
-
- 📙 **[Quick Start](#quick-start)** - Get started in 2 minutes
|
|
54
|
+
## Benchmarks
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
All measurements were taken on Linux x64 with Node.js v20. Each tool was run with default settings (zip deflate, gzip via `tar czf`, 7z LZMA2 at level 5, Roxify in compact mode with Zstd level 19). Roxify produces a valid PNG file rather than a raw archive.
|
|
48
57
|
|
|
49
|
-
###
|
|
58
|
+
### Compression Ratio and Speed
|
|
50
59
|
|
|
51
|
-
|
|
60
|
+
| Dataset | Files | Original | zip | tar.gz | 7z | Roxify (PNG) |
|
|
61
|
+
|---|---:|---|---|---|---|---|
|
|
62
|
+
| Text files (1 MB) | 128 | 1.00 MB | 259 KB (25.3%) 52 ms | 196 KB (19.2%) 78 ms | 162 KB (15.8%) 347 ms | 203 KB (19.9%) 534 ms |
|
|
63
|
+
| JSON files (1 MB) | 1 183 | 1.00 MB | 700 KB (68.3%) 78 ms | 270 KB (26.4%) 43 ms | 212 KB (20.7%) 191 ms | 339 KB (33.0%) 766 ms |
|
|
64
|
+
| Binary (random, 1 MB) | 33 | 1.00 MB | 1.00 MB (100.5%) 29 ms | 1.00 MB (100.4%) 47 ms | 1.00 MB (100.1%) 128 ms | 1.00 MB (100.5%) 689 ms |
|
|
65
|
+
| Mixed (text+JSON+bin, 5 MB) | 2 241 | 5.00 MB | 3.26 MB (65.2%) 253 ms | 2.43 MB (48.7%) 228 ms | 2.27 MB (45.5%) 1.21 s | 2.59 MB (51.7%) 2.04 s |
|
|
66
|
+
| Text files (10 MB) | 1 285 | 10.00 MB | 2.54 MB (25.4%) 357 ms | 1.91 MB (19.1%) 657 ms | 1.53 MB (15.3%) 4.70 s | 1.98 MB (19.8%) 2.15 s |
|
|
67
|
+
| Mixed (text+JSON+bin, 10 MB) | 4 467 | 10.00 MB | 6.52 MB (65.2%) 461 ms | 4.86 MB (48.6%) 452 ms | 4.54 MB (45.4%) 2.51 s | 5.16 MB (51.6%) 3.77 s |
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
npx rox encode input.zip output.png
|
|
55
|
-
npx rox decode output.png original.zip
|
|
56
|
-
```
|
|
69
|
+
### Key Observations
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
- **On compressible data** (text, JSON), Roxify achieves ratios comparable to tar.gz (19--33%) while producing a standard PNG image instead of an archive file. The output can be shared on platforms that only accept images.
|
|
72
|
+
- **On incompressible data** (random bytes), all tools converge to approximately 100% as expected. No compression algorithm can shrink truly random data.
|
|
73
|
+
- **7z (LZMA2)** achieves the best ratios overall but is significantly slower. Roxify finishes faster than 7z on the 10 MB text dataset (2.15 s vs 4.70 s).
|
|
74
|
+
- **zip** is the fastest tool but offers the weakest compression, especially on many small files (68.3% on JSON vs Roxify's 33.0%).
|
|
75
|
+
- The overhead of PNG framing and Zstd's higher compression level adds latency on small datasets. On larger inputs, Roxify's multi-threaded pipeline narrows the gap.
|
|
59
76
|
|
|
60
|
-
|
|
61
|
-
npm install roxify
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## CLI Usage
|
|
77
|
+
### Methodology
|
|
65
78
|
|
|
66
|
-
|
|
79
|
+
Benchmarks were generated using `test/benchmark.mjs`. Datasets consist of procedurally generated text, JSON, and random binary files. Each tool was invoked via its standard CLI with default or documented settings:
|
|
67
80
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
| Tool | Command |
|
|
82
|
+
|---|---|
|
|
83
|
+
| zip | `zip -r -q` |
|
|
84
|
+
| tar.gz | `tar czf` |
|
|
85
|
+
| 7z | `7z a -mx=5` |
|
|
86
|
+
| Roxify | `rox encode <dir> <output.png> -m compact` |
|
|
71
87
|
|
|
72
|
-
|
|
73
|
-
npx rox decode document.png document.pdf
|
|
88
|
+
To reproduce:
|
|
74
89
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
npx rox decode secret.png secret.zip -p mypassword
|
|
90
|
+
```bash
|
|
91
|
+
node test/benchmark.mjs
|
|
78
92
|
```
|
|
79
93
|
|
|
80
|
-
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Installation
|
|
81
97
|
|
|
82
|
-
|
|
98
|
+
### As a CLI tool (no installation required)
|
|
83
99
|
|
|
84
100
|
```bash
|
|
85
|
-
|
|
101
|
+
npx rox encode input.zip output.png
|
|
102
|
+
npx rox decode output.png original.zip
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
|
|
105
|
+
### As a library
|
|
89
106
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
- `0` = fastest, largest
|
|
94
|
-
- `11` = slowest, smallest
|
|
95
|
-
- `-e, --encrypt <type>` - Encryption: `auto|aes|xor|none` (default: `aes` if passphrase)
|
|
96
|
-
- `--no-compress` - Disable compression
|
|
97
|
-
- `-o, --output <path>` - Output file path
|
|
107
|
+
```bash
|
|
108
|
+
npm install roxify
|
|
109
|
+
```
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
### Global installation
|
|
100
112
|
|
|
101
113
|
```bash
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# Fast compression for large files
|
|
106
|
-
npx rox encode large-video.mp4 output.png -q 0
|
|
114
|
+
npm install -g roxify
|
|
115
|
+
rox encode input.zip output.png
|
|
116
|
+
```
|
|
107
117
|
|
|
108
|
-
|
|
109
|
-
npx rox encode config.json output.png -q 11
|
|
118
|
+
---
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
npx rox encode secret.pdf secure.png -p "my secure password"
|
|
120
|
+
## CLI Usage
|
|
113
121
|
|
|
114
|
-
|
|
115
|
-
npx rox encode data.bin tiny.png -m compact
|
|
122
|
+
### Encoding
|
|
116
123
|
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
```bash
|
|
125
|
+
rox encode <input> [output] [options]
|
|
119
126
|
```
|
|
120
127
|
|
|
121
|
-
|
|
128
|
+
| Option | Description | Default |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `-p, --passphrase <pass>` | Encrypt with AES-256-GCM | none |
|
|
131
|
+
| `-m, --mode <mode>` | Encoding mode: `screenshot`, `compact` | `screenshot` |
|
|
132
|
+
| `-q, --quality <0-11>` | Compression effort (0 = fastest, 11 = smallest) | `1` |
|
|
133
|
+
| `-e, --encrypt <type>` | Encryption method: `auto`, `aes`, `xor`, `none` | `aes` if passphrase is set |
|
|
134
|
+
| `--no-compress` | Disable compression entirely | false |
|
|
135
|
+
| `-o, --output <path>` | Explicit output file path | auto-generated |
|
|
136
|
+
|
|
137
|
+
### Decoding
|
|
122
138
|
|
|
123
139
|
```bash
|
|
124
|
-
|
|
140
|
+
rox decode <input> [output] [options]
|
|
125
141
|
```
|
|
126
142
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
| Option | Description | Default |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `-p, --passphrase <pass>` | Decryption passphrase | none |
|
|
146
|
+
| `-o, --output <path>` | Output file path | auto-detected from metadata |
|
|
147
|
+
| `--dict <file>` | Zstd dictionary for improved decompression | none |
|
|
131
148
|
|
|
132
|
-
|
|
149
|
+
### Examples
|
|
133
150
|
|
|
134
151
|
```bash
|
|
135
|
-
#
|
|
136
|
-
|
|
152
|
+
# Encode a single file
|
|
153
|
+
rox encode document.pdf document.png
|
|
137
154
|
|
|
138
|
-
#
|
|
139
|
-
|
|
155
|
+
# Encode with encryption
|
|
156
|
+
rox encode secret.zip secret.png -p "strong passphrase"
|
|
140
157
|
|
|
141
|
-
#
|
|
142
|
-
|
|
158
|
+
# Decode back to original
|
|
159
|
+
rox decode secret.png secret.zip -p "strong passphrase"
|
|
160
|
+
|
|
161
|
+
# Fast compression for large files
|
|
162
|
+
rox encode video.mp4 output.png -q 0
|
|
163
|
+
|
|
164
|
+
# Best compression for small files
|
|
165
|
+
rox encode config.json output.png -q 11 -m compact
|
|
166
|
+
|
|
167
|
+
# Encode an entire directory
|
|
168
|
+
rox encode ./my-project project.png
|
|
143
169
|
```
|
|
144
170
|
|
|
171
|
+
---
|
|
172
|
+
|
|
145
173
|
## JavaScript API
|
|
146
174
|
|
|
147
|
-
### Basic
|
|
175
|
+
### Basic Encode and Decode
|
|
148
176
|
|
|
149
177
|
```typescript
|
|
150
178
|
import { encodeBinaryToPng, decodePngToBinary } from 'roxify';
|
|
151
179
|
import { readFileSync, writeFileSync } from 'fs';
|
|
152
180
|
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
});
|
|
158
|
-
writeFileSync('output.png', png);
|
|
181
|
+
// Encode
|
|
182
|
+
const input = readFileSync('document.pdf');
|
|
183
|
+
const png = await encodeBinaryToPng(input, { name: 'document.pdf' });
|
|
184
|
+
writeFileSync('document.png', png);
|
|
159
185
|
|
|
160
|
-
|
|
186
|
+
// Decode
|
|
187
|
+
const encoded = readFileSync('document.png');
|
|
161
188
|
const result = await decodePngToBinary(encoded);
|
|
162
189
|
writeFileSync(result.meta?.name || 'output.bin', result.buf);
|
|
163
190
|
```
|
|
164
191
|
|
|
165
|
-
###
|
|
192
|
+
### Encrypted Roundtrip
|
|
166
193
|
|
|
167
194
|
```typescript
|
|
168
195
|
const png = await encodeBinaryToPng(input, {
|
|
169
|
-
|
|
170
|
-
passphrase: 'my-secret-password',
|
|
196
|
+
passphrase: 'my-secret',
|
|
171
197
|
encrypt: 'aes',
|
|
172
|
-
name: '
|
|
198
|
+
name: 'confidential.pdf',
|
|
173
199
|
});
|
|
174
200
|
|
|
175
|
-
const result = await decodePngToBinary(
|
|
176
|
-
passphrase: 'my-secret
|
|
201
|
+
const result = await decodePngToBinary(png, {
|
|
202
|
+
passphrase: 'my-secret',
|
|
177
203
|
});
|
|
178
204
|
```
|
|
179
205
|
|
|
180
|
-
###
|
|
206
|
+
### Directory Packing
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { packPaths, unpackBuffer } from 'roxify';
|
|
210
|
+
|
|
211
|
+
// Pack files into a buffer
|
|
212
|
+
const { buf, list } = packPaths(['./src', './README.md'], process.cwd());
|
|
213
|
+
|
|
214
|
+
// Encode the packed buffer into a PNG
|
|
215
|
+
const png = await encodeBinaryToPng(buf, { name: 'project.tar' });
|
|
216
|
+
|
|
217
|
+
// Later: decode and unpack
|
|
218
|
+
const decoded = await decodePngToBinary(png);
|
|
219
|
+
const unpacked = unpackBuffer(decoded.buf);
|
|
220
|
+
for (const file of unpacked.files) {
|
|
221
|
+
console.log(file.name, file.buf.length);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Progress Reporting
|
|
181
226
|
|
|
182
227
|
```typescript
|
|
183
228
|
const png = await encodeBinaryToPng(largeBuffer, {
|
|
184
|
-
mode: 'screenshot',
|
|
185
|
-
brQuality: 0,
|
|
186
229
|
name: 'large-file.bin',
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
mode: 'compact',
|
|
191
|
-
brQuality: 11,
|
|
192
|
-
name: 'config.json',
|
|
230
|
+
onProgress: ({ phase, loaded, total }) => {
|
|
231
|
+
console.log(`${phase}: ${loaded}/${total}`);
|
|
232
|
+
},
|
|
193
233
|
});
|
|
194
234
|
```
|
|
195
235
|
|
|
196
|
-
###
|
|
236
|
+
### EncodeOptions
|
|
197
237
|
|
|
198
|
-
|
|
238
|
+
```typescript
|
|
239
|
+
interface EncodeOptions {
|
|
240
|
+
compression?: 'zstd'; // Compression algorithm
|
|
241
|
+
compressionLevel?: number; // Zstd compression level (0-19)
|
|
242
|
+
passphrase?: string; // Encryption passphrase
|
|
243
|
+
dict?: Buffer; // Zstd dictionary for improved ratios
|
|
244
|
+
name?: string; // Original filename stored in metadata
|
|
245
|
+
mode?: 'screenshot'; // Encoding mode
|
|
246
|
+
encrypt?: 'auto' | 'aes' | 'xor' | 'none';
|
|
247
|
+
output?: 'auto' | 'png' | 'rox'; // Output format
|
|
248
|
+
includeName?: boolean; // Include filename in PNG metadata
|
|
249
|
+
includeFileList?: boolean; // Include file manifest in PNG
|
|
250
|
+
fileList?: Array<string | { name: string; size: number }>;
|
|
251
|
+
skipOptimization?: boolean; // Skip PNG optimization pass
|
|
252
|
+
onProgress?: (info: ProgressInfo) => void;
|
|
253
|
+
showProgress?: boolean;
|
|
254
|
+
verbose?: boolean;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
199
257
|
|
|
200
|
-
|
|
258
|
+
### DecodeOptions
|
|
201
259
|
|
|
202
260
|
```typescript
|
|
203
|
-
|
|
261
|
+
interface DecodeOptions {
|
|
262
|
+
passphrase?: string; // Decryption passphrase
|
|
263
|
+
outPath?: string; // Output directory for unpacked files
|
|
264
|
+
files?: string[]; // Extract only specific files from archive
|
|
265
|
+
onProgress?: (info: ProgressInfo) => void;
|
|
266
|
+
showProgress?: boolean;
|
|
267
|
+
verbose?: boolean;
|
|
268
|
+
}
|
|
204
269
|
```
|
|
205
270
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
Minimal 1x1 PNG with data in custom chunk. Fastest and smallest.
|
|
271
|
+
### DecodeResult
|
|
209
272
|
|
|
210
273
|
```typescript
|
|
211
|
-
|
|
274
|
+
interface DecodeResult {
|
|
275
|
+
buf?: Buffer; // Decoded binary payload
|
|
276
|
+
meta?: { name?: string }; // Metadata (original filename)
|
|
277
|
+
files?: PackedFile[]; // Unpacked directory entries, if applicable
|
|
278
|
+
}
|
|
212
279
|
```
|
|
213
280
|
|
|
214
|
-
|
|
281
|
+
---
|
|
215
282
|
|
|
216
|
-
|
|
283
|
+
## Encoding Modes
|
|
217
284
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
285
|
+
| Mode | Description | Use Case |
|
|
286
|
+
|---|---|---|
|
|
287
|
+
| `screenshot` | Encodes data as RGB pixels in a standard PNG. The image looks like a gradient or noise pattern and survives re-uploads and social media processing. | Sharing on image-only platforms, bypassing file-type filters |
|
|
288
|
+
| `compact` | Minimal 1x1 PNG with data embedded in a custom ancillary chunk (`rXDT`). Produces the smallest possible output. | Programmatic use, archival, maximum compression ratio |
|
|
221
289
|
|
|
222
|
-
|
|
290
|
+
---
|
|
223
291
|
|
|
224
|
-
|
|
292
|
+
## Encryption
|
|
225
293
|
|
|
226
|
-
|
|
227
|
-
const png = await encodeBinaryToPng(data, { mode: 'chunk' });
|
|
228
|
-
```
|
|
294
|
+
Roxify supports two encryption methods:
|
|
229
295
|
|
|
230
|
-
|
|
296
|
+
| Method | Algorithm | Strength | Use Case |
|
|
297
|
+
|---|---|---|---|
|
|
298
|
+
| `aes` | AES-256-GCM with PBKDF2 (100,000 iterations) | Cryptographically secure, authenticated | Sensitive data, confidential documents |
|
|
299
|
+
| `xor` | XOR cipher with passphrase-derived key | Obfuscation only, not cryptographically secure | Casual deterrent against inspection |
|
|
231
300
|
|
|
232
|
-
|
|
301
|
+
When `encrypt` is set to `auto` (the default when a passphrase is provided), AES is selected.
|
|
233
302
|
|
|
234
|
-
|
|
303
|
+
---
|
|
235
304
|
|
|
236
|
-
|
|
305
|
+
## Performance Tuning
|
|
237
306
|
|
|
238
|
-
|
|
239
|
-
- `options?: EncodeOptions` - Encoding options
|
|
307
|
+
### Compression Level
|
|
240
308
|
|
|
241
|
-
|
|
309
|
+
The `compressionLevel` option (CLI: `-q`) controls the trade-off between speed and output size:
|
|
242
310
|
|
|
243
|
-
|
|
311
|
+
| Level | Speed | Ratio | Recommendation |
|
|
312
|
+
|---|---|---|---|
|
|
313
|
+
| 0 | Fastest | Largest | Files over 100 MB, real-time workflows |
|
|
314
|
+
| 1 | Fast | Good | Default; general-purpose use |
|
|
315
|
+
| 5 | Moderate | Better | Archival of medium-sized datasets |
|
|
316
|
+
| 11 | Slowest | Smallest | Small files under 1 MB, long-term storage |
|
|
244
317
|
|
|
245
|
-
|
|
246
|
-
interface EncodeOptions {
|
|
247
|
-
compression?: 'br' | 'none';
|
|
318
|
+
### Native Module
|
|
248
319
|
|
|
249
|
-
|
|
320
|
+
The Rust native module provides 10--50x throughput improvement over the pure JavaScript fallback. It is loaded automatically when present. To verify availability:
|
|
250
321
|
|
|
251
|
-
|
|
322
|
+
```typescript
|
|
323
|
+
import { native } from 'roxify';
|
|
324
|
+
console.log('Native module loaded:', !!native);
|
|
325
|
+
```
|
|
252
326
|
|
|
253
|
-
|
|
327
|
+
If the native module is not found for the current platform, Roxify falls back to TypeScript transparently. No code changes are needed.
|
|
254
328
|
|
|
255
|
-
|
|
329
|
+
### Zstd Dictionary
|
|
256
330
|
|
|
257
|
-
|
|
331
|
+
For datasets consisting of many similar small files (e.g., JSON API responses, log entries), a Zstd dictionary can improve compression ratios by 20--40%:
|
|
258
332
|
|
|
259
|
-
|
|
333
|
+
```typescript
|
|
334
|
+
import { readFileSync } from 'fs';
|
|
260
335
|
|
|
261
|
-
|
|
262
|
-
}
|
|
336
|
+
const dict = readFileSync('my-dictionary.zdict');
|
|
337
|
+
const png = await encodeBinaryToPng(data, { dict });
|
|
263
338
|
```
|
|
264
339
|
|
|
265
|
-
|
|
340
|
+
---
|
|
266
341
|
|
|
267
|
-
|
|
342
|
+
## Cross-Platform Support
|
|
268
343
|
|
|
269
|
-
|
|
344
|
+
Roxify ships prebuilt native modules for the following targets:
|
|
270
345
|
|
|
271
|
-
|
|
272
|
-
|
|
346
|
+
| Platform | Architecture | Binary Name |
|
|
347
|
+
|---|---|---|
|
|
348
|
+
| Linux | x86_64 | `libroxify_native-x86_64-unknown-linux-gnu.node` |
|
|
349
|
+
| macOS | x86_64 | `libroxify_native-x86_64-apple-darwin.node` |
|
|
350
|
+
| macOS | ARM64 (Apple Silicon) | `libroxify_native-aarch64-apple-darwin.node` |
|
|
351
|
+
| Windows | x86_64 | `roxify_native-x86_64-pc-windows-msvc.node` |
|
|
273
352
|
|
|
274
|
-
|
|
353
|
+
The correct binary is resolved automatically at runtime. If no binary is found for the current platform, Roxify falls back silently to the pure JavaScript implementation.
|
|
275
354
|
|
|
276
|
-
|
|
355
|
+
### Building Native Modules for Specific Targets
|
|
277
356
|
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
357
|
+
```bash
|
|
358
|
+
# Current platform
|
|
359
|
+
npm run build:native
|
|
360
|
+
|
|
361
|
+
# Specific platform
|
|
362
|
+
npm run build:native:linux
|
|
363
|
+
npm run build:native:macos-x64
|
|
364
|
+
npm run build:native:macos-arm
|
|
365
|
+
npm run build:native:windows
|
|
366
|
+
|
|
367
|
+
# All configured targets
|
|
368
|
+
npm run build:native:targets
|
|
282
369
|
```
|
|
283
370
|
|
|
284
|
-
|
|
371
|
+
---
|
|
285
372
|
|
|
286
|
-
|
|
287
|
-
interface DecodeResult {
|
|
288
|
-
buf: Buffer;
|
|
373
|
+
## Building from Source
|
|
289
374
|
|
|
290
|
-
|
|
291
|
-
name?: string;
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
```
|
|
375
|
+
### Prerequisites
|
|
295
376
|
|
|
296
|
-
|
|
377
|
+
- Node.js 18 or later
|
|
378
|
+
- Rust 1.70 or later (install via [rustup](https://rustup.rs))
|
|
297
379
|
|
|
298
|
-
###
|
|
380
|
+
### Commands
|
|
299
381
|
|
|
300
382
|
```bash
|
|
301
|
-
#
|
|
302
|
-
|
|
303
|
-
```
|
|
383
|
+
# Install dependencies
|
|
384
|
+
npm install
|
|
304
385
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
386
|
+
# Build TypeScript only
|
|
387
|
+
npm run build
|
|
388
|
+
|
|
389
|
+
# Build native Rust module
|
|
390
|
+
npm run build:native
|
|
391
|
+
|
|
392
|
+
# Build everything (Rust + TypeScript + CLI binary)
|
|
393
|
+
npm run build:all
|
|
394
|
+
|
|
395
|
+
# Run the full test suite
|
|
396
|
+
npm test
|
|
310
397
|
```
|
|
311
398
|
|
|
312
|
-
###
|
|
399
|
+
### Project Structure
|
|
313
400
|
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
|
|
401
|
+
```
|
|
402
|
+
roxify/
|
|
403
|
+
native/ Rust source code (N-API module and CLI binary)
|
|
404
|
+
src/ TypeScript source code (library and CLI entry point)
|
|
405
|
+
dist/ Compiled JavaScript output
|
|
406
|
+
test/ Test suite and benchmarks
|
|
407
|
+
docs/ Additional documentation
|
|
408
|
+
scripts/ Build, release, and CI helper scripts
|
|
317
409
|
```
|
|
318
410
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Architecture
|
|
414
|
+
|
|
415
|
+
Roxify is a hybrid Rust and TypeScript module. The performance-critical paths -- compression, CRC computation, pixel scanning, encryption -- are implemented in Rust and exposed through N-API bindings. The TypeScript layer handles PNG construction, CLI argument parsing, and high-level orchestration.
|
|
416
|
+
|
|
417
|
+
### Compression Pipeline
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
Input --> Zstd Compress (multi-threaded, Rayon) --> AES-256-GCM Encrypt (optional) --> PNG Encode --> Output
|
|
324
421
|
```
|
|
325
422
|
|
|
326
|
-
###
|
|
423
|
+
### Decompression Pipeline
|
|
327
424
|
|
|
328
|
-
|
|
425
|
+
```
|
|
426
|
+
Input --> PNG Parse --> AES-256-GCM Decrypt (optional) --> Zstd Decompress --> Output
|
|
427
|
+
```
|
|
329
428
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
429
|
+
### Rust Modules
|
|
430
|
+
|
|
431
|
+
| Module | Responsibility |
|
|
432
|
+
|---|---|
|
|
433
|
+
| `core.rs` | Pixel scanning, CRC32, Adler32, delta coding, Zstd compress/decompress |
|
|
434
|
+
| `encoder.rs` | PNG payload encoding with marker pixels and metadata chunks |
|
|
435
|
+
| `packer.rs` | Directory tree serialization and streaming deserialization |
|
|
436
|
+
| `crypto.rs` | AES-256-GCM encryption and PBKDF2 key derivation |
|
|
437
|
+
| `archive.rs` | Tar-based archiving with optional Zstd compression |
|
|
438
|
+
| `reconstitution.rs` | Screenshot detection and automatic crop to recover encoded data |
|
|
439
|
+
| `bwt.rs` | Parallel Burrows-Wheeler Transform |
|
|
440
|
+
| `rans.rs` | rANS (Asymmetric Numeral Systems) entropy coder |
|
|
441
|
+
| `hybrid.rs` | Block-based orchestration of BWT, context mixing, and rANS |
|
|
442
|
+
| `pool.rs` | Buffer pooling and zero-copy memory management |
|
|
443
|
+
| `image_utils.rs` | Image resizing, pixel format conversion, metadata extraction |
|
|
444
|
+
| `png_utils.rs` | Low-level PNG chunk read/write operations |
|
|
445
|
+
| `progress.rs` | Progress tracking for long-running compression/decompression |
|
|
446
|
+
|
|
447
|
+
---
|
|
334
448
|
|
|
335
449
|
## Error Handling
|
|
336
450
|
|
|
451
|
+
Roxify throws descriptive errors for common failure modes:
|
|
452
|
+
|
|
337
453
|
```typescript
|
|
454
|
+
import { decodePngToBinary } from 'roxify';
|
|
455
|
+
|
|
338
456
|
try {
|
|
339
|
-
const result = await decodePngToBinary(
|
|
457
|
+
const result = await decodePngToBinary(pngBuffer, {
|
|
340
458
|
passphrase: 'wrong-password',
|
|
341
459
|
});
|
|
342
460
|
} catch (err) {
|
|
343
461
|
if (err.message.includes('Incorrect passphrase')) {
|
|
344
|
-
|
|
345
|
-
} else if (err.message.includes('
|
|
346
|
-
|
|
347
|
-
} else {
|
|
348
|
-
|
|
462
|
+
// Wrong decryption key
|
|
463
|
+
} else if (err.message.includes('not a valid PNG')) {
|
|
464
|
+
// Input is not a valid roxified PNG
|
|
465
|
+
} else if (err.message.includes('corrupted')) {
|
|
466
|
+
// Data integrity check failed
|
|
349
467
|
}
|
|
350
468
|
}
|
|
351
469
|
```
|
|
352
470
|
|
|
353
|
-
|
|
471
|
+
| Error | Cause |
|
|
472
|
+
|---|---|
|
|
473
|
+
| `Incorrect passphrase` | Wrong password provided for decryption |
|
|
474
|
+
| `not a valid PNG` | Input buffer is not a PNG or lacks Roxify markers |
|
|
475
|
+
| `Passphrase required` | File is encrypted but no passphrase was supplied |
|
|
476
|
+
| `Image too large to decode` | PNG dimensions exceed the in-process memory limit |
|
|
354
477
|
|
|
355
|
-
|
|
356
|
-
- **XOR cipher**: Simple obfuscation (not cryptographically secure)
|
|
357
|
-
- **No encryption**: Data is compressed but not encrypted
|
|
478
|
+
---
|
|
358
479
|
|
|
359
|
-
|
|
480
|
+
## Security Considerations
|
|
360
481
|
|
|
361
|
-
|
|
482
|
+
- **AES-256-GCM** provides authenticated encryption. Tampered ciphertext is detected and rejected.
|
|
483
|
+
- **PBKDF2** with 100,000 iterations is used for key derivation, making brute-force attacks computationally expensive.
|
|
484
|
+
- **XOR encryption** is not cryptographically secure. Use it only for casual obfuscation.
|
|
485
|
+
- Passphrases are never stored in the output file. There is no recovery mechanism for a lost passphrase.
|
|
486
|
+
- The PNG output does not visually reveal whether data is encrypted. An observer cannot distinguish an encrypted Roxify PNG from an unencrypted one by inspection.
|
|
362
487
|
|
|
363
|
-
|
|
488
|
+
---
|
|
364
489
|
|
|
365
490
|
## Contributing
|
|
366
491
|
|
|
367
|
-
Contributions welcome
|
|
492
|
+
Contributions are welcome. Please open an issue to discuss proposed changes before submitting a pull request.
|
|
368
493
|
|
|
369
|
-
|
|
494
|
+
1. Fork the repository
|
|
495
|
+
2. Create a feature branch (`git checkout -b feature/my-change`)
|
|
496
|
+
3. Run the test suite (`npm test`)
|
|
497
|
+
4. Submit a pull request
|
|
370
498
|
|
|
371
|
-
|
|
372
|
-
- [npm Package](https://www.npmjs.com/package/roxify)
|
|
373
|
-
- [Report Issues](https://github.com/RoxasYTB/roxify/issues)
|
|
499
|
+
---
|
|
374
500
|
|
|
375
|
-
##
|
|
501
|
+
## License
|
|
376
502
|
|
|
377
|
-
|
|
503
|
+
MIT. See [LICENSE](LICENSE) for details.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Links
|
|
508
|
+
|
|
509
|
+
- [npm Package](https://www.npmjs.com/package/roxify)
|
|
510
|
+
- [GitHub Repository](https://github.com/RoxasYTB/roxify)
|
|
511
|
+
- [Issue Tracker](https://github.com/RoxasYTB/roxify/issues)
|
|
512
|
+
- [CLI Documentation](./docs/CLI.md)
|
|
513
|
+
- [JavaScript SDK Reference](./docs/JAVASCRIPT_SDK.md)
|
|
514
|
+
- [Cross-Platform Build Guide](./docs/CROSS_PLATFORM.md)
|