O registro
O formato de dados do Label 309 — onde o registro fica sob o rótulo de metadados 309, o formato do seu mapa, as regras de CBOR canônico, o fracionamento para transporte e o esquema CDDL.
Um registro Label 309 é um único mapa CBOR transportado nos metadados de uma transação Cardano sob o rótulo 309. O mapa compromete um ou mais hashes de conteúdo com a cadeia; o horário do bloco da transação é a testemunha de que aqueles bytes já existiam até aquele momento. Todo o resto que o registro pode carregar — URIs de armazenamento, um envelope de criptografia, assinaturas de autoria, um ponteiro de substituição — são metadados opcionais sobre essa afirmação central.
Esta página define o formato em que o registro trafega: onde ele fica, como é codificado, como valores grandes demais são transportados e o esquema fechado que um validador estrutural verifica. As construções criptográficas mencionadas aqui (algoritmos de hash, o envelope selado, as assinaturas) têm suas próprias páginas; esta trata do formato de dados em si.
Onde o registro fica
Um registro de PoE MUST ser colocado sob o rótulo de metadados de transação
309, reservado como "Proof of Existence record" no
registro de rótulos de metadados do CIP-10.
Os metadados de uma transação são um mapa de rótulo inteiro para valor, de modo
que uma transação MUST NOT carregar mais de um registro de PoE — exatamente
um registro por transação.
Uma transação MAY carregar metadados adicionais sob outros rótulos (por
exemplo, uma mensagem
CIP-20
674). Um verificador que processa PoE MUST ignorar qualquer rótulo que não
seja o 309.
No ledger da era Conway, os metadados da transação são o campo metadata dentro
do auxiliary_data da transação. Os valores admitidos sob qualquer rótulo ficam
restritos ao tipo recursivo metadatum do ledger — inteiros, cadeias de bytes,
cadeias de texto, arrays e mapas, com cadeias de bytes e cadeias de texto
limitadas a 64 bytes cada uma:
metadatum =
{ * metadatum => metadatum }
/ [ * metadatum ]
/ int
/ bstr .size (0..64)
/ tstr .size (0..64)Uma transação que carregue qualquer bstr ou tstr isolado com mais de 64 bytes
é rejeitada pelos nós Cardano no momento da submissão, antes que qualquer
verificador a veja. Esse limite é a razão pela qual o Label 309 define uma
disciplina de fracionamento para transporte (a seguir); cada campo que o registro
carrega — base ou extensão — precisa se reduzir a um metadatum.
Transporte: o array de fragmentos do corpo inteiro
Um corpo de registro serializado costuma exceder 64 bytes, portanto não pode ser
armazenado sob o rótulo 309 como um valor solto. O corpo do registro é, então,
transportado como um array opaco de fragmentos do corpo inteiro: um único array
CBOR de cadeias de bytes de no máximo 64 bytes (bstr .size (1..64)) cuja
concatenação na ordem é o corpo do registro. Esse fracionamento de transporte é
o único que o Label 309 realiza — o único passo do formato genuinamente imposto
pelo ledger.
Como o ledger enxerga apenas esse array de transporte e nunca os campos dentro do
corpo remontado, esses campos são valores CBOR comuns, sem invólucros de fragmento
por campo e sem limite de 64 bytes por campo: uma URI de armazenamento é uma única
cadeia de texto, um COSE_Sign1 é uma única cadeia de bytes e um kem_ct X-Wing é
uma única cadeia de bytes de 1120 bytes. Um campo com mais de 64 bytes simplesmente
atravessa as fronteiras de fragmento do array do corpo inteiro como qualquer outro
trecho do corpo.
Um produtor MUST serializar o corpo do registro uma vez em CBOR canônico, dividir essa cadeia de bytes em fragmentos de 1 a 64 bytes e armazenar o array resultante de comprimento definido, com cadeias de bytes de comprimento definido, como o valor do rótulo 309. O formato em array é sempre obrigatório, mesmo para um corpo de 64 bytes ou menos: um corpo desse tipo é um array de comprimento 1, nunca um mapa ou cadeia de bytes solta. Os produtores SHOULD usar o fracionamento mínimo (cada fragmento, exceto o último, com exatamente 64 bytes) e SHOULD NOT emitir fragmentos de comprimento zero; fracionar em excesso desperdiça bytes de transação sem nenhum ganho.
Um verificador MUST concatenar os elementos do array, na ordem em que aparecem,
para remontar o corpo do registro antes da validação estrutural, e MUST
rejeitar qualquer valor do rótulo 309 que não seja um array desse tipo. As fronteiras
entre fragmentos não têm significado semântico: dois arrays de transporte cujas
concatenações sejam idênticas byte a byte denotam o mesmo registro. A taxonomia de
erros de transporte fixa os códigos de rejeição — um fragmento com mais de 64 bytes é
CHUNK_TOO_LARGE; um elemento de array que não seja uma cadeia de bytes, um array ou
elemento de comprimento indefinido, ou um valor do rótulo 309 que não seja um array
(mapa solto, cadeia de bytes solta, inteiro) é MALFORMED_CBOR. Um fragmento de
comprimento zero não contribui com bytes e é tolerado, nunca rejeitado por si só.
O esquema descreve o corpo já remontado
Tudo o que vem abaixo — o mapa do registro, o CDDL, as regras de campo — descreve o corpo do registro depois da remontagem dos fragmentos. O array de fragmentos do corpo inteiro não faz parte do esquema; ele é desfeito primeiro, e só então o corpo é validado.
O mapa do registro
O corpo do registro já remontado é um mapa CBOR. Os campos de valor inteiro são do tipo principal CBOR 0/1; os campos de texto são do tipo principal 3 e MUST ser UTF-8 válido; os campos de bytes são do tipo principal 2; os arrays são do tipo principal 4; os mapas aninhados são do tipo principal 5. Um campo opcional que esteja presente MUST NOT carregar um valor vazio.
O formato de nível superior é:
| Chave | Tipo | Status | Significado |
|---|---|---|---|
v | uint | REQUIRED | Versão do esquema; este documento define v = 1. |
items | array de mapas de item | OPTIONAL | Compromissos por conteúdo — veja Conteúdo e hashing. |
merkle | array de compromissos | OPTIONAL | Compromissos de lista que vinculam listas de folhas off-chain a uma única raiz. |
supersedes | bytes (32) | OPTIONAL | Hash da transação de um registro anterior que este substitui. |
sigs | array de mapas de assinatura | OPTIONAL | Assinaturas de autoria no nível do registro — veja Assinaturas. |
crit | array de cadeias de texto | OPTIONAL | Chaves de extensão que são obrigatórias de compreender. |
Um registro em conformidade MUST comprometer pelo menos um entre items (com
≥ 1 entrada) ou merkle (com ≥ 1 entrada). Um registro que não traga nenhum dos
dois — ou que traga um deles como um array vazio — é rejeitado como registro
vazio. Tirando essa regra, items e merkle são ortogonais: um registro pode
trazer qualquer um deles sozinho ou os dois juntos.
O Label 309 não impõe nenhum limite numérico ao número de entradas. O único teto é o tamanho máximo de transação vigente na Cardano, e os produtores pagam taxas por byte que naturalmente limitam o tamanho do registro. Um validador MUST NOT rejeitar um registro apenas por ele trazer muitas entradas, desde que caiba dentro do limite de tamanho do ledger.
O campo de versão
v é um inteiro sem sinal em CBOR, não uma cadeia de versão semântica. Este
documento define exatamente v = 1. Um validador MUST rejeitar, com um erro
tipado, um registro cujo v esteja fora do conjunto de versões que ele suporta;
MUST NOT entrar em pânico, abortar, nem silenciosamente tratar o registro como
um esquema de metadados diferente. O inteiro v só aumenta quando uma mudança
faria um parser de v1 interpretar o registro de forma errada — extensões aditivas
e com namespace próprio não o aumentam.
Itens
Cada entrada em items é um mapa CBOR com um campo obrigatório e dois opcionais:
hashes— REQUIRED, um mapa não vazio de identificador de algoritmo de hash para o digest bruto de 32 bytes. Pelo menos uma entrada; algoritmos duplicados são impossíveis, porque as chaves de um mapa CBOR são únicas. Veja Conteúdo e hashing.uris— OPTIONAL, uma lista plural de URIs de descoberta (regras a seguir).enc— OPTIONAL, o envelope de criptografia de um item selado. Veja PoE selada.
Não há campo de assinatura por item. A autoria é expressa apenas no nível do
registro, por uma entrada de sigs[] que cobre todos os itens de maneira uniforme.
Compromissos Merkle
Cada entrada em merkle vincula o registro a uma lista ordenada de folhas de 32
bytes por meio de uma construção canônica em árvore de hash, de modo que uma única
raiz de 32 bytes na cadeia pode representar uma lista de folhas off-chain de
tamanho arbitrário. Um compromisso é um mapa fechado:
| Campo | Tipo | Status | Significado |
|---|---|---|---|
alg | tstr | REQUIRED | Identificador de algoritmo de compromisso de lista registrado. |
root | bytes (32) | REQUIRED | Raiz canônica sobre a lista ordenada de folhas do produtor. |
leaf_count | uint | REQUIRED | Número de folhas comprometidas; vincula a raiz ao tamanho da lista. |
uris | lista de URIs | OPTIONAL | URI(s) endereçada(s) por conteúdo do arquivo da lista de folhas off-chain. |
Uma raiz Merkle compromete uma estrutura de lista de folhas, ao passo que uma
entrada de hashes compromete os bytes em texto claro; as duas são verificadas de
maneiras diferentes (prova de inclusão versus recomputação do texto claro), e é por
isso que os compromissos de lista ficam no nível superior, e não dentro de um item.
O catálogo de algoritmos de compromisso de lista é disjunto do catálogo de hash de
conteúdo — veja Catálogos de algoritmos.
Substituição
supersedes é um hash de transação Cardano opcional, de 32 bytes, que aponta para
um registro Label 309 anterior. É um vínculo independente de qualquer serviço e
somente de inserção: um registro posterior pode apontar para um registro anterior
sem nenhum banco de dados off-chain nem id de registro de fornecedor.
A substituição não remove, revoga nem invalida o registro anterior — a cadeia é somente de inserção, e os verificadores MUST continuar tratando o registro anterior como existente e verificável de forma independente. O ponteiro não carrega nenhum campo de motivo ou de texto livre; qualquer significado humano (correção, substituição, retirada) pertence ao novo conteúdo, não ao rótulo 309. Um verificador que resolva o ponteiro MUST procurá-lo na mesma rede Cardano da transação que o contém; o campo não carrega nenhum discriminador de rede, porque um hash de transação só é único dentro da sua própria rede.
Assinaturas
sigs é um array opcional de entradas de assinatura no nível do registro. Cada
entrada carrega uma estrutura
COSE_Sign1 destacada sobre o corpo do
registro — ou seja, o mapa completo do registro com sigs removido — e,
opcionalmente, a chave pública do signatário para o fluxo de assinatura por
carteira. Uma única assinatura atesta o corpo inteiro: todos os itens, todas as
URIs, todo envelope, o ponteiro de substituição se houver, e quaisquer chaves de
extensão. As assinaturas são sempre opcionais, e um algoritmo de assinatura não
reconhecido nunca invalida a afirmação sobre o conteúdo. A carga útil assinada, o
prefixo de separação de domínio, a resolução da chave do signatário e as regras de
verificação estrita estão especificados em Assinaturas.
Regras de URI
Quando presente, uris é uma lista não vazia; cada entrada é uma única cadeia de
texto CBOR carregando exatamente uma URI. Não há limite de comprimento por URI nem
forma de invólucro: o transporte do corpo inteiro já satisfaz o limite de 64 bytes
por cadeia do ledger, de modo que uma URI longa ipfs://<CIDv1>/<path> é uma cadeia
de texto como qualquer outra. Cada URI MUST ser absoluta, MUST incluir um
esquema e uma parte hierárquica, e MUST NOT conter um identificador de fragmento
— uma PoE é uma afirmação sobre os bytes do conteúdo, não sobre um subcomponente de
um documento.
O conjunto de esquemas da v1 é fechado e endereçado por conteúdo:
| Esquema | Observações |
|---|---|
ar:// | Id de transação Arweave (base64url de 43 caracteres). Forma ar://<txid>. |
ipfs:// | CID IPFS, de preferência CIDv1. Forma ipfs://<cid> ou ipfs://<cid>/<path>. |
Os produtores MUST NOT emitir qualquer outro esquema — https://, http://,
file://, data: e os demais são todos rejeitados. A restrição é deliberada, não
temporária: uma URI endereçada por conteúdo vincula os bytes obtidos à própria URI
por meio do modelo de integridade da camada de armazenamento (um CID IPFS é um
multihash do conteúdo; um id de transação Arweave compromete os dados sob o consenso
da Arweave), de modo que um verificador pode confirmar que "os bytes que obtive são
os bytes que o produtor comprometeu" sem confiar em DNS, TLS, gateways ou
autoridades certificadoras. Um esquema fora do conjunto torna o registro
estruturalmente inválido; ele nunca é validado como valid.
uris é opcional em toda parte. Um registro apenas com hash, sem uris, é uma
afirmação completa — a existência do conteúdo é afirmada sem comprometer nenhum
canal de recuperação. O perfil exato de CID (prefixos multibase aceitos, codecs e
multihashes) faz parte das regras de verificação; veja
Verificação.
CBOR canônico
Todo registro Label 309 MUST ser codificado em CBOR canônico conforme a RFC 8949 §4.2.1 (Core Deterministic Encoding). Concretamente:
- Serialização preferencial (na forma mais curta) para todo inteiro.
- Codificação de comprimento definido para todas as cadeias de bytes, cadeias de texto, arrays e mapas.
- Sem tags semânticas (este documento não exige nenhuma — uma tag de bignum 2/3 MUST NOT aparecer).
- Chaves de mapa ordenadas em ordem lexicográfica byte a byte da sua codificação CBOR.
- Cadeias de texto UTF-8 sem marca de ordem de bytes.
- Sem chaves duplicadas em nenhum mapa.
- Sem ponto flutuante nem valores simples não triviais — um registro carrega
apenas inteiros, cadeias de bytes, cadeias de texto, arrays, mapas e (onde um
esquema o admita)
true/false/null. Floats de tipo principal 7 (incluindo um1.0que seja inteiro), zero negativo eundefinedMUST ser rejeitados, não convertidos.
É o determinismo que torna o formato interoperável: dois produtores que expressam o mesmo registro lógico emitem bytes idênticos, de modo que uma assinatura calculada sobre o corpo por uma implementação é verificável por outra. Um validador MUST rejeitar uma codificação não canônica. Exploradores e carteiras podem exibir os metadados por meio de uma projeção JSON, mas um verificador em conformidade MUST validar o CBOR original da transação, nunca uma reescrita JSON com perdas dele.
Compatibilidade futura
A v1 do Label 309 reserva um conjunto fechado de chaves base: v, items,
merkle, supersedes, sigs, crit. Um registro MAY carregar adicionalmente
chaves de extensão cujos nomes correspondam a um de dois namespaces reservados:
^x-.+— o namespace de fornecedor / experimental.^[a-z]+-.+— o namespace de especificação acompanhante, em que o prefixo nomeia a especificação que faz o registro.
Um validador MUST decodificar e preservar as chaves de extensão, MUST NOT
rejeitar um registro só por elas estarem presentes, e MUST apresentá-las a
título informativo, sem afirmar que verificou o seu conteúdo. As chaves de extensão
fazem parte do corpo assinado, então uma assinatura no nível do registro as cobre —
um relay não pode injetar uma chave de extensão depois que a assinatura foi
produzida. Qualquer chave de nível superior desconhecida que não corresponda a
nenhum dos padrões (um erro de digitação como supersedess, ou uma variação de
maiúsculas/minúsculas como Sigs) é rejeitada como campo desconhecido. A tolerância
baseada em padrões preserva a detecção de erros de digitação no conjunto base, ao
mesmo tempo que mantém um espaço estável aberto para adições futuras.
Um produtor que exija que um verificador compreenda um campo fora do conjunto base
MUST listar o nome desse campo no array crit de nível superior. Um verificador
de v1 que encontre uma entrada de crit que não implementa MUST NOT reportar o
registro como válido. Cada entrada de crit MUST corresponder ao padrão de
chave de extensão (chaves base são proibidas em crit), MUST nomear um campo de
fato presente no registro e MUST ser única — assim, uma marca de criticidade é
sempre rastreável até um campo concreto cuja semântica o verificador é obrigado a
compreender. Essas regras seguem os precedentes de must-understand / must-ignore da
RFC 9052 §3.1 (crit do COSE)
e da RFC 7515 §4.1.11
(crit do JWS).
Orçamento de bytes
O único teto rígido para o tamanho do registro é o parâmetro de protocolo
maxTxSize vigente na Cardano — 16 384 bytes na versão principal de protocolo 10 na
mainnet, sujeito a atualizações de parâmetros do ledger. O Label 309 não impõe nenhum
limite no nível do esquema abaixo disso. Registros que excedem o
limite são rejeitados pelos nós Cardano no momento da submissão, de modo que nenhum
verificador jamais vê um deles; um validador MUST NOT inventar um teto específico
do Label 309 abaixo de maxTxSize.
Na prática, a estrutura não relativa a metadados de uma transação (entradas, saídas, testemunhas, campos de taxa e de validade) consome cerca de 245 bytes, deixando algo em torno de 16 KB para o registro do rótulo 309. Os produtores SHOULD mirar algumas centenas de bytes abaixo do limite, para absorver a variação da taxa, e SHOULD calcular o tamanho do registro candidato antes da submissão, falhando rápido caso ele não fosse caber. Os formatos realistas que cabem são generosos: bem mais de uma centena de itens de hash único, dezenas de assinaturas no nível do registro ou muitos slots de destinatário clássicos cabem confortavelmente em uma única transação — e uma única raiz Merkle compromete uma lista de folhas off-chain ilimitada a um custo on-chain fixo de 32 bytes.
Esquema CDDL
O CDDL a seguir é o esquema estrutural para o corpo do registro já remontado —
os bytes em CBOR canônico obtidos depois de concatenar o array de fragmentos de no
máximo 64 bytes armazenado sob o rótulo 309. O corpo remontado é CBOR determinístico
puro: ele próprio não é um metadatum do ledger, e seus campos não estão
sujeitos ao limite de 64 bytes por cadeia, que apenas o invólucro de transporte do
corpo inteiro satisfaz. O invólucro não é modelado aqui.
O bloco descreve o superconjunto permissivo de formatos bem formados; os invariantes
entre campos (a regra de items-ou-merkle, a exclusividade slots ⊕ passphrase
do envelope de criptografia, a pertença dos identificadores de algoritmo a um
catálogo, as regras de forma de slot por KEM) são impostos por um passo de validação
tipada sobre a estrutura decodificada, não pelo próprio CDDL.
; An extension value is any CBOR value the canonical (deterministic) encoding
; profile admits. Floats and semantic tags are excluded by that profile (they
; are rejected as MALFORMED_CBOR on decode), so the exclusion is not repeated
; here; the reassembled body carries no field-level 64-byte cap.
extension-value =
{ * extension-value => extension-value }
/ [ * extension-value ]
/ int
/ bstr
/ tstr
/ bool
/ null
; A conformant record MUST carry at least one of `items` (>= 1 entry) or
; `merkle` (>= 1 entry); a record with both absent (or both empty) is rejected
; as SCHEMA_EMPTY_RECORD by the typed pass, not at the CDDL layer.
poe-record = {
poe-common,
? "items": [ 1* item-entry ],
? "crit": [ 1* tstr ],
* extension-key => extension-value
}
poe-common = (
"v": 1,
? "merkle": [ 1* merkle-commit ],
? "supersedes": bytes32,
? "sigs": [ 1* sig-entry ],
)
extension-key = tstr .regexp "^x-.+"
/ tstr .regexp "^[a-z]+-.+"
item-entry = {
"hashes": hash-map,
? "uris": [ 1* uri ],
? "enc": enc,
}
; A non-empty CBOR map keyed by a content-hash algorithm identifier with the
; 32-byte digest as value. Map-key uniqueness makes duplicate algorithms
; structurally impossible.
hash-map = { + content-hash-alg => bytes32 }
; A list commitment binds the record to an ordered leaf list. `leaf_count`
; binds the on-chain commitment to the off-chain list size.
merkle-commit = {
"alg": merkle-commit-alg,
"root": bytes32,
"leaf_count": uint32,
? "uris": [ 1* uri ],
}
; `enc` is a choice between the scheme-1 envelope shape and a bounded opaque
; envelope (the degrade-to-opaque rule for an unsupported scheme/kem/aead). The
; typed pass enforces the slots/passphrase exclusivity and the per-KEM
; slot-shape rules over a supported envelope.
enc = enc-scheme-1 / enc-opaque
; `scheme: 1` is not a version counter for the `enc` map alone: it names the
; ENTIRE sealed cryptographic suite — the canonicalEncode rules, the slot
; schema, the HKDF and HMAC hashes, the wrap AEAD, the segmented-STREAM content
; format, the transcript schemas, the in-ciphertext passphrase commitment, the
; pinned X-Wing revision, every domain-separation label, and the Argon2id and
; passphrase-normalization profiles. Changing any one of them requires a new
; `scheme` value; see Sealed PoE for the construction it pins.
enc-scheme-1 = {
"scheme": 1,
"aead": aead-alg,
"nonce": bstr,
? "kem": kem-alg,
? "slots": [ 1* slot ],
? "slots_mac": bytes32,
? "passphrase": passphrase-block,
}
; The opaque reading of an envelope under an unsupported identifier: `scheme`
; is the only structurally required key, and every other entry is any key/value
; pair the canonical profile admits, subject to the generic decode bounds.
enc-opaque = {
"scheme": uint,
* tstr => extension-value
}
slot = classical-slot / hybrid-slot
; enc.kem = "x25519": the per-slot X25519 ephemeral public key + wrapped CEK.
classical-slot = {
"epk": bytes32,
"wrap": bytes48,
}
; enc.kem = "mlkem768x25519": the 1120-byte X-Wing ciphertext plus the wrapped
; CEK. There is NO `epk` — the X25519 ephemeral is the trailing 32 bytes of the
; X-Wing ciphertext inside `kem_ct`.
hybrid-slot = {
"kem_ct": bstr .size 1120,
"wrap": bytes48,
}
passphrase-block = {
"alg": kdf-alg,
"salt": bstr .size (16..64),
"params": { "m": uint32, "t": uint32, "p": uint32 },
}
; A signature entry is a closed map. `cose_sign1` is REQUIRED and carries the
; CBOR-encoded COSE_Sign1 as a single byte string; `cose_key` is OPTIONAL and
; carries the CBOR-encoded COSE_Key for the wallet-signing path as a single
; byte string.
sig-entry = {
"cose_sign1": bstr,
? "cose_key": bstr,
}
; A uri is one absolute URI in a single text string. The URI shape rules
; (absolute, no fragment, closed scheme set {ar://, ipfs://}) are enforced in
; the typed pass; the rule carries no length cap.
uri = tstr
bytes32 = bstr .size 32
bytes48 = bstr .size 48
; uint32 is the pinned range of every numeric field: an unsigned integer
; representable in 4 bytes (0 .. 2^32-1), handled as an exact integer.
uint32 = uint .size 4
; Algorithm-identifier strings are open `tstr`: the registries are
; authoritative for accepted values, and the typed pass emits the precise
; unsupported-algorithm code for any unrecognised identifier.
content-hash-alg = tstr ; e.g. "sha2-256", "blake2b-256"
merkle-commit-alg = tstr ; e.g. "rfc9162-sha256"
aead-alg = tstr
kem-alg = tstr
kdf-alg = tstrPáginas relacionadas
- Conteúdo e hashing — o mapa
hashes, o que um digest compromete e a semântica de bytes exatos. - Catálogos de algoritmos — os identificadores nomeados de hashes, compromissos de lista, AEADs, KEMs e KDFs.
- Assinaturas — a construção e a verificação de
sigsno nível do registro. - PoE selada — o envelope
ence os slots de chave de destinatário. - Verificação — o pipeline de validação, o perfil de CID e o catálogo de erros.
Introdução
O que é o Label 309, os princípios que ele garante e como qualquer pessoa pode verificar um registro sem precisar confiar em um servidor.
Conteúdo e hashing
Como o Label 309 vincula um registro ao seu conteúdo — o mapa de hashes, aquilo a que o digest se compromete e os compromissos de Merkle para ancorar muitos itens sob uma única raiz.