Guías

Guías · Parte 5 de 6

Lotes con Merkle

A veces no tiene un solo archivo: tiene mil. Una carpeta de documentos, un flujo de eventos, las entradas de un día en el registro de auditoría. Anclar cada uno en su propia transacción es un derroche. En su lugar, calcule el hash de cada elemento para obtener una hoja, pliegue las hojas en una única raíz de Merkle y publique esa única raíz. Las hojas ordenadas permanecen fuera de la cadena; más adelante podrá demostrar que cualquier elemento concreto estaba en el conjunto con una prueba que crece solo con el logaritmo del tamaño del lote.

Lo importante: construir el árbol y verificar las pruebas es totalmente sin conexión. Sin pasarela, sin cuenta, sin red. Solo la publicación de la raíz recurre a una pasarela; todo lo demás es cálculo puro que puede ejecutar en cualquier lugar, para siempre.

Con la CLI

Pase a merkle build los archivos que quiera anclar. Calcula el hash de cada uno para obtener una hoja, construye la raíz RFC 9162 y emite la lista canónica de hojas:

cardanowall merkle build --file a.pdf --file b.pdf --file c.pdf --json

También puede suministrar hojas precalculadas (un resumen SHA-256 de 64 caracteres hexadecimales por línea) por stdin o mediante --in leaves.txt. Conserve la root (publíquela) y la lista de hojas (la querrá todo aquel que luego necesite demostrar la inclusión).

Para demostrar que un elemento pertenece a una raíz publicada, escriba un pequeño archivo de prueba con la ruta de auditoría y verifíquelo, por completo sin conexión, contra cualquier raíz en la que confíe:

cardanowall merkle verify --root <root-hex> --proof proof.json

La forma de proof.json es { tree_alg, tree_size, index, leaf, proof[] }; pase --leaf <hex> para sustituir la hoja del archivo. El código de salida 0 significa que la hoja está en el árbol, y 1 que no lo está: intégrelo directamente en CI.

Con el SDK de TypeScript

Calcule el hash de sus elementos para obtener las hojas, construya la raíz y las pruebas localmente, y luego publique solo la raíz a través de una pasarela:

import { Label309Client, hash, merkle } from '@cardanowall/sdk-ts';

const items = [docA, docB, docC]; // Uint8Array content
const leaves = items.map((bytes) => hash.sha2256(bytes));

const root = merkle.merkleSha2256Root(leaves);

// Prove item 1 is in the set — no network, no gateway.
const proof = merkle.merkleSha2256InclusionProof(leaves, 1);
const ok = merkle.merkleSha2256VerifyInclusion(leaves[1], 1, leaves.length, proof, root);
console.log(ok); // true

merkleSha2256VerifyInclusion(leaf, index, treeSize, proof, root) es un predicado booleano puro: nunca accede a la red ni lanza una excepción ante una prueba incorrecta, simplemente devuelve false. Cualquiera que disponga de la lista de hojas puede recalcular la raíz y volver a derivar cualquier prueba; quien publicó nunca interviene en el proceso.

Publicar la raíz es el único paso que necesita una pasarela. Cotícela y luego publique las hojas: el SDK calcula la raíz localmente, la pasarela almacena la lista de hojas y ancla el compromiso en cadena:

const client = new Label309Client({
  baseUrl: 'https://your-gateway.example',
  apiKey: process.env.CW_API_KEY,
});

const quote = await client.poe.quote({
  recordBytes: 512,
  recipientCount: 0,
  fileBytesTotal: leaves.length * 32,
});

const published = await client.poe.publishMerkle({
  leaves, // raw 32-byte digests or hex strings
  quoteId: quote.quote_id,
});

console.log(published.root, published.leaf_count, published.tx_hash, published.ar_uri);

Con el SDK de Python

El gemelo byte por byte construye el mismo árbol y las mismas pruebas:

import cardanowall

items = [doc_a, doc_b, doc_c]  # bytes
leaves = [cardanowall.hash.sha2_256(b) for b in items]

root = cardanowall.merkle.merkle_sha2_256_root(leaves)

proof = cardanowall.merkle.merkle_sha2_256_inclusion_proof(leaves, 1)
ok = cardanowall.merkle.merkle_sha2_256_verify_inclusion(
    leaves[1], 1, len(leaves), proof, root
)
print(ok)  # True

Con el SDK de Rust

El crate cardanowall construye el mismo árbol y las mismas pruebas sin conexión, y luego publica solo la raíz a través del cliente de la pasarela:

use cardanowall::client::{
    Label309Client, Label309ClientConfig, MerkleLeaf, PublishMerkleInput, QuoteInput,
};
use cardanowall::{hash, merkle};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let items: Vec<Vec<u8>> = vec![doc_a, doc_b, doc_c]; // content bytes
    let leaves: Vec<[u8; 32]> = items.iter().map(|b| hash::sha256(b)).collect();

    // Build the root and prove inclusion — pure computation, no network.
    let root = merkle::merkle_root(&leaves)?;
    let proof = merkle::merkle_inclusion_proof(&leaves, 1)?;
    let ok = merkle::verify_inclusion(&leaves[1], 1, leaves.len(), &proof, &root);
    println!("{ok}"); // true

    // Publishing the root is the one step that needs a gateway.
    let client = Label309Client::new(Label309ClientConfig {
        base_url: Some("https://your-gateway.example".into()),
        api_key: std::env::var("CW_API_KEY").ok(),
    })?;

    let quote = client.poe().quote(&QuoteInput {
        record_bytes: 512,
        recipient_count: 0,
        file_bytes_total: (leaves.len() * 32) as u64,
    })?;

    let published = client.poe().publish_merkle(&PublishMerkleInput {
        leaves: leaves.iter().map(|l| MerkleLeaf::Bytes(l.to_vec())).collect(),
        quote_id: quote.quote_id,
        hash_alg: None,
        signer: None,
        idempotency_key: None,
        chunk_bytes: None,
    })?;

    println!("{} {} {:?}", published.root, published.leaf_count, published.tx_hash);
    Ok(())
}

Una raíz, muchos elementos, cero confianza

La raíz se compromete con cada hoja y con su posición. Meses después, con solo la lista de hojas y la raíz anclada en la cadena, cualquiera puede volver a derivar una prueba y confirmar que un elemento estaba en el lote, sin su pasarela, sin su servidor y sin su cooperación. Consulte Contenido y hashing para saber cómo se calcula el hash de las hojas, y Publique su primera PoE para el flujo de un solo elemento.