Questa è una traduzione a scopo informativo. Fa fede la versione inglese, che è quella normativa. Leggi la versione inglese

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:

CDDL
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:

ChiaveTipoStatoSignificato
vuintREQUIREDVersione dello schema; questo documento definisce v = 1.
itemsarray di mappe itemOPTIONALImpegni per contenuto, vedi Contenuto e hashing.
merklearray di impegniOPTIONALImpegni su liste che legano liste di foglie off-chain a un'unica radice.
supersedesbytes (32)OPTIONALHash della transazione di un record precedente che questo sostituisce.
sigsarray di mappe di firmaOPTIONALFirme di paternità a livello di record, vedi Firme.
critarray di stringhe di testoOPTIONALChiavi 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:

CampoTipoStatoSignificato
algtstrREQUIREDIdentificatore di algoritmo di impegno su lista registrato.
rootbytes (32)REQUIREDRadice canonica sulla lista ordinata di foglie del produttore.
leaf_countuintREQUIREDNumero di foglie impegnate; lega la radice alla dimensione della lista.
urislista URIOPTIONALURI 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:

SchemaNote
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:

  1. Serializzazione preferita (forma più breve) per ogni intero.
  2. Codifica a lunghezza definita per tutte le stringhe di byte, stringhe di testo, array e mappe.
  3. Nessun tag semantico (questo documento non ne richiede nessuno: un tag bignum 2/3 MUST NOT comparire).
  4. Chiavi di mappa ordinate in ordine lessicografico byte per byte della loro codifica CBOR.
  5. Stringhe di testo UTF-8 senza byte-order mark.
  6. Nessuna chiave duplicata in alcuna mappa.
  7. 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 un 1.0 intero), lo zero negativo e undefined MUST 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à slotspassphrase 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.

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            = tstr

Pagine 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 sigs a livello di record e la sua verifica.
  • Sealed PoE: la busta enc e gli slot delle chiavi dei destinatari.
  • Verifica: la pipeline di validazione, il profilo CID e il catalogo degli errori.