Anleitungen

Anleitungen · Teil 5 von 8

Stapelweise mit Merkle

Manchmal haben Sie nicht eine Datei, sondern tausend. Einen Ordner voller Dokumente, einen Strom von Ereignissen, die Audit-Log-Zeilen eines ganzen Tages. Jedes Element in einer eigenen Transaktion zu verankern wäre verschwenderisch. Hashen Sie stattdessen jeden Eintrag zu einem Blatt, falten Sie die Blätter zu einer einzigen Merkle-Wurzel und veröffentlichen Sie nur diese eine Wurzel. Die geordnete Blätterliste bleibt off-chain; später lässt sich die Aufnahme eines einzelnen Elements in die Menge mit einem Beweis nachweisen, der nur logarithmisch mit der Stapelgröße wächst.

Das Wesentliche: Den Baum zu erstellen und Beweise zu verifizieren ist vollständig offline möglich. Kein Gateway, kein Konto, kein Netzwerk. Allein das Veröffentlichen der Wurzel berührt ein Gateway; alles andere ist reine Berechnung, die Sie überall und jederzeit ausführen können.

Mit dem CLI

Übergeben Sie merkle build die Dateien, die Sie verankern möchten. Es hasht jede zu einem Blatt, erstellt die RFC 9162-Wurzel und gibt die kanonische Blätterliste aus:

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

Sie können auch vorberechnete Blätter übergeben (einen 64-stelligen SHA-256-Digest in Hex pro Zeile), über stdin oder per --in leaves.txt. Bewahren Sie die root auf (sie wird veröffentlicht) ebenso wie die Blätterliste (alle, die später eine Aufnahme nachweisen müssen, werden sie benötigen).

Um nachzuweisen, dass ein Element zu einer veröffentlichten Wurzel gehört, schreiben Sie eine kleine Proof-Datei mit dem Prüfpfad und verifizieren sie, vollständig offline und gegen jede Wurzel, der Sie vertrauen:

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

Die Struktur von proof.json ist { tree_alg, tree_size, index, leaf, proof[] }; mit --leaf <hex> überschreiben Sie das aus der Datei abgeleitete Blatt. Der Exit-Code 0 bedeutet, das Blatt liegt im Baum, 1 bedeutet, es ist nicht enthalten. Damit lässt es sich direkt in CI einbinden.

Die Wurzel zu veröffentlichen ist der einzige Schritt, der das Netzwerk berührt. Übergeben Sie submit --merkle die Blätterliste, die merkle build ausgegeben hat (oder eine schlichte Datei mit einem 64-stelligen Hex-Blatt pro Zeile); das Gateway verankert die Wurzel und speichert die Liste. Eine Merkle-Veröffentlichung kann einen früheren Datensatz auch direkt ablösen: Mit --supersedes <tx-hash> markieren Sie diesen Stapel als Nachfolger einer früheren Wurzel, etwa eines neu gebündelten Ordners oder eines korrigierten Tages an Log-Zeilen:

cardanowall submit \
  --merkle ./leaves.txt \
  --supersedes <tx-hash> \
  --base-url https://your-gateway.example \
  --api-key "$CW_API_KEY"

Mit dem TypeScript-SDK

Hashen Sie Ihre Elemente zu Blättern, erstellen Sie Wurzel und Beweise lokal und veröffentlichen Sie dann nur die Wurzel über ein 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) ist ein reines boolesches Prädikat. Es stellt nie eine Netzwerkverbindung her und wirft bei einem ungültigen Beweis keine Ausnahme, sondern gibt schlicht false zurück. Wer die Blätterliste besitzt, kann die Wurzel neu berechnen und jeden Beweis neu ableiten; der Aussteller ist dabei nie beteiligt.

Die Wurzel zu veröffentlichen ist der einzige Schritt, der ein Gateway braucht. publishMerkle bepreist die Veröffentlichung selbst: Es misst den exakten Datensatz, schreibt unter Ihrer maxUsdMicros-Obergrenze einen Preis fest, lädt die Blätterliste hoch und verankert die Festlegung on-chain. Das SDK berechnet die Wurzel lokal; Sie schätzen nie die Datensatzgröße und schreiben nie von Hand ein Angebot fest:

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

const published = await client.poe.publishMerkle({
  leaves, // raw 32-byte digests or hex strings
  leafAlg: 'sha2-256', // advisory: names how the leaves were computed
  maxUsdMicros: 2_000_000n, // refuse to spend more than $2
});

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

Mit dem Python-SDK

Der byteidentische Zwilling erstellt denselben Baum und dieselben Beweise:

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

Mit dem Rust-SDK

Das cardanowall-Crate erstellt denselben Baum und dieselben Beweise offline und veröffentlicht dann nur die Wurzel über den Gateway-Client:

use cardanowall::client::{
    Label309Client, Label309ClientConfig, MerkleLeaf, PublishMerkleInput,
};
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. `publish_merkle`
    // quotes the exact record size internally and enforces the price cap.
    let client = Label309Client::new(Label309ClientConfig {
        base_url: Some("https://your-gateway.example".into()),
        api_key: std::env::var("CW_API_KEY").ok(),
    })?;

    let published = client.poe().publish_merkle(
        &PublishMerkleInput::new(leaves.iter().map(|l| MerkleLeaf::Bytes(l.to_vec())).collect())
            .with_leaf_alg("sha2-256") // advisory: names how the leaves were computed
            .with_max_usd_micros(2_000_000), // refuse to spend more than $2
    )?;

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

Eine Wurzel, viele Elemente, kein Vertrauen nötig

Die Wurzel legt sich auf jedes Blatt und dessen Position fest. Monate später, allein mit der Blätterliste und der on-chain verankerten Wurzel, kann jede und jeder einen Beweis neu ableiten und bestätigen, dass ein Element im Stapel enthalten war, ganz ohne Ihr Gateway, Ihren Server oder Ihre Mitwirkung. Wie Blätter gehasht werden, beschreibt Inhalt und Hashing; den Ablauf für ein einzelnes Element zeigt Veröffentlichen Sie Ihren ersten PoE.