Oregami
Repositories/oxedyne/daimond

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
14export const GENESIS_PREV = '0'.repeat(64);
15
16export 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
21export async function sha256str(str) {
22 return sha256(new TextEncoder().encode(str));
23}
24
25export 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
32export async function bundleHash(files) {
33 return sha256str(manifestText(files));
34}
35
36export async function entryHash(e) {
37 return sha256str(e.seq + '|' + e.ts + '|' + e.build + '|' + e.bundle + '|' + e.prev);
38}
39
40export 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}