@zk-kit/merkle-tree
Version:
Merkle tree implementation in TypeScript.
228 lines (223 loc) • 8.22 kB
JavaScript
/**
* @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}
*/
;
Object.defineProperty(exports, '__esModule', { value: true });
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;
}());
exports.MerkleTree = MerkleTree;