oxedyne/daimond/verify/ext/fingerprint.js
1.9 KiB, 1 run
created by r2519314175:1013, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // verify/ext/fingerprint.js — the Daimond bundle fingerprint, carried by the |
| 2 | // extension itself. |
| 3 | // |
| 4 | // This is a deliberate copy of the algorithm in verify/lib.mjs, NOT a shared |
| 5 | // import from the site. That is the whole point of the extension: its copy of |
| 6 | // the check is installed from source and cannot be touched by the server it is |
| 7 | // checking. verify/verify.test.mjs asserts this file agrees, byte-for-byte, |
| 8 | // with verify/lib.mjs and www/js/verify.js — three implementations that must |
| 9 | // compute the identical hash, or none of them verifies anything. |
| 10 | // |
| 11 | // Uses the global Web Crypto (SubtleCrypto), present in a service worker and in |
| 12 | // Node 20+, so the same file runs under the extension and under the tests. |
| 13 | |
| 14 | export const GENESIS_PREV = '0'.repeat(64); |
| 15 | |
| 16 | export async function sha256(bytes) { |
| 17 | const buf = await crypto.subtle.digest('SHA-256', bytes); |
| 18 | return [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join(''); |
| 19 | } |
| 20 | |
| 21 | export async function sha256str(str) { |
| 22 | return sha256(new TextEncoder().encode(str)); |
| 23 | } |
| 24 | |
| 25 | export function manifestText(files) { |
| 26 | const rels = Object.keys(files).sort(); |
| 27 | let s = ''; |
| 28 | for (const rel of rels) s += rel + '\n' + files[rel] + '\n'; |
| 29 | return s; |
| 30 | } |
| 31 | |
| 32 | export async function bundleHash(files) { |
| 33 | return sha256str(manifestText(files)); |
| 34 | } |
| 35 | |
| 36 | export async function entryHash(e) { |
| 37 | return sha256str(e.seq + '|' + e.ts + '|' + e.build + '|' + e.bundle + '|' + e.prev); |
| 38 | } |
| 39 | |
| 40 | export async function verifyChain(entries) { |
| 41 | let prev = GENESIS_PREV; |
| 42 | for (let i = 0; i < entries.length; i++) { |
| 43 | const e = entries[i]; |
| 44 | if (e.seq !== i) return { ok: false, error: 'entry ' + i + ' out of order' }; |
| 45 | if (e.prev !== prev) return { ok: false, error: 'entry ' + i + ' does not chain on ' + (i - 1) }; |
| 46 | if (e.entry !== await entryHash(e)) return { ok: false, error: 'entry ' + i + ' hash mismatch' }; |
| 47 | prev = e.entry; |
| 48 | } |
| 49 | return { ok: true, error: '' }; |
| 50 | } |