Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"protocol": "TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFICATION_WITNESS",
"version": "v1.7.0",
"status": "PUBLIC_INDEPENDENT_VERIFICATION_WITNESS_BOUND",
"closure_claim": "A third party can verify TRUTHFRAMER public truth-frame continuity from public URLs only, without private source, local repository state, maintainer terminal output, or hidden context.",
"public_console_path": "/verifier/",
"public_verifier_script": "TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER.mjs",
"public_checked_url_count": 2,
"verifier": {
"protocol": "TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER",
"version": "v1.7.0",
"status": "PUBLIC_INDEPENDENT_VERIFICATION_PASS",
"public_inputs": {
"public_verification_capsule_url": "https://truthframer.github.io/truthframer-platform/capsule/TRUTHFRAMER_PUBLIC_VERIFICATION_CAPSULE.json",
"public_capsule_consumption_receipt_url": "https://truthframer.github.io/truthframer-platform/receipt/TRUTHFRAMER_PUBLIC_CAPSULE_CONSUMPTION_RECEIPT.json"
},
"public_object_digests": {
"capsule_raw_bytes_sha256": "8f1b6dc9cf953c91f1f300ee28d4b66378059d44c464989d00aa385f3c3163e4",
"capsule_canonical_json_sha256": "7d252db39d7f98ca1f239d3ecac2abaca0cfcad050c471e6efe1a1b7ec00f028",
"receipt_raw_bytes_sha256": "94ee2f308b5a3c3415534b9439b0ad394dd338d41e8d6eb8eebb9563c7b1df48",
"receipt_canonical_json_sha256": "ae2b0b1e7867d76ffb2df7ff97ebc6135e5b7b3eed5857b99a5d2161b75945f2"
},
"observed_public_claims": {
"capsule_sha256": "957b92e87d6e6ca0f341702e367cb454347b66eba143e4046e3f0e4a8489fe17",
"receipt_sha256": "ae2b0b1e7867d76ffb2df7ff97ebc6135e5b7b3eed5857b99a5d2161b75945f2",
"receipt_sha256_basis": "computed_public_receipt_canonical_json_sha256",
"receipt_capsule_sha256": "957b92e87d6e6ca0f341702e367cb454347b66eba143e4046e3f0e4a8489fe17",
"capsule_fetched_from_public_url": true,
"capsule_sha256_match": true,
"capsule_sha256_match_basis": "expected_sha256_found_inside_public_capsule_json",
"capsule_artifacts_live": true,
"release_tag_bound": true,
"no_private_source_required": true
},
"assertions": {
"capsule_public_fetch_ok": true,
"receipt_public_fetch_ok": true,
"capsule_declares_sha256": true,
"receipt_has_public_sha256_basis": true,
"receipt_capsule_sha256_matches_capsule": true,
"receipt_claims_capsule_public_fetch": true,
"receipt_claims_capsule_sha256_match": true,
"receipt_claims_capsule_artifacts_live": true,
"receipt_claims_release_tag_bound": true,
"receipt_claims_no_private_source_required": true,
"receipt_sha_basis_is_public_capsule_internal_digest": true
},
"verifier_result_sha256": "3de5138348ac2a3227ec2f60c3c062afeddf209a837664b8881f7f020529b2f1"
},
"witness_sha256": "132a366bc7aee1df8fddd0ebf87fc2d21d107afd915b51a489aaed13c12c2323"
}
227 changes: 227 additions & 0 deletions docs/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
const DEFAULTS = Object.freeze({
version: "v1.7.0",
capsuleUrl: "https://truthframer.github.io/truthframer-platform/capsule/TRUTHFRAMER_PUBLIC_VERIFICATION_CAPSULE.json",
receiptUrl: "https://truthframer.github.io/truthframer-platform/receipt/TRUTHFRAMER_PUBLIC_CAPSULE_CONSUMPTION_RECEIPT.json"
});

export function stableStringify(value) {
if (value === null || typeof value !== "object") return JSON.stringify(value);
if (Array.isArray(value)) return "[" + value.map(stableStringify).join(",") + "]";
const keys = Object.keys(value).sort();
return "{" + keys.map((key) => JSON.stringify(key) + ":" + stableStringify(value[key])).join(",") + "}";
}

export async function sha256Hex(input) {
const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;

if (globalThis.crypto && globalThis.crypto.subtle) {
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
}

const { createHash } = await import("node:crypto");
return createHash("sha256").update(Buffer.from(bytes)).digest("hex");
}

function normalizeKey(key) {
return String(key).replace(/[^a-zA-Z0-9]/g, "").toUpperCase();
}

function isSha256(value) {
return typeof value === "string" && /^[a-f0-9]{64}$/i.test(value);
}

