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

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.

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

IdentificatoreAlgoritmoRiferimentoImpronta
sha2-256SHA-256FIPS 180-432 B
blake2b-256BLAKE2b-256RFC 769332 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.

CBOR
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 n

Una 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.

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