HTMLTrust

Cryptographic authorship and a reader-controlled trust network for the open web

The HTMLTrust specification is small on purpose. It defines:

  1. A new <signed-section> HTML element
  2. Four required attributes carrying the signature, key reference, content hash, and algorithm
  3. A canonical signing payload bound to a publication origin
  4. An 8-phase canonicalization algorithm so the same content hashes to the same value regardless of which tool produced it
  5. Pluggable key resolution and optional federated trust directories
  6. A signed-JSON endorsement format for third-party attestations

The full paper lives in the htmltrust-spec repository. What follows is a tight tour.

The <signed-section> element

<signed-section
    keyid="did:web:author.example"
    signature="3q2+7w8NslfJ..."
    content-hash="sha256:RAyBCvKT..."
    algorithm="ed25519">
  <meta name="author"        content="Alice Example">
  <meta name="signed-at"     content="2026-05-01T10:30:00Z">
  <meta name="claim:License" content="CC-BY-4.0">
  <article>
    <h1>Verifiable Web Content</h1>
    <p>Content should be provable…</p>
  </article>
</signed-section>

Each <signed-section> is independent. A single page can host many of them, signed by different authors.

Four required attributes

AttributePurpose
keyidIdentifies the signer: DID, URL to a key, or directory reference
signatureThe cryptographic signature, unpadded standard Base64-encoded
content-hashHash of the canonicalized content, e.g. sha256:…
algorithmSignature algorithm registry identifier: ed25519 (MTI), ecdsa-p256, ecdsa-p384, rsa-pss-sha256, or rsa-pkcs1-sha256

Inner <meta> elements carry claims: author, timestamp, license, AI involvement, anything the signer wants attested. Claims are folded into the signing payload via a claims-hash.

The canonical signing payload

The signature is computed over a deterministic binding string:

{content-hash}:{claims-hash}:{domain}:{signed-at}
  • content-hash: hash of the canonicalized text plus signed semantic attributes
  • claims-hash: hash of every direct-child <meta> claim element, serialized as sorted name:content\n records
  • domain: the serialized Web origin where the content is authoritatively published
  • signed-at: ISO-8601 timestamp from the corresponding <meta> element

The signer's identity is implicit in the keyid resolution step. Any attempt to claim a signature under a different identity simply resolves to a different public key and fails verification.

Domain binding ties a signature to a specific publication origin, preventing signature replay on mirror sites. Legitimate republishing is supported by wrapping the original <signed-section> in an outer one; see the architecture page.

Canonicalization in two stages

Canonicalization runs in two stages. Stage 1 reduces the signed region's HTML to a canonical byte string. Stage 2 normalizes text and signed attribute values through deterministic phases.

This split is deliberate: HTMLTrust signs the readable text plus a small set of user-meaningful semantic attributes (href, src, alt, and aria-label). The attribute set is intentionally small in this revision and remains open for community feedback.

Stage 1: extract canonical text from HTML

Given the inner content of a <signed-section> element, produce a single UTF-8 text string:

  1. Walk the DOM of the signed region in document order.
  2. Concatenate text nodes verbatim. Whitespace within text nodes is preserved at this stage. Collapsing happens in Stage 2.
  3. Skip non-content elements entirely:
    • <meta> claim elements (they are hashed separately as claims-hash)
    • <script>, <style>, <noscript>, <template>
    • HTML comments
  4. Emit signed semantic attribute records for href, src, alt, and aria-label on included elements. href and src are resolved against the signed document base URL and serialized as URLs; alt and aria-label use text normalization.
  5. Insert a single \n after boundary-producing elements (<article>, <section>, <p>, <h1><h6>, <ul>, <ol>, <li>, <blockquote>, <pre>, <div>, <table>, <tr>, …) so paragraph and list structure survives extraction.
  6. <br> produces a single \n.
  7. Inline elements (<a>, <em>, <strong>, <code>, <span>, …) introduce no separator beyond any signed attribute records.
  8. Apply Stage 2 (the 8 phases below) to text and signed attribute values.

