@iden3/js-merkletree
Version:
javascript sparse merkle tree library
590 lines (589 loc) • 25.6 kB
JavaScript
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Merkletree_db, _Merkletree_root, _Merkletree_writable, _Merkletree_maxLevel;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Merkletree = void 0;
const hash_1 = require("../hash/hash");
const constants_1 = require("../../constants");
const node_1 = require("../node/node");
const utils_1 = require("../utils");
const crypto_1 = require("../utils/crypto");
const circom_1 = require("./circom");
const errors_1 = require("../errors");
const proof_1 = require("./proof");
const entry_1 = require("../entry");
class Merkletree {
constructor(_db, _writable, _maxLevels) {
_Merkletree_db.set(this, void 0);
_Merkletree_root.set(this, void 0);
_Merkletree_writable.set(this, void 0);
_Merkletree_maxLevel.set(this, void 0);
__classPrivateFieldSet(this, _Merkletree_db, _db, "f");
__classPrivateFieldSet(this, _Merkletree_writable, _writable, "f");
__classPrivateFieldSet(this, _Merkletree_maxLevel, _maxLevels, "f");
}
async root() {
if (!__classPrivateFieldGet(this, _Merkletree_root, "f")) {
__classPrivateFieldSet(this, _Merkletree_root, await __classPrivateFieldGet(this, _Merkletree_db, "f").getRoot(), "f");
}
return __classPrivateFieldGet(this, _Merkletree_root, "f");
}
get maxLevels() {
return __classPrivateFieldGet(this, _Merkletree_maxLevel, "f");
}
async add(k, v) {
if (!__classPrivateFieldGet(this, _Merkletree_writable, "f")) {
throw errors_1.ErrNotWritable;
}
__classPrivateFieldSet(this, _Merkletree_root, await this.root(), "f");
const kHash = hash_1.Hash.fromBigInt(k);
const vHash = hash_1.Hash.fromBigInt(v);
const newNodeLeaf = new node_1.NodeLeaf(kHash, vHash);
const path = (0, utils_1.getPath)(this.maxLevels, kHash.value);
const newRootKey = await this.addLeaf(newNodeLeaf, __classPrivateFieldGet(this, _Merkletree_root, "f"), 0, path);
__classPrivateFieldSet(this, _Merkletree_root, newRootKey, "f");
await __classPrivateFieldGet(this, _Merkletree_db, "f").setRoot(__classPrivateFieldGet(this, _Merkletree_root, "f"));
}
async updateNode(n) {
if (!__classPrivateFieldGet(this, _Merkletree_writable, "f")) {
throw errors_1.ErrNotWritable;
}
if (n.type === constants_1.NODE_TYPE_EMPTY) {
return await n.getKey();
}
const k = await n.getKey();
await __classPrivateFieldGet(this, _Merkletree_db, "f").put(k.value, n);
return k;
}
async addNode(n) {
if (!__classPrivateFieldGet(this, _Merkletree_writable, "f")) {
throw errors_1.ErrNotWritable;
}
if (n.type === constants_1.NODE_TYPE_EMPTY) {
return await n.getKey();
}
const k = await n.getKey();
// if (typeof this.#db.get(k.value) !== 'undefined') {
// throw ErrNodeKeyAlreadyExists;
// }
await __classPrivateFieldGet(this, _Merkletree_db, "f").put(k.value, n);
return k;
}
async addEntry(e) {
if (!__classPrivateFieldGet(this, _Merkletree_writable, "f")) {
throw errors_1.ErrNotWritable;
}
if (!(0, entry_1.checkEntryInField)(e)) {
throw 'elements not inside the finite field over r';
}
__classPrivateFieldSet(this, _Merkletree_root, await __classPrivateFieldGet(this, _Merkletree_db, "f").getRoot(), "f");
const hIndex = await e.hIndex();
const hValue = await e.hValue();
const newNodeLeaf = new node_1.NodeLeaf(hIndex, hValue);
const path = (0, utils_1.getPath)(this.maxLevels, hIndex.value);
const newRootKey = await this.addLeaf(newNodeLeaf, __classPrivateFieldGet(this, _Merkletree_root, "f"), 0, path);
__classPrivateFieldSet(this, _Merkletree_root, newRootKey, "f");
await __classPrivateFieldGet(this, _Merkletree_db, "f").setRoot(__classPrivateFieldGet(this, _Merkletree_root, "f"));
}
async pushLeaf(newLeaf, oldLeaf, lvl, pathNewLeaf, pathOldLeaf) {
if (lvl > __classPrivateFieldGet(this, _Merkletree_maxLevel, "f") - 2) {
throw new Error(errors_1.ErrReachedMaxLevel);
}
let newNodeMiddle;
if (pathNewLeaf[lvl] === pathOldLeaf[lvl]) {
const nextKey = await this.pushLeaf(newLeaf, oldLeaf, lvl + 1, pathNewLeaf, pathOldLeaf);
if (pathNewLeaf[lvl]) {
newNodeMiddle = new node_1.NodeMiddle(new hash_1.Hash(), nextKey);
}
else {
newNodeMiddle = new node_1.NodeMiddle(nextKey, new hash_1.Hash());
}
return await this.addNode(newNodeMiddle);
}
const oldLeafKey = await oldLeaf.getKey();
const newLeafKey = await newLeaf.getKey();
if (pathNewLeaf[lvl]) {
newNodeMiddle = new node_1.NodeMiddle(oldLeafKey, newLeafKey);
}
else {
newNodeMiddle = new node_1.NodeMiddle(newLeafKey, oldLeafKey);
}
await this.addNode(newLeaf);
return await this.addNode(newNodeMiddle);
}
async addLeaf(newLeaf, key, lvl, path) {
if (lvl > __classPrivateFieldGet(this, _Merkletree_maxLevel, "f") - 1) {
throw new Error(errors_1.ErrReachedMaxLevel);
}
const n = await this.getNode(key);
if (typeof n === 'undefined') {
throw errors_1.ErrNotFound;
}
switch (n.type) {
case constants_1.NODE_TYPE_EMPTY:
return this.addNode(newLeaf);
case constants_1.NODE_TYPE_LEAF: {
const nKey = n.entry[0];
const newLeafKey = newLeaf.entry[0];
if ((0, utils_1.bytesEqual)(nKey.value, newLeafKey.value)) {
throw errors_1.ErrEntryIndexAlreadyExists;
}
const pathOldLeaf = (0, utils_1.getPath)(this.maxLevels, nKey.value);
return this.pushLeaf(newLeaf, n, lvl, path, pathOldLeaf);
}
case constants_1.NODE_TYPE_MIDDLE: {
n;
let newNodeMiddle;
if (path[lvl]) {
const nextKey = await this.addLeaf(newLeaf, n.childR, lvl + 1, path);
newNodeMiddle = new node_1.NodeMiddle(n.childL, nextKey);
}
else {
const nextKey = await this.addLeaf(newLeaf, n.childL, lvl + 1, path);
newNodeMiddle = new node_1.NodeMiddle(nextKey, n.childR);
}
return this.addNode(newNodeMiddle);
}
default: {
throw errors_1.ErrInvalidNodeFound;
}
}
}
async get(k) {
const kHash = hash_1.Hash.fromBigInt(k);
const path = (0, utils_1.getPath)(this.maxLevels, kHash.value);
let nextKey = await this.root();
const siblings = [];
for (let i = 0; i < this.maxLevels; i++) {
const n = await this.getNode(nextKey);
if (typeof n === 'undefined') {
throw errors_1.ErrKeyNotFound;
}
switch (n.type) {
case constants_1.NODE_TYPE_EMPTY:
return {
key: BigInt('0'),
value: BigInt('0'),
siblings
};
case constants_1.NODE_TYPE_LEAF:
// if (bytesEqual(kHash.value, (n as NodeLeaf).entry[0].value)) {
// return {
// key: (n as NodeLeaf).entry[0].BigInt(),
// value: (n as NodeLeaf).entry[1].BigInt(),
// siblings,
// };
// }
return {
key: n.entry[0].bigInt(),
value: n.entry[1].bigInt(),
siblings
};
case constants_1.NODE_TYPE_MIDDLE:
if (path[i]) {
nextKey = n.childR;
siblings.push(n.childL);
}
else {
nextKey = n.childL;
siblings.push(n.childR);
}
break;
default:
throw errors_1.ErrInvalidNodeFound;
}
}
throw new Error(errors_1.ErrReachedMaxLevel);
}
async update(k, v) {
if (!__classPrivateFieldGet(this, _Merkletree_writable, "f")) {
throw errors_1.ErrNotWritable;
}
if (!(0, crypto_1.checkBigIntInField)(k)) {
throw 'key not inside the finite field';
}
if (!(0, crypto_1.checkBigIntInField)(v)) {
throw 'key not inside the finite field';
}
const kHash = hash_1.Hash.fromBigInt(k);
const vHash = hash_1.Hash.fromBigInt(v);
const path = (0, utils_1.getPath)(this.maxLevels, kHash.value);
const cp = new circom_1.CircomProcessorProof();
cp.fnc = 1;
cp.oldRoot = await this.root();
cp.oldKey = kHash;
cp.newKey = kHash;
cp.newValue = vHash;
let nextKey = await this.root();
const siblings = [];
for (let i = 0; i < this.maxLevels; i += 1) {
const n = await this.getNode(nextKey);
if (typeof n === 'undefined') {
throw errors_1.ErrNotFound;
}
switch (n.type) {
case constants_1.NODE_TYPE_EMPTY:
throw errors_1.ErrKeyNotFound;
case constants_1.NODE_TYPE_LEAF:
if ((0, utils_1.bytesEqual)(kHash.value, n.entry[0].value)) {
cp.oldValue = n.entry[1];
cp.siblings = (0, hash_1.circomSiblingsFromSiblings)([...siblings], this.maxLevels);
const newNodeLeaf = new node_1.NodeLeaf(kHash, vHash);
await this.updateNode(newNodeLeaf);
const newRootKey = await this.recalculatePathUntilRoot(path, newNodeLeaf, siblings);
__classPrivateFieldSet(this, _Merkletree_root, newRootKey, "f");
await __classPrivateFieldGet(this, _Merkletree_db, "f").setRoot(newRootKey);
cp.newRoot = newRootKey;
return cp;
}
break;
case constants_1.NODE_TYPE_MIDDLE:
if (path[i]) {
nextKey = n.childR;
siblings.push(n.childL);
}
else {
nextKey = n.childL;
siblings.push(n.childR);
}
break;
default:
throw errors_1.ErrInvalidNodeFound;
}
}
throw errors_1.ErrKeyNotFound;
}
async getNode(k) {
if ((0, utils_1.bytesEqual)(k.value, hash_1.ZERO_HASH.value)) {
return new node_1.NodeEmpty();
}
return await __classPrivateFieldGet(this, _Merkletree_db, "f").get(k.value);
}
async recalculatePathUntilRoot(path, node, siblings) {
for (let i = siblings.length - 1; i >= 0; i -= 1) {
const nodeKey = await node.getKey();
if (path[i]) {
node = new node_1.NodeMiddle(siblings[i], nodeKey);
}
else {
node = new node_1.NodeMiddle(nodeKey, siblings[i]);
}
await this.addNode(node);
}
const nodeKey = await node.getKey();
return nodeKey;
}
// Delete removes the specified Key from the MerkleTree and updates the path
// from the deleted key to the Root with the new values. This method removes
// the key from the MerkleTree, but does not remove the old nodes from the
// key-value database; this means that if the tree is accessed by an old Root
// where the key was not deleted yet, the key will still exist. If is desired
// to remove the key-values from the database that are not under the current
// Root, an option could be to dump all the leaves (using mt.DumpLeafs) and
// import them in a new MerkleTree in a new database (using
// mt.ImportDumpedLeafs), but this will loose all the Root history of the
// MerkleTree
async delete(k) {
if (!__classPrivateFieldGet(this, _Merkletree_writable, "f")) {
throw errors_1.ErrNotWritable;
}
const kHash = hash_1.Hash.fromBigInt(k);
const path = (0, utils_1.getPath)(this.maxLevels, kHash.value);
let nextKey = __classPrivateFieldGet(this, _Merkletree_root, "f");
const siblings = [];
for (let i = 0; i < __classPrivateFieldGet(this, _Merkletree_maxLevel, "f"); i += 1) {
const n = await this.getNode(nextKey);
if (typeof n === 'undefined') {
throw errors_1.ErrNotFound;
}
switch (n.type) {
case constants_1.NODE_TYPE_EMPTY:
throw errors_1.ErrKeyNotFound;
case constants_1.NODE_TYPE_LEAF:
if ((0, utils_1.bytesEqual)(kHash.bytes, n.entry[0].value)) {
await this.rmAndUpload(path, kHash, siblings);
return;
}
throw errors_1.ErrKeyNotFound;
case constants_1.NODE_TYPE_MIDDLE:
if (path[i]) {
nextKey = n.childR;
siblings.push(n.childL);
}
else {
nextKey = n.childL;
siblings.push(n.childR);
}
break;
default:
throw errors_1.ErrInvalidNodeFound;
}
}
throw errors_1.ErrKeyNotFound;
}
async rmAndUpload(path, kHash, siblings) {
if (siblings.length === 0) {
__classPrivateFieldSet(this, _Merkletree_root, hash_1.ZERO_HASH, "f");
await __classPrivateFieldGet(this, _Merkletree_db, "f").setRoot(__classPrivateFieldGet(this, _Merkletree_root, "f"));
return;
}
const toUpload = siblings[siblings.length - 1];
if (siblings.length < 2) {
__classPrivateFieldSet(this, _Merkletree_root, siblings[0], "f");
await __classPrivateFieldGet(this, _Merkletree_db, "f").setRoot(__classPrivateFieldGet(this, _Merkletree_root, "f"));
}
const nearestSibling = await __classPrivateFieldGet(this, _Merkletree_db, "f").get(toUpload.bytes);
if (nearestSibling?.type === constants_1.NODE_TYPE_MIDDLE) {
let newNode;
if (path[siblings.length - 1]) {
newNode = new node_1.NodeMiddle(toUpload, hash_1.ZERO_HASH);
}
else {
newNode = new node_1.NodeMiddle(hash_1.ZERO_HASH, toUpload);
}
await this.addNode(newNode);
const newRootKey = await this.recalculatePathUntilRoot(path, newNode, siblings.slice(0, siblings.length - 1));
__classPrivateFieldSet(this, _Merkletree_root, newRootKey, "f");
await __classPrivateFieldGet(this, _Merkletree_db, "f").setRoot(__classPrivateFieldGet(this, _Merkletree_root, "f"));
return;
}
for (let i = siblings.length - 2; i >= 0; i -= 1) {
if (!(0, utils_1.bytesEqual)(siblings[i].value, hash_1.ZERO_HASH.value)) {
let newNode;
if (path[i]) {
newNode = new node_1.NodeMiddle(siblings[i], toUpload);
}
else {
newNode = new node_1.NodeMiddle(toUpload, siblings[i]);
}
await this.addNode(newNode);
const newRootKey = await this.recalculatePathUntilRoot(path, newNode, siblings.slice(0, i));
__classPrivateFieldSet(this, _Merkletree_root, newRootKey, "f");
await __classPrivateFieldGet(this, _Merkletree_db, "f").setRoot(__classPrivateFieldGet(this, _Merkletree_root, "f"));
break;
}
if (i === 0) {
__classPrivateFieldSet(this, _Merkletree_root, toUpload, "f");
await __classPrivateFieldGet(this, _Merkletree_db, "f").setRoot(__classPrivateFieldGet(this, _Merkletree_root, "f"));
break;
}
}
}
async recWalk(key, f) {
const n = await this.getNode(key);
if (typeof n === 'undefined') {
throw errors_1.ErrNotFound;
}
switch (n.type) {
case constants_1.NODE_TYPE_EMPTY:
await f(n);
break;
case constants_1.NODE_TYPE_LEAF:
await f(n);
break;
case constants_1.NODE_TYPE_MIDDLE:
await f(n);
await this.recWalk(n.childL, f);
await this.recWalk(n.childR, f);
break;
default:
throw errors_1.ErrInvalidNodeFound;
}
}
async walk(rootKey, f) {
if ((0, utils_1.bytesEqual)(rootKey.value, hash_1.ZERO_HASH.value)) {
rootKey = await this.root();
}
await this.recWalk(rootKey, f);
}
async generateCircomVerifierProof(k, rootKey) {
const cp = await this.generateSCVerifierProof(k, rootKey);
cp.siblings = (0, hash_1.circomSiblingsFromSiblings)(cp.siblings, this.maxLevels);
return cp;
}
async generateSCVerifierProof(k, rootKey) {
if ((0, utils_1.bytesEqual)(rootKey.value, hash_1.ZERO_HASH.value)) {
rootKey = await this.root();
}
const { proof, value } = await this.generateProof(k, rootKey);
const cp = new circom_1.CircomVerifierProof();
cp.root = rootKey;
cp.siblings = proof.allSiblings();
if (typeof proof.nodeAux !== 'undefined') {
cp.oldKey = proof.nodeAux.key;
cp.oldValue = proof.nodeAux.value;
}
else {
cp.oldKey = hash_1.ZERO_HASH;
cp.oldValue = hash_1.ZERO_HASH;
}
cp.key = hash_1.Hash.fromBigInt(k);
cp.value = hash_1.Hash.fromBigInt(value);
if (proof.existence) {
cp.fnc = 0;
}
else {
cp.fnc = 1;
}
return cp;
}
async generateProof(k, rootKey) {
let siblingKey;
const kHash = hash_1.Hash.fromBigInt(k);
const path = (0, utils_1.getPath)(this.maxLevels, kHash.value);
if (!rootKey) {
rootKey = await this.root();
}
let nextKey = rootKey;
let depth = 0;
let existence = false;
const siblings = [];
let nodeAux;
for (depth = 0; depth < this.maxLevels; depth += 1) {
const n = await this.getNode(nextKey);
if (typeof n === 'undefined') {
throw errors_1.ErrNotFound;
}
switch (n.type) {
case constants_1.NODE_TYPE_EMPTY:
return {
proof: new proof_1.Proof({
existence,
nodeAux,
siblings
}),
value: BigInt('0')
};
case constants_1.NODE_TYPE_LEAF:
if ((0, utils_1.bytesEqual)(kHash.value, n.entry[0].value)) {
existence = true;
return {
proof: new proof_1.Proof({
existence,
nodeAux,
siblings
}),
value: n.entry[1].bigInt()
};
}
nodeAux = {
key: n.entry[0],
value: n.entry[1]
};
return {
proof: new proof_1.Proof({
existence,
nodeAux,
siblings
}),
value: n.entry[1].bigInt()
};
case constants_1.NODE_TYPE_MIDDLE:
if (path[depth]) {
nextKey = n.childR;
siblingKey = n.childL;
}
else {
nextKey = n.childL;
siblingKey = n.childR;
}
break;
default:
throw errors_1.ErrInvalidNodeFound;
}
siblings.push(siblingKey);
}
throw errors_1.ErrKeyNotFound;
}
async addAndGetCircomProof(k, v) {
const cp = new circom_1.CircomProcessorProof();
cp.fnc = 2;
cp.oldRoot = await this.root();
let key = BigInt('0');
let value = BigInt('0');
let siblings = [];
try {
const res = await this.get(k);
key = res.key;
value = res.value;
siblings = res.siblings;
}
catch (err) {
if (err !== errors_1.ErrKeyNotFound) {
throw err;
}
}
if (typeof key === 'undefined' || typeof value === 'undefined') {
throw 'key/value undefined';
}
cp.oldKey = hash_1.Hash.fromBigInt(key);
cp.oldValue = hash_1.Hash.fromBigInt(value);
if ((0, utils_1.bytesEqual)(cp.oldKey.value, hash_1.ZERO_HASH.value)) {
cp.isOld0 = true;
}
cp.siblings = (0, hash_1.circomSiblingsFromSiblings)(siblings, this.maxLevels);
await this.add(k, v);
cp.newKey = hash_1.Hash.fromBigInt(k);
cp.newValue = hash_1.Hash.fromBigInt(v);
cp.newRoot = await this.root();
return cp;
}
// NOTE: for now it only prints to console, will be updated in future
async graphViz(rootKey) {
let cnt = 0;
await this.walk(rootKey, async (n) => {
const k = await n.getKey();
let lr;
let emptyNodes;
switch (n.type) {
case constants_1.NODE_TYPE_EMPTY:
break;
case constants_1.NODE_TYPE_LEAF:
// eslint-disable-next-line no-console
console.log(`"${k.string()}" [style=filled]`);
break;
case constants_1.NODE_TYPE_MIDDLE:
lr = [n.childL.string(), n.childR.string()];
emptyNodes = '';
lr.forEach((s, i) => {
if (s === '0') {
lr[i] = `empty${cnt}`;
emptyNodes += `"${lr[i]}" [style=dashed,label=0];\n`;
cnt += 1;
}
});
// eslint-disable-next-line no-console
console.log(`"${k.string()}" -> {"${lr[1]}"}`);
// eslint-disable-next-line no-console
console.log(emptyNodes);
break;
default:
break;
}
});
// eslint-disable-next-line no-console
console.log(`}\n`);
}
async printGraphViz(rootKey) {
if ((0, utils_1.bytesEqual)(rootKey.value, hash_1.ZERO_HASH.value)) {
rootKey = await this.root();
}
// eslint-disable-next-line no-console
console.log(`--------\nGraphViz of the MerkleTree with RootKey ${rootKey.bigInt().toString(10)}\n`);
await this.graphViz(hash_1.ZERO_HASH);
// eslint-disable-next-line no-console
console.log(`End of GraphViz of the MerkleTree with RootKey ${rootKey.bigInt().toString(10)}\n--------\n`);
}
}
exports.Merkletree = Merkletree;
_Merkletree_db = new WeakMap(), _Merkletree_root = new WeakMap(), _Merkletree_writable = new WeakMap(), _Merkletree_maxLevel = new WeakMap();