MACGUARD · DOCUMENTACIÓN
English

Árboles Merkle

Cómo se calcula `rules_root` y la raíz del registro, con un ejemplo que puedes recalcular.

Reglas: rules_root

Los ficheros se ordenan por ruta (bytes UTF-8). La hoja de cada fichero es SHA-256(0x00 || ruta || 0x00 || sha256_hex) y los nodos internos SHA-256(0x01 || izquierda || derecha). Un árbol de n hojas se parte en la mayor potencia de dos menor que n (RFC 6962 / RFC 9162). La raíz se guarda en el manifiesto firmado.

Diagrama: Árbol Merkle de los ficheros de reglas

Dibujando el diagrama…

Descripción del diagrama

Los ficheros se ordenan por ruta. Cada hoja es el SHA-256 del byte 0x00 seguido de la ruta, un byte cero y el SHA-256 del fichero en hexadecimal. Con cinco hojas el árbol se parte en la mayor potencia de dos menor que cinco: cuatro hojas a la izquierda y una a la derecha. Los nodos internos son el SHA-256 de 0x01, izquierda y derecha. El hash superior es rules_root, guardado en el manifiesto firmado.

Código Mermaid
flowchart BT
  L0["leaf 0<br/>H(00 || path0 0 sha0)"]:::sec
  L1["leaf 1"]:::sec
  L2["leaf 2"]:::sec
  L3["leaf 3"]:::sec
  L4["leaf 4"]:::sec
  N01["node<br/>H(01 || leaf0 || leaf1)"]:::val
  N23["node<br/>H(01 || leaf2 || leaf3)"]:::val
  N03["node<br/>H(01 || N01 || N23)"]:::val
  R["rules_root<br/>H(01 || N03 || leaf4)"]:::gate
  M["manifest.json<br/>signed with Sigstore"]:::pub
  L0 --> N01
  L1 --> N01
  L2 --> N23
  L3 --> N23
  N01 --> N03
  N23 --> N03
  N03 --> R
  L4 --> R
  R --> M
  classDef src fill:#cfe3ff,stroke:#2f6df0,color:#0b1b33
  classDef val fill:#ffe9b8,stroke:#b8860b,color:#2b1d00
  classDef sec fill:#cdeedd,stroke:#1b6e3c,color:#08210f
  classDef pub fill:#e4d7ff,stroke:#6a3fd1,color:#1b0b3b
  classDef qua fill:#ffd4cc,stroke:#c0392b,color:#33100b
  classDef gate fill:#ffffff,stroke:#111111,stroke-width:3px,color:#111111

Prueba a recalcularlo tú con el manifest.json de la release (solo biblioteca estándar de Python). Debe imprimir True:

import hashlib, json
m = json.load(open('manifest.json'))
H = hashlib.sha256
leaf = lambda b: H(b'\x00' + b).digest()
node = lambda l, r: H(b'\x01' + l + r).digest()
def mth(xs):
    if len(xs) == 1: return xs[0]
    k = 1
    while k * 2 < len(xs): k *= 2
    return node(mth(xs[:k]), mth(xs[k:]))
files = sorted(m['files'], key=lambda f: f['path'].encode())
root = mth([leaf(f"{f['path']}\0{f['sha256']}".encode()) for f in files]).hex()
print(root, root == m['rules_root'])

Efecto práctico: añadir, quitar o modificar un solo byte de un fichero, o añadir un fichero extra al paquete, cambia rules_root y la verificación falla.

Registro: raíz del log

Cada línea de logs/log.jsonl es una hoja: SHA-256(0x00 || bytes exactos de la línea). Se combinan con la misma regla. La raíz y el número de entradas forman el checkpoint (ver Registro y checkpoints).

Diagrama: Árbol Merkle del registro de transparencia

Dibujando el diagrama…

Descripción del diagrama

Cada línea de logs/log.jsonl es una hoja: SHA-256 de 0x00 seguido de los bytes exactos de la línea. Las hojas se combinan con hashes de nodo con prefijo 0x01 hasta una raíz. La raíz y el número de entradas se escriben en un checkpoint y el checkpoint se firma. La prueba de inclusión de la entrada 4 son los hashes hermanos a lo largo de su camino.

Código Mermaid
flowchart BT
  E0["entry 0<br/>log.jsonl line"]:::src
  E1["entry 1"]:::src
  E2["entry 2"]:::src
  E3["entry 3"]:::src
  E4["entry 4<br/>the release entry"]:::pub
  E5["entry 5"]:::src
  A["node A = H(01 || L0 || L1)"]:::val
  B["node B = H(01 || L2 || L3)"]:::val
  C["node C = H(01 || L4 || L5)"]:::val
  AB["node AB"]:::val
  ROOT["log root"]:::gate
  CP["checkpoint<br/>origin, size, root"]:::sec
  E0 -->|leaf hash| A
  E1 --> A
  E2 --> B
  E3 --> B
  E4 -->|leaf hash| C
  E5 --> C
  A --> AB
  B --> AB
  AB --> ROOT
  C --> ROOT
  ROOT --> CP
  classDef src fill:#cfe3ff,stroke:#2f6df0,color:#0b1b33
  classDef val fill:#ffe9b8,stroke:#b8860b,color:#2b1d00
  classDef sec fill:#cdeedd,stroke:#1b6e3c,color:#08210f
  classDef pub fill:#e4d7ff,stroke:#6a3fd1,color:#1b0b3b
  classDef qua fill:#ffd4cc,stroke:#c0392b,color:#33100b
  classDef gate fill:#ffffff,stroke:#111111,stroke-width:3px,color:#111111

Pruebas

LÍMITE Las páginas web recalculan estas cuentas en el navegador, pero no verifican firmas Sigstore. Para eso usa cosign, el verificador público o la app.