UNPKG

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

Version:

A TypeScript implementation of Indexed Merkle Trees with Poseidon hash function support to perform non membership proofs

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