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.