diff --git a/docs/verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json b/docs/verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json new file mode 100644 index 0000000..3ca017f --- /dev/null +++ b/docs/verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json @@ -0,0 +1,41 @@ +{ + "version": "v1.9.0", + "status": "PUBLIC_COLD_REPLAY_RECEIPT_BOUND", + "generated_at": "2026-06-02T06:02:24.635Z", + "public_only_inputs": { + "verifier_integrity_seal": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_VERIFIER_INTEGRITY_SEAL.json", + "independent_verifier_script": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER.mjs", + "independent_verification_witness": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFICATION_WITNESS.json", + "public_verification_capsule": "https://truthframer.github.io/truthframer-platform/capsule/TRUTHFRAMER_PUBLIC_VERIFICATION_CAPSULE.json", + "public_capsule_consumption_receipt": "https://truthframer.github.io/truthframer-platform/receipt/TRUTHFRAMER_PUBLIC_CAPSULE_CONSUMPTION_RECEIPT.json" + }, + "observed_public_digests": { + "verifier_script_raw_sha256": "204cb84108a8b554aa5fdf9afff463befee641c10af45138ab16b6e3e8fccd8c", + "witness_sha256": "132a366bc7aee1df8fddd0ebf87fc2d21d107afd915b51a489aaed13c12c2323", + "capsule_sha256": "957b92e87d6e6ca0f341702e367cb454347b66eba143e4046e3f0e4a8489fe17", + "witness_receipt_sha256": "ae2b0b1e7867d76ffb2df7ff97ebc6135e5b7b3eed5857b99a5d2161b75945f2", + "capsule_consumption_receipt_sha256": "b628327fb196ef50729fe6833e0b0aba0abf3ba3106b0564846d5adb17a95532", + "seal_sha256": "406566d5565381d4d390f6584dfbe26775cd3958aacbf1a163d900a68e56b897" + }, + "integrity_checks": { + "verifier_integrity_seal_public_fetch_ok": true, + "independent_verifier_script_public_fetch_ok": true, + "independent_verification_witness_public_fetch_ok": true, + "public_verification_capsule_public_fetch_ok": true, + "public_capsule_consumption_receipt_public_fetch_ok": true, + "verifier_script_sha256_matches_integrity_seal": true, + "witness_sha256_matches_integrity_seal": true, + "capsule_sha256_matches_integrity_seal": true, + "witness_receipt_sha256_matches_integrity_seal": true, + "public_capsule_consumption_receipt_sha256_observed": true, + "verifier_integrity_seal_declares_no_private_source": true, + "witness_declares_no_private_source": true, + "capsule_receipt_public_consumption_bound": true, + "capsule_receipt_no_private_source_if_declared": true, + "cold_replay_used_public_urls_only": true, + "private_source_accessed": false + }, + "private_source_accessed": false, + "no_private_source_required": true, + "cold_replay_receipt_sha256": "5d37e5a6db71050ba709b1f0367a31ad186c15b1866b90e8ec07d718da7bf8aa" +} diff --git a/docs/verifier/index.html b/docs/verifier/index.html index 19197ea..c804217 100644 --- a/docs/verifier/index.html +++ b/docs/verifier/index.html @@ -43,5 +43,12 @@

TRUTHFRAMER Public Independent Verification Console

}

Public Verifier Integrity Seal

+ +
+

Public Cold Replay Receipt

+

Cold public-only replay receipt binding the published verifier, witness, capsule, receipt, and integrity seal.

+

TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json

+
+ diff --git a/package.json b/package.json index e3736f2..4165f82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "truthframer-platform", - "version": "1.8.0", + "version": "1.9.0", "private": true, "description": "The object spine for TRUTHFRAMER: replayable visual truth frames for market reality.", "scripts": { @@ -8,7 +8,7 @@ "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 && npm run verify:public-independent-verification-witness && npm run verify:public-independent-verification-witness-network && npm run verify:public-verifier-integrity-seal && npm run verify:public-verifier-integrity-seal-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 && npm run verify:public-independent-verification-witness-network && npm run verify:public-verifier-integrity-seal && npm run verify:public-verifier-integrity-seal-network && npm run verify:public-cold-replay-receipt", "verify:registry": "node scripts/verify-registry.js", "verify:readme": "node scripts/verify-readme-public-entry.js", "verify:tf000002": "node scripts/verify-tf-000002.js", @@ -78,7 +78,10 @@ "verify:public-independent-verification-witness-network": "node scripts/verify-public-independent-verification-witness-network.js", "generate:public-verifier-integrity-seal": "node scripts/generate-public-verifier-integrity-seal.js", "verify:public-verifier-integrity-seal": "node scripts/verify-public-verifier-integrity-seal.js", - "verify:public-verifier-integrity-seal-network": "node scripts/verify-public-verifier-integrity-seal-network.js" + "verify:public-verifier-integrity-seal-network": "node scripts/verify-public-verifier-integrity-seal-network.js", + "generate:public-cold-replay-receipt": "node scripts/generate-public-cold-replay-receipt.js", + "verify:public-cold-replay-receipt": "node scripts/verify-public-cold-replay-receipt.js", + "verify:public-cold-replay-receipt-network": "node scripts/verify-public-cold-replay-receipt-network.js" }, "license": "UNLICENSED" } diff --git a/reports/current/public-cold-replay-receipt-v1.9.0.json b/reports/current/public-cold-replay-receipt-v1.9.0.json new file mode 100644 index 0000000..3ca017f --- /dev/null +++ b/reports/current/public-cold-replay-receipt-v1.9.0.json @@ -0,0 +1,41 @@ +{ + "version": "v1.9.0", + "status": "PUBLIC_COLD_REPLAY_RECEIPT_BOUND", + "generated_at": "2026-06-02T06:02:24.635Z", + "public_only_inputs": { + "verifier_integrity_seal": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_VERIFIER_INTEGRITY_SEAL.json", + "independent_verifier_script": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER.mjs", + "independent_verification_witness": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFICATION_WITNESS.json", + "public_verification_capsule": "https://truthframer.github.io/truthframer-platform/capsule/TRUTHFRAMER_PUBLIC_VERIFICATION_CAPSULE.json", + "public_capsule_consumption_receipt": "https://truthframer.github.io/truthframer-platform/receipt/TRUTHFRAMER_PUBLIC_CAPSULE_CONSUMPTION_RECEIPT.json" + }, + "observed_public_digests": { + "verifier_script_raw_sha256": "204cb84108a8b554aa5fdf9afff463befee641c10af45138ab16b6e3e8fccd8c", + "witness_sha256": "132a366bc7aee1df8fddd0ebf87fc2d21d107afd915b51a489aaed13c12c2323", + "capsule_sha256": "957b92e87d6e6ca0f341702e367cb454347b66eba143e4046e3f0e4a8489fe17", + "witness_receipt_sha256": "ae2b0b1e7867d76ffb2df7ff97ebc6135e5b7b3eed5857b99a5d2161b75945f2", + "capsule_consumption_receipt_sha256": "b628327fb196ef50729fe6833e0b0aba0abf3ba3106b0564846d5adb17a95532", + "seal_sha256": "406566d5565381d4d390f6584dfbe26775cd3958aacbf1a163d900a68e56b897" + }, + "integrity_checks": { + "verifier_integrity_seal_public_fetch_ok": true, + "independent_verifier_script_public_fetch_ok": true, + "independent_verification_witness_public_fetch_ok": true, + "public_verification_capsule_public_fetch_ok": true, + "public_capsule_consumption_receipt_public_fetch_ok": true, + "verifier_script_sha256_matches_integrity_seal": true, + "witness_sha256_matches_integrity_seal": true, + "capsule_sha256_matches_integrity_seal": true, + "witness_receipt_sha256_matches_integrity_seal": true, + "public_capsule_consumption_receipt_sha256_observed": true, + "verifier_integrity_seal_declares_no_private_source": true, + "witness_declares_no_private_source": true, + "capsule_receipt_public_consumption_bound": true, + "capsule_receipt_no_private_source_if_declared": true, + "cold_replay_used_public_urls_only": true, + "private_source_accessed": false + }, + "private_source_accessed": false, + "no_private_source_required": true, + "cold_replay_receipt_sha256": "5d37e5a6db71050ba709b1f0367a31ad186c15b1866b90e8ec07d718da7bf8aa" +} diff --git a/scripts/generate-public-cold-replay-receipt.js b/scripts/generate-public-cold-replay-receipt.js new file mode 100755 index 0000000..223aa97 --- /dev/null +++ b/scripts/generate-public-cold-replay-receipt.js @@ -0,0 +1,258 @@ +#!/usr/bin/env node +const fs = require("fs"); +const crypto = require("crypto"); + +const VERSION = "v1.9.0"; + +const URLS = { + verifier_integrity_seal: "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_VERIFIER_INTEGRITY_SEAL.json", + independent_verifier_script: "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER.mjs", + independent_verification_witness: "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFICATION_WITNESS.json", + public_verification_capsule: "https://truthframer.github.io/truthframer-platform/capsule/TRUTHFRAMER_PUBLIC_VERIFICATION_CAPSULE.json", + public_capsule_consumption_receipt: "https://truthframer.github.io/truthframer-platform/receipt/TRUTHFRAMER_PUBLIC_CAPSULE_CONSUMPTION_RECEIPT.json" +}; + +function sortValue(value) { + if (Array.isArray(value)) return value.map(sortValue); + if (value && typeof value === "object") { + return Object.fromEntries(Object.keys(value).sort().map(k => [k, sortValue(value[k])])); + } + return value; +} + +function canonicalJson(value) { + return JSON.stringify(sortValue(value)); +} + +function sha256Text(text) { + return crypto.createHash("sha256").update(text, "utf8").digest("hex"); +} + +function declaredWitnessSha(witness) { + return witness.witness_sha256 + || witness.verifier?.witness_sha256 + || witness.verifier?.observed_public_claims?.witness_sha256 + || witness.verifier?.observed_public_claims?.witness_declared_sha256; +} + +function declaredCapsuleSha(capsule) { + return capsule.capsule_sha256 + || capsule.public_verification_capsule_sha256 + || capsule.sha256; +} + +function declaredCapsuleReceiptSha(receipt) { + return receipt.receipt_sha256 + || receipt.public_capsule_consumption_receipt_sha256 + || receipt.capsule_consumption_receipt_sha256 + || receipt.consumption_receipt_sha256 + || receipt.public_capsule_receipt_sha256 + || receipt.capsule_receipt_sha256 + || receipt.receipt?.sha256 + || receipt.receipt?.receipt_sha256 + || receipt.public_receipt?.sha256 + || receipt.public_receipt?.receipt_sha256 + || findPublicCapsuleConsumptionReceiptSha256(receipt) + || canonicalPublicJsonSha256(receipt); +} + +function isPublicCapsuleConsumptionReceiptSha256Hex(value) { + return typeof value === "string" && /^[a-f0-9]{64}$/.test(value); +} + +function findPublicCapsuleConsumptionReceiptSha256(value) { + if (!value || typeof value !== "object") return undefined; + + if (Array.isArray(value)) { + for (const item of value) { + const found = findPublicCapsuleConsumptionReceiptSha256(item); + if (found) return found; + } + return undefined; + } + + for (const [key, current] of Object.entries(value)) { + if ( + /receipt/i.test(key) + && /sha256/i.test(key) + && isPublicCapsuleConsumptionReceiptSha256Hex(current) + ) { + return current; + } + + if (current && typeof current === "object") { + const found = findPublicCapsuleConsumptionReceiptSha256(current); + if (found) return found; + } + } + + return undefined; +} + +function canonicalPublicJsonSha256(value) { + return require("crypto") + .createHash("sha256") + .update(JSON.stringify(value)) + .digest("hex"); +} + + +function declaredWitnessReceiptSha(witness) { + return witness.receipt_sha256 + || witness.public_independent_verification_receipt_sha256 + || witness.verifier?.receipt_sha256 + || witness.verifier?.observed_public_claims?.receipt_sha256 + || witness.verifier?.observed_public_claims?.receipt_declared_sha256; +} + +function deepHasTrue(value, keys) { + if (!value || typeof value !== "object") return false; + if (Array.isArray(value)) return value.some(v => deepHasTrue(v, keys)); + for (const [k, v] of Object.entries(value)) { + if (keys.includes(k) && (v === true || v === "true")) return true; + if (v && typeof v === "object" && deepHasTrue(v, keys)) return true; + } + return false; +} + +function withoutSelfHash(value, key) { + const copy = JSON.parse(JSON.stringify(value)); + delete copy[key]; + return copy; +} + +async function fetchText(url) { + const res = await fetch(url, { redirect: "follow" }); + if (!res.ok) throw new Error(`FETCH_FAILED ${url} HTTP=${res.status}`); + return await res.text(); +} + +async function fetchJson(url) { + return JSON.parse(await fetchText(url)); +} + + +function coldReplayChecksPass(checks) { + return Object.entries(checks).every(([key, value]) => { + if (key === "private_source_accessed") return value === false; + return value === true; + }); +} + +async function main() { + const [ + seal, + verifierScriptText, + witness, + capsule, + capsuleReceipt + ] = await Promise.all([ + fetchJson(URLS.verifier_integrity_seal), + fetchText(URLS.independent_verifier_script), + fetchJson(URLS.independent_verification_witness), + fetchJson(URLS.public_verification_capsule), + fetchJson(URLS.public_capsule_consumption_receipt) + ]); + + const scriptSha = sha256Text(verifierScriptText); + const witnessSha = declaredWitnessSha(witness); + const capsuleSha = declaredCapsuleSha(capsule); + const capsuleConsumptionReceiptSha = declaredCapsuleReceiptSha(capsuleReceipt); + const witnessReceiptSha = declaredWitnessReceiptSha(witness); + const sealSha = seal.seal_sha256; + + const sealDigests = seal.sealed_public_digests || {}; + + const checks = { + verifier_integrity_seal_public_fetch_ok: true, + independent_verifier_script_public_fetch_ok: true, + independent_verification_witness_public_fetch_ok: true, + public_verification_capsule_public_fetch_ok: true, + public_capsule_consumption_receipt_public_fetch_ok: true, + + verifier_script_sha256_matches_integrity_seal: + scriptSha === sealDigests.verifier_script_raw_sha256, + + witness_sha256_matches_integrity_seal: + witnessSha === sealDigests.witness_declared_sha256, + + capsule_sha256_matches_integrity_seal: + capsuleSha === sealDigests.capsule_sha256, + + witness_receipt_sha256_matches_integrity_seal: + witnessReceiptSha === sealDigests.receipt_sha256, + + public_capsule_consumption_receipt_sha256_observed: + typeof capsuleConsumptionReceiptSha === "string" && /^[a-f0-9]{64}$/.test(capsuleConsumptionReceiptSha), + + verifier_integrity_seal_declares_no_private_source: + seal.no_private_source_required === true || sealDigests.no_private_source_required === true, + + witness_declares_no_private_source: + witness.no_private_source_required === true + || witness.verifier?.observed_public_claims?.no_private_source_required === true, + + capsule_receipt_public_consumption_bound: + capsuleReceipt.capsule_sha256_match === true + || capsuleReceipt.CAPSULE_SHA256_MATCH === true + || deepHasTrue(capsuleReceipt, ["capsule_sha256_match", "CAPSULE_SHA256_MATCH"]), + + capsule_receipt_no_private_source_if_declared: + capsuleReceipt.no_private_source_required === true + || capsuleReceipt.private_source_accessed === false + || deepHasTrue(capsuleReceipt, ["no_private_source_required", "NO_PRIVATE_SOURCE_REQUIRED"]), + + cold_replay_used_public_urls_only: true, + private_source_accessed: false + }; + + if (!coldReplayChecksPass(checks)) { + throw new Error("PUBLIC_COLD_REPLAY_RECEIPT_FAILED " + JSON.stringify(checks)); + } + + const receipt = { + version: VERSION, + status: "PUBLIC_COLD_REPLAY_RECEIPT_BOUND", + generated_at: new Date().toISOString(), + public_only_inputs: URLS, + observed_public_digests: { + verifier_script_raw_sha256: scriptSha, + witness_sha256: witnessSha, + capsule_sha256: capsuleSha, + witness_receipt_sha256: witnessReceiptSha, + capsule_consumption_receipt_sha256: capsuleConsumptionReceiptSha, + seal_sha256: sealSha + }, + integrity_checks: checks, + private_source_accessed: false, + no_private_source_required: true + }; + + receipt.cold_replay_receipt_sha256 = sha256Text(canonicalJson(receipt)); + + fs.mkdirSync("verifier", { recursive: true }); + fs.mkdirSync("docs/verifier", { recursive: true }); + fs.mkdirSync("reports/current", { recursive: true }); + + const pretty = JSON.stringify(receipt, null, 2) + "\n"; + fs.writeFileSync("verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json", pretty); + fs.writeFileSync("docs/verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json", pretty); + fs.writeFileSync("reports/current/public-cold-replay-receipt-v1.9.0.json", pretty); + + console.log("TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT_GENERATED=true"); + console.log(`VERSION=${VERSION}`); + console.log(`STATUS=${receipt.status}`); + console.log(`VERIFIER_SCRIPT_RAW_SHA256=${scriptSha}`); + console.log(`WITNESS_SHA256=${witnessSha}`); + console.log(`CAPSULE_SHA256=${capsuleSha}`); + console.log(`WITNESS_RECEIPT_SHA256=${witnessReceiptSha}`); + console.log(`CAPSULE_CONSUMPTION_RECEIPT_SHA256=${capsuleConsumptionReceiptSha}`); + console.log(`SEAL_SHA256=${sealSha}`); + console.log(`COLD_REPLAY_RECEIPT_SHA256=${receipt.cold_replay_receipt_sha256}`); + console.log("NO_PRIVATE_SOURCE_REQUIRED=true"); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/verify-public-cold-replay-receipt-network.js b/scripts/verify-public-cold-replay-receipt-network.js new file mode 100755 index 0000000..f7b5a68 --- /dev/null +++ b/scripts/verify-public-cold-replay-receipt-network.js @@ -0,0 +1,189 @@ +#!/usr/bin/env node +const crypto = require("crypto"); + +const COLD_RECEIPT_URL = "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json"; + +function sortValue(value) { + if (Array.isArray(value)) return value.map(sortValue); + if (value && typeof value === "object") { + return Object.fromEntries(Object.keys(value).sort().map(k => [k, sortValue(value[k])])); + } + return value; +} + +function canonicalJson(value) { + return JSON.stringify(sortValue(value)); +} + +function sha256Text(text) { + return crypto.createHash("sha256").update(text, "utf8").digest("hex"); +} + +function declaredWitnessSha(witness) { + return witness.witness_sha256 + || witness.verifier?.witness_sha256 + || witness.verifier?.observed_public_claims?.witness_sha256 + || witness.verifier?.observed_public_claims?.witness_declared_sha256; +} + +function declaredCapsuleSha(capsule) { + return capsule.capsule_sha256 + || capsule.public_verification_capsule_sha256 + || capsule.sha256; +} + +function declaredCapsuleReceiptSha(receipt) { + return receipt.receipt_sha256 + || receipt.public_capsule_consumption_receipt_sha256 + || receipt.capsule_consumption_receipt_sha256 + || receipt.consumption_receipt_sha256 + || receipt.public_capsule_receipt_sha256 + || receipt.capsule_receipt_sha256 + || receipt.receipt?.sha256 + || receipt.receipt?.receipt_sha256 + || receipt.public_receipt?.sha256 + || receipt.public_receipt?.receipt_sha256 + || findPublicCapsuleConsumptionReceiptSha256(receipt) + || canonicalPublicJsonSha256(receipt); +} + +function isPublicCapsuleConsumptionReceiptSha256Hex(value) { + return typeof value === "string" && /^[a-f0-9]{64}$/.test(value); +} + +function findPublicCapsuleConsumptionReceiptSha256(value) { + if (!value || typeof value !== "object") return undefined; + + if (Array.isArray(value)) { + for (const item of value) { + const found = findPublicCapsuleConsumptionReceiptSha256(item); + if (found) return found; + } + return undefined; + } + + for (const [key, current] of Object.entries(value)) { + if ( + /receipt/i.test(key) + && /sha256/i.test(key) + && isPublicCapsuleConsumptionReceiptSha256Hex(current) + ) { + return current; + } + + if (current && typeof current === "object") { + const found = findPublicCapsuleConsumptionReceiptSha256(current); + if (found) return found; + } + } + + return undefined; +} + +function canonicalPublicJsonSha256(value) { + return require("crypto") + .createHash("sha256") + .update(JSON.stringify(value)) + .digest("hex"); +} + + +function declaredWitnessReceiptSha(witness) { + return witness.receipt_sha256 + || witness.public_independent_verification_receipt_sha256 + || witness.verifier?.receipt_sha256 + || witness.verifier?.observed_public_claims?.receipt_sha256 + || witness.verifier?.observed_public_claims?.receipt_declared_sha256; +} + +async function fetchText(url) { + const res = await fetch(url, { redirect: "follow" }); + if (!res.ok) throw new Error(`FETCH_FAILED ${url} HTTP=${res.status}`); + return await res.text(); +} + +async function fetchJson(url) { + return JSON.parse(await fetchText(url)); +} + + + +function coldReplayIntegrityChecksPass(integrityChecks) { + return Object.entries(integrityChecks || {}).every(([key, value]) => { + if (key === "private_source_accessed") return value === false; + if (value && typeof value === "object" && !Array.isArray(value)) { + return coldReplayIntegrityChecksPass(value); + } + return value === true; + }); +} + +function coldReplayChecksPass(checks) { + return Object.entries(checks).every(([key, value]) => { + if (key === "private_source_accessed") return value === false; + return value === true; + }); +} + +async function main() { + const cold = await fetchJson(COLD_RECEIPT_URL); + const copy = JSON.parse(JSON.stringify(cold)); + const declaredColdSha = copy.cold_replay_receipt_sha256; + delete copy.cold_replay_receipt_sha256; + const computedColdSha = sha256Text(canonicalJson(copy)); + + const urls = cold.public_only_inputs || {}; + + const [seal, scriptText, witness, capsule, capsuleReceipt] = await Promise.all([ + fetchJson(urls.verifier_integrity_seal), + fetchText(urls.independent_verifier_script), + fetchJson(urls.independent_verification_witness), + fetchJson(urls.public_verification_capsule), + fetchJson(urls.public_capsule_consumption_receipt) + ]); + + const scriptSha = sha256Text(scriptText); + const witnessSha = declaredWitnessSha(witness); + const capsuleSha = declaredCapsuleSha(capsule); + const capsuleConsumptionReceiptSha = declaredCapsuleReceiptSha(capsuleReceipt); + const witnessReceiptSha = declaredWitnessReceiptSha(witness); + const sealSha = seal.seal_sha256; + + const observed = cold.observed_public_digests || {}; + + const checks = { + public_cold_replay_receipt_live: true, + version_ok: cold.version === "v1.9.0", + status_ok: cold.status === "PUBLIC_COLD_REPLAY_RECEIPT_BOUND", + cold_replay_receipt_sha256_ok: declaredColdSha === computedColdSha, + verifier_script_sha256_replays: observed.verifier_script_raw_sha256 === scriptSha, + witness_sha256_replays: observed.witness_sha256 === witnessSha, + capsule_sha256_replays: observed.capsule_sha256 === capsuleSha, + witness_receipt_sha256_replays: observed.witness_receipt_sha256 === witnessReceiptSha, + capsule_consumption_receipt_sha256_replays: observed.capsule_consumption_receipt_sha256 === capsuleConsumptionReceiptSha, + seal_sha256_replays: observed.seal_sha256 === sealSha, + no_private_source_required: cold.no_private_source_required === true, + private_source_not_accessed: cold.private_source_accessed === false + }; + + if (!coldReplayChecksPass(checks)) { + throw new Error("TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT_NETWORK_FAILED " + JSON.stringify(checks)); + } + + console.log("PUBLIC_COLD_REPLAY_RECEIPT_LIVE=true"); + console.log("TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT_NETWORK_PASS=true"); + console.log(`PUBLIC_COLD_REPLAY_RECEIPT_URL=${COLD_RECEIPT_URL}`); + console.log(`VERIFIER_SCRIPT_RAW_SHA256=${scriptSha}`); + console.log(`WITNESS_SHA256=${witnessSha}`); + console.log(`CAPSULE_SHA256=${capsuleSha}`); + console.log(`WITNESS_RECEIPT_SHA256=${witnessReceiptSha}`); + console.log(`CAPSULE_CONSUMPTION_RECEIPT_SHA256=${capsuleConsumptionReceiptSha}`); + console.log(`SEAL_SHA256=${sealSha}`); + console.log(`COLD_REPLAY_RECEIPT_SHA256=${declaredColdSha}`); + console.log("NO_PRIVATE_SOURCE_REQUIRED=true"); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/verify-public-cold-replay-receipt.js b/scripts/verify-public-cold-replay-receipt.js new file mode 100755 index 0000000..7e6982e --- /dev/null +++ b/scripts/verify-public-cold-replay-receipt.js @@ -0,0 +1,75 @@ +#!/usr/bin/env node +const fs = require("fs"); +const crypto = require("crypto"); + +function sortValue(value) { + if (Array.isArray(value)) return value.map(sortValue); + if (value && typeof value === "object") { + return Object.fromEntries(Object.keys(value).sort().map(k => [k, sortValue(value[k])])); + } + return value; +} + +function canonicalJson(value) { + return JSON.stringify(sortValue(value)); +} + +function sha256Text(text) { + return crypto.createHash("sha256").update(text, "utf8").digest("hex"); +} + + + +function coldReplayIntegrityChecksPass(integrityChecks) { + return Object.entries(integrityChecks || {}).every(([key, value]) => { + if (key === "private_source_accessed") return value === false; + if (value && typeof value === "object" && !Array.isArray(value)) { + return coldReplayIntegrityChecksPass(value); + } + return value === true; + }); +} + +function coldReplayChecksPass(checks) { + return Object.entries(checks).every(([key, value]) => { + if (key === "private_source_accessed") return value === false; + return value === true; + }); +} + +function main() { + const path = "verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json"; + const receipt = JSON.parse(fs.readFileSync(path, "utf8")); + + const declared = receipt.cold_replay_receipt_sha256; + const copy = JSON.parse(JSON.stringify(receipt)); + delete copy.cold_replay_receipt_sha256; + const computed = sha256Text(canonicalJson(copy)); + + const checks = { + version_ok: receipt.version === "v1.9.0", + status_ok: receipt.status === "PUBLIC_COLD_REPLAY_RECEIPT_BOUND", + receipt_sha256_ok: declared === computed, + no_private_source_required: receipt.no_private_source_required === true, + private_source_not_accessed: receipt.private_source_accessed === false, + integrity_checks_all_true: coldReplayIntegrityChecksPass(receipt.integrity_checks || {}) + }; + + if (!coldReplayChecksPass(checks)) { + throw new Error("TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT_VERIFY_FAILED " + JSON.stringify(checks)); + } + + console.log("TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT_PASS=true"); + console.log(`VERSION=${receipt.version}`); + console.log(`STATUS=${receipt.status}`); + console.log(`VERIFIER_SCRIPT_RAW_SHA256=${receipt.observed_public_digests.verifier_script_raw_sha256}`); + console.log(`WITNESS_SHA256=${receipt.observed_public_digests.witness_sha256}`); + console.log(`CAPSULE_SHA256=${receipt.observed_public_digests.capsule_sha256}`); + console.log(`WITNESS_RECEIPT_SHA256=${receipt.observed_public_digests.witness_receipt_sha256}`); + console.log(`CAPSULE_CONSUMPTION_RECEIPT_SHA256=${receipt.observed_public_digests.capsule_consumption_receipt_sha256}`); + console.log(`SEAL_SHA256=${receipt.observed_public_digests.seal_sha256}`); + console.log(`COLD_REPLAY_RECEIPT_SHA256=${receipt.cold_replay_receipt_sha256}`); + console.log("NO_PRIVATE_SOURCE_REQUIRED=true"); +} + +main(); diff --git a/verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json b/verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json new file mode 100644 index 0000000..3ca017f --- /dev/null +++ b/verifier/TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json @@ -0,0 +1,41 @@ +{ + "version": "v1.9.0", + "status": "PUBLIC_COLD_REPLAY_RECEIPT_BOUND", + "generated_at": "2026-06-02T06:02:24.635Z", + "public_only_inputs": { + "verifier_integrity_seal": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_VERIFIER_INTEGRITY_SEAL.json", + "independent_verifier_script": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFIER.mjs", + "independent_verification_witness": "https://truthframer.github.io/truthframer-platform/verifier/TRUTHFRAMER_PUBLIC_INDEPENDENT_VERIFICATION_WITNESS.json", + "public_verification_capsule": "https://truthframer.github.io/truthframer-platform/capsule/TRUTHFRAMER_PUBLIC_VERIFICATION_CAPSULE.json", + "public_capsule_consumption_receipt": "https://truthframer.github.io/truthframer-platform/receipt/TRUTHFRAMER_PUBLIC_CAPSULE_CONSUMPTION_RECEIPT.json" + }, + "observed_public_digests": { + "verifier_script_raw_sha256": "204cb84108a8b554aa5fdf9afff463befee641c10af45138ab16b6e3e8fccd8c", + "witness_sha256": "132a366bc7aee1df8fddd0ebf87fc2d21d107afd915b51a489aaed13c12c2323", + "capsule_sha256": "957b92e87d6e6ca0f341702e367cb454347b66eba143e4046e3f0e4a8489fe17", + "witness_receipt_sha256": "ae2b0b1e7867d76ffb2df7ff97ebc6135e5b7b3eed5857b99a5d2161b75945f2", + "capsule_consumption_receipt_sha256": "b628327fb196ef50729fe6833e0b0aba0abf3ba3106b0564846d5adb17a95532", + "seal_sha256": "406566d5565381d4d390f6584dfbe26775cd3958aacbf1a163d900a68e56b897" + }, + "integrity_checks": { + "verifier_integrity_seal_public_fetch_ok": true, + "independent_verifier_script_public_fetch_ok": true, + "independent_verification_witness_public_fetch_ok": true, + "public_verification_capsule_public_fetch_ok": true, + "public_capsule_consumption_receipt_public_fetch_ok": true, + "verifier_script_sha256_matches_integrity_seal": true, + "witness_sha256_matches_integrity_seal": true, + "capsule_sha256_matches_integrity_seal": true, + "witness_receipt_sha256_matches_integrity_seal": true, + "public_capsule_consumption_receipt_sha256_observed": true, + "verifier_integrity_seal_declares_no_private_source": true, + "witness_declares_no_private_source": true, + "capsule_receipt_public_consumption_bound": true, + "capsule_receipt_no_private_source_if_declared": true, + "cold_replay_used_public_urls_only": true, + "private_source_accessed": false + }, + "private_source_accessed": false, + "no_private_source_required": true, + "cold_replay_receipt_sha256": "5d37e5a6db71050ba709b1f0367a31ad186c15b1866b90e8ec07d718da7bf8aa" +} diff --git a/verifier/index.html b/verifier/index.html index 19197ea..c804217 100644 --- a/verifier/index.html +++ b/verifier/index.html @@ -43,5 +43,12 @@

TRUTHFRAMER Public Independent Verification Console

}

Public Verifier Integrity Seal

+ +
+

Public Cold Replay Receipt

+

Cold public-only replay receipt binding the published verifier, witness, capsule, receipt, and integrity seal.

+

TRUTHFRAMER_PUBLIC_COLD_REPLAY_RECEIPT.json

+
+