UNPKG

@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
"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