@chainsafe/persistent-merkle-tree
Version:
Merkle tree implemented as a persistent datastructure
146 lines • 4.64 kB
JavaScript
import { gindexParent, gindexSibling } from "../gindex.js";
// Not currently in use, but simpler implementation useful for testing
/**
* Compute both the path and branch indices
*
* Path indices are parent indices upwards toward the root
* Branch indices are witnesses required for a merkle proof
*/
export function computeProofGindices(gindex) {
const path = new Set();
const branch = new Set();
let g = gindex;
while (g > 1) {
path.add(g);
branch.add(gindexSibling(g));
g = gindexParent(g);
}
return { path, branch };
}
/**
* Compute both the path and branch indices
*
* Path indices are parent indices upwards toward the root
* Branch indices are witnesses required for a merkle proof
*/
export function computeProofBitstrings(gindex) {
const path = new Set();
const branch = new Set();
let g = gindex;
while (g.length > 1) {
path.add(g);
const lastBit = g.at(-1);
const parent = g.substring(0, g.length - 1);
branch.add(parent + (Number(lastBit) ^ 1));
g = parent;
}
return { path, branch };
}
/**
* Sort generalized indices in-order
* @param bitLength maximum bit length of generalized indices to sort
*/
export function sortInOrderBitstrings(gindices, bitLength) {
if (!gindices.length) {
return [];
}
return gindices
.map((g) => g.padEnd(bitLength))
.sort()
.map((g) => g.trim());
}
/**
* Sort generalized indices in decreasing order
*/
export function sortDecreasingBitstrings(gindices) {
if (!gindices.length) {
return [];
}
return gindices.sort((a, b) => {
if (a.length < b.length)
return 1;
if (b.length < a.length)
return -1;
let aPos0 = a.indexOf("0");
let bPos0 = b.indexOf("0");
while (true) {
if (aPos0 === -1)
return -1;
if (bPos0 === -1)
return 1;
if (aPos0 < bPos0)
return 1;
if (bPos0 < aPos0)
return -1;
aPos0 = a.indexOf("0", aPos0 + 1);
bPos0 = b.indexOf("0", bPos0 + 1);
}
});
}
/**
* Filter out parent generalized indices
*/
export function filterParentBitstrings(gindices) {
const sortedBitstrings = gindices.slice().sort((a, b) => a.length - b.length);
const filtered = [];
outer: for (let i = 0; i < sortedBitstrings.length; i++) {
const bsA = sortedBitstrings[i];
for (let j = i + 1; j < sortedBitstrings.length; j++) {
const bsB = sortedBitstrings[j];
if (bsB.startsWith(bsA)) {
continue outer;
}
}
filtered.push(bsA);
}
return filtered;
}
export var SortOrder;
(function (SortOrder) {
SortOrder[SortOrder["InOrder"] = 0] = "InOrder";
SortOrder[SortOrder["Decreasing"] = 1] = "Decreasing";
SortOrder[SortOrder["Unsorted"] = 2] = "Unsorted";
})(SortOrder || (SortOrder = {}));
/**
* Return the set of generalized indices required for a multiproof
* This may include all leaves and any necessary witnesses
* @param gindices leaves to include in proof
* @returns all generalized indices required for a multiproof (leaves and witnesses), deduplicated and sorted
*/
export function computeMultiProofBitstrings(gindices, includeLeaves = true, sortOrder = SortOrder.InOrder) {
const leaves = filterParentBitstrings(gindices);
// Maybe initialize the proof indices with the leaves
const proof = new Set(includeLeaves ? leaves : []);
const paths = new Set();
const branches = new Set();
// Collect all path indices and all branch indices
let maxBitLength = 1;
for (const gindex of leaves) {
if (gindex.length > maxBitLength)
maxBitLength = gindex.length;
const { path, branch } = computeProofBitstrings(gindex);
for (const p of path) {
paths.add(p);
}
for (const b of branch) {
branches.add(b);
}
}
// Remove all branches that are included in the paths
for (const p of paths) {
branches.delete(p);
}
// Add all remaining branches to the leaves
for (const b of branches) {
proof.add(b);
}
switch (sortOrder) {
case SortOrder.InOrder:
return sortInOrderBitstrings(Array.from(proof), maxBitLength);
case SortOrder.Decreasing:
return sortDecreasingBitstrings(Array.from(proof));
case SortOrder.Unsorted:
return Array.from(proof);
}
}
//# sourceMappingURL=util.js.map