a11oy / static /shared /szl_receipt_cosign.js
betterwithage's picture
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
Raw
History Blame
11.3 kB
/* 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
};
});