Руководства · Часть 5 из 6
Пакетная обработка через Merkle
Иногда у вас не один файл, а тысяча: папка документов, поток событий, строки журнала аудита за целый день. Закреплять каждый из них отдельной транзакцией расточительно. Вместо этого захешируйте каждый элемент в лист, сверните листья в единый корень Merkle и опубликуйте только этот корень. Упорядоченные листья остаются вне блокчейна; позже вы сможете доказать, что любой отдельный элемент входил в набор, доказательством, размер которого растёт лишь как логарифм величины пакета.
Главное: построение дерева и проверка доказательств целиком происходят офлайн. Ни шлюза, ни аккаунта, ни сети. Шлюз нужен только для публикации корня — всё остальное это чистые вычисления, которые можно выполнить где угодно и когда угодно.
Через CLI
Передайте merkle build файлы, которые хотите закрепить в блокчейне. Команда
хеширует каждый из них в лист, строит корень по
RFC 9162 и выдаёт канонический список
листьев:
cardanowall merkle build --file a.pdf --file b.pdf --file c.pdf --jsonМожно также подать заранее вычисленные листья — по одному SHA-256-хешу из 64
шестнадцатеричных символов на строку — через стандартный ввод или через
--in leaves.txt. Сохраните root (его вы публикуете) и список листьев (он
понадобится всем, кому позже придётся доказывать включение).
Чтобы доказать, что элемент принадлежит опубликованному корню, составьте небольшой файл доказательства с путём аудита и проверьте его — полностью офлайн, относительно любого корня, которому вы доверяете:
cardanowall merkle verify --root <root-hex> --proof proof.jsonСтруктура proof.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 строит то же дерево и те же доказательства офлайн, а затем
публикует только корень через клиент шлюза:
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(())
}Один корень, много элементов, ноль доверия
Корень фиксирует каждый лист и его позицию. Спустя месяцы — имея только список листьев и ончейн-корень — любой может заново вывести доказательство и убедиться, что элемент входил в пакет, без вашего шлюза, вашего сервера и вашего участия. О том, как хешируются листья, читайте в разделе Содержимое и хеширование, а о публикации одного элемента — в разделе Опубликуйте своё первое подтверждение существования.