Minor updates #1
1
.npmrc
1
.npmrc
@ -1 +0,0 @@
|
||||
@tornado:registry=https://git.tornado.ws/api/packages/tornado-packages/npm/
|
922
dist/tornado.umd.js
vendored
922
dist/tornado.umd.js
vendored
@ -9511,160 +9511,160 @@ exports.bytes = exports.stringToBytes;
|
||||
/***/ ((__unused_webpack_module, exports) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.BaseTree = void 0;
|
||||
class BaseTree {
|
||||
get capacity() {
|
||||
return 2 ** this.levels;
|
||||
}
|
||||
get layers() {
|
||||
return this._layers.slice();
|
||||
}
|
||||
get zeros() {
|
||||
return this._zeros.slice();
|
||||
}
|
||||
get elements() {
|
||||
return this._layers[0].slice();
|
||||
}
|
||||
get root() {
|
||||
var _a;
|
||||
return (_a = this._layers[this.levels][0]) !== null && _a !== void 0 ? _a : this._zeros[this.levels];
|
||||
}
|
||||
/**
|
||||
* Find an element in the tree
|
||||
* @param elements elements of tree
|
||||
* @param element An element to find
|
||||
* @param comparator A function that checks leaf value equality
|
||||
* @param fromIndex The index to start the search at. If the index is greater than or equal to the array's length, -1 is returned
|
||||
* @returns {number} Index if element is found, otherwise -1
|
||||
*/
|
||||
static indexOf(elements, element, fromIndex, comparator) {
|
||||
if (comparator) {
|
||||
return elements.findIndex((el) => comparator(element, el));
|
||||
}
|
||||
else {
|
||||
return elements.indexOf(element, fromIndex);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Insert new element into the tree
|
||||
* @param element Element to insert
|
||||
*/
|
||||
insert(element) {
|
||||
if (this._layers[0].length >= this.capacity) {
|
||||
throw new Error('Tree is full');
|
||||
}
|
||||
this.update(this._layers[0].length, element);
|
||||
}
|
||||
/*
|
||||
* Insert multiple elements into the tree.
|
||||
* @param {Array} elements Elements to insert
|
||||
*/
|
||||
bulkInsert(elements) {
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
if (this._layers[0].length + elements.length > this.capacity) {
|
||||
throw new Error('Tree is full');
|
||||
}
|
||||
// First we insert all elements except the last one
|
||||
// updating only full subtree hashes (all layers where inserted element has odd index)
|
||||
// the last element will update the full path to the root making the tree consistent again
|
||||
for (let i = 0; i < elements.length - 1; i++) {
|
||||
this._layers[0].push(elements[i]);
|
||||
let level = 0;
|
||||
let index = this._layers[0].length - 1;
|
||||
while (index % 2 === 1) {
|
||||
level++;
|
||||
index >>= 1;
|
||||
const left = this._layers[level - 1][index * 2];
|
||||
const right = this._layers[level - 1][index * 2 + 1];
|
||||
this._layers[level][index] = this._hashFn(left, right);
|
||||
}
|
||||
}
|
||||
this.insert(elements[elements.length - 1]);
|
||||
}
|
||||
/**
|
||||
* Change an element in the tree
|
||||
* @param {number} index Index of element to change
|
||||
* @param element Updated element value
|
||||
*/
|
||||
update(index, element) {
|
||||
if (isNaN(Number(index)) || index < 0 || index > this._layers[0].length || index >= this.capacity) {
|
||||
throw new Error('Insert index out of bounds: ' + index);
|
||||
}
|
||||
this._layers[0][index] = element;
|
||||
this._processUpdate(index);
|
||||
}
|
||||
/**
|
||||
* Get merkle path to a leaf
|
||||
* @param {number} index Leaf index to generate path for
|
||||
* @returns {{pathElements: Object[], pathIndex: number[]}} An object containing adjacent elements and left-right index
|
||||
*/
|
||||
path(index) {
|
||||
if (isNaN(Number(index)) || index < 0 || index >= this._layers[0].length) {
|
||||
throw new Error('Index out of bounds: ' + index);
|
||||
}
|
||||
let elIndex = +index;
|
||||
const pathElements = [];
|
||||
const pathIndices = [];
|
||||
const pathPositions = [];
|
||||
for (let level = 0; level < this.levels; level++) {
|
||||
pathIndices[level] = elIndex % 2;
|
||||
const leafIndex = elIndex ^ 1;
|
||||
if (leafIndex < this._layers[level].length) {
|
||||
pathElements[level] = this._layers[level][leafIndex];
|
||||
pathPositions[level] = leafIndex;
|
||||
}
|
||||
else {
|
||||
pathElements[level] = this._zeros[level];
|
||||
pathPositions[level] = 0;
|
||||
}
|
||||
elIndex >>= 1;
|
||||
}
|
||||
return {
|
||||
pathElements,
|
||||
pathIndices,
|
||||
pathPositions,
|
||||
pathRoot: this.root,
|
||||
};
|
||||
}
|
||||
_buildZeros() {
|
||||
this._zeros = [this.zeroElement];
|
||||
for (let i = 1; i <= this.levels; i++) {
|
||||
this._zeros[i] = this._hashFn(this._zeros[i - 1], this._zeros[i - 1]);
|
||||
}
|
||||
}
|
||||
_processNodes(nodes, layerIndex) {
|
||||
const length = nodes.length;
|
||||
let currentLength = Math.ceil(length / 2);
|
||||
const currentLayer = new Array(currentLength);
|
||||
currentLength--;
|
||||
const starFrom = length - ((length % 2) ^ 1);
|
||||
let j = 0;
|
||||
for (let i = starFrom; i >= 0; i -= 2) {
|
||||
if (nodes[i - 1] === undefined)
|
||||
break;
|
||||
const left = nodes[i - 1];
|
||||
const right = (i === starFrom && length % 2 === 1) ? this._zeros[layerIndex - 1] : nodes[i];
|
||||
currentLayer[currentLength - j] = this._hashFn(left, right);
|
||||
j++;
|
||||
}
|
||||
return currentLayer;
|
||||
}
|
||||
_processUpdate(index) {
|
||||
for (let level = 1; level <= this.levels; level++) {
|
||||
index >>= 1;
|
||||
const left = this._layers[level - 1][index * 2];
|
||||
const right = index * 2 + 1 < this._layers[level - 1].length
|
||||
? this._layers[level - 1][index * 2 + 1]
|
||||
: this._zeros[level - 1];
|
||||
this._layers[level][index] = this._hashFn(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.BaseTree = BaseTree;
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.BaseTree = void 0;
|
||||
class BaseTree {
|
||||
get capacity() {
|
||||
return 2 ** this.levels;
|
||||
}
|
||||
get layers() {
|
||||
return this._layers.slice();
|
||||
}
|
||||
get zeros() {
|
||||
return this._zeros.slice();
|
||||
}
|
||||
get elements() {
|
||||
return this._layers[0].slice();
|
||||
}
|
||||
get root() {
|
||||
var _a;
|
||||
return (_a = this._layers[this.levels][0]) !== null && _a !== void 0 ? _a : this._zeros[this.levels];
|
||||
}
|
||||
/**
|
||||
* Find an element in the tree
|
||||
* @param elements elements of tree
|
||||
* @param element An element to find
|
||||
* @param comparator A function that checks leaf value equality
|
||||
* @param fromIndex The index to start the search at. If the index is greater than or equal to the array's length, -1 is returned
|
||||
* @returns {number} Index if element is found, otherwise -1
|
||||
*/
|
||||
static indexOf(elements, element, fromIndex, comparator) {
|
||||
if (comparator) {
|
||||
return elements.findIndex((el) => comparator(element, el));
|
||||
}
|
||||
else {
|
||||
return elements.indexOf(element, fromIndex);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Insert new element into the tree
|
||||
* @param element Element to insert
|
||||
*/
|
||||
insert(element) {
|
||||
if (this._layers[0].length >= this.capacity) {
|
||||
throw new Error('Tree is full');
|
||||
}
|
||||
this.update(this._layers[0].length, element);
|
||||
}
|
||||
/*
|
||||
* Insert multiple elements into the tree.
|
||||
* @param {Array} elements Elements to insert
|
||||
*/
|
||||
bulkInsert(elements) {
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
if (this._layers[0].length + elements.length > this.capacity) {
|
||||
throw new Error('Tree is full');
|
||||
}
|
||||
// First we insert all elements except the last one
|
||||
// updating only full subtree hashes (all layers where inserted element has odd index)
|
||||
// the last element will update the full path to the root making the tree consistent again
|
||||
for (let i = 0; i < elements.length - 1; i++) {
|
||||
this._layers[0].push(elements[i]);
|
||||
let level = 0;
|
||||
let index = this._layers[0].length - 1;
|
||||
while (index % 2 === 1) {
|
||||
level++;
|
||||
index >>= 1;
|
||||
const left = this._layers[level - 1][index * 2];
|
||||
const right = this._layers[level - 1][index * 2 + 1];
|
||||
this._layers[level][index] = this._hashFn(left, right);
|
||||
}
|
||||
}
|
||||
this.insert(elements[elements.length - 1]);
|
||||
}
|
||||
/**
|
||||
* Change an element in the tree
|
||||
* @param {number} index Index of element to change
|
||||
* @param element Updated element value
|
||||
*/
|
||||
update(index, element) {
|
||||
if (isNaN(Number(index)) || index < 0 || index > this._layers[0].length || index >= this.capacity) {
|
||||
throw new Error('Insert index out of bounds: ' + index);
|
||||
}
|
||||
this._layers[0][index] = element;
|
||||
this._processUpdate(index);
|
||||
}
|
||||
/**
|
||||
* Get merkle path to a leaf
|
||||
* @param {number} index Leaf index to generate path for
|
||||
* @returns {{pathElements: Object[], pathIndex: number[]}} An object containing adjacent elements and left-right index
|
||||
*/
|
||||
path(index) {
|
||||
if (isNaN(Number(index)) || index < 0 || index >= this._layers[0].length) {
|
||||
throw new Error('Index out of bounds: ' + index);
|
||||
}
|
||||
let elIndex = +index;
|
||||
const pathElements = [];
|
||||
const pathIndices = [];
|
||||
const pathPositions = [];
|
||||
for (let level = 0; level < this.levels; level++) {
|
||||
pathIndices[level] = elIndex % 2;
|
||||
const leafIndex = elIndex ^ 1;
|
||||
if (leafIndex < this._layers[level].length) {
|
||||
pathElements[level] = this._layers[level][leafIndex];
|
||||
pathPositions[level] = leafIndex;
|
||||
}
|
||||
else {
|
||||
pathElements[level] = this._zeros[level];
|
||||
pathPositions[level] = 0;
|
||||
}
|
||||
elIndex >>= 1;
|
||||
}
|
||||
return {
|
||||
pathElements,
|
||||
pathIndices,
|
||||
pathPositions,
|
||||
pathRoot: this.root,
|
||||
};
|
||||
}
|
||||
_buildZeros() {
|
||||
this._zeros = [this.zeroElement];
|
||||
for (let i = 1; i <= this.levels; i++) {
|
||||
this._zeros[i] = this._hashFn(this._zeros[i - 1], this._zeros[i - 1]);
|
||||
}
|
||||
}
|
||||
_processNodes(nodes, layerIndex) {
|
||||
const length = nodes.length;
|
||||
let currentLength = Math.ceil(length / 2);
|
||||
const currentLayer = new Array(currentLength);
|
||||
currentLength--;
|
||||
const starFrom = length - ((length % 2) ^ 1);
|
||||
let j = 0;
|
||||
for (let i = starFrom; i >= 0; i -= 2) {
|
||||
if (nodes[i - 1] === undefined)
|
||||
break;
|
||||
const left = nodes[i - 1];
|
||||
const right = (i === starFrom && length % 2 === 1) ? this._zeros[layerIndex - 1] : nodes[i];
|
||||
currentLayer[currentLength - j] = this._hashFn(left, right);
|
||||
j++;
|
||||
}
|
||||
return currentLayer;
|
||||
}
|
||||
_processUpdate(index) {
|
||||
for (let level = 1; level <= this.levels; level++) {
|
||||
index >>= 1;
|
||||
const left = this._layers[level - 1][index * 2];
|
||||
const right = index * 2 + 1 < this._layers[level - 1].length
|
||||
? this._layers[level - 1][index * 2 + 1]
|
||||
: this._zeros[level - 1];
|
||||
this._layers[level][index] = this._hashFn(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.BaseTree = BaseTree;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@ -9673,120 +9673,120 @@ exports.BaseTree = BaseTree;
|
||||
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const simpleHash_1 = __importDefault(__webpack_require__(5319));
|
||||
const BaseTree_1 = __webpack_require__(7736);
|
||||
class MerkleTree extends BaseTree_1.BaseTree {
|
||||
constructor(levels, elements = [], { hashFunction = simpleHash_1.default, zeroElement = 0, } = {}) {
|
||||
super();
|
||||
this.levels = levels;
|
||||
if (elements.length > this.capacity) {
|
||||
throw new Error('Tree is full');
|
||||
}
|
||||
this._hashFn = hashFunction;
|
||||
this.zeroElement = zeroElement;
|
||||
this._layers = [];
|
||||
const leaves = elements.slice();
|
||||
this._layers = [leaves];
|
||||
this._buildZeros();
|
||||
this._buildHashes();
|
||||
}
|
||||
_buildHashes() {
|
||||
for (let layerIndex = 1; layerIndex <= this.levels; layerIndex++) {
|
||||
const nodes = this._layers[layerIndex - 1];
|
||||
this._layers[layerIndex] = this._processNodes(nodes, layerIndex);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Insert multiple elements into the tree.
|
||||
* @param {Array} elements Elements to insert
|
||||
*/
|
||||
bulkInsert(elements) {
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
if (this._layers[0].length + elements.length > this.capacity) {
|
||||
throw new Error('Tree is full');
|
||||
}
|
||||
// First we insert all elements except the last one
|
||||
// updating only full subtree hashes (all layers where inserted element has odd index)
|
||||
// the last element will update the full path to the root making the tree consistent again
|
||||
for (let i = 0; i < elements.length - 1; i++) {
|
||||
this._layers[0].push(elements[i]);
|
||||
let level = 0;
|
||||
let index = this._layers[0].length - 1;
|
||||
while (index % 2 === 1) {
|
||||
level++;
|
||||
index >>= 1;
|
||||
this._layers[level][index] = this._hashFn(this._layers[level - 1][index * 2], this._layers[level - 1][index * 2 + 1]);
|
||||
}
|
||||
}
|
||||
this.insert(elements[elements.length - 1]);
|
||||
}
|
||||
indexOf(element, comparator) {
|
||||
return BaseTree_1.BaseTree.indexOf(this._layers[0], element, 0, comparator);
|
||||
}
|
||||
proof(element) {
|
||||
const index = this.indexOf(element);
|
||||
return this.path(index);
|
||||
}
|
||||
getTreeEdge(edgeIndex) {
|
||||
const edgeElement = this._layers[0][edgeIndex];
|
||||
if (edgeElement === undefined) {
|
||||
throw new Error('Element not found');
|
||||
}
|
||||
const edgePath = this.path(edgeIndex);
|
||||
return { edgePath, edgeElement, edgeIndex, edgeElementsCount: this._layers[0].length };
|
||||
}
|
||||
/**
|
||||
* 🪓
|
||||
* @param count
|
||||
*/
|
||||
getTreeSlices(count = 4) {
|
||||
const length = this._layers[0].length;
|
||||
let size = Math.ceil(length / count);
|
||||
if (size % 2)
|
||||
size++;
|
||||
const slices = [];
|
||||
for (let i = 0; i < length; i += size) {
|
||||
const edgeLeft = i;
|
||||
const edgeRight = i + size;
|
||||
slices.push({ edge: this.getTreeEdge(edgeLeft), elements: this.elements.slice(edgeLeft, edgeRight) });
|
||||
}
|
||||
return slices;
|
||||
}
|
||||
/**
|
||||
* Serialize entire tree state including intermediate layers into a plain object
|
||||
* Deserializing it back will not require to recompute any hashes
|
||||
* Elements are not converted to a plain type, this is responsibility of the caller
|
||||
*/
|
||||
serialize() {
|
||||
return {
|
||||
levels: this.levels,
|
||||
_zeros: this._zeros,
|
||||
_layers: this._layers,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Deserialize data into a MerkleTree instance
|
||||
* Make sure to provide the same hashFunction as was used in the source tree,
|
||||
* otherwise the tree state will be invalid
|
||||
*/
|
||||
static deserialize(data, hashFunction) {
|
||||
const instance = Object.assign(Object.create(this.prototype), data);
|
||||
instance._hashFn = hashFunction || simpleHash_1.default;
|
||||
instance.zeroElement = instance._zeros[0];
|
||||
return instance;
|
||||
}
|
||||
toString() {
|
||||
return JSON.stringify(this.serialize());
|
||||
}
|
||||
}
|
||||
exports["default"] = MerkleTree;
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const simpleHash_1 = __importDefault(__webpack_require__(5319));
|
||||
const BaseTree_1 = __webpack_require__(7736);
|
||||
class MerkleTree extends BaseTree_1.BaseTree {
|
||||
constructor(levels, elements = [], { hashFunction = simpleHash_1.default, zeroElement = 0, } = {}) {
|
||||
super();
|
||||
this.levels = levels;
|
||||
if (elements.length > this.capacity) {
|
||||
throw new Error('Tree is full');
|
||||
}
|
||||
this._hashFn = hashFunction;
|
||||
this.zeroElement = zeroElement;
|
||||
this._layers = [];
|
||||
const leaves = elements.slice();
|
||||
this._layers = [leaves];
|
||||
this._buildZeros();
|
||||
this._buildHashes();
|
||||
}
|
||||
_buildHashes() {
|
||||
for (let layerIndex = 1; layerIndex <= this.levels; layerIndex++) {
|
||||
const nodes = this._layers[layerIndex - 1];
|
||||
this._layers[layerIndex] = this._processNodes(nodes, layerIndex);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Insert multiple elements into the tree.
|
||||
* @param {Array} elements Elements to insert
|
||||
*/
|
||||
bulkInsert(elements) {
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
if (this._layers[0].length + elements.length > this.capacity) {
|
||||
throw new Error('Tree is full');
|
||||
}
|
||||
// First we insert all elements except the last one
|
||||
// updating only full subtree hashes (all layers where inserted element has odd index)
|
||||
// the last element will update the full path to the root making the tree consistent again
|
||||
for (let i = 0; i < elements.length - 1; i++) {
|
||||
this._layers[0].push(elements[i]);
|
||||
let level = 0;
|
||||
let index = this._layers[0].length - 1;
|
||||
while (index % 2 === 1) {
|
||||
level++;
|
||||
index >>= 1;
|
||||
this._layers[level][index] = this._hashFn(this._layers[level - 1][index * 2], this._layers[level - 1][index * 2 + 1]);
|
||||
}
|
||||
}
|
||||
this.insert(elements[elements.length - 1]);
|
||||
}
|
||||
indexOf(element, comparator) {
|
||||
return BaseTree_1.BaseTree.indexOf(this._layers[0], element, 0, comparator);
|
||||
}
|
||||
proof(element) {
|
||||
const index = this.indexOf(element);
|
||||
return this.path(index);
|
||||
}
|
||||
getTreeEdge(edgeIndex) {
|
||||
const edgeElement = this._layers[0][edgeIndex];
|
||||
if (edgeElement === undefined) {
|
||||
throw new Error('Element not found');
|
||||
}
|
||||
const edgePath = this.path(edgeIndex);
|
||||
return { edgePath, edgeElement, edgeIndex, edgeElementsCount: this._layers[0].length };
|
||||
}
|
||||
/**
|
||||
* 🪓
|
||||
* @param count
|
||||
*/
|
||||
getTreeSlices(count = 4) {
|
||||
const length = this._layers[0].length;
|
||||
let size = Math.ceil(length / count);
|
||||
if (size % 2)
|
||||
size++;
|
||||
const slices = [];
|
||||
for (let i = 0; i < length; i += size) {
|
||||
const edgeLeft = i;
|
||||
const edgeRight = i + size;
|
||||
slices.push({ edge: this.getTreeEdge(edgeLeft), elements: this.elements.slice(edgeLeft, edgeRight) });
|
||||
}
|
||||
return slices;
|
||||
}
|
||||
/**
|
||||
* Serialize entire tree state including intermediate layers into a plain object
|
||||
* Deserializing it back will not require to recompute any hashes
|
||||
* Elements are not converted to a plain type, this is responsibility of the caller
|
||||
*/
|
||||
serialize() {
|
||||
return {
|
||||
levels: this.levels,
|
||||
_zeros: this._zeros,
|
||||
_layers: this._layers,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Deserialize data into a MerkleTree instance
|
||||
* Make sure to provide the same hashFunction as was used in the source tree,
|
||||
* otherwise the tree state will be invalid
|
||||
*/
|
||||
static deserialize(data, hashFunction) {
|
||||
const instance = Object.assign(Object.create(this.prototype), data);
|
||||
instance._hashFn = hashFunction || simpleHash_1.default;
|
||||
instance.zeroElement = instance._zeros[0];
|
||||
return instance;
|
||||
}
|
||||
toString() {
|
||||
return JSON.stringify(this.serialize());
|
||||
}
|
||||
}
|
||||
exports["default"] = MerkleTree;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@ -9795,165 +9795,165 @@ exports["default"] = MerkleTree;
|
||||
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.PartialMerkleTree = void 0;
|
||||
const simpleHash_1 = __importDefault(__webpack_require__(5319));
|
||||
const BaseTree_1 = __webpack_require__(7736);
|
||||
class PartialMerkleTree extends BaseTree_1.BaseTree {
|
||||
constructor(levels, { edgePath, edgeElement, edgeIndex, edgeElementsCount, }, leaves, { hashFunction, zeroElement } = {}) {
|
||||
super();
|
||||
if (edgeIndex + leaves.length !== edgeElementsCount)
|
||||
throw new Error('Invalid number of elements');
|
||||
this._edgeLeafProof = edgePath;
|
||||
this._initialRoot = edgePath.pathRoot;
|
||||
this.zeroElement = zeroElement !== null && zeroElement !== void 0 ? zeroElement : 0;
|
||||
this._edgeLeaf = { data: edgeElement, index: edgeIndex };
|
||||
this._leavesAfterEdge = leaves;
|
||||
this.levels = levels;
|
||||
this._hashFn = hashFunction || simpleHash_1.default;
|
||||
this._createProofMap();
|
||||
this._buildTree();
|
||||
}
|
||||
get edgeIndex() {
|
||||
return this._edgeLeaf.index;
|
||||
}
|
||||
get edgeElement() {
|
||||
return this._edgeLeaf.data;
|
||||
}
|
||||
get edgeLeafProof() {
|
||||
return this._edgeLeafProof;
|
||||
}
|
||||
_createProofMap() {
|
||||
this._proofMap = this.edgeLeafProof.pathPositions.reduce((p, c, i) => {
|
||||
p.set(i, [c, this.edgeLeafProof.pathElements[i]]);
|
||||
return p;
|
||||
}, new Map());
|
||||
this._proofMap.set(this.levels, [0, this.edgeLeafProof.pathRoot]);
|
||||
}
|
||||
_buildTree() {
|
||||
const edgeLeafIndex = this._edgeLeaf.index;
|
||||
this._leaves = Array(edgeLeafIndex).concat(this._leavesAfterEdge);
|
||||
if (this._proofMap.has(0)) {
|
||||
const [proofPos, proofEl] = this._proofMap.get(0);
|
||||
this._leaves[proofPos] = proofEl;
|
||||
}
|
||||
this._layers = [this._leaves];
|
||||
this._buildZeros();
|
||||
this._buildHashes();
|
||||
}
|
||||
_buildHashes() {
|
||||
for (let layerIndex = 1; layerIndex <= this.levels; layerIndex++) {
|
||||
const nodes = this._layers[layerIndex - 1];
|
||||
const currentLayer = this._processNodes(nodes, layerIndex);
|
||||
if (this._proofMap.has(layerIndex)) {
|
||||
const [proofPos, proofEl] = this._proofMap.get(layerIndex);
|
||||
if (!currentLayer[proofPos])
|
||||
currentLayer[proofPos] = proofEl;
|
||||
}
|
||||
this._layers[layerIndex] = currentLayer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Change an element in the tree
|
||||
* @param {number} index Index of element to change
|
||||
* @param element Updated element value
|
||||
*/
|
||||
update(index, element) {
|
||||
if (isNaN(Number(index)) || index < 0 || index > this._layers[0].length || index >= this.capacity) {
|
||||
throw new Error('Insert index out of bounds: ' + index);
|
||||
}
|
||||
if (index < this._edgeLeaf.index) {
|
||||
throw new Error(`Index ${index} is below the edge: ${this._edgeLeaf.index}`);
|
||||
}
|
||||
this._layers[0][index] = element;
|
||||
this._processUpdate(index);
|
||||
}
|
||||
path(index) {
|
||||
var _a;
|
||||
if (isNaN(Number(index)) || index < 0 || index >= this._layers[0].length) {
|
||||
throw new Error('Index out of bounds: ' + index);
|
||||
}
|
||||
if (index < this._edgeLeaf.index) {
|
||||
throw new Error(`Index ${index} is below the edge: ${this._edgeLeaf.index}`);
|
||||
}
|
||||
let elIndex = Number(index);
|
||||
const pathElements = [];
|
||||
const pathIndices = [];
|
||||
const pathPositions = [];
|
||||
for (let level = 0; level < this.levels; level++) {
|
||||
pathIndices[level] = elIndex % 2;
|
||||
const leafIndex = elIndex ^ 1;
|
||||
if (leafIndex < this._layers[level].length) {
|
||||
pathElements[level] = this._layers[level][leafIndex];
|
||||
pathPositions[level] = leafIndex;
|
||||
}
|
||||
else {
|
||||
pathElements[level] = this._zeros[level];
|
||||
pathPositions[level] = 0;
|
||||
}
|
||||
const [proofPos, proofEl] = this._proofMap.get(level);
|
||||
pathElements[level] = (_a = pathElements[level]) !== null && _a !== void 0 ? _a : (proofPos === leafIndex ? proofEl : this._zeros[level]);
|
||||
elIndex >>= 1;
|
||||
}
|
||||
return {
|
||||
pathElements,
|
||||
pathIndices,
|
||||
pathPositions,
|
||||
pathRoot: this.root,
|
||||
};
|
||||
}
|
||||
indexOf(element, comparator) {
|
||||
return BaseTree_1.BaseTree.indexOf(this._layers[0], element, this.edgeIndex, comparator);
|
||||
}
|
||||
proof(element) {
|
||||
const index = this.indexOf(element);
|
||||
return this.path(index);
|
||||
}
|
||||
/**
|
||||
* Shifts edge of tree to left
|
||||
* @param edge new TreeEdge below current edge
|
||||
* @param elements leaves between old and new edge
|
||||
*/
|
||||
shiftEdge(edge, elements) {
|
||||
if (this._edgeLeaf.index <= edge.edgeIndex) {
|
||||
throw new Error(`New edgeIndex should be smaller then ${this._edgeLeaf.index}`);
|
||||
}
|
||||
if (elements.length !== (this._edgeLeaf.index - edge.edgeIndex)) {
|
||||
throw new Error(`Elements length should be ${this._edgeLeaf.index - edge.edgeIndex}`);
|
||||
}
|
||||
this._edgeLeafProof = edge.edgePath;
|
||||
this._edgeLeaf = { index: edge.edgeIndex, data: edge.edgeElement };
|
||||
this._leavesAfterEdge = [...elements, ...this._leavesAfterEdge];
|
||||
this._createProofMap();
|
||||
this._buildTree();
|
||||
}
|
||||
serialize() {
|
||||
return {
|
||||
_edgeLeafProof: this._edgeLeafProof,
|
||||
_edgeLeaf: this._edgeLeaf,
|
||||
_layers: this._layers,
|
||||
_zeros: this._zeros,
|
||||
levels: this.levels,
|
||||
};
|
||||
}
|
||||
static deserialize(data, hashFunction) {
|
||||
const instance = Object.assign(Object.create(this.prototype), data);
|
||||
instance._hashFn = hashFunction || simpleHash_1.default;
|
||||
instance._initialRoot = data._edgeLeafProof.pathRoot;
|
||||
instance.zeroElement = instance._zeros[0];
|
||||
instance._leavesAfterEdge = instance._layers[0].slice(data._edgeLeaf.index);
|
||||
instance._createProofMap();
|
||||
return instance;
|
||||
}
|
||||
toString() {
|
||||
return JSON.stringify(this.serialize());
|
||||
}
|
||||
}
|
||||
exports.PartialMerkleTree = PartialMerkleTree;
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.PartialMerkleTree = void 0;
|
||||
const simpleHash_1 = __importDefault(__webpack_require__(5319));
|
||||
const BaseTree_1 = __webpack_require__(7736);
|
||||
class PartialMerkleTree extends BaseTree_1.BaseTree {
|
||||
constructor(levels, { edgePath, edgeElement, edgeIndex, edgeElementsCount, }, leaves, { hashFunction, zeroElement } = {}) {
|
||||
super();
|
||||
if (edgeIndex + leaves.length !== edgeElementsCount)
|
||||
throw new Error('Invalid number of elements');
|
||||
this._edgeLeafProof = edgePath;
|
||||
this._initialRoot = edgePath.pathRoot;
|
||||
this.zeroElement = zeroElement !== null && zeroElement !== void 0 ? zeroElement : 0;
|
||||
this._edgeLeaf = { data: edgeElement, index: edgeIndex };
|
||||
this._leavesAfterEdge = leaves;
|
||||
this.levels = levels;
|
||||
this._hashFn = hashFunction || simpleHash_1.default;
|
||||
this._createProofMap();
|
||||
this._buildTree();
|
||||
}
|
||||
get edgeIndex() {
|
||||
return this._edgeLeaf.index;
|
||||
}
|
||||
get edgeElement() {
|
||||
return this._edgeLeaf.data;
|
||||
}
|
||||
get edgeLeafProof() {
|
||||
return this._edgeLeafProof;
|
||||
}
|
||||
_createProofMap() {
|
||||
this._proofMap = this.edgeLeafProof.pathPositions.reduce((p, c, i) => {
|
||||
p.set(i, [c, this.edgeLeafProof.pathElements[i]]);
|
||||
return p;
|
||||
}, new Map());
|
||||
this._proofMap.set(this.levels, [0, this.edgeLeafProof.pathRoot]);
|
||||
}
|
||||
_buildTree() {
|
||||
const edgeLeafIndex = this._edgeLeaf.index;
|
||||
this._leaves = Array(edgeLeafIndex).concat(this._leavesAfterEdge);
|
||||
if (this._proofMap.has(0)) {
|
||||
const [proofPos, proofEl] = this._proofMap.get(0);
|
||||
this._leaves[proofPos] = proofEl;
|
||||
}
|
||||
this._layers = [this._leaves];
|
||||
this._buildZeros();
|
||||
this._buildHashes();
|
||||
}
|
||||
_buildHashes() {
|
||||
for (let layerIndex = 1; layerIndex <= this.levels; layerIndex++) {
|
||||
const nodes = this._layers[layerIndex - 1];
|
||||
const currentLayer = this._processNodes(nodes, layerIndex);
|
||||
if (this._proofMap.has(layerIndex)) {
|
||||
const [proofPos, proofEl] = this._proofMap.get(layerIndex);
|
||||
if (!currentLayer[proofPos])
|
||||
currentLayer[proofPos] = proofEl;
|
||||
}
|
||||
this._layers[layerIndex] = currentLayer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Change an element in the tree
|
||||
* @param {number} index Index of element to change
|
||||
* @param element Updated element value
|
||||
*/
|
||||
update(index, element) {
|
||||
if (isNaN(Number(index)) || index < 0 || index > this._layers[0].length || index >= this.capacity) {
|
||||
throw new Error('Insert index out of bounds: ' + index);
|
||||
}
|
||||
if (index < this._edgeLeaf.index) {
|
||||
throw new Error(`Index ${index} is below the edge: ${this._edgeLeaf.index}`);
|
||||
}
|
||||
this._layers[0][index] = element;
|
||||
this._processUpdate(index);
|
||||
}
|
||||
path(index) {
|
||||
var _a;
|
||||
if (isNaN(Number(index)) || index < 0 || index >= this._layers[0].length) {
|
||||
throw new Error('Index out of bounds: ' + index);
|
||||
}
|
||||
if (index < this._edgeLeaf.index) {
|
||||
throw new Error(`Index ${index} is below the edge: ${this._edgeLeaf.index}`);
|
||||
}
|
||||
let elIndex = Number(index);
|
||||
const pathElements = [];
|
||||
const pathIndices = [];
|
||||
const pathPositions = [];
|
||||
for (let level = 0; level < this.levels; level++) {
|
||||
pathIndices[level] = elIndex % 2;
|
||||
const leafIndex = elIndex ^ 1;
|
||||
if (leafIndex < this._layers[level].length) {
|
||||
pathElements[level] = this._layers[level][leafIndex];
|
||||
pathPositions[level] = leafIndex;
|
||||
}
|
||||
else {
|
||||
pathElements[level] = this._zeros[level];
|
||||
pathPositions[level] = 0;
|
||||
}
|
||||
const [proofPos, proofEl] = this._proofMap.get(level);
|
||||
pathElements[level] = (_a = pathElements[level]) !== null && _a !== void 0 ? _a : (proofPos === leafIndex ? proofEl : this._zeros[level]);
|
||||
elIndex >>= 1;
|
||||
}
|
||||
return {
|
||||
pathElements,
|
||||
pathIndices,
|
||||
pathPositions,
|
||||
pathRoot: this.root,
|
||||
};
|
||||
}
|
||||
indexOf(element, comparator) {
|
||||
return BaseTree_1.BaseTree.indexOf(this._layers[0], element, this.edgeIndex, comparator);
|
||||
}
|
||||
proof(element) {
|
||||
const index = this.indexOf(element);
|
||||
return this.path(index);
|
||||
}
|
||||
/**
|
||||
* Shifts edge of tree to left
|
||||
* @param edge new TreeEdge below current edge
|
||||
* @param elements leaves between old and new edge
|
||||
*/
|
||||
shiftEdge(edge, elements) {
|
||||
if (this._edgeLeaf.index <= edge.edgeIndex) {
|
||||
throw new Error(`New edgeIndex should be smaller then ${this._edgeLeaf.index}`);
|
||||
}
|
||||
if (elements.length !== (this._edgeLeaf.index - edge.edgeIndex)) {
|
||||
throw new Error(`Elements length should be ${this._edgeLeaf.index - edge.edgeIndex}`);
|
||||
}
|
||||
this._edgeLeafProof = edge.edgePath;
|
||||
this._edgeLeaf = { index: edge.edgeIndex, data: edge.edgeElement };
|
||||
this._leavesAfterEdge = [...elements, ...this._leavesAfterEdge];
|
||||
this._createProofMap();
|
||||
this._buildTree();
|
||||
}
|
||||
serialize() {
|
||||
return {
|
||||
_edgeLeafProof: this._edgeLeafProof,
|
||||
_edgeLeaf: this._edgeLeaf,
|
||||
_layers: this._layers,
|
||||
_zeros: this._zeros,
|
||||
levels: this.levels,
|
||||
};
|
||||
}
|
||||
static deserialize(data, hashFunction) {
|
||||
const instance = Object.assign(Object.create(this.prototype), data);
|
||||
instance._hashFn = hashFunction || simpleHash_1.default;
|
||||
instance._initialRoot = data._edgeLeafProof.pathRoot;
|
||||
instance.zeroElement = instance._zeros[0];
|
||||
instance._leavesAfterEdge = instance._layers[0].slice(data._edgeLeaf.index);
|
||||
instance._createProofMap();
|
||||
return instance;
|
||||
}
|
||||
toString() {
|
||||
return JSON.stringify(this.serialize());
|
||||
}
|
||||
}
|
||||
exports.PartialMerkleTree = PartialMerkleTree;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@ -9962,19 +9962,19 @@ exports.PartialMerkleTree = PartialMerkleTree;
|
||||
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.MerkleTree = exports.simpleHash = exports.PartialMerkleTree = void 0;
|
||||
const FixedMerkleTree_1 = __importDefault(__webpack_require__(9093));
|
||||
Object.defineProperty(exports, "MerkleTree", ({ enumerable: true, get: function () { return FixedMerkleTree_1.default; } }));
|
||||
var PartialMerkleTree_1 = __webpack_require__(91230);
|
||||
Object.defineProperty(exports, "PartialMerkleTree", ({ enumerable: true, get: function () { return PartialMerkleTree_1.PartialMerkleTree; } }));
|
||||
var simpleHash_1 = __webpack_require__(5319);
|
||||
Object.defineProperty(exports, "simpleHash", ({ enumerable: true, get: function () { return simpleHash_1.simpleHash; } }));
|
||||
exports["default"] = FixedMerkleTree_1.default;
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.MerkleTree = exports.simpleHash = exports.PartialMerkleTree = void 0;
|
||||
const FixedMerkleTree_1 = __importDefault(__webpack_require__(9093));
|
||||
Object.defineProperty(exports, "MerkleTree", ({ enumerable: true, get: function () { return FixedMerkleTree_1.default; } }));
|
||||
var PartialMerkleTree_1 = __webpack_require__(91230);
|
||||
Object.defineProperty(exports, "PartialMerkleTree", ({ enumerable: true, get: function () { return PartialMerkleTree_1.PartialMerkleTree; } }));
|
||||
var simpleHash_1 = __webpack_require__(5319);
|
||||
Object.defineProperty(exports, "simpleHash", ({ enumerable: true, get: function () { return simpleHash_1.simpleHash; } }));
|
||||
exports["default"] = FixedMerkleTree_1.default;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@ -9983,27 +9983,27 @@ exports["default"] = FixedMerkleTree_1.default;
|
||||
/***/ ((__unused_webpack_module, exports) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.simpleHash = void 0;
|
||||
/***
|
||||
* This is insecure hash function, just for example only
|
||||
* @param data
|
||||
* @param seed
|
||||
* @param hashLength
|
||||
*/
|
||||
function simpleHash(data, seed, hashLength = 40) {
|
||||
const str = data.join('');
|
||||
let i, l, hval = seed !== null && seed !== void 0 ? seed : 0x811c9dcc5;
|
||||
for (i = 0, l = str.length; i < l; i++) {
|
||||
hval ^= str.charCodeAt(i);
|
||||
hval += (hval << 1) + (hval << 4) + (hval << 6) + (hval << 8) + (hval << 24);
|
||||
}
|
||||
const hash = (hval >>> 0).toString(16);
|
||||
return BigInt('0x' + hash.padEnd(hashLength - (hash.length - 1), '0')).toString(10);
|
||||
}
|
||||
exports.simpleHash = simpleHash;
|
||||
exports["default"] = (left, right) => simpleHash([left, right]);
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.simpleHash = void 0;
|
||||
/***
|
||||
* This is insecure hash function, just for example only
|
||||
* @param data
|
||||
* @param seed
|
||||
* @param hashLength
|
||||
*/
|
||||
function simpleHash(data, seed, hashLength = 40) {
|
||||
const str = data.join('');
|
||||
let i, l, hval = seed !== null && seed !== void 0 ? seed : 0x811c9dcc5;
|
||||
for (i = 0, l = str.length; i < l; i++) {
|
||||
hval ^= str.charCodeAt(i);
|
||||
hval += (hval << 1) + (hval << 4) + (hval << 6) + (hval << 8) + (hval << 24);
|
||||
}
|
||||
const hash = (hval >>> 0).toString(16);
|
||||
return BigInt('0x' + hash.padEnd(hashLength - (hash.length - 1), '0')).toString(10);
|
||||
}
|
||||
exports.simpleHash = simpleHash;
|
||||
exports["default"] = (left, right) => simpleHash([left, right]);
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
@ -32,10 +32,10 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@metamask/eth-sig-util": "^7.0.3",
|
||||
"@tornado/contracts": "git+https://git.tornado.ws/tornadocontrib/tornado-contracts.git#ece511f424dc811c3aec149a4bf0e3731c0598a4",
|
||||
"@tornado/fixed-merkle-tree": "^0.7.3",
|
||||
"@tornado/snarkjs": "^0.1.20",
|
||||
"@tornado/websnark": "^0.0.4",
|
||||
"@tornado/contracts": "git+https://codeberg.org/tornadocash/tornado-contracts.git#ece511f424dc811c3aec149a4bf0e3731c0598a4",
|
||||
"@tornado/fixed-merkle-tree": "git+https://codeberg.org/tornadocash/fixed-merkle-tree.git#5c3fca4cb11255760ad5f4fd95d7c6eb45c1fc99",
|
||||
"@tornado/snarkjs": "git+https://codeberg.org/tornadocash/snarkjs.git#d3915a760c437cde7bd317f9ea2c627954900656",
|
||||
"@tornado/websnark": "git+https://codeberg.org/tornadocash/websnark.git#b0c9fce5359ceba55167a2ad01a29d1e137843ec",
|
||||
"ajv": "^8.17.1",
|
||||
"bn.js": "^5.2.1",
|
||||
"circomlibjs": "0.1.7",
|
||||
|
21
yarn.lock
21
yarn.lock
@ -872,23 +872,21 @@
|
||||
"@noble/hashes" "~1.4.0"
|
||||
"@scure/base" "~1.1.6"
|
||||
|
||||
"@tornado/contracts@git+https://git.tornado.ws/tornadocontrib/tornado-contracts.git#ece511f424dc811c3aec149a4bf0e3731c0598a4":
|
||||
"@tornado/contracts@git+https://codeberg.org/tornadocash/tornado-contracts.git#ece511f424dc811c3aec149a4bf0e3731c0598a4":
|
||||
version "1.0.2"
|
||||
resolved "git+https://git.tornado.ws/tornadocontrib/tornado-contracts.git#ece511f424dc811c3aec149a4bf0e3731c0598a4"
|
||||
resolved "git+https://codeberg.org/tornadocash/tornado-contracts.git#ece511f424dc811c3aec149a4bf0e3731c0598a4"
|
||||
dependencies:
|
||||
"@openzeppelin/contracts" "5.0.2"
|
||||
"@openzeppelin/contracts-v3" "npm:@openzeppelin/contracts@3.2.0-rc.0"
|
||||
ethers "^6.13.4"
|
||||
|
||||
"@tornado/fixed-merkle-tree@^0.7.3":
|
||||
"@tornado/fixed-merkle-tree@git+https://codeberg.org/tornadocash/fixed-merkle-tree.git#5c3fca4cb11255760ad5f4fd95d7c6eb45c1fc99":
|
||||
version "0.7.3"
|
||||
resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Ffixed-merkle-tree/-/0.7.3/fixed-merkle-tree-0.7.3.tgz#6636ce9d334553c5f17e5a564fd22f2e9ec04472"
|
||||
integrity sha512-8UWvIzz0/rMGBkzXACwmCv/5I1VJmnshAKc4C+nkTfOdmnX8Pf1bBa0GlxUIZ25ZFGiU/h2IKEHYckmHovwzEA==
|
||||
resolved "git+https://codeberg.org/tornadocash/fixed-merkle-tree.git#5c3fca4cb11255760ad5f4fd95d7c6eb45c1fc99"
|
||||
|
||||
"@tornado/snarkjs@^0.1.20":
|
||||
"@tornado/snarkjs@git+https://codeberg.org/tornadocash/snarkjs.git#d3915a760c437cde7bd317f9ea2c627954900656":
|
||||
version "0.1.20"
|
||||
resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Fsnarkjs/-/0.1.20/snarkjs-0.1.20.tgz#d7610cd3c8dc10598da7dc3e40e5d7470c3aa8c7"
|
||||
integrity sha512-mn+ePoQjqOHyDyK8AMy8SXYqNSxJWVswWVmMYvuc75/9bBtJ7SNtwrTByxmfWjrf4S3BM3IrGfHqBHEXY6gR4Q==
|
||||
resolved "git+https://codeberg.org/tornadocash/snarkjs.git#d3915a760c437cde7bd317f9ea2c627954900656"
|
||||
dependencies:
|
||||
big-integer "^1.6.43"
|
||||
chai "^4.2.0"
|
||||
@ -897,12 +895,11 @@
|
||||
keccak "^2.0.0"
|
||||
yargs "^12.0.5"
|
||||
|
||||
"@tornado/websnark@^0.0.4":
|
||||
"@tornado/websnark@git+https://codeberg.org/tornadocash/websnark.git#b0c9fce5359ceba55167a2ad01a29d1e137843ec":
|
||||
version "0.0.4"
|
||||
resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Fwebsnark/-/0.0.4/websnark-0.0.4.tgz#4c603259b71172225a70e3d454344fa172710970"
|
||||
integrity sha512-dHbaS41ILPq5NyVBDW8AmUPoSKvamtkTeIhyuKKmoyOrZMAUhJ9y1B2zRcejUKfhCZkIjAgCt9bvdzdOGGswwQ==
|
||||
resolved "git+https://codeberg.org/tornadocash/websnark.git#b0c9fce5359ceba55167a2ad01a29d1e137843ec"
|
||||
dependencies:
|
||||
"@tornado/snarkjs" "^0.1.20"
|
||||
"@tornado/snarkjs" "git+https://codeberg.org/tornadocash/snarkjs.git#d3915a760c437cde7bd317f9ea2c627954900656"
|
||||
big-integer "1.6.42"
|
||||
|
||||
"@typechain/ethers-v6@^0.5.1":
|
||||
|
Loading…
Reference in New Issue
Block a user