ts-mls
Version: 
[](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml) [](https://badge.fury.io/js/ts-mls) [ => [i.encryptionKey, i.parentHash, i.originalSiblingTreeHash]);
export const decodeParentHashInput = mapDecoders([decodeVarLenData, decodeVarLenData, decodeVarLenData], (encryptionKey, parentHash, originalSiblingTreeHash) => ({
    encryptionKey,
    parentHash,
    originalSiblingTreeHash,
}));
function validateParentHashCoverage(parentIndices, coverage) {
    for (const index of parentIndices) {
        if ((coverage[index] ?? 0) !== 1) {
            return false;
        }
    }
    return true;
}
export async function verifyParentHashes(tree, h) {
    const parentNodes = tree.reduce((acc, cur, index) => {
        if (cur !== undefined && cur.nodeType === "parent") {
            return [...acc, index];
        }
        else
            return acc;
    }, []);
    if (parentNodes.length === 0)
        return true;
    const coverage = await parentHashCoverage(tree, h);
    return validateParentHashCoverage(parentNodes, coverage);
}
/**
 * Traverse tree from bottom up, verifying that all non-blank parent nodes are covered by exactly one chain
 */
function parentHashCoverage(tree, h) {
    const leaves = tree.filter((_v, i) => isLeaf(toNodeIndex(i)));
    return leaves.reduce(async (acc, leafNode, leafIndex) => {
        if (leafNode === undefined)
            return acc;
        let currentIndex = leafToNodeIndex(toLeafIndex(leafIndex));
        let updated = { ...(await acc) };
        const rootIndex = root(leafWidth(tree.length));
        while (currentIndex !== rootIndex) {
            const currentNode = tree[currentIndex];
            // skip blank nodes
            if (currentNode === undefined) {
                continue;
            }
            // parentHashNodeIndex is the node index where the nearest non blank ancestor was
            const [parentHash, parentHashNodeIndex] = await calculateParentHash(tree, currentIndex, h);
            if (parentHashNodeIndex === undefined) {
                throw new InternalError("Reached root before completing parent hash coeverage");
            }
            const expectedParentHash = getParentHash(currentNode);
            if (expectedParentHash !== undefined && constantTimeEqual(parentHash, expectedParentHash)) {
                const newCount = (updated[parentHashNodeIndex] ?? 0) + 1;
                updated = { ...updated, [parentHashNodeIndex]: newCount };
            }
            else {
                // skip to next leaf
                break;
            }
            currentIndex = parentHashNodeIndex;
        }
        return updated;
    }, Promise.resolve({}));
}
function getParentHash(node) {
    if (node.nodeType === "parent")
        return node.parent.parentHash;
    else if (node.leaf.leafNodeSource === "commit")
        return node.leaf.parentHash;
}
/**
 * Calculcates parent hash for a given node or leaf and returns the node index of the parent or undefined if the given node is the root node.
 */
export async function calculateParentHash(tree, nodeIndex, h) {
    const rootIndex = root(leafWidth(tree.length));
    if (nodeIndex === rootIndex) {
        return [new Uint8Array(), undefined];
    }
    const parentNodeIndex = findFirstNonBlankAncestor(tree, nodeIndex);
    const parentNode = tree[parentNodeIndex];
    if (parentNodeIndex === rootIndex && parentNode === undefined) {
        return [new Uint8Array(), parentNodeIndex];
    }
    const siblingIndex = nodeIndex < parentNodeIndex ? right(parentNodeIndex) : left(parentNodeIndex);
    if (parentNode === undefined || parentNode.nodeType === "leaf")
        throw new InternalError("Expected non-blank parent Node");
    const removedUnmerged = removeLeaves(tree, parentNode.parent.unmergedLeaves);
    const originalSiblingTreeHash = await treeHash(removedUnmerged, siblingIndex, h);
    const input = {
        encryptionKey: parentNode.parent.hpkePublicKey,
        parentHash: parentNode.parent.parentHash,
        originalSiblingTreeHash,
    };
    return [await h.digest(encodeParentHashInput(input)), parentNodeIndex];
}
//# sourceMappingURL=parentHash.js.map