가이드 · 전체 6부 중 5부
Merkle로 일괄 기록하기
파일이 하나가 아니라 천 개인 경우도 있습니다. 문서가 담긴 폴더, 이벤트 스트림, 하루치 감사 로그의 행들이 그렇습니다. 이를 하나씩 별도의 트랜잭션으로 기록하는 것은 낭비입니다. 그 대신, 모든 항목을 해시하여 리프로 만들고, 리프들을 하나의 Merkle 루트로 합친 다음, 그 루트 하나만 게시합니다. 순서가 매겨진 리프들은 온체인 바깥에 남겨 두며, 이후에 배치 크기의 로그에 비례하는 크기로만 커지는 증명으로 임의의 단일 항목이 집합에 포함되어 있었음을 증명할 수 있습니다.
핵심은 이것입니다. 트리를 구축하고 증명을 검증하는 일은 전적으로 오프라인에서 이루어집니다. 게이트웨이도, 계정도, 네트워크도 필요 없습니다. 게이트웨이가 필요한 단계는 루트를 게시할 때뿐이며, 그 밖의 모든 것은 어디서나 영원히 실행할 수 있는 순수한 계산입니다.
CLI로
merkle build에 기록하려는 파일들을 넘기십시오. 각 파일을 해시하여 리프로 만들고, RFC 9162 루트를 구축한 뒤, 정규화된 리프 목록을 출력합니다.
cardanowall merkle build --file a.pdf --file b.pdf --file c.pdf --json미리 계산해 둔 리프(한 줄당 64자리 16진수 SHA-256 다이제스트 하나)를 표준 입력이나 --in leaves.txt로 넘길 수도 있습니다. root(이것을 게시합니다)와 리프 목록(이후 포함성을 증명해야 하는 모든 사람이 필요로 합니다)은 보관해 두십시오.
어떤 항목이 게시된 루트에 속함을 증명하려면, 감사 경로를 담은 작은 증명 파일을 작성하여 검증하면 됩니다. 신뢰하는 어떤 루트에 대해서든 전적으로 오프라인으로 수행됩니다.
cardanowall merkle verify --root <root-hex> --proof proof.jsonproof.json의 형태는 { tree_alg, tree_size, index, leaf, proof[] }이며, --leaf <hex>를 넘기면 파일의 리프를 재정의할 수 있습니다. 종료 코드 0은 리프가 트리에 포함됨을, 1은 포함되지 않음을 의미하므로, 그대로 CI에 넣을 수 있습니다.
TypeScript SDK로
항목들을 해시하여 리프로 만들고, 루트와 증명을 로컬에서 구축한 다음, 루트만 게이트웨이를 통해 게시합니다.
import { Label309Client, hash, merkle } from '@cardanowall/sdk-ts';
const items = [docA, docB, docC]; // Uint8Array content
const leaves = items.map((bytes) => hash.sha2256(bytes));
const root = merkle.merkleSha2256Root(leaves);
// Prove item 1 is in the set — no network, no gateway.
const proof = merkle.merkleSha2256InclusionProof(leaves, 1);
const ok = merkle.merkleSha2256VerifyInclusion(leaves[1], 1, leaves.length, proof, root);
console.log(ok); // truemerkleSha2256VerifyInclusion(leaf, index, treeSize, proof, root)는 순수한 불리언 술어입니다. 네트워크에 접근하지 않으며, 잘못된 증명에 대해서도 예외를 던지지 않고 그저 false를 반환합니다. 리프 목록을 가진 사람이라면 누구나 루트를 다시 계산하고 어떤 증명이든 다시 도출할 수 있습니다. 게시자가 그 과정에 관여하는 일은 결코 없습니다.
루트의 게시는 게이트웨이가 필요한 유일한 단계입니다. 견적을 받은 뒤 리프를 게시하십시오. SDK가 로컬에서 루트를 계산하고, 게이트웨이가 리프 목록을 저장하며 그 커밋먼트를 온체인에 기록합니다.
const client = new Label309Client({
baseUrl: 'https://your-gateway.example',
apiKey: process.env.CW_API_KEY,
});
const quote = await client.poe.quote({
recordBytes: 512,
recipientCount: 0,
fileBytesTotal: leaves.length * 32,
});
const published = await client.poe.publishMerkle({
leaves, // raw 32-byte digests or hex strings
quoteId: quote.quote_id,
});
console.log(published.root, published.leaf_count, published.tx_hash, published.ar_uri);Python SDK로
바이트 단위로 일치하는 쌍둥이 구현이 동일한 트리와 동일한 증명을 구축합니다.
import cardanowall
items = [doc_a, doc_b, doc_c] # bytes
leaves = [cardanowall.hash.sha2_256(b) for b in items]
root = cardanowall.merkle.merkle_sha2_256_root(leaves)
proof = cardanowall.merkle.merkle_sha2_256_inclusion_proof(leaves, 1)
ok = cardanowall.merkle.merkle_sha2_256_verify_inclusion(
leaves[1], 1, len(leaves), proof, root
)
print(ok) # TrueRust SDK로
cardanowall 크레이트도 동일한 트리와 증명을 오프라인으로 구축한 다음, 루트만 게이트웨이 클라이언트를 통해 게시합니다.
use cardanowall::client::{
Label309Client, Label309ClientConfig, MerkleLeaf, PublishMerkleInput, QuoteInput,
};
use cardanowall::{hash, merkle};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let items: Vec<Vec<u8>> = vec![doc_a, doc_b, doc_c]; // content bytes
let leaves: Vec<[u8; 32]> = items.iter().map(|b| hash::sha256(b)).collect();
// Build the root and prove inclusion — pure computation, no network.
let root = merkle::merkle_root(&leaves)?;
let proof = merkle::merkle_inclusion_proof(&leaves, 1)?;
let ok = merkle::verify_inclusion(&leaves[1], 1, leaves.len(), &proof, &root);
println!("{ok}"); // true
// Publishing the root is the one step that needs a gateway.
let client = Label309Client::new(Label309ClientConfig {
base_url: Some("https://your-gateway.example".into()),
api_key: std::env::var("CW_API_KEY").ok(),
})?;
let quote = client.poe().quote(&QuoteInput {
record_bytes: 512,
recipient_count: 0,
file_bytes_total: (leaves.len() * 32) as u64,
})?;
let published = client.poe().publish_merkle(&PublishMerkleInput {
leaves: leaves.iter().map(|l| MerkleLeaf::Bytes(l.to_vec())).collect(),
quote_id: quote.quote_id,
hash_alg: None,
signer: None,
idempotency_key: None,
chunk_bytes: None,
})?;
println!("{} {} {:?}", published.root, published.leaf_count, published.tx_hash);
Ok(())
}하나의 루트, 다수의 항목, 신뢰 제로
루트는 모든 리프와 그 위치에 대해 커밋합니다. 몇 달이 지난 뒤에도, 리프 목록과 온체인의 루트만 있으면, 누구나 여러분의 게이트웨이도, 서버도, 협조도 없이 증명을 다시 도출하여 어떤 항목이 배치에 포함되어 있었음을 확인할 수 있습니다. 리프가 어떻게 해시되는지는 콘텐츠와 해싱을, 단일 항목 플로우는 첫 PoE 게시하기를 참조하십시오.