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

Assinaturas

O array opcional `sigs`, no nível do registro — um COSE_Sign1 destacado sobre todo o corpo do registro, sua carga útil assinada com separação de domínio, os dois caminhos para a chave do signatário e a verificação estrita de Ed25519.

Um registro Label 309 PODE conter uma ou mais assinaturas de autoria em um array sigs opcional, no nível superior. Cada entrada é um COSE_Sign1 (RFC 9052) destacado sobre o corpo do registro, atestando que alguma chave responde por aquele registro. A autoria é sempre opcional: o padrão nunca exige uma assinatura, e um registro sem o campo sigs é uma prova de existência (PoE) completa e plenamente verificável.

Uma assinatura é aditiva: ela acrescenta "e esta chave responde por isto" à afirmação de carimbo temporal, nunca a substitui. O hash do conteúdo é a afirmação principal; uma assinatura são metadados sobre quem está por trás dessa afirmação. O ponto essencial: uma assinatura que o verificador não consegue checar — um algoritmo não suportado, uma chave que não se resolve — nunca invalida a afirmação de conteúdo ou de carimbo temporal. As assinaturas falham de forma branda; a existência, não.

Esta página define o que uma assinatura cobre, os bytes exatos que são assinados, as duas formas de transportar a chave pública do signatário e a verificação estrita que um verificador público realiza. A própria chave Ed25519 é definida em Chaves; o campo sigs no formato de transmissão — em que cose_sign1 e cose_key são, cada um, uma única string de bytes CBOR — é definido em O registro.

O que uma assinatura cobre

Uma única entrada sigs[i] atesta todo o corpo do registro, de modo uniforme. Não há granularidade de assinatura por item, por URI ou por campo: uma assinatura compromete todos os itens, todos os URIs de armazenamento, todos os envelopes de criptografia, o ponteiro supersedes, se presente, e cada chave de extensão que o registro carrega. Um relé não pode adicionar, remover ou reescrever nenhum desses elementos depois do fato sem invalidar a assinatura.

O corpo assinado é o mapa do registro com o campo sigs removidoremove_keys(record_map, ["sigs"]), aqui denotado record_body. O array sigs fica de fora do que cada entrada assina porque uma assinatura não pode cobrir a si mesma e porque cada signatário compromete apenas a afirmação, não a lista de cossignatários. Concretamente, toda entrada assina {v, items?, merkle?, supersedes?, crit?, <extensions?>} — os mesmos bytes de record_body para todas as entradas — mas nenhuma entrada assina as demais entradas em sigs. Um signatário, portanto, atesta que o corpo que assinou é o corpo ao qual todas as outras entradas estão vinculadas; nenhum signatário atesta quais outros signatários cossinaram.

O escopo da assinatura é o corpo do registro, não a transação

Uma assinatura verificada prova que uma chave produziu uma assinatura sobre o corpo do registro. Ela não prova que a mesma chave submeteu a transação que o carrega, pagou sua taxa ou escolheu seu horário do bloco. Um corpo de registro idêntico PODE ser republicado por qualquer parte em uma transação posterior — isso é portabilidade de registro intencional. Apresente uma assinatura verificada como "assinado por <chave>", nunca como "<chave> submeteu isto" ou "publicado por <chave> em <horário>".

A carga útil assinada

Cada entrada carrega um COSE_Sign1 destacado, de modo que o campo de carga útil do COSE fica vazio e os bytes efetivamente assinados são reconstruídos pelo verificador a partir do registro on-chain. O signatário calcula:

record_body       = remove_keys(record_map, ["sigs"])
record_body_bytes = canonical_cbor(record_body)
SIG_DOMAIN_RECORD = utf8("cardano-poe-record-sig-v1")        ; 25 bytes
to_sign           = SIG_DOMAIN_RECORD || record_body_bytes   ; concatenation
Sig_structure     = [ "Signature1", protected, h'', to_sign ]
signature         = Sign(canonical_cbor(Sig_structure), signer_key)

