{
 "slug": "building-manav-verify-write-open-source-offline-verifier",
 "topic_id": "TOPIC-097",
 "cluster": "Cryptography & Standards",
 "tier": "Tier B",
 "title": "A verifier small enough that someone can read all of it",
 "summary": "Verification is the point where trust is established. A verifier pulling hundreds of transitive dependencies has a trust base nobody has examined.",
 "lede": "If the whole argument is that a third party can check the evidence themselves, the tool they use to check it should be something they can read in an afternoon.",
 "date": "2024-10-19",
 "category": "Compliance",
 "author_id": "nadia-ferreira-strand",
 "tags": [
  "open source",
  "verifier",
  "dependencies",
  "supply chain",
  "auditable code",
  "design"
 ],
 "image_title": "Verifier Small Enough To Read",
 "schema": "Article",
 "key_takeaways": [
  "A verifier's dependency tree is part of its trust base, and a large one cannot be reviewed by the parties who need to trust it.",
  "The work is small: canonicalise, hash, verify a signature, check a few fields. Platform primitives cover the cryptography.",
  "Constraining the format to one algorithm and one canonicalisation is what keeps the verifier small."
 ],
 "body": [
  {
   "type": "h2",
   "text": "Why dependency count is a security property here"
  },
  {
   "type": "diagram",
   "kind": "compare",
   "alt": "Two verifiers, two trust bases",
   "caption": [],
   "nodes": "The right column can be audited by the parties who need to rely on it.",
   "left": {
    "title": "Large dependency tree",
    "items": [
     "Hundreds of packages",
     "Supply chain is the trust base",
     "Uncertain to build in ten years",
     "Impractical to reimplement"
    ]
   },
   "right": {
    "title": "Minimal verifier",
    "items": [
     "A few hundred lines",
     "Platform crypto only",
     "Few moving parts",
     "Reimplementable from the spec"
    ]
   }
  },
  {
   "type": "p",
   "html": "For most software, dependencies are an engineering trade-off. For a verifier they are part of what a relying party must trust."
  },
  {
   "type": "table",
   "head": [
    "Question",
    "Large dependency tree",
    "Minimal verifier"
   ],
   "rows": [
    [
     "Can an auditor review it?",
     "No — hundreds of packages",
     "Yes — a few hundred lines"
    ],
    [
     "What is the supply chain exposure?",
     "Every transitive package",
     "The platform's own crypto"
    ],
    [
     "Will it build in ten years?",
     "Uncertain",
     "Likely — few moving parts"
    ],
    [
     "Can a sceptical party reimplement it?",
     "Impractical",
     "Yes, from the specification"
    ]
   ]
  },
  {
   "type": "p",
   "html": "The last row is the strongest argument. A format whose verifier can be reimplemented independently is one where a dispute can be settled by two parties writing their own checks and comparing."
  },
  {
   "type": "h2",
   "text": "What the verifier does"
  },
  {
   "type": "p",
   "html": "The whole task, enumerated. It is shorter than people expect."
  },
  {
   "type": "ol",
   "items": [
    "Parse the receipt as JSON.",
    "Canonicalise the statement per RFC 8785 and hash it.",
    "Compare the hash to the challenge in the authenticator data.",
    "Verify the authenticator's signature over the authenticator data and client data hash.",
    "Check the origin and relying party identifier against expected values.",
    "Check the user verification flag.",
    "Verify the issuer countersignature over the whole receipt.",
    "Check the key id against the published key set and the signing window.",
    "Check expiry and revocation.",
    "Return a structured result with a specific reason for any failure."
   ]
  },
  {
   "type": "p",
   "html": "Canonicalisation is the largest piece — number formatting and string escaping have edge cases. Everything else is short."
  },
  {
   "type": "h2",
   "text": "Using platform primitives"
  },
  {
   "type": "code",
   "text": "// Browser and Node: built in, no dependency\nconst ok = await crypto.subtle.verify(\n  \"Ed25519\", publicKey, signature, message\n);\n\n// Python: standard library plus one well-audited primitive\n// Go:     crypto/ed25519 in the standard library\n// Rust:   a single audited crate\n\n// The cryptography is not where the dependencies come from.\n// Convenience layers are."
  },
  {
   "type": "p",
   "html": "This is the practical insight. Verifier bloat comes from JSON schema validators, date libraries, HTTP clients and logging frameworks — not from cryptography, which every platform now provides."
  },
  {
   "type": "h2",
   "text": "What constraining the format buys"
  },
  {
   "type": "p",
   "html": "Each option in a format is code in every verifier, forever."
  },
  {
   "type": "ul",
   "items": [
    "One signature algorithm — no negotiation, no algorithm confusion, no dispatch table",
    "One canonicalisation — no format detection",
    "One receipt version at a time, with explicit migration rather than compatibility branches",
    "Required fields rather than optional ones, so there is no absent-field logic"
   ]
  },
  {
   "type": "p",
   "html": "These are restrictions that feel limiting when designing and are the reason the verifier is readable."
  },
  {
   "type": "h2",
   "text": "Error reporting matters more than it seems"
  },
  {
   "type": "p",
   "html": "A verifier that returns false is useless in an audit. The person running it needs to know which check failed and what that implies."
  },
  {
   "type": "code",
   "text": "{\n  \"valid\": false,\n  \"reason\": \"challenge_mismatch\",\n  \"detail\": \"statement digest 9c1f...a83e does not match\n             challenge 2b7d...4056 in authenticator data\",\n  \"implication\": \"the signed statement differs from the one\n                  presented\"\n}"
  },
  {
   "type": "p",
   "html": "An expired receipt, a revoked key and a statement mismatch mean entirely different things. Collapsing them into one boolean discards exactly the information an investigation needs."
  },
  {
   "type": "h2",
   "text": "What to publish alongside it"
  },
  {
   "type": "ol",
   "items": [
    "The source, under a licence permitting inspection and reimplementation.",
    "Test vectors, including negative cases — tampered statements, wrong keys, expired receipts.",
    "A written specification sufficient to reimplement without reading the code.",
    "A statement of what verification establishes and, explicitly, what it does not."
   ]
  },
  {
   "type": "p",
   "html": "The fourth is the one that tends to be omitted and is the most important. A verifier confirms a signature and a binding; it does not confirm that the signer understood what they signed, and saying so plainly is what makes the rest credible."
  },
  {
   "type": "h2",
   "text": "The whole task, enumerated"
  },
  {
   "type": "table",
   "caption": "Ten steps, and where the work actually is",
   "head": [
    "Step",
    "Effort"
   ],
   "rows": [
    [
     "Parse the receipt",
     "Trivial"
    ],
    [
     "Canonicalise and hash the statement",
     "<strong style=\"font-weight:600\">The largest piece</strong>"
    ],
    [
     "Compare to the challenge",
     "Trivial"
    ],
    [
     "Verify the authenticator signature",
     "Platform primitive"
    ],
    [
     "Check origin and RP ID",
     "Trivial"
    ],
    [
     "Check user verification",
     "Trivial"
    ],
    [
     "Verify the issuer countersignature",
     "Platform primitive"
    ],
    [
     "Check key id and signing window",
     "Trivial"
    ],
    [
     "Check expiry and revocation",
     "Trivial"
    ],
    [
     "Return a structured result",
     "<strong style=\"font-weight:600\">Matters more than it looks</strong>"
    ]
   ]
  },
  {
   "type": "p",
   "html": "Verifier bloat comes from JSON schema validators, date libraries, HTTP clients and logging frameworks — not from cryptography, which every modern platform provides natively."
  },
  {
   "type": "h2",
   "text": "Objections and honest limits"
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“A boolean result is simpler.”</strong> And useless in an audit. An expired receipt, a revoked key and a statement mismatch mean entirely different things, and collapsing them discards exactly what an investigation needs."
  },
  {
   "type": "p",
   "html": "<strong style=\"font-weight:600\">“Constraining the format limits future needs.”</strong> Every option is code in every verifier, forever. One algorithm and one canonicalisation is what keeps it readable, and readability is the whole point."
  }
 ],
 "faq": [
  {
   "q": "Why do dependencies matter for a verifier specifically?",
   "a": "They are part of what a relying party must trust. If an auditor cannot review the tool, they are trusting a supply chain instead of checking evidence."
  },
  {
   "q": "Where does verifier bloat actually come from?",
   "a": "Schema validators, date libraries, HTTP clients and logging — not cryptography, which every modern platform provides natively."
  },
  {
   "q": "Why constrain the format so tightly?",
   "a": "Every option is code in every verifier forever. One algorithm and one canonicalisation is what keeps it readable."
  },
  {
   "q": "Why does error detail matter?",
   "a": "An expired receipt, a revoked key and a statement mismatch mean different things. A boolean discards what an investigation needs."
  },
  {
   "q": "Where does verifier bloat come from?",
   "a": "Schema validators, date libraries, HTTP clients and logging — not cryptography, which platforms provide natively."
  },
  {
   "q": "What should be published alongside the code?",
   "a": "Test vectors with negative cases, a reimplementable specification, and an explicit statement of what verification does not establish."
  }
 ],
 "sources": [
  {
   "t": "RFC 8785 — JSON Canonicalization Scheme",
   "u": "https://www.rfc-editor.org/rfc/rfc8785"
  },
  {
   "t": "RFC 8032 — Edwards-curve Digital Signature Algorithm (EdDSA)",
   "u": "https://www.rfc-editor.org/rfc/rfc8032"
  },
  {
   "t": "W3C Web Cryptography API",
   "u": "https://www.w3.org/TR/WebCryptoAPI/"
  },
  {
   "t": "Published research on software supply chain risk in dependency trees."
  }
 ],
 "related": [
  {
   "slug": "cryptographic-receipt-primitive-rfc8785-ed25519-vs-jwt",
   "title": "A session token is not an audit record",
   "category": "Compliance"
  },
  {
   "slug": "automated-compliance-auditing-manav-verify-external-auditors-re",
   "title": "Re-performing controls offline",
   "category": "Developer"
  },
  {
   "slug": "signed-revocation-lists-vs-ocsp-building-fast-offline",
   "title": "Signed revocation lists versus OCSP",
   "category": "Comparison"
  }
 ],
 "image": "https://cdn.twc.sh/images/igcache/Verifier%20Small%20Enough%20To%20Read/1500_900/blog.jpg",
 "wordcount": 898,
 "url": "/blog/building-manav-verify-write-open-source-offline-verifier.html",
 "reading_time": "4 min read",
 "hub": {
  "slug": "topics/receipt-cryptography",
  "title": "Receipt cryptography and standards"
 },
 "answer": "Because its dependency tree is part of what a relying party must trust. If the argument is that a third party can check the evidence themselves, the tool they use has to be something they can read in an afternoon — and a verifier pulling hundreds of transitive packages is not.",
 "answer_q": "Why should a verifier have no dependencies?",
 "glossary": [
  {
   "term": "Transitive dependency",
   "def": "A package your dependency depends on — part of your trust base whether or not you chose it."
  },
  {
   "term": "Test vector",
   "def": "A known input and expected output used to validate an independent implementation."
  },
  {
   "term": "Negative test",
   "def": "A case that must fail, which is what distinguishes a correct verifier from a permissive one."
  }
 ],
 "checklist": {
  "title": "What to publish with a verifier",
  "id": "publish",
  "desc": "Four items.",
  "steps": [
   {
    "name": "The source, under a licence permitting reimplementation.",
    "text": "Inspection is not enough."
   },
   {
    "name": "Test vectors including negative cases.",
    "text": "Tampered statements, wrong keys, expired receipts."
   },
   {
    "name": "A specification sufficient to reimplement without the code.",
    "text": "So a sceptic can write their own."
   },
   {
    "name": "A statement of what verification does not establish.",
    "text": "The item most often omitted, and the most important."
   }
  ]
 },
 "cta": {
  "title": "Where this fits in Manav",
  "html": "Manav's receipt is deliberately small: RFC 8785 canonicalisation, one signature algorithm, keys published at a well-known URL, and a verifier short enough to audit or reimplement.",
  "href": "../architecture.html",
  "label": "Read the architecture"
 }
}