UNPKG

@kevincharm/sparse-merkle-tree

Version:

Sparse Merkle Tree implementation in Solidity with accompanying JS library

186 lines (185 loc) 6.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SparseMerkleTreeKV = exports.ZERO = exports.SMT_DEPTH = void 0; const utils_1 = require("./utils"); exports.SMT_DEPTH = 256; exports.ZERO = 0n; /** * SparseMerkleTreeKV * SMT-backed key-value database. */ class SparseMerkleTreeKV { constructor(options = {}) { this.db = new Map(); this._root = 0n; this._hashFn = options.hashFn || utils_1.keccak; this._deserialiserFn = options.deserialiserFn || utils_1.deserialise; this._serialiserFn = options.serialiserFn || utils_1.serialise; } hash(...inputs) { return this._hashFn(...inputs); } get root() { return this._serialiserFn(this._root); } /** * Get a proof of membership (or non-membership) of a key * * @param key Key * @returns {Proof} Proof of membership (or non-membership) */ get(key) { const index = this.hash(this._deserialiserFn(key)); let enables = 0n; const siblings = []; let child = this._root; // root->leaf for (let i = 0; i < exports.SMT_DEPTH; i++) { // MSB->LSB let j = Number((index >> BigInt(exports.SMT_DEPTH - i - 1)) & 1n); const sibling = this.db.get(child)?.[j ^ 1] || exports.ZERO; child = this.db.get(child)?.[j] || exports.ZERO; if (sibling > 0n) { siblings.unshift(sibling); enables |= 1n << BigInt(exports.SMT_DEPTH - i - 1); } } let exists = true; const leafKeyValue = this.db.get(child); if (!leafKeyValue) { exists = false; } if (leafKeyValue && leafKeyValue.length < 3) { throw new Error(`Invariant error: ${leafKeyValue} not a leaf: ${leafKeyValue}`); } return { exists, leaf: this._serialiserFn(child), value: leafKeyValue ? this._serialiserFn(leafKeyValue[1]) : null, index, enables, siblings: siblings.map(this._serialiserFn), }; } /** * Verify membership of a leaf using a Merkle proof. * * @param leaf Leaf = H(key, value, 1) * @param index Index = H(key), i.e. path of leaf in the Merkle tree * @param enables 256-bitstring signifying which siblings are non-zero in the path * @param siblings Non-zero sibling subtree hashes * @returns {boolean} true if the proof is valid for this tree */ verifyProof(leaf, index, enables, siblings) { // rebuild root from leaf->root let root = this._deserialiserFn(leaf); let s = 0; for (let i = 0; i < exports.SMT_DEPTH; i++) { // LSB->MSB let j = Number((index >> BigInt(i)) & 1n); const isRightChild = Boolean(j); const sibling = (enables >> BigInt(i)) & 1n ? this._deserialiserFn(siblings[s++]) : exports.ZERO; const children = isRightChild ? [sibling, root] : [root, sibling]; // Create new parent hash & store root = this.hash(...children); } return root === this._root; } /** * Walk down the tree to determine whether a key exists in the database. * * @param key * @returns true if key exists */ exists(key) { const index = this.hash(key); let leaf = this._root; for (let i = 0; i < exports.SMT_DEPTH; i++) { // MSB->LSB let j = Number((index >> BigInt(exports.SMT_DEPTH - i - 1)) & 1n); leaf = this.db.get(leaf)?.[j] || exports.ZERO; if (leaf === exports.ZERO) break; } return leaf !== exports.ZERO; } /** * Insert a (key,value) into the database. Throws if key already exists. * * @param key Key * @param value Value * @returns {Proof} Membership of previous (key,value) leaf */ insert(key, value) { const key_ = this._deserialiserFn(key); const index = this.hash(key_); if (this.exists(key_)) { throw new Error(`Leaf with key ${key_}@${index} already exists!`); } return this.upsert(key, value); } /** * Update a value belonging to an existing key. Throws if key does not exist. * * @param key Key * @param value New value * @returns {Proof} Membership of previous (key,value) leaf */ update(key, value) { const key_ = this._deserialiserFn(key); const index = this.hash(key_); if (!this.exists(key_)) { throw new Error(`Leaf with key ${key}@${index} does not exist!`); } return this.upsert(key, value); } /** * Update a value at key, regardless of whether it already exists or not. * * @param key Key * @param value Value * @returns {Proof} Membership of previous (key,value) leaf */ upsert(key, value) { const key_ = this._deserialiserFn(key); const value_ = this._deserialiserFn(value); const proof = this.get(key); // 1. Walk root->leaf and delete parent hashes (collect siblings while // we're at it) const siblings = []; let nextParent = this._root; for (let i = 0; i < exports.SMT_DEPTH; i++) { const currParent = nextParent; // MSB->LSB let j = Number((proof.index >> BigInt(exports.SMT_DEPTH - i - 1)) & 1n); // Get children before we delete this parent entry const sibling = this.db.get(nextParent)?.[j ^ 1] || exports.ZERO; siblings.push(sibling); nextParent = this.db.get(nextParent)?.[j] || exports.ZERO; // Delete this parent entry this.db.delete(currParent); } // 2. Insert new leaf const leaf = this.hash(key_, value_, 1n); this.db.set(leaf, [key_, value_, 1n]); // 3. Walk leaf->root and create parent hashes let child = leaf; for (let i = 0; i < exports.SMT_DEPTH; i++) { // LSB->MSB let j = Number((proof.index >> BigInt(i)) & 1n); const isRightChild = Boolean(j); const sibling = siblings.pop(); const children = isRightChild ? [sibling, child] : [child, sibling]; // Create new parent hash & store const parent = this.hash(...children); this.db.set(parent, children); child = parent; } this._root = child; return { newLeaf: this._serialiserFn(leaf), ...proof, }; } } exports.SparseMerkleTreeKV = SparseMerkleTreeKV;