Anleitungen

Anleitungen · Teil 4 von 8

Einen versiegelten PoE erstellen

Ein einfacher PoE belegt, dass ein bestimmter Inhalt existiert hat. Ein versiegelter PoE belegt dasselbe, hält den Inhalt selbst aber geheim: Sie verschlüsseln die Bytes für einen oder mehrere Empfängerschlüssel, speichern nur den Chiffretext und verankern den Datensatz on-chain (in der Blockchain). Jede und jeder kann sehen, dass der Datensatz existiert, und seine Struktur verifizieren; entschlüsseln können die Nutzdaten aber nur diejenigen, die einen passenden privaten Schlüssel besitzen. Das Umschlagformat beschreibt Versiegelter PoE, das Bedrohungsmodell Versiegelt bis zur Abholung.

Empfänger adressieren

Ein Empfänger wird über eine Zeichenkette im age-Stil identifiziert. Es gibt zwei Arten, die sich am Präfix unterscheiden lassen:

  • age1…: ein klassischer X25519-Schlüssel (32 Byte).
  • age1pqc…: ein hybrider X-Wing-Schlüssel (ML-KEM-768 + X25519, 1216 Byte).

X-Wing (mlkem768x25519) ist der Standard-KEM. Er bleibt auch gegenüber einem künftigen Quantenangreifer sicher, und jede Identität verfügt stets über eine age1pqc…-Adresse.

Ein Empfänger teilt Ihnen seine Zeichenkette über einen separaten, vertrauenswürdigen Kanal mit. Mit parseAgeRecipient wandeln Sie sie zurück in den rohen öffentlichen Schlüssel, den die Versiegelungs-Hilfsfunktion benötigt:

import { parseAgeRecipient } from '@cardanowall/sdk-ts';

const them = parseAgeRecipient('age1pqc…'); // { kem: 'mlkem768x25519', publicKey: Uint8Array }

Wenn Sie selbst einen 32-Byte-Seed besitzen, liefert Ihnen recipientsFromSeed beide eigenen Adressen. Geben Sie eine davon weiter, damit andere an Sie versiegeln können, und nehmen Sie Ihren eigenen Schlüssel in die Empfängerliste auf, um den Lesezugriff auf das von Ihnen Versendete zu behalten:

import { recipientsFromSeed } from '@cardanowall/sdk-ts';

const me = recipientsFromSeed(mySeed); // { age: 'age1…', age1pqc: 'age1pqc…' }

Versiegeln und veröffentlichen

Das Versiegeln läuft über ein Gateway, das die Cardano-Transaktion erstellt und verteilt und den Chiffretext jedes Elements für Sie speichert. Richten Sie den Client auf das von Ihnen genutzte Gateway; das SDK ist gateway-unabhängig.

publishSealed erwartet rohe öffentliche Empfängerschlüssel, entnehmen Sie also publicKey aus jeder geparsten Adresse. Alle Empfänger müssen denselben KEM verwenden, halten Sie age1pqc…-Schlüssel daher beieinander. items ist eine Liste, Sie können also einen ganzen Satz Dateien in einem einzigen Datensatz an dieselben Empfänger versiegeln; hier enthält sie nur eine einzelne Nutzlast:

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

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

const content = new TextEncoder().encode('the secret payload');
const recipients = ['age1pqc…recipient', me.age1pqc].map((r) => parseAgeRecipient(r).publicKey);

const result = await client.poe.publishSealed({
  items: [{ content }],
  recipients,
  maxUsdMicros: 2_000_000n, // refuse to spend more than $2
  // kem defaults to 'mlkem768x25519' (X-Wing); pass 'x25519' only for age1… keys.
});

console.log(result.response.id, result.uris);

Es gibt keinen separaten quote-Aufruf und kein eigenes Schätzen von recordBytes: publishSealed misst den exakten versiegelten Datensatz, schreibt einen Preis fest, lädt jeden Chiffretext hoch und reicht ihn ein — alles in einem einzigen Aufruf. maxUsdMicros ist Ihre Obergrenze; die Veröffentlichung verweigert sich, wenn der angebotene Preis darüber liegt, und prüft die Obergrenze erneut gegen einen frischen Preis, falls ein langsamer Upload die erste Festschreibung überdauert hat — so kann ein großer Upload seinem Angebot niemals davonlaufen. Ihr Seed und der Klartext verlassen Ihren Rechner dabei niemals im Klartext.

