Guide · Parte 4 di 8
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 di ogni elemento 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…. items è una lista, quindi puoi sigillare
un intero insieme di file per gli stessi destinatari in un unico record; qui contiene un
solo payload:
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);Non c'è una chiamata quote separata né una tua stima di recordBytes: publishSealed
misura il record sigillato esatto, blocca un prezzo, carica ogni testo cifrato e lo invia,
tutto in un'unica chiamata. maxUsdMicros è il tuo tetto massimo; la pubblicazione viene
rifiutata se il prezzo offerto lo supera e il tetto viene ricontrollato rispetto a un
prezzo aggiornato se un caricamento lento è sopravvissuto al primo blocco, così un
caricamento grande non può mai superare il suo preventivo. Il tuo seed e il testo in chiaro
non lasciano mai la tua macchina in chiaro.
Per impostazione predefinita l'affermazione on-chain di ogni elemento è il suo digest
sha2-256. Per legare un secondo digest allo stesso record, calcola l'hash dell'elemento
sotto entrambi gli algoritmi: passa hashAlgs: ['sha2-256', 'blake2b-256'] a publishSealed
nell'SDK, oppure ripeti --hash-alg sha2-256 --hash-alg blake2b-256 sul comando seal della
CLI. I due algoritmi del registro si comportano in modo identico in ogni modalità di
sigillatura.
Sigillare verso una passphrase
Non tutti hanno una chiave. A volte vuoi sigillare verso persone che condividono una frase
segreta, non una chiave: una password di squadra, una frase dettata durante una chiamata,
un valore già presente tra i secret della tua CI. Dai a seal una passphrase invece di
--to, e chiunque la conosca può aprire il record: nessun indirizzo age, nessuno scambio
di chiavi.
cardanowall seal \
--file ./contract.pdf \
--passphrase-file ./pw.txt \
--base-url https://your-gateway.example \
--api-key "$CW_API_KEY"Un record è sigillato verso dei destinatari oppure verso una passphrase, mai entrambi:
passare --passphrase insieme a --to/--to-self viene rifiutato. Tieni la frase fuori
dalla riga di comando: --passphrase-file, --passphrase-stdin o la variabile d'ambiente
CARDANOWALL_PASSPHRASE la leggono tutte senza che finisca nella cronologia della shell. La
chiave di contenuto viene derivata dalla frase con Argon2id, quindi una passphrase robusta e
ad alta entropia è tutto ciò che si frappone tra il testo cifrato e chi prova a indovinarla:
questo percorso è sicuro solo quanto la frase che scegli.
Per aprirlo non serve altro che la frase. Il verificatore autonomo decifra e ricontrolla l'hash del contenuto; l'inbox scrive il testo in chiaro recuperato su un file:
cardanowall verify <tx-hash> --passphrase-file ./pw.txt
cardanowall inbox decrypt <tx-hash> --passphrase-file ./pw.txt --out ./recovered.pdfLa sostituzione funziona allo stesso modo in entrambe le modalità. Una nuova sigillatura è
sempre un record del tutto nuovo: la cifratura è randomizzata ogni volta (una nuova chiave di
contenuto e un nuovo nonce, più un nuovo salt Argon2id sul percorso con passphrase), così i
byte non si deduplicano mai. Per contrassegnare una nuova sigillatura come successore di una
precedente —un file corretto, un insieme di destinatari ruotato— rimandala indietro con
--supersedes:
cardanowall seal --file ./contract-v2.pdf --to age1pqc…recipient --supersedes <tx-hash>Riprendere dopo un crash
La sigillatura estrae ogni volta una nuova chiave di contenuto, un nuovo nonce e nuovo
materiale KEM per slot, quindi un tentativo ingenuo ricifrerebbe, pagherebbe un secondo
caricamento del testo cifrato e produrrebbe byte del record diversi. Per i job di CI e i
payload di più gigabyte, dividi il flusso: sealPrepare cifra offline e restituisce un
artefatto portabile che puoi conservare; submitSealed esegue la metà online su di esso.
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 qui sopra non è altro che sealPrepare + submitSealed in un'unica
chiamata; gli SDK Python e Rust espongono la stessa coppia.
Se la pubblicazione fallisce a metà
La CLI si riprende allo stesso modo, senza il codice a due fasi. Un'esecuzione seal --to … che
muore dopo il caricamento di un testo cifrato scrive un <seal-fingerprint>.l309-seal-resume.json
privo di segreti (i caricamenti completati, e nessuna chiave o testo in chiaro). Rilancia con
cardanowall seal --resume <seal-fingerprint>.l309-seal-resume.json: richiede un nuovo preventivo,
completa solo i caricamenti ancora dovuti e pubblica. Un'esecuzione firmata ha di nuovo bisogno del
suo seed; le sigillature con passphrase non conservano alcuno stato di ripresa, perciò vanno
rilanciate da capo.
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:
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())Per un flusso ripristinabile, seal_prepare(...) (da cardanowall.client) restituisce
l'artefatto preparato e client.poe.submit_sealed(prepared=...) esegue la metà online: le
stesse due fasi di cui sopra.
Con Rust
Il crate cardanowall sigilla tramite lo stesso client gateway. parse_age_recipient
decodifica ogni indirizzo nella chiave grezza, e il KEM usa come impostazione predefinita
l'ibrido X-Wing:
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(())
}Per un flusso ripristinabile, seal_prepare restituisce un PreparedSeal che puoi
conservare e client.poe().submit_sealed(&SubmitSealedInput::new(&prepared)) esegue la metà
online. Anche la CLI sigilla dalla riga di comando, con le stesse fasi dietro le quinte:
cardanowall seal --file <path> --to <address>.
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.