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

Chaves

O modelo de chaves do Label 309 — uma semente de 32 bytes, três pares de chaves derivados dela por HKDF-SHA-256 com separação de domínio, as chaves de criptografia de chave por slot que uma PoE selada deriva sobre elas, e como as chaves públicas e os segredos dos destinatários são codificados.

O Label 309 precisa de três tipos de chave assimétrica: uma chave Ed25519 que assina registros, uma chave X25519 que recebe cargas úteis seladas clássicas e uma chave híbrida X-Wing (mlkem768x25519) que recebe cargas úteis seladas pós-quânticas. O padrão não trata essas chaves como três segredos independentes a serem armazenados e gerenciados separadamente. Ele define um único segredo — uma semente de 32 bytes — e uma regra determinística que o expande em todos os três pares de chaves.

Esta página especifica essa derivação: a semente, as três expansões HKDF com separação de domínio que produzem a chave privada de cada algoritmo, por que os domínios são mantidos separados, as chaves de criptografia de chave por slot que uma PoE selada deriva sobre elas e como as chaves públicas e os segredos dos destinatários resultantes são codificados para a troca. O que uma implementação faz com a semente além disso — onde ela reside, como é desbloqueada, se uma mesma pessoa detém várias — está fora do escopo. O Label 309 se importa apenas com o seguinte: dados os mesmos 32 bytes, toda implementação em conformidade deriva as mesmas chaves.

A semente

Um conjunto de chaves do Label 309 tem raiz em um único valor:

PropriedadeValor
Comprimento32 bytes (256 bits)
OrigemUm RNG criptograficamente seguro, ou qualquer valor de 32 bytes do usuário
FunçãoMaterial de chave de entrada para as três expansões HKDF abaixo

A semente é uma fonte de entropia pura, não uma chave no sentido de nenhum algoritmo específico. Ela não carrega curva, nem comprimento atrelado a uma primitiva, nem cerimônia de codificação. As chaves que uma implementação de fato usa são negociadas em cada derivação; a semente sobrevive às escolhas de algoritmo feitas a partir dela. Um produtor PODE gerar a semente do zero a partir do CSPRNG da plataforma ou importar um valor de 32 bytes já existente; de um jeito ou de outro, ela DEVE decodificar para exatamente 32 bytes. Nenhum padrão de baixa entropia é rejeitado na camada de derivação — uma semente toda-zeros é uma entrada válida, e é justamente isso que a torna utilizável como fixture reproduzível de conformidade.

A semente é a identidade inteira

Todo fato de chave pública que o Label 309 expressa sobre uma parte — a chave que atesta um registro, as chaves que recebem uma carga útil selada — é uma função determinística desses 32 bytes. Reproduza a semente e você reproduz os três pares de chaves, byte por byte.

Codificando a semente para backup

Como os 32 bytes da semente são a identidade, é esse o valor que um usuário faz backup, exporta e importa — e um blob nu de 32 bytes é fácil de truncar ou corromper silenciosamente. O Label 309 define para ele uma codificação em string com soma de verificação, aceita ao lado do hex bruto em todo lugar em que uma semente é recebida como entrada.

A forma em string é Bech32 (BIP-173, clássico, com o limite de 90 caracteres de comprimento removido) sob o prefixo legível por humanos l309-seed- — o hífen final faz parte do HRP, de modo que o separador Bech32 produz o prefixo visível l309-seed-1…. A codificação retorna a forma de exibição em MAIÚSCULAS L309-SEED-1…: segredos são chamativos, e a renderização em maiúsculas é visualmente distinta das strings de destinatário age1…, em minúsculas. A forma toda em minúsculas é uma codificação igualmente válida dos mesmos bytes.

seed (32 bytes)  0000…0000  ->  L309-SEED-1QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQLFUN82