Standardmäßig ist die On-Chain-Behauptung jedes Elements sein sha2-256-Digest. Um einen zweiten Digest in denselben Datensatz zu binden, hashen Sie das Element unter beiden Algorithmen zugleich: Übergeben Sie publishSealed im SDK hashAlgs: ['sha2-256', 'blake2b-256'], oder wiederholen Sie beim seal des CLI --hash-alg sha2-256 --hash-alg blake2b-256. Die beiden Registry-Algorithmen verhalten sich in jedem Versiegelungsmodus identisch.

An eine Passphrase versiegeln

Nicht jede und jeder hat einen Schlüssel. Manchmal möchten Sie an Personen versiegeln, die eine geheime Phrase teilen, keinen Schlüssel — ein Team-Passwort, eine am Telefon vorgelesene Phrase, einen Wert, der bereits in Ihren CI-Secrets liegt. Geben Sie seal eine Passphrase statt --to, und jede und jeder, die sie kennt, kann den Datensatz öffnen: keine age-Adresse, kein Schlüsselaustausch.

cardanowall seal \
  --file ./contract.pdf \
  --passphrase-file ./pw.txt \
  --base-url https://your-gateway.example \
  --api-key "$CW_API_KEY"

Ein Datensatz wird an Empfänger oder an eine Passphrase versiegelt, niemals an beide — --passphrase gemeinsam mit --to/--to-self zu übergeben, wird abgewiesen. Halten Sie die Phrase von der Befehlszeile fern: --passphrase-file, --passphrase-stdin oder die Umgebungsvariable CARDANOWALL_PASSPHRASE lesen sie alle ein, ohne dass sie in der Shell-History landet. Der Content-Key wird mit Argon2id aus der Phrase gestreckt, sodass allein eine starke Passphrase mit hoher Entropie zwischen dem Chiffretext und jemandem steht, der ihn zu erraten versucht — dieser Weg ist nur so sicher wie die Phrase, die Sie wählen.

Zum Öffnen braucht es nichts als die Phrase. Der eigenständige Verifizierer entschlüsselt und prüft den Inhalts-Hash erneut; der inbox-Befehl schreibt den wiederhergestellten Klartext in eine Datei:

cardanowall verify <tx-hash> --passphrase-file ./pw.txt
cardanowall inbox decrypt <tx-hash> --passphrase-file ./pw.txt --out ./recovered.pdf

Das Ablösen funktioniert unter beiden Modi gleich. Ein erneutes Versiegeln ist stets ein brandneuer Datensatz — die Verschlüsselung wird jedes Mal randomisiert (ein frischer Content-Key und eine frische Nonce sowie ein frisches Argon2id-Salt auf dem Passphrase-Weg), sodass die Bytes niemals dedupliziert werden. Um ein frisches Versiegeln als Nachfolger eines früheren zu markieren — eine korrigierte Datei, eine geänderte Empfängermenge — verweisen Sie mit --supersedes darauf zurück:

cardanowall seal --file ./contract-v2.pdf --to age1pqc…recipient --supersedes <tx-hash>

Nach einem Absturz fortsetzen

Das Versiegeln zieht jedes Mal einen frischen Content-Key, eine frische Nonce und frisches KEM-Material pro Slot; ein naiver erneuter Versuch würde also neu verschlüsseln, einen zweiten Chiffretext-Upload bezahlen und andere Datensatz-Bytes erzeugen. Für CI-Jobs und mehrere Gigabyte große Nutzlasten teilen Sie den Ablauf: sealPrepare verschlüsselt offline und liefert ein portables Artefakt, das Sie speichern können; submitSealed führt die Online-Hälfte dagegen aus.

import { sealPrepare, preparedSealToJson, preparedSealFromJson } from '@cardanowall/sdk-ts';

// Phase 1 — pure and offline: encrypt every item to the recipient set.
const prepared = sealPrepare({ items: [{ content }], recipients });
await save(preparedSealToJson(prepared)); // the portable prepared_seal_json_v1 artifact

// Phase 2 — online: quote, upload each ciphertext, publish. If it throws after
// an upload, the error's `uploads` carry validated receipts — pass them back as
// `uploaded` and the finished uploads are never paid for again.
const submission = await client.poe.submitSealed({
  prepared: preparedSealFromJson(await load()),
  maxUsdMicros: 2_000_000n,
});
console.log(submission.response.id, submission.uris);

