Guide

Guide · Parte 4 di 6

Costruire una PoE sigillata

Una PoE semplice dimostra che un certo contenuto è esistito. Una PoE sigillata dimostra la stessa cosa mantenendo segreto il contenuto stesso: cifri i byte per una o più chiavi di destinatario, conservi solo il testo cifrato e ancori il record on-chain (sulla blockchain). Chiunque può vedere che il record esiste e verificarne la struttura; solo chi possiede una chiave privata corrispondente può decifrare il payload. Vedi Sealed PoE per il formato della busta e Sealed until claimed per il modello di minaccia.

Indirizzare i destinatari

Un destinatario è identificato da una stringa in stile age. Ne esistono due tipi, distinti dal prefisso:

  • age1… — una chiave classica X25519 (32 byte).
  • age1pqc… — una chiave ibrida X-Wing (ML-KEM-768 + X25519, 1216 byte).

X-Wing (mlkem768x25519) è il KEM predefinito: resta sicuro contro un futuro avversario quantistico, e ogni identità dispone sempre di un indirizzo age1pqc….

Un destinatario ti consegna la propria stringa fuori banda. La decodifichi nella chiave pubblica grezza di cui ha bisogno l'helper di sigillatura con parseAgeRecipient:

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

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

Se hai tu stesso un seed a 32 byte, recipientsFromSeed ti restituisce entrambi i tuoi indirizzi: condividine uno perché altri possano sigillare per te, e includi la tua chiave nella lista dei destinatari per mantenere l'accesso in lettura a ciò che invii:

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

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

Sigillare e pubblicare

La sigillatura passa per un gateway, che costruisce e trasmette la transazione Cardano e conserva il testo cifrato al posto tuo. Punta il client al gateway che usi; l'SDK è indipendente dal gateway.

publishSealed accetta le chiavi pubbliche grezze dei destinatari, quindi raccogli la publicKey da ciascun indirizzo decodificato. Tutti i destinatari devono condividere lo stesso KEM: tieni insieme le chiavi age1pqc…. Prima blocca un prezzo con quote, poi pubblica:

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 quote = await client.poe.quote({
  recordBytes: 512,
  recipientCount: recipients.length,
  fileBytesTotal: content.length,
});

const result = await client.poe.publishSealed({
  content,
  recipients,
  quoteId: quote.quote_id,
  // kem defaults to 'mlkem768x25519' (X-Wing); pass 'x25519' only for age1… keys.
});

console.log(result.tx_hash);

L'helper cifra il contenuto, carica il testo cifrato, lega il suo URI ar:// e l'hash del testo in chiaro in un record Label 309 e lo invia. Il tuo seed e il testo in chiaro non lasciano mai la tua macchina in chiaro.

Con Python

cardanowall-sdk è un gemello byte per byte: stesso KEM predefinito, stessa busta:

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:
        quote = await client.poe.quote(
            record_bytes=512, recipient_count=len(recipients), file_bytes_total=len(content)
        )
        result = await client.poe.publish_sealed(
            content=content, recipients=recipients, quote_id=quote["quote_id"]
        )
        print(result["tx_hash"])

asyncio.run(main())

Con Rust

Il crate cardanowall sigilla tramite lo stesso client gateway. parse_age_recipient decodifica ogni indirizzo nella chiave grezza, e kem: None mantiene il valore predefinito X-Wing:

use cardanowall::client::{
    Label309Client, Label309ClientConfig, PublishSealedInput, QuoteInput,
};
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];

    let quote = client.poe().quote(&QuoteInput {
        record_bytes: 512,
        recipient_count: recipients.len() as u64,
        file_bytes_total: content.len() as u64,
    })?;

    let result = client.poe().publish_sealed(&PublishSealedInput {
        content,
        recipients,
        quote_id: quote.quote_id,
        hash_alg: None,
        kem: None, // defaults to mlkem768x25519 (X-Wing); set Some for x25519
        signer: None,
        idempotency_key: None,
    })?;

    println!("{:?}", result.tx_hash);
    Ok(())
}

La CLI pubblica record di solo hash e record Merkle; la sigillatura è oggi un flusso disponibile solo via SDK.

Una volta che il record è definitivamente registrato, ogni destinatario lo scopre, decifra il payload con la propria chiave privata e ricalcola l'hash del testo in chiaro per chiudere il cerchio: è la parte del destinatario in Verificare un record.

Sigilla anche per te stesso

publishSealed non ti aggiunge mai in silenzio alla lista dei destinatari. Se non includi una delle tue chiavi, pubblichi un record che non potrai mai più rileggere. Includi me.age1pqc (o me.age) tra i destinatari ogni volta che vuoi mantenere l'accesso a ciò che hai inviato.