Esta é uma tradução informativa. A versão em inglês é a oficial e prevalece. Ler a versão em inglês

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:

CDDL
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 é:

ChaveTipoStatusSignificado
vuintREQUIREDVersão do esquema; este documento define v = 1.
itemsarray de mapas de itemOPTIONALCompromissos por conteúdo — veja Conteúdo e hashing.
merklearray de compromissosOPTIONALCompromissos de lista que vinculam listas de folhas off-chain a uma única raiz.
supersedesbytes (32)OPTIONALHash da transação de um registro anterior que este substitui.
sigsarray de mapas de assinaturaOPTIONALAssinaturas de autoria no nível do registro — veja Assinaturas.
critarray de cadeias de textoOPTIONALChaves 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:

CampoTipoStatusSignificado
algtstrREQUIREDIdentificador de algoritmo de compromisso de lista registrado.
rootbytes (32)REQUIREDRaiz canônica sobre a lista ordenada de folhas do produtor.
leaf_countuintREQUIREDNúmero de folhas comprometidas; vincula a raiz ao tamanho da lista.
urislista de URIsOPTIONALURI(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:

EsquemaObservaçõ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:

  1. Serialização preferencial (na forma mais curta) para todo inteiro.
  2. Codificação de comprimento definido para todas as cadeias de bytes, cadeias de texto, arrays e mapas.
  3. Sem tags semânticas (este documento não exige nenhuma — uma tag de bignum 2/3 MUST NOT aparecer).
  4. Chaves de mapa ordenadas em ordem lexicográfica byte a byte da sua codificação CBOR.
  5. Cadeias de texto UTF-8 sem marca de ordem de bytes.
  6. Sem chaves duplicadas em nenhum mapa.
  7. 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 um 1.0 que seja inteiro), zero negativo e undefined MUST 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 slotspassphrase 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.

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            = tstr

Pá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 sigs no nível do registro.
  • PoE selada — o envelope enc e os slots de chave de destinatário.
  • Verificação — o pipeline de validação, o perfil de CID e o catálogo de erros.