humanproof format specification
This document is for anyone who wants to check a Hummand receipt without depending on Hummand, including by writing their own verifier. With it, the public key and the test vectors, an independent implementation checks signature, hash, chain and coherence and reaches the same results as the verifier on this site.
Anyone may implement this specification and use the vectors to test their implementation, without asking for permission. The text of this document is under the CC BY 4.0 license; the schemas, the vectors and conferir.mjs, under Apache-2.0.
- Files
- Test vectors (.zip)Receipts, batches, checkpoints and anchoring proofs of revisions 1 and 2, signed with test keys, the invalid ones, canonicalization, geneses and indice.json with the expected result of each.
- humanproof/1 schemaJSON Schema, draft 2020-12.
- humanproof-acesso/1 schemaJSON Schema, draft 2020-12.
- humanproof/1 schema, revision 2JSON Schema, draft 2020-12.
- humanproof-acesso/1 schema, revision 2JSON Schema, draft 2020-12.
- SHA256SUMS.txtThe hashes of the five files above.
- ICP-Brasil roots (JSON)The roots this site's verifier trusts for the timestamp, downloaded from the ITI's official repository, with each one's SHA-256 to check against another source.
- Technical note for forensic expertsFor the expert: how the evidence is produced, what each hash lets you check and how to check without Hummand.
- Hummand's public key (JWKS)The key production receipts are checked with.
1. Conventions
The words MUST, MUST NOT, SHOULD and MAY have the meaning of RFC 2119. A conforming verifier is one that meets every MUST in this specification and gives, for each vector in the index (section 11), the result declared there.
The reference implementation is the humanproof-core library, in Rust, the same one that signs on Hummand’s server, runs in the humanproof-verify command-line verifier and, compiled to WebAssembly, in the public verifier at labs.hummand.com.br/verificar. Where this document and the reference implementation disagree, the disagreement is a defect to fix, and the vectors say which of the two is right.
2. Model
- Issuer: Hummand. Signs each object with a key whose public part is published.
- Tenant: Hummand’s customer. Each tenant has two chains of its own, the acts chain and the accesses chain, and no receipt of one tenant references a receipt of another.
- Act receipt (
humanproof/1): issued when an act ends, whatever the result. Positive, negative, expired, cancelled, technical failure and revocation are all signed. - Access receipt (
humanproof-acesso/1): issued when someone opens the evidence of an act or the shared dossier. A denied attempt is signed too. - Checkpoint (
humanproof-ponto/1and/2): a signed statement of how many receipts a chain had at an instant and which one was the last. It is what makes truncation of the end detectable. - Anchoring proof (
humanproof-ancoragem/1): a checkpoint or a receipt bound to a timestamp from a Time Stamping Authority (TSA, “ACT” in Brazil), a third party that is not Hummand. The checkpoint proof attests that the tenant chain already had those receipts at that instant; the receipt proof, that the receipt already existed, and it is checked with that receipt alone. - Verifier: any person or program that checks the objects. It needs no access to Hummand beyond the public key.
- Revision: the version of a format’s rules, marked in the object by the
revisaomember (section 16). An object without the member is revision 1.
None of the formats carries biometrics, images, templates or the data subject’s data in the clear. What concerns the person goes in as a hash, and each format’s schema rejects unknown fields.
3. Canonicalization
Every hash and every signature in these formats is computed over the JCS canonical form (RFC 8785) of the object, with one restriction: numbers only as safe integers.
- Input MUST be I-JSON (RFC 7493): valid UTF-8, no duplicate member names in the same object. A verifier MUST reject text with a duplicate name, at any depth, before any other check.
- Numbers MUST be integers with |n| ≤ 2^53 − 1, written in the input without a fraction, without an exponent and without
-0. Input with a number outside this MUST be rejected, not normalized:1.0,1e3,1.5,-0and9007199254740992are rejected. - Output has no whitespace outside strings.
- Object members are sorted by name, compared as sequences of UTF-16 code units, not UTF-8 bytes. The two orders differ for characters outside the basic plane:
"😀"(U+1F600, units D83D DE00) comes before"~"(U+FF5E). - Arrays keep their order.
- Strings are written as
JSON.stringifywrites them:"and\escaped with a backslash;\b,\f,\n,\rand\tin short form; the other control characters below U+0020 as\ufollowed by four lowercase hex digits; everything else literal in UTF-8, including/, U+007F and U+2028. Escapes in the input are undone first. - Numbers in decimal, with no leading zeros;
true,falseandnullliteral.
The canonicalizacao.json vector file has each of these rules as an input and output pair, with the hash of the output, and the inputs that MUST be rejected with canonico: null.
4. Hash
hash(x) = "sha256:" + hex(SHA-256(UTF-8(x))), with 64 lowercase hex digits. Every hash field in these formats has this form, and a verifier MUST reject what does not.
- Hash of an object: the hash of the JCS of the whole object, signature included. It is what goes into the
prev_hashof the next object, which is why the chain also binds the signatures. - Acts genesis of a tenant:
hash("humanproof/1:genesis:" + tenant.id). - Accesses genesis of a tenant:
hash("humanproof-acesso/1:genesis:" + tenant.id).
In both geneses the hash is over the concatenated string, without JCS. The genesis.json file has the values for the test tenants.
5. Signature and keys
- Signed payload (revision 1; revision 2 is in section 16.1): the JCS of the object without the
assinaturamember.kidandalgstay inside the payload and are therefore covered by the signature. - Algorithms:
ES256(ECDSA over P-256 with SHA-256) andEdDSA(Ed25519). Hummand’s issuer signs withES256, with the private key in a managed key module it never leaves.EdDSAis part of the format and is exercised in the vectors. - Encoding: unpadded base64url (RFC 4648 §5) of the raw signature bytes. For
ES256,r || swith 32 bytes each, as in JWS (RFC 7518 §3.4), never DER. ForEdDSA, the 64 signature bytes. - Keys: a JWKS (RFC 7517). Each key has
kid,ktyandcrv(EC/P-256orOKP/Ed25519), the public coordinates andalg. Hummand’s issuer JWKS is athttps://gateway.hummand.com.br/.well-known/humanproof/keys.json. - Selection: the verifier MUST pick the key by the object’s
kidand MUST require the object’salgto match the key type. There is no negotiation: analgthat does not match the key is a signature failure, before any computation. So is akidabsent from the JWKS. - A JWKS can hold more than one key, and a chain can cross a change of key and of algorithm: the chain is by hash and does not depend on the key.
6. The act receipt humanproof/1
The exact structure is in the JSON Schema humanproof-1.schema.json (draft 2020-12), which is part of this specification and rejects unknown fields. Field names are in Portuguese, as in the format. The required members:
| Member | Content |
|---|---|
formato |
"humanproof/1" |
recibo_id, ato_id |
opaque identifiers |
tenant |
{ id, nome_publico } |
tipo |
act type, such as presenca.recorrente or mandato.pessoa |
nivel |
integer from 0 to 3: the level actually reached, not the one requested |
resultado |
positivo, negativo, expirado, cancelado, falhou or revogado |
motivo_codigo |
short reason code, or null |
sujeito |
ref_cliente_hash (HMAC-SHA256, with a per-tenant secret kept by the issuer, over the reference the customer gave the person, in hash form, or null) and, when there was one, referencia with the hash of the compared reference |
contexto |
hash of the JCS of the context the customer sent and chaves_declaradas, the key names; never the content |
consentimentos |
per layer: kind, versao, texto_hash and ts. texto_hash is the hash of the consent identifier (purpose, scope and version), not of the text shown; the text of each version is requested from the issuer |
etapas |
nome, resultado (ok or the short failure reason) and ts |
fontes |
source, the version of the issuer’s software (not that of the third-party component) and, when there is one, the component used |
garantias |
humano_vivo, mesma_pessoa_da_referencia, identidade_civil, consentiu (true, false or null when the step did not happen) and biometria_retida, always false |
mandato_ref, encadeamento, revisado_por |
mandate reference, position in a group of chained acts and human review mark, each null when not applicable |
politica_versao |
the policy applied |
ts_criado, ts_concluido |
RFC 3339 in UTC, with the Z suffix |
prev_hash |
hash of the previous receipt of the same tenant, or the acts genesis |
kid, alg, assinatura |
section 5 |
Optional members, by act type: backfill; escopo and mandatario in a mandate grant; verificacao_mandato in a mandate use; documento_anexo; assinatura_juridica; recibos_individuais and humanos_distintos in composite acts; gatilho.
Three reading notes:
contexto.hashonly checks against whoever has the context. Whoever has the original context recomputeshash(JCS(contexto))alone; the verifier checks only the form of the hash. An act without context uses the hash of{}. Receipts issued before 2026-09-13 whose context had a non-ASCII character hold the hash of an earlier recipe, which escaped those characters instead of writing them in UTF-8; for ASCII-only context both recipes give the same value.etapas[].tsis the instant the step ended, when the issuer measured it. When it did not, the issuer puts the act’sts_concluidothere, and the receipt remains valid. Nothing in the receipt tells the two cases apart.inconclusivois never a result. An act that goes through human review comes out aspositivoornegativo, withrevisado_porfilled in: the id of the act in which the reviewer proved their own presence, in a review done by the issuer, or, since 2026-10-29,apikey:<id>, the key with which the customer decided through the integration. The review decides the act, not presence:garantias.humano_vivois always the component’s verdict.
7. Coherence of humanproof/1
Beyond schema and signature, the verifier MUST apply the rules below. They ensure the receipt does not contradict itself, not that what it states happened outside it.
resultadofrom the vocabulary;nivelbetween 0 and 3; non-emptykid; every hash in the form of section 4;garantias.biometria_retidafalse.positivorequires every stepok.positivoat level 2 or above, withoutmandato_ref, requires apresencastep withok,garantias.humano_vivotrue and, when there is asujeito.referencia,garantias.mesma_pessoa_da_referenciatrue.positivoat level 3 requiresgarantias.identidade_civiltrue.revisado_poronly inpositivoornegativo.revogadorequiresmandato_ref, the revoked mandate.- Times in RFC 3339;
ts_concluidodoes not precedets_criado; no step afterts_concluido; steps in non-decreasingtsorder. A step may precedets_criado, because consent is collected before the act is born. - Mandate grant.
escopoandmandatariogo together.escopo.acoesnot empty;escopo.revogavelpresent;escopo.validade_atein RFC 3339 and, in the grant (withoutmandato_ref), later thants_criado: a mandate born expired authorizes nothing.mandatario.tipoispessoaoragente, andmandatario.ref_hashis a hash. - Mandate use.
verificacao_mandatorequiresmandato_refand the four statementsvalido,dentro_escopo,dentro_validadeandrevogado. They authorize when the first three are true and the last is false, and the receipt ispositivoexactly when they authorize.motivo_codigomust be supported by them:mandato_invalidorequiresvalidofalse;mandato_revogado,revogadotrue;mandato_vencido,dentro_validadefalse;fora_de_escopo,dentro_escopofalse. Theverificacao_mandatostep isokexactly when the four authorize.escopoandverificacao_mandatonever appear in the same receipt.
The verifier does not check the referenced mandate against the grant receipt: whoever wants the whole proof verifies both receipts, which are in the same tenant chain.
8. The access receipt humanproof-acesso/1
Same canonicalization, hash, signature and keys as sections 3 to 5. Schema in humanproof-acesso-1.schema.json, also closed.
| Member | Content |
|---|---|
formato |
"humanproof-acesso/1" |
acesso_id, ato_id |
the access and the act whose evidence was opened |
tenant |
{ id, nome_publico } |
ator |
{ papel, id }, with an opaque id, never a name or e-mail |
motivo_hash |
hash of the JCS of the reason string, that is, of the quoted text; the text does not go in |
componentes |
one per record touched: registro, registro_id and desfecho (aberto, apagado, nunca_houve, negado or erro) |
desfecho |
of the access: concedido, negado or erro |
ts_acesso |
RFC 3339 in UTC, with Z |
prev_hash |
hash of the tenant’s previous access, or the accesses genesis |
kid, alg, assinatura |
section 5 |
Roles and ator.id prefixes:
papel |
prefix | who |
|---|---|---|
cliente |
usuario: |
user of the tenant’s dashboard |
operador_hummand |
operador: |
Hummand operations |
revisor_hummand |
revisor: |
Hummand human review |
integracao |
apikey: |
the tenant’s integration |
terceiro |
compartilhamento: |
whoever opened, without an account, a dossier the customer shared by link; the id is the share’s, never the person’s |
Coherence the verifier MUST apply:
concedidorequires at least one component and nonenegado.negadorequires every componentnegado; the empty list is accepted, because the refusal can come before any record is touched.erroaccepts any mix.- Known
ator.papelandator.idwith the role’s prefix, followed by at least one character. ts_acessoin RFC 3339 withZ;prev_hashandmotivo_hashin hash form; non-emptykid.
The access receipt covers opening through the official path, which is the only one the issuer sees.
9. The chain
Each tenant has an acts chain and an accesses chain, independent, each with its own genesis. A batch is verified like this:
- Group the objects by
(formato, tenant.id). Each group is a chain. A verifier that receives a file as a single chain MUST reject one that mixes tenants or formats. - Compute each object’s hash and link each one to the object whose hash is its
prev_hash. Input order does not matter. - The chain is continuous when there is exactly one object whose
prev_hashis not the hash of another object in the batch, and each object has at most one successor. - Read the start: if that first object points to the genesis of the format and tenant, the chain is verified from genesis; if it points to another hash, it is continuous within the batch, of external origin, and MUST be presented as such, never as complete.
- Problems the verifier MUST report: more than one start (a gap in the middle or an object from another history), two objects with the same
prev_hash(fork), cycle and repeated object.
The chain does not promise time order: an expired act concludes at its expiry date, which can be later than that of acts issued after it.
What the chain does not catch. Removing the last objects leaves a chain continuous from genesis, and nothing inside the batch says how many there should be. That is what the checkpoint is for.
10. The checkpoint
humanproof-ponto/1, acts chain only:
{
"formato": "humanproof-ponto/1",
"tenant": { "id": "..." },
"total": 128,
"ultimo_hash": "sha256:...",
"ts": "2026-09-13T21:00:00Z",
"kid": "...", "alg": "ES256", "assinatura": "..."
}
humanproof-ponto/2, both chains, both required:
{
"formato": "humanproof-ponto/2",
"tenant": { "id": "..." },
"cadeias": {
"atos": { "total": 128, "ultimo_hash": "sha256:..." },
"acessos": { "total": 7, "ultimo_hash": "sha256:..." }
},
"ts": "2026-09-24T21:00:00Z",
"kid": "...", "alg": "ES256", "assinatura": "..."
}
total is how many objects the chain had up to ultimo_hash, inclusive. An empty chain has total 0 and ultimo_hash equal to the genesis of that chain. Signature as in section 5. /1 remains accepted and states nothing about accesses: checking accesses against it MUST be refused, never approved by omission.
Checking a batch against a checkpoint, per chain:
| Situation | Reading |
|---|---|
| the checkpoint does not verify (signature) or belongs to another tenant | contradicts |
total 0 and ultimo_hash equal to the genesis |
matches: the checkpoint states there was nothing yet |
the batch contains ultimo_hash and, if it starts at genesis, at position total |
matches |
the batch starts at genesis and contains ultimo_hash at another position |
contradicts |
the batch starts at genesis and does not contain ultimo_hash |
contradicts: truncated, forked or rewritten, or earlier than the checkpoint |
the batch does not start at genesis and does not contain ultimo_hash |
inconclusive: it may be a window earlier than the checkpoint |
The verifier SHOULD show both counts, the batch’s and the checkpoint’s, not just the reading.
A checkpoint is worth the distance in time. A checkpoint requested together with the batch comes from the same version of history and proves nothing; what proves is the checkpoint the auditor kept before and compares later.
11. Full procedure and vectors
For each object, in this order, reporting each step separately:
- Read as I-JSON, rejecting duplicate names, and identify
formato. An unknown format MUST be rejected. Also identify the revision (section 16): withoutrevisao, revision 1; withrevisao2, the rules of sections 16 to 20; any other value MUST be rejected. - Validate against the format’s schema.
- Select the key by
kidand checkalgagainst it (section 5). - Check the signature over the payload.
- Apply the format’s coherence rules (sections 7 and 8).
An object is valid when it passes all five. For a batch, after that, verify each chain (section 9) and, if there is a kept checkpoint, check each chain against it (section 10).
The vectors come with this document. They were signed with test keys derived from public labels, worthless outside tests: the P-256 scalar is the SHA-256 of the string humanproof/1:chave-de-teste:p256 (the second one, humanproof/1:chave-de-teste:p256:2), the Ed25519 seed is the SHA-256 of humanproof/1:chave-de-teste:ed25519, and the signatures are deterministic (RFC 6979). The public key files are jwks.json, jwks-rotacao.json and jwks-eddsa.json, and revisao-2/jwks.json for the revision 2 vectors, with revisao-2/jwks-chave-vencida.json and revisao-2/jwks-chave-comprometida.json, which carry the same key with a declared validity and reject the valid receipt.
indice.jsonlists each vector with the operation to apply and the expected result.recibo: the object alone.cadeia: the array must form one chain of one format and one tenant.lote: the array grouped by format and tenant.ponto: the array checked against the checkpoint given.ancoragem: the proof checked with the JWKS and the trusted root given inraizes.esperado.falhassays at which steps verification fails (leitura, reading, when the text is not I-JSON;schema,assinatura,coerencia,cadeia,ponto; in anchoring, the checkpointassinatura,caminhoandcarimbo); empty means a valid vector.esperado.validois true exactly whenesperado.falhasis empty.esperado.inicioisgenesisorexterno;esperado.pontoisconfere(matches),inconclusivoorcontradiz(contradicts).canonicalizacao.json: the input and output pairs of section 3.genesis.json: the geneses of the test tenants and a samplemotivo_hash.ancoragem/: the valid proofs, timestamps from a test TSA (RSA with SHA-512 and ECDSA P-256, issued by OpenSSL), a full TSA response and the DER certificates of the roots, the test one and a foreign one.revisao-2/: the revision 2 vectors (sections 16 to 20), with their ownjwks.json: the act receipt, a receipt with two signatures, a tampered receipt, the invalid ones for the new coherence (the dual approval with the same operator in both positions, an unknown critical extension incrit, a registry reason that does not fit the result) and a revision 1 receipt concluded after the cutoff date, the access receipt, a batch checked against the checkpoint (lote.jsonandponto.json) and the checkpoint and receipt anchoring proofs, with the timestamp and the head they use.
A conforming implementation gives, for each index entry, exactly the failures, start and reading declared. The same vector can appear with different operations: the batch truncated at the end is a valid chain from genesis and contradicts the checkpoint, and that is what shows what the checkpoint is for.
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.
16. Revision 2
Since 2026-10-30 the four formats have a revision 2, marked by the member "revisao": 2. It is not a new format: formato is still humanproof/1, humanproof-acesso/1, humanproof-ponto/2 and humanproof-ancoragem/1. An object without revisao is revision 1 and follows sections 6 to 10 and 15; an object with revisao 2 follows sections 16 to 20; any other value of revisao MUST be rejected. Canonicalization, hash and chain (sections 3, 4 and 9) are the same in both revisions, and a chain may hold receipts of both.
Cut-off date: 2026-10-30T05:21:39Z, the instant the Hummand issuer started issuing revision 2. An act receipt without revisao whose ts_concluido is later than that instant MUST be rejected in coherence, even with a valid signature: after the cut-off, revision 1 is not a way around the rules of revision 2. Revision 1 receipts concluded up to the cut-off remain valid.
Checkpoint cut-off date: 2026-11-01T22:51:16Z, the instant the issuer stopped issuing humanproof-ponto/1 and the /2 without revisao. A checkpoint without revisao whose ts is later than that instant, or whose ts is not RFC 3339, MUST be rejected in coherence, even with a valid signature, for the same reason as the receipt: without the cut-off, the revision 1 checkpoint, which has no per-key validity, would be a way around the key revocation of revision 2. It is a different date because the revision 1 checkpoint kept being issued after the receipt cut-off. Checkpoints without revisao issued up to that instant remain valid.
Revision 2 schemas: humanproof-1-revisao-2.schema.json and humanproof-acesso-1-revisao-2.schema.json, closed like those of revision 1.
16.1 Signatures
In revision 2, kid, alg and assinatura leave and assinaturas comes in, a list of { papel, kid, alg, valor }:
- each entry signs the JCS of
{ "recibo": <the object without the assinaturas member>, "assinatura": { "papel", "kid", "alg" } }, so that the role, key and algorithm of each signature are covered by that signature itself, and another signature can be added later without invalidating the earlier ones; valorwith the encoding of section 5;- roles:
emissor, mandatory, and the reservedemissor_complementar,testemunhaandcliente. The verifier MUST require at least oneemissorsignature and MUST check every one present; - the hash of the object (section 4) is still the hash of the JCS of the whole object, signatures included.
The same rule applies to the access receipt and to the revision 2 checkpoint.
16.2 Keys with validity
In revision 2, each JWKS key MAY carry valida_de, valida_ate, estado (ativa, aposentada or comprometida) and, when compromised, comprometida_desde. The verifier MUST reject a signature whose key, at the object’s ts_emitido, was not yet valid, was no longer valid or was compromised; an unknown state is also a rejection. Per-key validity is allowed by the format, not required: a key without these members is always valid, as in revision 1, and having no declared term does not make a key invalid. The issuer’s current key has no declared term. A repeated kid in the JWKS MUST be rejected, because the verifier would have to pick one of the keys. A retired key stays published in the issuer’s JWKS, so that old receipts remain checkable after a key change.
17. The act receipt, revision 2
| Member | Content |
|---|---|
formato, revisao |
"humanproof/1" and 2 |
recibo_id, ato_id |
opaque identifiers |
emissor |
{ id, jwks_uri }: who issued it and where the keys are |
tenant |
{ id, nome_publico } |
tipo |
act type, as in revision 1 |
resultado |
positivo, negativo, expirado, cancelado, falhou or revogado |
motivo |
null when positive; otherwise, a code from that result’s registry (17.1) |
nivel |
{ exigido, atingido }, integers from 0 to 3: what the policy required and what the act reached |
politica |
{ versao, hash }: the version and the hash of the JCS of the policy applied to the type |
sujeito |
ref_compromisso and ref_sal, together or both absent, and referencia when there was a comparison (17.2) |
contexto |
{ hash, chaves_declaradas }, with the keys sorted and without repetition |
consentimentos |
[{ camada, versao, idioma, texto_hash, ts }], with camada in geral, biometrico or escopo (17.3) |
etapas |
[{ nome, resultado, ts, medido }]; medido says whether the instant was measured or inherited from the end of the act |
fontes |
[{ fonte, fornecedor, componente, versao_componente, versao_adaptador, ambiente, certificacao? }]; versao_componente may be nao_informada; certificacao = { esquema, laboratorio, relatorio, data } |
garantias |
humano_vivo, mesma_pessoa_da_referencia, identidade_civil and consentiu, each { valor, sustentada_por }; biometria_retida, always false (17.4) |
evidencia_tecnica |
what the source measured, without a score: deteccao_ataque, deteccao_injecao and dispositivo when present, and politica_de_limiar with the id and hash of the comparison policy, never the calibrated values |
participantes |
[{ papel, ref_compromisso, ref_sal, presenca_recibo_hash? }], with ref_compromisso and ref_sal computed as in 17.2, with papel in sujeito, assistente, mandatario, testemunha, revisor or operador |
revisao_humana |
null or { via, revisor, protocolo, ts } (17.5) |
mandato |
null or exactly one of concessao, uso and revogacao (17.6) |
ingredientes |
[{ papel, alg, hash }]: the displayed term, the attached document, the trigger and other evidence, by hash only |
ts_criado, ts_concluido, ts_emitido |
RFC 3339 in UTC with milliseconds; ts_emitido is the instant of signing |
expira_em |
the deadline of the act |
retencao |
{ ate, base }: until when the issuer keeps the record of the act, and on what basis |
prev_hash |
hash of the tenant’s previous receipt, or the acts genesis (section 4) |
assinaturas |
section 16.1 |
Optional: inclusao, reserved for an inclusion proof in a log; extensoes, a map { "<namespace>": { ... } }; and crit, the list of the extensions the verifier needs to understand. An extension named in crit and absent, or named and unknown to the verifier, MUST be rejected; other extensions MAY be ignored.
17.1 Reasons per result
resultado |
motivo |
|---|---|
positivo |
null |
negativo |
presenca_negada, comparacao_negada, identidade_negada, passkey_negada, consentimento_recusado, revisao_negou, mandato_invalido, mandato_revogado, mandato_vencido, fora_de_escopo |
expirado |
prazo |
cancelado |
cancelado_pelo_cliente, cancelado_pela_pessoa |
falhou |
falha_tecnica_fonte, falha_tecnica_emissor |
revogado |
revogado_pelo_mandante |
17.2 The reference commitment
The receipt does not carry the reference the customer gave the person, such as a national ID, employee number or benefit number. It carries:
ref_sal: 16 random bytes, new for each receipt, as 32 hexadecimal digits;ref_compromisso="sha256:" + hex(HMAC-SHA256(key, bytes(ref_sal) || UTF-8(reference))), wherekeyis the tenant’s 32-byte reference key andbytes(ref_sal)are the 16 bytes the hexadecimal represents.
Without the key, the commitment does not reveal the reference or link two receipts of the same person, because the salt changes; referencia.hash, when present, is the same in receipts that used the same image. With the key, which the customer obtains in the dashboard, the customer recomputes the commitment from the reference and checks on their own that the receipt belongs to that person. referencia, when there was a comparison, is { tipo, origem, hash, anexada_em }: the hash of the reference image used, its origin (api, painel, ato_anterior or base_oficial) and when it was attached. Whoever kept the image checks the hash.
17.3 The consent text
texto_hash is hash(JCS(text)): the SHA-256 of the canonical form of the string of the text the person saw, in the indicated version and language. The texts are published at https://hummand.com.br/en/consentimentos, with the hash of each, and a published text does not change: new wording is a new version. ts is the instant the issuer recorded the acceptance, by its own clock. This is the difference from revision 1, whose texto_hash was the hash of the consent identifier (section 6).
17.4 Who backs each guarantee
valor is true, false or null (the step did not happen). sustentada_por says where the statement comes from: componente (the source’s technical component), revisao (human review), declaracao_cliente, base_oficial or mandato; it is null exactly when valor is null.
17.5 Human review
via is emissor or cliente. In a review by the issuer, revisor is the hash of the receipt in which the reviewer proved their own presence; in a review by the customer, it is apikey:<id>, the key with which the integration decided. The review decides the act, not presence: no biometric guarantee is backed by revisao.
17.6 Mandate
concessao:{ escopo, mandatario }, in the typesmandato.pessoaandmandato.agente.escopocarriesacoes, typedlimites({ valor, unidade, moeda?, janela? }),validade_ateandrevogavel;mandatariocarriestipo(pessoaoragente, consistent with the act type),ref_compromissoandref_sal, computed as in 17.2, and MAY carrychave_agente.uso:{ mandato_recibo_hash, acao, valores_hash, executor, nonce, expira_em, chave_agente?, verificacao }, in the typeato.delegado, withverificacao={ valido, dentro_escopo, dentro_validade, revogado }.revogacao:{ mandato_recibo_hash }, in the resultrevogado.
The mandate is referenced by the hash of the grant receipt, not by an id: whoever has both receipts checks that one points to the other.
18. Coherence of revision 2
Beyond the schema and the signatures (16.1 and 16.2), the verifier MUST apply:
motivoisnullexactly when positive, and from the result’s registry otherwise (17.1).- Positive requires
nivel.atingido≥nivel.exigidoand every stepok. - Positive at level 2 or above, outside
ato.delegado, requires apresencastepokandhumano_vivotrue backed bycomponente, with the sourcehummand.presencadeclared. Withsujeito.referencia, it also requires acomparacaostepokandmesma_pessoa_da_referenciatrue backed bycomponente. - Positive at level 3 requires
identidade_civiltrue backed bybase_oficial. Positive at level 1 requires apasskeystepok. ato.delegadois always level 0: there is no new human.- A
nullguarantee has nosustentada_por, and an asserted guarantee has one. No biometric guarantee is backed byrevisao. - A biometric step (
presenca,comparacao) requires a consent of layerbiometricobefore it andconsentiutrue. - No repeated step;
ts_criado≤ts_concluido≤ts_emitido; steps and consents not later thants_concluido; steps in non-decreasing order. contexto.chaves_declaradassorted and without repetition;ref_compromissoandref_saltogether.revisao_humanaonly in positive or negative; by the issuer, with the hash of a receipt; by the customer, withapikey:.operador.dupla: dual approval is two chained positions, and only the one that closes the pair lists operators. When the receipt lists two or moreoperadorparticipants, there are at most two, with differentref_compromisso(distinct people), and the first carriespresenca_recibo_hash, the hash of the receipt of the previous position; the second is the act itself and carries no presence. With fewer than two operators listed (the first position), the rule does not apply.- Mandate: each variant in the types of 17.6;
ato.delegadorequiresuso; the use is positive exactly when the check authorizes (the first three statements true andrevogadofalse), withmotivobacked by it, and does not conclude positive afterexpira_em; the grant hasvalidade_atelater thants_criado. critas in section 17.
As in revision 1, coherence is the absence of internal contradiction, not external confirmation.
19. Access and checkpoint, revision 2
Access receipt. The same role as in section 8, with revisao 2, emissor, ts_emitido and assinaturas (16.1), and:
ato_recibo_hash: the hash of the act receipt whose evidence was opened, besidesato_id;motivo_hash=hash(JCS({ "motivo": <text>, "sal": <motivo_sal> })), withmotivo_salin the receipt itself: without the salt, the same reason would give the same hash in every access;- each component also carries
conteudo_hash, the hash of what was delivered, andmodo,telaorarquivo; each may benull.
The coherence of section 8 applies unchanged.
Checkpoint. The revision 2 humanproof-ponto/2 gains revisao, emissor, assinaturas and ponto_anterior_hash, the hash of the same tenant’s previous checkpoint (null for the first): a tenant’s checkpoints form a chain, and a checkpoint removed from the middle becomes visible to whoever kept its neighbours. cadeias becomes an open map, in which atos and acessos are mandatory and other chains may be added later. ts is in milliseconds. Checking a batch against the checkpoint is as in section 10.
20. Anchoring, revision 2
The revision 2 humanproof-ancoragem/1 proof carries, besides what section 15 describes, revisao, arvore, tamanho and cabeca_anterior:
- Padded tree. The real leaves, checkpoints and receipts as in section 15, are followed by padding leaves up to
tamanho, the smallest power of two that is both ≥ 1024 and ≥ the number of real leaves. Padding leafiisSHA-256(0x00 || "humanproof-ancoragem/1:preenchimento:" || i), withiin decimal. So the size of the tree does not reveal how many customers and receipts there were in the cycle.arvoreis the root of that tree, and the path has exactly log2(tamanho) siblings. - Chained head. The TSA timestamp covers the head, not the tree:
head = SHA-256(0x02 || previous head || arvore || tamanho), with the previous head as 32 bytes (32 zeros in the first cycle) andtamanhoas 8 bytes big-endian.raizis that head, andcabeca_anterioris the previous cycle’s, ornull. - Public list. The issuer publishes the heads of the cycles at
https://gateway.hummand.com.br/v1/ancoragens/cabecas, with the tree, size, previous head and timestamp of each. Whoever keeps the list sees that each cycle covers the previous one and that the issuer did not timestamp two histories.
Checking a revision 2 proof: the steps of section 15, with the leaf climbing to arvore instead of raiz; the path with the length of the full tree; tamanho a power of two and ≥ 1024; and raiz equal to the head computed from cabeca_anterior, arvore and tamanho.
State in production. Anchoring is not yet active in production; the list exists and stays empty until then.