Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented updateMany method as required in issue #117 #314

Merged
merged 13 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions packages/lean-imt/src/lean-imt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,52 @@ export default class LeanIMT<N = bigint> {
this._nodes[this.depth] = [node]
}

/**
* Updates N leaves all at once.
* It is more efficient than using the {@link LeanIMT#update} method N times because it
* prevents updating middle nodes several times. This would happen when updating leaves
* with common ancestors. However, it doesn't offer a better worst-case time complexity.
* @param indices The list of indices of the respective leaves.
* @param leaves The list of leaves to be updated.
*/
public updateMany(indices: number[], leaves: N[]) {
requireDefined(leaves, "leaves")
requireDefined(indices, "indices")
requireArray(leaves, "leaves")
requireArray(indices, "indices")

if (leaves.length !== indices.length) {
cedoor marked this conversation as resolved.
Show resolved Hide resolved
cedoor marked this conversation as resolved.
Show resolved Hide resolved
throw new Error("There is no correspondence between indices and leaves")
}
for (let leaf = 0; leaf < indices.length; leaf += 1) {
vplasencia marked this conversation as resolved.
Show resolved Hide resolved
requireNumber(indices[leaf], `index ${leaf}`)
if (indices[leaf] < 0 || indices[leaf] >= this.size) {
throw new Error(`Index ${leaf} is out of range`)
}
}

// This will keep track of the outdated nodes of each level.
let modifiedIndices = new Set<number>()
// First, modify the first level, which consists only of raw, un-hashed values
for (let leaf = 0; leaf < indices.length; leaf += 1) {
this._nodes[0][indices[leaf]] = leaves[leaf]
modifiedIndices.add(indices[leaf] >> 1)
}

// Now update each node of the corresponding levels
for (let level = 1; level <= this.depth; level += 1) {
const newModifiedIndices: number[] = []
for (const index of modifiedIndices) {
const leftChild = this._nodes[level - 1][2 * index]
const rightChild = this._nodes[level - 1][2 * index + 1]
this._nodes[level][index] = rightChild ? this._hash(leftChild, rightChild) : leftChild
newModifiedIndices.push(index >> 1)
}
modifiedIndices.clear()
cedoor marked this conversation as resolved.
Show resolved Hide resolved
modifiedIndices = new Set<number>(newModifiedIndices)
}
}

/**
* It generates a {@link LeanIMTMerkleProof} for a leaf of the tree.
* That proof can be verified by this tree using the same hash function.
Expand Down
90 changes: 90 additions & 0 deletions packages/lean-imt/tests/lean-imt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,96 @@ describe("Lean IMT", () => {
})
})

describe("# updateMany", () => {
it(`Should not update any leaf if one of the parameters is not defined`, () => {
const tree = new LeanIMT(poseidon, leaves)

const fun1 = () => tree.updateMany([1], undefined as any)
const fun2 = () => tree.updateMany(undefined as any, [BigInt(1)])

expect(fun1).toThrow("Parameter 'leaves' is not defined")
expect(fun2).toThrow("Parameter 'indices' is not defined")
})

it(`Should not update any leaf if the parameters are not arrays`, () => {
const tree = new LeanIMT(poseidon, leaves)

const fun1 = () => tree.updateMany([3], BigInt(1) as any)
const fun2 = () => tree.updateMany(3 as any, [BigInt(1)])

expect(fun1).toThrow("Parameter 'leaves' is not an Array instance")
expect(fun2).toThrow("Parameter 'indices' is not an Array instance")
})

it(`Should not update any leaf if the parameters are of different size`, () => {
const tree = new LeanIMT(poseidon, leaves)

const fun1 = () => tree.updateMany([1, 2, 3], [BigInt(1), BigInt(2)])
const fun2 = () => tree.updateMany([1], [])

expect(fun1).toThrow("There is no correspondence between indices and leaves")
expect(fun2).toThrow("There is no correspondence between indices and leaves")
})

it(`Should not update any leaf if some index is not a number`, () => {
const tree = new LeanIMT(poseidon, leaves)

const fun1 = () => tree.updateMany([1, "hello" as any, 3], [BigInt(1), BigInt(2), BigInt(3)])
const fun2 = () => tree.updateMany([1, 2, undefined as any], [BigInt(1), BigInt(2), BigInt(3)])

expect(fun1).toThrow("Parameter 'index 1' is not a number")
expect(fun2).toThrow("Parameter 'index 2' is not a number")
})

it(`Should not update any leaf if some index is out of range`, () => {
const tree = new LeanIMT(poseidon, leaves)

const fun1 = () => tree.updateMany([-1, 2, 3], [BigInt(1), BigInt(2), BigInt(3)])
const fun2 = () => tree.updateMany([1, 200000, 3], [BigInt(1), BigInt(2), BigInt(3)])
const fun3 = () => tree.updateMany([1, 2, tree.size], [BigInt(1), BigInt(2), BigInt(3)])

expect(fun1).toThrow("Index 0 is out of range")
expect(fun2).toThrow("Index 1 is out of range")
expect(fun3).toThrow("Index 2 is out of range")
})

it(`Should not update any leaf when passing an empty list`, () => {
const tree = new LeanIMT(poseidon, leaves)
const previousRoot = tree.root

tree.updateMany([], [])

expect(tree.root).toBe(previousRoot)
})

it(`Should updateMany with 1 change be the same as update`, () => {
const tree1 = new LeanIMT(poseidon, leaves)
const tree2 = new LeanIMT(poseidon, leaves)

tree1.update(4, BigInt(-100))
tree2.updateMany([4], [BigInt(-100)])
expect(tree1.root).toBe(tree2.root)

tree1.update(0, BigInt(24))
tree2.updateMany([0], [BigInt(24)])
expect(tree1.root).toBe(tree2.root)
})

it(`Should update leaves correctly`, () => {
const tree = new LeanIMT(poseidon, leaves)

const updateLeaves = [BigInt(24), BigInt(-10), BigInt(100000)]
tree.updateMany([0, 1, 4], updateLeaves)

const h1_0 = poseidon(updateLeaves[0], updateLeaves[1])
const h1_1 = poseidon(leaves[2], leaves[3])
const h2_0 = poseidon(h1_0, h1_1)
const updatedRoot = poseidon(h2_0, updateLeaves[2])

expect(tree.root).toBe(updatedRoot)
})
})
vplasencia marked this conversation as resolved.
Show resolved Hide resolved

describe("# generateProof", () => {
it(`Should not generate any proof if the index is not defined`, () => {
const tree = new LeanIMT(poseidon, leaves)
Expand Down
Loading