fixed-merkle-tree/src/PartialMerkleTree.ts

179 lines
5.6 KiB
TypeScript
Raw Normal View History

import {
Element,
HashFunction,
LeafWithIndex,
MerkleTreeOptions,
ProofPath,
SerializedPartialTreeState,
TreeEdge,
} from './'
2022-03-13 06:28:37 +01:00
import defaultHash from './simpleHash'
import { BaseTree } from './BaseTree'
2022-03-13 06:28:37 +01:00
export class PartialMerkleTree extends BaseTree {
private _leaves: Element[]
private _leavesAfterEdge: Element[]
private _edgeLeaf: LeafWithIndex
private _initialRoot: Element
private _edgeLeafProof: ProofPath
2022-03-09 07:25:49 +01:00
private _proofMap: Map<number, [i: number, el: Element]>
constructor(levels: number, {
edgePath,
edgeElement,
edgeIndex,
2022-03-11 06:37:20 +01:00
edgeElementsCount,
2022-03-09 07:25:49 +01:00
}: TreeEdge, leaves: Element[], { hashFunction, zeroElement }: MerkleTreeOptions = {}) {
2022-03-13 06:28:37 +01:00
super()
2022-03-11 06:37:20 +01:00
if (edgeIndex + leaves.length !== edgeElementsCount) throw new Error('Invalid number of elements')
this._edgeLeafProof = edgePath
2022-03-09 07:25:49 +01:00
this._initialRoot = edgePath.pathRoot
this.zeroElement = zeroElement ?? 0
this._edgeLeaf = { data: edgeElement, index: edgeIndex }
this._leavesAfterEdge = leaves
2022-02-28 08:00:28 +01:00
this.levels = levels
2022-03-11 06:37:20 +01:00
this._hashFn = hashFunction || defaultHash
2022-03-09 07:25:49 +01:00
this._createProofMap()
this._buildTree()
}
2022-03-02 05:33:04 +01:00
get edgeIndex(): number {
return this._edgeLeaf.index
}
get edgeElement(): Element {
return this._edgeLeaf.data
}
2022-03-13 06:28:37 +01:00
get edgeLeafProof(): ProofPath {
return this._edgeLeafProof
}
2022-03-09 07:25:49 +01:00
private _createProofMap() {
this._proofMap = this.edgeLeafProof.pathPositions.reduce((p, c, i) => {
p.set(i, [c, this.edgeLeafProof.pathElements[i]])
return p
}, new Map())
2022-03-09 11:00:00 +01:00
this._proofMap.set(this.levels, [0, this.edgeLeafProof.pathRoot])
2022-03-09 07:25:49 +01:00
}
private _buildTree(): void {
const edgeLeafIndex = this._edgeLeaf.index
2022-03-09 07:25:49 +01:00
this._leaves = Array(edgeLeafIndex).concat(this._leavesAfterEdge)
2022-03-09 11:00:00 +01:00
if (this._proofMap.has(0)) {
const [proofPos, proofEl] = this._proofMap.get(0)
2022-03-09 11:35:34 +01:00
this._leaves[proofPos] = proofEl
}
this._layers = [this._leaves]
2022-02-28 08:00:28 +01:00
this._buildZeros()
2022-03-10 09:00:26 +01:00
this._buildHashes()
2022-02-28 08:00:28 +01:00
}
2022-03-13 06:28:37 +01:00
private _buildHashes() {
2022-03-09 11:00:00 +01:00
for (let layerIndex = 1; layerIndex <= this.levels; layerIndex++) {
2022-03-10 09:00:26 +01:00
const nodes = this._layers[layerIndex - 1]
2022-03-13 06:28:37 +01:00
const currentLayer = this._processNodes(nodes, layerIndex)
2022-03-09 11:00:00 +01:00
if (this._proofMap.has(layerIndex)) {
const [proofPos, proofEl] = this._proofMap.get(layerIndex)
2022-03-10 09:00:26 +01:00
if (!currentLayer[proofPos]) currentLayer[proofPos] = proofEl
2022-03-09 11:00:00 +01:00
}
2022-03-10 09:00:26 +01:00
this._layers[layerIndex] = currentLayer
2022-03-09 07:25:49 +01:00
}
}
2022-02-28 08:00:28 +01:00
/**
* Change an element in the tree
* @param {number} index Index of element to change
* @param element Updated element value
*/
update(index: number, element: Element) {
if (isNaN(Number(index)) || index < 0 || index > this._layers[0].length || index >= this.capacity) {
throw new Error('Insert index out of bounds: ' + index)
}
2022-03-01 07:02:45 +01:00
if (index < this._edgeLeaf.index) {
throw new Error(`Index ${index} is below the edge: ${this._edgeLeaf.index}`)
}
2022-02-28 08:00:28 +01:00
this._layers[0][index] = element
2022-03-13 06:28:37 +01:00
this._processUpdate(index)
2022-02-28 08:00:28 +01:00
}
2022-03-02 04:22:54 +01:00
path(index: number): ProofPath {
2022-02-28 08:00:28 +01:00
if (isNaN(Number(index)) || index < 0 || index >= this._layers[0].length) {
throw new Error('Index out of bounds: ' + index)
}
2022-03-01 07:02:45 +01:00
if (index < this._edgeLeaf.index) {
throw new Error(`Index ${index} is below the edge: ${this._edgeLeaf.index}`)
}
2022-03-10 09:00:26 +01:00
let elIndex = Number(index)
2022-02-28 08:00:28 +01:00
const pathElements: Element[] = []
const pathIndices: number[] = []
const pathPositions: number [] = []
for (let level = 0; level < this.levels; level++) {
pathIndices[level] = elIndex % 2
const leafIndex = elIndex ^ 1
if (leafIndex < this._layers[level].length) {
2022-03-28 09:44:50 +02:00
pathElements[level] = this._layers[level][leafIndex]
2022-02-28 08:00:28 +01:00
pathPositions[level] = leafIndex
} else {
pathElements[level] = this._zeros[level]
pathPositions[level] = 0
}
2022-03-28 09:44:50 +02:00
const [proofPos, proofEl] = this._proofMap.get(level)
pathElements[level] = pathElements[level] ?? (proofPos === leafIndex ? proofEl : this._zeros[level])
2022-02-28 08:00:28 +01:00
elIndex >>= 1
}
return {
pathElements,
pathIndices,
pathPositions,
2022-03-09 07:25:49 +01:00
pathRoot: this.root,
2022-02-28 08:00:28 +01:00
}
}
2022-03-02 04:22:54 +01:00
2022-03-02 05:33:04 +01:00
/**
* 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[]) {
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)) {
2022-03-09 07:25:49 +01:00
throw new Error(`Elements length should be ${this._edgeLeaf.index - edge.edgeIndex}`)
2022-03-02 05:33:04 +01:00
}
this._edgeLeafProof = edge.edgePath
this._edgeLeaf = { index: edge.edgeIndex, data: edge.edgeElement }
this._leavesAfterEdge = [...elements, ...this._leavesAfterEdge]
2022-03-09 07:25:49 +01:00
this._createProofMap()
2022-03-02 05:33:04 +01:00
this._buildTree()
}
serialize(): SerializedPartialTreeState {
return {
_edgeLeafProof: this._edgeLeafProof,
_edgeLeaf: this._edgeLeaf,
2022-03-28 09:44:50 +02:00
_layers: this._layers,
_zeros: this._zeros,
2022-03-28 09:44:50 +02:00
levels: this.levels,
}
}
static deserialize(data: SerializedPartialTreeState, hashFunction?: HashFunction<Element>): PartialMerkleTree {
2022-03-28 09:44:50 +02:00
const instance: PartialMerkleTree = Object.assign(Object.create(this.prototype), data)
instance._hashFn = hashFunction || defaultHash
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())
2022-02-28 08:00:28 +01:00
}
}