Guias

Guias · Parte 4 de 6

Construir uma PoE selada

Uma PoE simples prova que determinado conteúdo existiu. Uma PoE selada prova a mesma coisa mantendo o conteúdo em si sob sigilo: você criptografa os bytes para uma ou mais chaves de destinatários, armazena apenas o texto cifrado e ancora o registro na cadeia. Qualquer pessoa pode ver que o registro existe e verificar sua estrutura; somente o detentor de uma chave privada correspondente consegue descriptografar a carga útil. Consulte PoE selada para conhecer o formato do envelope e Selado até ser retirado para o modelo de ameaças.

Endereçar seus destinatários

Um destinatário é identificado por uma string no estilo age. Há dois tipos, e o prefixo os distingue:

  • age1… — uma chave clássica X25519 (32 bytes).
  • age1pqc… — uma chave híbrida X-Wing (ML-KEM-768 + X25519, 1216 bytes).

X-Wing (mlkem768x25519) é o KEM padrão — ele permanece seguro diante de um eventual adversário quântico no futuro, e toda identidade sempre tem um endereço age1pqc….

O destinatário lhe passa essa string por um canal à parte. Você a decodifica de volta para a chave pública bruta de que o helper de selagem precisa, usando parseAgeRecipient:

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

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

Se você mesmo tiver um seed de 32 bytes, o recipientsFromSeed devolve os seus dois endereços — compartilhe um deles para que outras pessoas possam selar registros para você, e inclua sua própria chave na lista de destinatários para manter acesso de leitura ao que envia:

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

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

Selar e publicar

A selagem passa por um gateway, que constrói e transmite a transação Cardano e guarda o texto cifrado por você. Aponte o cliente para o gateway que você usa; o SDK não depende de gateway algum.

O publishSealed recebe as chaves públicas brutas dos destinatários, então colete o publicKey de cada endereço decodificado. Todos os destinatários precisam compartilhar um único KEM — mantenha as chaves age1pqc… juntas. Primeiro, trave um preço com quote; depois, publique:

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

O helper criptografa o conteúdo, envia o texto cifrado, vincula a URI ar:// correspondente e o hash do texto claro a um registro Label 309 e o submete. Seu seed e o texto claro nunca saem da sua máquina às claras.

Com Python

O cardanowall-sdk é um gêmeo byte a byte — mesmo KEM padrão, mesmo envelope:

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

Com Rust

O crate cardanowall sela por meio do mesmo cliente de gateway. O parse_age_recipient decodifica cada endereço para a chave bruta, e kem: None mantém o padrão 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(())
}

A CLI publica registros somente com hash e registros Merkle; a selagem, por enquanto, é um fluxo exclusivo do SDK.

Assim que o registro for liquidado, cada destinatário o descobre, descriptografa a carga útil com sua chave privada e recalcula o hash do texto claro para fechar o ciclo — essa é a parte do destinatário em Verificar um registro.

Sele também para você mesmo

O publishSealed nunca adiciona você à lista de destinatários sem avisar. Se você não incluir uma de suas próprias chaves, vai publicar um registro que jamais conseguirá ler de volta. Inclua me.age1pqc (ou me.age) entre os destinatários sempre que quiser manter acesso ao que enviou.