zpk1-protocol 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 o0CroMag0o
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # zpk1
2
+
3
+ Strict parser / validator / packer for the **ZPK1** packed payload protocol.
4
+
5
+ ZPK1 is a deterministic, pipe-delimited, human-readable format for pre-hashed payloads intended for timestamping or attestation without transmitting raw data.
6
+
7
+ This package enforces the ZPK1 specification only. It does not perform hashing or timestamping. To obtain verifiable time receipts, submit a valid ZPK1 string to a timestamping authority (e.g., ZEUS Verify).
8
+
9
+ Design goals:
10
+
11
+ - minimal surface area
12
+ - strict structural enforcement
13
+ - zero dependencies
14
+ - no silent normalization
15
+ - deterministic behavior across environments
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm i zpk1
@@ -0,0 +1,31 @@
1
+ export type ZPK1Canon = "utf8_exact" | "json_sorted_compact" | "bytes_b64";
2
+ export type ZPK1Algo = "blake3" | "sha256";
3
+ export type ZPK1Parsed = {
4
+ version: "ZPK1";
5
+ canon: ZPK1Canon;
6
+ algo: ZPK1Algo;
7
+ digest: string;
8
+ tag?: string;
9
+ };
10
+ export type ZPK1ErrorCode = "ZPK1_BAD_PREFIX" | "ZPK1_WHITESPACE" | "ZPK1_BAD_ORDER" | "ZPK1_MISSING_FIELD" | "ZPK1_EXTRA_FIELD" | "ZPK1_BAD_KV" | "ZPK1_BAD_CANON" | "ZPK1_BAD_ALGO" | "ZPK1_BAD_DIGEST" | "ZPK1_BAD_TAG";
11
+ export declare class ZPK1Error extends Error {
12
+ code: ZPK1ErrorCode;
13
+ details?: Record<string, unknown>;
14
+ constructor(code: ZPK1ErrorCode, message: string, details?: Record<string, unknown>);
15
+ }
16
+ export declare function parse(zpk: string): ZPK1Parsed;
17
+ export declare function isZPK1(zpk: string): boolean;
18
+ export declare function assert(zpk: string): ZPK1Parsed;
19
+ export declare function validate(zpk: string): {
20
+ ok: true;
21
+ value: ZPK1Parsed;
22
+ } | {
23
+ ok: false;
24
+ error: ZPK1Error;
25
+ };
26
+ export declare function pack(input: {
27
+ canon: ZPK1Canon;
28
+ algo: ZPK1Algo;
29
+ digest: string;
30
+ tag?: string;
31
+ }): string;
@@ -0,0 +1,122 @@
1
+ export class ZPK1Error extends Error {
2
+ code;
3
+ details;
4
+ constructor(code, message, details) {
5
+ super(message);
6
+ this.name = "ZPK1Error";
7
+ this.code = code;
8
+ this.details = details;
9
+ }
10
+ }
11
+ const ALLOWED_CANON = new Set(["utf8_exact", "json_sorted_compact", "bytes_b64"]);
12
+ const ALLOWED_ALGO = new Set(["blake3", "sha256"]);
13
+ const HEX64 = /^[0-9a-f]{64}$/;
14
+ function hasWhitespace(s) {
15
+ // Disallow any whitespace anywhere
16
+ return /\s/.test(s);
17
+ }
18
+ function parseKV(part) {
19
+ const idx = part.indexOf("=");
20
+ if (idx <= 0 || idx === part.length - 1) {
21
+ throw new ZPK1Error("ZPK1_BAD_KV", "Malformed key/value field (expected key=value).", { part });
22
+ }
23
+ const key = part.slice(0, idx);
24
+ const value = part.slice(idx + 1);
25
+ return { key, value };
26
+ }
27
+ export function parse(zpk) {
28
+ if (typeof zpk !== "string") {
29
+ throw new ZPK1Error("ZPK1_BAD_PREFIX", "Input must be a string.");
30
+ }
31
+ if (hasWhitespace(zpk)) {
32
+ throw new ZPK1Error("ZPK1_WHITESPACE", "Whitespace is not permitted in ZPK1.");
33
+ }
34
+ const parts = zpk.split("|");
35
+ if (parts.length < 4) {
36
+ throw new ZPK1Error("ZPK1_MISSING_FIELD", "ZPK1 requires at least 4 pipe-delimited parts.");
37
+ }
38
+ const prefix = parts[0];
39
+ if (prefix !== "ZPK1") {
40
+ throw new ZPK1Error("ZPK1_BAD_PREFIX", "ZPK1 must begin with literal prefix 'ZPK1'.", { prefix });
41
+ }
42
+ // Allowed field sequences:
43
+ // ZPK1|canon=...|algo=...|digest=...
44
+ // ZPK1|canon=...|algo=...|tag=...|digest=...
45
+ const kvs = parts.slice(1).map(parseKV);
46
+ // Enforce exact keys and order
47
+ const keys = kvs.map(k => k.key);
48
+ const hasTag = keys.includes("tag");
49
+ const expected = hasTag
50
+ ? ["canon", "algo", "tag", "digest"]
51
+ : ["canon", "algo", "digest"];
52
+ // No extra fields allowed
53
+ if (keys.length !== expected.length) {
54
+ throw new ZPK1Error("ZPK1_EXTRA_FIELD", "Unexpected number of fields.", { keys, expected });
55
+ }
56
+ for (let i = 0; i < expected.length; i++) {
57
+ if (keys[i] !== expected[i]) {
58
+ throw new ZPK1Error("ZPK1_BAD_ORDER", "Fields must appear in strict order.", { keys, expected });
59
+ }
60
+ }
61
+ const canon = kvs[0].value;
62
+ const algo = kvs[1].value;
63
+ if (!ALLOWED_CANON.has(canon)) {
64
+ throw new ZPK1Error("ZPK1_BAD_CANON", "Unsupported canon value.", { canon });
65
+ }
66
+ if (!ALLOWED_ALGO.has(algo)) {
67
+ throw new ZPK1Error("ZPK1_BAD_ALGO", "Unsupported algo value.", { algo });
68
+ }
69
+ let tag;
70
+ let digest;
71
+ if (hasTag) {
72
+ tag = kvs[2].value;
73
+ digest = kvs[3].value;
74
+ if (!tag || tag.length === 0) {
75
+ throw new ZPK1Error("ZPK1_BAD_TAG", "Tag must not be empty.");
76
+ }
77
+ if (tag.includes("|") || tag.includes("=")) {
78
+ throw new ZPK1Error("ZPK1_BAD_TAG", "Tag must not contain '|' or '='.", { tag });
79
+ }
80
+ }
81
+ else {
82
+ digest = kvs[2].value;
83
+ }
84
+ if (!HEX64.test(digest)) {
85
+ throw new ZPK1Error("ZPK1_BAD_DIGEST", "Digest must be 64 lowercase hex characters.", { digest });
86
+ }
87
+ return {
88
+ version: "ZPK1",
89
+ canon: canon,
90
+ algo: algo,
91
+ digest,
92
+ ...(tag ? { tag } : {}),
93
+ };
94
+ }
95
+ export function isZPK1(zpk) {
96
+ try {
97
+ parse(zpk);
98
+ return true;
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ }
104
+ export function assert(zpk) {
105
+ return parse(zpk);
106
+ }
107
+ export function validate(zpk) {
108
+ try {
109
+ return { ok: true, value: parse(zpk) };
110
+ }
111
+ catch (e) {
112
+ return { ok: false, error: e instanceof ZPK1Error ? e : new ZPK1Error("ZPK1_BAD_KV", "Unknown ZPK1 error.") };
113
+ }
114
+ }
115
+ export function pack(input) {
116
+ // Validate using same rules (don’t allow pack to create invalid strings)
117
+ const base = `ZPK1|canon=${input.canon}|algo=${input.algo}`;
118
+ const mid = input.tag !== undefined ? `${base}|tag=${input.tag}|digest=${input.digest}` : `${base}|digest=${input.digest}`;
119
+ // Re-parse to enforce strictness
120
+ parse(mid);
121
+ return mid;
122
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "zpk1-protocol",
3
+ "version": "0.1.0",
4
+ "description": "Strict parser, validator, and packer for the ZPK1 packed payload protocol.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "test": "vitest run",
22
+ "prepublishOnly": "npm run test && npm run build"
23
+ },
24
+ "keywords": [
25
+ "zpk1",
26
+ "protocol",
27
+ "validator",
28
+ "attestation",
29
+ "timestamp",
30
+ "zeus",
31
+ "hash"
32
+ ],
33
+ "author": "Drew",
34
+ "license": "MIT",
35
+ "devDependencies": {
36
+ "@types/node": "^20.0.0",
37
+ "typescript": "^5.0.0",
38
+ "vitest": "^1.0.0"
39
+ }
40
+ }