@hcs-witness/verify 0.0.0 → 1.0.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/dist/verify.js ADDED
@@ -0,0 +1,145 @@
1
+ import { createPublicKey, verify as cryptoVerify } from "node:crypto";
2
+ import { ml_dsa65 } from "@noble/post-quantum/ml-dsa.js";
3
+ import { b64urlToBytes } from "./base64.js";
4
+ import { canonicalBytes } from "./jcs.js";
5
+ import { isKeyset, } from "./keyset.js";
6
+ import { isProof, signedPayload } from "./schema.js";
7
+ export const ML_DSA_CONTEXT = new TextEncoder().encode("gateguard:proof:v1");
8
+ const SIGNALS_MAX_BYTES = 8192;
9
+ const ES256_SIG_BYTES = 64;
10
+ const MLDSA_SIG_BYTES = 3309;
11
+ /** Ordre du groupe P-256 (secp256r1) — constante publique NIST. */
12
+ const P256_ORDER = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
13
+ function parseTime(value) {
14
+ const t = Date.parse(value);
15
+ return Number.isNaN(t) ? Number.NaN : t;
16
+ }
17
+ function keyCoversIssuance(key, issuedMs) {
18
+ const nb = parseTime(key.not_before);
19
+ if (Number.isNaN(nb) || issuedMs < nb)
20
+ return false;
21
+ const na = key.not_after === undefined ? undefined : parseTime(key.not_after);
22
+ return na === undefined || Number.isNaN(na) || issuedMs <= na;
23
+ }
24
+ export function verifyProof(proof, keyset, opts) {
25
+ const errors = [];
26
+ const fail = (codes) => ({
27
+ valid: false,
28
+ errors: codes,
29
+ });
30
+ if (typeof proof !== "object" || proof === null || Array.isArray(proof)) {
31
+ return fail(["SCHEMA_INVALID"]);
32
+ }
33
+ const v = proof.v;
34
+ if (v !== 1 && v !== 2) {
35
+ return fail(v === undefined ? ["SCHEMA_INVALID"] : ["UNSUPPORTED_VERSION"]);
36
+ }
37
+ if (!isProof(proof)) {
38
+ return fail(["SCHEMA_INVALID"]);
39
+ }
40
+ const p = proof;
41
+ const issuedMs = parseTime(p.issued_at);
42
+ const expiresMs = parseTime(p.expires_at);
43
+ if (Number.isNaN(issuedMs) || Number.isNaN(expiresMs)) {
44
+ errors.push("SCHEMA_INVALID");
45
+ }
46
+ else if (expiresMs <= issuedMs) {
47
+ errors.push("TIMESTAMPS_INCOHERENT");
48
+ }
49
+ if (p.signals !== undefined) {
50
+ try {
51
+ if (canonicalBytes(p.signals).length > SIGNALS_MAX_BYTES) {
52
+ errors.push("SCHEMA_INVALID");
53
+ }
54
+ }
55
+ catch {
56
+ errors.push("SCHEMA_INVALID");
57
+ }
58
+ }
59
+ if (!isKeyset(keyset)) {
60
+ errors.push("KEYSET_INVALID");
61
+ return { valid: false, errors, proof_id: p.proof_id };
62
+ }
63
+ const ks = keyset;
64
+ const esKey = ks.keys.find((k) => k.alg === "ES256" && k.kid === p.sig.es256.kid);
65
+ const mlKey = ks.keys.find((k) => k.alg === "ML-DSA-65" && k.kid === p.sig.mldsa65.kid);
66
+ if (esKey === undefined)
67
+ errors.push("ES256_KID_UNKNOWN");
68
+ if (mlKey === undefined)
69
+ errors.push("MLDSA_KID_UNKNOWN");
70
+ if (!Number.isNaN(issuedMs) &&
71
+ ((esKey !== undefined && !keyCoversIssuance(esKey, issuedMs)) ||
72
+ (mlKey !== undefined && !keyCoversIssuance(mlKey, issuedMs)))) {
73
+ errors.push("KEY_NOT_VALID_AT_ISSUANCE");
74
+ }
75
+ let payload;
76
+ try {
77
+ payload = signedPayload(p);
78
+ }
79
+ catch {
80
+ errors.push("SCHEMA_INVALID");
81
+ return { valid: false, errors, proof_id: p.proof_id };
82
+ }
83
+ if (esKey !== undefined) {
84
+ const sig = b64urlToBytes(p.sig.es256.value);
85
+ if (sig?.length !== ES256_SIG_BYTES) {
86
+ errors.push("ES256_SIG_INVALID");
87
+ }
88
+ else {
89
+ const s = BigInt(`0x${[...sig.subarray(32)].map((b) => b.toString(16).padStart(2, "0")).join("")}`);
90
+ if (s > P256_ORDER / 2n) {
91
+ errors.push("ES256_HIGH_S");
92
+ }
93
+ else {
94
+ let ok = false;
95
+ try {
96
+ const pub = createPublicKey({
97
+ key: {
98
+ kty: "EC",
99
+ crv: "P-256",
100
+ x: esKey.x,
101
+ y: esKey.y,
102
+ },
103
+ format: "jwk",
104
+ });
105
+ // Signature brute r‖s (IEEE P1363) sur SHA-256(payload).
106
+ ok = cryptoVerify("sha256", payload, { key: pub, dsaEncoding: "ieee-p1363" }, sig);
107
+ }
108
+ catch {
109
+ ok = false;
110
+ }
111
+ if (!ok)
112
+ errors.push("ES256_SIG_INVALID");
113
+ }
114
+ }
115
+ }
116
+ if (mlKey !== undefined) {
117
+ const sig = b64urlToBytes(p.sig.mldsa65.value);
118
+ const pub = b64urlToBytes(mlKey.pub);
119
+ if (sig?.length !== MLDSA_SIG_BYTES) {
120
+ errors.push("MLDSA_SIG_INVALID");
121
+ }
122
+ else {
123
+ let ok = false;
124
+ try {
125
+ ok =
126
+ pub === null
127
+ ? false
128
+ : ml_dsa65.verify(sig, payload, pub, {
129
+ context: ML_DSA_CONTEXT,
130
+ });
131
+ }
132
+ catch {
133
+ ok = false;
134
+ }
135
+ if (!ok)
136
+ errors.push("MLDSA_SIG_INVALID");
137
+ }
138
+ }
139
+ if (opts?.now !== undefined &&
140
+ !Number.isNaN(expiresMs) &&
141
+ opts.now.getTime() > expiresMs) {
142
+ errors.push("PROOF_EXPIRED");
143
+ }
144
+ return { valid: errors.length === 0, errors, proof_id: p.proof_id };
145
+ }
package/package.json CHANGED
@@ -1,8 +1,73 @@
1
1
  {
2
2
  "name": "@hcs-witness/verify",
3
- "version": "0.0.0",
4
- "description": "Placeholder reserving the name. Use version 1.0.0 or later, published with provenance from https://github.com/hcs-witness/verify",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Offline verification library and CLI for HCS Witness proofs. Zero network access.",
5
6
  "license": "Apache-2.0",
6
- "repository": { "type": "git", "url": "git+https://github.com/hcs-witness/verify.git" },
7
- "files": ["README.md"]
7
+ "author": "AI Solution",
8
+ "engines": {
9
+ "node": ">=22"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "bin": {
20
+ "hcs-witness-verify": "bin/hcs-witness-verify.mjs"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "bin",
25
+ "README.md",
26
+ "LICENSE",
27
+ "CHANGELOG.md"
28
+ ],
29
+ "sideEffects": false,
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/hcs-witness/verify.git"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "provenance": true
37
+ },
38
+ "keywords": [
39
+ "hcs-witness",
40
+ "proof",
41
+ "verification",
42
+ "offline",
43
+ "es256",
44
+ "ml-dsa-65",
45
+ "opentimestamps",
46
+ "merkle"
47
+ ],
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.json",
50
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
51
+ "test": "vitest run",
52
+ "test:cov": "vitest run --coverage",
53
+ "format:check": "prettier --check .",
54
+ "format": "prettier --write .",
55
+ "lint": "eslint . --max-warnings 0",
56
+ "knip": "knip"
57
+ },
58
+ "dependencies": {
59
+ "@noble/post-quantum": "0.7.1",
60
+ "canonicalize": "5.1.0"
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "24.19.2",
64
+ "@vitest/coverage-v8": "5.1.0",
65
+ "eslint": "9.39.5",
66
+ "typescript-eslint": "8.72.1",
67
+ "fast-check": "4.10.2",
68
+ "knip": "6.44.1",
69
+ "prettier": "3.9.10",
70
+ "typescript": "6.0.3",
71
+ "vitest": "5.1.0"
72
+ }
8
73
  }