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:
- Recomputes the canonical hash of its own content in the browser.
- Fetches the on-chain attestation by UID.
- 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.
Markup
Section titled “Markup”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.bodyoverlay, never mutating publisher DOM (so SSR-hydrated frameworks like React or Vue stay byte-clean).
Loading the script
Section titled “Loading the script”@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.
Computing the canonical hash
Section titled “Computing the canonical hash”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.from fidemark import hash_region
with open("post-42.html", "r", encoding="utf-8") as f: html = f.read()
content_hash = hash_region(html, selector="#post-42")# Then attest with that content_hash from your CMS pipeline.import "github.com/fidemark/sdk-go/fidemark"
contentHash, err := fidemark.HashRegion(htmlString, "#post-42")if err != nil { return err }// Then attest with that contentHash from your build.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 withdata-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).
Status states
Section titled “Status states”The region pill resolves to one of:
| State | Meaning |
|---|---|
| Verified | Declared hash matches the recomputed subtree, and the on-chain attestation covers that hash and is not revoked. |
| Tampered | Declared hash does not match the recomputed subtree. The visitor’s browser sees content that drifted from what was attested. |
| Mismatch | Declared 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. |
| Revoked | Three-way match but the attester revoked the attestation after publication. |
| Unreachable | The 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.
Attest then publish
Section titled “Attest then publish”The recommended workflow:
- Author the HTML with the region wrapper but a placeholder
content-hash="0x000...". - Run
hashRegionon the rendered subtree to get the canonical hash. - Attest the canonical hash on chain.
- 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.
When NOT to use regions
Section titled “When NOT to use regions”- 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
refUIDinstead. - Server-only content (PDFs, downloads, raw API responses): use
hashContenton the bytes and skip the DOM markup entirely.