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.
Un registro Label 309 PUEDE incluir una o más firmas de autoría en un array
opcional sigs de nivel superior. Cada entrada es un
COSE_Sign1 desvinculado (RFC 9052) sobre el
cuerpo del registro, que certifica que una determinada clave avala el registro. La
autoría es siempre opcional: el estándar nunca exige una firma, y un registro
que no incluya el campo sigs constituye una Prueba de Existencia (PoE) completa y
totalmente verificable.
Una firma es aditiva: responde «y esta clave lo avala» sobre la afirmación de la marca de tiempo, nunca en sustitución de ella. El hash del contenido es la afirmación principal; una firma es metadatos sobre quién respalda dicha afirmación. Es fundamental tener en cuenta que una firma que el verificador no puede comprobar (un algoritmo no admitido, una clave que no se puede resolver) nunca invalida la afirmación de contenido ni de marca de tiempo. Las firmas fallan de forma blanda; la existencia no.
Esta página define qué cubre una firma, los bytes exactos que se firman, las dos
formas en que se incluye la clave pública del firmante y la verificación estricta
que realiza un verificador público. La propia clave Ed25519 se define en
Claves; el campo sigs tal como viaja en cadena (donde cose_sign1 y
cose_key son cada uno una única cadena de bytes CBOR) se define en
El registro.
Qué cubre una firma
Una única entrada sigs[i] certifica el cuerpo completo del registro, de forma
uniforme. No existe granularidad de firma por elemento, por URI ni por campo: una
única firma se compromete con cada elemento, cada URI de almacenamiento, cada
envoltorio de cifrado, el puntero supersedes si está presente y cada clave de
extensión que incluya el registro. Un intermediario no puede agregar, eliminar ni
reescribir ninguno de ellos con posterioridad sin romper la firma.
El cuerpo firmado es el mapa del registro con el campo sigs eliminado:
remove_keys(record_map, ["sigs"]), denominado aquí record_body. El array sigs
queda excluido de lo que firma cada entrada porque una firma no puede cubrirse a sí
misma, y porque cada firmante se compromete únicamente con la afirmación, no con la
lista de cofirmantes. Concretamente, cada entrada firma {v, items?, merkle?, supersedes?, crit?, <extensions?>} (los mismos bytes record_body para cada
entrada), pero ninguna entrada firma las demás entradas de sigs. Por tanto, un
firmante certifica que el cuerpo que firmó es el cuerpo al que está vinculada cada
otra entrada; ningún firmante certifica quiénes son los demás cofirmantes.
El alcance de la firma es el cuerpo del registro, no la transacción
Una firma verificada demuestra que una clave produjo una firma sobre el cuerpo del registro. No demuestra que esa misma clave enviara la transacción portadora, pagara su comisión ni eligiera el tiempo del bloque. Un cuerpo de registro idéntico PUEDE ser republicado por cualquier parte en una transacción posterior: esto es portabilidad intencional del registro. Presente una firma verificada como «firmado por <clave>», nunca como «<clave> envió esto» ni «publicado por <clave> en <tiempo>».
La carga útil firmada
Cada entrada contiene un COSE_Sign1 desvinculado, por lo que el campo de carga útil de COSE está vacío y el verificador reconstruye los bytes que realmente se firman a partir del registro en cadena. El firmante 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 se serializa como CBOR canónico conforme a la
RFC 8949 §4.2.1: la misma
codificación determinista que utiliza todo el registro. El determinismo es lo que
hace que una firma sea interoperable: dos implementaciones que codifiquen el mismo
cuerpo lógico producen record_body_bytes idénticos byte a byte, de modo que una
firma producida por una se verifica con la otra.
El prefijo de separación de dominio
to_sign es la cadena UTF-8 de 25 bytes cardano-poe-record-sig-v1 antepuesta a
record_body_bytes. El prefijo vincula la firma a su función en Label 309 y evita
la reutilización entre protocolos. Un futuro esquema de metadatos de Cardano que
por casualidad compartiera la forma CBOR del cuerpo (mismas claves, mismos tipos)
no podría reutilizar una firma Label 309 contra sí mismo: su to_sign llevaría un
prefijo diferente, o ninguno, por lo que la secuencia de bytes firmados sería
distinta y la firma fallaría. Las implementaciones DEBEN incluir esta secuencia
literal de bytes como los bytes iniciales de to_sign de forma exacta; firmar
únicamente el CBOR canónico sin prefijo no es conforme.
Por qué external_aad está vacío
Label 309 coloca el separador de dominio dentro de to_sign, no en el
external_aad de COSE. El espacio external_aad (Sig_structure[2]) es
siempre la cadena de bytes vacía h''. Esta es una desviación deliberada y
documentada del patrón habitual de COSE de colocar una cadena de dominio en
external_aad, y la razón es la interoperabilidad con billeteras:
CIP-30
signData (la ruta estándar de firma con billetera en Cardano) estipula que no
se utiliza ningún external_aad y no ofrece a una dApp ningún modo de suministrar
uno. Un external_aad no vacío haría que toda firma producida por una billetera
fallara. Incorporar el prefijo en la carga útil preserva la misma propiedad de
protección contra la reutilización mientras mantiene idénticos, byte a byte, los
bytes producidos por la billetera y los recalculados por el verificador.
La Sig_structure
Sig_structure es el array de firma COSE_Sign1 de 4 elementos definido en
RFC 9052 §4.4:
| Espacio | Valor | Notas |
|---|---|---|
[0] | "Signature1" | Identificador de contexto COSE fijo, emitido como cadena de texto CBOR completa (11 bytes), nunca como UTF-8 sin procesar. |
[1] | protected | Los bytes de la cabecera protegida del firmante, envueltos en bstr y en CBOR canónico, utilizados literalmente: el verificador nunca los vuelve a canonicalizar. |
[2] | external_aad | Siempre h'' (bstr de longitud cero). |
[3] | to_sign | El prefijo de 25 bytes concatenado con record_body_bytes. |
El COSE_Sign1 publicado lleva su campo de carga útil (COSE_Sign1[2]) como CBOR
null (0xF6): la forma desvinculada. Se rechaza una carga útil adjunta,
incluida una cadena de bytes de longitud cero. Desvincular la carga útil es lo que
vincula los bytes firmados al cuerpo del registro que el verificador recalcula de
forma independiente; una forma adjunta permitiría a un productor firmar bytes
tomados prestados que no guardan ninguna relación con las afirmaciones en cadena.
Modo hash de billetera de hardware
CIP-30 / CIP-8
definen una marca opcional "hashed": true en la cabecera no protegida que puede establecer un
cofirmante de hardware con recursos limitados. Cuando está presente y es verdadera,
Sig_structure[3] es el resumen Blake2b-224(to_sign) de 28 bytes en lugar del propio to_sign;
los otros tres espacios permanecen sin cambios. Un verificador DEBE inspeccionar la cabecera
no protegida y realizar esta sustitución antes de la verificación estricta de Ed25519. Los
productores de software y SDK NO DEBERÍAN establecerla: no ahorra bytes en cadena y complica
las rutas de código del verificador.
Algoritmo de firma
El único algoritmo de firma en v1 es EdDSA sobre Ed25519
(RFC 8032), identificado por COSE como
alg = -8 (RFC 9053 §2.2),
que reside en la cabecera protegida del COSE_Sign1. La base obligatoria de un
verificador v1 es {-8}; PUEDE aceptar adicionalmente -19 (Ed25519,
completamente especificado) y verificar ambos puntos de código bajo la misma
primitiva Ed25519. El registro de algoritmos es extensible: las revisiones futuras
agregan firmas poscuánticas de forma aditiva, nunca como un cambio disruptivo.
Resolución de la clave del firmante
Un verificador público debe resolver la clave pública del firmante sin contactar ningún servicio, por lo que cada firma incluye su clave, o una referencia inequívoca dentro de la firma, en cadena. En v1 existen exactamente dos formas de transporte, y son mutuamente excluyentes dentro de una única entrada: una entrada que use ambas es un error estructural.
Ruta 1: firma de identidad (kid dentro de la firma)
La clave pública Ed25519 de 32 bytes en bruto se coloca en la etiqueta de cabecera
COSE 4 (kid,
RFC 9052 §3.1) dentro de la
cabecera protegida del COSE_Sign1. La entrada no lleva ningún campo cose_key.
Por convención de Label 309, un kid de cabecera protegida de exactamente 32 bytes
es la clave pública, no un puntero opaco a una clave que deba consultarse fuera
de banda. La longitud de 32 bytes es un discriminador inequívoco: las claves
públicas Ed25519 son siempre de 32 bytes. Colocar la clave en la cabecera protegida
(no en la no protegida) la vincula a la firma; un adversario que la reescribiera
rompería la verificación.
Esta convención es una desviación deliberada y documentada de la lectura de
identificador opaco de kid en RFC 9052; es lo que hace que la ruta de identidad
sea independiente del servicio, sin necesidad de ningún directorio de claves. El
modelo de claves se define en Claves.
Ruta 2: firma de billetera (cose_key en línea)
Una firma signData de CIP-30 devuelve la clave pública del firmante como un blob
cbor<COSE_Key> separado, no dentro del COSE_Sign1. Un productor que encadene
dicha firma en un registro DEBE colocar ese COSE_Key en la misma entrada
sigs[i] bajo la clave cose_key, como una única cadena de bytes CBOR. El
verificador lo decodifica como un COSE_Key y lee la clave pública Ed25519 de la
etiqueta -2. El COSE_Key
DEBE describir únicamente la mitad pública (kty = OKP (1), crv = Ed25519 (6), los 32 bytes de x en la etiqueta -2) y NO DEBE incluir material de
clave privada (etiqueta -4 y similares); publicar un escalar privado en un libro
contable permanente constituye una filtración irrecuperable de la clave.
Exclusión mutua
Las dos rutas se excluyen a nivel de los bytes. Una entrada lleva o bien un
kid de cabecera protegida de 32 bytes y ningún cose_key (ruta 1), o
bien un campo cose_key y ningún kid de cabecera protegida de 32 bytes
(ruta 2), nunca ambos. Una entrada que incluya los dos es rechazada; el
verificador no necesita hacer ninguna distinción en tiempo de verificación. La
resolución es, por tanto, una distinción a nivel de los bytes, no una precedencia
ordenada:
| Ruta | Condición | Clave del firmante |
|---|---|---|
| 1 | kid protegido de 32 bytes, sin cose_key | El valor kid de 32 bytes, usado directamente. |
| 2 | cose_key presente, sin kid de 32 bytes | La clave Ed25519 en la etiqueta -2 del COSE_Key. |
Un kid incluido únicamente en la cabecera no protegida no constituye una
ruta de resolución válida: está fuera del envoltorio firmado, por lo que un
intermediario podría reescribirlo sin romper la firma. Un verificador DEBE
ignorar los valores kid de la cabecera no protegida para la resolución. Si
ninguna ruta permitida produce una clave Ed25519 de 32 bytes, la entrada se reporta
como no resuelta y no aporta ninguna afirmación de autoría.
Verificación
Un verificador público comprueba cada sigs[i] de forma independiente, en este
orden:
- Decodificar. Analizar la cadena de bytes
sigs[i].cose_sign1como un COSE_Sign1. El campo de carga útil DEBE sernull(desvinculado); cualquier carga útil no nula o no vacía está malformada. - Algoritmo. Leer el
algde la cabecera protegida. Si está fuera del conjunto admitido por el verificador, la entrada es no admitida (véase más adelante), no un error en el registro. - Resolver la clave. Aplicar la discriminación entre ruta 1 y ruta 2 descrita anteriormente para obtener la clave pública Ed25519 de 32 bytes. Si ninguna ruta produce una, la entrada es no resuelta.
- Reconstruir y verificar. Reconstruir
to_signySig_structure = ["Signature1", protected, h'', to_sign], codificarlo en CBOR canónico y verificar la firma con Ed25519 estricto. (SustituirBlake2b-224(to_sign)porto_signprimero si la cabecera no protegida incluye"hashed": true.) - Vinculación con billetera (solo ruta 2). Recalcular la dirección de stake a
partir de la clave resuelta y compararla byte a byte con la
addressde la cabecera protegida; una discrepancia hace fallar la vinculación aunque la propia firma Ed25519 se haya verificado. Esta comprobación, exclusiva de la ruta 2, es lo que permite a una interfaz de usuario presentar un registro como vinculado a una billetera; las entradas de ruta 1 la omiten.
Ed25519 estricto
La verificación sigue las reglas estrictas de RFC 8032 §5.1.7: existe exactamente una respuesta aceptable para cualquier combinación de clave, mensaje y firma dada:
- Las codificaciones no canónicas de
Ro del escalar de firmaS(en particular cualquierS ≥ ℓ, el orden del grupo) DEBEN ser rechazadas. - Las claves públicas y los valores
Rde orden pequeño, subgrupo pequeño o con componente de torsión DEBEN ser rechazados. - La ecuación de verificación con cofactor (la forma ZIP-215 / compatible con procesamiento por lotes) NO DEBE sustituirse por la ecuación estricta.
La estrictez es lo que hace que el veredicto sea reproducible entre implementaciones: un verificador con cofactor aceptaría firmas que uno estricto rechazaría, de modo que dos verificadores conformes discreparían. Las implementaciones deben seleccionar una biblioteca (o un modo de biblioteca) que realice una verificación estricta sin cofactor.
Semántica del veredicto
Las firmas son aditivas, por lo que una firma no verificable se reporta sobre la
entrada, no se convierte en un fallo a nivel de registro. Cada sigs[i] se
resuelve en uno de estos resultados tipados por entrada; el catálogo completo de
errores y las reglas del veredicto a nivel de registro se encuentran en
Verificación:
| Resultado | Significado |
|---|---|
| verificada | Ed25519 estricto (y, para la ruta 2, la vinculación de dirección) se ha superado. |
| firma no admitida | El alg de la cabecera protegida está fuera del conjunto del verificador. Informativo, nunca un error. |
| clave del firmante no resuelta | Ninguna ruta permitida produce una clave pública Ed25519 de 32 bytes. |
| firma inválida | Ed25519 estricto ha devuelto false sobre la Sig_structure reconstruida. |
| discrepancia de dirección de billetera | Ruta 2: la firma se verificó, pero la dirección de stake recalculada ≠ la declarada. |
Una firma no admitida nunca invalida la prueba
Un algoritmo de firma no reconocido o no admitido produce un resultado tipado de
firma-no-admitida con nivel de severidad informativo. La afirmación de contenido y de marca de
tiempo (el compromiso hashes en cadena) es estructuralmente válida independientemente de los
algoritmos de firma que implemente el verificador. Un registro que contenga únicamente firmas con
algoritmos futuros seguirá presentándose como una Prueba de Existencia válida, con cada una de
esas entradas etiquetadas como no admitidas. Las firmas son aditivas; la existencia no depende de
ellas.
Páginas relacionadas
- Claves: la clave de firma Ed25519, su derivación y la clave
pública de 32 bytes incluida en el
kidde la ruta 1. - El registro: el campo
sigsde nivel superior, el mapa cerradosig-entry(cose_sign1/cose_key, cada uno una única cadena de bytes) y el transporte del cuerpo completo. - Verificación: los códigos de resultado por entrada, las reglas del veredicto a nivel de registro y la canalización completa de validación.
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.
PoE sellada
El sobre de cifrado de Label 309: cómo un remitente sella el contenido para una o varias claves de destinatario mientras la cadena solo transporta el hash del texto plano y las ranuras de clave envueltas, nunca el texto plano y nunca los destinatarios.