ExistBefore

Audit della prova di inclusione T1

Verifica una singola attestazione contro il batch giornaliero T1.

Ogni attestazione registrata tramite ExistBefore (sito gratuito o API CertiSigma) è raggruppata con migliaia di altre in un albero di Merkle giornaliero. La radice di quell'albero — e solo la radice — è timestampata da una TSA qualificata eIDAS ai sensi dell'Articolo 41 del Regolamento 910/2014. Questa pagina ti permette di provare, nel tuo browser o con qualsiasi libreria RFC 6962, che una data attestazione è una delle foglie di quel batch firmato.

La prova è piccola (una manciata di hash fratelli da 32 byte), il verificatore è breve (poche decine di righe) e la matematica è la stessa usata da Certificate Transparency per controllare i certificati TLS del mondo. Per eseguire il controllo non serve fidarsi di ExistBefore o di CertiSigma.

Verifica una prova nel tuo browser.

Apri la pagina pubblica di prova per qualsiasi attestazione, espandi il pannello Mostra prova di inclusione nel batch e copia il JSON mostrato. Incollalo qui. La verifica viene eseguita localmente con WebCrypto — nulla transita a nessun server.

Campi richiesti: root, leaf, leafIndex, treeSize, auditPath. Tutti gli hash sono in esadecimale minuscolo (64 caratteri).

Verifica con qualsiasi libreria RFC 6962.

Non devi fidarti del JavaScript di questa pagina. Il formato della prova è l'audit path RFC 6962 (Certificate Transparency) con prefisso foglia 0x00 e prefisso nodo interno 0x01. Qualsiasi libreria che implementa lo standard produrrà la stessa risposta. Seguono due implementazioni di riferimento minimali.

Python (solo libreria standard)

import hashlib
import json

def leaf_hash(b: bytes) -> bytes:
    return hashlib.sha256(b'\x00' + b).digest()

def node_hash(left: bytes, right: bytes) -> bytes:
    return hashlib.sha256(b'\x01' + left + right).digest()

def verify_inclusion(proof: dict) -> bool:
    """Verifica della prova di inclusione RFC 6962."""
    fn = proof['leafIndex']
    sn = proof['treeSize'] - 1
    if fn > sn or fn < 0:
        return False
    leaf_bytes = bytes.fromhex(proof['leaf'])
    # Se proof['leaf'] è già l'hash della foglia (32B), usalo direttamente.
    # Altrimenti calcola leaf_hash(leaf_bytes).
    r = leaf_bytes if len(leaf_bytes) == 32 else leaf_hash(leaf_bytes)
    for sibling_hex in proof['auditPath']:
        sibling = bytes.fromhex(sibling_hex)
        if sn == 0:
            return False  # path troppo lungo
        if (fn & 1) or (fn == sn):
            r = node_hash(sibling, r)
            while not (fn & 1):
                fn >>= 1
                sn >>= 1
        else:
            r = node_hash(r, sibling)
        fn >>= 1
        sn >>= 1
    if sn != 0:
        return False  # path troppo corto
    return r.hex() == proof['root']

# Uso:
proof = json.loads(open('proof.json').read())
print('VALIDA' if verify_inclusion(proof) else 'NON VALIDA')

JavaScript (browser, WebCrypto, zero dipendenze)

async function sha256(bytes) {
  return new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
}
const hex = (b) => [...b].map(x => x.toString(16).padStart(2, '0')).join('');
const unhex = (s) => new Uint8Array(s.match(/../g).map(b => parseInt(b, 16)));

async function leafHash(b)         { return sha256(Uint8Array.of(0x00, ...b)); }
async function nodeHash(left, right) { return sha256(Uint8Array.of(0x01, ...left, ...right)); }

export async function verifyInclusion(proof) {
  let fn = proof.leafIndex;
  let sn = proof.treeSize - 1;
  if (fn > sn || fn < 0) return false;
  const leafBytes = unhex(proof.leaf);
  let r = leafBytes.length === 32 ? leafBytes : await leafHash(leafBytes);
  for (const siblingHex of proof.auditPath) {
    const sibling = unhex(siblingHex);
    if (sn === 0) return false;
    if ((fn & 1) || fn === sn) {
      r = await nodeHash(sibling, r);
      while (!(fn & 1) && fn !== 0) { fn >>= 1; sn >>= 1; }
    } else {
      r = await nodeHash(r, sibling);
    }
    fn >>= 1;
    sn >>= 1;
  }
  return sn === 0 && hex(r) === proof.root;
}

Entrambi gli snippet sono rilasciati sotto licenza MIT; copiali nel tuo toolkit di audit. Una qualsiasi delle due implementazioni, eseguita sul JSON che la pagina di prova esporta, deve restituire lo stesso booleano di questa pagina.

Cosa prova davvero un verdetto positivo.

Cosa un verdetto positivo non prova: che il contenuto originale non sia stato modificato da quando l'attestazione è stata creata (confronta il suo SHA-256 attuale con quello nel certificato per verificarlo) o che il contenuto del file significhi ciò che il nome del file suggerisce (la semantica è fuori scope; sono ancorati solo i byte).