@chainsafe/ssz
Version:
Simple Serialize
127 lines • 5.11 kB
JavaScript
import { BranchNode, concatGindices, digest64Into, getNodesAtDepth, merkleizeBlocksBytes, subtreeFillToContents, toGindex, zeroNode, } from "@chainsafe/persistent-merkle-tree";
export const PROGRESSIVE_LIST_MAX_SIZE = 0xffffffff;
const BASE_CHUNK_COUNT = 1;
const SCALING_FACTOR = 4;
const ZERO_ROOT = new Uint8Array(32);
/**
* Return the generalized index of a chunk inside a progressive Merkle tree.
*
* The progressive tree is a right-recursive, zero-terminated sequence of
* subtrees with capacities 1, 4, 16, ...
*/
export function progressiveChunkGindex(chunkIndex) {
if (chunkIndex < 0 || !Number.isSafeInteger(chunkIndex)) {
throw Error(`Invalid progressive chunk index ${chunkIndex}`);
}
let subtreeIndex = 0;
let subtreeStart = 0;
let subtreeLength = BASE_CHUNK_COUNT;
while (chunkIndex >= subtreeStart + subtreeLength) {
subtreeStart += subtreeLength;
subtreeLength *= SCALING_FACTOR;
subtreeIndex++;
}
const parts = [];
for (let i = 0; i < subtreeIndex; i++) {
// Navigate to the successor subtree.
parts.push(BigInt(3));
}
// Navigate to the subtree containing this chunk.
parts.push(BigInt(2));
const subtreeDepth = progressiveSubtreeDepth(subtreeIndex);
if (subtreeDepth > 0) {
parts.push(toGindex(subtreeDepth, BigInt(chunkIndex - subtreeStart)));
}
return concatGindices(parts);
}
export function progressiveChunkGindexFromRoot(rootGindex, chunkIndex) {
return concatGindices([rootGindex, progressiveChunkGindex(chunkIndex)]);
}
export function progressiveSubtreeCount(chunkCount) {
let remaining = chunkCount;
let subtreeLength = BASE_CHUNK_COUNT;
let count = 0;
while (remaining > 0) {
remaining -= Math.min(remaining, subtreeLength);
subtreeLength *= SCALING_FACTOR;
count++;
}
return count;
}
export function progressiveSubtreeDepth(subtreeIndex) {
return subtreeIndex * 2;
}
export function merkleizeProgressiveBytes(chunksBytes, chunkCount, output, offset) {
if (chunkCount === 0) {
output.set(ZERO_ROOT, offset);
return;
}
if (chunksBytes.length < chunkCount * 32) {
throw Error(`chunksBytes length ${chunksBytes.length} is less than chunkCount ${chunkCount}`);
}
const subtreeRoots = new Array(progressiveSubtreeCount(chunkCount));
let chunkOffset = 0;
let subtreeLength = BASE_CHUNK_COUNT;
for (let subtreeIndex = 0; chunkOffset < chunkCount; subtreeIndex++) {
const subtreeChunkCount = Math.min(subtreeLength, chunkCount - chunkOffset);
const subtreeRoot = new Uint8Array(32);
const start = chunkOffset * 32;
const end = start + subtreeChunkCount * 32;
let subtreeBytes;
if (subtreeChunkCount === subtreeLength && (subtreeLength === 1 || subtreeChunkCount % 2 === 0)) {
subtreeBytes = chunksBytes.subarray(start, end);
}
else {
subtreeBytes = new Uint8Array(Math.max(32, Math.ceil(subtreeChunkCount / 2) * 64));
subtreeBytes.set(chunksBytes.subarray(start, end));
}
merkleizeBlocksBytes(subtreeBytes, subtreeLength, subtreeRoot, 0);
subtreeRoots[subtreeIndex] = subtreeRoot;
chunkOffset += subtreeChunkCount;
subtreeLength *= SCALING_FACTOR;
}
const root = new Uint8Array(32);
for (let i = subtreeRoots.length - 1; i >= 0; i--) {
digest64Into(subtreeRoots[i], root, root);
}
output.set(root, offset);
}
export function progressiveSubtreeFillToContents(nodes) {
if (nodes.length === 0) {
return zeroNode(0);
}
const subtreeRoots = [];
let nodeOffset = 0;
let subtreeLength = BASE_CHUNK_COUNT;
for (let subtreeIndex = 0; nodeOffset < nodes.length; subtreeIndex++) {
const subtreeNodeCount = Math.min(subtreeLength, nodes.length - nodeOffset);
const subtreeDepth = progressiveSubtreeDepth(subtreeIndex);
subtreeRoots.push(subtreeFillToContents(nodes.slice(nodeOffset, nodeOffset + subtreeNodeCount), subtreeDepth));
nodeOffset += subtreeNodeCount;
subtreeLength *= SCALING_FACTOR;
}
let root = zeroNode(0);
for (let i = subtreeRoots.length - 1; i >= 0; i--) {
root = new BranchNode(subtreeRoots[i], root);
}
return root;
}
export function getNodesAtProgressiveDepth(rootNode, count) {
const nodes = [];
let node = rootNode;
let remaining = count;
let subtreeLength = BASE_CHUNK_COUNT;
for (let subtreeIndex = 0; remaining > 0; subtreeIndex++) {
if (node.isLeaf()) {
throw Error("Invalid progressive tree: missing subtree branch");
}
const subtreeNodeCount = Math.min(subtreeLength, remaining);
const subtreeDepth = progressiveSubtreeDepth(subtreeIndex);
nodes.push(...getNodesAtDepth(node.left, subtreeDepth, 0, subtreeNodeCount));
node = node.right;
remaining -= subtreeNodeCount;
subtreeLength *= SCALING_FACTOR;
}
return nodes;
}
//# sourceMappingURL=progressive.js.map