Skip to content

Region verification

Page-level badges answer “is something on this page attested?” Region verification answers the more useful question: which content is covered, and does it still match what was attested? Each attested subtree wraps in a small custom element that:

  1. Recomputes the canonical hash of its own content in the browser.
  2. Fetches the on-chain attestation by UID.
  3. Compares the declared hash, the recomputed hash, and the on-chain hash, and renders a status pill anchored to the region.

If any of the three diverge the pill flips to tampered or mismatch so the visitor can see precisely where the trust chain broke.

Two equivalent forms are supported. Pick whichever is friendlier to your CMS:

<!-- Custom element (preferred). -->
<fidemark-region
uid="0x036295c587e1bf82ab6f6ed881d1b5b2bc38eb69023ea2ed213cdf1a0d726a43"
content-hash="0xaa3ec16e6acc809d8b2818662276256abfd2f1b441cb51574933f3d4bd115d11"
>
<article>
<h2>The attested headline</h2>
<p>The attested body paragraph.</p>
</article>
</fidemark-region>
<!-- Or the attribute-only form, drops in anywhere markup is locked. -->
<article
data-fidemark-uid="0x036295c587e1bf82ab6f6ed881d1b5b2bc38eb69023ea2ed213cdf1a0d726a43"
data-fidemark-content-hash="0xaa3ec16e6acc809d8b2818662276256abfd2f1b441cb51574933f3d4bd115d11"
>
<h2>The attested headline</h2>
<p>The attested body paragraph.</p>
</article>

Both forms convey the same intent, but the runtimes differ:

  • <fidemark-region> is a custom element provided by @fidemark/badge. It self-renders its pill and side panel from a shadow root, with no extension required. The browser extension intentionally skips elements of this tag because the badge already owns the UI.
  • The attribute-only form is meant for pages that ship without the badge script. The Fidemark browser extension scans for the attribute pair and renders an equivalent pill from a document.body overlay, never mutating publisher DOM (so SSR-hydrated frameworks like React or Vue stay byte-clean).

@fidemark/badge exports the region web component as ./region:

<script
type="module"
src="https://cdn.jsdelivr.net/npm/@fidemark/badge@^0.1/dist/region.js"
></script>

For React, import the wrapper:

import { FidemarkRegion } from "@fidemark/badge";
export function Article() {
return (
<FidemarkRegion uid="0x036..." contentHash="0xaa3...">
<article>{/* ... */}</article>
</FidemarkRegion>
);
}

The Fidemark browser extension complements this script: it renders an equivalent pill on every attribute-form region without needing the badge package, so visitors who have the extension installed see verification on bare-markup pages too.

Both publisher and verifier must hash the same bytes. Fidemark ships a deterministic DOM canonicalization spec used by every SDK and the badge:

import { hashRegion } from "@fidemark/sdk/dom";
// In a Node script (build time) or a browser script (publish time):
const region = document.querySelector("article#post-42")!;
const contentHash = await hashRegion(region);
// Then attest with that contentHash, paste both UID and hash into your markup.

The full canonicalization rules are described in DOM canonicalization spec. The short version:

  • Walks the subtree depth-first.
  • Skips <script>, <style>, <noscript>, <template>, comments, and any element with data-fidemark-ignore.
  • Collapses runs of ASCII whitespace inside text nodes to a single space.
  • Treats block-level element boundaries (<p>, <div>, <li>, <h1>-<h6>, <article>, <section>, etc.) as a single newline.
  • Trims trailing spaces per line, drops leading and trailing blank lines.
  • NFC-normalizes the result before hashing.

Because attribute, class, and presentational changes are ignored, the hash survives style refactors, build-tool wrapper churn, and most ad-injection patterns (when the ad slot is marked with data-fidemark-ignore).

The region pill resolves to one of:

StateMeaning
VerifiedDeclared hash matches the recomputed subtree, and the on-chain attestation covers that hash and is not revoked.
TamperedDeclared hash does not match the recomputed subtree. The visitor’s browser sees content that drifted from what was attested.
MismatchDeclared hash matches the subtree but the on-chain attestation’s contentHash differs. Either the markup carries the wrong UID or the attester signed off on different bytes.
RevokedThree-way match but the attester revoked the attestation after publication.
UnreachableThe browser could not fetch the attestation (network issue, malformed UID, etc).

Click the pill to open the side panel, which shows the attester, timestamp, and a copy of all three hashes side by side. The visitor never has to trust the badge: the recomputed hash is shown next to the declared hash, so any mismatch is visible.

The recommended workflow:

  1. Author the HTML with the region wrapper but a placeholder content-hash="0x000...".
  2. Run hashRegion on the rendered subtree to get the canonical hash.
  3. Attest the canonical hash on chain.
  4. Replace the placeholder with the real UID + hash and ship the page.

If you change the wrapped content after step 4, the region will flip to “tampered” until you attest a new hash.

  • Highly dynamic content (live counters, A/B tests, timestamps): the canonical hash will rarely match. Either attest a stable subset (extract the dynamic parts under data-fidemark-ignore) or use the page-level badge with a UID-only marker.
  • Streaming or long-lived feeds (chat logs, comment threads): one attestation per region scales linearly, so you may want a single root attestation that references children via refUID instead.
  • Server-only content (PDFs, downloads, raw API responses): use hashContent on the bytes and skip the DOM markup entirely.