publishSealed oben ist lediglich sealPrepare + submitSealed in einem Aufruf; die Python- und Rust-SDKs bieten dasselbe Paar.

Wenn die Veröffentlichung auf halbem Weg scheitert

Das CLI erreicht dieselbe Wiederaufnahme, ohne den zweiphasigen Code. Ein seal --to …-Lauf, der nach einem Chiffretext-Upload abbricht, schreibt eine geheimnisfreie <seal-fingerprint>.l309-seal-resume.json (die abgeschlossenen Uploads, keine Schlüssel und kein Klartext). Führen Sie ihn mit cardanowall seal --resume <seal-fingerprint>.l309-seal-resume.json erneut aus: Es holt ein neues Preisangebot ein, erledigt nur die noch ausstehenden Uploads und veröffentlicht. Ein signierter Lauf braucht seinen Seed erneut; Passphrase-Versiegelungen halten keinen Fortsetzungszustand vor, führen Sie diese daher von vorn aus.

Mit Python

cardanowall-sdk ist ein byteidentischer Zwilling: derselbe KEM-Standard, derselbe Umschlag.

import asyncio
import os
from cardanowall import Label309Client, parse_age_recipient

async def main():
    content = b"the secret payload"
    recipients = [parse_age_recipient("age1pqc…recipient").public_key]

    async with Label309Client(
        base_url="https://your-gateway.example",
        api_key=os.environ["CW_API_KEY"],
    ) as client:
        submission = await client.poe.publish_sealed(
            items=[content],           # a list of plaintext items (bytes or str)
            recipients=recipients,
            max_usd_micros=2_000_000,  # refuse to spend more than $2
        )
        print(submission.response["id"], submission.uris)

asyncio.run(main())

Für einen fortsetzbaren Ablauf liefert seal_prepare(...) (aus cardanowall.client) das vorbereitete Artefakt, und client.poe.submit_sealed(prepared=...) führt die Online-Hälfte aus — dieselben zwei Phasen wie oben.

Mit Rust

Das cardanowall-Crate versiegelt über denselben Gateway-Client. parse_age_recipient dekodiert jede Adresse in den rohen Schlüssel, und der KEM ist standardmäßig der X-Wing-Hybrid:

use cardanowall::client::{
    Label309Client, Label309ClientConfig, PublishSealedInput, SealPrepareItem,
};
use cardanowall::recipient::parse_age_recipient;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Label309Client::new(Label309ClientConfig {
        base_url: Some("https://your-gateway.example".into()),
        api_key: std::env::var("CW_API_KEY").ok(),
    })?;

    let content = b"the secret payload".to_vec();
    let recipients = vec![parse_age_recipient("age1pqc…recipient")?.public_key];

    // One-shot: seal, quote the exact record size, upload, publish. The KEM
    // defaults to mlkem768x25519 (X-Wing); `with_kem` selects x25519.
    let submission = client.poe().publish_sealed(
        &PublishSealedInput::new(vec![SealPrepareItem::new(&content)], recipients)
            .with_max_usd_micros(2_000_000), // refuse to spend more than $2
    )?;

    println!("{}", submission.response.id);
    Ok(())
}

Für einen fortsetzbaren Ablauf liefert seal_prepare ein PreparedSeal, das Sie speichern können, und client.poe().submit_sealed(&SubmitSealedInput::new(&prepared)) führt die Online-Hälfte aus. Auch das CLI versiegelt von der Kommandozeile aus, mit denselben Phasen im Hintergrund: cardanowall seal --file <path> --to <address>.

Sobald der Datensatz endgültig in die Blockchain aufgenommen ist, findet ihn jeder Empfänger, entschlüsselt die Nutzdaten mit seinem privaten Schlüssel und berechnet den Klartext-Hash neu, um den Kreis zu schließen. Das ist die Empfänger-Seite von Einen Datensatz verifizieren.

Versiegeln Sie auch an sich selbst

publishSealed fügt Sie nicht stillschweigend zur Empfängerliste hinzu. Nehmen Sie keinen Ihrer eigenen Schlüssel auf, veröffentlichen Sie einen Datensatz, den Sie nie wieder lesen können. Nehmen Sie me.age1pqc (oder me.age) unter die Empfänger auf, wann immer Sie den Zugriff auf das von Ihnen Versendete behalten möchten.