Um parser aceita duas representações e despacha conforme a forma:

  • A string Bech32 em um único caso (caso misto é rejeitado conforme a BIP-173), com a soma de verificação conferida e a carga útil decodificada com exatamente 32 bytes.
  • Hex bruto — 64 dígitos hexadecimais, sem distinção de maiúsculas/minúsculas, tolerando um prefixo 0x e espaços em branco ao redor ou internos.

Cada entrada rejeitada mapeia para um código de erro distinto da API de construção, de modo que quem chama consegue distinguir um erro de digitação de um tipo de chave errado:

EntradaCódigo de erro
Uma string Bech32 cuja soma de verificação falha (um caractere trocado, um truncamento)SEED_STRING_BAD_CHECKSUM
Uma string Bech32 misturando maiúsculas e minúsculasSEED_STRING_MIXED_CASE
Uma string Bech32 válida sob um HRP diferente (p. ex. um destinatário age1…)SEED_STRING_WRONG_HRP
Uma string Bech32 ou hexadecimal que decodifica para ≠ 32 bytesSEED_STRING_WRONG_LENGTH
Qualquer coisa que não seja nem uma string Bech32 reconhecida nem hex (incl. vazia)SEED_STRING_UNRECOGNIZED

Esses códigos descrevem o codec de string da semente, que é uma conveniência de manuseio de chaves em torno da derivação; eles são distintos do registro de códigos de erro de transmissão que um validador estrutural emite (Verificação). A codificação carrega os 32 bytes nus e nada mais — sem versão, sem parâmetros de derivação — porque o significado da semente é fixado pelas três strings info abaixo, não por como ela foi transportada.

Derivando os três pares de chaves

A chave privada de cada algoritmo é uma expansão HKDF-SHA-256 independente da mesma semente, conforme a RFC 5869. As três expansões compartilham o material de chave de entrada e o salt (ausente), e diferem em apenas um parâmetro — a string info que nomeia o algoritmo:

AlgoritmoString infoSaída
Ed25519cardano-poe-ed25519-v1Semente secreta Ed25519 de 32 bytes
X25519cardano-poe-x25519-v1Semente secreta X25519 de 32 bytes
mlkem768x25519cardano-poe-mlkem768x25519-v1Semente da chave de desencapsulamento X-Wing (32 bytes)

A derivação em pseudocódigo:

ed25519_priv        = HKDF-SHA-256(ikm = seed, salt = "", info = "cardano-poe-ed25519-v1",        length = 32)
x25519_priv         = HKDF-SHA-256(ikm = seed, salt = "", info = "cardano-poe-x25519-v1",         length = 32)
mlkem768x25519_priv = HKDF-SHA-256(ikm = seed, salt = "", info = "cardano-poe-mlkem768x25519-v1", length = 32)

Três regras tornam essas saídas interoperáveis entre implementações:

  1. O salt é vazio. O salt do HKDF DEVE ser a string de bytes de comprimento zero. Conforme a RFC 5869 §2.2, um salt ausente é tratado como HashLen bytes zero — 32 bytes zero no caso do SHA-256 — de modo que toda biblioteca em conformidade chega à mesma etapa de extração.
  2. A saída tem 32 bytes. Cada expansão solicita exatamente 32 bytes (um único bloco HKDF para SHA-256).
  3. As strings info são ASCII exato. Cada valor de info DEVE ser codificado precisamente como os bytes mostrados — sem espaços ao redor, sem terminador nulo, sem marca de ordem de bytes, sem quebra de linha no final. As três strings têm 22, 21 e 29 bytes, respectivamente.

Os 32 bytes de saída são a semente secreta do algoritmo, não o seu escalar de curva expandido. A RFC 8032 §5.1.5 faz essa distinção para o Ed25519: a semente secreta tem 32 bytes, e a biblioteca de assinatura a expande (via SHA-512 e depois clamping) no escalar de fato e no prefixo de assinatura, internamente. O mesmo vale para o X25519, em que o clamping é aplicado dentro da primitiva conforme a RFC 7748 §5. Uma implementação DEVE passar a saída bruta de 32 bytes do HKDF para a primitiva e deixar que a biblioteca realize a expansão e o clamping — ela não faz pré-clamping nem pré-expansão. No caso do X-Wing, a saída de 32 bytes é a semente da chave de desencapsulamento do X-Wing, a partir da qual o par de chaves completo — incluindo a chave pública de 1216 bytes — é regenerado de forma determinística pela geração de chaves do X-Wing. Em todos os casos, a semente compacta de 32 bytes, nunca uma chave expandida, é a forma canônica para armazenamento e transporte.

