Initial import of verae-tree-node from zapier monorepo
This commit is contained in:
commit
3fe774fa0a
6 changed files with 169 additions and 0 deletions
96
src/merkle.js
Normal file
96
src/merkle.js
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue