{
 "slug": "webauthn-level-4-txauthsimple-10-year-battle-sign",
 "topic_id": "TOPIC-091",
 "cluster": "Cryptography & Standards",
 "tier": "Tier B",
 "title": "Transaction confirmation in the browser: the extension that never shipped",
 "summary": "WebAuthn signs a challenge. It has never signed the text a user was shown. An extension to do that has been discussed for a decade and is not deployed.",
 "lede": "The gap is well known to everyone who works on the specification: the browser can prove a user touched their authenticator, and cannot prove what was on screen when they did.",
 "date": "2024-11-04",
 "category": "Compliance",
 "author_id": "tobias-lindqvist-rao",
 "tags": [
  "WebAuthn",
  "transaction confirmation",
  "txAuthSimple",
  "standards",
  "browser",
  "trusted display"
 ],
 "image_title": "Transaction Confirmation Never Shipped",
 "schema": "Article",
 "key_takeaways": [
  "Transaction confirmation extensions would have the authenticator display text and include it in the signature. They are not implemented in practice.",
  "The obstacles are structural — constrained displays, internationalisation, platform fragmentation — rather than a lack of interest.",
  "The available approximation binds the challenge to a digest of the rendered statement, which is weaker in a specific, nameable way."
 ],
 "body": [
  {
   "type": "h2",
   "text": "What the extension would do"
  },
  {
   "type": "diagram",
   "kind": "compare",
   "alt": "What the extension would add, and what we have instead",
   "caption": [],
   "nodes": "The approximation differs in exactly one place, which is worth naming precisely.",
   "left": {
    "title": "Transaction confirmation",
    "items": [
     "Authenticator displays the text",
     "Text is inside the signature",
     "Host cannot alter the display",
     "Not deployable"
    ]
   },
   "right": {
    "title": "Digest binding",
    "items": [
     "Host displays the statement",
     "Digest is the challenge",
     "Host could display falsely",
     "Deployable today"
    ]
   }
  },
  {
   "type": "p",
   "html": "The relying party supplies a short text describing the transaction. The authenticator displays that text on its own screen, requires confirmation, and includes the text in the signed data."
  },
  {
   "type": "p",
   "html": "The effect is that the signature attests to what was displayed, on hardware the host cannot control. That closes the trusted-display gap properly, which nothing else does."
  },
  {
   "type": "h2",
   "text": "Why it has not shipped"
  },
  {
   "type": "table",
   "head": [
    "Obstacle",
    "Detail"
   ],
   "rows": [
    [
     "Display capability",
     "Most authenticators have no screen; those that do are small and monochrome"
    ],
    [
     "Text length",
     "A useful description of a payment does not fit in a line"
    ],
    [
     "Internationalisation",
     "Rendering arbitrary scripts on constrained hardware is hard"
    ],
    [
     "Platform mediation",
     "Platform authenticators would need OS-level display support"
    ],
    [
     "Fragmentation",
     "Some authenticators support it and most do not, so a relying party cannot depend on it"
    ],
    [
     "Incentives",
     "No party owns the problem; it spans authenticator vendors, browsers and platforms"
    ]
   ]
  },
  {
   "type": "p",
   "html": "The last row is the real one. The specification work is tractable; coordinating vendors, browser engines and operating systems around a feature none of them owns has not happened in ten years."
  },
  {
   "type": "h2",
   "text": "What can be done with current primitives"
  },
  {
   "type": "p",
   "html": "The challenge in a WebAuthn ceremony is arbitrary bytes chosen by the relying party. Nothing prevents making those bytes a digest of a rendered statement."
  },
  {
   "type": "code",
   "text": "statement = {\n  \"action\": \"payment.release\",\n  \"beneficiary\": \"Acme Supplies Ltd\",\n  \"account\": \"****9023\",\n  \"amount\": \"84,000.00\",\n  \"currency\": \"GBP\"\n}\nchallenge = sha256(JCS(statement))\n\n# Displayed to the user, rendered FROM the statement\n# Signed: the challenge, plus origin and rpIdHash by the browser\n# Verified: signature valid, challenge == sha256(JCS(statement)),\n#           statement matches the payload about to execute"
  },
  {
   "type": "p",
   "html": "The result is a signature cryptographically bound to a specific statement, verifiable by a third party, with the statement human-readable."
  },
  {
   "type": "h2",
   "text": "Where the approximation is weaker"
  },
  {
   "type": "p",
   "html": "Precisely one place, and it should be stated plainly rather than glossed over."
  },
  {
   "type": "p",
   "html": "The rendering happens on the host. If the host's display path is compromised, the user can be shown one statement while a different one is signed. The authenticator has no view of the screen and cannot detect this."
  },
  {
   "type": "table",
   "head": [
    "Threat",
    "Extension",
    "Digest binding"
   ],
   "rows": [
    [
     "Compromised host alters the payload after display",
     "Prevented",
     "<strong style=\"font-weight:600\">Prevented</strong> — digest mismatch"
    ],
    [
     "Compromised host displays false text",
     "Prevented",
     "<strong style=\"font-weight:600\">Not prevented</strong>"
    ],
    [
     "Relying party verification bug",
     "Neither prevents",
     "Neither prevents"
    ],
    [
     "Phishing proxy",
     "Prevented by origin binding",
     "Prevented by origin binding"
    ]
   ]
  },
  {
   "type": "p",
   "html": "The second row is the entire difference. Everything else is equivalent, which is why the approximation is worth deploying rather than waiting."
  },
  {
   "type": "h2",
   "text": "Reducing the residual risk"
  },
  {
   "type": "ol",
   "items": [
    "<strong style=\"font-weight:600\">Render on a different device.</strong> A transaction initiated on a laptop, displayed and confirmed on a phone, requires two compromises rather than one.",
    "<strong style=\"font-weight:600\">Verify independently before execution.</strong> A service that recomputes the digest from the payload it is about to execute catches manipulation between display and execution.",
    "<strong style=\"font-weight:600\">Use a secondary channel.</strong> A message to the user restating what was authorised, after the fact, surfaces a discrepancy quickly — which does not prevent it but bounds the damage.",
    "<strong style=\"font-weight:600\">Keep statements short and specific.</strong> A user scanning a long statement notices less; four fields are read, twenty are not."
   ]
  },
  {
   "type": "h2",
   "text": "What to expect"
  },
  {
   "type": "p",
   "html": "Honest assessment: transaction confirmation in the browser is unlikely to become broadly deployable soon. The obstacles are organisational rather than technical, and organisational obstacles of this shape persist."
  },
  {
   "type": "p",
   "html": "Building on digest binding now is therefore not a stopgap for a thing arriving next year. It is the available mechanism, and a system built on it would need minimal change if the extension ever shipped — the statement is already canonical and already rendered from itself."
  },
  {
   "type": "h2",
   "text": "Where the approximation is weaker, precisely"
  },
  {
   "type": "table",
   "caption": "Threat by threat",
   "head": [
    "Threat",
    "Extension",
    "Digest binding"
   ],
   "rows": [
    [
     "Payload altered after display",
     "Prevented",
     "<strong style=\"font-weight:600\">Prevented</strong>"
    ],
    [
     "<strong style=\"font-weight:600\">Host displays false text</strong>",
     "<strong style=\"font-weight:600\">Prevented</strong>",
     "<strong style=\"font-weight:600\">Not prevented</strong>"
    ],
    [
     "Relying party verification bug",
     "Neither",
     "Neither"
    ],
    [
     "Phishing proxy",
     "Origin binding",
     "Origin binding"
    ]
   ]
  },
  {
   "type": "p",
   "html": "One row differs. Everything else is equivalent, which is why the approximation is worth deploying rather than waiting for a feature that has not moved in a decade."
  },
  {
   "type": "h2",
   "text": "Objections and honest limits"
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“So we should wait.”</strong> The obstacles are organisational rather than technical, and organisational obstacles of this shape persist. A system built on digest binding needs minimal change if the extension ever ships."
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“Rendering on a second device solves it fully.”</strong> It requires two compromises rather than one, which is a real improvement and not a proof. Combined with independent verification before execution it is the practical ceiling today."
  }
 ],
 "faq": [
  {
   "q": "What would transaction confirmation add?",
   "a": "The authenticator would display the text on its own screen and include it in the signature, so the attestation covers what was displayed on hardware the host cannot control."
  },
  {
   "q": "Why hasn't it shipped?",
   "a": "Most authenticators have no display, text and internationalisation are hard on constrained hardware, and no single party owns a feature spanning vendors, browsers and platforms."
  },
  {
   "q": "How is digest binding weaker?",
   "a": "In exactly one way: a compromised host can display false text. It still prevents payload alteration after display, and origin binding still defeats phishing proxies."
  },
  {
   "q": "How do you reduce the residual risk?",
   "a": "Render on a different device from the one that built the transaction, verify the digest independently before execution, and keep statements short enough to be read."
  },
  {
   "q": "What would the extension add?",
   "a": "The authenticator would display the text on hardware the host cannot control and include it in the signature."
  }
 ],
 "sources": [
  {
   "t": "W3C Web Authentication Level 3",
   "u": "https://www.w3.org/TR/webauthn-3/"
  },
  {
   "t": "FIDO CTAP 2.2 specification",
   "u": "https://fidoalliance.org/specs/fido-v2.2-ps-20250714/fido-client-to-authenticator-protocol-v2.2-ps-20250714.html"
  },
  {
   "t": "Published discussion of transaction confirmation extension status."
  },
  {
   "t": "RFC 8785 — JSON Canonicalization Scheme",
   "u": "https://www.rfc-editor.org/rfc/rfc8785"
  },
  {
   "t": "FIDO Alliance — specifications",
   "u": "https://fidoalliance.org/specifications/"
  }
 ],
 "related": [
  {
   "slug": "four-tier-presence-hierarchy-p1-p4-matching",
   "title": "The four-tier presence hierarchy",
   "category": "Compliance"
  },
  {
   "slug": "human-middle-hitm-attack-passkeys-phishing-proxies",
   "title": "Phishing proxies and passkeys",
   "category": "Developer"
  },
  {
   "slug": "bybit-wazirx-multisig-hack-payload-mismatch-postmortem",
   "title": "The payload flip",
   "category": "Crypto"
  }
 ],
 "image": "https://cdn.twc.sh/images/igcache/Transaction%20Confirmation%20Never%20Shipped/1200_630/blog.jpg",
 "wordcount": 879,
 "url": "/blog/webauthn-level-4-txauthsimple-10-year-battle-sign.html",
 "reading_time": "4 min read",
 "seo_title": "Transaction confirmation: the extension that never shipped",
 "hub": {
  "slug": "topics/receipt-cryptography",
  "title": "Receipt cryptography and standards"
 },
 "answer": "Because no single party owns it. The extension would have the authenticator display the transaction on its own screen and include that text in the signature. Most authenticators have no screen, text and internationalisation are hard on constrained hardware, and the feature spans vendors, browsers and operating systems.",
 "answer_q": "Why has transaction confirmation never shipped in browsers?",
 "glossary": [
  {
   "term": "Transaction confirmation",
   "def": "A proposed extension having the authenticator display and sign the transaction text."
  },
  {
   "term": "Digest binding",
   "def": "Using a hash of the rendered statement as the WebAuthn challenge, so the signature covers a specific statement."
  },
  {
   "term": "Trusted display",
   "def": "Hardware the host cannot control, which is what the extension would provide."
  }
 ],
 "checklist": {
  "title": "Reducing the residual display risk",
  "id": "residual",
  "desc": "Four steps.",
  "steps": [
   {
    "name": "Render on a different device from the one that built the transaction.",
    "text": "Two compromises, not one."
   },
   {
    "name": "Verify independently before execution.",
    "text": "Catches manipulation between display and execution."
   },
   {
    "name": "Notify out of band after the fact.",
    "text": "Bounds the damage rather than preventing it."
   },
   {
    "name": "Keep statements short.",
    "text": "Four fields are read; twenty are not."
   }
  ]
 },
 "cta": {
  "title": "Where this fits in Manav",
  "html": "Manav's receipt is deliberately small: RFC 8785 canonicalisation, one algorithm, keys at a well-known URL, and a verifier short enough to audit or reimplement.",
  "href": "../architecture.html",
  "label": "Read the architecture"
 }
}