Guides · Partie 4 sur 8
Construire une PoE scellée
Une PoE simple prouve _qu’_un contenu donné a existé. Une PoE scellée prouve la même chose tout en gardant le contenu lui-même secret : vous chiffrez les octets pour une ou plusieurs clés de destinataire, ne stockez que le texte chiffré et ancrez l’enregistrement sur la chaîne. Quiconque peut constater que l’enregistrement existe et en vérifier la structure ; seul un détenteur d’une clé privée correspondante peut déchiffrer la charge utile. Voir Sealed PoE pour le format de l’enveloppe et Sealed until claimed pour le modèle de menace.
Adresser vos destinataires
Un destinataire est identifié par une chaîne de style age. Il en existe deux
types, que le préfixe permet de distinguer :
age1…— une clé classique X25519 (32 octets).age1pqc…— une clé hybride X-Wing (ML-KEM-768 + X25519, 1216 octets).
X-Wing (mlkem768x25519) est le KEM par défaut : il reste sûr face à un
futur adversaire quantique, et toute identité dispose toujours d’une adresse
age1pqc….
Un destinataire vous transmet sa chaîne par un canal externe. Vous la décodez
pour retrouver la clé publique brute dont l’assistant de scellement a besoin à
l’aide de parseAgeRecipient :
import { parseAgeRecipient } from '@cardanowall/sdk-ts';
const them = parseAgeRecipient('age1pqc…'); // { kem: 'mlkem768x25519', publicKey: Uint8Array }Si vous détenez vous-même un seed de 32 octets, recipientsFromSeed vous fournit
vos deux adresses propres : partagez-en une pour que d’autres puissent vous
sceller des enregistrements, et incluez votre propre clé dans la liste des
destinataires afin de conserver l’accès en lecture à ce que vous envoyez :
import { recipientsFromSeed } from '@cardanowall/sdk-ts';
const me = recipientsFromSeed(mySeed); // { age: 'age1…', age1pqc: 'age1pqc…' }Sceller et publier
Le scellement passe par une passerelle, qui construit et diffuse la transaction Cardano et stocke le texte chiffré de chaque élément à votre place. Pointez le client vers la passerelle que vous utilisez ; le SDK est indépendant de la passerelle.
publishSealed reçoit des clés publiques de destinataire brutes ; recueillez
donc le publicKey de chaque adresse décodée. Tous les destinataires doivent
partager un même KEM : gardez les clés age1pqc… ensemble. items est une liste :
vous pouvez donc sceller tout un ensemble de fichiers pour les mêmes destinataires
dans un seul enregistrement ; ici, elle ne contient qu’une seule charge utile :
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);Il n’y a pas d’appel quote distinct ni d’estimation de recordBytes de votre
part : publishSealed mesure l’enregistrement scellé exact, verrouille un prix,
téléverse chaque texte chiffré et le soumet, le tout en un seul appel. maxUsdMicros
est votre plafond ; la publication est refusée si le prix proposé le dépasse, et le
plafond est revérifié contre un prix rafraîchi si un téléversement lent a survécu au
premier verrou, de sorte qu’un gros téléversement ne peut jamais distancer son devis.
Votre seed et le texte en clair ne quittent jamais votre machine en clair.
Par défaut, l’affirmation sur la chaîne de chaque élément est son condensé
sha2-256. Pour lier un second condensé au même enregistrement, calculez
l’empreinte de l’élément sous les deux algorithmes à la fois : passez
hashAlgs: ['sha2-256', 'blake2b-256'] à publishSealed dans le SDK, ou répétez
--hash-alg sha2-256 --hash-alg blake2b-256 sur le seal de la CLI. Les deux
algorithmes du registre se comportent de façon identique dans tous les modes de
scellement.
Sceller avec une phrase secrète
Tout le monde n’a pas de clé. Parfois, vous voulez sceller pour des personnes
qui partagent une phrase secrète, non une clé — un mot de passe d’équipe, une
phrase lue à voix haute lors d’un appel, une valeur déjà présente dans vos
secrets de CI. Donnez à seal une phrase secrète au lieu de --to, et quiconque
la connaît peut ouvrir l’enregistrement : aucune adresse age, aucun échange de
clés.
cardanowall seal \
--file ./contract.pdf \
--passphrase-file ./pw.txt \
--base-url https://your-gateway.example \
--api-key "$CW_API_KEY"Un enregistrement est scellé pour des destinataires ou pour une phrase
secrète, jamais les deux — passer --passphrase en même temps que
--to/--to-self est refusé. Gardez la phrase hors de la ligne de commande :
--passphrase-file, --passphrase-stdin ou la variable d’environnement
CARDANOWALL_PASSPHRASE la lisent toutes sans qu’elle n’atterrisse dans
l’historique du shell. La clé de contenu est étirée depuis la phrase avec
Argon2id, de sorte que seule une phrase secrète forte, à haute entropie, se
dresse entre le texte chiffré et quiconque cherche à la deviner — ce chemin n’est
jamais plus sûr que la phrase que vous choisissez.
Pour l’ouvrir, il ne faut rien d’autre que la phrase. Le vérificateur autonome
déchiffre et recontrôle l’empreinte du contenu ; la commande inbox écrit le
texte en clair récupéré dans un fichier :
cardanowall verify <tx-hash> --passphrase-file ./pw.txt
cardanowall inbox decrypt <tx-hash> --passphrase-file ./pw.txt --out ./recovered.pdfLe remplacement fonctionne de la même façon dans les deux modes. Un nouveau
scellement est toujours un enregistrement flambant neuf — le chiffrement est
randomisé à chaque fois (une nouvelle clé de contenu et un nouveau nonce, ainsi
qu’un nouveau sel Argon2id sur le chemin de la phrase secrète), de sorte que les
octets ne se dédupliquent jamais. Pour marquer un nouveau scellement comme le
successeur d’un précédent — un fichier corrigé, un ensemble de destinataires
renouvelé — pointez-le en retour avec --supersedes :
cardanowall seal --file ./contract-v2.pdf --to age1pqc…recipient --supersedes <tx-hash>Reprendre après une panne
Le scellement tire à chaque fois une nouvelle clé de contenu, un nouveau nonce et
un nouveau matériel KEM par emplacement ; une reprise naïve rechiffrerait donc,
paierait un second téléversement de texte chiffré et produirait des octets
d’enregistrement différents. Pour les tâches de CI et les charges utiles de plusieurs
gigaoctets, scindez le flux : sealPrepare chiffre hors ligne et renvoie un artefact
portable que vous pouvez conserver ; submitSealed en exécute la moitié en ligne.
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 ci-dessus n’est que sealPrepare + submitSealed en un seul appel ;
les SDK Python et Rust exposent la même paire.
Si la publication échoue en cours de route
La CLI se rétablit de la même manière, sans le code en deux phases. Une exécution seal --to …
qui meurt après le téléversement d’un texte chiffré écrit un
<seal-fingerprint>.l309-seal-resume.json sans secret (les téléversements achevés, et aucune clé
ni texte en clair). Relancez avec cardanowall seal --resume <seal-fingerprint>.l309-seal-resume.json : elle redemande un devis, ne termine que les
téléversements encore dus et publie. Une exécution signée a de nouveau besoin de son seed ; les
scellements par phrase secrète ne conservent aucun état de reprise, relancez-les donc depuis le
début.
Avec Python
cardanowall-sdk est un jumeau octet pour octet : même KEM par défaut, même
enveloppe :
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())Pour un flux avec reprise, seal_prepare(...) (de cardanowall.client) renvoie
l’artefact préparé et client.poe.submit_sealed(prepared=...) en exécute la moitié
en ligne : les deux mêmes phases que ci-dessus.
Avec Rust
Le crate cardanowall scelle via le même client de passerelle.
parse_age_recipient décode chaque adresse en clé brute, et le KEM adopte par défaut
l’hybride 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(())
}Pour un flux avec reprise, seal_prepare renvoie un PreparedSeal que vous pouvez
conserver et client.poe().submit_sealed(&SubmitSealedInput::new(&prepared)) en
exécute la moitié en ligne. La CLI scelle elle aussi depuis la ligne de commande,
avec les mêmes phases en coulisses : cardanowall seal --file <path> --to <address>.
Une fois l’enregistrement consolidé, chaque destinataire le découvre, déchiffre la charge utile avec sa clé privée et recalcule l’empreinte du texte en clair pour boucler la boucle : c’est la moitié destinataire de Vérifier un enregistrement.
Scellez aussi pour vous-même
publishSealed ne vous ajoute jamais en silence à la liste des destinataires. Si vous n’incluez
pas l’une de vos propres clés, vous publiez un enregistrement que vous ne pourrez jamais relire.
Incluez me.age1pqc (ou me.age) parmi les destinataires chaque fois que vous voulez conserver
l’accès à ce que vous avez envoyé.