指南 · 第 5 部分,共 6 部分
用 Merkle 批量锚定
有时候你要处理的不是一个文件,而是一千个:一整个文件夹的文档、一条事件流、一整天的审计日志。给每一项各开一笔交易太浪费。更好的做法是:把每一项哈希成一个叶子,再把这些叶子层层折叠成单一的 Merkle 根,然后只发布这一个根。有序的叶子列表留在链下;日后,你只需一份大小仅随批量规模对数增长的证明,就能证明其中任意一项确实在这个集合里。
关键在于:**构建这棵树、验证证明,全程都可以离线完成。**不需要网关、不需要账号、不需要网络。只有发布这个根才会用到网关——其余一切都是纯计算,你可以在任何地方、永远跑下去。
使用 CLI
把你想锚定的文件交给 merkle build。它会把每个文件哈希成一个叶子,构建出 RFC 9162 根,并输出规范的叶子列表:
cardanowall merkle build --file a.pdf --file b.pdf --file c.pdf --json你也可以喂入预先算好的叶子——每行一个 64 位十六进制的 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) # True使用 Rust SDK
cardanowall crate 离线构建出同样的树和证明,然后只把根通过网关客户端发布出去:
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。