function asString(value) {
if (value === null || value === undefined) return "";
return String(value);
}

function asBool(value) {
if (value === true) return true;
if (value === false) return false;
if (typeof value === "string") return value.toLowerCase() === "true";
return false;
}

function walk(value, path = [], out = []) {
if (!value || typeof value !== "object") return out;

if (Array.isArray(value)) {
value.forEach((item, index) => walk(item, path.concat(String(index)), out));
return out;
}

for (const [key, child] of Object.entries(value)) {
const itemPath = path.concat(key);
out.push({
key,
normalizedKey: normalizeKey(key),
normalizedPath: itemPath.map(normalizeKey).join("."),
value: child
});
walk(child, itemPath, out);
}

return out;
}

function firstBoolBySemanticKey(root, names) {
const wanted = new Set(names.map(normalizeKey));
const hit = walk(root).find((entry) => wanted.has(entry.normalizedKey));
return hit ? asBool(hit.value) : false;
}

function firstStringBySemanticKey(root, names) {
const wanted = new Set(names.map(normalizeKey));
const hit = walk(root).find((entry) => wanted.has(entry.normalizedKey));
return hit ? asString(hit.value) : "";
}

function shaCandidates(root, predicate) {
return walk(root)
.filter((entry) => isSha256(entry.value))
.filter(predicate)
.map((entry) => ({
key: entry.key,
path: entry.normalizedPath,
value: String(entry.value).toLowerCase()
}));
}

function preferredSha(root, predicate) {
const hits = shaCandidates(root, predicate);
if (!hits.length) return "";
const exact = hits.find((hit) => hit.path.split(".").length <= 3);
return (exact || hits[0]).value;
}

function capsuleShaFromCapsule(capsuleJson) {
return preferredSha(capsuleJson, (entry) => {
const k = entry.normalizedKey;
const p = entry.normalizedPath;
return (
k.endsWith("CAPSULESHA256") &&
!p.includes("RAWBYTES") &&
!p.includes("CANONICALJSON") &&
!p.includes("WITHOUTSHAFIELDS") &&
!p.includes("RECEIPT")
);
});
}

function capsuleShaFromReceipt(receiptJson) {
return preferredSha(receiptJson, (entry) => {
const k = entry.normalizedKey;
const p = entry.normalizedPath;
return (
k.endsWith("CAPSULESHA256") &&
!p.includes("RAWBYTES") &&
!p.includes("CANONICALJSON") &&
!p.includes("WITHOUTSHAFIELDS") &&
!p.includes("RECEIPT")
);
});
}

function receiptShaFromReceipt(receiptJson) {
return preferredSha(receiptJson, (entry) => {
const k = entry.normalizedKey;
const p = entry.normalizedPath;
return (
k.endsWith("RECEIPTSHA256") ||
k.endsWith("CONSUMPTIONRECEIPTSHA256") ||
p.endsWith("RECEIPTSHA256") ||
p.endsWith("CONSUMPTIONRECEIPTSHA256")
);
});
}

async function fetchPublicJson(url) {
const response = await fetch(url, { cache: "no-store" });
const text = await response.text();

if (!response.ok) {
throw new Error(`PUBLIC_FETCH_FAILED url=${url} status=${response.status}`);
}

return {
url,
status: response.status,
text,
json: JSON.parse(text),
raw_bytes_sha256: await sha256Hex(text),
canonical_json_sha256: await sha256Hex(stableStringify(JSON.parse(text)))
};
}

