upload lib files

This commit is contained in:
Sergei SMART 2022-03-14 19:42:36 +10:00
parent 1f34fa69fa
commit 2e65f7cac0
10 changed files with 607 additions and 0 deletions

42
lib/BaseTree.d.ts vendored Normal file

@ -0,0 +1,42 @@
import { Element, HashFunction, ProofPath } from './index';
export declare class BaseTree {
levels: number;
protected _hashFn: HashFunction<Element>;
protected zeroElement: Element;
protected _zeros: Element[];
protected _layers: Array<Element[]>;
get capacity(): number;
get layers(): Array<Element[]>;
get zeros(): Element[];
get elements(): Element[];
get root(): Element;
/**
* Find an element in the tree
* @param element An element to find
* @param comparator A function that checks leaf value equality
* @returns {number} Index if element is found, otherwise -1
*/
indexOf(element: Element, comparator?: <T>(arg0: T, arg1: T) => boolean): number;
/**
* Insert new element into the tree
* @param element Element to insert
*/
insert(element: Element): void;
bulkInsert(elements: Element[]): void;
/**
* Change an element in the tree
* @param {number} index Index of element to change
* @param element Updated element value
*/
update(index: number, element: Element): void;
proof(element: Element): ProofPath;
/**
* 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: number): ProofPath;
protected _buildZeros(): void;
protected _processNodes(nodes: Element[], layerIndex: number): any[];
protected _processUpdate(index: number): void;
}

156
lib/BaseTree.js Normal file

@ -0,0 +1,156 @@
"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 element An element to find
* @param comparator A function that checks leaf value equality
* @returns {number} Index if element is found, otherwise -1
*/
indexOf(element, comparator) {
if (comparator) {
return this._layers[0].findIndex((el) => comparator(element, el));
}
else {
return this._layers[0].indexOf(element);
}
}
/**
* 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);
}
proof(element) {
const index = this.indexOf(element);
return this.path(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;

30
lib/FixedMerkleTree.d.ts vendored Normal file

@ -0,0 +1,30 @@
import { Element, HashFunction, MerkleTreeOptions, SerializedTreeState, TreeEdge, TreeSlice } from './';
import { BaseTree } from './BaseTree';
export default class MerkleTree extends BaseTree {
constructor(levels: number, elements?: Element[], { hashFunction, zeroElement, }?: MerkleTreeOptions);
private _buildHashes;
/**
* Insert multiple elements into the tree.
* @param {Array} elements Elements to insert
*/
bulkInsert(elements: Element[]): void;
getTreeEdge(edgeIndex: number): TreeEdge;
/**
* 🪓
* @param count
*/
getTreeSlices(count?: number): TreeSlice[];
/**
* 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(): SerializedTreeState;
/**
* 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: SerializedTreeState, hashFunction?: HashFunction<Element>): MerkleTree;
toString(): string;
}

104
lib/FixedMerkleTree.js Normal file

@ -0,0 +1,104 @@
"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(require("./simpleHash"));
const BaseTree_1 = require("./BaseTree");
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]);
}
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) {
return new MerkleTree(data.levels, data._layers[0], { hashFunction, zeroElement: data._zeros[0] });
}
toString() {
return JSON.stringify(this.serialize());
}
}
exports.default = MerkleTree;

33
lib/PartialMerkleTree.d.ts vendored Normal file

@ -0,0 +1,33 @@
import { Element, HashFunction, MerkleTreeOptions, ProofPath, SerializedPartialTreeState, TreeEdge } from './';
import { BaseTree } from './BaseTree';
export declare class PartialMerkleTree extends BaseTree {
private _leaves;
private _leavesAfterEdge;
private _edgeLeaf;
private _initialRoot;
private _edgeLeafProof;
private _proofMap;
constructor(levels: number, { edgePath, edgeElement, edgeIndex, edgeElementsCount, }: TreeEdge, leaves: Element[], { hashFunction, zeroElement }?: MerkleTreeOptions);
get edgeIndex(): number;
get edgeElement(): Element;
get edgeLeafProof(): ProofPath;
private _createProofMap;
private _buildTree;
private _buildHashes;
/**
* Change an element in the tree
* @param {number} index Index of element to change
* @param element Updated element value
*/
update(index: number, element: Element): void;
path(index: number): ProofPath;
/**
* Shifts edge of tree to left
* @param edge new TreeEdge below current edge
* @param elements leaves between old and new edge
*/
shiftEdge(edge: TreeEdge, elements: Element[]): void;
serialize(): SerializedPartialTreeState;
static deserialize(data: SerializedPartialTreeState, hashFunction?: HashFunction<Element>): PartialMerkleTree;
toString(): string;
}

