@chainsafe/persistent-merkle-tree
Version:
Merkle tree implemented as a persistent datastructure
130 lines • 4.8 kB
JavaScript
import { convertGindexToBitstring } from "../gindex.js";
import { BranchNode, LeafNode } from "../node.js";
import { computeProofBitstrings } from "./util.js";
export function computeDescriptor(indices) {
// include all helper indices
const proofBitstrings = new Set();
const pathBitstrings = new Set();
for (const leafIndex of indices) {
const leafBitstring = convertGindexToBitstring(leafIndex);
proofBitstrings.add(leafBitstring);
const { branch, path } = computeProofBitstrings(leafBitstring);
path.delete(leafBitstring);
for (const pathIndex of path) {
pathBitstrings.add(pathIndex);
}
for (const branchIndex of branch) {
proofBitstrings.add(branchIndex);
}
}
for (const pathIndex of pathBitstrings) {
proofBitstrings.delete(pathIndex);
}
// sort gindex bitstrings in-order
const allBitstringsSorted = Array.from(proofBitstrings).sort((a, b) => a.localeCompare(b));
// convert gindex bitstrings into descriptor bitstring
let descriptorBitstring = "";
for (const gindexBitstring of allBitstringsSorted) {
for (let i = 0; i < gindexBitstring.length; i++) {
if (gindexBitstring.at(-1 - i) === "1") {
descriptorBitstring += "1".padStart(i + 1, "0");
break;
}
}
}
// append zero bits to byte-alignt
if (descriptorBitstring.length % 8 !== 0) {
descriptorBitstring = descriptorBitstring.padEnd(8 - (descriptorBitstring.length % 8) + descriptorBitstring.length, "0");
}
// convert descriptor bitstring to bytes
const descriptor = new Uint8Array(descriptorBitstring.length / 8);
for (let i = 0; i < descriptor.length; i++) {
descriptor[i] = Number(`0b${descriptorBitstring.substring(i * 8, (i + 1) * 8)}`);
}
return descriptor;
}
function getBit(bitlist, bitIndex) {
const bit = bitIndex % 8;
const byteIdx = Math.floor(bitIndex / 8);
const byte = bitlist[byteIdx];
switch (bit) {
case 0:
return (byte & 0b1000_0000) !== 0;
case 1:
return (byte & 0b0100_0000) !== 0;
case 2:
return (byte & 0b0010_0000) !== 0;
case 3:
return (byte & 0b0001_0000) !== 0;
case 4:
return (byte & 0b0000_1000) !== 0;
case 5:
return (byte & 0b0000_0100) !== 0;
case 6:
return (byte & 0b0000_0010) !== 0;
case 7:
return (byte & 0b0000_0001) !== 0;
default:
throw new Error("unreachable");
}
}
export function descriptorToBitlist(descriptor) {
const bools = [];
const maxBitLength = descriptor.length * 8;
let count0 = 0;
let count1 = 0;
for (let i = 0; i < maxBitLength; i++) {
const bit = getBit(descriptor, i);
bools.push(bit);
if (bit) {
count1++;
}
else {
count0++;
}
if (count1 > count0) {
i++;
if (i + 7 < maxBitLength) {
throw new Error("Invalid descriptor: too many bytes");
}
for (; i < maxBitLength; i++) {
const bit = getBit(descriptor, i);
if (bit) {
throw new Error("Invalid descriptor: too many 1 bits");
}
}
return bools;
}
}
throw new Error("Invalid descriptor: not enough 1 bits");
}
export function nodeToCompactMultiProof(node, bitlist, bitIndex) {
if (bitlist[bitIndex]) {
return [node.root];
}
const left = nodeToCompactMultiProof(node.left, bitlist, bitIndex + 1);
const right = nodeToCompactMultiProof(node.right, bitlist, bitIndex + left.length * 2);
return [...left, ...right];
}
/**
* Create a Node given a validated bitlist, leaves, and a pointer into the bitlist and leaves
*
* Recursive definition
*/
export function compactMultiProofToNode(bitlist, leaves, pointer) {
if (bitlist[pointer.bitIndex++]) {
return LeafNode.fromRoot(leaves[pointer.leafIndex++]);
}
return new BranchNode(compactMultiProofToNode(bitlist, leaves, pointer), compactMultiProofToNode(bitlist, leaves, pointer));
}
export function createCompactMultiProof(rootNode, descriptor) {
return nodeToCompactMultiProof(rootNode, descriptorToBitlist(descriptor), 0);
}
export function createNodeFromCompactMultiProof(leaves, descriptor) {
const bools = descriptorToBitlist(descriptor);
if (bools.length !== leaves.length * 2 - 1) {
throw new Error("Invalid multiproof: invalid number of leaves");
}
return compactMultiProofToNode(bools, leaves, { bitIndex: 0, leafIndex: 0 });
}
//# sourceMappingURL=compactMulti.js.map