The output of Stage 1 is a single canonical string consisting of readable words, signed attribute records, and \n block boundaries. The intent: two different HTML serializations of the same signed semantics should produce the same Stage 1 output.

Open design point. Stage 1 is being firmed up. The rules above reflect the current direction but are not yet normative. Specifically: the exact list of block-level elements, the treatment of tables (cell separators?), the handling of phrasing-content <br> inside inline contexts, and the question of whether to preserve any structural attributes are all subjects of active discussion. Community input is welcome via the spec repository.

Stage 2: text canonicalization (8 phases)

To ensure the same Stage 1 output always hashes to the same value regardless of which tool produced the upstream HTML, HTMLTrust defines an 8-phase text canonicalization algorithm:

PhaseWhat it does
1. NFKCUnicode normalize: ligatures, fullwidth/halfwidth, presentation forms
2. WhitespaceAll Unicode whitespace → ASCII; collapse runs; trim
3. Quotation marksCurly, guillemets, CJK brackets → ASCII " and '
4. DashesEn, em, figure, non-breaking → ASCII -
5. PunctuationEllipsis ...; minus sign → hyphen-minus
6. Strip invisiblesRemove ZWSP, BOM, variation selectors, tatweel
7. BidiStrip bidi controls; use the HTML dir attribute
8. LanguagePreserve semantic ZWNJ/ZWJ for Indic, Arabic, emoji

JavaScript, Go, PHP, Rust and Python all produce identical bytes for the shared conformance corpus, which is 130 fixtures at v0.3.0. They do so by binding one Rust core rather than by reimplementing the algorithm; five independent ports agreeing byte-for-byte is the earlier result that showed these rules are precise enough to reimplement at all. See htmltrust-canonicalization and the implementation page for the distinction.

Pluggable key resolution

Implementations MUST accept multiple keyid resolution methods. None is canonical.

  • DID: did:web:author.example resolves via a well-known DID document at the author's origin.
  • Direct URL: https://author.example/key.json, fetched from the author's origin as a static file.
  • Trust directory reference: https://directory.example/keys/abc123, where a federated directory serves as a convenience registry for authors who prefer hosted key discovery.

The keyid is opaque to the signature protocol. Only the resolved public key matters for cryptographic verification, which is a local operation in the user agent and never requires contacting a directory.

Endorsements

Third parties (publishers, experts, other users) may issue signed JSON endorsements of specific content hashes:

{
  "endorser":    "did:web:publisher.org",
  "endorsement": "sha256-RAyBCvKT...",
  "signature":   "BASE64_SIG",
  "timestamp":   "2026-05-01T00:00:00Z"
}
  • Endorsements target specific pieces of content, not signers
  • Stored and served by trust directories, indexed by content hash
  • Verified cryptographically by the consumer before contributing to any trust score

Ongoing or collective trust of a signer is expressed via that signer's reputation in one or more trust directories, not via persistent signer-level endorsement artifacts. This separation keeps endorsement semantics clean: an endorsement is a point-in-time attestation about a specific artifact, while directory reputation reflects an ongoing curatorial opinion about a signer.

Two layers, kept separate

User agents perform verification in two distinct layers:

  1. Cryptographic verification (local). Canonicalize → hash → resolve key → verify. Yes/no. Local, no network call beyond key resolution.
  2. Trust decision (user policy). Given a cryptographically valid signature, the user agent applies the current user's trust policy: personal trust lists, endorsements from designated parties, reputation scores from selected directories.

User interfaces SHOULD present the trust outcome as a graduated score, not a binary verdict, and SHOULD distinguish the two layers visually. A signature either verifies or it does not. Trust is a matter of degree.

Next

  • W3C CG Report: HTMLTrust

    Pre-submission draft of the W3C Community Group Report covering the HTMLTrust HTML element and DOM integration.

  • IETF Internet-Draft: HTMLTrust Wire Protocol

    Pre-submission draft of the IETF Internet-Draft covering HTMLTrust canonicalization, signing payload, encoding, key resolution, trust-directory HTTP API, and endorsement format.