mirror of
https://github.com/privacy-scaling-explorations/zk-kit.git
synced 2026-04-22 03:00:15 -04:00
nary incrementaly merkle tree
Former-commit-id: 812614c0cc1f79817b314a46491d6b61710e4942 [formerly 522c3bd30389b7ce4175c2830c79ccf22a4404d9] [formerly cc13abf762f896a1959190f8767eeba8a4c8028c [formerly 312e3ef206]]
Former-commit-id: 0a9e445f0ec532bc892a540c4027de886ab6f188 [formerly e0fa3503670f7130116a766f4af21996450d715f]
Former-commit-id: ea5a71189761e75320b173d25664d3f72ae0d74d
This commit is contained in:
14802
packages/identity/package-lock.json
generated
14802
packages/identity/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
8563
packages/merkle-tree/package-lock.json
generated
Normal file
8563
packages/merkle-tree/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
97
packages/merkle-tree/src/incremental-tree.ts
Normal file
97
packages/merkle-tree/src/incremental-tree.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import checkParameter from "./checkParameter"
|
||||
import { HashFunction, Node } from "./types"
|
||||
|
||||
export default class IncrementalTree {
|
||||
static readonly maxDepth = 32 // 5**32
|
||||
|
||||
protected _root: Node
|
||||
protected readonly _nodes: Node[][]
|
||||
protected readonly _zeroes: Node[]
|
||||
protected readonly _hash: HashFunction
|
||||
protected readonly _depth: number
|
||||
protected readonly _arity: number
|
||||
|
||||
constructor(hash: HashFunction, depth: number, zeroValue: Node, arity: number) {
|
||||
checkParameter(hash, "hash", "function")
|
||||
checkParameter(depth, "depth", "number")
|
||||
checkParameter(zeroValue, "zeroValue", "number", "string", "bigint")
|
||||
|
||||
if (depth < 1 || depth > IncrementalTree.maxDepth) {
|
||||
throw new Error("The tree depth must be between 1 and 32")
|
||||
}
|
||||
|
||||
// Initialize the attributes.
|
||||
this._hash = hash
|
||||
this._depth = depth
|
||||
this._zeroes = []
|
||||
this._nodes = []
|
||||
this._arity = arity;
|
||||
|
||||
for (let i = 0; i < depth; i += 1) {
|
||||
this._zeroes.push(zeroValue)
|
||||
this._nodes[i] = []
|
||||
// There must be a zero value for each tree level (except the root).
|
||||
zeroValue = hash(Array(this._arity).fill(zeroValue))
|
||||
}
|
||||
|
||||
// The default root is the last zero value.
|
||||
this._root = zeroValue
|
||||
|
||||
// Freeze the array objects. It prevents unintentional changes.
|
||||
Object.freeze(this._zeroes)
|
||||
Object.freeze(this._nodes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root hash of the tree.
|
||||
* @returns Root hash.
|
||||
*/
|
||||
public get root(): Node {
|
||||
return this._root
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the depth of the tree.
|
||||
* @returns Tree depth.
|
||||
*/
|
||||
public get depth(): number {
|
||||
return this._depth
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the leaves of the tree.
|
||||
* @returns List of leaves.
|
||||
*/
|
||||
public get leaves(): Node[] {
|
||||
return this._nodes[0].slice()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the zeroes nodes of the tree.
|
||||
* @returns List of zeroes.
|
||||
*/
|
||||
public get zeroes(): Node[] {
|
||||
return this._zeroes
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the zeroes nodes of the tree.
|
||||
* @returns List of zeroes.
|
||||
*/
|
||||
public get arity(): number {
|
||||
return this._arity
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index of a leaf. If the leaf does not exist it returns -1.
|
||||
* @param leaf Tree leaf.
|
||||
* @returns Index of the leaf.
|
||||
*/
|
||||
public indexOf(leaf: Node): number {
|
||||
checkParameter(leaf, "leaf", "number", "string", "bigint")
|
||||
|
||||
return this.leaves.indexOf(leaf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import MerkleTree from "./merkle-tree"
|
||||
import NAryIncrementalTree from "./nary-incremental-tree"
|
||||
|
||||
export { MerkleTree }
|
||||
export { MerkleTree, NAryIncrementalTree }
|
||||
export * from "./types"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import checkParameter from "./checkParameter"
|
||||
import { HashFunction, Proof, Node } from "./types"
|
||||
import IncrementalTree from "./incremental-tree"
|
||||
|
||||
/**
|
||||
* A Merkle tree is a tree in which every leaf node is labelled with the cryptographic hash of a
|
||||
@@ -8,15 +9,7 @@ import { HashFunction, Proof, Node } from "./types"
|
||||
* The MerkleTree class is a TypeScript implementation of Merkle tree and it provides all the functions to create
|
||||
* efficient trees and to generate and verify proofs of membership.
|
||||
*/
|
||||
export default class MerkleTree {
|
||||
static readonly maxDepth = 32 // 2**32 = 4294967296 possible leaves.
|
||||
|
||||
private _root: Node
|
||||
private readonly _nodes: Node[][]
|
||||
private readonly _zeroes: Node[]
|
||||
private readonly _hash: HashFunction
|
||||
private readonly _depth: number
|
||||
|
||||
export default class MerkleTree extends IncrementalTree {
|
||||
/**
|
||||
* Initializes the Merkle tree with the hash function, the depth and the zero value to use for zeroes.
|
||||
* @param hash Hash function.
|
||||
@@ -24,65 +17,7 @@ export default class MerkleTree {
|
||||
* @param zeroValue Zero values for zeroes.
|
||||
*/
|
||||
constructor(hash: HashFunction, depth: number, zeroValue: Node) {
|
||||
checkParameter(hash, "hash", "function")
|
||||
checkParameter(depth, "depth", "number")
|
||||
checkParameter(zeroValue, "zeroValue", "number", "string", "bigint")
|
||||
|
||||
if (depth < 1 || depth > MerkleTree.maxDepth) {
|
||||
throw new Error("The tree depth must be between 1 and 32")
|
||||
}
|
||||
|
||||
// Initialize the attributes.
|
||||
this._hash = hash
|
||||
this._depth = depth
|
||||
this._zeroes = []
|
||||
this._nodes = []
|
||||
|
||||
for (let i = 0; i < depth; i += 1) {
|
||||
this._zeroes.push(zeroValue)
|
||||
this._nodes[i] = []
|
||||
// There must be a zero value for each tree level (except the root).
|
||||
zeroValue = hash([zeroValue, zeroValue])
|
||||
}
|
||||
|
||||
// The default root is the last zero value.
|
||||
this._root = zeroValue
|
||||
|
||||
// Freeze the array objects. It prevents unintentional changes.
|
||||
Object.freeze(this._zeroes)
|
||||
Object.freeze(this._nodes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root hash of the tree.
|
||||
* @returns Root hash.
|
||||
*/
|
||||
public get root(): Node {
|
||||
return this._root
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the depth of the tree.
|
||||
* @returns Tree depth.
|
||||
*/
|
||||
public get depth(): number {
|
||||
return this._depth
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the leaves of the tree.
|
||||
* @returns List of leaves.
|
||||
*/
|
||||
public get leaves(): Node[] {
|
||||
return this._nodes[0].slice()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the zeroes nodes of the tree.
|
||||
* @returns List of zeroes.
|
||||
*/
|
||||
public get zeroes(): Node[] {
|
||||
return this._zeroes
|
||||
super(hash, depth, zeroValue, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,17 +130,6 @@ export default class MerkleTree {
|
||||
return proof.root === node
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of a leaf. If the leaf does not exist it returns -1.
|
||||
* @param leaf Tree leaf.
|
||||
* @returns Index of the leaf.
|
||||
*/
|
||||
public indexOf(leaf: Node): number {
|
||||
checkParameter(leaf, "leaf", "number", "string", "bigint")
|
||||
|
||||
return this.leaves.indexOf(leaf)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a bottom-up tree traversal where for each level it calls a callback.
|
||||
* @param index Index of the leaf.
|
||||
|
||||
103
packages/merkle-tree/src/nary-incremental-tree.ts
Normal file
103
packages/merkle-tree/src/nary-incremental-tree.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import checkParameter from "./checkParameter"
|
||||
import { HashFunction, Proof, Node } from "./types"
|
||||
import IncrementalTree from "./incremental-tree"
|
||||
|
||||
export default class NAryIncrementalTree extends IncrementalTree {
|
||||
constructor(hash: HashFunction, depth: number, zeroValue: Node, arity: number) {
|
||||
super(hash, depth, zeroValue, arity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new leaf in the tree.
|
||||
* @param leaf New leaf.
|
||||
*/
|
||||
public insert(leaf: Node) {
|
||||
checkParameter(leaf, "leaf", "number", "string", "bigint")
|
||||
|
||||
if (leaf === this.zeroes[0]) {
|
||||
throw new Error("The leaf cannot be a zero value")
|
||||
}
|
||||
|
||||
if (this.leaves.length >= this.arity ** this.depth) {
|
||||
throw new Error("The tree is full")
|
||||
}
|
||||
|
||||
let node = leaf
|
||||
|
||||
this.forEachLevel(this.leaves.length, (level, index, position) => {
|
||||
this._nodes[level][index] = node
|
||||
|
||||
const leftNeighbours: Array<Node> = this._nodes[level].slice(index - position, index)
|
||||
const rightNeighbours: Array<Node> = Array(this._arity - 1 - position).fill(this.zeroes[level])
|
||||
|
||||
node = this._hash(leftNeighbours.concat([node], rightNeighbours))
|
||||
})
|
||||
|
||||
this._root = node
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a proof of membership.
|
||||
* @param index Index of the proof's leaf.
|
||||
* @returns Proof object.
|
||||
*/
|
||||
public createProof(index: number): Proof {
|
||||
checkParameter(index, "index", "number")
|
||||
|
||||
if (index < 0 || index >= this.leaves.length) {
|
||||
throw new Error("The leaf does not exist in this tree")
|
||||
}
|
||||
|
||||
const siblingNodes: Node[] = []
|
||||
const path: Array<number> = []
|
||||
|
||||
this.forEachLevel(index, (level, index, position) => {
|
||||
path.push(position);
|
||||
|
||||
const leftNeighbours: Array<Node> = this._nodes[level].slice(index - position, index)
|
||||
const lastRighIndex = index + this._arity - position
|
||||
const numOfLeaves = this._nodes[level].length
|
||||
let rightNeighbours: Array<Node> = this._nodes[level].slice(index + 1, Math.min(numOfLeaves, lastRighIndex))
|
||||
|
||||
if (numOfLeaves < lastRighIndex) {
|
||||
rightNeighbours = rightNeighbours.concat(Array(lastRighIndex - numOfLeaves).fill(this.zeroes[level]))
|
||||
}
|
||||
|
||||
siblingNodes[level] = leftNeighbours.concat(rightNeighbours)
|
||||
})
|
||||
|
||||
return { root: this._root, leaf: this.leaves[index], siblingNodes, path }
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a proof and return true or false.
|
||||
* @param proof Proof to be verified.
|
||||
* @returns True or false.
|
||||
*/
|
||||
public verifyProof(proof: Proof): boolean {
|
||||
checkParameter(proof, "proof", "object")
|
||||
checkParameter(proof.root, "proof.root", "number", "string", "bigint")
|
||||
checkParameter(proof.leaf, "proof.leaf", "number", "string", "bigint")
|
||||
checkParameter(proof.siblingNodes, "proof.siblingNodes", "object")
|
||||
checkParameter(proof.path, "proof.path", "object")
|
||||
|
||||
let node = proof.leaf
|
||||
|
||||
for (let i = 0; i < proof.siblingNodes.length; i += 1) {
|
||||
proof.siblingNodes[i].splice(proof.path[i], 0, node)
|
||||
node = this._hash(proof.siblingNodes[i])
|
||||
}
|
||||
|
||||
return proof.root === node
|
||||
}
|
||||
|
||||
private forEachLevel(index: number, callback: (level: number, index: number, position: number) => void) {
|
||||
for (let level = 0; level < this._depth; level += 1) {
|
||||
callback(level, index, index % this._arity)
|
||||
|
||||
index = Math.floor(index / this.arity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -6,5 +6,5 @@ export type Proof = {
|
||||
root: Node
|
||||
leaf: Node
|
||||
siblingNodes: Node[]
|
||||
path: (0 | 1)[]
|
||||
path: Array<number>
|
||||
}
|
||||
|
||||
59
packages/merkle-tree/tests/nary.test.ts
Normal file
59
packages/merkle-tree/tests/nary.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { poseidon } from "circomlibjs"
|
||||
import { NAryIncrementalTree } from "../src"
|
||||
|
||||
describe("Nary Merkle Tree", () => {
|
||||
const depth = 20;
|
||||
const arity = 5;
|
||||
let tree: NAryIncrementalTree;
|
||||
|
||||
describe("Merkle Tree class", () => {
|
||||
beforeEach(() => {
|
||||
tree = new NAryIncrementalTree(poseidon, depth, BigInt(0), 5)
|
||||
})
|
||||
|
||||
it("Should not initialize a Merkle tree with wrong parameters", () => {
|
||||
expect(() => new NAryIncrementalTree(undefined as any, 33, 0, arity)).toThrow("Parameter 'hash' is not defined")
|
||||
expect(() => new NAryIncrementalTree(1 as any, 33, 0, arity)).toThrow("Parameter 'hash' is none of these types: function")
|
||||
})
|
||||
|
||||
it("Should not initialize a Merkle tree with depth > 32", () => {
|
||||
expect(() => new NAryIncrementalTree(poseidon, 33, BigInt(0), arity)).toThrow("The tree depth must be between 1 and 32")
|
||||
})
|
||||
|
||||
it("Should initialize a Merkle tree", () => {
|
||||
// console.log(tree.root)
|
||||
expect(tree.depth).toEqual(depth)
|
||||
// expect(tree.root).toEqual(BigInt("3315762791558236426429898223445373782079540514426385620818139644150484427120n"))
|
||||
expect(tree.leaves).toHaveLength(0)
|
||||
expect(tree.zeroes).toHaveLength(depth)
|
||||
expect(tree.arity).toEqual(arity)
|
||||
})
|
||||
|
||||
it("Should not insert a zero leaf", () => {
|
||||
expect(() => tree.insert(BigInt(0))).toThrow("The leaf cannot be a zero value")
|
||||
})
|
||||
|
||||
it("Should not insert a leaf in a full tree", () => {
|
||||
const fullTree = new NAryIncrementalTree(poseidon, 1, BigInt(0), 3)
|
||||
|
||||
fullTree.insert(BigInt(1))
|
||||
fullTree.insert(BigInt(2))
|
||||
fullTree.insert(BigInt(3))
|
||||
|
||||
expect(() => fullTree.insert(BigInt(4))).toThrow("The tree is full")
|
||||
})
|
||||
})
|
||||
|
||||
it("Should create a valid proof", () => {
|
||||
const numberOfLeaves = 50;
|
||||
|
||||
for (let i = 0; i < numberOfLeaves; i+=1) {
|
||||
tree.insert(BigInt(i + 1))
|
||||
}
|
||||
|
||||
for (let i = 0; i < numberOfLeaves; i += 1) {
|
||||
const proof = tree.createProof(i)
|
||||
expect(tree.verifyProof(proof)).toBeTruthy()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1 +1 @@
|
||||
8799a1b1047496f5091e6e0b5321f1cadfd7404a
|
||||
4c454c6f5e66fdbf5b136bd6fddb262b6ea20646
|
||||
18258
packages/sparse-merkle-tree/package-lock.json
generated
18258
packages/sparse-merkle-tree/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user