Factum Verifier
Verify a certificate without us.
One file. It runs offline on your machine and needs only Node 18 or later: no install, no network, no account. Verifying is free. It stays free, because the file is yours once you hold it and it never calls Factum.
The file
- File
- factum-verify.mjs
- Version
- 1.0.0
- Size
- 32,347 bytes
- sha256
- 6cb8f26560867806b9f4b6a1b0f631eded8861f2672097b6c337e7045bd0bf67
The file is published byte for byte as it was built and tested. Check the hash before you run it, and compare it with the one printed here.
Linux
sha256sum -c factum-verify.mjs.sha256
macOS
shasum -a 256 -c factum-verify.mjs.sha256
Windows
certutil -hashfile factum-verify.mjs SHA256
One command
node factum-verify.mjs <certificate.json> <public-key>
The first argument is a certificate, exactly as it was returned. The second is the issuer's public key: the base64 of the raw 32-byte Ed25519 key, given inline or as a file, or a saved key set. With a key set, the certificate's own key id selects the key, so a certificate signed before a key rotation still verifies.
With no arguments, the file runs a frozen known-answer test of its own canonical form and signature check.
| Exit code | Meaning |
|---|---|
| 0 | Verified |
| 1 | Does not verify A named code is printed. |
| 2 | The command was called wrongly. Nothing was checked. |
What a verification says
CERTIFIED BY THIS CHECK
The document was signed by the holder of the private half of the key you supplied, over the canonical form named in its signature block, and has not been altered by one byte since.
NOT ATTESTED
That the key you supplied is the issuer's. That comes from where you obtained the key, and from nothing in the file. A valid signature under the wrong key is exactly what a forgery looks like.
A certificate is tamper-evident: a change is detectable. The verifier detects it. Nothing prevents the change.
Try it on a recorded certificate
The demonstration publishes two certificates of one document, the key that signed them and a copy with one digit changed. Download them beside the verifier and run the three commands.
The files: run-1.json, run-2.json, run-1-one-digit-changed.json, public-key.txt.
node factum-verify.mjs run-1.json public-key.txt
VERIFIED keyId vd-418b7ced1cea5f8e canonical bytes 59952 barcode … (the signed barcode, 479 characters, printed in full when you run it) surface FULL - the body carries no surface block, so every claim the run captured is on it CERTIFIED BY THIS CHECK: this document was signed by the holder of the private half of the key you supplied, over the canonical form named in its signature block, and has not been altered by one byte since. NOT CERTIFIED BY THIS CHECK: that the key you supplied is the operator's. That comes from WHERE YOU OBTAINED THE KEY and from nothing in this file - a valid signature under the wrong key is exactly what a forgery looks like.
Exit code 0.
node factum-verify.mjs run-1-one-digit-changed.json public-key.txt
NOT VERIFIED code signature_does_not_verify document keyId vd-418b7ced1cea5f8e your key's keyId vd-418b7ced1cea5f8e reason the signature does not verify over this document under the supplied key. The keyId matched, so the key is the right one: the document does not hold the bytes that were signed.
Exit code 1. One digit of the total was changed.
node factum-verify.mjs
ok W1 VD-CANON-1 still produces the FROZEN canonical bytes (213 chars) ok W1b NON-VACUITY: the frozen string is 213 code units and genuinely sorted, so W1 is not comparing two empty strings ok W1c the accented key is the TWO-CODE-UNIT combining form, which is what the frozen bytes were minted from ok W2 the FROZEN signature verifies against the FROZEN public key ok W3 and the keyId DERIVES from that key alone - no marker, no second representation ok W4 a ONE-FIELD tamper on the frozen vector is REFUSED by name ok W5 and a stripped block is a FAILURE rather than "nothing to check" ok W6 THE SET PATH: the frozen vector verifies against the published set with its key RETIRED and NOT FIRST - the document's own keyId selects, so a certificate issued before a rotation still verifies ok W7 and a set that does NOT hold its key is still key_id_mismatch - the set widens WHO may verify, never WHETHER ok W8 and a tamper is refused through the set exactly as through a single key - a retired key verifies, it does not excuse 10/10 checks passed
Exit code 0. The known-answer test, check lines only.
Where the key comes from
The issuer's public keys, current and retired, are published by the service and served without an account. A certificate names the id of the key that signed it. The address of the key set is given with access, and anyone you pass it to can use it.
The verifier is the part of the system that needs no other part. Every certificate on this site, from every worked example, is checked by this one file.