El registro
El formato de transmisión de Label 309: dónde reside el registro bajo la etiqueta de metadatos 309, la forma de su mapa, las reglas de CBOR canónico, el fragmentado para el transporte y el esquema CDDL.
Un registro de Label 309 es un único mapa CBOR transportado en los metadatos de una transacción de Cardano bajo la etiqueta 309. El mapa compromete uno o varios hashes de contenido con la cadena; el tiempo del bloque de la transacción es el testigo de que esos bytes existían a más tardar en ese momento. Todo lo demás que el registro puede transportar (URIs de almacenamiento, un sobre de cifrado, firmas de autoría, un puntero de sustitución) son metadatos opcionales sobre esa afirmación central.
Esta página define la forma sobre el cable: dónde se ubica el registro, cómo se codifica, cómo se transportan los valores sobredimensionados y el esquema cerrado contra el que verifica un validador estructural. Las construcciones criptográficas que aquí se mencionan (algoritmos de hash, el sobre sellado, las firmas) tienen sus propias páginas; esta describe el formato de transmisión.
Dónde reside el registro
Un registro PoE DEBE colocarse bajo la etiqueta de metadatos de transacción
309, reservada como «Proof of Existence record» en el
registro de etiquetas de metadatos de CIP-10.
Los metadatos de transacción son un mapa de etiqueta entera a valor, de modo que una
transacción NO DEBE transportar más de un registro PoE: exactamente un registro
por transacción.
Una transacción PUEDE transportar metadatos adicionales bajo otras etiquetas (por
ejemplo, un mensaje
CIP-20
674). Un verificador que procesa PoE DEBE ignorar toda etiqueta distinta de
309.
En el ledger de la era Conway, los metadatos de transacción son el campo metadata
dentro de los auxiliary_data de la transacción. Los valores admitidos bajo
cualquier etiqueta se restringen al tipo recursivo metadatum del ledger: enteros,
cadenas de bytes, cadenas de texto, arrays y mapas, con tanto las cadenas de bytes
como las de texto limitadas a 64 bytes cada una:
metadatum =
{ * metadatum => metadatum }
/ [ * metadatum ]
/ int
/ bstr .size (0..64)
/ tstr .size (0..64)Una transacción que transporte cualquier bstr o tstr individual de más de 64
bytes es rechazada por los nodos de Cardano en el momento del envío, antes de que
ningún verificador la vea. Ese límite es la razón por la que Label 309 define una
disciplina de fragmentado para el transporte (más abajo); cada campo que el registro
transporta, base o de extensión, debe reducirse a un metadatum.
Transporte: el array de fragmentos del cuerpo completo
El cuerpo serializado de un registro supera de forma habitual los 64 bytes, así que no
puede almacenarse bajo la etiqueta 309 como un valor suelto. Por ello, el cuerpo del
registro se transporta como un array opaco de fragmentos del cuerpo completo: un
único array CBOR de cadenas de bytes de ≤ 64 bytes (bstr .size (1..64)) cuya
concatenación en orden es el cuerpo del registro. Esta división para el transporte
es el único fragmentado que realiza Label 309: el único paso del formato que el
ledger fuerza de veras.
Como el ledger solo ve este array de transporte y nunca los campos del cuerpo
reensamblado, esos campos son valores CBOR ordinarios sin envoltorios de fragmento
por campo y sin límite de 64 bytes a nivel de campo: una URI de almacenamiento es una
única cadena de texto, un COSE_Sign1 es una única cadena de bytes y un kem_ct de
X-Wing es una única cadena de bytes de 1120 bytes. Un campo de más de 64 bytes
simplemente atraviesa los límites entre fragmentos del array del cuerpo completo, igual
que cualquier otro tramo del cuerpo.
Un productor DEBE serializar el cuerpo del registro una sola vez a CBOR canónico, dividir esa cadena de bytes en fragmentos de 1 a 64 bytes y almacenar el array resultante (de longitud definida, con cadenas de bytes de longitud definida) como el valor de la etiqueta 309. La forma de array es siempre obligatoria, incluso para un cuerpo de 64 bytes o menos: tal cuerpo es un array de longitud 1, nunca un mapa ni una cadena de bytes suelta. Los productores DEBERÍAN usar la división mínima (cada fragmento, salvo el último, de exactamente 64 bytes) y NO DEBERÍAN emitir fragmentos de longitud cero; fragmentar en exceso desperdicia bytes de transacción sin beneficio alguno.
Un verificador DEBE concatenar byte a byte los elementos del array en orden para
reensamblar el cuerpo del registro antes de la validación estructural, y DEBE
rechazar cualquier valor de la etiqueta 309 que no sea tal array. Los límites entre
fragmentos no tienen ningún significado semántico: dos arrays de transporte cuyas
concatenaciones son idénticas byte a byte denotan el mismo registro. La taxonomía de
errores de transporte fija los códigos de rechazo: un fragmento de más de 64 bytes es
CHUNK_TOO_LARGE; un elemento de array que no sea una cadena de bytes, un array o
elemento de longitud indefinida, o un valor de la etiqueta 309 que no sea un array (un
mapa suelto, una cadena de bytes suelta, un entero) es MALFORMED_CBOR. Un fragmento de
longitud cero no aporta bytes y se tolera; nunca se rechaza por sí solo.
El esquema describe el cuerpo reensamblado
Todo lo que sigue (el mapa del registro, el CDDL, las reglas de los campos) describe el cuerpo del registro tras el reensamblado de los fragmentos. El array de fragmentos del cuerpo completo no forma parte del esquema; primero se deshace y luego se valida el cuerpo.
El mapa del registro
El cuerpo del registro reensamblado es un mapa CBOR. Los campos de valor entero son del tipo mayor CBOR 0/1; los campos de texto son del tipo mayor 3 y DEBEN ser UTF-8 válido; los campos de bytes son del tipo mayor 2; los arrays son del tipo mayor 4; los mapas anidados son del tipo mayor 5. Un campo opcional presente NO DEBE llevar un valor vacío.
La forma de nivel superior es:
| Clave | Tipo | Estado | Significado |
|---|---|---|---|
v | uint | OBLIGATORIO | Versión del esquema; este documento define v = 1. |
items | array de mapas de ítem | OPCIONAL | Compromisos por contenido: véase Contenido y hashing. |
merkle | array de compromisos | OPCIONAL | Compromisos de lista que vinculan listas de hojas fuera de cadena a una sola raíz. |
supersedes | bytes (32) | OPCIONAL | Hash de transacción de un registro anterior que este reemplaza. |
sigs | array de mapas de firma | OPCIONAL | Firmas de autoría a nivel de registro: véase Firmas. |
crit | array de cadenas de texto | OPCIONAL | Claves de extensión que es obligatorio comprender. |
Un registro conforme DEBE comprometerse a al menos uno de items (con ≥ 1 entrada)
o merkle (con ≥ 1 entrada). Un registro que no transporta ninguno (o que transporta
uno de ellos como array vacío) se rechaza por ser un registro vacío. Salvo por esta
regla, items y merkle son ortogonales: un registro puede transportar cualquiera de
los dos por separado o ambos a la vez.
Label 309 no impone ningún límite numérico al número de entradas. El único techo es el tamaño máximo de transacción de Cardano en vigor, y los productores pagan tarifas por byte que acotan de forma natural el tamaño del registro. Un validador NO DEBE rechazar un registro únicamente porque transporte muchas entradas, siempre que quepa bajo el límite de tamaño del ledger.
El campo de versión
v es un entero sin signo CBOR, no una cadena de versión semántica. Este documento
define exactamente v = 1. Un validador DEBE rechazar con un error tipado un
registro cuyo v quede fuera de su conjunto admitido; NO DEBE entrar en pánico,
abortar ni tratar silenciosamente el registro como si fuera un esquema de metadatos
distinto. El entero v solo cambia cuando una modificación haría que un analizador de
v1 malinterpretara el registro: las extensiones aditivas y con espacio de nombres propio
no lo cambian.
Items
Cada entrada de items es un mapa CBOR con un campo obligatorio y dos opcionales:
hashes: OBLIGATORIO, un mapa no vacío de identificador de algoritmo de hash a resumen sin procesar de 32 bytes. Al menos una entrada; los algoritmos duplicados son imposibles porque las claves de un mapa CBOR son únicas. Véase Contenido y hashing.uris: OPCIONAL, una lista plural de URIs de descubrimiento (reglas más abajo).enc: OPCIONAL, el sobre de cifrado de un ítem sellado. Véase PoE sellada.
No hay ranura de firma por ítem. La autoría se expresa únicamente a nivel de registro,
mediante una entrada de sigs[] que cubre todos los ítems de manera uniforme.
Compromisos Merkle
Cada entrada de merkle vincula el registro a una lista ordenada de hojas de 32 bytes
mediante una construcción canónica de árbol de hashes, de modo que una sola raíz de 32
bytes en la cadena puede representar una lista de hojas fuera de cadena de tamaño
arbitrario. Un compromiso es un mapa cerrado:
| Campo | Tipo | Estado | Significado |
|---|---|---|---|
alg | tstr | OBLIGATORIO | Identificador registrado del algoritmo de compromiso de lista. |
root | bytes (32) | OBLIGATORIO | Raíz canónica sobre la lista ordenada de hojas del productor. |
leaf_count | uint | OBLIGATORIO | Número de hojas comprometidas; vincula la raíz al tamaño de la lista. |
uris | lista de URIs | OPCIONAL | URI(s) direccionadas por contenido del archivo de la lista de hojas fuera de cadena. |
Una raíz Merkle se compromete con una estructura de lista de hojas, mientras que una
entrada de hashes se compromete con bytes de texto plano; ambas se verifican de forma
distinta (prueba de inclusión frente a recálculo del texto plano), y por eso los
compromisos de lista residen en el nivel superior y no dentro de un ítem. El registro de
algoritmos de compromiso de lista es disjunto del registro de algoritmos de hash de
contenido: véase Registros de algoritmos.
Supersedes
supersedes es un hash de transacción de Cardano opcional de 32 bytes que apunta a un
registro de Label 309 anterior. Es un enlace de solo anexado e independiente del
servicio: un registro posterior puede apuntar a uno anterior sin base de datos fuera de
cadena ni identificador de registro de un proveedor.
La sustitución no elimina, revoca ni invalida el registro anterior: la cadena es de solo anexado, y los verificadores DEBEN seguir tratando el registro anterior como existente y verificable de forma independiente. El puntero no lleva ningún campo de motivo ni de texto libre; cualquier significado humano (corrección, reemplazo, retirada) pertenece al nuevo contenido, no a la etiqueta 309. Un verificador que resuelve el puntero DEBE buscarlo en la misma red de Cardano que la transacción contenedora; el campo no lleva discriminador de red porque un hash de transacción solo es único dentro de su propia red.
Firmas
sigs es un array opcional de entradas de firma a nivel de registro. Cada entrada lleva
una estructura COSE_Sign1 desacoplada sobre el
cuerpo del registro (es decir, el mapa completo del registro con sigs eliminado) y,
opcionalmente, la clave pública del firmante para la vía de firma con billetera. Una
sola firma da fe del cuerpo entero: cada ítem, cada URI, cada sobre, el puntero de
sustitución si está presente y cualesquiera claves de extensión. Las firmas son siempre
opcionales, y un algoritmo de firma no reconocido nunca invalida la afirmación sobre el
contenido. La carga útil firmada, el prefijo de separación de dominio, la resolución de
la clave del firmante y las reglas estrictas de verificación se especifican en
Firmas.
Reglas de las URIs
Cuando está presente, uris es una lista no vacía; cada entrada es una única cadena de
texto CBOR que lleva exactamente una URI. No hay límite de longitud por URI ni forma
envolvente: el transporte del cuerpo completo ya satisface el límite de 64 bytes por
cadena del ledger, de modo que una URI ipfs://<CIDv1>/<path> larga es una cadena de
texto como cualquier otra. Cada URI DEBE ser absoluta, DEBE incluir un esquema y
una parte jerárquica, y NO DEBE contener un identificador de fragmento: una PoE es
una afirmación sobre los bytes del contenido, no sobre un subcomponente de un documento.
El conjunto de esquemas de v1 es cerrado y direccionado por contenido:
| Esquema | Notas |
|---|---|
ar:// | Id de transacción de Arweave (base64url de 43 caracteres). Forma ar://<txid>. |
ipfs:// | CID de IPFS, preferiblemente CIDv1. Forma ipfs://<cid> o ipfs://<cid>/<path>. |
Los productores NO DEBEN emitir ningún otro esquema: https://, http://,
file://, data: y los demás se rechazan todos. La restricción es deliberada, no
temporal: una URI direccionada por contenido vincula los bytes obtenidos a la propia URI
a través del modelo de integridad de la capa de almacenamiento (un CID de IPFS es un
multihash del contenido; un id de transacción de Arweave se compromete con los datos bajo
el consenso de Arweave), de modo que un verificador puede confirmar que «los bytes que
obtuve son los bytes con los que se comprometió el productor» sin confiar en DNS, TLS,
pasarelas ni autoridades de certificación. Un esquema fuera del conjunto hace que un
registro sea estructuralmente inválido; nunca se valida como valid.
uris es opcional en todo momento. Un registro de solo hash, con uris omitido, es una
afirmación completa: la existencia del contenido se afirma sin comprometerse con un canal
de recuperación. El perfil exacto de CID (prefijos multibase aceptados, códecs y
multihashes) forma parte de las reglas de verificación; véase
Verificación.
CBOR canónico
Todo registro de Label 309 DEBE codificarse como CBOR canónico según el RFC 8949 §4.2.1 (Core Deterministic Encoding). En concreto:
- Serialización preferida (forma más corta) para cada entero.
- Codificación de longitud definida para todas las cadenas de bytes, cadenas de texto, arrays y mapas.
- Sin etiquetas semánticas (este documento no requiere ninguna: una etiqueta de bignum 2/3 NO DEBE aparecer).
- Claves de mapa ordenadas en orden lexicográfico byte a byte de su codificación CBOR.
- Cadenas de texto UTF-8 sin marca de orden de bytes.
- Sin claves duplicadas en ningún mapa.
- Sin valores de coma flotante ni valores simples no triviales: un registro transporta
únicamente enteros, cadenas de bytes, cadenas de texto, arrays, mapas y (allí donde un
esquema lo admita)
true/false/null. Los flotantes de tipo mayor 7 (incluido un1.0entero), el cero negativo yundefinedDEBEN rechazarse, no coercerse.
El determinismo es lo que hace que el formato sea interoperable: dos productores que expresan el mismo registro lógico emiten bytes idénticos, de modo que una firma calculada sobre el cuerpo por una implementación se verifica bajo otra. Un validador DEBE rechazar una codificación no canónica. Los exploradores y las billeteras pueden exponer los metadatos a través de una proyección JSON, pero un verificador conforme DEBE validar el CBOR original de la transacción, nunca una recodificación JSON con pérdida de él.
Compatibilidad hacia delante
Label 309 v1 reserva un conjunto cerrado de claves base: v, items, merkle,
supersedes, sigs, crit. Un registro PUEDE transportar además claves de
extensión cuyos nombres coincidan con uno de dos espacios de nombres reservados:
^x-.+: el espacio de nombres de proveedor / experimental.^[a-z]+-.+: el espacio de nombres de especificación complementaria, donde el prefijo nombra a la especificación que lo registra.
Un validador DEBE decodificar y preservar las claves de extensión, NO DEBE
rechazar un registro únicamente porque estén presentes, y DEBE exponerlas a título
informativo sin afirmar que ha verificado su contenido. Las claves de extensión forman
parte del cuerpo firmado, así que una firma a nivel de registro las cubre: un
retransmisor no puede inyectar una clave de extensión después de producida la firma.
Cualquier clave de nivel superior desconocida que no coincida con ninguno de los
patrones (una errata como supersedess, o una variante de mayúsculas como Sigs) se
rechaza por ser un campo desconocido. La tolerancia basada en patrones preserva la
detección de erratas en el conjunto base, a la vez que mantiene abierto un grupo estable
para futuras adiciones.
Un productor que requiere que un verificador comprenda un campo no base DEBE listar
el nombre de ese campo en el array crit de nivel superior. Un verificador de v1 que se
encuentra una entrada de crit que no implementa NO DEBE informar del registro como
válido. Cada entrada de crit DEBE coincidir con el patrón de clave de extensión
(las claves base están prohibidas en crit), DEBE nombrar un campo realmente
presente en el registro y DEBE ser única, de modo que una marca de criticidad siempre
pueda rastrearse hasta un campo concreto cuya semántica el verificador está obligado a
comprender. Estas reglas siguen los precedentes de must-understand / must-ignore de
RFC 9052 §3.1
(crit de COSE) y RFC 7515 §4.1.11
(crit de JWS).
Presupuesto de bytes
El único techo absoluto del tamaño del registro es el parámetro de protocolo maxTxSize
de Cardano en vigor: 16 384 bytes en la versión mayor 10 del protocolo en mainnet, sujeto
a actualizaciones de los parámetros del ledger. Label 309 no impone ningún límite a nivel
de esquema por debajo de eso. Los registros que superan el límite son rechazados por los
nodos de Cardano en el momento del envío, así que ningún verificador llega a ver uno; un
validador NO DEBE inventar un techo específico de Label 309 por debajo de
maxTxSize.
En la práctica, la estructura no relativa a metadatos de una transacción (entradas, salidas, testigos, campos de tarifa y validez) consume unos 245 bytes, lo que deja del orden de 16 KB para el registro de la etiqueta 309. Los productores DEBERÍAN apuntar a unos cientos de bytes por debajo del límite para absorber la variación de las tarifas y DEBERÍAN calcular el tamaño del registro candidato antes del envío, fallando rápido si no fuera a caber. Las formas realistas que caben son generosas: bastante más de un centenar de ítems de un solo hash, decenas de firmas a nivel de registro o muchas ranuras de destinatario clásicas caben holgadamente en una sola transacción, y una única raíz Merkle se compromete con una lista de hojas fuera de cadena ilimitada a un coste fijo en la cadena de 32 bytes.
Esquema CDDL
El siguiente CDDL es el esquema estructural del cuerpo del registro reensamblado: los
bytes de CBOR canónico obtenidos tras concatenar el array de fragmentos de ≤ 64 bytes
almacenado bajo la etiqueta 309. El cuerpo reensamblado es CBOR determinista corriente:
no es en sí mismo un metadatum del ledger, y sus campos no están sujetos al límite
de 64 bytes por cadena, que satisface por sí solo el envoltorio de transporte del cuerpo
completo. El envoltorio no se modela aquí.
El bloque describe el superconjunto permisivo de formas bien formadas; los invariantes
entre campos (la regla de items o merkle, la exclusividad slots ⊕ passphrase del
sobre de cifrado, la pertenencia de los identificadores de algoritmo a un registro, las
reglas de forma de ranura por KEM) los impone un pase de validación tipado sobre la
estructura decodificada, no el propio CDDL.
; An extension value is any CBOR value the canonical (deterministic) encoding
; profile admits. Floats and semantic tags are excluded by that profile (they
; are rejected as MALFORMED_CBOR on decode), so the exclusion is not repeated
; here; the reassembled body carries no field-level 64-byte cap.
extension-value =
{ * extension-value => extension-value }
/ [ * extension-value ]
/ int
/ bstr
/ tstr
/ bool
/ null
; A conformant record MUST carry at least one of `items` (>= 1 entry) or
; `merkle` (>= 1 entry); a record with both absent (or both empty) is rejected
; as SCHEMA_EMPTY_RECORD by the typed pass, not at the CDDL layer.
poe-record = {
poe-common,
? "items": [ 1* item-entry ],
? "crit": [ 1* tstr ],
* extension-key => extension-value
}
poe-common = (
"v": 1,
? "merkle": [ 1* merkle-commit ],
? "supersedes": bytes32,
? "sigs": [ 1* sig-entry ],
)
extension-key = tstr .regexp "^x-.+"
/ tstr .regexp "^[a-z]+-.+"
item-entry = {
"hashes": hash-map,
? "uris": [ 1* uri ],
? "enc": enc,
}
; A non-empty CBOR map keyed by a content-hash algorithm identifier with the
; 32-byte digest as value. Map-key uniqueness makes duplicate algorithms
; structurally impossible.
hash-map = { + content-hash-alg => bytes32 }
; A list commitment binds the record to an ordered leaf list. `leaf_count`
; binds the on-chain commitment to the off-chain list size.
merkle-commit = {
"alg": merkle-commit-alg,
"root": bytes32,
"leaf_count": uint32,
? "uris": [ 1* uri ],
}
; `enc` is a choice between the scheme-1 envelope shape and a bounded opaque
; envelope (the degrade-to-opaque rule for an unsupported scheme/kem/aead). The
; typed pass enforces the slots/passphrase exclusivity and the per-KEM
; slot-shape rules over a supported envelope.
enc = enc-scheme-1 / enc-opaque
; `scheme: 1` is not a version counter for the `enc` map alone: it names the
; ENTIRE sealed cryptographic suite — the canonicalEncode rules, the slot
; schema, the HKDF and HMAC hashes, the wrap AEAD, the segmented-STREAM content
; format, the transcript schemas, the in-ciphertext passphrase commitment, the
; pinned X-Wing revision, every domain-separation label, and the Argon2id and
; passphrase-normalization profiles. Changing any one of them requires a new
; `scheme` value; see Sealed PoE for the construction it pins.
enc-scheme-1 = {
"scheme": 1,
"aead": aead-alg,
"nonce": bstr,
? "kem": kem-alg,
? "slots": [ 1* slot ],
? "slots_mac": bytes32,
? "passphrase": passphrase-block,
}
; The opaque reading of an envelope under an unsupported identifier: `scheme`
; is the only structurally required key, and every other entry is any key/value
; pair the canonical profile admits, subject to the generic decode bounds.
enc-opaque = {
"scheme": uint,
* tstr => extension-value
}
slot = classical-slot / hybrid-slot
; enc.kem = "x25519": the per-slot X25519 ephemeral public key + wrapped CEK.
classical-slot = {
"epk": bytes32,
"wrap": bytes48,
}
; enc.kem = "mlkem768x25519": the 1120-byte X-Wing ciphertext plus the wrapped
; CEK. There is NO `epk` — the X25519 ephemeral is the trailing 32 bytes of the
; X-Wing ciphertext inside `kem_ct`.
hybrid-slot = {
"kem_ct": bstr .size 1120,
"wrap": bytes48,
}
passphrase-block = {
"alg": kdf-alg,
"salt": bstr .size (16..64),
"params": { "m": uint32, "t": uint32, "p": uint32 },
}
; A signature entry is a closed map. `cose_sign1` is REQUIRED and carries the
; CBOR-encoded COSE_Sign1 as a single byte string; `cose_key` is OPTIONAL and
; carries the CBOR-encoded COSE_Key for the wallet-signing path as a single
; byte string.
sig-entry = {
"cose_sign1": bstr,
? "cose_key": bstr,
}
; A uri is one absolute URI in a single text string. The URI shape rules
; (absolute, no fragment, closed scheme set {ar://, ipfs://}) are enforced in
; the typed pass; the rule carries no length cap.
uri = tstr
bytes32 = bstr .size 32
bytes48 = bstr .size 48
; uint32 is the pinned range of every numeric field: an unsigned integer
; representable in 4 bytes (0 .. 2^32-1), handled as an exact integer.
uint32 = uint .size 4
; Algorithm-identifier strings are open `tstr`: the registries are
; authoritative for accepted values, and the typed pass emits the precise
; unsupported-algorithm code for any unrecognised identifier.
content-hash-alg = tstr ; e.g. "sha2-256", "blake2b-256"
merkle-commit-alg = tstr ; e.g. "rfc9162-sha256"
aead-alg = tstr
kem-alg = tstr
kdf-alg = tstrPáginas relacionadas
- Contenido y hashing: el mapa
hashes, qué compromete un resumen y la semántica de bytes exactos. - Registros de algoritmos: los identificadores nombrados de hashes, compromisos de lista, AEADs, KEMs y KDFs.
- Firmas: la construcción y verificación de
sigsa nivel de registro. - PoE sellada: el sobre
ency las ranuras de clave de destinatario. - Verificación: el flujo de validación, el perfil de CID y el catálogo de errores.
Introducción
Qué es Label 309, los principios que garantiza y cómo cualquiera puede verificar un registro sin confiar en un servidor.
Contenido y hashing
Cómo Label 309 vincula un registro con su contenido: el mapa de hashes, a qué se compromete el resumen criptográfico y los compromisos Merkle para anclar muchos elementos bajo una sola raíz.