@kevincharm/sparse-merkle-tree
Version:
Sparse Merkle Tree implementation in Solidity with accompanying JS library
186 lines (185 loc) • 6.74 kB
JavaScript
"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;