Proof format specification
This document describes the VerifBox proof format precisely enough for anyone to verify a proof without VerifBox. Version 1.1, October 2026: adds the anchor of the attestation (section 7) and the IIC TSA time-stamp token (section 11); the signed content is unchanged, so earlier proofs remain valid.
1. Overview
A VerifBox proof binds the SHA-256 fingerprint of a file to a date and time, by means of two independent signatures: Ed25519 (RFC 8032) and ML-DSA-65 (NIST FIPS 204). It can be complemented by two OpenTimestamps anchors in the Bitcoin blockchain and by an RFC 3161 time-stamp token. The file itself is never sent to the service.
2. Proof file (.verifbox.json)
{
"contenu": {
"format": "verifbox-preuve-1",
"emetteur": "verifbox.com",
"algorithme": "SHA-256",
"empreinte": "<SHA-256 of the file, 64 lowercase hexadecimal characters>",
"horodatage": "<UTC date and time, ISO 8601, e.g. 2026-10-03T07:12:22.736Z>",
"identifiant": "<proof identifier, 32 hexadecimal characters>",
"cle": "<signing key identifier, 16 hexadecimal characters>"
},
"signatures": { "ed25519": "<base64url>", "ml_dsa_65": "<base64url>" },
"ancrages": {
"bitcoin": { "format": "opentimestamps", "etat": "...", "ots": "<base64>" },
"attestation": { "format": "opentimestamps", "etat": "...", "ots": "<base64>" }
},
"jetons": {
"iic_tsa": { "format": "rfc3161", "emetteur": "IIC TSA", "politique": "...", "heure": "...", "serie": "...", "tsr": "<base64>" }
},
"informatif": { "fichier": "...", "taille": 0, "note": "..." }
}
Strict format. The seven fields of contenu are all required and no other field is allowed: format is verifbox-preuve-1; emetteur is verifbox.com; algorithme is SHA-256; empreinte is 64 lowercase hexadecimal characters; identifiant is 32 and cle 16 lowercase hexadecimal characters; horodatage is an ISO 8601 UTC date and time ending in Z, with at most six fractional digits. The signatures are base64url strings without padding, decoding to exactly 64 bytes (Ed25519) and 3,309 bytes (ML-DSA-65). A verifier must reject any proof that does not comply, as well as any proof file larger than 1 MB.
Only contenu is signed. ancrages, jetons and informatif are not signed: the anchors and the token carry their own proof (sections 7 and 11), and the informative fields are for convenience only. jetons is optional.
3. Signed message
All values of contenu are ASCII strings. Its canonical form is the JSON serialisation with keys sorted in lexicographic order, no whitespace, and ASCII-only escaping (Python: json.dumps(contenu, sort_keys=True, separators=(",", ":"), ensure_ascii=True)).
The signed message is the UTF-8 encoding of the prefix verifbox-preuve-v1 followed by a line feed (0x0A), then the canonical form. The two signatures are computed over this same message, without pre-hashing:
- Ed25519, pure mode (RFC 8032), 64-byte signature;
- ML-DSA-65, pure mode, empty context (FIPS 204), 3,309-byte signature.
Signatures are encoded in base64url without padding.
4. Public key file
The public keys are published at /.well-known/verifbox-cles.json, in the verifbox-cles-2 format:
{
"format": "verifbox-cles-2",
"cles": [{
"cle": "258e57366bcdb6ce",
"statut": "active", <- active | retiree | revoquee
"valide_depuis": "2026-10-03T06:20:00Z",
"valide_jusqu": null, <- retirement date, or null
"revoquee_le": "...", <- only if the key is revoked
"ed25519_public": "<base64url, 32 bytes>",
"mldsa65_public": "<base64url, 1,952 bytes>",
"attestation": { ... } <- absent for the root key
}]
}
The key identifier is the first 16 hexadecimal characters of SHA-256(Ed25519 public key ‖ ML-DSA-65 public key), computed on the raw bytes. A retired key remains published forever, so that proofs it signed can still be verified.
Test keys. Keys used for tests, evaluations or proofs of concept are generated separately from production keys, carry their own identifiers and are never listed in the production key file. No private key, production or test, appears in the published repositories or examples.
5. Key lifecycle
Root key and trust. The first key carries no attestation. A verifier must define its root keys from a source it trusts, and identify them by the full SHA-256 of their two public keys, checked against the independent publications (section 6). A key file obtained from any other source may only add keys attested by a key that is already trusted, and may only make known keys more restrictive (retired, revoked): it can never add a root key or lift a revocation. The root key currently in use is 258e57366bcdb6ce, whose full fingerprint is 258e57366bcdb6ce16cc22fe972c638346de2a5415327154a030e5a6b9293946.
Rotation. When a key is replaced, the old key signs the new one. The attestation contains par (old key identifier), date, and the two signatures, computed over the prefix verifbox-transition-v1 followed by a line feed, then the canonical form of {"ancienne", "nouvelle", "date", "ed25519_public", "mldsa65_public"}, where the last two values are those of the new key. The old key then becomes retiree, with valide_jusqu set to the transition date.
Temporal rules. An attestation must be dated within the validity period of the attesting key (between its valide_depuis and, where set, its valide_jusqu). The validity of an attested key starts at the later of its valide_depuis and the date of its attestation: since that date is covered by the attestation signatures, it cannot be moved earlier by editing the key file. A proof dated before the start of validity of its key is invalid.
Dating a rotation is not authorising it. A Bitcoin anchor can show when a transition existed; it does not show that the transition was authorised. Authorisation comes from the attestation signed by the previous key (Ed25519 and ML-DSA-65) and from the independent publication of the key file (DNS, GitHub). A verifier relies on both, never on an anchor alone. The key file records, for each key, the previous key, the transition date, the status, the validity period and, where applicable, the revocation date.
Restrictive key files. Because a key file from another source can make known keys more restrictive, it can cause a valid proof to be refused; it can never cause an invalid proof to be accepted. A verifier should report which keys were accepted and rejected.
Revocation. If a private key is compromised, the key is marked revoquee with the date revoquee_le. Since whoever holds the key could produce backdated attestations, a proof signed by a revoked key is only reliable if the Bitcoin anchor of its attestation (section 7) is recorded in a block dated before the revocation. The anchor of the file is not enough: it shows that the file existed, not that this attestation was issued before the key was stolen. For a proof without an anchor of its attestation (issued before version 1.1), the signed date can no longer be relied upon after a revocation; at most, the anchor of the file shows that the file existed before its block. A revoked key can no longer attest another key.
Revocation record. The key file is the public record of key statuses: every retirement and every revocation appears in it with its date, and is mirrored in the DNS publication and on GitHub. The version history of the GitHub repository keeps every published state of the key file, with its date, so that the status of a key at any past moment can be checked.
6. Independent publication (root of trust)
The SHA-256 fingerprint of the public key file is published outside verifbox.com, through channels that do not depend on the service:
- DNS of internetidentitycard.com, signed with DNSSEC: TXT record
_verifbox.internetidentitycard.com, of the formverifbox-cle=<identifier> sha256=<fingerprint of the key file> depuis=<date>. Check:dig +dnssec TXT _verifbox.internetidentitycard.com. - GitHub repository: https://github.com/httpscard/verifbox-cles, which holds a copy of the key file and its fingerprint, with a dated history.
Compare the SHA-256 of /.well-known/verifbox-cles.json with these publications. Any key rotation is accompanied by their update.
7. Bitcoin anchor
ancrages.bitcoin.ots is a standard OpenTimestamps file (detached timestamp, SHA-256 operation), encoded in base64. The file digest it contains must be equal to contenu.empreinte. It can be checked with the official client: ots verify file.ots. The anchor establishes that the file existed before the date of the Bitcoin block, independently of VerifBox and of its keys.
Anchor of the attestation. Since version 1.1, ancrages.attestation.ots is a second OpenTimestamps file, built in the same way, whose file digest is the digest of the signed attestation:
SHA-256( "verifbox-attestation-v1\n" + canonical(contenu) + "\n" + signatures.ed25519 + "\n" + signatures.ml_dsa_65 )
where the strings are UTF-8 encoded, the canonical form is that of section 3 and the signatures are the base64url strings of the proof. This anchor proves that this exact attestation, with its signed date, existed before the date of its Bitcoin block. It is what allows a proof to remain reliable after the revocation of its key. It can be checked with the official client: ots verify -d <attestation digest> attestation.ots.
What an anchor establishes, and what it does not. Three notions must be kept apart. The declared time (horodatage) is the time given by VerifBox and covered by its signature. A Bitcoin anchor establishes an upper bound: the anchored digest was committed no later than its block. Block timestamps are not precise to the second; they can differ from real time by up to about two hours. The revocation status of a key is a third, separate question: it depends on how recent the key file used by the verifier is. An anchor never proves the exact moment a file was created.
8. Verification algorithm
- Compute the SHA-256 of the original file and compare it with
contenu.empreinte. - Obtain the public key file, and compare its SHA-256 with its independent publication (section 6).
- Find the entry whose
cleequalscontenu.cle, and recompute the identifier from its two public keys. - Check the attestation chain up to a root key.
- Check that
horodatagelies betweenvalide_depuisandvalide_jusqu. - Rebuild the signed message and verify both signatures. Both must be valid.
- If the anchors are present, check that the digest of the file anchor equals
empreinteand that the digest of the attestation anchor equals the digest of the attestation (section 7), then verify them against Bitcoin. - If the key is revoked, accept the proof only if the anchor of its attestation is in a block dated before the revocation.
- If the proof contains an IIC TSA token, verify it (section 11). An invalid token makes the proof invalid; a proof without a token remains valid.
9. Independent verifiers
In a browser, offline: verifbox-verify-offline.html, published on GitHub, is a single HTML page that includes the cryptographic libraries and the public key file. Its source code can be read on GitHub before downloading it. Once saved on your computer, it opens in any browser and works without any internet connection; its security policy forbids it from making any network request. It checks the file fingerprint, both signatures and the key lifecycle, and extracts the .ots file. It is a fallback for when verifbox.com is not available: the Verify page of this website remains the usual way to check a proof.
In Python:
A verifier implementing this algorithm, in a single Python file with no dependency on VerifBox, is available: verifbox_verify.py (MIT licence), on GitHub or on verifbox.com.
pip install cryptography dilithium-py
python3 verifbox_verify.py proof.verifbox.json original-file --ots anchor.ots
ots verify anchor.ots
It downloads the key file from verifbox.com (or uses a local copy with --keys) and automatically compares its fingerprint with the DNS publication, validated by DNSSEC. With --offline, it works without any network access.
The block number it displays is read from the .ots file itself and is not verified against Bitcoin; the bloc and etat fields of ancrages, which are not signed, are never displayed as authoritative.
What offline verification covers. Without any network access, a verifier can check the fingerprint of the file, both signatures, the key identifiers and attestation chain, the validity dates, and that both anchors relate to this file and this attestation (structural check). Two things require external, current information: confirming the anchors against the Bitcoin blockchain, and knowing the current status of a key. An offline result about revocation is valid as of the date of the key file used, and should be compared with the independent publications when a network is available.
10. Test vector
The following values come from a real proof, issued on 3 October 2026, and allow any implementation to be checked. Its contenu is:
{
"format": "verifbox-preuve-1",
"emetteur": "verifbox.com",
"algorithme": "SHA-256",
"empreinte": "b1382589c3d05a7096e4a328a77fa743c0dd9714e5e45a8aa8e81be0ff29f10c",
"horodatage": "2026-10-03T07:12:22.736Z",
"identifiant": "3145d9d82f124d8fb3c8ae863b644c79",
"cle": "258e57366bcdb6ce"
}
Canonical form (one line, no whitespace):
{"algorithme":"SHA-256","cle":"258e57366bcdb6ce","emetteur":"verifbox.com","empreinte":"b1382589c3d05a7096e4a328a77fa743c0dd9714e5e45a8aa8e81be0ff29f10c","format":"verifbox-preuve-1","horodatage":"2026-10-03T07:12:22.736Z","identifiant":"3145d9d82f124d8fb3c8ae863b644c79"}
SHA-256 of the signed message (prefix verifbox-preuve-v1 and line feed, then the canonical form): f534d90d90f99cc7ecc129ae75eaa88d8bc892681162ea07670aa80617f90f68
Ed25519 signature, which verifies with the public key 258e57366bcdb6ce published in verifbox-cles.json:
MwONZqUvQacifN3ulnzGp9jJO_m_913SgNMb9gDjmnJAieXJTO4hAFoHG9DluYI1lOdiR5kYWb0VcNbUcldbCA
11. RFC 3161 time-stamp token (IIC TSA)
From the opening of the IIC TSA service, a proof may contain jetons.iic_tsa.tsr: an RFC 3161 time-stamp response (with the certificate identification of RFC 5816), DER-encoded then base64-encoded. The token covers contenu.empreinte, that is the SHA-256 fingerprint of the file, like the Bitcoin anchor of the file; it can therefore be verified with the original file alone. The fields politique, heure and serie are informative copies: only the token is authoritative.
Verification. The status is granted; the token’s SHA-256 imprint equals contenu.empreinte; the policy is 1.3.6.1.4.1.67100.1.1.1.1; the signed attributes include the content type id-ct-TSTInfo, the SHA-256 digest of the TSTInfo and the SHA-256 identification of the signing certificate; the ECDSA P-256 with SHA-256 signature is valid; the certificate is issued by the IIC TSA root published in /.well-known/iic-tsa.json, with the time-stamping usage only, marked critical; the token time falls within its validity period; and the certificate was not revoked before that time, according to the revocation list signed by the root (iic-tsa-racine.crl). With OpenSSL:
openssl ts -verify -in token.tsr -data original-file -CAfile iic-tsa-root.pem
The .tsr file is obtained with the “IIC TSA token (.tsr)” button on the Verify page, or with verifbox_verify.py --tsr token.tsr.
Token time and declared time. The token is requested at the same time as the signature: the two times normally differ by a second or two. A verifier reports any gap above ten seconds. The token declares an accuracy of ± 1 second; before each token, the IIC TSA clock is compared with four independent sources reached over encrypted connections: the web clock of PTB, Germany’s national metrology institute (traceable to UTC), Akamai, Google and Cloudflare; unless at least three of them agree, PTB included, no token is issued.
Transparency. IIC TSA is operated by HTTPS CARD — Internet Identity Card Limited, the company that publishes VerifBox: it is not an independent third party. It is a non-qualified time-stamping service within the meaning of Regulation (EU) No 910/2014 (eIDAS). The token provides a time to the second, under a published policy, verifiable with standard tools; the two Bitcoin anchors, for their part, remain independent of us.
Control of the IIC TSA log. Every token is recorded in a log that can be neither changed nor deleted for twelve years. Every night, the SHA-256 digest of the previous day’s log is time-stamped by DigiCert’s public RFC 3161 server (FreeTSA as a fallback), used without contract, then anchored in Bitcoin through OpenTimestamps: a token cannot be backdated after the fact without it showing.
No token. The token is optional: if IIC TSA does not respond, the proof is issued without it and remains valid. Proofs issued before the service opened do not contain one.