UNPKG

@jayanth-kumar-morem/sparse-merkle-tree

Version:

A TypeScript implementation of Sparse Merkle Trees with Poseidon hash function support

158 lines 5.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SMT = void 0; class SMT { constructor(poseidon, depth = 20) { this.leaves = new Map(); this.poseidon = poseidon; this.depth = depth; // Precompute default values - same as circuit logic this.defaults = [0n]; for (let i = 1; i <= depth; i++) { this.defaults[i] = this.hash(this.defaults[i - 1], this.defaults[i - 1]); } } hash(left, right) { const F = this.poseidon.F; return F.toObject(this.poseidon([left, right])); } getPathBits(key) { const bits = []; for (let i = 0; i < this.depth; i++) { bits.push(Number(key >> BigInt(i) & 1n)); } return bits; } add(key, value) { this.leaves.set(key.toString(), value); } get(key) { return this.leaves.get(key.toString()); } has(key) { return this.leaves.has(key.toString()); } // Get the root of the current tree get root() { if (this.leaves.size === 0) { return this.defaults[this.depth]; } // Build the tree efficiently const tree = new Map(); // Set all leaf values for (const [keyStr, value] of this.leaves) { const key = BigInt(keyStr); const pathBits = this.getPathBits(key); let path = ''; for (let i = 0; i < this.depth; i++) { path = pathBits[i].toString() + path; } tree.set(`leaf:${path}`, value); } // Build tree bottom-up for (let level = this.depth - 1; level >= 0; level--) { for (let nodeIndex = 0; nodeIndex < (1 << level); nodeIndex++) { const nodePath = nodeIndex.toString(2).padStart(level, '0'); const leftKey = level === this.depth - 1 ? `leaf:${nodePath}0` : `node:${level + 1}:${nodePath}0`; const rightKey = level === this.depth - 1 ? `leaf:${nodePath}1` : `node:${level + 1}:${nodePath}1`; const leftChild = tree.get(leftKey) || 0n; const rightChild = tree.get(rightKey) || 0n; let nodeValue; if (leftChild === 0n && rightChild === 0n) { nodeValue = this.defaults[level + 1]; } else { nodeValue = this.hash(leftChild, rightChild); } tree.set(`node:${level}:${nodePath}`, nodeValue); } } return tree.get('node:0:') || this.defaults[this.depth]; } // Create a non-membership proof for a key createNonMembershipProof(key) { const pathBits = this.getPathBits(key); const siblings = []; if (this.leaves.size === 0) { // For empty tree, all siblings are default values for (let level = 0; level < this.depth; level++) { siblings.push(this.defaults[level]); } } else { // For non-empty tree, compute siblings by building the tree and extracting them // We'll use a recursive approach to compute the sibling at each level for (let level = 0; level < this.depth; level++) { const sibling = this.computeSiblingAtLevel(key, level); siblings.push(sibling); } } return { siblings, pathBits, root: this.root }; } // Compute the sibling at a specific level for a given key computeSiblingAtLevel(key, level) { const pathBits = this.getPathBits(key); // Get the bit at this level (0 = left, 1 = right) const bit = pathBits[level]; // Build the sibling key by flipping the bit at this level let siblingKey = key; if (bit === 0) { // Set the bit at this level siblingKey = key | (1n << BigInt(level)); } else { // Clear the bit at this level siblingKey = key & ~(1n << BigInt(level)); } // Mask off the bits above this level to get the subtree key const mask = (1n << BigInt(level + 1)) - 1n; siblingKey = siblingKey & mask; // If we're at the leaf level, check if the sibling exists if (level === 0) { return this.get(siblingKey) || 0n; } // For internal levels, compute the subtree root return this.computeSubtreeRoot(siblingKey, level); } // Compute the root of a subtree starting at a given key and level computeSubtreeRoot(baseKey, level) { if (level === 0) { return this.get(baseKey) || 0n; } // Get left and right children const leftKey = baseKey; const rightKey = baseKey | (1n << BigInt(level - 1)); const leftChild = this.computeSubtreeRoot(leftKey, level - 1); const rightChild = this.computeSubtreeRoot(rightKey, level - 1); // If both children are 0, return the default for this level if (leftChild === 0n && rightChild === 0n) { return this.defaults[level]; } return this.hash(leftChild, rightChild); } // Verify a non-membership proof verifyNonMembershipProof(key, siblings, pathBits, expectedRoot) { let current = 0n; // Non-membership means value is 0 for (let i = 0; i < this.depth; i++) { const sibling = siblings[i]; const isRight = pathBits[i]; let left, right; if (isRight === 1) { left = sibling; right = current; } else { left = current; right = sibling; } current = this.hash(left, right); } return current === expectedRoot; } } exports.SMT = SMT; //# sourceMappingURL=index.js.map