Guías · Parte 4 de 8
Construir una PoE sellada
Una PoE simple prueba que cierto contenido existió. Una PoE sellada prueba lo mismo manteniendo en secreto el propio contenido: cifra los bytes para una o varias claves de destinatario, almacena únicamente el texto cifrado y ancla el registro en cadena. Cualquiera puede ver que el registro existe y verificar su estructura; solo quien posea una clave privada correspondiente puede descifrar la carga útil. Consulte PoE sellada para el formato del sobre y Sellada hasta su reclamación para el modelo de amenaza.
Identificar a los destinatarios
Un destinatario se identifica mediante una cadena al estilo de age. Hay dos
tipos, y el prefijo los distingue:
age1…: una clave clásica X25519 (32 bytes).age1pqc…: una clave híbrida X-Wing (ML-KEM-768 + X25519, 1216 bytes).
X-Wing (mlkem768x25519) es el KEM predeterminado: se mantiene seguro frente
a un futuro adversario cuántico, y toda identidad dispone siempre de una
dirección age1pqc….
Un destinatario le entrega su cadena por un canal externo. Para decodificarla y
recuperar la clave pública sin procesar que necesita el asistente de sellado, use
parseAgeRecipient:
import { parseAgeRecipient } from '@cardanowall/sdk-ts';
const them = parseAgeRecipient('age1pqc…'); // { kem: 'mlkem768x25519', publicKey: Uint8Array }Si usted mismo dispone de una semilla de 32 bytes, recipientsFromSeed le
proporciona sus dos direcciones propias: comparta una para que otros puedan
sellarle registros, e incluya su propia clave en la lista de destinatarios para
conservar el acceso de lectura a lo que envía:
import { recipientsFromSeed } from '@cardanowall/sdk-ts';
const me = recipientsFromSeed(mySeed); // { age: 'age1…', age1pqc: 'age1pqc…' }Sellar y publicar
El sellado se realiza a través de una pasarela, que construye y difunde la transacción de Cardano y almacena el texto cifrado de cada elemento por usted. Apunte el cliente a la pasarela que utilice; el SDK es independiente de la pasarela.
publishSealed recibe claves públicas de destinatario sin procesar, así que
recopile el publicKey de cada dirección decodificada. Todos los destinatarios
deben compartir un mismo KEM: agrupe las claves age1pqc…. items es una lista,
así que puede sellar todo un conjunto de archivos para los mismos destinatarios en
un solo registro; aquí contiene una única carga útil:
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);No hay una llamada quote aparte ni una estimación propia de recordBytes:
publishSealed mide el registro sellado exacto, fija un precio, sube cada texto
cifrado y lo envía, todo en una sola llamada. maxUsdMicros es su límite máximo;
la publicación se rechaza si el precio ofertado lo supera, y vuelve a comprobar el
límite contra un precio nuevo si una subida lenta sobrevivió al primer bloqueo, de
modo que una subida grande nunca puede dejar atrás su oferta. Su semilla y el texto
plano nunca salen de su máquina sin cifrar.
De forma predeterminada, la afirmación en cadena de cada elemento es su resumen
sha2-256. Para vincular un segundo resumen al mismo registro, calcule el hash
del elemento bajo ambos algoritmos a la vez: pase hashAlgs: ['sha2-256', 'blake2b-256'] a publishSealed en el SDK, o repita
--hash-alg sha2-256 --hash-alg blake2b-256 en el seal de la CLI. Los dos
algoritmos registrados se comportan de forma idéntica en todos los modos de
sellado.
Sellar con una frase de contraseña
No todo el mundo tiene una clave. A veces quiere sellar para personas que
comparten una frase secreta, no una clave: una contraseña de equipo, una frase
leída en voz alta en una llamada, un valor que ya reside en sus secretos de CI.
Dé a seal una frase de contraseña en lugar de --to, y cualquiera que la
conozca podrá abrir el registro: sin dirección age, sin intercambio de claves.
cardanowall seal \
--file ./contract.pdf \
--passphrase-file ./pw.txt \
--base-url https://your-gateway.example \
--api-key "$CW_API_KEY"Un registro se sella para destinatarios o para una frase de contraseña, nunca
para ambos: pasar --passphrase junto con --to/--to-self se rechaza. Mantenga
la frase fuera de la línea de comandos: --passphrase-file, --passphrase-stdin
o la variable de entorno CARDANOWALL_PASSPHRASE la leen sin que acabe en el
historial del shell. La clave de contenido se deriva de la frase con Argon2id, así
que lo único que se interpone entre el texto cifrado y quien quiera adivinarla es
una frase de contraseña fuerte y de alta entropía: este camino es tan seguro como
la frase que elija.
Abrirlo no necesita nada más que la frase. El verificador autónomo descifra y
vuelve a comprobar el hash del contenido; el comando inbox escribe el texto
plano recuperado en un archivo:
cardanowall verify <tx-hash> --passphrase-file ./pw.txt
cardanowall inbox decrypt <tx-hash> --passphrase-file ./pw.txt --out ./recovered.pdfEl reemplazo funciona igual en cualquiera de los dos modos. Un nuevo sellado es
siempre un registro completamente nuevo: el cifrado se aleatoriza cada vez (una
clave de contenido y un nonce nuevos, y una sal Argon2id nueva en el camino de la
frase de contraseña), de modo que los bytes nunca se deduplican. Para marcar un
sellado nuevo como el sucesor de uno anterior —un archivo corregido, un conjunto
de destinatarios rotado— apúntelo de vuelta con --supersedes:
cardanowall seal --file ./contract-v2.pdf --to age1pqc…recipient --supersedes <tx-hash>Reanudar tras un fallo
El sellado extrae cada vez una clave de contenido, un nonce y material KEM por
ranura nuevos, así que un reintento ingenuo volvería a cifrar, pagaría una segunda
subida de texto cifrado y produciría bytes de registro distintos. Para trabajos de
CI y cargas útiles de varios gigabytes, divida el flujo: sealPrepare cifra sin
conexión y devuelve un artefacto portátil que puede conservar; submitSealed
ejecuta la mitad en línea sobre él.
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, más arriba, es simplemente sealPrepare + submitSealed en una
sola llamada; los SDK de Python y Rust exponen el mismo par.
Si la publicación falla a medias
La CLI se recupera de la misma manera, sin el código de dos fases. Una ejecución de seal --to …
que muere tras subir un texto cifrado escribe un <seal-fingerprint>.l309-seal-resume.json sin
secretos (las subidas completadas, y ninguna clave ni texto plano). Vuelva a ejecutar con
cardanowall seal --resume <seal-fingerprint>.l309-seal-resume.json: vuelve a cotizar, termina
solo las subidas que aún faltan y publica. Una ejecución firmada necesita de nuevo su semilla; los
sellados con frase de contraseña no guardan ningún estado de reanudación, así que reejecútelos
desde el principio.
Con Python
cardanowall-sdk es un gemelo byte por byte: mismo KEM predeterminado, mismo sobre:
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())Para un flujo reanudable, seal_prepare(...) (de cardanowall.client) devuelve el
artefacto preparado y client.poe.submit_sealed(prepared=...) ejecuta la mitad en
línea: las mismas dos fases que arriba.
Con Rust
El crate cardanowall sella a través del mismo cliente de pasarela.
parse_age_recipient decodifica cada dirección a la clave sin procesar, y el KEM
adopta de forma predeterminada el híbrido 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(())
}Para un flujo reanudable, seal_prepare devuelve un PreparedSeal que puede
conservar y client.poe().submit_sealed(&SubmitSealedInput::new(&prepared)) ejecuta
la mitad en línea. La CLI también sella desde la línea de comandos, con las mismas
fases por debajo: cardanowall seal --file <path> --to <address>.
Una vez que el registro se consolida, cada destinatario lo descubre, descifra la carga útil con su clave privada y recalcula el hash del texto plano para cerrar el ciclo: esa es la mitad del destinatario de Verificar un registro.
Séllese también a usted mismo
publishSealed nunca lo añade a la lista de destinatarios de forma silenciosa. Si no incluye una
de sus propias claves, publica un registro que nunca podrá volver a leer. Incluya me.age1pqc (o
me.age) entre los destinatarios siempre que quiera conservar el acceso a lo que envió.