155
lib/PartialMerkleTree.js Normal file

@ -0,0 +1,155 @@
"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(require("./simpleHash"));
const BaseTree_1 = require("./BaseTree");
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) {
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) {
const [proofPos, proofEl] = this._proofMap.get(level);
pathElements[level] = proofPos === leafIndex ? proofEl : 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,
};
}
/**
* 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() {
const leaves = this.layers[0].slice(this._edgeLeaf.index);
return {
_edgeLeafProof: this._edgeLeafProof,
_edgeLeaf: this._edgeLeaf,
_edgeElementsCount: this._layers[0].length,
levels: this.levels,
leaves,
_zeros: this._zeros,
};
}
static deserialize(data, hashFunction) {
const edge = {
edgePath: data._edgeLeafProof,
edgeElement: data._edgeLeaf.data,
edgeIndex: data._edgeLeaf.index,
edgeElementsCount: data._edgeElementsCount,
};
return new PartialMerkleTree(data.levels, edge, data.leaves, {
hashFunction,
zeroElement: data._zeros[0],
});
}
toString() {
return JSON.stringify(this.serialize());
}
}
exports.PartialMerkleTree = PartialMerkleTree;

44
lib/index.d.ts vendored Normal file

@ -0,0 +1,44 @@
export { default as MerkleTree } from './FixedMerkleTree';
export { PartialMerkleTree } from './PartialMerkleTree';
export { simpleHash } from './simpleHash';
export declare type HashFunction<T> = {
(left: T, right: T): string;
};
export declare type MerkleTreeOptions = {
hashFunction?: HashFunction<Element>;
zeroElement?: Element;
};
export declare type Element = string | number;
export declare type SerializedTreeState = {
levels: number;
_zeros: Array<Element>;
_layers: Array<Element[]>;
};
export declare type SerializedPartialTreeState = {
levels: number;
leaves: Element[];
_edgeElementsCount: number;
_zeros: Array<Element>;
_edgeLeafProof: ProofPath;
_edgeLeaf: LeafWithIndex;
};
export declare type ProofPath = {
pathElements: Element[];
pathIndices: number[];
pathPositions: number[];
pathRoot: Element;
};
export declare type TreeEdge = {
edgeElement: Element;
edgePath: ProofPath;
edgeIndex: number;
edgeElementsCount: number;
};
export declare type TreeSlice = {
edge: TreeEdge;
elements: Element[];
};
export declare type LeafWithIndex = {
index: number;
data: Element;
};

12
lib/index.js Normal file

@ -0,0 +1,12 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.simpleHash = exports.PartialMerkleTree = exports.MerkleTree = void 0;
var FixedMerkleTree_1 = require("./FixedMerkleTree");
Object.defineProperty(exports, "MerkleTree", { enumerable: true, get: function () { return __importDefault(FixedMerkleTree_1).default; } });
var PartialMerkleTree_1 = require("./PartialMerkleTree");
Object.defineProperty(exports, "PartialMerkleTree", { enumerable: true, get: function () { return PartialMerkleTree_1.PartialMerkleTree; } });
var simpleHash_1 = require("./simpleHash");
Object.defineProperty(exports, "simpleHash", { enumerable: true, get: function () { return simpleHash_1.simpleHash; } });

10
lib/simpleHash.d.ts vendored Normal file

@ -0,0 +1,10 @@
import { Element } from './index';
/***
* This is insecure hash function, just for example only
* @param data
* @param seed
* @param hashLength
*/
export declare function simpleHash<T>(data: T[], seed?: number, hashLength?: number): string;
declare const _default: (left: Element, right: Element) => string;
export default _default;

21
lib/simpleHash.js Normal file

@ -0,0 +1,21 @@
"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]);