UNPKG

@kevincharm/sparse-merkle-tree

Version:

Sparse Merkle Tree implementation in Solidity with accompanying JS library

180 lines (179 loc) 6.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SparseMerkleTree = void 0; const utils_1 = require("./utils"); /** * SparseMerkleTree * Raw sparse Merkle tree implementation */ class SparseMerkleTree { constructor(depth, options = {}) { this.db = new Map(); this._root = 0n; this._depth = depth; this._zeroElement = options.zeroElement || 0n; this._hashFn = options.hashFn || utils_1.keccak; this._deserialiserFn = options.deserialiserFn || utils_1.deserialise; this._serialiserFn = options.serialiserFn || utils_1.serialise; } hash(...inputs) { return this._hashFn(...inputs); } get root() { return this._serialiserFn(this._root); } /** * Get a proof of membership (or non-membership) of a leaf * * @param index Index of leaf * @returns {Proof} Proof of membership (or non-membership) */ get(index) { let enables = 0n; const siblings = []; let child = this._root; // root->leaf for (let i = 0; i < this._depth; i++) { // MSB->LSB let j = Number((index >> BigInt(this._depth - i - 1)) & 1n); const sibling = this.db.get(child)?.[j ^ 1] || this._zeroElement; child = this.db.get(child)?.[j] || this._zeroElement; if (sibling > 0n) { siblings.unshift(sibling); enables |= 1n << BigInt(this._depth - i - 1); } } let exists = true; const leafKeyValue = this.db.get(child); if (!leafKeyValue) { exists = false; } if (leafKeyValue && leafKeyValue.length < 3) { throw new Error(`Invariant error: ${leafKeyValue} not a leaf: ${leafKeyValue}`); } return { exists, leaf: this._serialiserFn(child), value: leafKeyValue ? this._serialiserFn(leafKeyValue[1]) : null, index, enables, siblings: siblings.map(this._serialiserFn), }; } /** * Verify membership of a leaf using a Merkle proof. * * @param leaf Leaf = H(key, value, 1) * @param index Index = H(key), i.e. path of leaf in the Merkle tree * @param enables 256-bitstring signifying which siblings are non-zero in the path * @param siblings Non-zero sibling subtree hashes * @returns {boolean} true if the proof is valid for this tree */ verifyProof(leaf, index, enables, siblings) { // rebuild root from leaf->root let root = this._deserialiserFn(leaf); let s = 0; for (let i = 0; i < this._depth; i++) { // LSB->MSB let j = Number((index >> BigInt(i)) & 1n); const isRightChild = Boolean(j); const sibling = (enables >> BigInt(i)) & 1n ? this._deserialiserFn(siblings[s++]) : this._zeroElement; const children = isRightChild ? [sibling, root] : [root, sibling]; // Create new parent hash & store root = this.hash(...children); } return root === this._root; } /** * Walk down the tree to determine whether a key exists in the database. * * @param index Index of leaf * @returns true if key exists */ exists(index) { let leaf = this._root; for (let i = 0; i < this._depth; i++) { // MSB->LSB let j = Number((index >> BigInt(this._depth - i - 1)) & 1n); leaf = this.db.get(leaf)?.[j] || this._zeroElement; if (leaf === this._zeroElement) break; } return leaf !== this._zeroElement; } /** * Insert a (key,value) into the database. Throws if key already exists. * * @param index Index of leaf * @param leaf Value of leaf * @returns {Proof} Membership of previous (key,value) leaf */ insert(index, leaf) { if (this.exists(index)) { throw new Error(`Leaf at index ${index} already exists!`); } return this.upsert(index, leaf); } /** * Update a value belonging to an existing key. Throws if key does not exist. * * @param index Index of leaf * @param newLeaf New value of leaf * @returns {Proof} Membership of previous (key,value) leaf */ update(index, newLeaf) { if (!this.exists(index)) { throw new Error(`Leaf at index ${index} does not exist!`); } return this.upsert(index, newLeaf); } /** * Update the value of a leaf at a specified index * * @param index Index of leaf * @param newLeaf_ New value of leaf * @returns {Proof} Membership of previous (key,value) leaf */ upsert(index, newLeaf_) { const newLeaf = this._deserialiserFn(newLeaf_); const proof = this.get(index); // 1. Walk root->leaf and delete parent hashes (collect siblings while // we're at it) const siblings = []; let nextParent = this._root; for (let i = 0; i < this._depth; i++) { const currParent = nextParent; // MSB->LSB let j = Number((proof.index >> BigInt(this._depth - i - 1)) & 1n); // Get children before we delete this parent entry const sibling = this.db.get(nextParent)?.[j ^ 1] || this._zeroElement; siblings.push(sibling); nextParent = this.db.get(nextParent)?.[j] || this._zeroElement; // Delete this parent entry this.db.delete(currParent); } // 2. Insert new leaf this.db.set(newLeaf, [index, newLeaf, 1n]); // 3. Walk leaf->root and create parent hashes let child = newLeaf; for (let i = 0; i < this._depth; i++) { // LSB->MSB let j = Number((proof.index >> BigInt(i)) & 1n); const isRightChild = Boolean(j); const sibling = siblings.pop(); const children = isRightChild ? [sibling, child] : [child, sibling]; // Create new parent hash & store const parent = this.hash(...children); this.db.set(parent, children); child = parent; } this._root = child; return { newLeaf: this._serialiserFn(newLeaf), ...proof, }; } } exports.SparseMerkleTree = SparseMerkleTree;