neozipkit 1.0.5 → 1.5.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/NEOZIP_APPNOTE.md +116 -18
- package/README.md +38 -8
- package/WHATS_NEW.md +115 -0
- package/dist/browser/zstd-browser-shim.d.ts +17 -9
- package/dist/browser/zstd-browser-shim.d.ts.map +1 -1
- package/dist/browser/zstd-browser-shim.js +99 -21
- package/dist/browser/zstd-browser-shim.js.map +1 -1
- package/dist/browser-esm/index.js +6178 -1261
- package/dist/browser-esm/index.js.map +4 -4
- package/dist/browser-umd/index.js +6178 -1261
- package/dist/browser-umd/index.js.map +4 -4
- package/dist/browser-umd/index.min.js +6 -3
- package/dist/core/ZipCompress.d.ts +6 -0
- package/dist/core/ZipCompress.d.ts.map +1 -1
- package/dist/core/ZipCompress.js.map +1 -1
- package/dist/core/ZipCopy.d.ts.map +1 -1
- package/dist/core/ZipCopy.js +33 -25
- package/dist/core/ZipCopy.js.map +1 -1
- package/dist/core/ZipEntry.d.ts +18 -0
- package/dist/core/ZipEntry.d.ts.map +1 -1
- package/dist/core/ZipEntry.js +139 -13
- package/dist/core/ZipEntry.js.map +1 -1
- package/dist/core/Zipkit.d.ts.map +1 -1
- package/dist/core/Zipkit.js +26 -19
- package/dist/core/Zipkit.js.map +1 -1
- package/dist/core/constants/Headers.d.ts +1 -0
- package/dist/core/constants/Headers.d.ts.map +1 -1
- package/dist/core/constants/Headers.js +1 -0
- package/dist/core/constants/Headers.js.map +1 -1
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/inflateZipPayload.d.ts +6 -0
- package/dist/core/inflateZipPayload.d.ts.map +1 -0
- package/dist/core/inflateZipPayload.js +28 -0
- package/dist/core/inflateZipPayload.js.map +1 -0
- package/dist/core/origin/OriginExtra.d.ts +43 -0
- package/dist/core/origin/OriginExtra.d.ts.map +1 -0
- package/dist/core/origin/OriginExtra.js +187 -0
- package/dist/core/origin/OriginExtra.js.map +1 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zip64/Zip64.d.ts +138 -0
- package/dist/core/zip64/Zip64.d.ts.map +1 -0
- package/dist/core/zip64/Zip64.js +353 -0
- package/dist/core/zip64/Zip64.js.map +1 -0
- package/dist/node/ZipCopyNode.d.ts.map +1 -1
- package/dist/node/ZipCopyNode.js +26 -26
- package/dist/node/ZipCopyNode.js.map +1 -1
- package/dist/node/ZipDecompressNode.d.ts.map +1 -1
- package/dist/node/ZipDecompressNode.js +8 -6
- package/dist/node/ZipDecompressNode.js.map +1 -1
- package/dist/node/ZipkitNode.d.ts +14 -1
- package/dist/node/ZipkitNode.d.ts.map +1 -1
- package/dist/node/ZipkitNode.js +91 -53
- package/dist/node/ZipkitNode.js.map +1 -1
- package/dist/node/ZstdNode.d.ts +14 -4
- package/dist/node/ZstdNode.d.ts.map +1 -1
- package/dist/node/ZstdNode.js +28 -4
- package/dist/node/ZstdNode.js.map +1 -1
- package/dist/node/buildZipBuffer.d.ts +49 -0
- package/dist/node/buildZipBuffer.d.ts.map +1 -0
- package/dist/node/buildZipBuffer.js +146 -0
- package/dist/node/buildZipBuffer.js.map +1 -0
- package/dist/node/index.d.ts +3 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +6 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/inflateZipPayload.d.ts +5 -0
- package/dist/node/inflateZipPayload.d.ts.map +1 -0
- package/dist/node/inflateZipPayload.js +21 -0
- package/dist/node/inflateZipPayload.js.map +1 -0
- package/dist/types/index.js +2 -2
- package/node-esm.mjs +32 -0
- package/package.json +11 -5
- package/src/browser/zstd-browser-shim.ts +110 -28
- package/src/core/ZipCompress.ts +6 -0
- package/src/core/ZipCopy.ts +56 -27
- package/src/core/ZipEntry.ts +192 -13
- package/src/core/Zipkit.ts +40 -22
- package/src/core/constants/Headers.ts +1 -0
- package/src/core/index.ts +3 -0
- package/src/core/inflateZipPayload.ts +27 -0
- package/src/core/origin/OriginExtra.ts +209 -0
- package/src/core/version.ts +1 -1
- package/src/core/zip64/Zip64.ts +473 -0
- package/src/node/ZipCopyNode.ts +31 -29
- package/src/node/ZipDecompressNode.ts +6 -5
- package/src/node/ZipkitNode.ts +121 -68
- package/src/node/ZstdNode.ts +33 -8
- package/src/node/buildZipBuffer.ts +221 -0
- package/src/node/index.ts +7 -0
- package/src/node/inflateZipPayload.ts +19 -0
- package/src/types/index.ts +2 -2
package/NEOZIP_APPNOTE.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# NeoZip Application Note
|
|
2
2
|
|
|
3
3
|
**Format:** `.nzip` (ZIP profile)
|
|
4
|
-
**Version:** 0.1.
|
|
4
|
+
**Version:** 0.1.1
|
|
5
5
|
**Status:** Release — extends PKWARE APPNOTE 6.3.10
|
|
6
|
-
**Date:** 2026-08-
|
|
6
|
+
**Date:** 2026-08-14
|
|
7
7
|
**Base specification:** PKWARE [APPNOTE.TXT](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) (`.ZIP` File Format Specification, Version 6.3.10)
|
|
8
|
-
**Scope:** Compression, encryption, integrity, and blockchain / Token Service extensions for the NeoZip wire profile. Optional **AI-aware** metadata (`META-INF/manifest.json`) is defined for future agent ingestion; it is **not** required for minting, timestamping, or
|
|
8
|
+
**Scope:** Compression, encryption (including optional **recipient / hybrid** access control), integrity, and blockchain / Token Service extensions for the NeoZip wire profile. Optional **AI-aware** metadata (`META-INF/manifest.json`) is defined for future agent ingestion; it is **not** required for minting, timestamping, integrity verification, or recipient decryption.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -65,11 +65,12 @@ APPNOTE does **not** require `MANIFEST.MF`, does not require Java attribute synt
|
|
|
65
65
|
An archive is recognized as a NeoZip profile if **any** of the following are present:
|
|
66
66
|
- Extra Field `0x014E` on one or more content entries.
|
|
67
67
|
- Sidecar proof entries `META-INF/TOKEN.NZIP` or `META-INF/TIMESTAMP.NZIP` (or pending `META-INF/TS-SUBMIT.NZIP`).
|
|
68
|
+
- Recipient-access sidecar `META-INF/ACCESS.NZIP` (§2.3 / §4.3).
|
|
68
69
|
- Optional AI-aware manifest `META-INF/manifest.json` with `"format": "neozip"`.
|
|
69
70
|
|
|
70
71
|
Absence of all of the above means a plain ZIP (or JAR / other profile).
|
|
71
72
|
|
|
72
|
-
> **Note for implementers:** Minting, timestamping, and Merkle verification **MUST NOT** depend on `manifest.json` being present. Core cryptographic operations rely on central-directory entries, Extra Field `0x014E`, and `META-INF/*.NZIP` sidecars.
|
|
73
|
+
> **Note for implementers:** Minting, timestamping, and Merkle verification **MUST NOT** depend on `manifest.json` being present. Core cryptographic operations rely on central-directory entries, Extra Field `0x014E`, and `META-INF/*.NZIP` sidecars. Recipient decryption uses `ACCESS.NZIP` plus content-entry AES (§4.3); it likewise **MUST NOT** require `manifest.json`.
|
|
73
74
|
|
|
74
75
|
---
|
|
75
76
|
|
|
@@ -99,7 +100,7 @@ knowledge_bundle.nzip
|
|
|
99
100
|
|
|
100
101
|
`META-INF/manifest.json` is reserved for structured discovery so AI tools can understand the archive without parsing low-level headers. It is not required for L1–L3 cryptographic proof.
|
|
101
102
|
|
|
102
|
-
### 2.3 Blockchain / Token Service sidecars
|
|
103
|
+
### 2.3 Blockchain / Token Service / access sidecars
|
|
103
104
|
|
|
104
105
|
When present, these **MUST** live under `META-INF/`:
|
|
105
106
|
|
|
@@ -108,9 +109,17 @@ When present, these **MUST** live under `META-INF/`:
|
|
|
108
109
|
| `META-INF/TOKEN.NZIP` | After successful on-chain mint | On-chain token binding (tokenId, contract, tx, merkleRoot, …) |
|
|
109
110
|
| `META-INF/TIMESTAMP.NZIP` | After confirmed Token Service stamp | Timestamp proof metadata |
|
|
110
111
|
| `META-INF/TS-SUBMIT.NZIP` | After stamp submit, before confirm | Pending timestamp submission |
|
|
112
|
+
| `META-INF/ACCESS.NZIP` | After recipient / hybrid encryption (§4.3) | ECIES-wrapped inner password + recipient list (UTF-8 JSON) |
|
|
111
113
|
|
|
112
114
|
Alternate token entry recognized by NeoZip: `META-INF/NZIP.TOKEN` (legacy, also uppercase).
|
|
113
|
-
Writers **MUST** emit the canonical uppercase forms above. NeoZip-aware readers **MUST** discover these reserved paths with **ASCII case-insensitive** comparison (see §2.4), so `META-INF/token.nzip`
|
|
115
|
+
Writers **MUST** emit the canonical uppercase forms above. NeoZip-aware readers **MUST** discover these reserved paths with **ASCII case-insensitive** comparison (see §2.4), so `META-INF/token.nzip` / `META-INF/access.nzip` are accepted for discovery; writers must still not emit those spellings.
|
|
116
|
+
|
|
117
|
+
**`ACCESS.NZIP` write rules (normative when the entry is present):**
|
|
118
|
+
|
|
119
|
+
1. Writers **MUST** emit the path exactly as `META-INF/ACCESS.NZIP`.
|
|
120
|
+
2. The entry **SHOULD** use compression method **Store (0)** so tools can read the JSON without inflate support beyond local headers.
|
|
121
|
+
3. The entry **MUST NOT** be encrypted with the same inner password / AES layer used for content entries. Recipient-aware tools and generic ZIP listers **MUST** be able to read `ACCESS.NZIP` without unwrapping any recipient key. (See §4.2 discouragement of encrypting public `META-INF/` discovery payloads.)
|
|
122
|
+
4. Body **MUST** be UTF-8 JSON conforming to the logical schema in §4.3.
|
|
114
123
|
|
|
115
124
|
These files are NeoZip application payloads (JSON or versioned binary envelopes as implemented by NeoZip). Generic unzip tools treat them as ordinary files under `META-INF/` and typically do not present them as “the” content of the archive.
|
|
116
125
|
|
|
@@ -125,12 +134,13 @@ ZIP stores entry names as opaque byte strings (case-preserving). This profile se
|
|
|
125
134
|
| Directory | `META-INF/` (uppercase, JAR/APPNOTE convention) |
|
|
126
135
|
| Optional AI-aware manifest | `META-INF/manifest.json` |
|
|
127
136
|
| Token / timestamp | `META-INF/TOKEN.NZIP`, `TIMESTAMP.NZIP`, `TS-SUBMIT.NZIP` |
|
|
137
|
+
| Recipient access | `META-INF/ACCESS.NZIP` |
|
|
128
138
|
|
|
129
139
|
**Reader lookup for reserved meta paths (NeoZip-aware tools MUST):**
|
|
130
140
|
|
|
131
141
|
1. Match the reserved prefixes and filenames with **ASCII case-insensitive** comparison
|
|
132
142
|
(`META-INF/manifest.json` ≡ `meta-inf/Manifest.JSON` ≡ `META-INF/MANIFEST.JSON` for *discovery*).
|
|
133
|
-
The same rule applies to `TOKEN.NZIP`, `NZIP.TOKEN`, `TIMESTAMP.NZIP`, `TS-SUBMIT.NZIP`, and the OTS equivalents.
|
|
143
|
+
The same rule applies to `TOKEN.NZIP`, `NZIP.TOKEN`, `TIMESTAMP.NZIP`, `TS-SUBMIT.NZIP`, `ACCESS.NZIP`, and the OTS equivalents.
|
|
134
144
|
2. If **more than one** central-directory entry matches the same reserved meta target under ASCII case-insensitive comparison, the archive is **malformed**. Readers **MUST** reject it and **MUST NOT** recover by preferring the canonical spelling, the first match, or any other silent selection. Silent recovery creates parser differentials across NeoZip reader implementations.
|
|
135
145
|
3. Paths **inside** content payloads (the original document’s own name) stay as stored; case folding applies only to reserved `META-INF/**` profile entries, not to arbitrary archive members.
|
|
136
146
|
4. Paths recorded *inside* `manifest.json` bodies **SHOULD** use canonical casing when written; readers resolving those pointers **MUST** also try ASCII case-insensitive match within the ZIP central directory. If that resolution yields multiple case-insensitive matches for one reserved target, apply rule 2 (reject as malformed).
|
|
@@ -141,10 +151,10 @@ ZIP stores entry names as opaque byte strings (case-preserving). This profile se
|
|
|
141
151
|
|
|
142
152
|
### 2.5 Root entry rules
|
|
143
153
|
|
|
144
|
-
1. All token and
|
|
154
|
+
1. All token, timestamp, and recipient-access data **MUST** be under `META-INF/`.
|
|
145
155
|
2. Content payloads are every ZIP entry outside `META-INF/`.
|
|
146
156
|
3. When optional `manifest.json` is present, content payloads **SHOULD** be listed in `manifest.json` `content[]` for AI discovery; cryptographic Merkle construction still uses the central directory (§6).
|
|
147
|
-
4. A general NeoZip archive **MAY** contain many content entries without blockchain sidecars and without `manifest.json`.
|
|
157
|
+
4. A general NeoZip archive **MAY** contain many content entries without blockchain sidecars, without `ACCESS.NZIP`, and without `manifest.json`.
|
|
148
158
|
|
|
149
159
|
---
|
|
150
160
|
|
|
@@ -170,11 +180,11 @@ When the file is present, the body is a UTF-8 JSON object. Unknown keys **MUST**
|
|
|
170
180
|
| :---- | :---- | :---- |
|
|
171
181
|
| `content` | array | Content index for agents (non-`META-INF/` files). Each: `{ "path", "sha256", "size", "mimeType?" }`. Informational only; Merkle leaves come from the central directory (§6). |
|
|
172
182
|
| `merkleRoot` | string | Optional hex copy of the content Merkle root for agents (§6). Authoritative on-chain binding remains in `TOKEN.NZIP` / `TIMESTAMP.NZIP` when present. |
|
|
173
|
-
| `profiles` | string[] | Declared profiles, e.g. `["integrity"]`, `["tokenized"]`, `["timestamped"]`, `["ai-aware"]` |
|
|
183
|
+
| `profiles` | string[] | Declared profiles, e.g. `["integrity"]`, `["tokenized"]`, `["timestamped"]`, `["ai-aware"]`, `["access-controlled"]` |
|
|
174
184
|
| `blockchain` | object | Token / network summary when tokenized (full proof remains in `TOKEN.NZIP`) |
|
|
175
185
|
| `timestamp` | object | Stamp summary when stamped (full proof in `TIMESTAMP.NZIP`) |
|
|
176
186
|
| `compression` | object | Defaults: `{ "method": "zstd" \| "deflate" \| "store", "level"?: number }` |
|
|
177
|
-
| `encryption` | object | `{ "method": "none" \| "neo-aes-256" \| "aes-256" \| "pkzip" }` (`neo-aes-256` = NeoZip default AES / NeoEncrypt) |
|
|
187
|
+
| `encryption` | object | `{ "method": "none" \| "neo-aes-256" \| "aes-256" \| "pkzip" \| "hybrid-recipient" }` (`neo-aes-256` = NeoZip default AES / NeoEncrypt; `hybrid-recipient` = inner AES + `ACCESS.NZIP` outer wrap, §4.3) |
|
|
178
188
|
|
|
179
189
|
### 3.3 Example (AI-aware integrity summary, not tokenized)
|
|
180
190
|
|
|
@@ -285,9 +295,93 @@ WinZip AE-x is a separate on-wire profile (PKWARE method **99** + Extra Field **
|
|
|
285
295
|
|
|
286
296
|
Writers **MUST NOT** place both `0x9901` and `0x024E` on the same entry. Readers that support both **MUST** discriminate on Extra Field ID / method 99, not on “encrypted + password” alone.
|
|
287
297
|
|
|
288
|
-
When optional `manifest.json` is written, writers **SHOULD** set `encryption.method` to `"neo-aes-256"` (default NeoZip AES), `"aes-256"` (WinZip AE), `"pkzip"`, or `"none"`.
|
|
298
|
+
When optional `manifest.json` is written, writers **SHOULD** set `encryption.method` to `"neo-aes-256"` (default NeoZip AES), `"aes-256"` (WinZip AE), `"pkzip"`, `"hybrid-recipient"` (inner AES + `ACCESS.NZIP`, §4.3), or `"none"`.
|
|
299
|
+
|
|
300
|
+
Encryption of `META-INF/*.NZIP` public proofs (including `ACCESS.NZIP`) and of optional `META-INF/manifest.json` is **DISCOURAGED** when discovery matters (tools cannot find digests/proofs/recipient wraps). Prefer encrypting content entries while leaving `META-INF/` public proofs readable, unless the entire archive is confidential.
|
|
301
|
+
|
|
302
|
+
### 4.3 Recipient / hybrid encryption (`META-INF/ACCESS.NZIP`)
|
|
303
|
+
|
|
304
|
+
Optional **recipient access control** binds content decryption to holders of specific **secp256k1** private keys. The on-wire profile is a **hybrid** design: an inner ZIP AES password encrypts content entries; that password is wrapped once per recipient with **ECIES** and stored in a public sidecar.
|
|
305
|
+
|
|
306
|
+
This section is normative for NeoZip-aware tools that **read or write** `META-INF/ACCESS.NZIP`. Base NeoZipKit MAY omit write support; Pro / access-controlled products implement the full pipeline.
|
|
307
|
+
|
|
308
|
+
#### 4.3.1 Hybrid model
|
|
309
|
+
|
|
310
|
+
1. **Inner (symmetric):** Generate a high-entropy secret (NeoZip Pro practice: 32 random bytes encoded as a 64-character hex string). Pass it as the ZIP password for **NeoEncrypt** (`0x024E`, §4.2.1) by default, or optionally WinZip AES (`0x9901` / method 99, §4.2.2). Encrypt **content** entries with that password.
|
|
311
|
+
2. **Outer (asymmetric):** For each recipient, wrap the **UTF-8 bytes of the inner password string** with **ECIES on secp256k1** (algorithm id `ecies-secp256k1-aes256gcm`).
|
|
312
|
+
3. **Sidecar:** Serialize recipient wraps and metadata as UTF-8 JSON and write **`META-INF/ACCESS.NZIP`** per §2.3 (STORED, **unencrypted**).
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
Inner password ──► NeoEncrypt / WinZip AES ──► encrypted content entries
|
|
316
|
+
│
|
|
317
|
+
└──► ECIES wrap (per recipient) ──► META-INF/ACCESS.NZIP (public JSON)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### 4.3.2 Outer wrap algorithm (`ecies-secp256k1-aes256gcm`)
|
|
321
|
+
|
|
322
|
+
**Wrap (per recipient):**
|
|
323
|
+
|
|
324
|
+
1. Recipient public key: uncompressed secp256k1, hex with `04` prefix (130 hex characters).
|
|
325
|
+
2. Generate an ephemeral secp256k1 key pair.
|
|
326
|
+
3. ECDH: ephemeral private × recipient public → shared secret.
|
|
327
|
+
4. HKDF-SHA256 over the shared secret → 32-byte wrapping key.
|
|
328
|
+
5. AES-256-GCM encrypt the UTF-8 password bytes (random 12-byte IV).
|
|
329
|
+
6. Store ciphertext as base64 of **`IV (12) ‖ auth tag (16) ‖ ciphertext`**, plus the ephemeral public key (uncompressed hex).
|
|
330
|
+
|
|
331
|
+
**Unwrap:** ECDH with recipient private × ephemeral public → same HKDF → AES-GCM decrypt. Match the recipient by deriving the public key from the supplied private key and comparing (case-insensitive) to `recipients[].publicKeyHex`.
|
|
332
|
+
|
|
333
|
+
#### 4.3.3 `ACCESS.NZIP` logical schema
|
|
334
|
+
|
|
335
|
+
Top-level fields (UTF-8 JSON):
|
|
336
|
+
|
|
337
|
+
| Field | Type | Purpose |
|
|
338
|
+
| :---- | :---- | :---- |
|
|
339
|
+
| `version` | string | Metadata format version (e.g. `"1.0"`) |
|
|
340
|
+
| `scheme` | string | How identities were resolved: `ens-hybrid` \| `address-hybrid` \| `did-hybrid` \| `lit-protocol` |
|
|
341
|
+
| `recipients` | array | Per-recipient wrap records (§4.3.4) |
|
|
342
|
+
| `encryption` | object | Inner method: `{ "method": "neo-aes256" \| "aes-256-winzip", "note"? }` |
|
|
343
|
+
| `created` | string | ISO-8601 timestamp |
|
|
344
|
+
| `proVersion` | string | Implementing product version (informative) |
|
|
345
|
+
|
|
346
|
+
When optional `manifest.json` is present for an access-controlled archive, writers **SHOULD** include `"access-controlled"` (and/or `"recipient-encrypted"`) in `profiles` and set `encryption.method` to `"hybrid-recipient"`.
|
|
347
|
+
|
|
348
|
+
#### 4.3.4 Each `recipients[]` entry
|
|
349
|
+
|
|
350
|
+
| Field | Type | Purpose |
|
|
351
|
+
| :---- | :---- | :---- |
|
|
352
|
+
| `identity` | string | Original identity string (ENS name, `0x…` address, `pkp:0x…`, DID, …) |
|
|
353
|
+
| `identityType` | string | `ens` \| `address` \| `did` \| `lit-pkp` |
|
|
354
|
+
| `resolvedAddress` | string | Checksummed Ethereum address where applicable |
|
|
355
|
+
| `publicKeyHex` | string | Uncompressed secp256k1 public key used for wrapping |
|
|
356
|
+
| `wrappedKey` | string | Base64 ECIES payload (§4.3.2) |
|
|
357
|
+
| `ephemeralPublicKey` | string | Hex ephemeral public key |
|
|
358
|
+
| `keyAlgorithm` | string | e.g. `ecies-secp256k1-aes256gcm` |
|
|
359
|
+
|
|
360
|
+
**Homogeneity:** All recipients in one archive **MUST** share the same `identityType` (no mixing ENS with Lit PKP in a single `ACCESS.NZIP`).
|
|
361
|
+
|
|
362
|
+
**Multiple recipients:** Each entry wraps the **same** inner password. Any matching private key recovers the AES layer.
|
|
363
|
+
|
|
364
|
+
#### 4.3.5 Identity resolution (informative)
|
|
365
|
+
|
|
366
|
+
Resolution is product-side; the wire format only requires a trusted `publicKeyHex` per recipient.
|
|
367
|
+
|
|
368
|
+
| Type | Identity string | Notes |
|
|
369
|
+
| :---- | :---- | :---- |
|
|
370
|
+
| `ens` | `*.eth` | Resolve name → ENS text `io.neozip.pubkey` (uncompressed hex) |
|
|
371
|
+
| `address` | `0x` + 40 hex | Address alone does not yield a unique pubkey; caller **MUST** supply `publicKeyHex` |
|
|
372
|
+
| `lit-pkp` | `pkp:0x…` | Same hybrid ECIES wrap to the PKP’s secp256k1 pubkey; scheme `lit-protocol`. Lit **Access Control Conditions** / threshold decryption without the unwrap key are **out of scope** for this profile |
|
|
373
|
+
| `did` | DID URI | Reserved for DID-based resolution (`did-hybrid`) |
|
|
374
|
+
|
|
375
|
+
#### 4.3.6 Decrypt flow (informative)
|
|
376
|
+
|
|
377
|
+
1. Locate and parse `META-INF/ACCESS.NZIP` (case-insensitive discovery, §2.4).
|
|
378
|
+
2. Find `recipients[]` entry matching the caller’s public key.
|
|
379
|
+
3. Unwrap → inner password; decrypt content entries with NeoZip AES rules (§4.2).
|
|
380
|
+
4. If no recipient matches, fail explicitly (wrong key).
|
|
381
|
+
|
|
382
|
+
#### 4.3.7 Privacy note
|
|
289
383
|
|
|
290
|
-
|
|
384
|
+
`ACCESS.NZIP` is **not** confidential: anyone with the archive can read recipient identities, addresses, algorithms, and ciphertext blobs. Wrapped keys remain ciphertext without the matching private key; the sidecar still leaks **who** was targeted.
|
|
291
385
|
|
|
292
386
|
---
|
|
293
387
|
|
|
@@ -351,7 +445,7 @@ Reference design notes: [Merkle Root calculation — RFC 6962](https://docs.goog
|
|
|
351
445
|
|
|
352
446
|
**Status:** legacy. Writers **MUST NOT** emit new proof-bound roots with v0. Verifiers retain v0 only for §6.4 fallback against existing on-chain / Token Service records.
|
|
353
447
|
|
|
354
|
-
1. Collect all non-`META-INF/` content entries **directly from the ZIP central directory** (exclude every entry whose name is under `META-INF
|
|
448
|
+
1. Collect all non-`META-INF/` content entries **directly from the ZIP central directory** (exclude every entry whose name is under `META-INF/**`, including `ACCESS.NZIP`, token/timestamp sidecars, and optional `manifest.json`).
|
|
355
449
|
2. For each entry, take `H_leaf = SHA-256(uncompressed bytes)` (= Extra Field `0x014E` value when present).
|
|
356
450
|
3. Sort leaves by central-directory path (UTF-8 byte order). Path normalization was not required by v0; verifiers reconstructing v0 roots **SHOULD** use the path bytes exactly as stored in the central directory file name.
|
|
357
451
|
4. Build a binary merkle tree with SHA-256. **Odd-leaf rule:** when a level has an odd count, **duplicate the last leaf** before pairing (Bitcoin-style):
|
|
@@ -364,7 +458,7 @@ For a single content entry, a v0 root **MAY** equal that entry’s SHA-256 (sing
|
|
|
364
458
|
|
|
365
459
|
**Status:** current. Writers **MUST** use v1 for new archives.
|
|
366
460
|
|
|
367
|
-
1. Collect all non-`META-INF/` content entries **directly from the ZIP central directory** (exclude every entry whose name is under `META-INF
|
|
461
|
+
1. Collect all non-`META-INF/` content entries **directly from the ZIP central directory** (exclude every entry whose name is under `META-INF/**`, including `ACCESS.NZIP`, token/timestamp sidecars, and optional `manifest.json`). Do **not** require `manifest.json` `content[]` for Merkle construction.
|
|
368
462
|
2. **Normalize each entry path** before sorting:
|
|
369
463
|
- Decode as UTF-8 (central-directory file name bytes per APPNOTE).
|
|
370
464
|
- Use POSIX separators only (`/` — convert `\` to `/`).
|
|
@@ -482,8 +576,9 @@ Credentials (wallet passkeys, Token Service email/token, network prefs) live in
|
|
|
482
576
|
| **L2** | NeoZip timestamped | L1 + valid `META-INF/TIMESTAMP.NZIP` bound to Merkle root |
|
|
483
577
|
| **L3** | NeoZip tokenized | L1 + valid `META-INF/TOKEN.NZIP` bound to Merkle root (L2 optional) |
|
|
484
578
|
| **+AI** | AI-aware enhanced | Any L1–L3 level **plus** `META-INF/manifest.json` for agent / LLM ingestion |
|
|
579
|
+
| **+Access** | Recipient-encrypted | Any L0–L3 level **plus** valid `META-INF/ACCESS.NZIP` and decryptable content for at least one listed recipient (§4.3). Combinable with **+AI**. |
|
|
485
580
|
|
|
486
|
-
A file may advertise multiple profiles; verifiers check each independently. **+AI**
|
|
581
|
+
A file may advertise multiple profiles; verifiers check each independently. **+AI** and **+Access** are additive: they never substitute for L1–L3 cryptographic requirements when those levels are claimed.
|
|
487
582
|
|
|
488
583
|
---
|
|
489
584
|
|
|
@@ -491,10 +586,11 @@ A file may advertise multiple profiles; verifiers check each independently. **+A
|
|
|
491
586
|
|
|
492
587
|
| Consumer | Expected behavior |
|
|
493
588
|
| :---- | :---- |
|
|
494
|
-
| Info-ZIP / Finder / Explorer | Lists/extracts content; shows `META-INF/` as a folder; ignores Extra Field `0x014E`; cannot inflate Zstd (method 93) without a Zstd-capable reader |
|
|
589
|
+
| Info-ZIP / Finder / Explorer | Lists/extracts content; shows `META-INF/` as a folder; ignores Extra Field `0x014E`; cannot inflate Zstd (method 93) without a Zstd-capable reader; can read plaintext `ACCESS.NZIP` JSON but cannot unwrap without a NeoZip-aware recipient tool |
|
|
495
590
|
| Java `jar` tools | Safe if `MANIFEST.MF` absent or is a valid attribute stub (§1.3); must not find JSON in `MANIFEST.MF` |
|
|
496
591
|
| WinZip 25+ / other APPNOTE 6.3.8+ Zstd readers | Can inflate method 93; NeoZip Extra Fields and `META-INF/*.NZIP` treated as ordinary data unless NeoZip-aware |
|
|
497
592
|
| NeoZip-aware tools | Full L1–L3 verify from Extra Fields + `*.NZIP` sidecars; Zstd; NeoEncrypt / AES; mint/stamp **without** requiring `manifest.json`; apply §6.4; extract after hard integrity mismatch only when explicitly opted out |
|
|
593
|
+
| NeoZip recipient / Pro tools | Discover `ACCESS.NZIP`, unwrap ECIES for a matching private key, decrypt content (§4.3 / **+Access**); Merkle still excludes `META-INF/**` |
|
|
498
594
|
| AI / automation agents | Prefer optional `META-INF/manifest.json` when present (**+AI**); otherwise fall back to product JSON / schema interfaces for operations |
|
|
499
595
|
|
|
500
596
|
---
|
|
@@ -510,11 +606,12 @@ When present, archives may declare `specVersion` in `META-INF/manifest.json` for
|
|
|
510
606
|
|
|
511
607
|
## 12. Open items (draft)
|
|
512
608
|
|
|
513
|
-
1. Publish formal JSON Schema for `META-INF/manifest.json`, `TOKEN.NZIP`, and `
|
|
609
|
+
1. Publish formal JSON Schema for `META-INF/manifest.json`, `TOKEN.NZIP`, `TIMESTAMP.NZIP`, and `ACCESS.NZIP`.
|
|
514
610
|
2. Decide whether to always emit the optional Java `MANIFEST.MF` stub.
|
|
515
611
|
3. Register additional Extra Field IDs with PKWARE only after the entry-name convention proves itself in the field.
|
|
516
612
|
4. Optional later merkle algorithm: bind normalized path into leaf digests for single-file inclusion proofs (explicit new algorithm id; not a silent change to v1).
|
|
517
613
|
5. Future AI-aware enhancements: deeper semantic content indexes, agent instruction fields, and related extensions in `manifest.json` without requiring changes to token/timestamp verification.
|
|
614
|
+
6. Optional Lit ACC / threshold outer layer (distinct from ECIES-to-pubkey `lit-pkp` in §4.3) if product demand warrants a new `scheme`.
|
|
518
615
|
|
|
519
616
|
---
|
|
520
617
|
|
|
@@ -522,6 +619,7 @@ When present, archives may declare `specVersion` in `META-INF/manifest.json` for
|
|
|
522
619
|
|
|
523
620
|
| Date | Version | Change |
|
|
524
621
|
| :---- | :---- | :---- |
|
|
622
|
+
| 2026-08-14 | 0.1.1 | §2.3 / §4.3: reserve `META-INF/ACCESS.NZIP` recipient / hybrid encryption profile (ECIES outer wrap + NeoEncrypt/WinZip AES inner); discovery, Merkle exclusion, **+Access** conformance, schema open item. |
|
|
525
623
|
| 2026-08-09 | 0.1.0 | Editorial: on-wire profile only — verification, mint/stamp, and interop stated without product command/flag names. |
|
|
526
624
|
| 2026-08-03 | 0.1.0 | §4.2: NeoEncrypt (`0x024E` + real compression method) is NeoZip default AES-256; WinZip AES (method **99** + **`0x9901`**) is recognized for interop, not default write. |
|
|
527
625
|
| 2026-08-03 | 0.1.0 | Initial release. |
|
package/README.md
CHANGED
|
@@ -4,16 +4,17 @@ Advanced ZIP file creation, compression, and encryption library for Node.js and
|
|
|
4
4
|
|
|
5
5
|
**Scope of this package:** NeoZipKit focuses solely on creating and manipulating ZIP files (compression, encryption, extraction). All blockchain-related functionality—timestamping, NFT tokenization, verification, wallet integration, and smart contracts—lives in the sibling **[neozip-blockchain](../neozip-blockchain/)** package within this monorepo. Use that package when you need to link ZIPs to the blockchain.
|
|
6
6
|
|
|
7
|
-
**Format note:** See **[NEOZIP_APPNOTE.md](./NEOZIP_APPNOTE.md)** for the NeoZip ZIP profile (compression methods, Extra Field `0x014E`, `META-INF` integrity and blockchain sidecars).
|
|
7
|
+
**Format note:** See **[NEOZIP_APPNOTE.md](./NEOZIP_APPNOTE.md)** for the NeoZip ZIP profile (compression methods, Extra Field `0x014E`, `META-INF` integrity and blockchain sidecars). Kit backlog: **[docs/FUTURE_ENHANCEMENTS.md](docs/FUTURE_ENHANCEMENTS.md)**.
|
|
8
8
|
|
|
9
|
-
> **Stable 1.
|
|
9
|
+
> **Stable 1.5:** NeoZipKit **1.5.1** (Zip64, Extra Field `0x014F`, browser/buffer zstd; pairs with `neozip-blockchain@1.5.0`). See [NEOZIP_APPNOTE.md](./NEOZIP_APPNOTE.md) and [WHATS_NEW.md](WHATS_NEW.md). Please report issues on [GitHub](https://github.com/NeoWareInc/neozipkit/issues).
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
- **Advanced ZIP compression** with support for multiple compression methods (Deflate, ZStandard, Stored)
|
|
15
|
+
- **Advanced ZIP compression** with support for multiple compression methods (Deflate, ZStandard, Stored), including zstd on in-memory `Buffer` / `ArrayBuffer` and in the browser via `CompressionStream`
|
|
16
16
|
- **Streaming compression** for memory-efficient processing of large files
|
|
17
|
+
- **Zip64 (APPNOTE Version 1)** for archives past classic ZIP limits: Node streaming for member sizes / offsets ≥ 4 GiB; buffer APIs support Zip64 for entry counts > 65 535 and can **read** Zip64 metadata when the archive already fits in memory. Use `forceZip64: true` to emit Zip64 for small test archives (`pnpm test:zip64`).
|
|
17
18
|
- **Encryption** with ZIP (Legacy), **NeoEncrypt** (default AES-256 via Extra Field `0x024E`), and **WinZip AES-256** (AE-1/AE-2, method 99 — write with `encryptionMethod: 'aes256'`, always readable on extract); create and extract in Node and browser
|
|
18
19
|
- **Hash-based verification** with Merkle tree support (CRC-32, SHA-256)
|
|
19
20
|
- **Real-time progress tracking** for long-running operations
|
|
@@ -104,15 +105,44 @@ All blockchain code lives in the sibling **[neozip-blockchain](../neozip-blockch
|
|
|
104
105
|
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
105
106
|
| `ZipkitNode` create / extract / list | File streaming (`loadZipFile`, `writeZipEntry`, `extractToFile`, …) |
|
|
106
107
|
| Core `Zipkit.loadZip(Buffer)` / browser | Buffer APIs kept for browser and small in-process buffers |
|
|
107
|
-
|
|
|
108
|
+
| Zip64 sizes / offsets ≥ 4 GiB | **Node streaming only** (`ZipkitNode` / `ZipCopyNode`). Buffer builders refuse with a clear error |
|
|
109
|
+
| Zip64 entry count > 65 535 | Node **or** buffer/browser when the whole archive still fits in memory |
|
|
110
|
+
| Zstd (method 93) | Node: native `zlib` streams and buffer compress/inflate (Node ≥ 22.15). Browser: `CompressionStream` zstd on `ArrayBuffer` (no WASM). Sync zstd is Node-only |
|
|
108
111
|
| Legacy WASM zstd archives | Detect with `detectLegacyWasmZstd` / `ZipkitNode.detectLegacyZstdEntries`; extract truncates +18 zero padding |
|
|
109
112
|
|
|
110
113
|
|
|
111
114
|
See also: [docs/ZSTD_USAGE.md](docs/ZSTD_USAGE.md) and the Rust crate notes in `neozip-rust` (`crates/neozipkit/src/limits.rs`).
|
|
112
115
|
|
|
113
|
-
##
|
|
116
|
+
## Zip64 (large archives)
|
|
114
117
|
|
|
115
|
-
|
|
118
|
+
Classic ZIP fields are **16-bit** entry counts and **32-bit** sizes/offsets. Past those limits the kit emits APPNOTE **Zip64 Version 1** automatically (no opt-in flag). Spanned/split multi-disk archives are not supported.
|
|
119
|
+
|
|
120
|
+
| Limit exceeded | Threshold | Use |
|
|
121
|
+
| -------------- | --------- | --- |
|
|
122
|
+
| Member uncompressed or compressed size | ≥ 4 GiB (`> 0xFFFFFFFF`) | `ZipkitNode` file create / extract |
|
|
123
|
+
| Local-header or central-directory offset | ≥ 4 GiB | `ZipkitNode` / `ZipCopyNode` |
|
|
124
|
+
| Number of members | > 65 535 | Node **or** in-memory (`buildZipBufferSync` / browser) if RAM allows |
|
|
125
|
+
| Listing / reading Zip64 metadata | — | Any surface that can open the archive; Extra Field `0x0001` and Zip64 EOCD are parsed |
|
|
126
|
+
|
|
127
|
+
**Wire details (automatic):** Extra Field `0x0001` on overflowing members, Zip64 end-of-central-directory + locator before the classic EOCD, classic fields set to `0xFFFF` / `0xFFFFFFFF` sentinels, version needed to extract **45**.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { ZipkitNode } from 'neozipkit/node';
|
|
131
|
+
|
|
132
|
+
// Large members: stream to/from disk — do not use buildZipBufferSync for ≥ 4 GiB payloads
|
|
133
|
+
const zip = new ZipkitNode();
|
|
134
|
+
await zip.createZipFromFiles(['huge-video.mkv'], 'archive.nzip');
|
|
135
|
+
await zip.loadZipFile('archive.nzip');
|
|
136
|
+
const entries = zip.getDirectory();
|
|
137
|
+
// entries[0].uncompressedSize / compressedSize / localHdrOffset hold full 64-bit values
|
|
138
|
+
await zip.closeFile();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Do **not** call `buildZipBufferSync` or browser Blob create when a member size or archive offset would need Zip64 for ≥ 4 GiB — that path throws and points you at `ZipkitNode`. Prefer streaming extract for huge members rather than `extractToBuffer`.
|
|
142
|
+
|
|
143
|
+
Release notes: [WHATS_NEW.md](WHATS_NEW.md). Spec: PKWARE [docs/APPNOTE.txt](docs/APPNOTE.txt) §§4.3.14–4.3.16, 4.5.3.
|
|
144
|
+
|
|
145
|
+
The published tarball includes **`dist/`**, **`src/`** (for `neozipkit/src` conditional exports), **`node-esm.mjs`** (the ESM entry for `neozipkit/node`), **[`README.md`](README.md)**, and **[`WHATS_NEW.md`](WHATS_NEW.md)**. **`examples/`** and other repo-only folders are excluded—use **`pnpm publish:dry-run`** (`npm publish --dry-run`) to preview the file list.
|
|
116
146
|
|
|
117
147
|
## Development
|
|
118
148
|
|
|
@@ -166,7 +196,7 @@ As of **v0.6.1**, `loadZipFile()` automatically **closes any prior read handle**
|
|
|
166
196
|
|
|
167
197
|
- **STORED (0)** – No compression
|
|
168
198
|
- **DEFLATED (8)** – Deflate (default)
|
|
169
|
-
- **ZSTD (93)** – Zstandard
|
|
199
|
+
- **ZSTD (93)** – Zstandard. Node uses native `zlib` (streams and `Buffer` / `ArrayBuffer`). The browser bundle uses `CompressionStream` zstd. `ZipkitNode.writeMembersSync` / `buildZipBufferSync` default to raw zstd level 7. Caller extra blocks such as ZipWiki `0x014F` go on `ZipEntry.additionalExtra` and are written beside `0x014E`.
|
|
170
200
|
|
|
171
201
|
|
|
172
202
|
|
|
@@ -181,7 +211,7 @@ As of **v0.6.1**, `loadZipFile()` automatically **closes any prior read handle**
|
|
|
181
211
|
|
|
182
212
|
## What’s new
|
|
183
213
|
|
|
184
|
-
See [WHATS_NEW.md](WHATS_NEW.md) for release notes.
|
|
214
|
+
See [WHATS_NEW.md](WHATS_NEW.md) for release notes. The unreleased section covers Extra Field `0x014F`, buffer and browser zstd, and `writeMembersSync`.
|
|
185
215
|
|
|
186
216
|
## Security
|
|
187
217
|
|
package/WHATS_NEW.md
CHANGED
|
@@ -2,6 +2,121 @@
|
|
|
2
2
|
|
|
3
3
|
Release notes for people who install and use **`neozipkit`** from npm. For blockchain timestamping and NFTs, see the sibling package [`neozip-blockchain`](https://www.npmjs.com/package/neozip-blockchain).
|
|
4
4
|
|
|
5
|
+
## 1.5.1 (2026-10-25)
|
|
6
|
+
|
|
7
|
+
Same contents as **1.5.0**. Republished because npm left `neozipkit@1.5.0` in a stuck “staged” state that blocked publish (`E409`). Sibling **`neozip-blockchain@1.5.0`** is unchanged.
|
|
8
|
+
|
|
9
|
+
## 1.5.0 (2026-10-25)
|
|
10
|
+
|
|
11
|
+
Released with sibling **`neozip-blockchain@1.5.0`**. *(Kit tarball: use **1.5.1** on npm.)*
|
|
12
|
+
|
|
13
|
+
### forceZip64 (testing Zip64 without huge files)
|
|
14
|
+
|
|
15
|
+
`CompressOptions.forceZip64` (and `buildZipBufferSync` / `writeZipFileSync` `{ forceZip64: true }`) emit APPNOTE Zip64 Version 1 even when sizes and offsets fit classic fields — Extra Field `0x0001`, version needed **45**, Zip64 EOCD + locator, classic CD-offset sentinel (Info-ZIP `-fz` style). Use it to exercise list/integrity paths with the small files under `examples/test-files/`.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
await zip.createZipFromFiles(files, 'out-zip64.zip', {
|
|
19
|
+
forceZip64: true,
|
|
20
|
+
useZstd: false,
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Smoke script (create → list → `testOnly`): `pnpm example:create-zip64` or `pnpm test:zip64`.
|
|
25
|
+
|
|
26
|
+
### Zip64 (APPNOTE Version 1)
|
|
27
|
+
|
|
28
|
+
NeoZipKit can create and open archives past classic ZIP limits: **more than 65 535 members**, or **sizes / offsets ≥ 4 GiB**. Zip64 Version 1 is emitted automatically when a classic field would overflow. Spanned (multi-disk) Zip64 is still out of scope.
|
|
29
|
+
|
|
30
|
+
**What you should use**
|
|
31
|
+
|
|
32
|
+
| Goal | API |
|
|
33
|
+
| ---- | --- |
|
|
34
|
+
| Write or copy members ≥ 4 GiB, or archives whose CD offset ≥ 4 GiB | **`ZipkitNode`** / **`ZipCopyNode`** (file streaming) |
|
|
35
|
+
| Many small members (> 65 535) that still fit in RAM | Node **or** `buildZipBufferSync` / browser |
|
|
36
|
+
| Open a Zip64 archive already in a `Buffer` | `Zipkit.loadZip` — lists and parses sizes correctly; avoid loading multi-GiB members into memory |
|
|
37
|
+
| Force Zip64 for small test archives | `createZipFromFiles(..., { forceZip64: true })` |
|
|
38
|
+
|
|
39
|
+
**On the wire**
|
|
40
|
+
|
|
41
|
+
- Per-member Extra Field **`0x0001`** (only overflowing fields, APPNOTE order); local Zip64 extras always carry both uncompressed and compressed sizes
|
|
42
|
+
- **Zip64 EOCD** (`0x06064b50`) + **locator** (`0x07064b50`) before the classic EOCD
|
|
43
|
+
- Classic EOCD / header fields use `0xFFFF` / `0xFFFFFFFF` sentinels where they overflow
|
|
44
|
+
- Version needed to extract **45** on Zip64 members
|
|
45
|
+
|
|
46
|
+
**Read path:** any classic EOCD sentinel triggers locator → Zip64 EOCD; `ZipEntry.readZipEntry` merges `0x0001` into `uncompressedSize`, `compressedSize`, and `localHdrOffset`. Copy paths use 8-byte data-descriptor sizes when `usesZip64Extra` is set.
|
|
47
|
+
|
|
48
|
+
**Buffer / browser policy:** size or offset Zip64 that would build a multi-GiB `Buffer` / Blob throws a clear error directing you to the Node file API. Entry-count Zip64 is allowed when the archive remains memory-feasible.
|
|
49
|
+
|
|
50
|
+
Helpers: `src/core/zip64/Zip64.ts` (also exported from the package). Tests: `tests/unit/core/zip64/Zip64.test.ts`.
|
|
51
|
+
|
|
52
|
+
### Caller extra fields, including ZipWiki `0x014F`
|
|
53
|
+
|
|
54
|
+
`ZipEntry.createLocalHdr()` and `centralDirEntry()` write caller-supplied extra-field records beside **`0x014E`**, WinZip AES, and NeoEncrypt. Pass a buffer of complete records (header id + size + payload) on `entry.additionalExtra`.
|
|
55
|
+
|
|
56
|
+
- **`0x014F`** is ZipWiki’s origin locator (URI, size, mtime, CRC-32 or SHA-256). The id is `HDR_ID.ORIGIN`. Helpers: `makeOriginExtra` / `parseOriginFromExtra` / `parseOriginFromEntry` (`src/core/origin/OriginExtra.ts`).
|
|
57
|
+
- Unknown extra ids survive a read. They are collected into `additionalExtra` so a later header rebuild does not drop them.
|
|
58
|
+
- `ZipCopyNode` copies local bytes as-is and copies `additionalExtra` onto the rebuilt central-directory header, so an origin tag survives a raw copy.
|
|
59
|
+
- Set `entry.emitUnicodePath = false` when the caller extra blob must be the only extra (ZipWiki pack does this). The default remains the Info-ZIP Unicode Path extra when the name needs it.
|
|
60
|
+
- Method **93** records version needed to extract **6.3** (63).
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { makeOriginExtra, parseOriginFromEntry, buildZipBufferSync } from 'neozipkit/node';
|
|
64
|
+
|
|
65
|
+
const originExtra = makeOriginExtra({
|
|
66
|
+
uri: 'https://example.org/docs/sample.pdf',
|
|
67
|
+
size: 1234,
|
|
68
|
+
});
|
|
69
|
+
const buf = buildZipBufferSync([
|
|
70
|
+
{ name: 'wiki/parsed/sample.pdf.md', data: notes, method: 0, additionalExtra: originExtra },
|
|
71
|
+
]);
|
|
72
|
+
// After loadZip / loadZipFile:
|
|
73
|
+
const loc = parseOriginFromEntry(entries[0]);
|
|
74
|
+
console.log(loc?.uri);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Tests: `tests/unit/core/origin/OriginExtra.test.ts`.
|
|
78
|
+
|
|
79
|
+
### Zstd on in-memory buffers, including the browser
|
|
80
|
+
|
|
81
|
+
Method 93 is no longer only a Node file stream.
|
|
82
|
+
|
|
83
|
+
- **Node:** `ZstdNode.compress`, `decompress`, `compressSync`, and `decompressSync` accept a `Buffer`, `Uint8Array`, or `ArrayBuffer`. `compressSyncAtLevel(data, zstdLevel)` uses a raw zlib zstd level (1–19). ZipWiki pack uses level **7** on that scale. The 1–9 kit scale (`mapCompressionLevel`, about `level * 2.1`) is unchanged for `createZipFromFiles` / `writeZipEntry`.
|
|
84
|
+
- **Browser:** the bundle no longer throws from the zstd stub. Compress and inflate use `CompressionStream` / `DecompressionStream` (`'zstd'`) on an `ArrayBuffer`. There is still no WASM codec. Sync `compressSync` / `decompressSync` are Node-only; the browser API is async. Browsers without `CompressionStream` zstd (and Node itself) throw a clear error. Deflate and store are unchanged.
|
|
85
|
+
- `inflateZipPayload(method, bytes)` (browser and core) and `inflateZipPayloadSync(method, buffer)` (`neozipkit/node`) inflate store (0), raw deflate (8), and zstd (93).
|
|
86
|
+
|
|
87
|
+
### In-memory and streaming writer
|
|
88
|
+
|
|
89
|
+
`ZipkitNode.writeMembersSync(path, members)` streams local headers, payloads, the central directory, and the EOCD to a file. `buildZipBufferSync(members)` builds the same archive as one `Buffer` for tests and small rewrites. `writeZipFileSync` is the same stream without the class.
|
|
90
|
+
|
|
91
|
+
- Default method is zstd **93** at raw level **7**. Pass `method: 0 | 8 | 93`. Empty payloads and payloads that do not shrink are stored.
|
|
92
|
+
- `useSHA256: true` writes Extra Field **`0x014E`**. `additionalExtra` is the caller block (`0x014F`). NeoEncrypt is not turned on.
|
|
93
|
+
- `precompressed` copies an existing method, compressed bytes, CRC-32, and extra blob without recompressing.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { ZipkitNode } from 'neozipkit/node';
|
|
97
|
+
|
|
98
|
+
const zip = new ZipkitNode();
|
|
99
|
+
zip.writeMembersSync('notes.zipwiki', [
|
|
100
|
+
{
|
|
101
|
+
name: 'wiki/parsed/notes.txt.md',
|
|
102
|
+
data: Buffer.from('# Notes\n'),
|
|
103
|
+
method: 93,
|
|
104
|
+
level: 7,
|
|
105
|
+
additionalExtra: originExtra, // complete 0x014F record
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Merkle v1 on CRC-only archives
|
|
111
|
+
|
|
112
|
+
`getMerkleRootAsync()` (v1) now captures `merkleLeafV1` while `testEntry` checks CRC-32, not only when Extra Field `0x014E` is present. v0 is unchanged: bare `0x014E` digests for older archives.
|
|
113
|
+
|
|
114
|
+
### ESM import of `neozipkit/node`
|
|
115
|
+
|
|
116
|
+
The compiled kit is CommonJS. The package `import` condition is `node-esm.mjs`, so named imports (`ZipkitNode`, `buildZipBufferSync`, `ZipCopyNode`, Merkle helpers) work from ESM. `require('neozipkit/node')` still loads `dist/node/index.js`. `minimatch` is a runtime dependency of `ZipkitNode`.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
5
120
|
## 1.0.5 (2026-08-14)
|
|
6
121
|
|
|
7
122
|
### NeoZip Application Note + APPNOTE §6 Merkle (v0 / v1)
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser
|
|
3
|
-
*
|
|
2
|
+
* Browser zstd codec for in-memory buffers.
|
|
3
|
+
* Uses CompressionStream / DecompressionStream ('zstd') so the old WASM codec stays out.
|
|
4
|
+
* Node builds use src/node/ZstdNode.ts (native zlib) instead of this file.
|
|
4
5
|
*/
|
|
6
|
+
import { Buffer } from 'buffer';
|
|
7
|
+
/** Bytes accepted by the buffer zstd codec. */
|
|
8
|
+
export type ZstdInput = Buffer | Uint8Array | ArrayBuffer;
|
|
5
9
|
export declare function isNativeZstdAvailable(): boolean;
|
|
6
|
-
export declare function mapCompressionLevel(
|
|
10
|
+
export declare function mapCompressionLevel(level: number): number;
|
|
7
11
|
export declare function createCompressTransform(_level?: number): never;
|
|
8
12
|
export declare function createDecompressTransform(): never;
|
|
9
|
-
export declare function compress(
|
|
10
|
-
export declare function decompress(
|
|
11
|
-
export declare function compressSync(_data:
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function
|
|
14
|
-
export declare function
|
|
13
|
+
export declare function compress(data: ZstdInput, _level?: number): Promise<Buffer>;
|
|
14
|
+
export declare function decompress(data: ZstdInput): Promise<Buffer>;
|
|
15
|
+
export declare function compressSync(_data: ZstdInput, _level?: number): Buffer;
|
|
16
|
+
export declare function compressSyncAtLevel(data: ZstdInput, _zstdLevel?: number): Buffer;
|
|
17
|
+
export declare function decompressSync(_data: ZstdInput): Buffer;
|
|
18
|
+
export declare function asZstdBuffer(data: ZstdInput): Buffer;
|
|
19
|
+
export declare function compressChunks(readChunk: (position: number, size: number) => Buffer, totalSize: number, chunkSize: number, level: number, _onPlainChunk?: (chunk: Buffer) => void, onCompressedChunk?: (chunk: Buffer) => void | Promise<void>): Promise<number>;
|
|
20
|
+
export declare function decompressStream(compressedStream: AsyncIterable<Buffer>): AsyncGenerator<Buffer>;
|
|
15
21
|
export declare const ZstdNode: {
|
|
16
22
|
isNativeZstdAvailable: typeof isNativeZstdAvailable;
|
|
17
23
|
mapCompressionLevel: typeof mapCompressionLevel;
|
|
@@ -20,7 +26,9 @@ export declare const ZstdNode: {
|
|
|
20
26
|
compress: typeof compress;
|
|
21
27
|
decompress: typeof decompress;
|
|
22
28
|
compressSync: typeof compressSync;
|
|
29
|
+
compressSyncAtLevel: typeof compressSyncAtLevel;
|
|
23
30
|
decompressSync: typeof decompressSync;
|
|
31
|
+
asZstdBuffer: typeof asZstdBuffer;
|
|
24
32
|
compressChunks: typeof compressChunks;
|
|
25
33
|
decompressStream: typeof decompressStream;
|
|
26
34
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zstd-browser-shim.d.ts","sourceRoot":"","sources":["../../src/browser/zstd-browser-shim.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"zstd-browser-shim.d.ts","sourceRoot":"","sources":["../../src/browser/zstd-browser-shim.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,+CAA+C;AAC/C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC;AAe1D,wBAAgB,qBAAqB,IAAI,OAAO,CAY/C;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAqCD,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAE9D;AAED,wBAAgB,yBAAyB,IAAI,KAAK,CAEjD;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEhF;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAEvD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAEpD;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,EACrD,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACvC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC1D,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED,wBAAuB,gBAAgB,CACrC,gBAAgB,EAAE,aAAa,CAAC,MAAM,CAAC,GACtC,cAAc,CAAC,MAAM,CAAC,CAMxB;AAED,eAAO,MAAM,QAAQ;;;;;;;;;;;;;CAapB,CAAC;AAEF,eAAe,QAAQ,CAAC"}
|