record_body é serializado como CBOR canônico conforme RFC 8949 §4.2.1 — a mesma codificação determinística usada por todo o registro. O determinismo é o que torna uma assinatura interoperável: duas implementações que codificam o mesmo corpo lógico produzem record_body_bytes idênticos byte a byte, de modo que uma assinatura produzida por uma é verificada sob a outra.

O prefixo de separação de domínio

to_sign é a string UTF-8 de 25 bytes cardano-poe-record-sig-v1 anteposta a record_body_bytes. O prefixo vincula a assinatura ao seu papel no Label 309 e impede a reutilização entre protocolos. Um futuro esquema de metadados Cardano que por acaso compartilhasse a forma CBOR do corpo (mesmas chaves, mesmos tipos) não poderia reutilizar uma assinatura Label 309 contra si mesmo: seu to_sign carregaria um prefixo diferente, ou nenhum, de modo que a sequência de bytes assinados seria distinta e a assinatura falharia. As implementações DEVEM embutir essa sequência literal de bytes como os bytes iniciais de to_sign exatamente; assinar apenas o CBOR canônico nu, sem prefixo, não é conforme.

Por que external_aad é vazio

O Label 309 coloca o separador de domínio dentro de to_sign, e não no external_aad do COSE. O campo external_aad (Sig_structure[2]) é sempre a string de bytes vazia h''. Esse é um afastamento deliberado do padrão usual do COSE de colocar uma string de domínio em external_aad, e o motivo é a interoperabilidade com carteiras: o CIP-30 signData — o caminho padrão de assinatura por carteira no Cardano — estipula que nenhum external_aad seja usado e não dá a uma dApp nenhuma forma de fornecer um. Um external_aad não vazio faria toda assinatura produzida por carteira falhar. Embutir o prefixo na carga útil preserva a mesma propriedade contra reutilização e, ao mesmo tempo, mantém os bytes produzidos pela carteira e recomputados pelo verificador iguais byte a byte.

A Sig_structure

Sig_structure é o array de assinatura COSE_Sign1 de 4 elementos do RFC 9052 §4.4:

SlotValorNotas
[0]"Signature1"Identificador de contexto COSE fixo, emitido como a string de texto CBOR completa (11 bytes), nunca o UTF-8 nu.
[1]protectedOs bytes do cabeçalho protegido do signatário, em CBOR canônico e envolvidos em bstr, usados literalmente — nunca recanonicalizados pelo verificador.
[2]external_aadSempre h'' (bstr de comprimento zero).
[3]to_signO prefixo de 25 bytes concatenado com record_body_bytes.

O COSE_Sign1 publicado carrega seu campo de carga útil (COSE_Sign1[2]) como o CBOR null (0xF6) — a forma destacada. Uma carga útil anexada, inclusive uma string de bytes de comprimento zero, é rejeitada. Destacar a carga útil é o que fixa os bytes assinados ao corpo do registro que o verificador recomputa de forma independente; uma forma anexada permitiria que um produtor assinasse bytes emprestados, sem relação alguma com as afirmações on-chain.

Modo com hash de carteira de hardware

O CIP-30 / CIP-8 definem uma flag opcional "hashed": true de cabeçalho não protegido que um cossignatário de hardware com recursos limitados PODE definir. Quando presente e verdadeira, Sig_structure[3] é o digest Blake2b-224(to_sign) de 28 bytes em vez do próprio to_sign; os outros três slots permanecem inalterados. Um verificador DEVE inspecionar o cabeçalho não protegido e realizar essa substituição antes da verificação estrita de Ed25519. Produtores de software e SDK NÃO DEVERIAM defini-la — ela não economiza bytes na transmissão e complica os caminhos de código do verificador.

Algoritmo de assinatura

O único algoritmo de assinatura na v1 é EdDSA sobre Ed25519 (RFC 8032), identificado pelo COSE alg = -8 (RFC 9053 §2.2), que reside no cabeçalho protegido do COSE_Sign1. A linha de base obrigatória de um verificador v1 é {-8}; ele PODE aceitar adicionalmente -19 (Ed25519, totalmente especificado) e verificar ambos os codepoints sob a mesma primitiva Ed25519. O catálogo de algoritmos é extensível — revisões futuras adicionam assinaturas pós-quânticas de forma aditiva, nunca como uma mudança incompatível.