Por que três domínios, e não um

O parâmetro info do HKDF é a sua tag de separação de domínio: ele vincula a saída expandida a um contexto de aplicação específico, e a RFC 5869 §3.1 recomenda fortemente fornecer uma quando há contexto disponível. O Label 309 fornece uma tag distinta por algoritmo em vez de reutilizar uma mesma expansão para os três, ainda que as três chaves privadas tenham, por acaso, a mesma largura de 32 bytes. A razão é o isolamento:

  • As falhas ficam contidas. Se dois pares de chaves compartilhassem bytes idênticos, uma fraqueza específica de um algoritmo — uma falha na derivação de nonce, um canal lateral em uma multiplicação de escalar — poderia expor a chave de um algoritmo sem relação alguma. A separação de domínio garante que as três chaves privadas sejam funções independentes da semente, de modo que o comprometimento de uma não ensina nada ao atacante sobre as demais.
  • A migração permanece aditiva. Cada string info termina em -v1. Adotar uma curva diferente ou um híbrido diferente em uma revisão futura deriva uma nova chave -v2 da mesma semente sob uma nova tag, sem colisão com as chaves v1 já implantadas. Isso espelha a agilidade quanto a algoritmos da qual o próprio formato de fio depende.

A terceira tag, cardano-poe-mlkem768x25519-v1, dá ao híbrido pós-quântico um domínio próprio, ainda que a semente de sua chave de desencapsulamento tenha a mesma largura de 32 bytes que o segredo X25519 clássico. Uma falha no ML-KEM-768, no X25519 ou no combinador do X-Wing, portanto, não pode contaminar de forma cruzada a chave de criptografia clássica nem a chave de assinatura.

Essa tag de chave de identidade, cardano-poe-mlkem768x25519-v1, também não carrega segmento -kek-: ela é distinta do rótulo de derivação de KEK por registro cardano-poe-kek-mlkem768x25519-v1 mais adiante, de modo que a expansão semente → chave de identidade e o envoltório de chave por slot de uma PoE selada nunca compartilham uma string info.

Chaves de criptografia de chave por slot

Os três pares de chaves derivados da semente acima são chaves de identidade de longa duração. Uma PoE selada acrescenta uma segunda camada de HKDF-SHA-256, por registro: para cada slot de destinatário, o remetente deriva uma chave de criptografia de chave (KEK) nova de 32 bytes que envolve a chave de criptografia de conteúdo do registro. A derivação da KEK faz parte do modelo de chaves, então é especificada aqui; como a chave envolvida então viaja no envelope está em PoE selada.

Ambos os KEMs derivam a KEK com HKDF-SHA-256 e um info específico do KEM, sob um salt de hash rotulado que vincula três valores: o material de KEM do próprio slot (de modo que a KEK seja única por slot), a chave pública do destinatário pub_R (de modo que um encapsulamento forjado para um destinatário não possa ser repassado contra outro) e o enc.nonce único do envelope (de modo que a KEK fique ancorada a um único envelope). O segredo compartilhado é a saída do próprio ECDH do KEM (clássico) ou do desencapsulamento X-Wing (híbrido) — o mesmo valor de 32 bytes quer o remetente encapsule, quer o destinatário desencapsule — e salt e info são idênticos dos dois lados:

