Add Zapier interface simulator, tree-node Merkle lookups, and user docs
Some checks are pending
offline / test (push) Waiting to run

In-process trace console validates hops, faults, recoveries, and
suggested changes before zapier-platform push. User guide covers
signup through central-chain and bulk-summary tree-node hash lookup.
This commit is contained in:
George Lambert 2026-09-11 12:58:03 -04:00
parent 3daa88866d
commit f3dc0e6eee
56 changed files with 2341 additions and 8 deletions

View file

@ -0,0 +1,96 @@
/**
* Merkle tree over SHA-256 leaf hex digests.
* Only the root is sealed on the main Verae chain; leaves live on tree nodes.
* @module merkle
*/
import { createHash } from 'node:crypto';
/**
* @param {string|Buffer|Uint8Array} data
* @returns {string} lowercase hex
*/
export function sha256Hex(data) {
const buf = Buffer.isBuffer(data) ? data : Buffer.from(String(data), 'utf8');
return createHash('sha256').update(buf).digest('hex');
}
/**
* Parent of two 32-byte hex children (left || right).
* Odd last child is duplicated (Bitcoin-style).
* @param {string} left
* @param {string} right
*/
export function parentHash(left, right) {
return createHash('sha256')
.update(Buffer.from(left, 'hex'))
.update(Buffer.from(right, 'hex'))
.digest('hex');
}
/**
* @param {string[]} leaves hex SHA-256 digests
* @returns {{ root: string, layers: string[][], leaves: string[] }}
*/
export function buildMerkle(leaves) {
if (!leaves.length) {
throw new Error('at least one leaf is required');
}
const layer0 = leaves.map((h) => String(h).toLowerCase());
const layers = [layer0];
let layer = layer0;
while (layer.length > 1) {
const next = [];
for (let i = 0; i < layer.length; i += 2) {
const left = layer[i];
const right = layer[i + 1] ?? layer[i];
next.push(parentHash(left, right));
}
layer = next;
layers.push(layer);
}
return { root: layer[0], layers, leaves: layer0 };
}
/**
* Inclusion proof for leaf index.
* @param {string[][]} layers
* @param {number} index
* @returns {Array<{ sibling: string, position: 'left'|'right' }>}
*/
export function merkleProof(layers, index) {
const proof = [];
let i = index;
for (let level = 0; level < layers.length - 1; level += 1) {
const layer = layers[level];
const isRight = i % 2 === 1;
const sib = isRight ? i - 1 : i + 1;
const sibling = layer[sib] ?? layer[i];
proof.push({ sibling, position: isRight ? 'left' : 'right' });
i = Math.floor(i / 2);
}
return proof;
}
/**
* @param {string} leaf
* @param {Array<{ sibling: string, position: 'left'|'right' }>} proof
* @param {string} root
*/
export function verifyProof(leaf, proof, root) {
let h = String(leaf).toLowerCase();
for (const step of proof) {
h = step.position === 'left' ? parentHash(step.sibling, h) : parentHash(h, step.sibling);
}
return h === String(root).toLowerCase();
}
/**
* Shard a leaf onto one of N tree nodes (others stay silent on bloom miss).
* @param {string} sha256
* @param {number} n
*/
export function shardIndex(sha256, n) {
const b = Buffer.from(String(sha256).slice(0, 8), 'hex');
return n <= 0 ? 0 : b.readUInt32BE(0) % n;
}