@zk-kit/incremental-merkle-tree
Version:
Incremental Merkle tree implementation in TypeScript.
298 lines (290 loc) • 11.4 kB
JavaScript
/**
* @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 };