charter-attestation-verifier / tree / verifier.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// Founding Charter attestation verifier. Runs in the browser and in Node 20 unchanged.
import { EAS_ADDRESS, NETWORKS, DEFAULT_IPFS_GATEWAY } from './config.js';
import { encodeGetAttestation, decodeAttestation, decodeCharterData } from './abi.js';

export { decodeCharterData } from './abi.js';
export { NETWORKS, EAS_ADDRESS, CHARTER_SCHEMA, KOGNAI_REHEARSAL } from './config.js';

const ZERO_UID = '0x' + '0'.repeat(64);

export async function sha256Hex(bytes) {
  const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
  return '0x' + Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('');
}

const message = (e) => (e && e.message ? String(e.message) : String(e));

// verifyCharter never throws: every failure is reported in reason or in a text's error.
export async function verifyCharter({ uid, network, rpc, ipfs } = {}) {
  const attestation = { uid: typeof uid === 'string' ? uid.toLowerCase() : uid, attester: null, time: null, revoked: false };
  const fail = (reason, texts = []) => ({ ok: false, reason, attestation, texts });
  if (!NETWORKS[network]) return fail(`unknown network "${network}": use base or base-sepolia`);
  if (typeof rpc !== 'function' || typeof ipfs !== 'function') return fail('rpc and ipfs must be functions');
  let call;
  try {
    call = encodeGetAttestation(uid);
  } catch (e) {
    return fail(message(e));
  }
  let raw;
  try {
    raw = await rpc('eth_call', [{ to: EAS_ADDRESS, data: call }, 'latest']);
  } catch (e) {
    return fail(`the RPC call failed: ${message(e)}`);
  }
  let att;
  try {
    att = decodeAttestation(raw);
  } catch (e) {
    return fail(message(e));
  }
  if (att.uid === ZERO_UID) return fail(`no attestation with this UID on ${network}`);
  attestation.attester = att.attester;
  attestation.time = att.time;
  attestation.revoked = att.revocationTime !== 0;
  let charter;
  try {
    charter = decodeCharterData(att.data);
  } catch (e) {
    const why = `the attestation data is not a Charter attestation: ${message(e)}`;
    return fail(attestation.revoked ? `the attestation was revoked, and ${why}` : why);
  }
  const texts = await Promise.all(
    charter.names.map(async (name, i) => {
      const entry = { name, cid: charter.cids[i], expected: charter.digests[i].toLowerCase(), actual: null, match: false };
      try {
        const bytes = await ipfs(entry.cid);
        if (!(bytes instanceof Uint8Array)) throw new Error('the text did not come back as bytes');
        entry.actual = await sha256Hex(bytes);
        entry.match = entry.actual === entry.expected;
      } catch (e) {
        entry.error = `the text could not be fetched: ${message(e)}`;
      }
      return entry;
    }),
  );
  const result = { ok: false, attestation, texts, charter: { version: charter.version, date: charter.date } };
  if (attestation.revoked) return { ...result, reason: 'the attestation was revoked' };
  if (texts.length === 0) return { ...result, reason: 'the attestation lists no texts' };
  const bad = texts.filter((t) => !t.match);
  if (bad.length) {
    return { ...result, reason: `${bad.length} of ${texts.length} texts do not match: ${bad.map((t) => t.name).join(', ')}` };
  }
  return { ...result, ok: true };
}

// Fetch based helpers the page uses. Tests pass their own rpc and ipfs.
export function httpRpc(url, fetchImpl = globalThis.fetch) {
  let id = 0;
  return async (method, params) => {
    const res = await fetchImpl(url, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ jsonrpc: '2.0', id: ++id, method, params }),
    });
    if (!res.ok) throw new Error(`HTTP ${res.status} from the RPC`);
    const body = await res.json();
    if (body.error) throw new Error(body.error.message || 'RPC error');
    return body.result;
  };
}

export function httpIpfs(gateway = DEFAULT_IPFS_GATEWAY, fetchImpl = globalThis.fetch) {
  const base = gateway.endsWith('/') ? gateway : gateway + '/';
  return async (cid) => {
    if (typeof cid !== 'string' || !/^[A-Za-z0-9]+(\/[^?#]*)?$/.test(cid)) throw new Error('bad CID');
    const res = await fetchImpl(base + cid);
    if (!res.ok) throw new Error(`HTTP ${res.status} from ${new URL(base).host}`);
    return new Uint8Array(await res.arrayBuffer());
  };
}

agentgate-source: code mirror on Cloudflare Pages. All repos