export async function verifyTruthframerPublicIndependently(config = {}) {
const capsuleUrl = config.capsuleUrl || DEFAULTS.capsuleUrl;
const receiptUrl = config.receiptUrl || DEFAULTS.receiptUrl;

const capsule = await fetchPublicJson(capsuleUrl);
const receipt = await fetchPublicJson(receiptUrl);

const capsuleDeclaredSha = capsuleShaFromCapsule(capsule.json);
const receiptCapsuleSha = capsuleShaFromReceipt(receipt.json);
const receiptDeclaredSha = receiptShaFromReceipt(receipt.json);
const receiptObservedSha = receiptDeclaredSha || receipt.canonical_json_sha256;

const claims = {
capsule_sha256: capsuleDeclaredSha,
receipt_sha256: receiptObservedSha,
receipt_sha256_basis: receiptDeclaredSha ? "declared_inside_public_receipt_json" : "computed_public_receipt_canonical_json_sha256",
receipt_capsule_sha256: receiptCapsuleSha,
capsule_fetched_from_public_url: firstBoolBySemanticKey(receipt.json, ["CAPSULE_FETCHED_FROM_PUBLIC_URL"]),
capsule_sha256_match: firstBoolBySemanticKey(receipt.json, ["CAPSULE_SHA256_MATCH"]),
capsule_sha256_match_basis: firstStringBySemanticKey(receipt.json, ["CAPSULE_SHA256_MATCH_BASIS"]),
capsule_artifacts_live: firstBoolBySemanticKey(receipt.json, ["CAPSULE_ARTIFACTS_LIVE"]),
release_tag_bound: firstBoolBySemanticKey(receipt.json, ["RELEASE_TAG_BOUND"]),
no_private_source_required: firstBoolBySemanticKey(receipt.json, ["NO_PRIVATE_SOURCE_REQUIRED"])
};

const assertions = {
capsule_public_fetch_ok: capsule.status === 200,
receipt_public_fetch_ok: receipt.status === 200,
capsule_declares_sha256: isSha256(claims.capsule_sha256),
receipt_has_public_sha256_basis: isSha256(claims.receipt_sha256),
receipt_capsule_sha256_matches_capsule: claims.receipt_capsule_sha256 === claims.capsule_sha256,
receipt_claims_capsule_public_fetch: claims.capsule_fetched_from_public_url === true,
receipt_claims_capsule_sha256_match: claims.capsule_sha256_match === true,
receipt_claims_capsule_artifacts_live: claims.capsule_artifacts_live === true,
receipt_claims_release_tag_bound: claims.release_tag_bound === true,
receipt_claims_no_private_source_required: claims.no_private_source_required === true,
receipt_sha_basis_is_public_capsule_internal_digest:
claims.capsule_sha256_match_basis === "expected_sha256_found_inside_public_capsule_json"
};

const pass = Object.values(assertions).every(Boolean);

const result = {
protocol: "TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER",
version: DEFAULTS.version,
status: pass ? "PUBLIC_INDEPENDENT_VERIFICATION_PASS" : "PUBLIC_INDEPENDENT_VERIFICATION_FAIL",
public_inputs: {
public_verification_capsule_url: capsuleUrl,
public_capsule_consumption_receipt_url: receiptUrl
},
public_object_digests: {
capsule_raw_bytes_sha256: capsule.raw_bytes_sha256,
capsule_canonical_json_sha256: capsule.canonical_json_sha256,
receipt_raw_bytes_sha256: receipt.raw_bytes_sha256,
receipt_canonical_json_sha256: receipt.canonical_json_sha256
},
observed_public_claims: claims,
assertions
};

result.verifier_result_sha256 = await sha256Hex(stableStringify(result));
return result;
}

