mirror of
https://github.com/tornadocash/tornado-core.git
synced 2024-12-28 07:39:22 -05:00
185 lines
5.8 KiB
JavaScript
185 lines
5.8 KiB
JavaScript
const jsStorage = require("./Storage");
|
|
const mimcHasher = require("./MiMC");
|
|
|
|
class MerkleTree {
|
|
|
|
constructor(n_levels, zero_value, defaultElements, prefix, storage, hasher) {
|
|
this.prefix = prefix;
|
|
this.storage = storage || new jsStorage();
|
|
this.hasher = hasher || new mimcHasher();
|
|
this.n_levels = n_levels;
|
|
this.zero_values = [];
|
|
this.totalElements = 0;
|
|
|
|
let current_zero_value = zero_value || 0;
|
|
this.zero_values.push(current_zero_value);
|
|
for (let i = 0; i < n_levels; i++) {
|
|
current_zero_value = this.hasher.hash(i, current_zero_value, current_zero_value);
|
|
this.zero_values.push(
|
|
current_zero_value.toString(),
|
|
);
|
|
}
|
|
if (defaultElements) {
|
|
let level = 0
|
|
this.totalElements = defaultElements.length
|
|
defaultElements.forEach((element, i) => {
|
|
this.storage.put(MerkleTree.index_to_key(prefix, level, i), element)
|
|
})
|
|
level++
|
|
let numberOfElementInRow = Math.ceil(defaultElements.length / 2)
|
|
for (level; level <= this.n_levels; level++) {
|
|
for(let i = 0; i < numberOfElementInRow; i++) {
|
|
const leftKey = MerkleTree.index_to_key(prefix, level - 1, 2 * i)
|
|
const rightKey = MerkleTree.index_to_key(prefix, level - 1, 2 * i + 1)
|
|
|
|
const left = this.storage.get(leftKey)
|
|
const right = this.storage.get_or_element(rightKey, this.zero_values[level - 1])
|
|
|
|
const subRoot = this.hasher.hash(null, left, right);
|
|
this.storage.put(MerkleTree.index_to_key(prefix, level, i), subRoot)
|
|
}
|
|
numberOfElementInRow = Math.max(Math.ceil(numberOfElementInRow / 2), 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
static index_to_key(prefix, level, index) {
|
|
const key = `${prefix}_tree_${level}_${index}`;
|
|
return key;
|
|
}
|
|
|
|
async root() {
|
|
let root = await this.storage.get_or_element(
|
|
MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
|
|
this.zero_values[this.n_levels],
|
|
);
|
|
|
|
return root;
|
|
}
|
|
|
|
async path(index) {
|
|
class PathTraverser {
|
|
constructor(prefix, storage, zero_values) {
|
|
this.prefix = prefix;
|
|
this.storage = storage;
|
|
this.zero_values = zero_values;
|
|
this.path_elements = [];
|
|
this.path_index = [];
|
|
}
|
|
|
|
async handle_index(level, element_index, sibling_index) {
|
|
const sibling = await this.storage.get_or_element(
|
|
MerkleTree.index_to_key(this.prefix, level, sibling_index),
|
|
this.zero_values[level],
|
|
);
|
|
this.path_elements.push(sibling);
|
|
this.path_index.push(element_index % 2);
|
|
}
|
|
}
|
|
let traverser = new PathTraverser(this.prefix, this.storage, this.zero_values);
|
|
const root = await this.storage.get_or_element(
|
|
MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
|
|
this.zero_values[this.n_levels],
|
|
);
|
|
|
|
const element = await this.storage.get_or_element(
|
|
MerkleTree.index_to_key(this.prefix, 0, index),
|
|
this.zero_values[0],
|
|
);
|
|
|
|
await this.traverse(index, traverser);
|
|
return {
|
|
root,
|
|
path_elements: traverser.path_elements,
|
|
path_index: traverser.path_index,
|
|
element
|
|
};
|
|
}
|
|
|
|
async update(index, element, insert = false) {
|
|
if (!insert && index >= this.totalElements) {
|
|
throw Error('Use insert method for new elements.')
|
|
} else if(insert && index < this.totalElements) {
|
|
throw Error('Use update method for existing elements.')
|
|
}
|
|
try {
|
|
class UpdateTraverser {
|
|
constructor(prefix, storage, hasher, element, zero_values) {
|
|
this.prefix = prefix;
|
|
this.current_element = element;
|
|
this.zero_values = zero_values;
|
|
this.storage = storage;
|
|
this.hasher = hasher;
|
|
this.key_values_to_put = [];
|
|
}
|
|
|
|
async handle_index(level, element_index, sibling_index) {
|
|
if (level == 0) {
|
|
this.original_element = await this.storage.get_or_element(
|
|
MerkleTree.index_to_key(this.prefix, level, element_index),
|
|
this.zero_values[level],
|
|
);
|
|
}
|
|
const sibling = await this.storage.get_or_element(
|
|
MerkleTree.index_to_key(this.prefix, level, sibling_index),
|
|
this.zero_values[level],
|
|
);
|
|
let left, right;
|
|
if (element_index % 2 == 0) {
|
|
left = this.current_element;
|
|
right = sibling;
|
|
} else {
|
|
left = sibling;
|
|
right = this.current_element;
|
|
}
|
|
|
|
this.key_values_to_put.push({
|
|
key: MerkleTree.index_to_key(this.prefix, level, element_index),
|
|
value: this.current_element,
|
|
});
|
|
this.current_element = this.hasher.hash(level, left, right);
|
|
}
|
|
}
|
|
let traverser = new UpdateTraverser(
|
|
this.prefix,
|
|
this.storage,
|
|
this.hasher,
|
|
element,
|
|
this.zero_values
|
|
);
|
|
|
|
await this.traverse(index, traverser);
|
|
traverser.key_values_to_put.push({
|
|
key: MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
|
|
value: traverser.current_element,
|
|
});
|
|
|
|
await this.storage.put_batch(traverser.key_values_to_put);
|
|
} catch(e) {
|
|
console.error(e)
|
|
}
|
|
}
|
|
|
|
async insert(element) {
|
|
const index = this.totalElements
|
|
await this.update(index, element, true)
|
|
this.totalElements++
|
|
}
|
|
|
|
async traverse(index, handler) {
|
|
let current_index = index;
|
|
for (let i = 0; i < this.n_levels; i++) {
|
|
let sibling_index = current_index;
|
|
if (current_index % 2 == 0) {
|
|
sibling_index += 1;
|
|
} else {
|
|
sibling_index -= 1;
|
|
}
|
|
await handler.handle_index(i, current_index, sibling_index);
|
|
current_index = Math.floor(current_index / 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = MerkleTree;
|