UNPKG

@zk-kit/incremental-merkle-tree

Version:
298 lines (290 loc) 11.4 kB
/** * @module @zk-kit/incremental-merkle-tree * @version 1.1.0 * @file Incremental Merkle tree implementation in TypeScript. * @copyright Cedoor 2023 * @license MIT * @see [Github]{@link https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/incremental-merkle-tree} */ function checkParameter(value, name) { var types = []; for (var _i = 2; _i < arguments.length; _i++) { types[_i - 2] = arguments[_i]; } if (value === undefined) { throw new TypeError("Parameter '".concat(name, "' is not defined")); } if (!types.includes(typeof value)) { throw new TypeError("Parameter '".concat(name, "' is none of these types: ").concat(types.join(", "))); } } function createProof(index, depth, arity, nodes, zeroes, root) { checkParameter(index, "index", "number"); if (index < 0 || index >= nodes[0].length) { throw new Error("The leaf does not exist in this tree"); } var siblings = []; var pathIndices = []; var leafIndex = index; for (var level = 0; level < depth; level += 1) { var position = index % arity; var levelStartIndex = index - position; var levelEndIndex = levelStartIndex + arity; pathIndices[level] = position; siblings[level] = []; for (var i = levelStartIndex; i < levelEndIndex; i += 1) { if (i !== index) { if (i < nodes[level].length) { siblings[level].push(nodes[level][i]); } else { siblings[level].push(zeroes[level]); } } } index = Math.floor(index / arity); } return { root: root, leaf: nodes[0][leafIndex], pathIndices: pathIndices, siblings: siblings }; } function indexOf(leaf, nodes) { checkParameter(leaf, "leaf", "number", "string", "bigint"); return nodes[0].indexOf(leaf); } function insert(leaf, depth, arity, nodes, zeroes, hash) { checkParameter(leaf, "leaf", "number", "string", "bigint"); if (nodes[0].length >= Math.pow(arity, depth)) { throw new Error("The tree is full"); } var node = leaf; var index = nodes[0].length; for (var level = 0; level < depth; level += 1) { var position = index % arity; var levelStartIndex = index - position; var levelEndIndex = levelStartIndex + arity; var children = []; nodes[level][index] = node; for (var i = levelStartIndex; i < levelEndIndex; i += 1) { if (i < nodes[level].length) { children.push(nodes[level][i]); } else { children.push(zeroes[level]); } } node = hash(children); index = Math.floor(index / arity); } return node; } function update(index, value, depth, arity, nodes, zeroes, hash) { checkParameter(index, "index", "number"); if (index < 0 || index >= nodes[0].length) { throw new Error("The leaf does not exist in this tree"); } var node = value; for (var level = 0; level < depth; level += 1) { var position = index % arity; var levelStartIndex = index - position; var levelEndIndex = levelStartIndex + arity; var children = []; nodes[level][index] = node; for (var i = levelStartIndex; i < levelEndIndex; i += 1) { if (i < nodes[level].length) { children.push(nodes[level][i]); } else { children.push(zeroes[level]); } } node = hash(children); index = Math.floor(index / arity); } return node; } function verifyProof(proof, hash) { checkParameter(proof, "proof", "object"); checkParameter(proof.root, "proof.root", "number", "string", "bigint"); checkParameter(proof.leaf, "proof.leaf", "number", "string", "bigint"); checkParameter(proof.siblings, "proof.siblings", "object"); checkParameter(proof.pathIndices, "proof.pathElements", "object"); var node = proof.leaf; for (var i = 0; i < proof.siblings.length; i += 1) { var children = proof.siblings[i].slice(); children.splice(proof.pathIndices[i], 0, node); node = hash(children); } return proof.root === node; } /** * A Merkle tree is a tree in which every leaf node is labelled with the cryptographic hash of a * data block, and every non-leaf node is labelled with the cryptographic hash of the labels of its child nodes. * It allows efficient and secure verification of the contents of large data structures. * The IncrementalMerkleTree class is a TypeScript implementation of Incremental Merkle tree and it * provides all the functions to create efficient trees and to generate and verify proofs of membership. */ var IncrementalMerkleTree = /** @class */ (function () { /** * Initializes the tree with the hash function, the depth, the zero value to use for zeroes * and the arity (i.e. the number of children for each node). * @param hash Hash function. * @param depth Tree depth. * @param zeroValue Zero values for zeroes. * @param arity The number of children for each node. * @param leaves The list of initial leaves. */ function IncrementalMerkleTree(hash, depth, zeroValue, arity, leaves) { if (arity === void 0) { arity = 2; } if (leaves === void 0) { leaves = []; } var _a; checkParameter(hash, "hash", "function"); checkParameter(depth, "depth", "number"); checkParameter(zeroValue, "zeroValue", "number", "string", "bigint"); checkParameter(arity, "arity", "number"); checkParameter(leaves, "leaves", "object"); if (depth < 1 || depth > IncrementalMerkleTree.maxDepth) { throw new Error("The tree depth must be between 1 and 32"); } if (leaves.length > Math.pow(arity, depth)) { throw new Error("The tree cannot contain more than ".concat(Math.pow(arity, depth), " leaves")); } // Initialize the attributes. this._hash = hash; this._depth = depth; this._zeroes = []; this._nodes = []; this._arity = arity; for (var level = 0; level < depth; level += 1) { this._zeroes.push(zeroValue); this._nodes[level] = []; // There must be a zero value for each tree level (except the root). zeroValue = hash(Array(this._arity).fill(zeroValue)); } this._nodes[depth] = []; // It initializes the tree with a list of leaves if there are any. if (leaves.length > 0) { this._nodes[0] = leaves; for (var level = 0; level < depth; level += 1) { for (var index = 0; index < Math.ceil(this._nodes[level].length / arity); index += 1) { var position = index * arity; var children = []; for (var i = 0; i < arity; i += 1) { children.push((_a = this._nodes[level][position + i]) !== null && _a !== void 0 ? _a : this.zeroes[level]); } this._nodes[level + 1][index] = hash(children); } } } else { // If there are no leaves, the default root is the last zero value. this._nodes[depth][0] = zeroValue; } // Freeze the array objects. It prevents unintentional changes. Object.freeze(this._zeroes); Object.freeze(this._nodes); } Object.defineProperty(IncrementalMerkleTree.prototype, "root", { /** * Returns the root hash of the tree. * @returns Root hash. */ get: function () { return this._nodes[this.depth][0]; }, enumerable: false, configurable: true }); Object.defineProperty(IncrementalMerkleTree.prototype, "depth", { /** * Returns the depth of the tree. * @returns Tree depth. */ get: function () { return this._depth; }, enumerable: false, configurable: true }); Object.defineProperty(IncrementalMerkleTree.prototype, "leaves", { /** * Returns the leaves of the tree. * @returns List of leaves. */ get: function () { return this._nodes[0].slice(); }, enumerable: false, configurable: true }); Object.defineProperty(IncrementalMerkleTree.prototype, "zeroes", { /** * Returns the zeroes nodes of the tree. * @returns List of zeroes. */ get: function () { return this._zeroes; }, enumerable: false, configurable: true }); Object.defineProperty(IncrementalMerkleTree.prototype, "arity", { /** * Returns the number of children for each node. * @returns Number of children per node. */ get: function () { return this._arity; }, enumerable: false, configurable: true }); /** * Returns the index of a leaf. If the leaf does not exist it returns -1. * @param leaf Tree leaf. * @returns Index of the leaf. */ IncrementalMerkleTree.prototype.indexOf = function (leaf) { return indexOf(leaf, this._nodes); }; /** * Inserts a new leaf in the tree. * @param leaf New leaf. */ IncrementalMerkleTree.prototype.insert = function (leaf) { this._nodes[this.depth][0] = insert(leaf, this.depth, this.arity, this._nodes, this.zeroes, this._hash); }; /** * Deletes a leaf from the tree. It does not remove the leaf from * the data structure. It set the leaf to be deleted to a zero value. * @param index Index of the leaf to be deleted. */ IncrementalMerkleTree.prototype.delete = function (index) { this._nodes[this.depth][0] = update(index, this.zeroes[0], this.depth, this.arity, this._nodes, this.zeroes, this._hash); }; /** * Updates a leaf in the tree. * @param index Index of the leaf to be updated. * @param newLeaf New leaf value. */ IncrementalMerkleTree.prototype.update = function (index, newLeaf) { this._nodes[this.depth][0] = update(index, newLeaf, this.depth, this.arity, this._nodes, this.zeroes, this._hash); }; /** * Creates a proof of membership. * @param index Index of the proof's leaf. * @returns Proof object. */ IncrementalMerkleTree.prototype.createProof = function (index) { return createProof(index, this.depth, this.arity, this._nodes, this.zeroes, this.root); }; /** * Verifies a proof and return true or false. * @param proof Proof to be verified. * @returns True or false. */ IncrementalMerkleTree.prototype.verifyProof = function (proof) { return verifyProof(proof, this._hash); }; IncrementalMerkleTree.maxDepth = 32; return IncrementalMerkleTree; }()); export { IncrementalMerkleTree };