ガイド

ガイド · 全6部中 第4部

封緘済み PoE を作る

通常の存在証明(Proof of Existence, PoE)は、あるコンテンツが存在した「という事実」を証明します。封緘済み PoE は、同じことを証明しつつ、コンテンツそのものは秘密に保ちます。バイト列を1つ以上の受信者の鍵で暗号化し、暗号文だけを保存して、レコードをチェーン上に記録するのです。だれでもレコードの存在を確認し、その構造を検証できますが、ペイロードを復号できるのは、対応する秘密鍵を持つ者だけです。エンベロープの形式については封緘済み PoEを、脅威モデルについては受け取られるまで封緘されるを参照してください。

受信者を指定する

受信者は age スタイルの文字列で識別されます。次の2種類があり、プレフィックスで見分けられます。

  • age1… — 古典的な X25519 鍵(32バイト)。
  • age1pqc…X-Wing ハイブリッド鍵(ML-KEM-768 + X25519、1216バイト)。

X-Wing(mlkem768x25519)がデフォルトの KEM です。将来の量子コンピューターによる攻撃に対しても安全性を保ち、どのアイデンティティにも必ず age1pqc… アドレスが備わっています。

受信者は、この文字列を別経路(アウトオブバンド)で渡してきます。封緘ヘルパーが必要とする生の公開鍵に戻すには、parseAgeRecipient でデコードします。

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

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

自分が32バイトのシードを持っている場合は、recipientsFromSeed で自分の両方のアドレスが得られます。一方を共有すれば他者が自分宛に封緘でき、受信者リストに自分の鍵を含めておけば、自分が送ったものへの読み取りアクセスを保てます。

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

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

封緘して発行する

封緘はゲートウェイ経由で行います。ゲートウェイが Cardano のトランザクションを構築してブロードキャストし、暗号文を保存します。SDK はゲートウェイ非依存なので、利用しているゲートウェイにクライアントを向けてください。

publishSealed は生の受信者公開鍵を受け取るので、パースした各アドレスから publicKey を集めます。受信者は全員が同じ KEM を共有しなければなりません。age1pqc… 鍵はまとめて扱ってください。まず quote で価格をロックし、それから発行します。

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

このヘルパーは、コンテンツを暗号化し、暗号文をアップロードし、その ar:// URI と平文のハッシュ値を Label 309 レコードに結び付けて送信します。シードと平文が平文のまま手元のマシンを離れることはありません。

Python から

cardanowall-sdk はバイト単位で一致する双子です。KEM のデフォルトもエンベロープも同じです。

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

Rust から

cardanowall クレートも、同じゲートウェイクライアントを通じて封緘を行います。parse_age_recipient で各アドレスを生の鍵にデコードし、kem: None のままにすれば 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(())
}

CLI はハッシュのみのレコードと Merkle レコードを発行します。封緘は、現時点では SDK 上のフローです。

レコードが確定すると、各受信者はそれを見つけ出し、自分の秘密鍵でペイロードを復号し、平文のハッシュ値を再計算して一連の流れを締めくくります。これがレコードを検証するの受信者側の操作です。

自分宛にも封緘を

publishSealed が黙って自分を受信者リストに加えることはありません。自分の鍵を1つも含めなければ、二度と読み返せないレコードを発行することになります。送ったものへのアクセスを保ちたいときは、受信者の中に必ず me.age1pqc(または me.age)を含めてください。