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.

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 r = await fetch(`https://api.akaeon.com/v1/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.merkle_inclusion.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

| Check | What it proves | |---|---| | 1. Ed25519 signature | The registry actually attested to this opt-out. The canonical_message and signature are bound to the registry's published public key. | | 2. Merkle inclusion | This specific opt-out was part of the claimed batch. The proof reconstructs the claimed root from the leaf alone. | | 3. Arweave anchor | The 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.