Spaces:
Running
Running
Dev5 ecosystem foundation (direct HF push): byte-identical shared modules + ecosystem router + serve.py registration + Dockerfile COPY. Fixes partial GitHub auto-sync; unblocks COPY.
22595c8 verified | /* SPDX-License-Identifier: Apache-2.0 | |
| * (c) 2026 Lutar, Stephen P. - SZL Holdings - ORCID 0009-0001-0110-4173 | |
| * ============================================================================ | |
| * szl_receipt_cosign.js - SHARED MODULE (byte-identical across a11oy + killinchu) | |
| * ---------------------------------------------------------------------------- | |
| * The single DSSE receipt + cosign-verify primitive for the SZL estate. | |
| * NO CDN, no build step. Browser-native SubtleCrypto only. Node-safe import. | |
| * | |
| * *** ONE documented signing scheme for the whole estate: *** | |
| * ECDSA P-256 (SECP256R1) over SHA-256 == Sigstore cosign default. | |
| * | |
| * WHY P-256 and NOT Ed25519 (resolving the signing-scheme drift, gate G4): | |
| * - The deployed estate, szl_dsse.py (byte-identical in both apps), every | |
| * server verify path, and the PUBLISHED key szl-holdings/.github/cosign.pub | |
| * are ALL ECDSA P-256. cosign sign-blob / verify-blob round-trips against it. | |
| * - a11oy_signing_key.py states the curve is "deliberately NOT Ed25519" to | |
| * match cosign.pub and every existing verify path. | |
| * - Switching to Ed25519 would orphan cosign.pub and break cosign-CLI | |
| * interop. Doctrine G8 ("don't claim/ship more than is real") => keep the | |
| * scheme that actually verifies. The Ed25519 suggestion is therefore | |
| * RESOLVED in favor of ECDSA-P256. HMAC is forbidden (symmetric, not | |
| * publicly verifiable). This is the single source of truth for clients. | |
| * | |
| * DSSE (secure-systems-lab/dsse) PAE pre-authentication encoding (must match | |
| * szl_dsse.py byte-for-byte): | |
| * PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body | |
| * SIGNATURE = ECDSA-P256-SHA256( PAE(payloadType, canonical_body) ) | |
| * | |
| * HONESTY (gate G7/G8): this module NEVER fabricates a signature. Signing is | |
| * done by the SAME-ORIGIN server endpoint POST /khipu/sign (which holds the | |
| * szlholdings-cosign private key). The client: | |
| * - builds the canonical receipt + chain hash (SHA-256, re-checkable), | |
| * - asks the server to sign, | |
| * - records keyid + sig_type returned (truthfully), | |
| * - if the signer is unreachable OR returns an ephemeral/non-canonical key, | |
| * marks the receipt UNSIGNED / CROSS-APP-PENDING and shows it honestly. | |
| * | |
| * Public API (window.SZLReceipts / module.exports): | |
| * SCHEME - "ECDSA-P256-SHA256" (frozen) | |
| * COSIGN_KEYID - "szlholdings-cosign" (frozen) | |
| * COSIGN_PUB_URL - canonical published key URL | |
| * canonicalJSON(obj) - deterministic JSON (sorted keys) | |
| * sha256Hex(strOrBytes) -> Promise - SubtleCrypto SHA-256 hex | |
| * pae(type, bodyBytes) -> Uint8Array - DSSE PAE bytes | |
| * chainHash(prevHash, receipt) -> P - SHA-256 of (prev_hash || canonical) | |
| * signReceipt(receipt, opts) -> P - POST /khipu/sign; honest never-fake | |
| * verifyEnvelope(env, opts) -> P - POST /khipu/verify; cross-app verdict | |
| * isCanonicalKey(keyid) - keyid === szlholdings-cosign ? | |
| * newChain(opts) - a small append-only signed chain helper | |
| * ============================================================================ */ | |
| (function (root, factory) { | |
| var mod = factory(); | |
| if (typeof module === "object" && module.exports) { module.exports = mod; } | |
| if (root) { root.SZLReceipts = mod; } | |
| })(typeof self !== "undefined" ? self : (typeof window !== "undefined" ? window : null), function () { | |
| "use strict"; | |
| var SCHEME = "ECDSA-P256-SHA256"; | |
| var COSIGN_KEYID = "szlholdings-cosign"; | |
| var COSIGN_PUB_URL = "https://github.com/szl-holdings/.github/blob/main/cosign.pub"; | |
| var KHIPU_PAYLOAD_TYPE = "application/vnd.szl.khipu+json"; | |
| function _subtle() { | |
| var c = (typeof self !== "undefined" && self.crypto) ? self.crypto | |
| : (typeof window !== "undefined" && window.crypto) ? window.crypto | |
| : (typeof globalThis !== "undefined" && globalThis.crypto) ? globalThis.crypto : null; | |
| return c && c.subtle ? c.subtle : null; | |
| } | |
| /* Deterministic canonical JSON: object keys sorted recursively, no spaces. | |
| * MUST match szl_dsse.canonical_json (json.dumps(sort_keys, separators=(',',':'))). */ | |
| function canonicalJSON(obj) { | |
| if (obj === null || typeof obj !== "object") { return JSON.stringify(obj); } | |
| if (Object.prototype.toString.call(obj) === "[object Array]") { | |
| var items = []; for (var i = 0; i < obj.length; i++) { items.push(canonicalJSON(obj[i])); } | |
| return "[" + items.join(",") + "]"; | |
| } | |
| var keys = []; for (var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) { keys.push(k); } } | |
| keys.sort(); | |
| var parts = []; for (var j = 0; j < keys.length; j++) { parts.push(JSON.stringify(keys[j]) + ":" + canonicalJSON(obj[keys[j]])); } | |
| return "{" + parts.join(",") + "}"; | |
| } | |
| function _toBytes(strOrBytes) { | |
| if (strOrBytes instanceof Uint8Array) { return strOrBytes; } | |
| var s = String(strOrBytes); | |
| if (typeof TextEncoder !== "undefined") { return new TextEncoder().encode(s); } | |
| var arr = []; for (var i = 0; i < s.length; i++) { arr.push(s.charCodeAt(i) & 0xff); } | |
| return new Uint8Array(arr); | |
| } | |
| function _hex(buf) { | |
| var b = new Uint8Array(buf), h = ""; for (var i = 0; i < b.length; i++) { h += (b[i] < 16 ? "0" : "") + b[i].toString(16); } | |
| return h; | |
| } | |
| function sha256Hex(strOrBytes) { | |
| var sub = _subtle(); | |
| if (!sub) { return Promise.reject(new Error("SubtleCrypto unavailable (needs HTTPS/secure context)")); } | |
| return sub.digest("SHA-256", _toBytes(strOrBytes)).then(_hex); | |
| } | |
| /* DSSE PAE bytes. Lengths are decimal ASCII of the UTF-8 byte length. */ | |
| function pae(payloadType, bodyBytes) { | |
| var t = _toBytes(payloadType), b = (bodyBytes instanceof Uint8Array) ? bodyBytes : _toBytes(bodyBytes); | |
| var head = "DSSEv1 " + t.length + " "; | |
| var mid = " " + b.length + " "; | |
| var pre = _toBytes(head), tb = t, midb = _toBytes(mid); | |
| var out = new Uint8Array(pre.length + tb.length + midb.length + b.length); | |
| var o = 0; | |
| out.set(pre, o); o += pre.length; | |
| out.set(tb, o); o += tb.length; | |
| out.set(midb, o); o += midb.length; | |
| out.set(b, o); | |
| return out; | |
| } | |
| function chainHash(prevHash, receipt) { | |
| var canonical = canonicalJSON(receipt); | |
| return sha256Hex(String(prevHash || "0") + "|" + canonical).then(function (h) { | |
| return { hash: h, canonical: canonical }; | |
| }); | |
| } | |
| function isCanonicalKey(keyid) { return keyid === COSIGN_KEYID; } | |
| /* Ask the SAME-ORIGIN server to sign. NEVER fabricates a signature. | |
| * opts: { base:"", payloadType, fetchImpl } */ | |
| function signReceipt(receipt, opts) { | |
| opts = opts || {}; | |
| var base = opts.base || ""; | |
| var f = opts.fetchImpl || (typeof fetch !== "undefined" ? fetch : null); | |
| if (!f) { return Promise.reject(new Error("fetch unavailable")); } | |
| var body = JSON.stringify(receipt); | |
| return f(base + "/khipu/sign", { | |
| method: "POST", headers: { "Content-Type": "application/json" }, body: body | |
| }).then(function (resp) { | |
| var ct = resp.headers && resp.headers.get ? (resp.headers.get("content-type") || "") : ""; | |
| if (!resp.ok) { return resp.text().then(function (t) { throw new Error("signer HTTP " + resp.status + ": " + t.slice(0, 160)); }); } | |
| if (ct.indexOf("application/json") === -1) { throw new Error("signer returned non-JSON (route not mounted)"); } | |
| return resp.json(); | |
| }).then(function (j) { | |
| var env = j.envelope || j; | |
| var sig0 = (env.signatures && env.signatures[0]) || null; | |
| var keyid = sig0 ? sig0.keyid : null; | |
| var sigType = (sig0 && sig0.sig_type) || (j.sig_types && j.sig_types[0]) || SCHEME; | |
| var hasSig = !!(sig0 && sig0.sig); | |
| var canonical = isCanonicalKey(keyid); | |
| return { | |
| signed: hasSig, | |
| canonicalKey: canonical, | |
| crossAppVerifiable: hasSig && canonical, | |
| keyid: keyid, | |
| sigType: sigType, | |
| sig: sig0 ? sig0.sig : null, | |
| envelope: env, | |
| signerNote: hasSig | |
| ? (canonical ? "" : "signer used a NON-CANONICAL key (" + keyid + "); cross-app verify PENDING operator cosign secret") | |
| : "signer returned no signature - receipt is UNSIGNED (honest)" | |
| }; | |
| })["catch"](function (err) { | |
| // HONEST failure - never fake a signature | |
| return { | |
| signed: false, canonicalKey: false, crossAppVerifiable: false, | |
| keyid: null, sigType: null, sig: null, envelope: null, | |
| signerNote: "signer unreachable - receipt is UNSIGNED (honest): " + err.message | |
| }; | |
| }); | |
| } | |
| /* Verify a DSSE envelope via the server /khipu/verify (verifies against the | |
| * published cosign.pub). Returns a truthful verdict. */ | |
| function verifyEnvelope(envelope, opts) { | |
| opts = opts || {}; | |
| var base = opts.base || ""; | |
| var f = opts.fetchImpl || (typeof fetch !== "undefined" ? fetch : null); | |
| if (!f) { return Promise.reject(new Error("fetch unavailable")); } | |
| return f(base + "/khipu/verify", { | |
| method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(envelope) | |
| }).then(function (resp) { | |
| if (!resp.ok) { throw new Error("verify HTTP " + resp.status); } | |
| return resp.json(); | |
| }).then(function (j) { | |
| return { | |
| verified: j.verified === true, | |
| keyidMatch: j.keyid_match === true, | |
| keyidExpected: (j.detail && j.detail.keyid_expected) || COSIGN_KEYID, | |
| pubFingerprint: (j.detail && j.detail.pub_fingerprint_sha256) || null, | |
| verifyKeyUrl: (j.detail && j.detail.verify_key_url) || COSIGN_PUB_URL, | |
| raw: j | |
| }; | |
| }); | |
| } | |
| /* Small append-only signed chain helper. Each push() hashes (prev||receipt), | |
| * signs via the server, and links. Honest about UNSIGNED / cross-app state. */ | |
| function newChain(opts) { | |
| opts = opts || {}; | |
| var base = opts.base || ""; | |
| var chain = []; | |
| var tip = "0"; | |
| return { | |
| receipts: chain, | |
| tip: function () { return tip; }, | |
| push: function (action, data) { | |
| var seq = chain.length; | |
| var receipt = { action: String(action), seq: seq, prev_hash: tip, data: data || {}, ts: new Date().toISOString() }; | |
| return chainHash(tip, receipt).then(function (ch) { | |
| receipt.hash = ch.hash; receipt.canonical = ch.canonical; | |
| return signReceipt({ action: receipt.action, seq: seq, prev_hash: tip, data: receipt.data }, { base: base }); | |
| }).then(function (sg) { | |
| receipt.signed = sg.signed; receipt.keyid = sg.keyid; receipt.sigType = sg.sigType; | |
| receipt.sig = sg.sig; receipt.canonicalKey = sg.canonicalKey; | |
| receipt.crossAppVerifiable = sg.crossAppVerifiable; receipt.signerNote = sg.signerNote; | |
| chain.push(receipt); tip = receipt.hash; | |
| return receipt; | |
| }); | |
| } | |
| }; | |
| } | |
| try { /* freeze constants */ } catch (e) {} | |
| return { | |
| VERSION: "1.0.0", | |
| SCHEME: SCHEME, | |
| COSIGN_KEYID: COSIGN_KEYID, | |
| COSIGN_PUB_URL: COSIGN_PUB_URL, | |
| PAYLOAD_TYPE: KHIPU_PAYLOAD_TYPE, | |
| canonicalJSON: canonicalJSON, | |
| sha256Hex: sha256Hex, | |
| pae: pae, | |
| chainHash: chainHash, | |
| isCanonicalKey: isCanonicalKey, | |
| signReceipt: signReceipt, | |
| verifyEnvelope: verifyEnvelope, | |
| newChain: newChain | |
| }; | |
| }); | |