Merkle Trees
How thousands of commitments compress into one anchored root.
Anchoring each decision individually would be prohibitively expensive. Instead, commitments are collected into a batch, hashed into a Merkle tree, and only the 32-byte root reaches the chain. Each decision keeps the sibling path that proves its membership.
Construction
leaf = SHA-256("merit.merkle.leaf.v1 " || commitmentHex)
node = SHA-256("merit.merkle.node.v1 " || leftBytes || rightBytes)Leaves and internal nodes are hashed under different domain tags. Without that separation, an attacker could present an internal node as though it were a leaf and claim membership for data that was never committed.
When a level has an odd number of nodes, the unpaired node is promoted unchanged to the next level rather than hashed with a copy of itself. Duplicating the final node — the classic Bitcoin construction — allows two different leaf sets to produce the same root.
Verifying a proof
Hash the commitment under the leaf tag, then fold each sibling in the order given, combining left or right as the step indicates. The result must equal the anchored root.
import { verifyProofOffline } from "@merit-protocol/sdk";
const proof = await agent.getProof(decisionId);
const result = verifyProofOffline(proof);
result.valid; // true
result.computedRoot; // recomputed locally, not taken from the API