; x25519 (classical) — salt is a labelled SHA-256 over the ephemeral and recipient keys
kek_salt = SHA-256("cardano-poe-x25519-kek-salt-v1" || enc.nonce || pub_epk || pub_R)  ; 32 bytes
KEK      = HKDF-SHA-256(ikm  = shared,                  ; the X25519 ECDH shared secret
                        salt = kek_salt,
                        info = "cardano-poe-kek-v1",
                        L    = 32)

; mlkem768x25519 (hybrid) — same labelled-salt shape under the hybrid's own label
kek_salt = SHA-256("cardano-poe-xwing-kek-salt-v1" || enc.nonce || kem_ct || pub_R)    ; 32 bytes
KEK      = HKDF-SHA-256(ikm  = shared,                  ; the X-Wing shared secret
                        salt = kek_salt,
                        info = "cardano-poe-kek-mlkem768x25519-v1",
                        L    = 32)

Os dois salts têm a mesma forma — SHA-256(label || enc.nonce || <material de KEM do slot> || pub_R) —, diferindo apenas no rótulo por KEM e em qual material de KEM carregam: o efêmero pub_epk de 32 bytes no caminho clássico, o texto cifrado X-Wing kem_ct de 1120 bytes no caminho híbrido. Ambos são dobrados por meio de um digest SHA-256 de comprimento fixo porque as entradas híbridas são grandes demais para um salt bruto e uma única forma uniforme mantém os dois caminhos alinhados. O vínculo é calculado fora do KEM, sobre os próprios bytes de transmissão do slot, de modo que ele trata o X-Wing como um KEM de caixa preta e não se apoia em nenhuma propriedade do hashing interno do combinador. O rótulo info distinto por KEM garante, ainda, que uma KEK derivada sob um KEM nunca possa ser igual a uma KEK derivada sob o outro a partir de um segredo compartilhado idêntico de 32 bytes.

Em ambos os salts, pub_R é a codificação de transmissão canônica da chave do destinatário — exatamente a chave pública X25519 de 32 bytes para x25519, exatamente a string de bytes de 1216 bytes da chave pública X-Wing fixada para mlkem768x25519. Remetente e destinatário DEVEM usar essa codificação exata e NÃO DEVEM substituí-la por qualquer equivalente não canônico ou recodificado: caso contrário, os dois lados alimentariam salts diferentes no HKDF e derivariam KEKs diferentes, e o slot nunca abriria.

Cada KEK e seu prefixo de salt são peças de construção internas do enc.scheme: 1: não carregam identificador de transmissão e não são selecionáveis. Os dois rótulos de prefixo de salt e os dois rótulos info aqui são quatro dos onze literais de rótulo da construção selada catalogados em Registros de algoritmos; um verificador DEVE usar cada um byte a byte.

Codificações de chave pública do destinatário

Quem envia uma PoE selada precisa da chave pública do destinatário em uma forma portátil de string, e um destinatário faz backup do seu segredo na forma correspondente. O Label 309 reutiliza as codificações Bech32 de destinatário do ecossistema age, um prefixo legível por humanos (HRP) para cada mecanismo de encapsulamento de chave registrado.

No Bech32, o 1 é o separador entre o HRP e a parte de dados, então o prefixo visível a uma pessoa em uma string é o seu HRP mais esse 1. O HRP e o prefixo visível são, portanto, distintos, e a tabela os mantém em colunas separadas:

KEM (enc.kem)Chave públicaHRP da chave públicaPrefixo visível da chave públicaHRP do segredoPrefixo visível do segredo
x25519Chave pública X25519 (32 bytes)ageage1… (62 caracteres)AGE-SECRET-KEY-AGE-SECRET-KEY-1…
mlkem768x25519Chave pública X-Wing (1216 bytes)age1pqcage1pqc1… (1960 caracteres)AGE-SECRET-KEY-PQ-AGE-SECRET-KEY-PQ-1…

A string de destinatário clássica x25519 tem HRP age e a forma age v1 padrão age1…. A chave pública híbrida concatena uma chave de encapsulamento ML-KEM-768 (1184 bytes) com uma chave pública X25519 (32 bytes); com 1216 bytes, sua string de destinatário age1pqc1… tem 1960 caracteres.

