UNPKG

@zk-kit/merkle-tree

Version:
224 lines (221 loc) 8.13 kB
/** * @module @zk-kit/merkle-tree * @version 0.1.0 * @file Merkle tree implementation in TypeScript. * @copyright Omar Desogus 2022 * @license MIT * @see [Github]{@link https://github.com/appliedzkp/zk-kit/tree/main/packages/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(", "))); } } /** * 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 MerkleTree class is a TypeScript implementation of Merkle tree and it provides all the functions to create * efficient trees and to generate and verify proofs of membership. */ var MerkleTree = /** @class */ (function () { /** * Initializes the Merkle tree with the hash function, the depth and the zero value to use for zeroes. * @param hash Hash function. * @param depth Tree depth. * @param zeroValue Zero values for zeroes. */ function MerkleTree(hash, depth, zeroValue) { checkParameter(hash, "hash", "function"); checkParameter(depth, "depth", "number"); checkParameter(zeroValue, "zeroValue", "number", "string", "bigint"); if (depth < 1 || depth > MerkleTree.maxDepth) { throw new Error("The tree depth must be between 1 and 32"); } // Initialize the attributes. this._hash = hash; this._depth = depth; this._zeroes = []; this._nodes = []; for (var i = 0; i < depth; i += 1) { this._zeroes.push(zeroValue); this._nodes[i] = []; // There must be a zero value for each tree level (except the root). zeroValue = hash([zeroValue, zeroValue]); } // The default root is the last zero value. this._root = zeroValue; // Freeze the array objects. It prevents unintentional changes. Object.freeze(this._zeroes); Object.freeze(this._nodes); } Object.defineProperty(MerkleTree.prototype, "root", { /** * Returns the root hash of the tree. * @returns Root hash. */ get: function () { return this._root; }, enumerable: false, configurable: true }); Object.defineProperty(MerkleTree.prototype, "depth", { /** * Returns the depth of the tree. * @returns Tree depth. */ get: function () { return this._depth; }, enumerable: false, configurable: true }); Object.defineProperty(MerkleTree.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(MerkleTree.prototype, "zeroes", { /** * Returns the zeroes nodes of the tree. * @returns List of zeroes. */ get: function () { return this._zeroes; }, enumerable: false, configurable: true }); /** * Inserts a new leaf in the tree. * @param leaf New leaf. */ MerkleTree.prototype.insert = function (leaf) { var _this = this; checkParameter(leaf, "leaf", "number", "string", "bigint"); if (leaf === this._zeroes[0]) { throw new Error("The leaf cannot be a zero value"); } if (this.leaves.length >= Math.pow(2, this._depth)) { throw new Error("The tree is full"); } var node = leaf; this.forEachLevel(this.leaves.length, function (l, i, d) { _this._nodes[l][i] = node; if (d) { node = _this._hash([node, _this._zeroes[l]]); } else { node = _this._hash([_this._nodes[l][i - 1], node]); } }); this._root = node; }; /** * 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. */ MerkleTree.prototype.delete = function (index) { var _this = this; checkParameter(index, "index", "number"); if (index < 0 || index >= this.leaves.length) { throw new Error("The leaf does not exist in this tree"); } var node = this._zeroes[0]; this.forEachLevel(index, function (l, i, d) { _this._nodes[l][i] = node; if (d) { node = _this._hash([node, _this._nodes[l][i + 1] || _this._zeroes[l]]); } else { node = _this._hash([_this._nodes[l][i - 1], node]); } }); this._root = node; }; /** * Creates a proof of membership. * @param index Index of the proof's leaf. * @returns Proof object. */ MerkleTree.prototype.createProof = function (index) { var _this = this; checkParameter(index, "index", "number"); if (index < 0 || index >= this.leaves.length) { throw new Error("The leaf does not exist in this tree"); } var siblingNodes = []; var path = []; this.forEachLevel(index, function (l, i, d) { if (d) { path.push(0); siblingNodes.push(_this._nodes[l][i + 1] || _this._zeroes[l]); } else { path.push(1); siblingNodes.push(_this._nodes[l][i - 1]); } }); return { root: this._root, leaf: this.leaves[index], siblingNodes: siblingNodes, path: path }; }; /** * Verifies a proof and return true or false. * @param proof Proof to be verified. * @returns True or false. */ MerkleTree.prototype.verifyProof = function (proof) { checkParameter(proof, "proof", "object"); checkParameter(proof.root, "proof.root", "number", "string", "bigint"); checkParameter(proof.leaf, "proof.leaf", "number", "string", "bigint"); checkParameter(proof.siblingNodes, "proof.siblingNodes", "object"); checkParameter(proof.path, "proof.path", "object"); var node = proof.leaf; for (var i = 0; i < proof.siblingNodes.length; i += 1) { if (proof.path[i]) { node = this._hash([proof.siblingNodes[i], node]); } else { node = this._hash([node, proof.siblingNodes[i]]); } } return proof.root === node; }; /** * Returns the index of a leaf. If the leaf does not exist it returns -1. * @param leaf Tree leaf. * @returns Index of the leaf. */ MerkleTree.prototype.indexOf = function (leaf) { checkParameter(leaf, "leaf", "number", "string", "bigint"); return this.leaves.indexOf(leaf); }; /** * Provides a bottom-up tree traversal where for each level it calls a callback. * @param index Index of the leaf. * @param callback Callback with tree level, index of node in that level and direction (left node: true, right node: false). */ MerkleTree.prototype.forEachLevel = function (index, callback) { for (var level = 0; level < this._depth; level += 1) { callback(level, index, index % 2 === 0); index = Math.floor(index / 2); } }; MerkleTree.maxDepth = 32; // 2**32 = 4294967296 possible leaves. return MerkleTree; }()); export { MerkleTree };