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.
L'hash del contenuto è la rivendicazione. Tutto ciò che un record Label 309 afferma sull'esistenza discende da un'impronta crittografica dei byte del contenuto, ancorata on-chain (sulla blockchain) sotto la label dei metadati 309. Questa pagina definisce come quell'impronta viene trasportata, a cosa esattamente si vincola e come una singola radice di 32 byte può rappresentare un insieme di elementi grande a piacere.
La mappa hashes
Ogni elemento di un record porta con sé una mappa hashes: una mappa CBOR che
associa a un identificatore di algoritmo un'impronta (l'hash del contenuto) grezza
di 32 byte.
hashes = {
"sha2-256": h'…32 bytes…', ; key = algorithm id, value = raw digest
}Le chiavi sono identificatori in forma di stringa di testo, tratti dal registro degli hash; i valori sono stringhe di byte grezze, mai codificate in esadecimale. La mappa deve contenere almeno una voce e ogni algoritmo di hash registrato produce esattamente 32 byte:
| Identificatore | Algoritmo | Riferimento | Impronta |
|---|---|---|---|
sha2-256 | SHA-256 | FIPS 180-4 | 32 B |
blake2b-256 | BLAKE2b-256 | RFC 7693 | 32 B |
Entrambi gli identificatori sono obbligatori per chi implementa un verificatore, quindi un record con un solo hash sotto l'uno o l'altro è valido ovunque. Un verificatore che incontra un identificatore sconosciuto rifiuta il record con un codice di errore stabile, anziché ignorare in silenzio la voce. Il registro completo, comprese le posizioni post-quantistiche riservate, si trova in Registri degli algoritmi.
L'uso di una mappa CBOR, anziché di array paralleli o di un elenco di
sotto-oggetti {alg, digest}, ha tre conseguenze che fanno parte del contratto
del formato. Gli algoritmi duplicati sono impossibili per costruzione, perché le
chiavi di una mappa CBOR sono uniche. L'ordinamento canonico è automatico, perché
il CBOR canonico ordina le chiavi in base ai byte della loro codifica: così due
produttori che esprimono lo stesso insieme di hash emettono mappe identiche byte
per byte, e qualsiasi firma a livello di record che le copre resta stabile. La
struttura, inoltre, non richiede alcuna validazione voce per voce: un validatore
strutturale verifica soltanto che ogni chiave sia registrata e che ogni valore
abbia la lunghezza dell'impronta prevista dall'algoritmo.
A cosa si vincola l'hash
L'impronta si vincola ai byte del contenuto, cioè all'esatta sequenza di byte a
cui il produttore sta apponendo un timestamp. Ogni voce di una mappa hashes deve
essere l'impronta di quella stessa sequenza di byte secondo l'algoritmo indicato;
un record le cui voci descrivono testi in chiaro diversi non è conforme. Quando i
byte del contenuto sono a disposizione del verificatore, questo deve
ricalcolare ogni impronta e rifiutare il record se anche una sola non corrisponde.
Quando un record porta una busta di cifratura (enc), l'hash lega il testo in
chiaro, mai il testo cifrato. È una scelta deliberata: una Proof of Existence
(prova di esistenza) esiste affinché un autore possa, in un secondo momento,
rivelare il testo in chiaro e dimostrare che esisteva a un dato istante. Calcolare
l'hash del testo cifrato proverebbe soltanto l'esistenza di un blob cifrato, il che
non dice nulla sul contenuto sottostante. Così un record sigillato continua a
dimostrare con precisione quale testo in chiaro è stato sottoposto a timestamp: il
destinatario decifra, ricalcola le impronte del testo in chiaro e le confronta con
l'impegno on-chain. Un elemento che porta enc deve pertanto contenere almeno una
voce con l'hash del contenuto; senza di essa non vi sarebbe alcuna rivendicazione
sul testo in chiaro contro cui ricalcolare.
Vincolo al testo in chiaro, anche quando è sigillato
L'impronta on-chain di un record sigillato è l'impronta del testo in chiaro. Il testo cifrato vero
e proprio risiede a un URI ar:// o ipfs:// indirizzato per contenuto, perciò i byte restituiti
da uno storage gateway sono verificabili a manomissione rispetto all'indirizzo, senza dover dare
fiducia al gateway; un destinatario decifra e ricalcola l'hash del testo in chiaro per chiudere il
cerchio fino alla rivendicazione on-chain.
Un solo hash, o più di uno
Un singolo hash del contenuto è pienamente conforme. Per tutti gli hash a 256 bit
del registro, i migliori attacchi noti di seconda preimmagine si collocano a 2^256
o nelle sue vicinanze in ambito classico: un singolo hash a 256 bit ben
progettato copre già il modello di minaccia realistico lungo l'intera vita
d'archivio di un record, e i validatori strutturali non emettono alcun avviso per i
record con una sola voce.
Un produttore può aggiungere una seconda voce appartenente a una famiglia di
progettazione indipendente, come misura facoltativa di difesa in profondità,
abbinando sha2-256 (SHA-2: costruzione Merkle–Damgård) a blake2b-256 (BLAKE2:
una costruzione HAIFA su una permutazione derivata da ChaCha). Poiché le due
famiglie non condividono alcuna parentela strutturale, un record che le porta
entrambe risulta indebolito solo se entrambe cadono sotto la crittanalisi nello
stesso momento. Il costo è un'impronta aggiuntiva di 32 byte più il suo breve
identificatore per ciascun elemento; la scelta spetta al produttore e non è mai
obbligatoria.
Impegni Merkle su lotti
Un singolo hash del contenuto ancora un singolo contenuto. Per ancorare un insieme
grande a piacere, ad esempio 500 file di artefatti di CI, un flusso di eventi IoT o
un lotto di log di audit, Label 309 definisce un array merkle[] di primo livello.
Ogni voce si impegna su un elenco ordinato di foglie da 32 byte con un'unica radice
da 32 byte pubblicata on-chain; le foglie ordinate vere e proprie risiedono off
chain.
merkle = [
{
"alg": "rfc9162-sha256",
"root": h'…32 bytes…', ; canonical root over the ordered leaves
"leaf_count": 4, ; binds the on-chain root to the leaf-list size
"uris": [ … ], ; OPTIONAL — where the off-chain leaves list lives
},
]L'algoritmo di impegno registrato è rfc9162-sha256: il Merkle Tree Hash di
RFC 9162 §2.1.1, con SHA-256 come hash sottostante. È una costruzione di
impegno su un elenco, distinta dal registro degli hash di contenuto (una radice
Merkle si impegna su una struttura a elenco di foglie, un'impronta sha2-256 si
impegna sui byte del testo in chiaro) e per questo risiede in un proprio array
anziché dentro hashes. Il leaf_count on-chain lega la radice alla dimensione
dell'elenco off chain, precludendo una sostituzione che ricostruisca un albero di
dimensione diversa con la stessa radice per qualche posizione di foglia.
Costruzione dell'albero
La costruzione distingue le foglie dai nodi interni con un prefisso di un byte per
la separazione di dominio (0x00 per le foglie, 0x01 per i nodi interni), così
che un attaccante non possa forgiare un nodo interno che collida con una foglia.
Per un elenco ordinato L = (d_0, …, d_{n-1}) di valori da 32 byte con n ≥ 1, il
Merkle Tree Hash è definito ricorsivamente:
MTH(L) = SHA-256(0x00 || d_0) when n == 1
MTH(L) = SHA-256(0x01 || MTH(L[0:k]) || MTH(L[k:n])) when n > 1
where k is the largest power of 2 strictly less than nUna conseguenza cruciale: una foglia singola viene sottoposta ad hashing come
SHA-256(0x00 || d_0), non come la foglia nuda. La radice di un albero con una
sola foglia non è quindi mai uguale alla foglia stessa. Chi vuole apporre un
timestamp a un singolo contenuto deve usare direttamente una voce sha2-256 o
blake2b-256, non un albero Merkle con una sola foglia. Un albero vuoto
(n == 0) è vietato.
La costruzione è sensibile all'ordine (permutare le foglie produce una radice diversa), quindi i produttori devono trattare l'elenco delle foglie come una sequenza ordinata e conservarne l'ordine attraverso la pubblicazione, l'archiviazione e qualsiasi successiva generazione di prove.
L'elenco off chain delle foglie
La radice è inutile senza l'elenco delle foglie, perciò i produttori conservano le
foglie ordinate off chain. L'artefatto canonico è un documento
cardano-poe-merkle-leaves-v1, codificato come CBOR canonico
(RFC 8949): una radice da 32 byte, l'array ordinato di foglie da 32 byte e
il conteggio delle foglie.
leaves-list = {
"format": "cardano-poe-merkle-leaves-v1",
"tree_alg": tstr, ; registered list-commitment algorithm id
"root": bytes .size 32, ; raw 32 bytes, not hex
"leaves": [ + bytes .size 32 ], ; ordered raw 32-byte leaves
"leaf_count": 1..4294967295, ; 1 .. 2^32-1; MUST equal the length of `leaves`
? "leaf_alg": tstr, ; informative; no verification semantics
}Un verificatore risolve l'elenco off chain, ricalcola la radice dalle sue leaves
con la costruzione descritta sopra e la confronta byte per byte con la
merkle[i].root on-chain; il leaf_count presente nel file deve essere uguale sia
al leaf_count on-chain sia a len(leaves). Questo contenitore in CBOR canonico è
l'unica forma normativa dell'elenco delle foglie: non esiste alcuna proiezione
JSON né serializzazione alternativa, perciò due implementazioni che si scambiano un
elenco di foglie si scambiano sempre documenti confrontabili byte per byte.
Prove di inclusione
Lo scopo del raggruppamento in lotti è la divulgazione selettiva: dimostrare che un
elemento faceva parte dell'elenco impegnato senza ripubblicare, né tantomeno
rivelare, tutto il resto. Una prova di inclusione per una foglia è l'elenco
ordinato degli hash dei nodi fratelli lungo il percorso da quella foglia fino alla
radice: un percorso di fratelli O(log n). Un verificatore ripiega la foglia e i
fratelli risalendo l'albero secondo RFC 9162 e accetta la prova se, e solo se, la
radice ricostruita coincide byte per byte con la radice pubblicata.
Poiché gli alberi RFC 9162 non sono riempiti fino a una potenza di due, una foglia sul bordo destro di un albero non bilanciato può avere un percorso più corto rispetto a una foglia sul lato pieno. Il controllo che fa fede è perciò algoritmico (il ripiegamento riproduce la radice?), mai un confronto tra le lunghezze delle prove.
Perché il raggruppamento in lotti conta
Una sola transazione e una sola radice da 32 byte possono rappresentare migliaia o milioni di
foglie. Chiunque sia in possesso di una prova O(log n) può in seguito dimostrare "questo
elemento era nel mio elenco", mentre ogni foglia non divulgata resta privata: la radice non rivela
nulla delle foglie su cui si impegna.
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.
Registri degli algoritmi
I registri di identificatori per hash, AEAD, KEM, KDF e firme, e la regola di agilità che rende la migrazione post-quantistica additiva anziché incompatibile.