if (typeof window !== "undefined") {
window.TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER = {
verifyTruthframerPublicIndependently,
stableStringify,
sha256Hex
};
}
46 changes: 46 additions & 0 deletions docs/verifier/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>TRUTHFRAMER Public Independent Verification Console</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root { color-scheme: dark; background:#050505; color:#d8d8d8; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
body { margin:0; padding:32px; }
main { max-width:1120px; margin:0 auto; }
h1 { font-size:18px; font-weight:600; letter-spacing:.04em; color:#f2f2f2; }
p { color:#9a9a9a; line-height:1.5; max-width:880px; }
pre { white-space:pre-wrap; word-break:break-word; background:#0b0b0b; border:1px solid #242424; padding:20px; border-radius:10px; }
.pass { color:#8ff0a4; }
.fail { color:#ff9b9b; }
</style>
</head>
<body>
<main>
<h1>TRUTHFRAMER Public Independent Verification Console</h1>
<p>
This page verifies the public capsule and public consumption receipt from public URLs only.
No private source, local repository state, maintainer terminal output, or hidden context is required.
</p>
<p id="status">VERIFYING...</p>
<pre id="output"></pre>
</main>
<script type="module">
import { verifyTruthframerPublicIndependently } from "./TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER.mjs";

const status = document.getElementById("status");
const output = document.getElementById("output");

try {
const result = await verifyTruthframerPublicIndependently();
status.textContent = result.status;
status.className = result.status.endsWith("_PASS") ? "pass" : "fail";
output.textContent = JSON.stringify(result, null, 2);
} catch (error) {
status.textContent = "PUBLIC_INDEPENDENT_VERIFICATION_ERROR";
status.className = "fail";
output.textContent = String(error && error.stack ? error.stack : error);
}
</script>
</body>
</html>
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "truthframer-platform",
"version": "1.6.0",
"version": "1.7.0",
"private": true,
"description": "The object spine for TRUTHFRAMER: replayable visual truth frames for market reality.",
"scripts": {
"test": "node scripts/verify-truthframer.js",
"verify": "node scripts/verify-truthframer.js",
"verify:render": "node scripts/verify-render.js",
"verify:public": "node scripts/verify-public-surface.js",
"verify:all": "npm run verify && npm run verify:render && npm run verify:public && npm run verify:registry && npm run verify:readme && npm run verify:tf000002 && npm run verify:tf000003 && npm run verify:tf000004 && npm run verify:root && npm run verify:audit && npm run verify:hardening && npm run verify:network-seal && npm run verify:verification-index && npm run verify:index-seal && npm run verify:verification-atlas && npm run verify:atlas-seal && npm run verify:stack-closure && npm run verify:stack-closure-seal && npm run verify:continuity-sentinel && npm run verify:continuity-sentinel-seal && npm run verify:release-closure && npm run verify:release-closure-seal && npm run verify:root-finality && npm run verify:root-finality-seal && npm run verify:privacy-perimeter && npm run verify:legal-privacy-perimeter && npm run verify:legal-privacy-perimeter-seal && npm run verify:legal-privacy-network && npm run verify:distribution-egress && npm run verify:distribution-egress-firewall && npm run verify:distribution-egress-firewall-seal && npm run verify:distribution-egress-network && npm run verify:source-provenance && npm run verify:source-provenance-seal && npm run verify:source-provenance-network && npm run verify:verification-purity && npm run verify:verification-immutability && npm run verify:verification-immutability-seal && npm run verify:verification-immutability-network && npm run verify:truth-frame-spine && npm run verify:truth-frame-spine-network && npm run verify:truth-frame-admission && npm run verify:truth-frame-admission-network && npm run verify:truth-frame-admission-refusal && npm run verify:truth-frame-admission-refusal-network && npm run verify:truth-frame-admission-closure && npm run verify:truth-frame-admission-closure-network && npm run verify:public-verification-capsule && npm run verify:public-verification-capsule-network && npm run verify:public-capsule-consumption-receipt && npm run verify:public-capsule-consumption-receipt-network",
"verify:all": "npm run verify && npm run verify:render && npm run verify:public && npm run verify:registry && npm run verify:readme && npm run verify:tf000002 && npm run verify:tf000003 && npm run verify:tf000004 && npm run verify:root && npm run verify:audit && npm run verify:hardening && npm run verify:network-seal && npm run verify:verification-index && npm run verify:index-seal && npm run verify:verification-atlas && npm run verify:atlas-seal && npm run verify:stack-closure && npm run verify:stack-closure-seal && npm run verify:continuity-sentinel && npm run verify:continuity-sentinel-seal && npm run verify:release-closure && npm run verify:release-closure-seal && npm run verify:root-finality && npm run verify:root-finality-seal && npm run verify:privacy-perimeter && npm run verify:legal-privacy-perimeter && npm run verify:legal-privacy-perimeter-seal && npm run verify:legal-privacy-network && npm run verify:distribution-egress && npm run verify:distribution-egress-firewall && npm run verify:distribution-egress-firewall-seal && npm run verify:distribution-egress-network && npm run verify:source-provenance && npm run verify:source-provenance-seal && npm run verify:source-provenance-network && npm run verify:verification-purity && npm run verify:verification-immutability && npm run verify:verification-immutability-seal && npm run verify:verification-immutability-network && npm run verify:truth-frame-spine && npm run verify:truth-frame-spine-network && npm run verify:truth-frame-admission && npm run verify:truth-frame-admission-network && npm run verify:truth-frame-admission-refusal && npm run verify:truth-frame-admission-refusal-network && npm run verify:truth-frame-admission-closure && npm run verify:truth-frame-admission-closure-network && npm run verify:public-verification-capsule && npm run verify:public-verification-capsule-network && npm run verify:public-capsule-consumption-receipt && npm run verify:public-capsule-consumption-receipt-network && npm run verify:public-independent-verification-witness",
"verify:registry": "node scripts/verify-registry.js",
"verify:readme": "node scripts/verify-readme-public-entry.js",
"verify:tf000002": "node scripts/verify-tf-000002.js",
Expand Down Expand Up @@ -72,7 +72,10 @@
"verify:public-verification-capsule-network": "node scripts/verify-public-verification-capsule-network.js",
"generate:public-capsule-consumption-receipt": "node scripts/generate-public-capsule-consumption-receipt.js",
"verify:public-capsule-consumption-receipt": "node scripts/verify-public-capsule-consumption-receipt.js",
"verify:public-capsule-consumption-receipt-network": "node scripts/verify-public-capsule-consumption-receipt-network.js"
"verify:public-capsule-consumption-receipt-network": "node scripts/verify-public-capsule-consumption-receipt-network.js",
"generate:public-independent-verification-witness": "node scripts/generate-public-independent-verification-witness.js",
"verify:public-independent-verification-witness": "node scripts/verify-public-independent-verification-witness.js",
"verify:public-independent-verification-witness-network": "node scripts/verify-public-independent-verification-witness-network.js"
},
"license": "UNLICENSED"
}
Loading
Loading