This document specifies how content publishers embed cryptographic signatures into HTML so that browsers, extensions, and crawlers can discover and verify them.
Signed content uses the <signed-section> custom HTML element, as defined in the HTMLTrust specification. This element wraps or accompanies signed content and carries the cryptographic signature as attributes.
| Attribute | Description | Example |
|---|---|---|
signature |
Base64-encoded cryptographic signature of the content hash + domain + author ID | signature="aBcDeF123..." |
keyid |
URL where the author's public key can be fetched, or a DID | keyid="https://api.example.com/authors/123/public-key" |
algorithm |
Cryptographic algorithm used for the signature | algorithm="ed25519" |
content-hash |
Hash of the canonicalized content, prefixed with the algorithm | content-hash="sha256:abc123def456..." |
| Value | Description |
|---|---|
ed25519 |
Ed25519 (recommended) |
rsa |
RSA with SHA-256 |
ecdsa |
ECDSA with secp256k1 |
The <signed-section> element MAY contain <meta> tags that describe the signature's claims and context. This makes signatures self-describing — a crawler or verifier can read the claims directly from the HTML without calling the trust directory API.
| Name | Description | Example |
|---|---|---|
author |
Display name of the content author | <meta name="author" content="Alice Example"> |
signed-at |
ISO 8601 timestamp of when the content was signed | <meta name="signed-at" content="2025-05-01T10:30:00Z"> |
Claims use the prefix claim: followed by the claim type name:
| Name | Description | Example |
|---|---|---|
claim:ContentType |
Type of content | Article, Opinion, Research, News |
claim:License |
Content license | CC-BY-4.0, Public Domain, All Rights Reserved |
claim:AIAssistance |
Level of AI involvement | None, Human+AI, AI+Human, AI-only |
claim:LLMTraining |
AI training preference | Allowed, NotAllowed |
Custom claim types are permitted. The claim vocabulary is extensible.
The <signed-section> element can either wrap the signed content:
<signed-section
signature="BASE64_SIG"
keyid="https://api.example.com/authors/123/public-key"
algorithm="ed25519"
content-hash="sha256:abc123def456...">
<meta name="author" content="Alice Example">
<meta name="signed-at" content="2025-05-01T10:30:00Z">
<meta name="claim:ContentType" content="Article">
<meta name="claim:License" content="CC-BY-4.0">
<meta name="claim:AIAssistance" content="None">
<article>
<h1>Verifiable Web Content</h1>
<p>This content is signed and verifiable.</p>
</article>
</signed-section>Or appear as a standalone marker alongside content (e.g., when added by a CMS after the content):
<article>
<h1>Verifiable Web Content</h1>
<p>This content is signed and verifiable.</p>
</article>
<signed-section
signature="BASE64_SIG"
keyid="https://api.example.com/authors/123/public-key"
algorithm="ed25519"
content-hash="sha256:abc123def456...">
<meta name="author" content="Alice Example">
<meta name="signed-at" content="2025-05-01T10:30:00Z">
<meta name="claim:ContentType" content="Article">
<meta name="claim:License" content="CC-BY-4.0">
</signed-section>Both forms are valid. Verifying clients should handle either case.
Before hashing, content MUST be canonicalized:
- Parse the HTML and extract text nodes in document order
- Strip all HTML markup (tags and attributes); only the text content contributes to the hash
- Collapse all whitespace sequences to a single space
- Trim leading and trailing whitespace
- Apply the text normalization defined by the
@htmltrust/canonicalizationlibrary (NFKC, quote normalization, dash normalization, invisible character stripping) - Encode as UTF-8
The resulting string is hashed with SHA-256 and expressed as sha256:<base64_digest>, where <base64_digest> is the unpadded Base64 encoding of the 32-byte digest.
The canonicalization hashes text content only, not the HTML markup or attributes that surround it. This means an adversary with possession of signed text MAY:
- Rewrap the text in misleading block elements (e.g., change an
<h1>to a<del>strikethrough) - Alter link destinations (
hrefvalues) on<a>elements surrounding the signed text - Introduce, remove, or swap images and other media around the signed text
These are semantic integrity concerns, not cryptographic ones. HTMLTrust addresses them through a layered design:
- Domain binding (see Signature Data Format below): signatures bind the content to a specific publication origin. A reader or crawler encountering signed content at an unexpected origin is alerted by signature check failure.
- Research and reputation path: crawlers and researchers can trace signed content back to its canonical publication origin through the trust directory, flag imposter copies, and mark manipulated surrounding context. Over time the directory's reputation and reports surface altered copies to any consumer whose trust policy considers them.
The layered design keeps cryptographic verification simple and portable across language implementations, while delegating semantic-integrity detection to the research ecosystem where it can evolve without breaking existing signatures.
Open design question: a future revision MAY extend the hash to cover particularly meaningful attributes, especially href on <a> elements (since link-swap within the original publication origin is a phishing vector that domain-binding and research cannot address alone). Feedback on which attributes to cover is explicitly welcome.
The signature binds four values, concatenated with : separators:
{content-hash}:{claims-hash}:{domain}:{signed-at}
content-hash— hash of the canonicalized text content (see above)claims-hash— SHA-256 hash of the canonical serialization of all inner<meta>claim elements, ordered lexically by name (ensures tamper-evident claim metadata)domain— the origin where the content is authoritatively published (anti-theft binding)signed-at— the ISO-8601 timestamp from the<meta name="signed-at">element
For example:
sha256:RAyBCvKT...:sha256:eFgHiJkL...:example.com:2025-05-01T10:30:00Z
The author's identity is not included in the binding because it is implicit in the keyid resolution step: any attempt to claim a signature under a different identity would resolve to a different public key and fail verification. This string is signed with the author's private key using the algorithm declared in the algorithm attribute.
Hash encoding (open feedback): hashes are encoded as unpadded Base64, which is shorter than hexadecimal by roughly one-third. Community feedback on alternative encodings (hex, Base32) for ecosystem alignment is welcome.
HTMLTrust separates verification into two distinct layers, per the specification:
A verifying client (browser extension, crawler, library) performs these steps locally, with no network calls beyond the key resolution step:
- Discover
<signed-section>elements in the page DOM - Read the
signature,keyid,algorithm, andcontent-hashattributes - Resolve the
keyidto a public key. Thekeyidmay be a DID (e.g.,did:web:author.example), a direct URL to a public key JSON document, or a trust directory reference. Implementations MUST accept multiple resolution methods. - Canonicalize the inner text content per the rules above and compute its hash
- Compare the computed hash with the
content-hashattribute (content integrity check) - Compute the
claims-hashfrom the canonical serialization of inner<meta>claim elements - Construct the binding string
{content-hash}:{claims-hash}:{domain}:{signed-at} - Verify the cryptographic signature over the binding string using the resolved public key and the declared
algorithm
This layer produces a deterministic yes/no result: either the signature is cryptographically valid or it is not. No server or directory is required for this step beyond whatever key resolution demands.
Given a cryptographically valid signature, the client then applies the user's trust policy to decide how to present the content. This layer is entirely client-side and may draw on:
- A personal list of trusted keyids (option A)
- Trusted origin domains (option B)
- Endorsements from designated third parties (fetched from trust directories and independently verified)
- Reputation scores from one or more user-selected trust directories
- Local or cached revocation state
- Any combination of the above, weighted as the user configures
The output is a trust score or ranking, not a binary verdict. User interfaces SHOULD present the outcome as a graduated signal (for example a red/yellow/green score) with hover or detail views exposing which inputs contributed to the final score.
In addition to the two layers above, a client MAY query one or more trust directories for:
- Author reputation (signer-level trust, ongoing curatorial opinion)
- Content endorsements (point-in-time attestations from third parties)
- Key revocation and reports
These queries enrich the trust decision but are never required for signature verification itself.
A single page MAY contain multiple <signed-section> elements (e.g., a forum with posts from different authors). Each is verified independently.
<signed-section> is a custom HTML element. Browsers that don't recognize it treat it as an unknown inline element and render its children normally. Adding signed-section { display: block; } in CSS ensures consistent block-level rendering. The content remains fully readable and functional.
Implementations should include:
signed-section {
display: block;
}