site/verify/core.js recomputes every header hash (keyed BLAKE2b, the node's field order), checks the parent links from the previous locked checkpoint, hashes each voter's G1 key to its vote_key_hash, verifies the BLS aggregate over "igneum-vote-v1/" || chain_id || 0 || index_le64 || checkpoint under the vote tag with the bitmap's keys, and applies Q3 (2/3 of active, 17/30 of total). verify.js drives it from /api/checkpoint with @noble/hashes 2.4.0 and @noble/curves 2.4.0 pinned from jsdelivr and fills the homepage card; the badge says LIVE only after a pass in the tab. site/api/checkpoint.mjs ships the data: certificate bytes, voter table with public keys, header chain. tools/observer stores every certificate a block carries (new table live_certificates, voter table read at the lock, selected-chain headers back to the previous lock, one-off backfill of the newest lock on start) and keeps header nonces exact; the FinalityLock write no longer fails on a missing votes_seen. Tested on the igneum-devnet-7 test network: checkpoint 95 verifies in Chrome in 103 ms; a flipped signature bit, a dropped voter, an altered key, an altered header and a removed header all fail with the reason named. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
66 lines
3.5 KiB
JavaScript
66 lines
3.5 KiB
JavaScript
// Igneum light client, version zero, browser driver. Fetches /api/checkpoint and verifies it in this tab with
|
|
// core.js, then fills the homepage card. Everything is computed here; the server only ships data.
|
|
// Libraries, pinned: BLAKE2b from @noble/hashes 2.4.0, BLS12-381 from @noble/curves 2.4.0 (pure JavaScript,
|
|
// served by jsdelivr as ES modules).
|
|
import { blake2b } from 'https://cdn.jsdelivr.net/npm/@noble/hashes@2.4.0/blake2.js/+esm';
|
|
import { bls12_381 } from 'https://cdn.jsdelivr.net/npm/@noble/curves@2.4.0/bls12-381.js/+esm';
|
|
import { verifyCheckpoint } from './core.js';
|
|
|
|
export const LIBRARIES = { '@noble/hashes': '2.4.0', '@noble/curves': '2.4.0' };
|
|
const deps = { blake2b, bls: bls12_381 };
|
|
|
|
export async function fetchCheckpoint(url = '/api/checkpoint') {
|
|
const r = await fetch(url, { cache: 'no-store' });
|
|
let body = null;
|
|
try { body = await r.json(); } catch { body = null; }
|
|
if (!body) return { ok: false, error: `api answered ${r.status}` };
|
|
return body;
|
|
}
|
|
|
|
export function verify(data) { return verifyCheckpoint(data, deps); }
|
|
|
|
// The homepage card. Cells carry data-lc="..." so the layout and classes stay as they are.
|
|
const $ = sel => document.querySelector(sel);
|
|
const pct = x => `${(x * 100).toFixed(1)}%`;
|
|
|
|
export function renderCard(result, data) {
|
|
const badge = $('[data-lc="badge"]'), badgeText = $('[data-lc="badge-text"]');
|
|
const cp = $('[data-lc="checkpoint"]'), proof = $('[data-lc="proof"]'), sys = $('[data-lc="system"]'), tab = $('[data-lc="tab"]');
|
|
const line = $('[data-lc="line"]');
|
|
if (!cp) return;
|
|
if (proof) proof.textContent = 'not yet';
|
|
if (sys) sys.textContent = 'BLS aggregate, version 1';
|
|
if (result.verified) {
|
|
const count = (window.__igneumVerified = (window.__igneumVerified || 0) + 1);
|
|
cp.textContent = `${count} verified in this tab`;
|
|
cp.title = `checkpoint ${result.index} on ${result.network}, block ${String(result.hash).slice(0, 12)}, ${result.headers_checked} headers checked`;
|
|
tab.textContent = `${result.ms} ms · ${pct(result.weight_fraction_total)} of weight`;
|
|
tab.title = `${result.signers} of ${result.voters} voters signed: ${pct(result.weight_fraction_active)} of active weight, ${pct(result.weight_fraction_total)} of total${result.weights_exact ? '' : ` (weights read at checkpoint ${result.weights_at_index})`}`;
|
|
tab.style.color = '';
|
|
if (badgeText) badgeText.textContent = data.source === 'test' ? 'LIVE · test network' : 'LIVE';
|
|
if (badge) badge.classList.add('on');
|
|
if (line) line.textContent = data.source === 'test'
|
|
? `Checkpoint ${result.index} of the test network, certified by ${result.signers} miners. Verified here.`
|
|
: `Checkpoint ${result.index}, certified by ${result.signers} miners. Verified here.`;
|
|
} else {
|
|
cp.textContent = 'could not verify';
|
|
cp.title = result.reason || '';
|
|
tab.textContent = result.reason ? result.reason : 'could not verify';
|
|
tab.style.color = 'var(--molten)';
|
|
if (badgeText) badgeText.textContent = 'PREVIEW';
|
|
if (badge) badge.classList.remove('on');
|
|
}
|
|
}
|
|
|
|
export async function run(url) {
|
|
const data = await fetchCheckpoint(url);
|
|
const result = verify(data);
|
|
window.__igneumLightClient = { result, data };
|
|
renderCard(result, data);
|
|
return result;
|
|
}
|
|
|
|
if (typeof document !== 'undefined' && document.querySelector('[data-lc="checkpoint"]')) {
|
|
const start = () => run().catch(e => renderCard({ verified: false, reason: String(e.message || e) }, {}));
|
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start();
|
|
}
|