UNPKG

@chainsafe/persistent-merkle-tree

Version:

Merkle tree implemented as a persistent datastructure

103 lines 4.49 kB
import { BranchNode, LeafNode } from "../node.js"; import { computeMultiProofBitstrings } from "./util.js"; /** * Compute offsets and leaves of a tree-offset proof * * Recursive function * * See https://github.com/protolambda/eth-merkle-trees/blob/master/tree_offsets.md * @param node current node in the tree * @param gindex current generalized index in the tree * @param proofGindices generalized indices to left include in the proof - must be sorted in-order according to the tree */ export function nodeToTreeOffsetProof(node, gindex, proofGindices) { if (!proofGindices.length || !proofGindices[0].startsWith(gindex)) { // there are no proof indices left OR the current subtree contains no remaining proof indices return [[], []]; } if (gindex === proofGindices[0]) { // the current node is at the next proof index proofGindices.shift(); return [[], [node.root]]; } // recursively compute offsets, leaves for the left and right subtree const [leftOffsets, leftLeaves] = nodeToTreeOffsetProof(node.left, `${gindex}0`, proofGindices); const [rightOffsets, rightLeaves] = nodeToTreeOffsetProof(node.right, `${gindex}1`, proofGindices); // the offset prepended to the list is # of leaves in the left subtree const pivot = leftLeaves.length; return [[pivot].concat(leftOffsets, rightOffsets), leftLeaves.concat(rightLeaves)]; } /** * Recreate a `Node` given offsets and leaves of a tree-offset proof * * Recursive definition * * See https://github.com/protolambda/eth-merkle-trees/blob/master/tree_offsets.md */ export function treeOffsetProofToNode(offsets, leaves) { if (!leaves.length) throw new Error("Proof must contain gt 0 leaves"); if (leaves.length === 1) return LeafNode.fromRoot(leaves[0]); // the offset popped from the list is the # of leaves in the left subtree const pivot = offsets[0]; return new BranchNode(treeOffsetProofToNode(offsets.slice(1, pivot), leaves.slice(0, pivot)), treeOffsetProofToNode(offsets.slice(pivot), leaves.slice(pivot))); } /** * Create a tree-offset proof * * @param rootNode the root node of the tree * @param gindices generalized indices to include in the proof */ export function createTreeOffsetProof(rootNode, gindices) { return nodeToTreeOffsetProof(rootNode, "1", computeMultiProofBitstrings(gindices.map((g) => g.toString(2)))); } /** * Recreate a `Node` given a tree-offset proof * * @param offsets offsets of a tree-offset proof * @param leaves leaves of a tree-offset proof */ export function createNodeFromTreeOffsetProof(offsets, leaves) { // TODO validation return treeOffsetProofToNode(offsets, leaves); } export function computeTreeOffsetProofSerializedLength(offsets, leaves) { // add 1 for # of leaves return (offsets.length + 1) * 2 + leaves.length * 32; } // Serialized tree offset proof structure: // # of leaves - 2 bytes // offsets - 2 bytes each // leaves - 32 bytes each export function serializeTreeOffsetProof(output, byteOffset, offsets, leaves) { const writer = new DataView(output.buffer, output.byteOffset, output.byteLength); // set # of leaves writer.setUint16(byteOffset, leaves.length, true); // set offsets const offsetsStartIndex = byteOffset + 2; for (let i = 0; i < offsets.length; i++) { writer.setUint16(i * 2 + offsetsStartIndex, offsets[i], true); } // set leaves const leavesStartIndex = offsetsStartIndex + offsets.length * 2; for (let i = 0; i < leaves.length; i++) { output.set(leaves[i], i * 32 + leavesStartIndex); } } export function deserializeTreeOffsetProof(data, byteOffset) { const reader = new DataView(data.buffer, data.byteOffset, data.byteLength); // get # of leaves const leafCount = reader.getUint16(byteOffset, true); if (data.length < (leafCount - 1) * 2 + leafCount * 32) { throw new Error("Unable to deserialize tree offset proof: not enough bytes"); } // get offsets const offsetsStartIndex = byteOffset + 2; const offsets = Array.from({ length: leafCount - 1 }, (_, i) => reader.getUint16(i * 2 + offsetsStartIndex, true)); // get leaves const leavesStartIndex = offsetsStartIndex + offsets.length * 2; const leaves = Array.from({ length: leafCount }, (_, i) => data.subarray(i * 32 + leavesStartIndex, (i + 1) * 32 + leavesStartIndex)); return [offsets, leaves]; } //# sourceMappingURL=treeOffset.js.map