Skip to main content
Security & architectureSecurity reviewers, ITReviewed by Security

Verifying a published results document

What InviziPoll signs when it publishes results, how to check the signature with a stock ML-DSA-65 verifier, what the floors mean, and what the document leaves out.

7 MIN READUPDATED SEP 10, 2026MAINTAINED BY SECURITY
On this page

A published results document is a set of aggregate numbers, the anonymity floors that governed them, and a signature made with a key that belongs to the workspace. Anyone holding the document and the workspace's public key can check that these exact numbers were published by that workspace, with no account, no login, and no InviziPoll code. Use this page when a client, a board, or an auditor wants to check a finding rather than take it on trust.

When to use it

  • A security team has been handed a survey finding and wants to confirm it matches what the tool produced.
  • A consultant's report cites a signed document and the client wants to check the citation.
  • You're comparing a PDF someone sent you against the machine-readable document it claims to come from.

What is signed

Every document is a JSON body with a fixed shape and a signature envelope around it. The body is the claim. The envelope says who vouched for it.

The body carries:

FieldWhat it holds
docTypeinvizipoll.published-aggregate
v3 for anything published today
subjectpoll, series, wave, as-of date
scopepoll or cohort, its label, and n
floorsthe three floors, as integers
questionsone aggregate per question
cutsnamed cuts of the same responses
inclusionReceiptRootreserved, always null

The signature envelope wraps the body without changing it:

{
  "body": { "docType": "invizipoll.published-aggregate", "v": 3 },
  "canonical": "base64url SHA-256 of the canonical bytes",
  "signatures": [
    {
      "kind": "workspace",
      "keyId": "base64url SHA-256 of the public key",
      "alg": "ML-DSA-65",
      "sig": "base64url",
      "signedAt": "2026-09-10T09:12:00.000Z"
    }
  ]
}

Three properties matter for a verifier:

  • The bytes are canonical. Keys serialize in a fixed order, integers stay integers, other numbers are rounded to four decimal places with no exponent, and the array of cuts is sorted by its id before signing. Two independent implementations produce the same byte string from the same inputs, which is what lets you re-derive and compare.
  • Nothing in the body says where it was computed. No browser, no device, no "generated at" instant. The only timestamp is signedAt, and it lives on the signature, not in the claim. Re-signing the same body with a different key yields the same document.
  • The signer is the workspace. Each workspace holds one ML-DSA-65 keypair. The secret is created in an owner's or admin's browser and stored wrapped under the workspace's own encryption key. The public key is published on the workspace. InviziPoll's servers never hold the signing secret, so a document signed by the workspace is a claim we couldn't have forged.

What the floors mean

floors is the part most readers skip and the part that makes the numbers meaningful.

FloorValueMeaning
resultsUnlock3responses before any result exists
cohortMin5smallest group in any cut
authoredKadmin's choice, at least 3the minimum set on this poll

A poll-scoped document must have scope.n at or above resultsUnlock. A cohort-scoped document, and every entry in cuts, must have n at or above cohortMin. The check is against the document's own floors, so a validator with the published shape and no knowledge of InviziPoll can run it.

The floors are signed alongside the counts. Nobody can widen or narrow a cohort after the fact to change how a number reads, because the conditions of the number's validity are inside the same signature. A group below the floor isn't in the document at all. There is no "suppressed" marker, because a marker would tell you a small group exists, which is itself a disclosure.

How to verify

You need the document's JSON and the workspace's public key. The public results page fetches both from the same place and does this check in the reader's browser. The evidence packet an admin can export carries the JSON beside its PDF. Any ML-DSA-65 library will do for the check itself.

  1. Parse the document and confirm it matches the shape above. Refuse anything that doesn't.
  2. Rebuild the canonical bytes from body using the ordering rules, and hash them with SHA-256. The base64url of that hash must equal canonical.
  3. Find a signature you recognise. Take the entry with kind: "workspace" whose keyId equals the base64url SHA-256 of the public key you hold. If no entry matches, the check fails. Zero recognised signatures is a failure, not a warning.
  4. Verify sig over the canonical bytes with ML-DSA-65 and the public key.
  5. Read the floors and check every n against them.

What a failed verification looks like

InviziPoll's own pages say the failure out loud rather than dropping a badge quietly. The public results page and a shared slice show one of these sentences:

  • "Signature checks out. These numbers are exactly what the workspace published."
  • "We couldn't verify this. Nothing here carries a signature this build knows how to check." The document was published unsigned, usually because the publishing browser hadn't unlocked the workspace encryption.
  • "We couldn't verify this. The signature didn't match the workspace's published key."
  • "We couldn't verify this. The numbers don't match the fingerprint that was signed, so treat them as unconfirmed."

In every failing case the charts still render, under the warning. The reader is told what they're looking at, and left to decide what it's worth.

If you edit one digit in a signed document and run the check yourself, step 2 fails first: the canonical hash no longer matches.

What the document does not contain

  • No written answer. Open-text questions appear as recurring terms with counts.
  • No per-response data of any kind, and no submission times. Response records carry no timestamp.
  • No group below the floor.
  • No respondent identity, and no roster values other than the labels of the cuts that cleared the floor.
  • No record of the device or person that computed it.

The document is a claim about aggregates. The reason it's worth signing is that the underlying answers were encrypted in each respondent's browser, so the aggregate is the only readable form the data ever takes outside the workspace's own keys. See Post-quantum cryptography for the primitives and Encryption model overview for the key hierarchy the signing key sits inside.

Limits and gates

  • One signing key per workspace. There is no rotation yet. keyId travels in every signature so rotation can arrive later without invalidating documents already out there.
  • A document can be published unsigned. The publishing admin is warned before the click, and the page says so afterwards.
  • There is no document history. A republish overwrites the current document on the public page. A copy you saved stays valid on its own bytes.
  • Verification says nothing about whether a PDF was produced from the document. Compare the PDF's numbers to the JSON yourself.