Il record
Il formato wire di Label 309. Dove risiede il record sotto il label dei metadati 309, la forma della sua mappa, le regole del CBOR canonico, la suddivisione in chunk per il trasporto e lo schema CDDL.
Un record Label 309 è un'unica mappa CBOR trasportata nei metadati di una transazione Cardano sotto il label 309. La mappa impegna sulla blockchain uno o più hash di contenuto; il block time (l'orario del blocco) della transazione è la prova che quei byte esistevano non più tardi di quell'istante. Tutto il resto che il record può contenere (URI di storage, una busta di cifratura, firme di paternità, un puntatore di sostituzione) sono metadati opzionali su quella rivendicazione centrale.
Questa pagina definisce la forma sul wire: dove si colloca il record, come viene codificato, come si trasportano i valori troppo grandi e lo schema chiuso rispetto al quale opera un validatore strutturale. Le costruzioni crittografiche qui richiamate (algoritmi di hash, la busta sigillata, le firme) hanno pagine dedicate; questa riguarda il formato wire.
Dove risiede il record
Un record PoE MUST essere collocato sotto il label dei metadati di transazione
309, riservato come "Proof of Existence record" nel
registro delle label di metadati di CIP-10.
I metadati di transazione sono una mappa da label intero a valore, quindi una
transazione MUST NOT trasportare più di un record PoE: esattamente un record per
transazione.
Una transazione MAY trasportare metadati aggiuntivi sotto altre label (per
esempio un messaggio 674
CIP-20).
Un verificatore che elabora una PoE MUST ignorare qualsiasi label diverso dal
309.
Sul ledger dell'era Conway, i metadati di transazione sono il campo metadata
all'interno dell'auxiliary_data della transazione. I valori ammessi sotto una
qualsiasi label sono vincolati al tipo ricorsivo metadatum del ledger (interi,
stringhe di byte, stringhe di testo, array e mappe), con sia le stringhe di byte
sia le stringhe di testo limitate a 64 byte ciascuna:
metadatum =
{ * metadatum => metadatum }
/ [ * metadatum ]
/ int
/ bstr .size (0..64)
/ tstr .size (0..64)Una transazione che trasporti una qualsiasi singola bstr o tstr più lunga di 64
byte viene rifiutata dai nodi Cardano al momento dell'invio, prima che un verificatore
la veda. Quel limite è la ragione per cui Label 309 definisce una disciplina di
suddivisione in chunk per il trasporto (più sotto); ogni campo che il record contiene,
di base o di estensione, deve ridursi a un metadatum.
Trasporto: l'array di chunk dell'intero corpo
Un corpo di record serializzato supera abitualmente i 64 byte, perciò non può essere
memorizzato sotto il label 309 come valore nudo. Il corpo del record viene quindi
trasportato come un array opaco di chunk dell'intero corpo: un singolo array CBOR di
stringhe di byte da ≤ 64 byte (bstr .size (1..64)) la cui concatenazione in ordine è
il corpo del record. Questa suddivisione di trasporto è l'unica suddivisione in chunk
che Label 309 esegue, l'unico passo del formato davvero imposto dal ledger.
Poiché il ledger vede solo questo array di trasporto e mai i campi all'interno del corpo
ricomposto, quei campi sono normali valori CBOR senza alcun wrapper di chunk per campo e
senza alcun tetto di 64 byte a livello di campo: un URI di storage è un'unica stringa di
testo, un COSE_Sign1 è un'unica stringa di byte e un kem_ct X-Wing è un'unica stringa
di byte da 1120 byte. Un campo più lungo di 64 byte si estende semplicemente sui confini
dei chunk dell'array dell'intero corpo come qualunque altro tratto del corpo.
Un produttore MUST serializzare il corpo del record una volta in CBOR canonico, suddividere quella stringa di byte in chunk da 1 a 64 byte e memorizzare l'array a lunghezza definita di stringhe di byte a lunghezza definita risultante come valore del label 309. La forma ad array è sempre obbligatoria, anche per un corpo di 64 byte o meno: un corpo di questo tipo è un array di lunghezza 1, mai una mappa o una stringa di byte nuda. I produttori SHOULD usare la suddivisione minima (ogni chunk tranne l'ultimo esattamente di 64 byte) e SHOULD NOT emettere chunk di lunghezza zero; la suddivisione in eccesso spreca byte di transazione senza alcun vantaggio.
Un verificatore MUST concatenare i byte degli elementi dell'array nell'ordine dato per
ricomporre il corpo del record prima della validazione strutturale, e MUST
rifiutare qualsiasi valore del label 309 che non sia un array di questo tipo. I confini tra
i chunk non hanno alcun significato semantico: due array di trasporto le cui concatenazioni
sono byte-identiche denotano lo stesso record. La tassonomia degli errori di trasporto
fissa i codici di rifiuto: un chunk più lungo di 64 byte è CHUNK_TOO_LARGE; un elemento
dell'array che non sia una stringa di byte, un array o elemento a lunghezza indefinita, o
un valore del label 309 che non sia un array (mappa nuda, stringa di byte nuda, intero) è
MALFORMED_CBOR. Un chunk di lunghezza zero non apporta byte ed è tollerato, mai rifiutato
di per sé.
Lo schema descrive il corpo ricomposto
Tutto ciò che segue (la mappa del record, il CDDL, le regole dei campi) descrive il corpo del record dopo la ricomposizione dei chunk. L'array di chunk dell'intero corpo non fa parte dello schema; viene prima disfatto, poi il corpo viene validato.
La mappa del record
Il corpo del record ricomposto è una mappa CBOR. I campi a valore intero sono di CBOR major type 0/1; i campi di testo sono di major type 3 e MUST essere UTF-8 valido; i campi di byte sono di major type 2; gli array sono di major type 4; le mappe annidate sono di major type 5. Un campo opzionale presente MUST NOT contenere un valore vuoto.
La forma di primo livello è la seguente:
| Chiave | Tipo | Stato | Significato |
|---|---|---|---|
v | uint | REQUIRED | Versione dello schema; questo documento definisce v = 1. |
items | array di mappe item | OPTIONAL | Impegni per contenuto, vedi Contenuto e hashing. |
merkle | array di impegni | OPTIONAL | Impegni su liste che legano liste di foglie off-chain a un'unica radice. |
supersedes | bytes (32) | OPTIONAL | Hash della transazione di un record precedente che questo sostituisce. |
sigs | array di mappe di firma | OPTIONAL | Firme di paternità a livello di record, vedi Firme. |
crit | array di stringhe di testo | OPTIONAL | Chiavi di estensione che è obbligatorio comprendere. |
Un record conforme MUST impegnarsi su almeno uno tra items (con ≥ 1 voce) o
merkle (con ≥ 1 voce). Un record che non contiene nessuno dei due, o che ne contiene
uno come array vuoto, viene rifiutato come record vuoto. A parte questa regola, items e
merkle sono ortogonali: un record può contenere uno solo dei due oppure entrambi
insieme.
Label 309 non impone alcun limite numerico al numero di voci. L'unico tetto è la dimensione massima della transazione Cardano in vigore, e i produttori pagano commissioni per byte che limitano naturalmente la dimensione del record. Un validatore MUST NOT rifiutare un record solo perché contiene molte voci, purché rientri nel limite di dimensione del ledger.
Il campo di versione
v è un intero senza segno CBOR, non una stringa di versione semantica. Questo documento
definisce esattamente v = 1. Un validatore MUST rifiutare un record il cui v sia
fuori dall'insieme supportato con un errore tipizzato; MUST NOT andare in panic,
interrompersi o trattare silenziosamente il record come uno schema di metadati diverso.
L'intero v viene incrementato solo quando un cambiamento porterebbe un parser v1 a
interpretare male il record; le estensioni additive e con namespace non lo incrementano.
Items
Ogni voce in items è una mappa CBOR con un campo obbligatorio e due opzionali:
hashes: REQUIRED, una mappa non vuota da identificatore di algoritmo di hash a digest grezzo di 32 byte. Almeno una voce; algoritmi duplicati sono impossibili perché le chiavi di una mappa CBOR sono uniche. Vedi Contenuto e hashing.uris: OPTIONAL, una lista plurale di URI di discovery (regole più sotto).enc: OPTIONAL, la busta di cifratura per un item sigillato. Vedi Sealed PoE.
Non esiste uno slot di firma per singolo item. La paternità si esprime solo a livello di
record, tramite una voce sigs[] che copre uniformemente ogni item.
Impegni Merkle
Ogni voce in merkle lega il record a una lista ordinata di foglie da 32 byte tramite una
costruzione canonica ad albero di hash, così che un'unica radice da 32 byte sulla
blockchain possa rappresentare una lista di foglie off-chain arbitrariamente grande. Un
impegno è una mappa chiusa:
| Campo | Tipo | Stato | Significato |
|---|---|---|---|
alg | tstr | REQUIRED | Identificatore di algoritmo di impegno su lista registrato. |
root | bytes (32) | REQUIRED | Radice canonica sulla lista ordinata di foglie del produttore. |
leaf_count | uint | REQUIRED | Numero di foglie impegnate; lega la radice alla dimensione della lista. |
uris | lista URI | OPTIONAL | URI content-addressed per il file della lista di foglie off-chain. |
Una radice Merkle si impegna su una struttura a lista di foglie, mentre una voce hashes si
impegna su byte in chiaro; le due si verificano in modo diverso (prova di inclusione contro
ricalcolo del testo in chiaro), ed è per questo che gli impegni su lista risiedono al primo
livello anziché dentro un item. Il registro degli impegni su lista è disgiunto dal registro
degli hash di contenuto, vedi Registri di algoritmi.
Supersedes
supersedes è un hash di transazione Cardano opzionale di 32 byte che punta a un record
Label 309 precedente. È un collegamento indipendente dal servizio e append-only: un record
successivo può puntare a un record precedente senza alcun database off-chain né id di record
del fornitore.
La sostituzione non rimuove, revoca o invalida il record precedente: la blockchain è append-only, e i verificatori MUST continuare a trattare il record precedente come esistente e verificabile in autonomia. Il puntatore non porta con sé alcun campo di motivo o testo libero; qualsiasi significato umano (correzione, sostituzione, ritiro) appartiene al nuovo contenuto, non al label 309. Un verificatore che risolve il puntatore MUST cercarlo sulla stessa rete Cardano della transazione che lo contiene; il campo non porta alcun discriminatore di rete, perché un hash di transazione è unico solo all'interno della propria rete.
Firme
sigs è un array opzionale di voci di firma a livello di record. Ogni voce porta con sé una
struttura COSE_Sign1 detached sul corpo del record
(cioè la mappa completa del record con sigs rimosso) e, opzionalmente, la chiave pubblica
del firmatario per il percorso di firma con wallet. Una singola firma attesta l'intero corpo:
ogni item, ogni URI, ogni busta, il puntatore di sostituzione se presente e qualsiasi chiave
di estensione. Le firme sono sempre opzionali, e un algoritmo di firma non riconosciuto non
invalida mai la rivendicazione sul contenuto. Il payload firmato, il prefisso di separazione
di dominio, la risoluzione della chiave del firmatario e le regole di verifica rigorose sono
specificati in Firme.
Regole per gli URI
Quando presente, uris è una lista non vuota; ogni voce è un'unica stringa di testo CBOR
che porta esattamente un URI. Non esiste alcun tetto di lunghezza per URI né alcuna forma di
wrapping: il trasporto dell'intero corpo soddisfa già il tetto di 64 byte del ledger sulle
stringhe, perciò un lungo URI ipfs://<CIDv1>/<path> è una stringa di testo come qualunque
altra. Ogni URI MUST essere assoluto, MUST includere uno schema e una parte
gerarchica e MUST NOT contenere un identificatore di frammento: una PoE è una
rivendicazione sui byte di un contenuto, non su un sotto-componente di un documento.
L'insieme di schemi della v1 è chiuso e content-addressed:
| Schema | Note |
|---|---|
ar:// | Id di transazione Arweave (43 caratteri base64url). Forma ar://<txid>. |
ipfs:// | CID IPFS, CIDv1 preferito. Forma ipfs://<cid> o ipfs://<cid>/<path>. |
I produttori MUST NOT emettere alcun altro schema: https://, http://, file://,
data: e tutti gli altri vengono rifiutati. La restrizione è deliberata, non temporanea: un
URI content-addressed lega i byte recuperati all'URI stesso attraverso il modello di
integrità del livello di storage (un CID IPFS è un multihash del contenuto; un id di
transazione Arweave si impegna sui dati sotto il consenso di Arweave), così un verificatore
può confermare "i byte che ho recuperato sono i byte su cui il produttore si è impegnato"
senza fidarsi di DNS, TLS, gateway o autorità di certificazione. Uno schema fuori
dall'insieme rende un record strutturalmente non valido; non viene mai validato come
valid.
uris è opzionale ovunque. Un record di solo hash con uris omesso è una rivendicazione
completa: l'esistenza del contenuto è affermata senza impegnarsi su un canale di recupero. Il
profilo CID esatto (prefissi multibase accettati, codec e multihash) fa parte delle regole di
verifica; vedi Verifica.
CBOR canonico
Ogni record Label 309 MUST essere codificato come CBOR canonico secondo RFC 8949 §4.2.1 (Core Deterministic Encoding). In concreto:
- Serializzazione preferita (forma più breve) per ogni intero.
- Codifica a lunghezza definita per tutte le stringhe di byte, stringhe di testo, array e mappe.
- Nessun tag semantico (questo documento non ne richiede nessuno: un tag bignum 2/3 MUST NOT comparire).
- Chiavi di mappa ordinate in ordine lessicografico byte per byte della loro codifica CBOR.
- Stringhe di testo UTF-8 senza byte-order mark.
- Nessuna chiave duplicata in alcuna mappa.
- Nessun valore in virgola mobile o valore semplice non banale: un record contiene solo
interi, stringhe di byte, stringhe di testo, array, mappe e (dove uno schema lo ammette)
true/false/null. I float di major type 7 (incluso un1.0intero), lo zero negativo eundefinedMUST essere rifiutati, non convertiti.
Il determinismo è ciò che rende il formato interoperabile: due produttori che esprimono lo stesso record logico emettono byte identici, così una firma calcolata sul corpo da un'implementazione si verifica sotto un'altra. Un validatore MUST rifiutare una codifica non canonica. Explorer e wallet possono mostrare i metadati attraverso una proiezione JSON, ma un verificatore conforme MUST validare il CBOR originale della transazione, mai una sua ricodifica JSON con perdita di informazione.
Compatibilità in avanti
Label 309 v1 riserva un insieme chiuso di chiavi di base: v, items, merkle,
supersedes, sigs, crit. Un record MAY contenere inoltre chiavi di estensione i
cui nomi corrispondano a uno di due namespace riservati:
^x-.+: il namespace vendor / sperimentale.^[a-z]+-.+: il namespace delle specifiche companion, dove il prefisso indica la specifica che registra.
Un validatore MUST decodificare e preservare le chiavi di estensione, MUST NOT
rifiutare un record solo perché sono presenti e MUST mostrarle a titolo informativo
senza affermare di averne verificato il contenuto. Le chiavi di estensione fanno parte del
corpo firmato, quindi una firma a livello di record le copre: un relay non può iniettare una
chiave di estensione dopo che la firma è stata prodotta. Qualsiasi chiave di primo livello
sconosciuta che non corrisponda a nessuno dei due pattern (un errore di battitura come
supersedess, o una variante di maiuscole come Sigs) viene rifiutata come campo
sconosciuto. La tolleranza basata sui pattern preserva il rilevamento degli errori di
battitura sull'insieme di base, mantenendo al contempo aperto un bacino stabile per future
aggiunte.
Un produttore che richiede a un verificatore di comprendere un campo non di base MUST
elencare il nome di quel campo nell'array crit di primo livello. Un verificatore v1 che
incontra una voce crit che non implementa MUST NOT segnalare il record come valido.
Ogni voce di crit MUST corrispondere al pattern delle chiavi di estensione (le chiavi
di base sono vietate in crit), MUST nominare un campo effettivamente presente nel
record e MUST essere unica, così che una marcatura critica sia sempre riconducibile a un
campo concreto di cui il verificatore è tenuto a comprendere la semantica. Queste regole
seguono i precedenti must-understand / must-ignore di
RFC 9052 §3.1
(COSE crit) e RFC 7515 §4.1.11
(JWS crit).
Budget di byte
L'unico tetto rigido sulla dimensione del record è il parametro di protocollo Cardano
maxTxSize in vigore, pari a 16 384 byte alla major version 10 del protocollo su mainnet,
soggetto agli aggiornamenti dei parametri del ledger. Label 309 non impone
alcun limite a livello di schema al di sotto di quel valore. I record che superano il limite
vengono rifiutati dai nodi Cardano al momento dell'invio, quindi nessun verificatore ne vede
mai uno; un validatore MUST NOT inventare un tetto specifico di Label 309 al di sotto di
maxTxSize.
In pratica la struttura non-metadati di una transazione (input, output, witness, campi di commissione e di validità) consuma all'incirca 245 byte, lasciando dell'ordine di 16 KB per il record label-309. I produttori SHOULD puntare a qualche centinaio di byte sotto il limite per assorbire la variabilità delle commissioni e SHOULD calcolare la dimensione del record candidato prima dell'invio, fallendo subito se non dovesse rientrare. Le forme realistiche che entrano sono generose: ben oltre un centinaio di item a hash singolo, decine di firme a livello di record o molti slot classici per destinatario stanno comodamente dentro un'unica transazione, e un'unica radice Merkle si impegna su una lista di foglie off-chain illimitata a un costo on-chain fisso di 32 byte.
Schema CDDL
Il CDDL seguente è lo schema strutturale per il corpo del record ricomposto, cioè i byte
in CBOR canonico ottenuti dopo aver concatenato l'array di chunk di ≤ 64 byte memorizzato
sotto il label 309. Il corpo ricomposto è puro CBOR deterministico: non è esso stesso un
metadatum del ledger, e i suoi campi non sono soggetti al tetto di 64 byte sulle
stringhe, che il solo wrapper di trasporto dell'intero corpo soddisfa. Il wrapper non è
modellato qui.
Il blocco descrive il sovrainsieme permissivo delle forme ben formate; le invarianti tra
campi (la regola items-o-merkle, l'esclusività slots ⊕ passphrase della busta di
cifratura, l'appartenenza a un registro degli identificatori di algoritmo, le regole sulla
forma degli slot per KEM) sono imposte da un passaggio di validazione tipizzato sulla
struttura decodificata, non dal CDDL stesso.
; 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 = tstrPagine correlate
- Contenuto e hashing: la mappa
hashes, cosa impegna un digest e la semantica dei byte esatti. - Registri di algoritmi: gli identificatori nominati per hash, impegni su lista, AEAD, KEM e KDF.
- Firme: la costruzione
sigsa livello di record e la sua verifica. - Sealed PoE: la busta
ence gli slot delle chiavi dei destinatari. - Verifica: la pipeline di validazione, il profilo CID e il catalogo degli errori.
Introduzione
Che cos'è Label 309, i principi che garantisce e come chiunque può verificare un record senza fidarsi di alcun server.
Contenuto e hashing
Come Label 309 lega un record al suo contenuto: la mappa degli hash, ciò a cui l'impronta si vincola e gli impegni Merkle per ancorare molti elementi sotto un'unica radice.