Resolução da chave do signatário

Um verificador público deve resolver a chave pública do signatário sem contatar serviço algum, de modo que toda assinatura carrega sua chave, ou uma referência inequívoca a ela dentro da própria assinatura, on-chain. Há exatamente duas formas de transporte na v1, e elas são mutuamente exclusivas dentro de uma única entrada — uma entrada que use ambas é um erro estrutural.

Caminho 1 — assinatura por identidade (kid na própria assinatura)

A chave pública Ed25519 bruta de 32 bytes é colocada no rótulo de cabeçalho COSE 4 (kid, RFC 9052 §3.1) dentro do cabeçalho protegido do COSE_Sign1. A entrada não carrega nenhum campo cose_key. Pela convenção do Label 309, um kid de cabeçalho protegido com exatamente 32 bytes é a chave pública — não um ponteiro opaco para uma chave buscada fora de banda. O comprimento de 32 bytes é um discriminador inequívoco: as chaves públicas Ed25519 têm sempre 32 bytes. Colocar a chave no cabeçalho protegido (e não no não protegido) a vincula à assinatura; um adversário que a reescrevesse invalidaria a verificação.

Essa convenção é um desvio deliberado e documentado da leitura de kid como identificador opaco na RFC 9052; é o que torna o caminho de identidade independente de serviços, sem nenhum diretório de chaves necessário. O modelo de chaves é definido em Chaves.

Caminho 2 — assinatura por carteira (cose_key embutido)

Uma assinatura signData do CIP-30 retorna a chave pública do signatário como um blob cbor<COSE_Key> separado, não dentro do COSE_Sign1. Um produtor que encadeie tal assinatura em um registro DEVE colocar essa COSE_Key na mesma entrada sigs[i], sob a chave cose_key, como uma única string de bytes CBOR. O verificador a decodifica como uma COSE_Key e lê a chave pública Ed25519 no rótulo -2. A COSE_Key DEVE descrever apenas a metade pública — kty = OKP (1), crv = Ed25519 (6), o x de 32 bytes no rótulo -2 — e NÃO DEVE carregar material de chave privada (rótulo -4 e similares); publicar um escalar privado em um livro-razão permanente é um vazamento de chave irreversível.

Exclusão mútua

Os dois caminhos são exclusivos no nível da transmissão. Uma entrada carrega ou um kid de cabeçalho protegido de 32 bytes e nenhum cose_key (caminho 1), ou um campo cose_key e nenhum kid de cabeçalho protegido de 32 bytes (caminho 2) — nunca ambos. Uma entrada que carregue ambos é rejeitada; um verificador nunca precisa desambiguar no momento da verificação. A resolução é, portanto, uma distinção no nível da transmissão, e não uma precedência ordenada:

CaminhoCondiçãoChave do signatário
1kid protegido de 32 bytes, sem cose_keyO valor de kid de 32 bytes, usado diretamente.
2cose_key presente, sem kid de 32 bytesA chave Ed25519 no rótulo -2 da COSE_Key.

Um kid carregado apenas no cabeçalho não protegido não é um caminho de resolução sancionado: ele fica fora do envelope assinado, de modo que um relé poderia reescrevê-lo sem invalidar a assinatura. Um verificador DEVE ignorar valores de kid de cabeçalho não protegido na resolução. Se nenhum caminho permitido produzir uma chave Ed25519 de 32 bytes, a entrada é reportada como não resolvida e não contribui com nenhuma afirmação de autoria.

Verificação

