Verifica
I tre ruoli di verifica di Label 309, gli stati del verdetto, la profondità di finalità e il catalogo tipizzato degli errori. Come chiunque arriva alla stessa risposta partendo dalla sola infrastruttura pubblica.
Label 309 si verifica, non si dichiara. Chi pubblica ancora un hash di contenuto su Cardano sotto la label 309; da quel momento la rivendicazione si regge sui propri byte, e chiunque disponga del riferimento di transazione può controllarla. Questa pagina definisce come avviene quel controllo: i tre ruoli di verifica, ciò che ciascuno tocca e ciò che lascia intatto, gli stati del verdetto che producono, la profondità di conferma al di sotto della quale un verdetto resta provvisorio, e il catalogo tipizzato degli errori che fa convergere due implementazioni indipendenti sullo stesso esito di fallimento per lo stesso input.
La proprietà che definisce tutto questo è l'indipendenza dal servizio. Un verificatore conforme arriva al proprio verdetto usando solo la blockchain pubblica, un explorer o un gateway Cardano scelto dal verificatore stesso e, per le rivendicazioni di contenuto e quelle sigillate, dei gateway di storage indirizzati al contenuto, anch'essi scelti dal verificatore. Non contatta mai chi pubblica. Lo standard non nomina alcun fornitore specifico; il gateway è un input fornito dall'operatore.
Tre ruoli, ciascuno un'estensione rigorosa del precedente
La verifica è stratificata. Ogni ruolo fa tutto ciò che fa il ruolo che lo precede, poi aggiunge una sola capacità. Un ruolo inferiore è di per sé un verificatore completo e utile: dimostra semplicemente meno cose.
| Ruolo | Aggiunge | Tocca |
|---|---|---|
| Validatore strutturale | conformità di schema e di dominio sui byte del record | nulla, è una funzione pura |
| Verificatore pubblico | risoluzione sulla blockchain, inclusione on-chain, controllo delle firme | un explorer Cardano + gateway di contenuto |
| Verificatore destinatario | decifratura di prova di un payload cifrato, ricalcolo dell'hash del testo in chiaro | la chiave privata del verificatore stesso |
Validatore strutturale: una funzione pura sui byte
Il validatore strutturale è un'unica funzione che va da una stringa di byte a un risultato. Non esegue alcun I/O, nessun controllo crittografico delle firme e nessuna decifratura. Non vede mai una rete, una transazione o una chiave. A parità di input restituisce lo stesso output, ogni volta, ovunque: è proprio questo che gli consente di girare prima della pubblicazione all'interno degli strumenti di chi pubblica, dentro un indexer di terze parti o dentro uno strumento di archiviazione che ne conferma la correttezza formale nel lungo periodo, sempre senza alcun server.
La sua pipeline è fissa:
1. resource bounds — a local, non-normative guard on input size; never a
Label 309 conformance error.
2. canonical decode — decode with a canonical-CBOR decoder (RFC 8949 §4.2.1):
definite lengths, sorted map keys, no duplicate keys,
valid UTF-8. Any malformed or non-canonical input →
a single MALFORMED_CBOR.
3. schema parse — type, length, and the chunk/length bounds; a strict
object mode that rejects unknown fields.
4. domain rules — cross-field constraints the schema cannot express:
registry membership, the items-or-merkle rule, COSE
structural shape, URI reconstruction, envelope shape.
5. result — { valid, record } with optional warnings/info, or a
sorted list of typed issues.Il validatore è indipendente dal profilo: analizza l'intero schema v1 a prescindere
dal sottoinsieme su cui un verificatore a valle intende agire. Gli errori invalidano
il record; le voci di tipo warning e info vengono segnalate ma lo lasciano valido. Un
punto cruciale: conferma la forma di un COSE_Sign1 (array a quattro elementi,
payload distaccato cioè null, un protected header ben formato) ma non verifica mai
la firma, e non rifiuta mai un record solo perché l'algoritmo di firma è uno che non
riconosce (in tal caso emette SIGNATURE_UNSUPPORTED, severità info, e il record
resta valido). Verificare la firma è compito del verificatore pubblico. Lo schema che
questo passaggio applica è descritto in Il record.
Verificatore pubblico: blockchain, inclusione e firme
Il verificatore pubblico aggiunge la blockchain sopra il validatore strutturale. Dato un riferimento di transazione Cardano:
- Risolve un explorer scelto dal verificatore. La sequenza di explorer è un input;
il verificatore li prova nell'ordine indicato, recuperando il CBOR grezzo della
transazione on-chain, mai la proiezione metadata-JSON dell'explorer. La vista JSON
appiattisce i tipi maggiori del CBOR in un'unione JSON e scarta l'ordine delle chiavi
della mappa, l'incapsulamento a lunghezza definita e la distinzione byte/testo, perciò
un verificatore che ricodificasse a partire da essa non potrebbe riprodurre l'input di
firma byte per byte esatto e ogni firma su un record conforme fallirebbe. La risposta
negativa di un singolo provider non fa fede sulla blockchain: la transazione
potrebbe essere on-chain e semplicemente sconosciuta a quel provider, perciò il
verificatore consulta tutti i provider rimanenti prima di emettere
TX_NOT_FOUND; se ogni provider è irraggiungibile emettePROVIDER_UNAVAILABLE. - Lega i byte recuperati al riferimento di transazione. Prima di leggere
qualunque cosa da una transazione recuperata, il verificatore ricalcola
blake2b-256sui byte del corpo della transazione recuperati — per definizione del ledger, l'id della transazione — e rifiuta la risposta a ogni discordanza con l'hash richiesto. Ricalcola poiblake2b-256sui byte dell'auxiliary-data recuperati e rifiuta a ogni discordanza con l'auxiliary_data_hashdel corpo ormai verificato. Entrambi i digest sono calcolati sui byte esattamente come recuperati, mai su una ricodifica. Una risposta che fallisce uno dei due controlli porta byte dimostrabilmente errati e viene scartata; se nessun provider fornisce una risposta che sopravvive al legame, il report portaTX_INTEGRITY_MISMATCH. Dopo questo passo ogni byte del record e della transazione circostante è crittograficamente impegnato all'hash fornito dal chiamante: nessun explorer può sostituire, modificare o troncare il record senza produrre una seconda preimmagine blake2b-256. - Estrae l'auxiliary data. Il ledger dell'era Conway ammette tre codifiche
dell'auxiliary-data, tutte valide: una mappa di metadati nuda e priva di tag, un array
a due elementi
[ metadata, scripts ]e una mappa con tag 259 con i metadati sotto la chiave0. Il verificatore DEVE accettarle tutte e tre e diramare puramente in base al tipo CBOR di primo livello e al tag: un valore con tag 259 è la forma a mappa con chiave, un array privo di tag è la forma a due elementi e una mappa priva di tag è sempre la mappa dei metadati stessa. NON DEVE ispezionare le chiavi di una mappa per indovinarne la forma; qualsiasi altra forma di primo livello, o qualsiasi tag diverso da 259, èMALFORMED_CBOR. Se la transazione legata non porta metadati sotto la label 309, il verificatore emetteMETADATA_NOT_FOUND, un esito attribuibile al record, perché la transazione legata stessa prova l'assenza: nessun provider avrebbe potuto rimuovere i metadati senza far fallire il legame. - Ricompone l'array di chunk dell'intero corpo. Il valore della label 309 è il corpo del record in canonical-CBOR suddiviso in un array di stringhe di byte da ≤ 64 byte. Il verificatore concatena byte a byte gli elementi nell'ordine dato, restituendo i byte grezzi senza alcun passaggio di ricodifica, così che il controllo canonical-CBOR possa comunque rilevare una codifica on-chain non conforme.
- Valida strutturalmente il corpo ricomposto (il ruolo descritto sopra). Un rifiuto
del validatore interrompe subito il report con verdetto
failed. - Controlla la profondità di conferma (vedi sotto). Una transazione al di sotto
della soglia si ferma qui con verdetto
pending. - Verifica ogni firma a livello di record secondo Ed25519 in modalità rigorosa. Non decifra. La risoluzione della firma, il payload con separazione di dominio e le regole di verifica rigorose sono specificati in Firme.
- Recupera e verifica l'hash del contenuto, chiamando il record a rispondere solo dei byte che può attribuire all'indirizzo di contenuto proprio di un URI (vedi sotto). Non decifra.
Perché CBOR grezzo e non JSON
Una firma è calcolata sul CBOR canonico, byte per byte esatto, del corpo del record. Una proiezione JSON dei metadati è per costruzione affetta da perdita di informazione: non può tornare indietro a quei byte. Ricodificare a partire dal JSON rompe ogni firma su un record conforme. Il CBOR grezzo della transazione è l'unico input autorevole per qualsiasi controllo crittografico; una vista JSON serve per la visualizzazione a uso umano, dopo che la verifica è già stata superata.
Verificatore destinatario: decifra e ricalcola
Il verificatore destinatario è un verificatore pubblico che possiede in più una chiave privata. Per un elemento sigillato indirizzato a lui, prova a decifrare gli slot di chiave on-chain con la propria chiave; in caso di successo recupera la chiave del contenuto, decifra il testo cifrato e infine ricalcola gli hash del testo in chiaro confrontandoli con l'impegno on-chain, chiudendo così il cerchio tra i byte cifrati e la rivendicazione di esistenza del contenuto. Poiché ogni elemento sigillato porta con sé almeno una voce di hash del contenuto, quel ricalcolo ha sempre qualcosa di concreto con cui confrontarsi. La busta sigillata, gli slot di chiave e il costrutto di apertura sono specificati in PoE sigillata.
Prima che il destinatario tocchi una qualsiasi primitiva KEM o AEAD, riapplica gli
stessi controlli di forma e di risorse della busta che il validatore strutturale ha già
eseguito, rifiutando per prima una busta strutturalmente non valida o sovradimensionata.
Due di quelle protezioni pre-primitiva sono soglie di risorse fissate dal deployment, non
campi del wire: le soglie di riferimento sono MAX_SLOTS = 1024 slot e 65536 byte per
la busta enc decodificata, entrambe ben al di sopra del tetto di ~16 KiB dei metadati
delle transazioni Cardano che vincola qualsiasi record onesto, perciò un record che
superi l'una o l'altra è malformato e viene rifiutato con ENC_SLOTS_TOO_MANY o
ENC_ENVELOPE_TOO_LARGE prima dell'esecuzione di una singola primitiva. I deployment
POSSONO restringerle.
Un controllo di forma è cruciale per la sicurezza: il materiale di incapsulamento DEVE
essere distinto all'interno di un singolo slots[], cioè tutti i valori epk per
x25519, oppure tutti i valori kem_ct per mlkem768x25519. Un duplicato
all'interno del record viene rifiutato con ENC_SLOTS_DUPLICATE_KEM_MATERIAL prima
dell'esecuzione di qualsiasi primitiva KEM o AEAD, perché un epk/kem_ct ripetuto
romperebbe l'unicità della chiave per slot su cui si fonda l'avvolgimento a nonce
azzerato. Il riutilizzo della chiave tra record o tra chiavi è un obbligo del produttore
e non è rilevabile dal verificatore; solo il duplicato all'interno del record lo è. Tutti
e tre i controlli sono strutturali: il validatore li impone su ogni record (i codici del
materiale duplicato, del conteggio degli slot e della busta decodificata sono codici di
Parte A); il destinatario semplicemente li riesegue come difesa in profondità.
L'apertura vera e propria è composta da due passi crittografici che il destinatario
riproduce dalla busta, mai da un qualsiasi input esterno. Per primo ricalcola l'hash
di trascrizione degli slot slots_hash una sola volta, prima del ciclo, e lo mantiene
costante per ogni slot:
slots_hash = SHA-256("cardano-poe-slots-transcript-v1" || canonicalEncode(SLOTS_TRANSCRIPT)),
dove la trascrizione fissa i campi dell'intestazione, l'insieme di slot on-wire (ogni
campo di slot è un'unica stringa di byte: non c'è alcuna suddivisione in chunk per campo
da normalizzare) e la rivendicazione dell'hash dell'elemento tramite hashes_hash.
Legare hashes_hash è ciò che permette al destinatario di confermare che la busta è
stata sigillata per questa esatta rivendicazione dell'hash dai soli byte on-chain,
prima di recuperare qualsiasi testo cifrato: una busta innestata su un elemento con una
mappa hashes diversa fallisce il MAC. Itera tutti gli slot senza alcuna uscita
anticipata; uno slot è accettato solo quando la chiave di contenuto che produce riproduce
anche lo slots_mac on-wire su quello slots_hash costante. L'accettazione incorpora inoltre un
bit di validità indipendente dal segreto: sul percorso classico uno slot
confezionato per portare il segreto condiviso X25519 al valore tutto a zero imposta quel
bit a falso (un confronto a tempo costante contro 0^32), la KEK viene selezionata a
tempo costante verso un valore fittizio derivato da 0^32 così che il ciclo esegua
lavoro identico, e il bit governa l'accettazione dello slot: uno slot con ECDH non valido
non può mai aprirsi a prescindere dal suo wrap o MAC. Sia l'apertura del wrap sia la
successiva apertura del contenuto sono atomiche: in caso di fallimento del tag AEAD
non restituiscono alcun testo in chiaro, e il candidato che restituiscono (la chiave
avvolta, o il testo in chiaro del contenuto) è un valore fittizio fisso o pseudocasuale
che è indipendente dal testo cifrato fallito: nessun testo in chiaro non verificato viene
mai rilasciato.
Una chiave del destinatario PUÒ legittimamente corrispondere a più di uno slot: un
produttore può sigillare la stessa chiave di contenuto verso lo stesso destinatario in
più slot, ciascuno con materiale KEM per slot fresco, per gonfiare il conteggio dei
destinatari: una valida tecnica di privacy, e distinta dal rifiuto del
materiale-di-encapsulation-duplicato, che scatta solo su un epk/kem_ct identico. Il
verificatore seleziona la chiave della prima corrispondenza e NON DEVE rifiutare
solo perché più slot hanno corrisposto. L'unica anomalia che DEVE rifiutare è
costituita da due slot corrispondenti che recuperano chiavi di contenuto diverse
(confrontate a tempo costante): il ciclo porta un bit cek_conflict e fa emergere
l'unico fallimento generico se una qualsiasi corrispondenza successiva produce una chiave
che differisce da quella selezionata. Questa è difesa in profondità: sotto il commitment
dell'insieme di slot una corrispondenza con chiave distinta è già infattibile, perciò il
controllo semplicemente fallisce in modo chiuso.
Poi, sulla chiave di cifratura del contenuto recuperata, deriva la chiave del contenuto
(una foglia HKDF di quella chiave salata da enc.nonce) e apre il testo cifrato a
STREAM segmentato chunk per chunk, verificando il tag di ciascun chunk prima di
rilasciarne il testo in chiaro. L'AAD per chunk è vuoto: tutto il contesto
dell'intestazione è già legato alla chiave in modo transitivo tramite slots_mac,
perciò ribaltare un qualsiasi campo dell'intestazione cambia ciò che il destinatario
deriva e lo stream non si apre. La troncatura viene colta dal flag finale: un chunk
finale mancante, dati dopo di esso, un flag finale su un chunk non finale o un chunk non
finale troppo corto falliscono tutti come TAMPERED_CIPHERTEXT. Il formato segmentato
non impone alcun tetto crittografico al payload (il contatore di chunk a 88 bit ammette
2^88 chunk); un massimo pratico è una policy contro il denial-of-service del
deployment, imposta in modo incrementale man mano che lo stream viene letto. Sul percorso
a passphrase il destinatario legge per prima l'intestazione di impegno da 32 byte iniziale
e la confronta a tempo costante prima di aprire qualsiasi chunk.
È nel percorso del destinatario che il catalogo degli errori dà il meglio della sua precisione: distingue il caso in cui nessuno slot ha accettato questa chiave (destinatario sbagliato) da quello in cui uno slot ha accettato la chiave ma è stato manomesso l'insieme degli slot oppure il testo cifrato. Sono rivendicazioni di sicurezza diverse e portano codici diversi (vedi sotto). Un chiamante non fidato, però, riceve esattamente un'unica forma di fallimento generico a prescindere dalla causa (nessuno slot aperto, insieme di slot manomesso, o tag del contenuto fallito), e la risposta non rivela mai quale caso si sia verificato, né quale slot abbia corrisposto; i codici tipizzati sono una diagnostica interna riservata a un chiamante locale fidato.
I tempi seguono un unico modello esplicito. Il verificatore PUÒ ritornare al controllo di mancata corrispondenza, prima della decifratura del contenuto, perciò un non destinatario e un destinatario impiegano un tempo misurabilmente diverso. Quella differenza rivela soltanto destinatario contro non destinatario, mai quale slot abbia corrisposto né alcun materiale di chiave. Tempi uniformi tra un non destinatario e un destinatario il cui testo cifrato non si apre non sono richiesti, e un'apertura fittizia del contenuto NON DEVE essere imposta: imporrebbe il costo della decifratura del contenuto a ogni passante. La garanzia a tempo costante che vale è quella tra gli slot: all'interno del passaggio di una singola chiave privata il ciclo scorre tutti gli slot con confronti a tempo costante, perciò nulla fa trapelare quale slot, se mai uno, quella chiave apra.
Finalità: profondità di conferma
Il regolamento di Cardano è probabilistico. Una transazione profonda un solo blocco
può ancora finire orfana a causa di un breve reorg; una transazione profonda molti
blocchi si è ormai assestata con probabilità schiacciante. Un verificatore che
dichiarasse valid un record profondo un solo blocco permetterebbe a un attaccante di
ri-ancorare un record contraddittorio su un fork concorrente e di incassare un verdetto
"valid" su entrambi, infrangendo silenziosamente l'ipotesi di sola aggiunta su cui si
fonda l'intera prova.
Per questo, finché un record resta sotto una soglia di profondità di conferma, il
verificatore lo riporta come pending, non failed. La soglia generica
CONSIGLIATA è ≥ 15 blocchi (all'incirca cinque minuti). La soglia è una
politica del verificatore, non una costante del formato: i deployment che trattano
record di alto valore o probatori DOVREBBERO alzarla verso la finalità definitiva,
e un verificatore DEVE rendere esplicita la soglia che ha usato, così che i
consumatori possano sovrapporvi una politica più rigorosa. Un record pending è ben
formato e on-chain; semplicemente non si è ancora assestato a sufficienza, e può
risolversi in valid in un tentativo successivo.
La profondità di conferma, l'altezza del blocco e il block time sono fatti asseriti
dall'explorer che il verificatore non fabbrica mai. Il legame al riferimento di
transazione rende affidabile il contenuto della transazione, ma i fatti di blockchain
che la riguardano non sono derivabili dai byte: il CBOR della transazione non porta né
timestamp né altezza. La profondità si calcola come (altezza del tip dell'explorer che risolve) − (altezza del blocco che la include) + 1, perciò una transazione nel blocco di
tip ha profondità esattamente 1; il block time è il timestamp POSIX (secondi interi, UTC)
dello slot del blocco che la include, preso dal campo temporale dell'explorer, mai
ricalcolato dal verificatore. Poiché questi fatti poggiano sulla parola dell'explorer, un
verificatore DOVREBBE risolverli da almeno due explorer indipendenti e segnalare ogni
divergenza; i deployment per cui il block time è determinante (notarizzazione legale,
dispute sulle scadenze) DEVONO verificarlo in modo incrociato. Il report porta la
profondità risolta accanto alla soglia con cui è stata confrontata, e block_time (con
block_slot quando disponibile).
Stati del verdetto
Un verificatore conclude in uno di quattro verdetti leggibili dalla macchina,
ciascuno accoppiato uno a uno con un codice di uscita del processo, così che un chiamante
(un controllo di CI, un monitor, uno script) possa distinguere un fallimento attribuibile
al record da uno transitorio di natura operativa, senza dover analizzare il report
strutturato. Il principio che governa è l'attribuzione: condannare un record richiede
prove che i byte propri del record — o byte legati in modo attribuibile ai suoi
riferimenti — effettivamente forniscono. Nessun comportamento scorretto di un provider può
fabbricare un failed.
| Verdetto | Uscita | Significato |
|---|---|---|
| valid | 0 | ogni controllo eseguito dal verificatore ha restituito ok; non è presente alcuna voce con severità error. |
| failed | 1 | un fallimento attribuibile al record: il validatore strutturale ha rifiutato i byte, una firma non si è verificata, un hash attribuibile non corrispondeva, la transazione legata non porta metadati sotto la label 309 (METADATA_NOT_FOUND), oppure è scattata una regola di deny-host. |
| unverifiable | 2 | nessun errore attribuibile al record, ma un controllo richiesto non ha potuto eseguirsi o non ha potuto essere attribuito: la transazione non si è risolta, nessuna risposta di un provider è sopravvissuta al legame (TX_INTEGRITY_MISMATCH), oppure non è stato possibile ottenere o attribuire il contenuto/testo cifrato impegnato. Lo stesso record può verificarsi valid a un nuovo tentativo o sotto un gateway diverso. |
| pending | 3 | strutturalmente ben formato e on-chain, ma sotto la soglia di profondità di conferma (INSUFFICIENT_CONFIRMATIONS); potrebbe assestarsi. Nessun esito di un record pending può essere presentato come definitivo. |
I fallimenti runtime dell'host del verificatore che non sono attribuibili al record usano i codici di uscita 4 e superiori e non corrispondono ad alcun verdetto.
Un verdetto valid NON DEVE essere riportato quando è presente una qualsiasi voce
con severità error; un record PUÒ essere valid con una lista warnings e/o
info non vuota. Nessuno dei due livelli può "ammorbidire" un errore trasformandolo in
warning per far passare un record. Ogni verdetto è riservato al proprio caso: pending
non viene mai usato al posto di valid o failed, e un fallimento attribuibile a un
provider è unverifiable, mai failed.
Una soglia minima di impegno governa la disponibilità: quando il controllo del
contenuto è stato eseguito ma i fallimenti di disponibilità non hanno lasciato alcun
impegno sul contenuto del record effettivamente verificato, il verdetto è unverifiable,
mai valid: un verdetto valid significa che almeno un impegno sul contenuto è stato
controllato. Gli esiti di integrità non sono toccati dalla soglia: byte attribuibili che
falliscono un impegno producono failed a prescindere da ciò che altro fosse
disponibile.
Legame all'indirizzo di contenuto e attribuzione
Il passo 8 (e, per il verificatore destinatario, la decifratura) recupera byte da storage gateway indirizzati per contenuto scelti dal verificatore, e i gateway non sono fidati. Il principio del verdetto qui sopra ruota attorno a un'unica domanda a cui il verificatore DEVE saper rispondere per ogni flusso di byte recuperato: questi byte possono essere attribuiti all'URI stesso? Entrambi gli schemi sono indirizzati per contenuto, perciò possono:
ipfs://: ricalcola il CID sul contenuto recuperato (il multihash direttamente per un CID con codec raw; i digest blocco per blocco lungo il percorso risolto per un CID in forma DAG).ar://: valida la transazione Arweave firmata e ricalcola il Merkle tree dei chunk rispetto al suodata_root; per un data item ANS-104, ricalcola il deep-hash, verifica la firma del proprietario e controlla che lo SHA-256 della firma sia uguale all'id nell'URI.
I byte che soddisfano i digest propri del record non richiedono alcun controllo di legame: l'impegno del record è forte almeno quanto quello dello strato di storage. Dove il legame viene applicato, l'attribuzione decide cosa significhi una discordanza:
- Byte attribuibili (legame verificato, o forniti fuori banda dal chiamante) sono
imputati al record quando falliscono un impegno:
URI_INTEGRITY_MISMATCHper il contenuto di un elemento (un fallimento di integrità netto, a prescindere da ciò che porta un URI fratello), la famigliaMERKLE_*per un elenco di foglie,TAMPERED_CIPHERTEXTper un blob di testo cifrato attribuibile. Verdettofailed. - Byte non attribuibili (legame non verificato, o verificato e fallito) incolpano il
provider che li serve, mai il record:
URI_PROVIDER_INTEGRITY_MISMATCH(warning). Il verificatore prosegue con gli URI e i gateway rimanenti; una rivendicazione lasciata senza byte attribuibili termina con un esito di disponibilità (CONTENT_UNAVAILABLE,MERKLE_LEAVES_UNAVAILABLE,CIPHERTEXT_UNAVAILABLE), verdettounverifiable, esattamente come se non si fosse recuperato nulla.
Questa è la controparte lato storage del legame al riferimento di transazione: un gateway
che si comporta in modo scorretto può degradare solo la disponibilità, mai il
verdetto. URI_INTEGRITY_MISMATCH e URI_PROVIDER_INTEGRITY_MISMATCH sono perciò codici
distinti — il primo condanna il record, il secondo condanna un provider — e un
verificatore che li confondesse permetterebbe a un gateway ostile di forgiare un failed.
Il ricontrollo dell'hash del testo in chiaro dopo la decifratura non ha bisogno di alcun
qualificatore di attribuzione: un testo cifrato che si apre sotto la busta autenticata è
attribuito dall'AEAD stesso, perciò una discordanza dell'hash del testo in chiaro in quel
punto (URI_INTEGRITY_MISMATCH) è sempre attribuibile al record e impone failed.
Il catalogo tipizzato degli errori
Ogni modalità di fallimento si risolve in un codice tratto da un unico catalogo chiuso.
I codici sono in SCREAMING_SNAKE_CASE, e un'implementazione conforme DEVE emettere
esattamente quelle stringhe: mai un codice interno in minuscolo di un parser, mai un
messaggio in forma libera. Due implementazioni in due linguaggi diversi che ricevono lo
stesso input emettono lo stesso codice; la lista normativa completa è fissata byte per
byte dalla suite di test di conformità, e il catalogo è bloccato (si aggiungono codici
solo per emendamento).
Modello di severità
Ogni voce porta una di tre severità, e la distinzione è determinante:
- error: invalida il verdetto. Un risultato
validnon può coesistere con alcunerror. - warning: un'anomalia non fatale a tempo di esecuzione (un singolo gateway è
fallito, una lista di foglie era disponibile solo in parte) che non impedisce il
valid. - info: un mancato controllo deliberato, ossia un aspetto che il verificatore ha scelto di non valutare (un campo fuori dal suo profilo, un algoritmo opzionale non riconosciuto). Una voce info non è un errore ammorbidito e non viene mai usata come tale.
Un codice fa storia a sé: INSUFFICIENT_CONFIRMATIONS non si mappa su una severità ma
sul verdetto pending, perché il record è ben formato e attende soltanto di
assestarsi.
Famiglie di errori
Il catalogo si raggruppa in famiglie. Un insieme rappresentativo, non esaustivo, di codici:
| Famiglia | Severità | Codici rappresentativi |
|---|---|---|
| CBOR malformato / non canonico | error | MALFORMED_CBOR, CHUNK_TOO_LARGE |
| Schema | error | SCHEMA_TYPE_MISMATCH, SCHEMA_MISSING_REQUIRED, SCHEMA_UNKNOWN_FIELD, SCHEMA_EMPTY_RECORD |
| Algoritmo non supportato | error | UNSUPPORTED_HASH_ALG, UNSUPPORTED_AEAD_ALG, UNSUPPORTED_KEM_ALG, UNSUPPORTED_MERKLE_COMMIT_ALG |
| Explorer / metadati | error / pending | TX_NOT_FOUND, PROVIDER_UNAVAILABLE, TX_INTEGRITY_MISMATCH (→ unverifiable), METADATA_NOT_FOUND (→ failed); INSUFFICIENT_CONFIRMATIONS (→ pending) |
| Firma | error / info | MALFORMED_SIG_COSE_SIGN1, SIGNER_KEY_UNRESOLVED, SIGNATURE_INVALID, WALLET_ADDRESS_MISMATCH; SIGNATURE_UNSUPPORTED (info) |
| Cifratura / KEM / wrap | error | KEM_EPK_LENGTH_MISMATCH, KEM_CT_LENGTH_MISMATCH, WRAP_LENGTH_MISMATCH, ENC_SLOTS_DUPLICATE_KEM_MATERIAL, ENC_SLOTS_TOO_MANY, ENC_ENVELOPE_TOO_LARGE, ENC_REQUIRES_CONTENT_HASH |
| Esito della decifratura | error | WRONG_DECRYPTION_INPUT_SHAPE, WRONG_RECIPIENT_KEY, TAMPERED_HEADER, TAMPERED_CIPHERTEXT, KDF_DERIVATION_FAILED |
| URI / contenuto | error / warning | INVALID_URI, URI_TARGET_FORBIDDEN, URI_INTEGRITY_MISMATCH, CONTENT_UNAVAILABLE (→ unverifiable), CIPHERTEXT_UNAVAILABLE (→ unverifiable); URI_PROVIDER_INTEGRITY_MISMATCH, URI_FETCH_FAILED (warning) |
| Impegno su lista Merkle | error / warning / info | MERKLE_ROOT_MISMATCH, SCHEMA_MERKLE_LEAF_COUNT_MISMATCH; MERKLE_LEAVES_UNAVAILABLE (dual); MERKLE_UNSUPPORTED (dual) |
| Indipendenza dal servizio | error | SERVICE_INDEPENDENCE_VIOLATION |
I codici di rete/policy (TX_NOT_FOUND, PROVIDER_UNAVAILABLE, CONTENT_UNAVAILABLE,
CIPHERTEXT_UNAVAILABLE) — e TX_INTEGRITY_MISMATCH, la cui discordanza è dimostrabile
contro i provider anziché contro il record — hanno severità error nella lista delle voci
(bloccano un verdetto valid) ma non sono attribuibili al record, perciò si mappano su
unverifiable, mai su failed. URI_PROVIDER_INTEGRITY_MISMATCH è il warning per
recupero sotto lo stesso principio. Alcuni codici portano una doppia severità —
ENC_UNSUPPORTED, MERKLE_UNSUPPORTED, OUT_OF_PROFILE_SKIPPED e
MERKLE_LEAVES_UNAVAILABLE — che leggono info/warning per impostazione predefinita e
si promuovono a error in un contesto rigoroso (il ruolo del destinatario, l'escalation
merkle-only, la modalità rigorosa end-to-end o la soglia minima di impegno).
Il percorso del destinatario è la famiglia diagnosticamente più precisa. Per un
chiamante locale fidato, una decifratura fallita si risolve in uno di tre codici
interni: WRONG_RECIPIENT_KEY significa che nessuno slot ha accettato la chiave
fornita (non è mai stata recuperata alcuna chiave del contenuto); TAMPERED_HEADER
significa che una chiave è stata recuperata ma la chiave di contenuto candidata non ha
riprodotto lo slots_mac su slots_hash (uno slot, un campo dell'intestazione, o lo
slots_mac stesso è stato alterato); TAMPERED_CIPHERTEXT significa che l'insieme
degli slot era integro ma il tag AEAD del contenuto è fallito dopo che la chiave era
stata recuperata. I tre casi sono strutturalmente distinguibili per quel chiamante
locale, e il confine tra loro non lascia trapelare alcun materiale di chiave. Per un
chiamante esterno non fidato, tutti e tre collassano nell'unico fallimento generico
descritto sopra, non distinguibile né per forma della risposta né per tempo, così che
la precisione diagnostica non diventi mai un oracolo.
Come i codici si mappano sui quattro verdetti
Un codice emesso dal validatore strutturale significa che i byte del record non sono
conformi. Il verificatore pubblico interrompe subito il report con verdetto failed
(uscita 1) e con la lista delle voci del validatore, senza eseguire ulteriore lavoro sulla
blockchain o di crittografia. Anche un codice emesso solo dopo che la validazione
strutturale è stata superata (una firma che non si è verificata, un hash attribuibile
che non corrispondeva, METADATA_NOT_FOUND sulla transazione legata) è del pari failed:
ciascuno è attribuibile al record, prova che i byte propri del record forniscono.
Un fallimento transitorio o attribuibile a un provider è un verdetto diverso: contenuto
non recuperabile o non attribuibile, un explorer irraggiungibile, o un provider che ha
servito byte che falliscono il legame al riferimento di transazione si mappano tutti su
unverifiable (uscita 2), perché nessuno è colpa del record e lo stesso record può
verificarsi valid a un nuovo tentativo. La distinzione preservata dal codice di uscita è
perciò a tre vie tra gli stati di fallimento: failed attribuibile al record (1),
unverifiable operativo o attribuibile a un provider (2), e pending sotto-soglia (3).
Alcuni codici hanno una severità che dipende dal contesto. Un verificatore che legge un
record più ricco del profilo che ha dichiarato (per esempio un verificatore di solo
hash che incontra un elemento sigillato) riporta il campo aggiuntivo come info in un
contesto di visualizzazione e valida comunque la rivendicazione di hash, oppure come
error in un contesto di audit rigoroso end-to-end. Allo stesso modo, un algoritmo di
firma opzionale non riconosciuto è info, perché la rivendicazione di esistenza del
contenuto non dipende da esso, e così una prova pubblica di solo hash resta valid
anche quando una firma accessoria non è verificabile.
L'indipendenza dal servizio non è opzionale
La verifica non torna mai a contattare chi pubblica. Ogni chiamata in uscita di un
verificatore conforme è diretta a infrastruttura scelta dall'operatore: un explorer
Cardano per la transazione, e gateway di storage indirizzati al contenuto per
eventuali byte ar:// o ipfs://. Questo è imposto strutturalmente, non è una
promessa affidata a un commento nel codice:
- Ogni chiamata di rete passa attraverso un unico wrapper di uscita che registra
url,method,status, conteggio dei byte e finalità per ogni chiamata (successo, fallimento e ritentativo allo stesso modo) in una traccia di audit obbligatoria sul report. Un verificatore che non riesce a produrre quella traccia non può dimostrare la propria indipendenza. - Quel wrapper accetta una lista di deny-host fornita al deployment e fa fallire in modo
netto qualsiasi chiamata verso un host corrispondente con
SERVICE_INDEPENDENCE_VIOLATION. La lista è un input dell'operatore (una suite di conformità la popola con i domini propri di chi implementa), non una costante del formato di Label 309. - Un'apparecchiatura di conformità esegue il verificatore contro transazioni di
fixture congelate in una rete dove i domini propri dell'operatore non risolvono da
nessuna parte, e verifica che il verificatore restituisca comunque
valid. La verifica avviene al livello di rete del sistema operativo, non ispezionando il codice sorgente con grep: un verificatore che raggiungesse un host proibito tramite un IP scritto a mano supererebbe una scansione del sorgente ma fallirebbe questo test.
Al verificatore servono un explorer Cardano raggiungibile e nulla di specifico per l'implementazione. I gateway di contenuto, una chiave privata del destinatario e una lista di foglie off-chain sono input opzionali che sbloccano rispettivamente i controlli su contenuto, destinatario e Merkle. Nessuno di essi è un servizio nominato dallo standard, e nessuno di essi è chi pubblica.
Pagine correlate
- Il record: il formato del wire rispetto a cui il validatore strutturale effettua i controlli, ovvero la label 309, la forma della mappa, la ricomposizione dei chunk e lo schema CDDL.
- Firme: il costrutto COSE_Sign1 a livello di record, il payload con separazione di dominio e le regole di verifica rigorose di Ed25519.
- PoE sigillata: la busta di cifratura, gli slot di chiave del destinatario e l'apertura che il verificatore destinatario esegue.
Sealed PoE
La busta di cifratura di Label 309. Come un mittente sigilla un contenuto verso una o più chiavi di destinatario, mentre la blockchain trasporta soltanto l'hash del testo in chiaro e gli slot di chiave incapsulati, mai il testo in chiaro e mai i destinatari.
Modello di sicurezza
Ciò di cui un verificatore Label 309 si fida e ciò di cui non si fida: l'invariante della verificabilità in autonomia, le garanzie di riservatezza della PoE sigillata, le regole crittografiche normative che ogni implementazione deve rispettare e i limiti noti del formato wire.