O segredo que uma implementação faz backup e importa é, nos dois caminhos, a semente de 32 bytes — a semente secreta X25519 sob AGE-SECRET-KEY- e a semente da chave de desencapsulamento X-Wing (a terceira saída do HKDF acima, info = "cardano-poe-mlkem768x25519-v1") sob AGE-SECRET-KEY-PQ-. A chave pública híbrida de 1216 bytes deriva dessa semente; a semente compacta, nunca a chave expandida, é o segredo canônico a armazenar.

A BIP-173 limita uma string Bech32 a 90 caracteres, mas esse limite existe para endereços de pagamento digitados por pessoas e não se aplica aqui. Uma implementação DEVE codificar e decodificar a string age1pqc1… sem impor o limite de 90 caracteres, mas aplicando ainda as regras de checksum e de conjunto de caracteres do Bech32. O HRP distinto age1pqc impede que o destinatário híbrido colida com qualquer destinatário clássico age — e é deliberadamente não age1pq, o prefixo mais curto que uma codificação nativa upstream de ML-KEM-768 + X25519 já reivindica para a mesma primitiva, de modo que as duas codificações de destinatário nunca colidem em trânsito. A codificação clássica permanece dentro de comprimentos comuns e é tratada sem alterações.

Essas strings são apenas uma conveniência para a descoberta de destinatários. Uma chave pública de destinatário nunca aparece no envelope de criptografia de um registro Label 309 — uma entrada de enc.slots[] carrega o material de chave por slot e um valor wrap, e o identificador do KEM aparece uma vez em enc.kem. O modo como o envelope e os slots são construídos é abordado em PoE selada.

A chave pública Ed25519 como kid da assinatura

A chave pública Ed25519 não cumpre nenhum papel de destinatário; ela é o identificador de chave contra o qual um verificador resolve uma assinatura. Quando um produtor assina um registro, a chave pública Ed25519 bruta de 32 bytes é o kid (rótulo 4) no cabeçalho protegido do COSE_Sign1, conforme a RFC 9052. O verificador lê esse valor de 32 bytes diretamente da assinatura on-chain e confere o corpo do registro contra ele — a chave pública viaja junto com a assinatura, de modo que nenhuma consulta separada é necessária para verificar a autoria. A construção de assinatura completa, a carga útil assinada e as regras de verificação estão especificadas em Assinaturas.

Troca de chaves fora de banda

O Label 309 especifica como as chaves públicas dos destinatários são codificadas, não como são descobertas. O padrão não prescreve diretório, nem registro de identificadores, nem formato de anúncio on-chain para chaves de destinatários. Quem quer receber uma carga útil selada publica sua string age1… ou age1pqc1… por qualquer canal em que ambos os lados já confiem — uma entrega em mãos, um registro assinado sob a própria chave Ed25519, um registro em um local estável na web ou endereçado por conteúdo — e o remetente é responsável pela procedência de qualquer chave para a qual criptografe.

Esse é um limite deliberado. A mesma propriedade que permite verificar um registro sem confiar em um servidor significa que a troca de chaves não pode reintroduzir, sorrateiramente, um intermediário de confiança. Um nome colocado ao lado de uma chave é uma afirmação de quem o colocou, nunca uma alegação criptográfica: duas partes que usam o mesmo identificador ainda produzem chaves com bytes diferentes, e o verificador compara os bytes. Mapear nomes legíveis por humanos para chaves é algo que uma aplicação construída sobre o Label 309 PODE oferecer, mas é um recurso da aplicação, fora do protocolo.

Páginas relacionadas

  • Assinaturas — como a chave Ed25519 assina um registro e como o kid é verificado.
  • PoE selada — como as chaves públicas X25519 e X-Wing endereçam uma carga útil criptografada a destinatários específicos.
  • Registros de algoritmos — os identificadores nomeados para assinaturas, KEMs, AEADs e KDFs referenciados aqui.