Um verificador público checa cada sigs[i] de forma independente, nesta ordem:

  1. Decodificar. Faça o parse da string de bytes sigs[i].cose_sign1 como um COSE_Sign1. O campo de carga útil DEVE ser null (destacado); qualquer carga útil não nula ou não vazia é malformada.
  2. Algoritmo. Leia o alg do cabeçalho protegido. Se estiver fora do conjunto suportado pelo verificador, a entrada é não suportada (veja abaixo) — e não um erro no registro.
  3. Resolver a chave. Aplique a distinção entre caminho 1 e caminho 2 acima para obter a chave pública Ed25519 de 32 bytes. Se nenhum caminho produzir uma, a entrada é não resolvida.
  4. Reconstruir e verificar. Reconstrua to_sign e Sig_structure = ["Signature1", protected, h'', to_sign], codifique-o em CBOR canônico e verifique a assinatura com Ed25519 estrito. (Substitua to_sign por Blake2b-224(to_sign) primeiro, se o cabeçalho não protegido carregar "hashed": true.)
  5. Vínculo com a carteira (apenas caminho 2). Recompute o endereço de stake a partir da chave resolvida e compare-o byte a byte com o address do cabeçalho protegido; uma divergência faz o vínculo falhar, ainda que a própria assinatura Ed25519 tenha sido verificada. Essa checagem, exclusiva do caminho 2, é o que permite a uma interface apresentar um registro como vinculado a uma carteira; as entradas do caminho 1 a ignoram.

Ed25519 estrito

A verificação segue as regras estritas da RFC 8032 §5.1.7 — há exatamente uma resposta aceitável para qualquer chave, mensagem e assinatura dadas:

  • Codificações não canônicas de R ou do escalar de assinatura S (em especial qualquer S ≥ ℓ, a ordem do grupo) DEVEM ser rejeitadas.
  • Chaves públicas e valores de R de ordem pequena, de subgrupo pequeno ou com componente de torção DEVEM ser rejeitados.
  • A equação de verificação com cofator (a forma ZIP-215 / favorável a lotes) NÃO DEVE ser substituída pela equação estrita.

O rigor é o que torna o veredicto reproduzível entre implementações: um verificador com cofator aceitaria assinaturas que um estrito rejeita, de modo que dois verificadores conformes discordariam. As implementações devem escolher uma biblioteca — ou um modo de biblioteca — que realize verificação estrita, sem cofator.

Semântica do veredicto

As assinaturas são aditivas, portanto uma assinatura não verificável é reportada na entrada, não promovida a uma falha no nível do registro. Cada sigs[i] resolve-se em um destes resultados tipados por entrada; o catálogo completo de erros e as regras do veredicto no nível do registro estão em Verificação:

ResultadoSignificado
verificadaO Ed25519 estrito (e, para o caminho 2, o vínculo de endereço) passou.
assinatura não suportadaO alg do cabeçalho protegido está fora do conjunto do verificador. Informativo, nunca um erro.
chave do signatário não resolvidaNenhum caminho permitido produz uma chave pública Ed25519 de 32 bytes.
assinatura inválidaO Ed25519 estrito retornou false sobre a Sig_structure reconstruída.
endereço de carteira divergenteCaminho 2: a assinatura foi verificada, mas o endereço de stake recomputado ≠ o reivindicado.

Uma assinatura não suportada nunca invalida a prova

Um algoritmo de assinatura não reconhecido ou não suportado produz um resultado tipado de assinatura não suportada com severidade informativa. A afirmação de conteúdo e de carimbo temporal — o compromisso hashes on-chain — é estruturalmente válida, independentemente dos algoritmos de assinatura que um verificador implementa. Um registro que carrega apenas assinaturas de algoritmos futuros ainda se apresenta como uma prova de existência válida, com cada entrada dessas marcada como não suportada. As assinaturas são aditivas; a existência não depende delas.

Páginas relacionadas

  • Chaves — a chave de assinatura Ed25519, sua derivação e a chave pública de 32 bytes carregada no kid do caminho 1.
  • O registro — o campo sigs no nível superior, o mapa sig-entry fechado (cose_sign1 / cose_key, cada um uma única string de bytes) e o transporte sobre o corpo inteiro.
  • Verificação — os códigos de resultado por entrada, as regras do veredicto no nível do registro e o pipeline completo de validação.