Claves
El modelo de claves de Label 309: una semilla de 32 bytes, tres pares de claves de algoritmos derivados de ella mediante HKDF-SHA-256 con separación de dominio, las claves de cifrado de clave por ranura que una PoE sellada deriva por encima, y cómo se codifican las claves públicas y los secretos de los destinatarios.
Label 309 requiere tres tipos de clave asimétrica: una clave Ed25519 que firma
registros, una clave X25519 que recibe cargas útiles cifradas selladas clásicas, y una
clave híbrida X-Wing (mlkem768x25519) que recibe cargas útiles selladas poscuánticas.
El estándar no trata estas claves como tres secretos independientes que se deben
almacenar y gestionar por separado. Define exactamente un secreto, una semilla de 32 bytes,
y una regla determinista que la expande en los tres pares de claves.
Esta página especifica esa derivación: la semilla, las tres expansiones HKDF con separación de dominio que producen la clave privada de cada algoritmo, el motivo por el que los dominios se mantienen separados, las claves de cifrado de clave por ranura que una PoE sellada deriva por encima de ellas, y cómo se codifican las claves públicas y los secretos de los destinatarios resultantes para su intercambio. Lo que una implementación haga con la semilla más allá de esto (dónde reside, cómo se desbloquea, si una persona mantiene varias) queda fuera del alcance. Label 309 solo exige que, dados los mismos 32 bytes, toda implementación conforme derive las mismas claves.
La semilla
Un conjunto de claves de Label 309 tiene su raíz en un único valor:
| Propiedad | Valor |
|---|---|
| Longitud | 32 bytes (256 bits) |
| Origen | Un generador de números aleatorios criptográficamente seguro, o cualquier valor de 32 bytes que el usuario posea |
| Función | Material de clave de entrada para las tres expansiones HKDF descritas a continuación |
La semilla es una fuente de entropía pura, no una clave en el sentido de ningún algoritmo concreto. No lleva asociada ninguna curva, ninguna longitud ligada a un primitivo ni ninguna ceremonia de codificación. Las claves que una implementación utiliza efectivamente se negocian por cada derivación; la semilla sobrevive a las elecciones de algoritmo que se tomen sobre ella. Un productor PUEDE generar la semilla de nuevo desde el CSPRNG de la plataforma o importar un valor de 32 bytes existente; en cualquier caso, DEBE decodificar exactamente a 32 bytes. No se rechaza ningún patrón de baja entropía en la capa de derivación: una semilla compuesta solo de ceros es una entrada válida, y eso es justamente lo que la hace utilizable como caso de prueba de conformidad reproducible.
La semilla es la identidad completa
Toda afirmación de clave pública que Label 309 expresa sobre una parte (la clave que avala un registro, las claves que reciben una carga útil sellada) es una función determinista de estos 32 bytes. Si se reproduce la semilla, se reproducen los tres pares de claves, byte por byte.
Codificar la semilla para la copia de seguridad
Como la semilla de 32 bytes es la identidad, es el valor que un usuario respalda, exporta e importa, y un blob desnudo de 32 bytes es fácil de truncar o corromper en silencio. Label 309 define para ella una codificación de cadena con suma de verificación, aceptada junto al hex en bruto en todo lugar donde se toma una semilla como entrada.
La forma de cadena es Bech32 (BIP-173,
clásico, con el tope de longitud de 90 caracteres levantado) bajo el prefijo legible por
humanos l309-seed-: el guion final forma parte del HRP, de modo que el separador de
Bech32 representa el prefijo visible l309-seed-1…. La codificación devuelve la forma
de visualización en MAYÚSCULAS L309-SEED-1…: los secretos se anuncian con fuerza, y
la representación en mayúsculas es visualmente distinta de las cadenas de destinatario
age1… en minúsculas. La forma íntegramente en minúsculas es una codificación
igualmente válida de los mismos bytes.
seed (32 bytes) 0000…0000 -> L309-SEED-1QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQLFUN82Un analizador acepta dos representaciones y discrimina según la forma:
- La cadena Bech32 en un único caso de mayúsculas/minúsculas (la mezcla de casos se rechaza conforme a BIP-173), con la suma de verificación comprobada y la carga útil decodificada de exactamente 32 bytes.
- Hex en bruto: 64 dígitos hexadecimales, indiferente a mayúsculas/minúsculas,
tolerando un prefijo
0xy espacios en blanco circundantes o internos.
Cada entrada rechazada se asigna a un código de error distinto de la API de construcción, de modo que un llamante pueda distinguir una errata de un tipo de clave equivocado:
| Entrada | Código de error |
|---|---|
| Una cadena Bech32 cuya suma de verificación falla (un carácter alterado, un truncamiento) | SEED_STRING_BAD_CHECKSUM |
| Una cadena Bech32 que mezcla mayúsculas y minúsculas | SEED_STRING_MIXED_CASE |
Una cadena Bech32 válida bajo un HRP distinto (p. ej. un destinatario age1…) | SEED_STRING_WRONG_HRP |
| Una cadena Bech32 o hexadecimal que decodifica a ≠ 32 bytes | SEED_STRING_WRONG_LENGTH |
| Cualquier cosa que no sea ni una cadena Bech32 reconocida ni hex (incl. vacía) | SEED_STRING_UNRECOGNIZED |
Estos códigos describen el códec de la cadena de la semilla, que es una comodidad de
manejo de claves en torno a la derivación; son distintos del registro de códigos de error
de transmisión que emite un validador estructural (Verificación). La
codificación transporta los 32 bytes desnudos y nada más (sin versión, sin parámetros de
derivación) porque el significado de la semilla queda fijado por las tres cadenas info
de más abajo, no por cómo se transportó.
Derivación de los tres pares de claves
La clave privada de cada algoritmo es una expansión HKDF-SHA-256 independiente de la
misma semilla, conforme a RFC 5869. Las tres
expansiones comparten su material de clave de entrada y su sal (ausente), y se diferencian
únicamente en un parámetro: la cadena info que nombra el algoritmo:
| Algoritmo | Cadena info | Salida |
|---|---|---|
| Ed25519 | cardano-poe-ed25519-v1 | Semilla secreta Ed25519 de 32 bytes |
| X25519 | cardano-poe-x25519-v1 | Semilla secreta X25519 de 32 bytes |
mlkem768x25519 | cardano-poe-mlkem768x25519-v1 | Semilla de clave de desencapsulación X-Wing de 32 bytes |
La derivación en 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)Tres reglas hacen que estas salidas sean interoperables entre implementaciones:
- La sal está vacía. La sal HKDF DEBE ser la cadena de bytes de longitud cero.
Conforme a RFC 5869 §2.2, una
sal ausente se trata como
HashLenbytes en cero (32 bytes en cero para SHA-256), por lo que toda biblioteca conforme alcanza el mismo paso de extracción. - La salida es de 32 bytes. Cada expansión solicita exactamente 32 bytes (un único bloque HKDF para SHA-256).
- Las cadenas
infoson ASCII exactas. Cada valorinfoDEBE estar codificado exactamente como los bytes mostrados: sin espacios en blanco circundantes, sin terminador nulo, sin marca de orden de bytes, sin salto de línea al final. Las tres cadenas tienen 22, 21 y 29 bytes respectivamente.
Los 32 bytes de salida son la semilla secreta del algoritmo, no su escalar de curva expandido. RFC 8032 §5.1.5 establece esta distinción para Ed25519: la semilla secreta es de 32 bytes, y la biblioteca de firma la expande internamente (mediante SHA-512 y luego ajuste de bits) en el escalar real y el prefijo de firma. Lo mismo aplica para X25519, donde el ajuste de bits se realiza dentro del primitivo conforme a RFC 7748 §5. Una implementación DEBE pasar la salida HKDF de 32 bytes en bruto al primitivo y dejar que la biblioteca realice la expansión y el ajuste de bits: no aplica el ajuste ni la expansión por adelantado. Para X-Wing, la salida de 32 bytes es la semilla de clave de desencapsulación de X-Wing, a partir de la cual se regenera de forma determinista el par de claves completo (incluida la clave pública de 1216 bytes) mediante la generación de claves de X-Wing. En todos los casos, la semilla compacta de 32 bytes, nunca una clave expandida, es la forma canónica para almacenar y transportar.
Por qué tres dominios, no uno
El parámetro info de HKDF es su etiqueta de separación de dominio: vincula la salida
expandida a un contexto de aplicación específico, y
RFC 5869 §3.1 recomienda
encarecidamente suministrar una cuando el contexto esté disponible. Label 309 suministra una
etiqueta distinta por algoritmo en lugar de reutilizar una única expansión para los tres,
aunque las tres claves privadas resulten tener 32 bytes de ancho. El motivo es el
aislamiento:
- Los fallos permanecen contenidos. Si dos pares de claves compartieran bytes idénticos, una debilidad específica de un algoritmo (un fallo en la derivación de nonces, un canal lateral en una multiplicación escalar) podría exponer la clave de un algoritmo no relacionado. La separación de dominio garantiza que las tres claves privadas son funciones independientes de la semilla, de modo que comprometer una no revela nada al atacante sobre las demás.
- La migración es aditiva. Cada cadena
infotermina en-v1. Adoptar una curva diferente o un esquema híbrido distinto en una revisión futura deriva una clave-v2nueva a partir de la misma semilla bajo una nueva etiqueta, sin colisión con las claves v1 ya desplegadas. Esto es coherente con la agilidad de algoritmos de la que también depende el propio formato de transmisión.
La tercera etiqueta, cardano-poe-mlkem768x25519-v1, otorga al esquema híbrido poscuántico
su propio dominio aunque su semilla de clave de desencapsulación tenga el mismo ancho de 32
bytes que el secreto X25519 clásico. Un fallo en ML-KEM-768, en X25519 o en el combinador
X-Wing no puede contaminar, por tanto, la clave de cifrado clásica ni la clave de firma.
Esta etiqueta de clave de identidad, cardano-poe-mlkem768x25519-v1, tampoco lleva ningún
segmento -kek-: es distinta de la etiqueta de derivación de KEK por registro
cardano-poe-kek-mlkem768x25519-v1 que aparece más abajo, de modo que la expansión de
semilla → clave de identidad y el envoltorio de clave por ranura de una PoE sellada nunca
comparten una cadena info.
Claves de cifrado de clave por ranura
Los tres pares de claves derivados de la semilla anteriores son claves de identidad de larga duración. Una PoE sellada añade una segunda capa de HKDF-SHA-256, esta vez por registro: para cada ranura de destinatario, el remitente deriva una clave de cifrado de clave (KEK) fresca de 32 bytes que envuelve la clave de cifrado de contenido del registro. La derivación de la KEK forma parte del modelo de claves, así que se especifica aquí; cómo cabalga después la clave envuelta sobre el sobre está en PoE sellada.
Ambos KEM derivan la KEK con HKDF-SHA-256 y un info específico del KEM, bajo un salt de
hash etiquetado que vincula tres valores: el propio material KEM de la ranura (de modo que
la KEK es única por ranura), la clave pública del destinatario pub_R (de modo que un
encapsulamiento fabricado para un destinatario no pueda reenviarse contra otro) y el
enc.nonce único del sobre (de modo que la KEK quede anclada a un único sobre). El secreto
compartido es la salida del propio ECDH del KEM (clásico) o de la desencapsulación X-Wing
(híbrido), el mismo valor de 32 bytes tanto si el remitente encapsula como si el destinatario
desencapsula, y salt e info son idénticos en ambos 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)Los dos salts tienen la misma forma, SHA-256(label || enc.nonce || <material KEM de la ranura> || pub_R), y difieren únicamente en la etiqueta por KEM y en qué material KEM
transportan: el efímero pub_epk de 32 bytes en la ruta clásica, el texto cifrado X-Wing
kem_ct de 1120 bytes en la ruta híbrida. Ambos se pliegan a través de un resumen SHA-256 de
longitud fija porque las entradas híbridas son demasiado grandes para un salt en bruto y una
única forma uniforme mantiene alineadas ambas rutas. La vinculación se calcula fuera del
KEM, sobre los propios bytes de transmisión de la ranura, de modo que trata X-Wing como un KEM
de caja negra y no depende de ninguna propiedad del hashing interno del combinador. La etiqueta
info diferenciada por KEM garantiza, además, que una KEK derivada bajo un KEM nunca puede
igualar a una KEK derivada bajo el otro sobre un secreto compartido idéntico de 32 bytes.
En ambos salts, pub_R es la codificación de transmisión canónica de la clave del
destinatario: exactamente la clave pública X25519 de 32 bytes para x25519, exactamente la
cadena de bytes de clave pública X-Wing fijada de 1216 bytes para mlkem768x25519. El remitente
y el destinatario DEBEN usar esa codificación exacta y NO DEBEN sustituirla por ningún
equivalente no canónico o recodificado: de lo contrario, ambos lados alimentarían salts distintos
a HKDF y derivarían KEK distintas, y la ranura nunca se abriría.
Cada KEK y su prefijo de salt son bloques de construcción internos de enc.scheme: 1: no
llevan ningún identificador de transmisión y no son seleccionables. Las dos etiquetas de
prefijo de salt y las dos etiquetas info de aquí son cuatro de los once literales de etiqueta
de la construcción sellada catalogados en Registros de algoritmos;
un verificador DEBE usar cada uno byte a byte.
Codificaciones de claves públicas de destinatarios
Un remitente de PoE sellada necesita la clave pública del destinatario en una forma de cadena portable, y un destinatario respalda su secreto en la forma correspondiente. Label 309 reutiliza las codificaciones Bech32 de destinatarios del ecosistema age, un prefijo legible (HRP) por cada mecanismo de encapsulación de claves registrado.
En Bech32, el 1 es el separador entre el HRP y la parte de datos, así que el prefijo
visible de una cadena es su HRP más ese 1. El HRP y el prefijo visible son, por tanto,
distintos, y la tabla los mantiene en columnas separadas:
KEM (enc.kem) | Clave pública | HRP de clave pública | Prefijo visible de clave pública | HRP de secreto | Prefijo visible de secreto |
|---|---|---|---|---|---|
x25519 | Clave pública X25519 de 32 bytes | age | age1… (62 caracteres) | AGE-SECRET-KEY- | AGE-SECRET-KEY-1… |
mlkem768x25519 | Clave pública X-Wing de 1216 bytes | age1pqc | age1pqc1… (1960 caracteres) | AGE-SECRET-KEY-PQ- | AGE-SECRET-KEY-PQ-1… |
La cadena de destinatario clásica x25519 tiene HRP age y la forma estándar age1… de
age v1. La clave pública híbrida concatena una clave de encapsulación ML-KEM-768 (1184 bytes)
con una clave pública X25519 (32 bytes); con sus 1216 bytes, su cadena de destinatario
age1pqc1… tiene 1960 caracteres.
El secreto que una implementación respalda e importa es, en ambas rutas, la semilla de
32 bytes: la semilla secreta X25519 bajo AGE-SECRET-KEY-, y la semilla de clave de
desencapsulación X-Wing (la tercera salida HKDF de más arriba, info = "cardano-poe-mlkem768x25519-v1") bajo AGE-SECRET-KEY-PQ-. La clave pública híbrida de
1216 bytes se deriva de esa semilla; la semilla compacta, nunca la clave expandida, es el
secreto canónico que se almacena.
BIP-173 limita una cadena
Bech32 a 90 caracteres, pero ese límite existe para direcciones de pago escritas
manualmente y no aplica aquí. Una implementación DEBE codificar y decodificar la
cadena age1pqc1… sin imponer el límite de 90 caracteres, aplicando al mismo tiempo las
reglas de suma de verificación y conjunto de caracteres de Bech32. El HRP distinto age1pqc
evita que el destinatario híbrido colisione con cualquier destinatario clásico age, y
deliberadamente no es age1pq, el prefijo más corto que una codificación nativa upstream
de ML-KEM-768 + X25519 ya reclama para la misma primitiva, de modo que las dos codificaciones
de destinatario nunca colisionan en la transmisión. La codificación clásica permanece dentro
de las longitudes habituales y se gestiona sin cambios.
Estas cadenas son únicamente comodidades para el descubrimiento de destinatarios. Una
clave pública de destinatario nunca aparece en el sobre de cifrado de un registro de
Label 309: una entrada enc.slots[] lleva material de clave por ranura y un valor wrap,
y el identificador KEM aparece una sola vez en enc.kem. La construcción del sobre y las
ranuras se describe en PoE sellada.
La clave pública Ed25519 como kid de firma
La clave pública Ed25519 no desempeña ninguna función de destinatario; es el identificador
de clave que un verificador utiliza para resolver una firma. Cuando un productor firma un
registro, la clave pública Ed25519 en bruto de 32 bytes es el kid (etiqueta 4) en la
cabecera protegida COSE_Sign1, conforme a RFC 9052.
Un verificador lee ese valor de 32 bytes directamente de la firma en cadena y coteja el
cuerpo del registro con él: la clave pública viaja junto con la firma, por lo que no se
requiere ninguna consulta adicional para verificar la autoría. La construcción completa de
firma, la carga útil firmada y las reglas de verificación se especifican en
Firmas.
Intercambio de claves fuera de banda
Label 309 especifica cómo se codifican las claves públicas de los destinatarios, no cómo
se descubren. El estándar no prescribe ningún directorio, ningún registro de algoritmos
ni ningún formato de anuncio en cadena para las claves de destinatarios. Una parte que desee
recibir una carga útil sellada publica su cadena age1… o age1pqc1… a través del canal
que ambas partes ya conozcan y en el que confíen (una entrega en persona, un registro
firmado con su propia clave Ed25519, un registro en una ubicación web estable o direccionada
por contenido), y el remitente es responsable de la procedencia de cualquier clave a la que
cifre.
Este es un límite deliberado. La misma propiedad que permite verificar un registro sin confiar en un servidor implica que el intercambio de claves no debe reintroducir subrepticiamente a un intermediario de confianza. Un nombre colocado junto a una clave es una afirmación de quien lo colocó, nunca una afirmación criptográfica: dos partes que usen el mismo identificador siguen produciendo claves con bytes diferentes, y el verificador compara los bytes. Asociar nombres legibles por personas con claves es algo que una aplicación construida sobre Label 309 PUEDE ofrecer, pero es una función de la aplicación, externa al protocolo.
Páginas relacionadas
- Firmas: cómo la clave Ed25519 firma un registro y cómo se verifica
el
kid. - PoE sellada: cómo las claves públicas X25519 y X-Wing dirigen una carga útil cifrada a destinatarios específicos.
- Registros de algoritmos: los identificadores nombrados para firmas, KEMs, AEADs y KDFs referenciados aquí.
Registros de algoritmos
Los registros de identificadores nombrados para hashes, AEAD, KEM, KDF y firmas, junto con la regla de agilidad que convierte la migración poscuántica en algo aditivo en lugar de disruptivo.
Firmas
El array opcional `sigs` a nivel de registro: un COSE_Sign1 desvinculado sobre el cuerpo completo del registro, su carga útil firmada con separación de dominio, las dos rutas para la clave del firmante y la verificación estricta de Ed25519.