가이드 · 전체 6부 중 4부
봉인된 PoE 만들기
일반적인 PoE는 어떤 콘텐츠가 존재했다는 사실 을 증명합니다. 봉인된 PoE 는 동일한 사실을 증명하면서도 콘텐츠 자체는 비밀로 유지합니다. 바이트를 한 명 이상의 수신자 키로 암호화하고, 암호문만 저장한 뒤, 레코드를 온체인에 기록하는 것입니다. 누구나 레코드의 존재를 확인하고 그 구조를 검증할 수 있지만, 페이로드를 복호화할 수 있는 것은 일치하는 개인 키를 가진 사람뿐입니다. 봉투 형식은 봉인된 PoE를, 위협 모델은 수령될 때까지 봉인된 상태를 참조하십시오.
수신자 지정하기
수신자는 age 스타일의 문자열로 식별됩니다. 두 종류가 있으며 접두사로 구분합니다.
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는 사용자를 수신자 목록에 조용히 추가하지 않습니다. 자신의 키를 하나도 포함하지
않으면, 두 번 다시 읽을 수 없는 레코드를 게시하게 됩니다. 보낸 것에 대한 접근 권한을 유지하고 싶을
때는 수신자 중에 반드시 me.age1pqc(또는 me.age)를 포함하십시오.