Guias

Guias · Parte 5 de 6

Agrupar em lote com Merkle

Às vezes você não tem um arquivo só — tem mil. Uma pasta de documentos, um fluxo de eventos, um dia inteiro de linhas de log de auditoria. Ancorar cada um em sua própria transação é desperdício. Em vez disso, calcule o hash de cada item em uma folha, combine as folhas em uma única raiz Merkle e publique apenas essa raiz. As folhas ordenadas ficam fora da cadeia; mais tarde, você pode provar que qualquer item isolado pertencia ao conjunto com uma prova que cresce apenas com o logaritmo do tamanho do lote.

O ponto essencial: construir a árvore e verificar as provas é totalmente offline. Sem gateway, sem conta, sem rede. Só a publicação da raiz envolve um gateway — todo o resto é puro processamento, que você pode executar em qualquer lugar, para sempre.

Com a CLI

Passe para o merkle build os arquivos que deseja ancorar. Ele calcula o hash de cada um em uma folha, constrói a raiz RFC 9162 e emite a lista canônica de folhas:

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

Você também pode fornecer folhas pré-computadas — um digest SHA-256 em 64 dígitos hexadecimais por linha — pela entrada padrão (stdin) ou com --in leaves.txt. Guarde o root (que você publica) e a lista de folhas (de que precisará quem quer que vá comprovar a inclusão mais tarde).

Para provar que um item pertence a uma raiz publicada, monte um pequeno arquivo de prova com o caminho de auditoria e verifique-o — inteiramente offline, contra qualquer raiz em que você confie:

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

O formato de proof.json é { tree_alg, tree_size, index, leaf, proof[] }; passe --leaf <hex> para substituir a folha do arquivo. O código de saída 0 significa que a folha está na árvore e 1, que não está — pronto para integrar diretamente ao CI.

Com o SDK TypeScript

Calcule o hash dos seus itens, transformando-os em folhas, construa a raiz e as provas localmente e, depois, publique apenas a raiz por meio de um gateway:

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) é um predicado booleano puro — ele nunca acessa a rede e nunca lança exceção diante de uma prova inválida; apenas retorna false. Qualquer pessoa que tenha a lista de folhas pode recalcular a raiz e reconstruir qualquer prova; quem publicou nunca precisa entrar em cena.

Publicar a raiz é o único passo que envolve um gateway. Faça a cotação e, em seguida, publique as folhas — o SDK calcula a raiz localmente, o gateway guarda a lista de folhas e ancora o compromisso na cadeia:

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);

Com o SDK Python

O gêmeo byte a byte constrói a mesma árvore e as mesmas provas:

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

Com o SDK Rust

O crate cardanowall constrói a mesma árvore e as mesmas provas offline e, depois, publica apenas a raiz por meio do cliente de gateway:

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(())
}

Uma raiz, muitos itens, confiança zero

A raiz fixa cada folha e a respectiva posição. Meses depois — com apenas a lista de folhas e a raiz registrada na cadeia — qualquer pessoa pode reconstruir uma prova e confirmar que um item pertencia ao lote, sem precisar do seu gateway, do seu servidor ou da sua colaboração. Consulte Conteúdo e hashing para entender como as folhas têm seu hash calculado, e Publique sua primeira PoE para o fluxo de item único.