@jayanth-kumar-morem/indexed-merkle-tree
Version:
Indexed Merkle Tree implemented in Typescript and circom circuits using Poseidon hashing to perform non membership proofs with serialising and deserialising to off chain storage
242 lines • 8.62 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.IndexedMerkleTree = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const poseidon_lite_1 = require("poseidon-lite");
class IndexedMerkleTree {
constructor() {
this.depth = 32;
this.nodes = [];
this.leaves = [];
this.zeros = [0n];
for (let i = 1; i <= this.depth; i++) {
this.zeros[i] = (0, poseidon_lite_1.poseidon2)([this.zeros[i - 1], this.zeros[i - 1]]);
}
this.zero = this.zeros[this.depth];
for (let i = 0; i <= this.depth; i++)
this.nodes[i] = [];
this.leaves.push({ val: 0n, nextVal: 0n, nextIdx: 0 });
this.updateLeafHash(0);
}
async new() {
return new IndexedMerkleTree();
}
leafHash(v, nextVal) {
const hash = (0, poseidon_lite_1.poseidon2)([v, nextVal]);
return this.toField(hash);
}
parentHash(l, r) {
const hash = (0, poseidon_lite_1.poseidon2)([l, r]);
return this.toField(hash);
}
toField(hash) {
if (typeof hash === 'bigint') {
return hash;
}
else if (hash instanceof Uint8Array) {
let result = 0n;
for (let i = 0; i < hash.length; i++) {
result = result * 256n + BigInt(hash[i]);
}
return result;
}
else if (Array.isArray(hash)) {
let result = 0n;
for (let i = 0; i < hash.length; i++) {
result = result * 256n + BigInt(hash[i]);
}
return result;
}
else {
return BigInt(hash);
}
}
predecessorIndex(x) {
let bestIdx = -1;
let bestVal = -1n;
for (let i = 0; i < this.leaves.length; i++) {
if (this.leaves[i].val < x && this.leaves[i].val > bestVal) {
bestIdx = i;
bestVal = this.leaves[i].val;
}
}
return bestIdx;
}
setNode(level, index, h) {
const arr = this.nodes[level];
while (arr.length <= index)
arr.push(this.zeros[level]);
arr[index] = h;
}
async insert(x) {
if (this.leaves.some(leaf => leaf.val === x)) {
throw new Error(`Value ${x} already exists in the tree`);
}
const preIdx = this.predecessorIndex(x);
const newIdx = this.leaves.length;
if (preIdx >= 0) {
const predecessor = this.leaves[preIdx];
const sucIdx = predecessor.nextIdx;
const sucVal = predecessor.nextVal;
this.leaves.push({ val: x, nextIdx: sucIdx, nextVal: sucVal });
this.leaves[preIdx].nextIdx = newIdx;
this.leaves[preIdx].nextVal = x;
this.updateLeafHash(preIdx);
}
else {
throw new Error("No predecessor found - tree not properly initialized");
}
this.updateLeafHash(newIdx);
}
updateLeafHash(leafIdx) {
const leaf = this.leaves[leafIdx];
let h = this.leafHash(leaf.val, leaf.nextVal);
this.setNode(0, leafIdx, h);
let currentIdx = leafIdx;
for (let lvl = 0; lvl < this.depth; lvl++) {
const isRight = currentIdx & 1;
const siblingIdx = isRight ? currentIdx - 1 : currentIdx + 1;
const siblingHash = this.nodes[lvl][siblingIdx] ?? this.zeros[lvl];
const leftHash = isRight ? siblingHash : h;
const rightHash = isRight ? h : siblingHash;
h = this.parentHash(leftHash, rightHash);
currentIdx = Math.floor(currentIdx / 2);
this.setNode(lvl + 1, currentIdx, h);
}
}
get root() {
const rootLevel = this.nodes[this.depth];
if (rootLevel && rootLevel.length > 0) {
return rootLevel[0];
}
return this.zero;
}
createNonMembershipProof(x) {
const preLeafIdx = this.predecessorIndex(x);
if (preLeafIdx < 0) {
throw new Error("IMT requires at least one leaf before proving absence");
}
const preLeaf = this.leaves[preLeafIdx];
const path = [];
const directions = [];
let currentIdx = preLeafIdx;
for (let lvl = 0; lvl < this.depth; lvl++) {
const isRight = currentIdx & 1;
const siblingIdx = isRight ? currentIdx - 1 : currentIdx + 1;
const siblingHash = this.nodes[lvl][siblingIdx] ?? this.zeros[lvl];
path.push(siblingHash);
directions.push(isRight ? 1 : 0);
currentIdx = Math.floor(currentIdx / 2);
}
return {
directions,
path,
preLeaf,
query: x,
root: this.root
};
}
verifyNonMembershipProof(p) {
let h = this.leafHash(p.preLeaf.val, p.preLeaf.nextVal);
for (let lvl = 0; lvl < this.depth; lvl++) {
const sib = p.path[lvl];
const dir = p.directions[lvl];
h = dir ? this.parentHash(sib, h) : this.parentHash(h, sib);
}
if (h !== p.root)
return false;
if (!(p.preLeaf.val < p.query))
return false;
if (p.preLeaf.nextVal !== 0n && !(p.query < p.preLeaf.nextVal))
return false;
return true;
}
serialize() {
return {
depth: this.depth,
nodes: this.nodes.map(level => level.map(node => node.toString())),
leaves: this.leaves.map(leaf => ({
val: leaf.val.toString(),
nextVal: leaf.nextVal.toString(),
nextIdx: leaf.nextIdx
}))
};
}
static deserialize(data) {
const tree = new IndexedMerkleTree();
tree.nodes = [];
tree.leaves = [];
tree.nodes = data.nodes.map(level => level.map(node => BigInt(node)));
tree.leaves = data.leaves.map(leaf => ({
val: BigInt(leaf.val),
nextVal: BigInt(leaf.nextVal),
nextIdx: leaf.nextIdx
}));
return tree;
}
async saveToFile(filePath) {
const serialized = this.serialize();
const jsonString = JSON.stringify(serialized, null, 2);
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filePath, jsonString, 'utf8');
}
static loadFromFile(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error(`File not found: ${filePath}`);
}
const jsonString = fs.readFileSync(filePath, 'utf8');
const data = JSON.parse(jsonString);
return IndexedMerkleTree.deserialize(data);
}
get size() {
return this.leaves.length;
}
getLeaves() {
return this.leaves.map(leaf => ({ ...leaf }));
}
getNodesAtLevel(level) {
if (level < 0 || level >= this.nodes.length) {
throw new Error(`Invalid level: ${level}`);
}
return [...this.nodes[level]];
}
}
exports.IndexedMerkleTree = IndexedMerkleTree;
//# sourceMappingURL=index.js.map