This commit is contained in:
Sergei SMART 2022-03-09 16:25:49 +10:00
parent ed4e372c93
commit 2dc7b4b5b3
7 changed files with 4608 additions and 74 deletions

4460
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -38,6 +38,7 @@
"nyc": "^15.1.0", "nyc": "^15.1.0",
"mocha": "^9.2.1", "mocha": "^9.2.1",
"ts-mocha": "^9.0.2", "ts-mocha": "^9.0.2",
"typescript": "^4.5.5" "typescript": "^4.5.5",
"circomlib": "git+https://github.com/tornadocash/circomlib.git#5beb6aee94923052faeecea40135d45b6ce6172c"
} }
} }

@ -21,13 +21,15 @@ export default class MerkleTree {
this.zeroElement = zeroElement this.zeroElement = zeroElement
this._layers = [] this._layers = []
this._layers[0] = elements.slice() const leaves = elements.slice()
this._layers = [leaves]
this._buildZeros() this._buildZeros()
this._buildHashes() this._buildHashes()
// this._buildHashes2(leaves)
} }
get capacity() { get capacity() {
return this.levels ** 2 return 2 ** this.levels
} }
get layers(): Array<Element[]> { get layers(): Array<Element[]> {
@ -63,6 +65,20 @@ export default class MerkleTree {
} }
} }
// _buildHashes2(nodes: Element[]) {
// let layerIndex = 0
// while (layerIndex < this.levels) {
// layerIndex = this._layers.length
// this._layers[layerIndex] = []
// for (let i = 0; i < nodes.length; i += 2) {
// const left = nodes[i]
// const right = (i + 1 === nodes.length && nodes.length % 2 === 1) ? this._zeros[layerIndex - 1] : nodes[i + 1]
// this._layers[layerIndex].push(this._hashFn(left, right))
// }
// nodes = this._layers[layerIndex]
// }
// }
/** /**
* Get tree root * Get tree root
*/ */
@ -124,12 +140,11 @@ export default class MerkleTree {
this._layers[0][index] = element this._layers[0][index] = element
for (let level = 1; level <= this.levels; level++) { for (let level = 1; level <= this.levels; level++) {
index >>= 1 index >>= 1
this._layers[level][index] = this._hashFn( const left = this._layers[level - 1][index * 2]
this._layers[level - 1][index * 2], const right = index * 2 + 1 < this._layers[level - 1].length
index * 2 + 1 < this._layers[level - 1].length
? this._layers[level - 1][index * 2 + 1] ? this._layers[level - 1][index * 2 + 1]
: this._zeros[level - 1], : this._zeros[level - 1]
) this._layers[level][index] = this._hashFn(left, right)
} }
} }
@ -162,6 +177,7 @@ export default class MerkleTree {
pathElements, pathElements,
pathIndices, pathIndices,
pathPositions, pathPositions,
pathRoot: this.root,
} }
} }
@ -184,9 +200,9 @@ export default class MerkleTree {
return this.path(index) return this.path(index)
} }
getTreeEdge(edgeElement: Element): TreeEdge { getTreeEdge(edgeIndex: number): TreeEdge {
const edgeIndex = this.indexOf(edgeElement) const edgeElement = this._layers[0][edgeIndex]
if (edgeIndex <= -1) { if (!edgeElement) {
throw new Error('Element not found') throw new Error('Element not found')
} }
const edgePath = this.path(edgeIndex) const edgePath = this.path(edgeIndex)

@ -12,6 +12,10 @@ import {
export const defaultHash = (left: Element, right: Element): string => simpleHash([left, right]) export const defaultHash = (left: Element, right: Element): string => simpleHash([left, right])
export class PartialMerkleTree { export class PartialMerkleTree {
get edgeLeafProof(): ProofPath {
return this._edgeLeafProof
}
levels: number levels: number
private zeroElement: Element private zeroElement: Element
private _zeros: Element[] private _zeros: Element[]
@ -22,26 +26,28 @@ export class PartialMerkleTree {
private _initialRoot: Element private _initialRoot: Element
private _hashFn: HashFunction<Element> private _hashFn: HashFunction<Element>
private _edgeLeafProof: ProofPath private _edgeLeafProof: ProofPath
private _proofMap: Map<number, [i: number, el: Element]>
constructor(levels: number, { constructor(levels: number, {
edgePath, edgePath,
edgeElement, edgeElement,
edgeIndex, edgeIndex,
}: TreeEdge, leaves: Element[], root: Element, { hashFunction, zeroElement }: MerkleTreeOptions = {}) { }: TreeEdge, leaves: Element[], { hashFunction, zeroElement }: MerkleTreeOptions = {}) {
hashFunction = hashFunction || defaultHash hashFunction = hashFunction || defaultHash
const hashFn = (left, right) => (left !== null && right !== null) ? hashFunction(left, right) : null const hashFn = (left, right) => (left !== undefined && right !== undefined) ? hashFunction(left, right) : undefined
this._edgeLeafProof = edgePath this._edgeLeafProof = edgePath
this._initialRoot = edgePath.pathRoot
this.zeroElement = zeroElement ?? 0 this.zeroElement = zeroElement ?? 0
this._edgeLeaf = { data: edgeElement, index: edgeIndex } this._edgeLeaf = { data: edgeElement, index: edgeIndex }
this._leavesAfterEdge = leaves this._leavesAfterEdge = leaves
this._initialRoot = root
this.levels = levels this.levels = levels
this._hashFn = hashFn this._hashFn = hashFn
this._createProofMap()
this._buildTree() this._buildTree()
} }
get capacity() { get capacity() {
return this.levels ** 2 return 2 ** this.levels
} }
get layers(): Array<Element[]> { get layers(): Array<Element[]> {
@ -68,15 +74,25 @@ export class PartialMerkleTree {
return this._edgeLeaf.data return this._edgeLeaf.data
} }
private _createProofMap() {
this._proofMap = this.edgeLeafProof.pathPositions.reduce((p, c, i) => {
p.set(i, [c, this.edgeLeafProof.pathElements[i]])
return p
}, new Map())
}
private _buildTree(): void { private _buildTree(): void {
const edgeLeafIndex = this._edgeLeaf.index const edgeLeafIndex = this._edgeLeaf.index
this._leaves = [...Array.from({ length: edgeLeafIndex }, () => null), ...this._leavesAfterEdge] this._leaves = Array(edgeLeafIndex).concat(this._leavesAfterEdge)
if (this._edgeLeafProof.pathIndices[0] === 1) { if (this._edgeLeafProof.pathIndices[0] === 1) {
this._leaves[this._edgeLeafProof.pathPositions[0]] = this._edgeLeafProof.pathElements[0] this._leaves[this._edgeLeafProof.pathPositions[0]] = this._edgeLeafProof.pathElements[0]
} }
this._layers = [this._leaves] this._layers = [this._leaves]
this._buildZeros() this._buildZeros()
this._buildHashes() // this._buildHashes()
this._buildHashes2()
} }
private _buildZeros() { private _buildZeros() {
@ -102,6 +118,30 @@ export class PartialMerkleTree {
} }
} }
_buildHashes2() {
let index = this.edgeIndex
let nodes: Element[]
for (let layerIndex = 1; layerIndex <= this.levels; layerIndex++) {
nodes = this._layers[layerIndex - 1]
// console.log({ layerIndex, nodes })
this._layers[layerIndex] = []
index = layerIndex > 1 ? Math.ceil(index / 2) : index
for (let i = 0; i < nodes.length; i += 2) {
const left = nodes[i]
const right = (i + 1 < nodes.length) ? nodes[i + 1] : this._zeros[layerIndex - 1]
let hash: Element = this._hashFn(left, right)
if (layerIndex === this.levels) hash = hash || this._edgeLeafProof.pathRoot
// console.log({ layerIndex, i, left, right, hash })
this._layers[layerIndex].push(hash)
}
if (this._proofMap.has(layerIndex)) {
const [proofPos, proofEl] = this._proofMap.get(layerIndex)
this._layers[layerIndex][proofPos] = this._layers[layerIndex][proofPos] || proofEl
}
}
}
/** /**
* Insert new element into the tree * Insert new element into the tree
* @param element Element to insert * @param element Element to insert
@ -113,7 +153,7 @@ export class PartialMerkleTree {
this.update(this._layers[0].length, element) this.update(this._layers[0].length, element)
} }
/** /*
* Insert multiple elements into the tree. * Insert multiple elements into the tree.
* @param {Array} elements Elements to insert * @param {Array} elements Elements to insert
*/ */
@ -135,10 +175,11 @@ export class PartialMerkleTree {
while (index % 2 === 1) { while (index % 2 === 1) {
level++ level++
index >>= 1 index >>= 1
this._layers[level][index] = this._hashFn( const left = this._layers[level - 1][index * 2]
this._layers[level - 1][index * 2], const right = this._layers[level - 1][index * 2 + 1]
this._layers[level - 1][index * 2 + 1], let hash: Element = this._hashFn(left, right)
) if (!hash && this._edgeLeafProof.pathPositions[level] === i) hash = this._edgeLeafProof.pathElements[level]
this._layers[level][index] = hash
} }
} }
this.insert(elements[elements.length - 1]) this.insert(elements[elements.length - 1])
@ -157,6 +198,8 @@ export class PartialMerkleTree {
throw new Error(`Index ${index} is below the edge: ${this._edgeLeaf.index}`) throw new Error(`Index ${index} is below the edge: ${this._edgeLeaf.index}`)
} }
this._layers[0][index] = element this._layers[0][index] = element
for (let level = 1; level <= this.levels; level++) { for (let level = 1; level <= this.levels; level++) {
index >>= 1 index >>= 1
const left = this._layers[level - 1][index * 2] const left = this._layers[level - 1][index * 2]
@ -164,7 +207,7 @@ export class PartialMerkleTree {
? this._layers[level - 1][index * 2 + 1] ? this._layers[level - 1][index * 2 + 1]
: this._zeros[level - 1] : this._zeros[level - 1]
let hash: Element = this._hashFn(left, right) let hash: Element = this._hashFn(left, right)
if (!hash && this._edgeLeafProof.pathPositions[level] === index) { if (!hash && this._edgeLeafProof.pathPositions[level] === index * 2) {
hash = this._edgeLeafProof.pathElements[level] hash = this._edgeLeafProof.pathElements[level]
} }
if (level === this.levels) { if (level === this.levels) {
@ -189,7 +232,8 @@ export class PartialMerkleTree {
pathIndices[level] = elIndex % 2 pathIndices[level] = elIndex % 2
const leafIndex = elIndex ^ 1 const leafIndex = elIndex ^ 1
if (leafIndex < this._layers[level].length) { if (leafIndex < this._layers[level].length) {
pathElements[level] = this._layers[level][leafIndex] const [proofPos, proofEl] = this._proofMap.get(level)
pathElements[level] = proofPos === leafIndex ? proofEl : this._layers[level][leafIndex]
pathPositions[level] = leafIndex pathPositions[level] = leafIndex
} else { } else {
pathElements[level] = this._zeros[level] pathElements[level] = this._zeros[level]
@ -201,6 +245,7 @@ export class PartialMerkleTree {
pathElements, pathElements,
pathIndices, pathIndices,
pathPositions, pathPositions,
pathRoot: this.root,
} }
} }
@ -228,11 +273,12 @@ export class PartialMerkleTree {
throw new Error(`New edgeIndex should be smaller then ${this._edgeLeaf.index}`) throw new Error(`New edgeIndex should be smaller then ${this._edgeLeaf.index}`)
} }
if (elements.length !== (this._edgeLeaf.index - edge.edgeIndex)) { if (elements.length !== (this._edgeLeaf.index - edge.edgeIndex)) {
throw new Error(`Elements length should be ${elements.length}`) throw new Error(`Elements length should be ${this._edgeLeaf.index - edge.edgeIndex}`)
} }
this._edgeLeafProof = edge.edgePath this._edgeLeafProof = edge.edgePath
this._edgeLeaf = { index: edge.edgeIndex, data: edge.edgeElement } this._edgeLeaf = { index: edge.edgeIndex, data: edge.edgeElement }
this._leavesAfterEdge = [...elements, ...this._leavesAfterEdge] this._leavesAfterEdge = [...elements, ...this._leavesAfterEdge]
this._createProofMap()
this._buildTree() this._buildTree()
} }
@ -254,7 +300,7 @@ export class PartialMerkleTree {
edgeElement: data._edgeLeaf.data, edgeElement: data._edgeLeaf.data,
edgeIndex: data._edgeLeaf.index, edgeIndex: data._edgeLeaf.index,
} }
return new PartialMerkleTree(data.levels, edge, data.leaves, data._initialRoot, { return new PartialMerkleTree(data.levels, edge, data.leaves, {
hashFunction, hashFunction,
zeroElement: data._zeros[0], zeroElement: data._zeros[0],
}) })

@ -32,11 +32,12 @@ export type ProofPath = {
pathElements: Element[], pathElements: Element[],
pathIndices: number[], pathIndices: number[],
pathPositions: number[], pathPositions: number[],
pathRoot: Element
} }
export type TreeEdge = { export type TreeEdge = {
edgeElement: Element; edgeElement: Element;
edgePath: ProofPath; edgePath: ProofPath;
edgeIndex: number edgeIndex: number;
} }
export type LeafWithIndex = { index: number, data: Element } export type LeafWithIndex = { index: number, data: Element }

@ -1,9 +1,12 @@
import { MerkleTree, TreeEdge } from '../src' import { MerkleTree, TreeEdge } from '../src'
import { assert, should } from 'chai' import { assert, should } from 'chai'
import { mimcsponge } from 'circomlib'
import { createHash } from 'crypto' import { createHash } from 'crypto'
import { it } from 'mocha' import { it } from 'mocha'
const sha256Hash = (left, right) => createHash('sha256').update(`${left}${right}`).digest('hex') const sha256Hash = (left, right) => createHash('sha256').update(`${left}${right}`).digest('hex')
const mimcHash = (left, right) => mimcsponge.multiHash([BigInt(left), BigInt(right)]).toString()
const ZERO_ELEMENT = '21663839004416932945382355908790599225266501822907911457504978515578255421292'
describe('MerkleTree', () => { describe('MerkleTree', () => {
@ -42,6 +45,11 @@ describe('MerkleTree', () => {
const tree = new MerkleTree(10, [1, 2, 3, 4, 5, 6], { hashFunction: sha256Hash, zeroElement: 'zero' }) const tree = new MerkleTree(10, [1, 2, 3, 4, 5, 6], { hashFunction: sha256Hash, zeroElement: 'zero' })
should().equal(tree.root, 'a377b9fa0ed41add83e56f7e1d0e2ebdb46550b9d8b26b77dece60cb67283f19') should().equal(tree.root, 'a377b9fa0ed41add83e56f7e1d0e2ebdb46550b9d8b26b77dece60cb67283f19')
}) })
it('should work with mimc hash function and zero element', () => {
const tree = new MerkleTree(10, [1, 2, 3], { hashFunction: mimcHash, zeroElement: ZERO_ELEMENT })
should().equal(tree.root, '13605252518346649016266481317890801910232739395710162921320863289825142055129')
})
}) })
describe('#insert', () => { describe('#insert', () => {
@ -232,11 +240,13 @@ describe('MerkleTree', () => {
const tree = new MerkleTree(10, [1, 2, 3, 4]) const tree = new MerkleTree(10, [1, 2, 3, 4])
should().throw((() => tree.path(-1)), 'Index out of bounds: -1') should().throw((() => tree.path(-1)), 'Index out of bounds: -1')
should().throw((() => tree.path(5)), 'Index out of bounds: 5') should().throw((() => tree.path(5)), 'Index out of bounds: 5')
// @ts-ignore
should().throw((() => tree.path('qwe')), 'Index out of bounds: qwe') should().throw((() => tree.path('qwe')), 'Index out of bounds: qwe')
}) })
it('should work for correct string index', () => { it('should work for correct string index', () => {
const tree = new MerkleTree(10, [1, 2, 3, 4, 5]) const tree = new MerkleTree(10, [1, 2, 3, 4, 5])
// @ts-ignore
const path = tree.path('2') const path = tree.path('2')
assert.deepEqual(path.pathIndices, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]) assert.deepEqual(path.pathIndices, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0])
assert.deepEqual(path.pathElements, [ assert.deepEqual(path.pathElements, [
@ -273,6 +283,7 @@ describe('MerkleTree', () => {
], ],
pathIndices: [0, 0, 1, 0], pathIndices: [0, 0, 1, 0],
pathPositions: [5, 0, 0, 0], pathPositions: [5, 0, 0, 0],
pathRoot: '3283298202329284319899364273680487022592',
}, },
edgeElement: 4, edgeElement: 4,
edgeIndex: 4, edgeIndex: 4,

@ -7,15 +7,15 @@ import { createHash } from 'crypto'
const sha256Hash = (left, right) => createHash('sha256').update(`${left}${right}`).digest('hex') const sha256Hash = (left, right) => createHash('sha256').update(`${left}${right}`).digest('hex')
describe('PartialMerkleTree', () => { describe('PartialMerkleTree', () => {
const getTestTrees = (levels: number, elements: Element[], edgeElement: Element, treeOptions: MerkleTreeOptions = {}) => { const getTestTrees = (levels: number, elements: Element[], edgeIndex: number, treeOptions: MerkleTreeOptions = {}) => {
const fullTree = new MerkleTree(levels, elements, treeOptions) const fullTree = new MerkleTree(levels, elements, treeOptions)
const edge = fullTree.getTreeEdge(edgeElement) const edge = fullTree.getTreeEdge(edgeIndex)
const leavesAfterEdge = elements.slice(edge.edgeIndex) const leavesAfterEdge = elements.slice(edge.edgeIndex)
const partialTree = new PartialMerkleTree(levels, edge, leavesAfterEdge, fullTree.root, treeOptions) const partialTree = new PartialMerkleTree(levels, edge, leavesAfterEdge, treeOptions)
return { fullTree, partialTree } return { fullTree, partialTree }
} }
describe('#constructor', () => { describe('#constructor', () => {
const { fullTree, partialTree } = getTestTrees(20, ['0', '1', '2', '3', '4', '5'], '2') const { fullTree, partialTree } = getTestTrees(20, ['0', '1', '2', '3', '4', '5'], 2)
it('should initialize merkle tree with same root', () => { it('should initialize merkle tree with same root', () => {
should().equal(fullTree.root, partialTree.root) should().equal(fullTree.root, partialTree.root)
}) })
@ -25,7 +25,7 @@ describe('PartialMerkleTree', () => {
}) })
it('should work with optional hash function and zero element', () => { it('should work with optional hash function and zero element', () => {
const { partialTree, fullTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6], 4, { const { partialTree, fullTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6], 3, {
hashFunction: sha256Hash, hashFunction: sha256Hash,
zeroElement: 'zero', zeroElement: 'zero',
}) })
@ -36,15 +36,15 @@ describe('PartialMerkleTree', () => {
describe('#insert', () => { describe('#insert', () => {
it('should have equal root to full tree after insertion ', () => { it('should have equal root to full tree after insertion ', () => {
const { fullTree, partialTree } = getTestTrees(10, ['0', '1', '2', '3', '4', '5', '6', '7'], '5') const { fullTree, partialTree } = getTestTrees(10, ['0', '1', '2', '3', '4', '5', '6', '7'], 5)
fullTree.insert('9') fullTree.insert('9')
partialTree.insert('9') partialTree.insert('9')
should().equal(fullTree.root, partialTree.root) should().equal(fullTree.root, partialTree.root)
}) })
it('should fail to insert when tree is full', () => { it('should fail to insert when tree is full', () => {
const { partialTree } = getTestTrees(3, ['0', '1', '2', '3', '4', '5', '6', '7', '8'], '5') const { partialTree } = getTestTrees(3, ['0', '1', '2', '3', '4', '5', '6', '7'], 5)
const call = () => partialTree.insert('9') const call = () => partialTree.insert('8')
should().throw(call, 'Tree is full') should().throw(call, 'Tree is full')
}) })
}) })
@ -52,7 +52,7 @@ describe('PartialMerkleTree', () => {
describe('#bulkInsert', () => { describe('#bulkInsert', () => {
it('should work like full tree', () => { it('should work like full tree', () => {
const { fullTree, partialTree } = getTestTrees(20, [1, 2, 3, 4, 5], 3) const { fullTree, partialTree } = getTestTrees(20, [1, 2, 3, 4, 5], 2)
partialTree.bulkInsert([6, 7, 8]) partialTree.bulkInsert([6, 7, 8])
fullTree.bulkInsert([6, 7, 8]) fullTree.bulkInsert([6, 7, 8])
should().equal(fullTree.root, partialTree.root) should().equal(fullTree.root, partialTree.root)
@ -73,8 +73,8 @@ describe('PartialMerkleTree', () => {
] ]
for (const initial of initialArray) { for (const initial of initialArray) {
for (const inserted of insertedArray) { for (const inserted of insertedArray) {
const { partialTree: tree1 } = getTestTrees(10, initial, initial.length > 1 ? initial.length - 1 : initial.length) const { partialTree: tree1 } = getTestTrees(10, initial, initial.length - 1)
const { partialTree: tree2 } = getTestTrees(10, initial, initial.length > 1 ? initial.length - 1 : initial.length) const { partialTree: tree2 } = getTestTrees(10, initial, initial.length - 1)
tree1.bulkInsert(inserted) tree1.bulkInsert(inserted)
for (const item of inserted) { for (const item of inserted) {
tree2.insert(item) tree2.insert(item)
@ -85,20 +85,20 @@ describe('PartialMerkleTree', () => {
}).timeout(10000) }).timeout(10000)
it('should fail to insert too many elements', () => { it('should fail to insert too many elements', () => {
const { partialTree } = getTestTrees(2, [1, 2, 3, 4], 3) const { partialTree } = getTestTrees(2, [1, 2, 3, 4], 2)
const call = () => partialTree.bulkInsert([5, 6, 7]) const call = () => partialTree.bulkInsert([5, 6, 7])
should().throw(call, 'Tree is full') should().throw(call, 'Tree is full')
}) })
it('should bypass empty elements', () => { it('should bypass empty elements', () => {
const elements = [1, 2, 3, 4] const elements = [1, 2, 3, 4]
const { partialTree } = getTestTrees(2, elements, 3) const { partialTree } = getTestTrees(2, elements, 2)
partialTree.bulkInsert([]) partialTree.bulkInsert([])
should().equal(partialTree.elements.length, elements.length, 'No elements inserted') should().equal(partialTree.elements.length, elements.length, 'No elements inserted')
}) })
}) })
describe('#update', () => { describe('#update', () => {
it('should update last element', () => { it('should update last element', () => {
const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3) const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 2)
partialTree.update(4, 42) partialTree.update(4, 42)
fullTree.update(4, 42) fullTree.update(4, 42)
should().equal(partialTree.root, fullTree.root) should().equal(partialTree.root, fullTree.root)
@ -106,28 +106,28 @@ describe('PartialMerkleTree', () => {
it('should update odd element', () => { it('should update odd element', () => {
const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8], 3) const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8], 2)
partialTree.update(4, 42) partialTree.update(4, 42)
fullTree.update(4, 42) fullTree.update(4, 42)
should().equal(partialTree.root, fullTree.root) should().equal(partialTree.root, fullTree.root)
}) })
it('should update even element', () => { it('should update even element', () => {
const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8], 3) const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8], 2)
partialTree.update(3, 42) partialTree.update(3, 42)
fullTree.update(3, 42) fullTree.update(3, 42)
should().equal(partialTree.root, fullTree.root) should().equal(partialTree.root, fullTree.root)
}) })
it('should update extra element', () => { it('should update extra element', () => {
const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3) const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 2)
partialTree.update(5, 6) partialTree.update(5, 6)
fullTree.update(5, 6) fullTree.update(5, 6)
should().equal(fullTree.root, partialTree.root) should().equal(fullTree.root, partialTree.root)
}) })
it('should fail to update incorrect index', () => { it('should fail to update incorrect index', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 4) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3)
should().throw((() => partialTree.update(-1, 42)), 'Insert index out of bounds: -1') should().throw((() => partialTree.update(-1, 42)), 'Insert index out of bounds: -1')
should().throw((() => partialTree.update(6, 42)), 'Insert index out of bounds: 6') should().throw((() => partialTree.update(6, 42)), 'Insert index out of bounds: 6')
should().throw((() => partialTree.update(2, 42)), 'Index 2 is below the edge: 3') should().throw((() => partialTree.update(2, 42)), 'Index 2 is below the edge: 3')
@ -136,36 +136,36 @@ describe('PartialMerkleTree', () => {
}) })
it('should fail to update over capacity', () => { it('should fail to update over capacity', () => {
const { partialTree } = getTestTrees(2, [1, 2, 3, 4], 2) const { partialTree } = getTestTrees(2, [1, 2, 3, 4], 1)
const call = () => partialTree.update(4, 42) const call = () => partialTree.update(4, 42)
should().throw(call, 'Insert index out of bounds: 4') should().throw(call, 'Insert index out of bounds: 4')
}) })
}) })
describe('#indexOf', () => { describe('#indexOf', () => {
it('should return same result as full tree', () => { it('should return same result as full tree', () => {
const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8], 4) const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8], 3)
should().equal(partialTree.indexOf(5), fullTree.indexOf(5)) should().equal(partialTree.indexOf(5), fullTree.indexOf(5))
}) })
it('should find index', () => { it('should find index', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 2)
should().equal(partialTree.indexOf(3), 2) should().equal(partialTree.indexOf(3), 2)
}) })
it('should work with comparator', () => { it('should work with comparator', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 2)
should().equal(partialTree.indexOf(4, (arg0, arg1) => arg0 === arg1), 3) should().equal(partialTree.indexOf(4, (arg0, arg1) => arg0 === arg1), 3)
}) })
it('should return -1 for non existent element', () => { it('should return -1 for non existent element', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 2)
should().equal(partialTree.indexOf(42), -1) should().equal(partialTree.indexOf(42), -1)
}) })
}) })
describe('#proof', () => { describe('#proof', () => {
it('should return proof for known leaf', () => { it('should return proof for known leaf', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 2)
assert.deepEqual(partialTree.proof(4), partialTree.path(3)) assert.deepEqual(partialTree.proof(4), partialTree.path(3))
}) })
}) })
@ -173,8 +173,8 @@ describe('PartialMerkleTree', () => {
describe('#getters', () => { describe('#getters', () => {
it('should return capacity', () => { it('should return capacity', () => {
const levels = 10 const levels = 10
const capacity = levels ** 2 const capacity = 2 ** levels
const { fullTree, partialTree } = getTestTrees(levels, [1, 2, 3, 4, 5], 3) const { fullTree, partialTree } = getTestTrees(levels, [1, 2, 3, 4, 5], 2)
should().equal(fullTree.capacity, capacity) should().equal(fullTree.capacity, capacity)
should().equal(partialTree.capacity, capacity) should().equal(partialTree.capacity, capacity)
}) })
@ -188,13 +188,13 @@ describe('PartialMerkleTree', () => {
}) })
it('should return copy of layers', () => { it('should return copy of layers', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 2)
const layers = partialTree.layers const layers = partialTree.layers
should().not.equal(layers, partialTree.layers) should().not.equal(layers, partialTree.layers)
}) })
it('should return copy of zeros', () => { it('should return copy of zeros', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 3) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5], 2)
const zeros = partialTree.zeros const zeros = partialTree.zeros
should().not.equal(zeros, partialTree.zeros) should().not.equal(zeros, partialTree.zeros)
}) })
@ -204,14 +204,14 @@ describe('PartialMerkleTree', () => {
it('should return path for known nodes', () => { it('should return path for known nodes', () => {
const levels = 20 const levels = 20
const capacity = levels ** 2 const capacity = 2 ** levels
const elements = Array.from({ length: capacity }, (_, i) => i) const elements = Array.from({ length: capacity / 2 }, (_, i) => i)
const { fullTree, partialTree } = getTestTrees(levels, elements, 250) const { fullTree, partialTree } = getTestTrees(levels, elements, 250)
assert.deepEqual(fullTree.path(250), partialTree.path(250)) assert.deepEqual(fullTree.path(250), partialTree.path(250))
}) }).timeout(10000)
it('should fail on incorrect index', () => { it('should fail on incorrect index', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8, 9], 5) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8, 9], 4)
should().throw((() => partialTree.path(-1)), 'Index out of bounds: -1') should().throw((() => partialTree.path(-1)), 'Index out of bounds: -1')
should().throw((() => partialTree.path(10)), 'Index out of bounds: 10') should().throw((() => partialTree.path(10)), 'Index out of bounds: 10')
// @ts-ignore // @ts-ignore
@ -219,39 +219,38 @@ describe('PartialMerkleTree', () => {
}) })
it('should fail if index is below edge', () => { it('should fail if index is below edge', () => {
const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8, 9], 5) const { partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8, 9], 4)
const call = () => partialTree.path(2) const call = () => partialTree.path(2)
should().throw(call, 'Index 2 is below the edge: 4') should().throw(call, 'Index 2 is below the edge: 4')
}) })
}) })
describe('#shiftEdge', () => { describe('#shiftEdge', () => {
it('should work', () => { it('should work', () => {
const levels = 20 const levels = 10
const elements: Element[] = Array.from({ length: levels ** 2 }, (_, i) => i) const elements: Element[] = Array.from({ length: 20 ** 2 }, (_, i) => i)
const tree = new MerkleTree(levels, elements) const tree = new MerkleTree(levels, elements)
const edge1 = tree.getTreeEdge(200) const edge1 = tree.getTreeEdge(200)
const edge2 = tree.getTreeEdge(100) const edge2 = tree.getTreeEdge(100)
const partialTree1 = new PartialMerkleTree(levels, edge1, elements.slice(edge1.edgeIndex), tree.root) const partialTree = new PartialMerkleTree(levels, edge1, elements.slice(edge1.edgeIndex))
const partialTree2 = new PartialMerkleTree(levels, edge2, elements.slice(edge2.edgeIndex), tree.root) partialTree.shiftEdge(edge2, elements.slice(edge2.edgeIndex, partialTree.edgeIndex))
partialTree1.shiftEdge(edge2, elements.slice(edge2.edgeIndex, partialTree1.edgeIndex)) assert.deepEqual(partialTree.path(110), tree.path(110))
assert.deepEqual(partialTree1.path(105), partialTree2.path(105))
}) })
it('should fail if new edge index is over current edge', () => { it('should fail if new edge index is over current edge', () => {
const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8, 9], 5) const { fullTree, partialTree } = getTestTrees(10, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4)
const newEdge = fullTree.getTreeEdge(6) const newEdge = fullTree.getTreeEdge(4)
const call = () => partialTree.shiftEdge(newEdge, [1, 2]) const call = () => partialTree.shiftEdge(newEdge, [1, 2])
should().throw(call, 'New edgeIndex should be smaller then 4') should().throw(call, 'New edgeIndex should be smaller then 4')
}) })
it('should fail if elements length are incorrect', () => { it('should fail if elements length are incorrect', () => {
const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8, 9], 5) const { fullTree, partialTree } = getTestTrees(10, [1, 2, 3, 4, 5, 6, 7, 8, 9], 4)
const newEdge = fullTree.getTreeEdge(4) const newEdge = fullTree.getTreeEdge(3)
const call = () => partialTree.shiftEdge(newEdge, [1, 2]) const call = () => partialTree.shiftEdge(newEdge, [1, 2])
should().throw(call, 'Elements length should be 2') should().throw(call, 'Elements length should be 1')
}) })
}) })
describe('#serialize', () => { describe('#serialize', () => {
it('should work', () => { it('should work', () => {
const { partialTree } = getTestTrees(5, [1, 2, 3, 4, 5, 6, 7, 8, 9], 6) const { partialTree } = getTestTrees(5, [1, 2, 3, 4, 5, 6, 7, 8, 9], 5)
const data = partialTree.serialize() const data = partialTree.serialize()
const dst = PartialMerkleTree.deserialize(data) const dst = PartialMerkleTree.deserialize(data)
should().equal(partialTree.root, dst.root) should().equal(partialTree.root, dst.root)
@ -264,7 +263,7 @@ describe('PartialMerkleTree', () => {
}) })
describe('#toString', () => { describe('#toString', () => {
it('should return correct stringified representation', () => { it('should return correct stringified representation', () => {
const { partialTree } = getTestTrees(5, [1, 2, 3, 4, 5, 6, 7, 8, 9], 6) const { partialTree } = getTestTrees(5, [1, 2, 3, 4, 5, 6, 7, 8, 9], 5)
const str = partialTree.toString() const str = partialTree.toString()
const dst = PartialMerkleTree.deserialize(JSON.parse(str)) const dst = PartialMerkleTree.deserialize(JSON.parse(str))
should().equal(partialTree.root, dst.root) should().equal(partialTree.root, dst.root)