humanproof format specification
12. Versions
A format change is a new format (humanproof/2, for example); a published format is not edited. This specification can gain additive values within a format, such as the terceiro role added on 2026-10-19, and the document records each one with its date. In version 1.1 of the document, from the same day as 1.0, rejecting duplicate names went from SHOULD to MUST, with the invalidos/nome-duplicado.json vector. Version 1.2, also from 2026-10-19, adds the humanproof-ancoragem/1 format (section 15). Version 1.3, from 2026-10-20, adds to it the receipt proof (recibo_hash), before the format had any proof issued in production. Version 1.4, from 2026-10-29, corrects three descriptions that did not match the issuer (texto_hash is the hash of the consent identifier, not of the text; the version in fontes is that of the issuer’s software, not of the component; the ref_cliente_hash secret stays with the issuer) and describes the revisado_por of a review done by the customer; no format changed. Version 2.0, of 2026-10-30, adds revision 2 of the formats (sections 16 to 20), with the cut-off date, and the licenses of the text and of the artifacts; what describes revision 1 did not change. Version 2.1, also of 2026-10-30, corrects descriptions without changing any format, schema or vector: anchoring is not yet active in production (sections 13 to 15 and 20); without the key, the commitment does not link two receipts of the same person, but referencia.hash is the same in receipts that used the same image (17.2); participantes and mandatario carry ref_sal (17 and 17.6); the receipt identifies the component, and the version recorded depends on the revision (13); what each hash lets you check, by revision (14); the revision 2 vectors and esperado.valido (11); per-key validity is allowed by the format, not required (16.2). Version 2.2, also of 2026-10-30, adds vectors without changing format or schema: four invalid receipts in revisao-2/invalidos/ (rule 11 of dual approval, crit, a reason outside the result and revision 1 after the cutoff) and two JWKS with the key expired or compromised before issuance, against which the valid receipt fails on the signature (11). Version 2.3, of 2026-11-01, adds the checkpoint cut-off date (section 16) and fixes the per-key validity check, which passed without checking when the issuance instant was not RFC 3339 and now rejects; no format, schema or vector changes. An older verifier rejects a value it does not know, because the schemas are closed; it never accepts it by omission.
13. What the formats do not prove
- The receipt states what the declared sources support, and nothing beyond. Liveness is the verdict of a third-party component, identified in the receipt, that decides by a probabilistic criterion; the receipt records the verdict, not a score. In revision 1, the version in
fontesis that of the issuer’s software, not of the component; in revision 2,versao_componentemay benao_informada. - The receipt does not prove the legitimacy of the reference the customer sent: a good match against the wrong reference is high confidence in the wrong person.
- The chain and the checkpoint cover what was issued. An act that never became a receipt appears in no chain.
- The access receipt covers opening through the official path. Access outside it produces no receipt.
- Coherence is the absence of internal contradiction, not external confirmation.
- The reference commitment (17.2) proves whose receipt it is only to whoever has the reference key and the reference; it does not say whether the reference the customer registered really belongs to the person.
- The timestamp attests when the checkpoint or the receipt existed, not that what the receipts state happened. Receipts issued between one anchoring and the next remain unanchored until it. Anchoring is not yet active in production: until it is, no receipt or checkpoint from the issuer is anchored.
14. Security
- Trust in the key. A signature only proves that whoever had the private key signed. The verifier MUST obtain the JWKS from the issuer’s address or keep it beforehand, and MUST NOT trust a key that came in the same package as the receipts: whoever forges a package also forges the key inside it. The package’s key is only for comparison.
- Duplicate names. Two parsers that resolve duplicate names differently (first wins, last wins) would read the same text as different receipts. That is why input MUST be I-JSON, and a verifier MUST reject text with a duplicate name instead of picking one of the values. The
invalidos/nome-duplicado.jsonvector is a genuine negative receipt with a secondresultado, positive, at the start of the object: whoever keeps the last value verifies the signature, and whoever keeps the first reads a positive. - Reference key. Whoever has a tenant’s reference key can test candidate references against the commitments in that tenant’s receipts. It is not changed (there is no rotation), because changing it would make the old receipts impossible to check, and it should be kept like a password.
- Privacy. In revision 1,
ref_cliente_hashis an HMAC with a per-tenant secret, kept by the issuer, because the plain hash of an identity document number is reversed by enumeration; only the issuer recomputes it. Also in revision 1,texto_hashcan only be checked by whoever has the consent’s purpose, scope and version. In revision 2,ref_compromissocan be checked by whoever has the reference key and the reference (17.2), andtexto_hashby anyone with the published text (17.3). In both revisions,motivo_hashandcontexto.hashcan only be checked by whoever has the original text. - Checkpoint. See section 10: only a checkpoint kept before the check catches truncation of the end, or an anchored checkpoint (section 15), once anchoring is active in production.
- Anchoring roots. A timestamp is worth what the root the verifier trusts is worth. The roots MUST come from the official source (for ICP-Brasil, the ITI), never from the same package that carries the proof.
15. The anchoring proof humanproof-ancoragem/1
Periodically the issuer builds a Merkle tree with two kinds of leaf and asks a TSA for an RFC 3161 timestamp over the root:
- the signed
humanproof-ponto/2of each tenant with new receipts, which protects the whole chain against truncation of the end; - each receipt issued since the previous anchoring, act or access, by its hash (section 4), which proves that receipt existed to whoever holds only it.
Each leaf has its own proof. The checkpoint one:
{
"formato": "humanproof-ancoragem/1",
"ponto": { "formato": "humanproof-ponto/2", "...": "the tenant's signed checkpoint" },
"caminho": [
{ "lado": "direita", "hash": "sha256:..." },
{ "lado": "direita", "hash": "sha256:..." }
],
"raiz": "sha256:...",
"carimbo": "<RFC 3161 token, DER, base64 with padding>"
}
The receipt one replaces ponto with recibo_hash, the receipt hash:
{
"formato": "humanproof-ancoragem/1",
"recibo_hash": "sha256:...",
"caminho": [ { "lado": "esquerda", "hash": "sha256:..." } ],
"raiz": "sha256:...",
"carimbo": "<RFC 3161 token, DER, base64 with padding>"
}
formato, caminho, raiz and carimbo are required, with exactly one of ponto and recibo_hash; no other member is accepted, and in the caminho items only lado and hash. The proof carries no Hummand signature: the checkpoint inside it is signed, the receipt is signed and in the hands of whoever checks, and the timestamp is signed by the TSA. The receipt proof carries nothing of the receipt beyond its hash: no tenant, no act.
The tree follows RFC 6962 §2.1, over the 32 bytes of each hash:
- checkpoint leaf:
SHA-256(0x00 || JCS(ponto)), with the checkpoint signature; - receipt leaf:
SHA-256(0x00 || "humanproof-ancoragem/1:recibo:" || recibo_hash), with the hash in thesha256:<hex>form, in UTF-8. The checkpoint leaf preimage starts with{and the receipt one withh: one never passes for the other; - node:
SHA-256(0x01 || left || right); - with n leaves, n ≥ 2, the root is the node of the roots of the first k and of the remaining n − k, k being the largest power of two less than n; with one leaf, the root is the leaf.
caminho lists the leaf’s siblings up to the root, lowest first; lado says which side the sibling is on (esquerda left, direita right). Climbing is: start at the leaf and, for each item, make the node with the sibling on the left or on the right. The empty path is the one-leaf tree.
The timestamp is a TimeStampToken (RFC 3161 §2.4.2): a CMS ContentInfo SignedData (RFC 5652) whose content is a TSTInfo. The messageImprint uses SHA-256, and the hashedMessage is the 32 bytes of the root.
Checking a proof, reporting each step:
- Form: the members above,
raiz,recibo_hashand eachhashin the form of section 4,carimboin base64. - Leaf: in the checkpoint proof, the checkpoint verifies with Hummand’s JWKS, as in section 10. In the receipt proof, whoever checks holds the receipt and verifies that its hash (section 4) is
recibo_hash; the proof holds for that receipt and no other. - Path: the leaf, climbing the path, gives exactly
raiz. - Timestamp, over the bytes of
raiz:- the
TSTInfostamps those 32 bytes, with SHA-256; - there is a single
SignerInfo, and its signed attributes havecontent-typeequal toid-ct-TSTInfoandmessage-digestequal to the digest of theTSTInfo, with theSignerInfodigestAlgorithm(SHA-256, SHA-384 or SHA-512); - the signature, over the DER encoding of the signed attributes with the SET tag, verifies with the key of the TSA certificate, which comes in the timestamp and is identified by the
sid. Algorithms: RSA PKCS #1 v1.5 with SHA-256, SHA-384 or SHA-512, and ECDSA P-256 with SHA-256; - the TSA certificate has the
timeStampingextended key usage; - the chain climbs from the TSA certificate to one of the verifier’s trusted roots, each certificate signed by the one above and valid at the
genTimeinstant, and every intermediate issuer with CAbasicConstraints.
- the
- In the checkpoint proof, the checkpoint
tsis not later thangenTime.
The proof is valid when it passes all of them. The verifier SHOULD show the genTime, the policy, the TSA and the root the chain reached.
What each one proves: the receipt proof, that the receipt existed at genTime, which is enough for whoever receives a dossier with it, without seeing any other receipt of the tenant. The checkpoint proof, that the tenant chain had total receipts ending in ultimo_hash at that instant; the tenant batch is checked against that checkpoint as against a kept checkpoint (section 10), and the auditor need not have kept a checkpoint beforehand.
What offline checking does not do, and the verifier MUST say so when relevant: consult the TSA revocation list, which needs the network; check the timestamp signing-certificate attribute (the signature and the chain already bind the certificate); accept RSASSA-PSS.
State in production. Anchoring is not yet active in production. The format, the vectors and the verifier are ready; the issuer does not yet issue anchoring proofs.