@jayanth-kumar-morem/sparse-merkle-tree
Version:
A TypeScript implementation of Sparse Merkle Trees with Poseidon hash function support
158 lines • 5.96 kB
JavaScript
"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