minauth-erc721-timelock-plugin
Version:
This package contains an implementation of a MinAuth plugin that combines zero-knowledge Merkle "membership" proofs with the ability to register into to a Merkle tree with locking an Ethereum NFT.
150 lines • 5.09 kB
JavaScript
/**
* @module MerkleTree
* This module provides an implementation of a Merkle Tree, a fundamental component
* in blockchain and cryptographic applications. It leverages the 'o1js' library for
* efficient Merkle Tree operations and is tailored for managing leaves represented as `Field` elements.
*/
import { MerkleTree as O1jsMerkleTree } from 'o1js';
import { TreeWitness, TREE_HEIGHT } from './merkle-membership-program.js';
import assert from 'assert';
class CustomSet {
constructor(elems, compare = (x, y) => x === y) {
this.compare = compare;
this.elements = [];
elems.forEach((e) => this.add(e));
}
add(element) {
if (!this.contains(element)) {
this.elements.push(element);
}
}
contains(element) {
return this.elements.some((e) => this.compare(e, element));
}
at(index) {
return this.elements[index];
}
get length() {
return this.elements.length;
}
toArray() {
return this.elements;
}
}
class CustomMap {
constructor(compare) {
this.compare = compare;
this._keys = [];
this._values = [];
}
set(key, value) {
const existingIndex = this.keys.findIndex((existingKey) => this.compare(existingKey, key));
if (existingIndex > -1) {
// Replace the value if the key already exists
this.values[existingIndex] = value;
}
else {
// Add the new key and value
this.keys.push(key);
this.values.push(value);
}
}
get(key) {
const keyIndex = this.keys.findIndex((existingKey) => this.compare(existingKey, key));
if (keyIndex > -1) {
return this.values[keyIndex];
}
return undefined;
}
has(key) {
return this.keys.some((existingKey) => this.compare(existingKey, key));
}
delete(key) {
const keyIndex = this.keys.findIndex((existingKey) => this.compare(existingKey, key));
if (keyIndex > -1) {
this.keys.splice(keyIndex, 1);
this.values.splice(keyIndex, 1);
return true;
}
return false;
}
clear() {
this._keys = [];
this._values = [];
}
get size() {
return this.values.length;
}
get keys() {
return this._keys;
}
get values() {
return this._values;
}
}
/**
* A wrapper class for the Merkle Tree implementation from 'o1js' library.
* Non-unique leaves will be discarded.
*/
export class MerkleTree {
/**
* Constructs a Merkle Tree from a given array of leaves.
* @param leaves An array of `Field` elements to be used as leaves in the tree.
*/
constructor(leaves, logger) {
this.logger = logger;
this.leaves = new CustomSet(leaves, (a, b) => a.equals(b).toBoolean());
this.leafMap = this.mkLeafToIndexMap(this.leaves);
this.merkleTree = new O1jsMerkleTree(TREE_HEIGHT);
this.merkleTree.fill(this.leaves.toArray());
if (this.logger) {
const ls = leaves.map((leaf) => leaf.toString());
this.logger.debug(`Merkle tree constructed with leaves: ${ls} and root: ${this.root.toString()}`);
}
assert(this.leafMap.size === this.leaves.length);
}
/**
* Creates a mapping from leaves to their corresponding indices in the tree.
* @param leaves An array of `Field` elements representing the leaves.
* @returns A Map object mapping each leaf to its index.
*/
mkLeafToIndexMap(leaves) {
const leafToIndex = new CustomMap((a, b) => a.equals(b).toBoolean());
for (let i = 0; i < leaves.length; i++) {
leafToIndex.set(leaves.at(i), BigInt(i));
}
return leafToIndex;
}
/**
* Returns the root of the Merkle Tree.
* @returns The `Field` element representing the root of the tree.
*/
get root() {
return this.merkleTree.getRoot();
}
/**
* Returns the number of leaves in the Merkle Tree.
* @returns The count of leaves as a bigint.
*/
get leafCount() {
return this.leafMap.size;
}
/**
* Generates a witness for a given leaf in the Merkle Tree.
* @param leaf The `Field` element for which the witness is to be generated.
* @returns A `TreeWitness` object representing the proof of the leaf's inclusion in the tree.
* @throws Error if the leaf is not found in the tree.
*/
getWitness(leaf) {
const leafIndex = this.leafMap.get(leaf);
if (this.logger) {
const ls = this.leaves.toArray().map((leaf) => leaf.toString());
this.logger?.debug(`Building a witness for leaf ${leaf.toString()} with index ${leafIndex} in tree with leaves: ${ls} and root: ${this.root.toString()}`);
}
if (leafIndex === undefined) {
throw new Error('Leaf not found');
}
return new TreeWitness(this.merkleTree.getWitness(leafIndex));
}
}
//# sourceMappingURL=merkle-tree.js.map