Esta es una traducción a título informativo. La versión en inglés es la normativa y es la que prevalece. Leer la versión en inglés

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:

EspacioValorNotas
[0]"Signature1"Identificador de contexto COSE fijo, emitido como cadena de texto CBOR completa (11 bytes), nunca como UTF-8 sin procesar.
[1]protectedLos 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_aadSiempre h'' (bstr de longitud cero).
[3]to_signEl 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:

RutaCondiciónClave del firmante
1kid protegido de 32 bytes, sin cose_keyEl valor kid de 32 bytes, usado directamente.
2cose_key presente, sin kid de 32 bytesLa 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:

  1. Decodificar. Analizar la cadena de bytes sigs[i].cose_sign1 como un COSE_Sign1. El campo de carga útil DEBE ser null (desvinculado); cualquier carga útil no nula o no vacía está malformada.
  2. Algoritmo. Leer el alg de 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.
  3. 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.
  4. Reconstruir y verificar. Reconstruir to_sign y Sig_structure = ["Signature1", protected, h'', to_sign], codificarlo en CBOR canónico y verificar la firma con Ed25519 estricto. (Sustituir Blake2b-224(to_sign) por to_sign primero si la cabecera no protegida incluye "hashed": true.)
  5. 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 address de 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 R o del escalar de firma S (en particular cualquier S ≥ ℓ, el orden del grupo) DEBEN ser rechazadas.
  • Las claves públicas y los valores R de 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:

ResultadoSignificado
verificadaEd25519 estricto (y, para la ruta 2, la vinculación de dirección) se ha superado.
firma no admitidaEl alg de la cabecera protegida está fuera del conjunto del verificador. Informativo, nunca un error.
clave del firmante no resueltaNinguna ruta permitida produce una clave pública Ed25519 de 32 bytes.
firma inválidaEd25519 estricto ha devuelto false sobre la Sig_structure reconstruida.
discrepancia de dirección de billeteraRuta 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 kid de la ruta 1.
  • El registro: el campo sigs de nivel superior, el mapa cerrado sig-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.