Standalone verifier

Copy-paste-ready Node.js verifier for Akaeon Registry records. No SDK. Standard library only. No Akaeon code in the verify path.

The verifier is a single file. It uses Node's built-in crypto module — no third-party SDK, no proprietary cryptography. Three independent checks, run in the lab's environment, against the public Arweave network. It fetches the record from the free public lookup endpoint, so it runs with no API key and no Akaeon account at all.

For the production-grade reference with full RFC 6962 odd-count-promotion handling and canonical JSON re-serialization, see the integration runbook §5. The version on this page is the condensed three-check verifier — faithful to the spec, easier to read, and what you'd paste into a smoke test.

The verifier

// verifier.mjs — three independent checks. Standard library only.
import crypto from 'node:crypto'

const SPKI_HEADER = Buffer.from('302a300506032b6570032100', 'hex')
const sha256 = (b) => crypto.createHash('sha256').update(b).digest()

const domain = process.argv[2] // e.g. example-publisher.com
// The public lookup is free and unauthenticated — no API key needed to verify.
const r = await fetch(`https://api.akaeon.com/v1/public/lookup?domain=${domain}`).then(x => x.json())
const o = r.optouts[0]

// 1. Ed25519 signature on the registry's canonical message
const pk = crypto.createPublicKey({
  key: Buffer.concat([SPKI_HEADER, Buffer.from(o.registry_signature.public_key, 'base64')]),
  format: 'der', type: 'spki',
})
const sigOk = crypto.verify(null,
  Buffer.from(o.registry_signature.canonical_message, 'utf8'),
  pk,
  Buffer.from(o.registry_signature.signature, 'base64'))

// 2. Merkle inclusion proof reconstructs the claimed root
let c = Buffer.from(o.merkle_inclusion.leaf_hash, 'hex'), i = o.merkle_inclusion.leaf_index
for (const s of o.merkle_inclusion.merkle_proof) {
  const [l, ri] = (i & 1) === 0 ? [c, Buffer.from(s, 'hex')] : [Buffer.from(s, 'hex'), c]
  c = sha256(Buffer.concat([Buffer.from([0x01]), l, ri]))
  i >>= 1
}
const merkleOk = c.toString('hex') === o.merkle_inclusion.merkle_root

// 3. Arweave-anchored batch payload contains the same root
const ar = await fetch(o.anchor.arweave_url).then(x => x.json())
const anchorOk = ar.merkle_root_sha256_hex === o.merkle_inclusion.merkle_root

console.log({ sigOk, merkleOk, anchorOk })

What each check is doing

CheckWhat it proves
1. Ed25519 signatureThe registry actually attested to this opt-out. The canonical_message and signature are bound to the registry's published public key.
2. Merkle inclusionThis specific opt-out was part of the claimed batch. The proof reconstructs the claimed root from the leaf alone.
3. Arweave anchorThe batch root was actually anchored, at the public-network timestamp. The Arweave transaction body must contain the same root the proof reconstructs.

If all three are true, the chain from publisher-stated opt-out to Arweave-anchored public timestamp is intact.

What's not in this condensed version

The production reference at integration §5 handles three details this condensed teaser elides:

  1. Odd-count Merkle promotion. RFC 6962-style trees promote the last node at an odd-count level unchanged, rather than duplicating it (Bitcoin-style). The condensed loop above assumes the leaf path doesn't pass through a promoted node. For non-power-of-2 batch sizes, use the runbook version, which takes tree_size and walks the right edge correctly.
  2. Canonical record re-hashing. The runbook version recomputes the leaf hash from the canonical record bytes (lexicographic key sort, no whitespace, UTF-8) and compares it to merkle_inclusion.leaf_hash. The condensed version trusts the registry's leaf hash. For the strongest audit posture, re-hash.
  3. Arweave batch signature verification. The runbook version also verifies the registry's signature on the Arweave-anchored batch payload itself, not just the per-record signature.

The condensed version above passes the verify-in-15-lines test for the happy path; the production version passes it for adversarial inputs as well. Use the production version in your audit pipeline.

Why this matters

Because the substrate — Ed25519, SHA-256, Arweave — carries the cryptographic work. The verifier just composes the primitives. If your verifier needs to import an Akaeon SDK, you're no longer verifying against the public substrate; you're verifying against us, and the trust property degrades. The whole point is that you don't have to.