merkletreejs
Version:
Construct Merkle Trees and verify proofs
253 lines (252 loc) • 8.88 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IncrementalMerkleTree = void 0;
const Base_1 = __importDefault(require("./Base"));
const treeify_1 = __importDefault(require("treeify"));
class IncrementalMerkleTree extends Base_1.default {
constructor(hashFn, options) {
super();
this.hashFn = hashFn;
if (options.depth) {
this.depth = options.depth;
}
if (options.arity) {
this.arity = options.arity;
}
if (this.depth < 1) {
throw new Error('depth must be greater than 0');
}
if (this.arity < 1) {
throw new Error('arity must be greater than 0');
}
const nodes = [];
let zeroValue = options.zeroValue;
this.zeroValue = zeroValue;
this.zeroes = [];
if (this.depth) {
for (let i = 0; i < this.depth; i++) {
this.zeroes.push(zeroValue);
nodes[i] = [];
zeroValue = this.hashFn(Array(this.arity).fill(zeroValue));
}
}
this.nodes = nodes;
this.root = zeroValue;
}
getRoot() {
return this.root;
}
getHexRoot() {
return this.bufferToHex(this.bufferify(this.getRoot()));
}
insert(leaf) {
if (this.depth && this.arity) {
if (this.nodes[0].length >= this.getMaxLeaves()) {
throw new Error('tree is full');
}
}
let node = leaf;
let index = this.nodes[0].length;
for (let level = 0; level < this.depth; level += 1) {
const position = index % this.arity;
const levelStartIndex = index - position;
const levelEndIndex = levelStartIndex + this.arity;
const children = [];
this.nodes[level][index] = node;
for (let i = levelStartIndex; i < levelEndIndex; i += 1) {
if (i < this.nodes[level].length) {
children.push(this.nodes[level][i]);
}
else {
children.push(this.zeroes[level]);
}
}
node = this.hashFn(children);
index = Math.floor(index / this.arity);
}
this.root = node;
}
delete(index) {
this.update(index, this.zeroValue);
}
update(index, newLeaf) {
if (index < 0 || index >= this.nodes[0].length) {
throw new Error('out of bounds');
}
let node = newLeaf;
for (let level = 0; level < this.depth; level += 1) {
const position = index % this.arity;
const levelStartIndex = index - position;
const levelEndIndex = levelStartIndex + this.arity;
const children = [];
this.nodes[level][index] = node;
for (let i = levelStartIndex; i < levelEndIndex; i += 1) {
if (i < this.nodes[level].length) {
children.push(this.nodes[level][i]);
}
else {
children.push(this.zeroes[level]);
}
}
node = this.hashFn(children);
index = Math.floor(index / this.arity);
}
this.root = node;
}
getDepth() {
return this.depth;
}
getArity() {
return this.arity;
}
getMaxLeaves() {
return Math.pow(this.depth, this.arity);
}
indexOf(leaf) {
return this.nodes[0].indexOf(leaf);
}
getLeaves() {
const leaves = this.copyList(this.nodes[0]);
const index = this.nodes[0].length;
for (let i = index; i < this.getMaxLeaves(); i++) {
leaves[i] = this.zeroValue;
}
return leaves;
}
copyList(list) {
return list.map((x) => BigInt(x));
}
getLayers() {
const layers = [];
for (const list of this.nodes) {
layers.push(this.copyList(list));
}
if (layers[0].length < this.getMaxLeaves()) {
let index = layers[0].length;
for (let i = index; i < this.getMaxLeaves(); i++) {
layers[0][i] = this.zeroValue;
}
for (let level = 0; level < this.depth; level++) {
const position = index % this.arity;
const levelStartIndex = index - position;
const levelEndIndex = levelStartIndex + this.arity;
for (let i = levelStartIndex; i < levelEndIndex; i++) {
if (i >= layers[level].length) {
layers[level][i] = this.zeroes[level];
}
}
index = Math.floor(index / this.arity);
}
}
layers.push([this.root]);
return layers;
}
getHexLayers() {
return this.getLayers().reduce((acc, item) => {
if (Array.isArray(item)) {
acc.push(item.map(layer => this.bufferToHex(this.bufferify(layer))));
}
else {
acc.push(item);
}
return acc;
}, []);
}
getLayersAsObject() {
const layers = this.getLayers().map((layer) => layer.map((value) => this.bufferToHex(this.bufferify(value), false)));
const objs = [];
for (let i = 0; i < layers.length; i++) {
const arr = [];
for (let j = 0; j < layers[i].length; j++) {
const obj = { [layers[i][j]]: null };
if (objs.length) {
obj[layers[i][j]] = {};
const a = objs.shift();
const akey = Object.keys(a)[0];
obj[layers[i][j]][akey] = a[akey];
if (objs.length) {
const b = objs.shift();
const bkey = Object.keys(b)[0];
obj[layers[i][j]][bkey] = b[bkey];
}
}
arr.push(obj);
}
objs.push(...arr);
}
return objs[0];
}
computeRoot() {
let node;
let index = this.nodes[0].length;
for (let level = 0; level < this.depth; level += 1) {
const position = index % this.arity;
const levelStartIndex = index - position;
const levelEndIndex = levelStartIndex + this.arity;
const children = [];
for (let i = levelStartIndex; i < levelEndIndex; i += 1) {
if (i < this.nodes[level].length) {
children.push(this.nodes[level][i]);
}
else {
children.push(this.zeroes[level]);
}
}
node = this.hashFn(children);
index = Math.floor(index / this.arity);
}
return node;
}
getProof(index) {
if (index < 0 || index >= this.nodes[0].length) {
throw new Error('The leaf does not exist in this tree');
}
const siblings = [];
const pathIndices = [];
const leafIndex = index;
for (let level = 0; level < this.depth; level += 1) {
const position = index % this.arity;
const levelStartIndex = index - position;
const levelEndIndex = levelStartIndex + this.arity;
pathIndices[level] = position;
siblings[level] = [];
for (let i = levelStartIndex; i < levelEndIndex; i += 1) {
if (i !== index) {
if (i < this.nodes[level].length) {
siblings[level].push(this.nodes[level][i]);
}
else {
siblings[level].push(this.zeroes[level]);
}
}
}
index = Math.floor(index / this.arity);
}
return { root: this.root, leaf: this.nodes[0][leafIndex], pathIndices, siblings };
}
verify(proof) {
let node = proof.leaf;
for (let i = 0; i < proof.siblings.length; i += 1) {
const children = proof.siblings[i].slice();
children.splice(proof.pathIndices[i], 0, node);
node = this.hashFn(children);
}
return proof.root === node;
}
toString() {
return this.toTreeString();
}
toTreeString() {
const obj = this.getLayersAsObject();
return treeify_1.default.asTree(obj, true);
}
}
exports.IncrementalMerkleTree = IncrementalMerkleTree;
if (typeof window !== 'undefined') {
;
window.IncrementalMerkleTree = IncrementalMerkleTree;
}
exports.default = IncrementalMerkleTree;