From 130b977d0317c1ac5dab868e7f4660df347a7f41 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Wed, 28 Aug 2024 17:08:25 -0300 Subject: [PATCH 01/21] feat(lean-imt): added `updateMany` method to package re #117 --- packages/lean-imt/src/lean-imt.ts | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 0a1f14d71..d4cbcb6b9 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -233,6 +233,55 @@ export default class LeanIMT { 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. + * @param leaves The list of leaves to be updated. + * @param indices The list of indices of the respective leaves. + */ + public updateMany(leaves: N[], indices: number[]) { + requireDefined(leaves, "leaves") + requireDefined(indices, "indices") + requireArray(leaves, "leaves") + requireArray(indices, "indices") + + if (leaves.length === 0) { + throw new Error("There are no leaves to modify") + } + if (leaves.length !== indices.length) { + throw new Error("There is no correspondence between indices and leaves") + } + for (let leaf = 0; leaf < indices.length; leaf += 1) { + if (indices[leaf] < 0 || indices[leaf] >= this.size) { + throw new Error(`Index ${leaf} is out of range`) + // throw new Error("Index " + leaf.toString() + " is out of range") + } + } + + // This will keep track of the outdated nodes of each level. + let modifiedIndices = new Set() + // 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() + modifiedIndices = new Set(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. From 6049ea415b04b953a297a8da6da441a1d266a07e Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Wed, 28 Aug 2024 17:38:07 -0300 Subject: [PATCH 02/21] feat(lean-imt): implemented some tests on lean-imt re #117 --- packages/lean-imt/src/lean-imt.ts | 8 +--- packages/lean-imt/tests/lean-imt.test.ts | 53 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index d4cbcb6b9..4fdc4f067 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -238,25 +238,21 @@ export default class LeanIMT { * 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. - * @param leaves The list of leaves to be updated. * @param indices The list of indices of the respective leaves. + * @param leaves The list of leaves to be updated. */ - public updateMany(leaves: N[], indices: number[]) { + public updateMany(indices: number[], leaves: N[]) { requireDefined(leaves, "leaves") requireDefined(indices, "indices") requireArray(leaves, "leaves") requireArray(indices, "indices") - if (leaves.length === 0) { - throw new Error("There are no leaves to modify") - } if (leaves.length !== indices.length) { throw new Error("There is no correspondence between indices and leaves") } for (let leaf = 0; leaf < indices.length; leaf += 1) { if (indices[leaf] < 0 || indices[leaf] >= this.size) { throw new Error(`Index ${leaf} is out of range`) - // throw new Error("Index " + leaf.toString() + " is out of range") } } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 0fb3c57e6..1c7633cd0 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -220,6 +220,59 @@ 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 fun = () => tree.updateMany([1, 2, 3], [BigInt(1), BigInt(2)]) + + expect(fun).toThrow("There is no correspondence between indices and leaves") + }) + + 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, 345], [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 update leafs correctly`, () => {}) + }) + describe("# generateProof", () => { it(`Should not generate any proof if the index is not defined`, () => { const tree = new LeanIMT(poseidon, leaves) From e00a89156fc6de79c99648727eb71edcd8650aac Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Thu, 29 Aug 2024 12:46:34 -0300 Subject: [PATCH 03/21] feat(lean-imt): added more precondition checks re #117 --- packages/lean-imt/src/lean-imt.ts | 3 ++- packages/lean-imt/tests/lean-imt.test.ts | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 4fdc4f067..c74d43b3d 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -237,7 +237,7 @@ export default class LeanIMT { * 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. + * 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. */ @@ -251,6 +251,7 @@ export default class LeanIMT { throw new Error("There is no correspondence between indices and leaves") } for (let leaf = 0; leaf < indices.length; leaf += 1) { + requireNumber(indices[leaf], `index ${leaf}`) if (indices[leaf] < 0 || indices[leaf] >= this.size) { throw new Error(`Index ${leaf} is out of range`) } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 1c7633cd0..c683c1910 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -244,9 +244,21 @@ describe("Lean IMT", () => { it(`Should not update any leaf if the parameters are of different size`, () => { const tree = new LeanIMT(poseidon, leaves) - const fun = () => tree.updateMany([1, 2, 3], [BigInt(1), BigInt(2)]) + const fun1 = () => tree.updateMany([1, 2, 3], [BigInt(1), BigInt(2)]) + const fun2 = () => tree.updateMany([1], []) - expect(fun).toThrow("There is no correspondence between indices and leaves") + 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`, () => { @@ -254,7 +266,7 @@ describe("Lean IMT", () => { 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, 345], [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") From 3e9e54bc62357a21bd3088fa09c17bf98b344640 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Thu, 29 Aug 2024 13:15:00 -0300 Subject: [PATCH 04/21] feat(lean-imt): finished testing on `updateMany` method re #117 --- packages/lean-imt/tests/lean-imt.test.ts | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index c683c1910..7ab99c5af 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -282,7 +282,32 @@ describe("Lean IMT", () => { expect(tree.root).toBe(previousRoot) }) - it(`Should update leafs correctly`, () => {}) + 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) + }) }) describe("# generateProof", () => { From cd3156eae1397b4fd9943fcca733f8924a29bb35 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sat, 31 Aug 2024 13:00:39 -0300 Subject: [PATCH 05/21] feat(lean-imt): added test to the case when passing repeated indices re #117 --- packages/lean-imt/src/lean-imt.ts | 1 - packages/lean-imt/tests/lean-imt.test.ts | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index c74d43b3d..3971b3777 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -274,7 +274,6 @@ export default class LeanIMT { this._nodes[level][index] = rightChild ? this._hash(leftChild, rightChild) : leftChild newModifiedIndices.push(index >> 1) } - modifiedIndices.clear() modifiedIndices = new Set(newModifiedIndices) } } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 7ab99c5af..4ed1d0325 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -282,7 +282,7 @@ describe("Lean IMT", () => { expect(tree.root).toBe(previousRoot) }) - it(`Should updateMany with 1 change be the same as update`, () => { + it(`'updateMany' with 1 change should be the same as 'update'`, () => { const tree1 = new LeanIMT(poseidon, leaves) const tree2 = new LeanIMT(poseidon, leaves) @@ -295,6 +295,19 @@ describe("Lean IMT", () => { expect(tree1.root).toBe(tree2.root) }) + it(`'updateMany' with repeated indices should overwrite the last update`, () => { + const tree = new LeanIMT(poseidon, leaves) + + tree.updateMany([4, 1, 4, 4], [BigInt(-100), BigInt(-17), BigInt(1), BigInt(24)]) + + const n1_0 = poseidon(leaves[0], BigInt(-17)) + const n1_1 = poseidon(leaves[2], leaves[3]) + const n2_0 = poseidon(n1_0, n1_1) + const newRoot = poseidon(n2_0, BigInt(24)) + + expect(tree.root).toBe(newRoot) + }) + it(`Should update leaves correctly`, () => { const tree = new LeanIMT(poseidon, leaves) From 06e941c26f5eb357afd51042cee498ba91f64055 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sat, 31 Aug 2024 19:36:00 -0300 Subject: [PATCH 06/21] feat(lean-imt): added complexity documentation for `updateMany` method re #117 --- packages/lean-imt/src/lean-imt.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 3971b3777..8ff709b41 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -234,10 +234,26 @@ export default class LeanIMT { } /** - * 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. + * Updates m leaves all at once. + * It is more efficient than using the {@link LeanIMT#update} method m times because + * it prevents updating middle nodes several times, which would happen when + * updating leaves with common ancestors. The worst-case time complexity + * of the naive approach of calling 'update' m times is O(m*log(n)) where n is the size + * of the tree, while this algorithm offers a complexity of O(m*(log(n)-log(m)) + m). + * This is a slight improvement, mostly when m ~ n. This makes sense because + * if we update a lot of leaves, most of them will have common ancestors. + * The proof for this would be as follows: If we have a list of m updates in + * a tree of depth d ~ log(n), let k be the smallest integer such that m <= 2^k. + * In the worst case, the paths of the m updates will pass through m different nodes + * of the level k of the tree. In other words, the m leaves to update have + * all different ancestors up to the level k (which has 2^k nodes). In this scenario, + * the number of nodes of level k or higher that need to be updated is exactly m*(d-k), + * and the number of nodes of level k-1 or lower to update is at least m/2 (because + * there are at least m/2 different ancestors of a set of m nodes) and at most + * 2*m (because there are not even 2^k of nodes among all those levels, and 2^k < 2m). + * Therefore, the number of nodes to update is at least m*(d-k) + m/2 and at most + * m*(d-k) + 2m. This gives us that the number of nodes to update, which is the most + * expensive operation of the method, is O(m*(log(n)-log(m)) + m) since k ~ log(m). * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From 4b51e44ec1d325d65c94004fefa645e7ee9c3838 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sun, 1 Sep 2024 18:28:50 -0300 Subject: [PATCH 07/21] feat(lean-imt): added test of several updates re #117 --- packages/lean-imt/src/lean-imt.ts | 8 ++++---- packages/lean-imt/tests/lean-imt.test.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 8ff709b41..49eed4163 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -266,10 +266,10 @@ export default class LeanIMT { if (leaves.length !== indices.length) { throw new Error("There is no correspondence between indices and leaves") } - for (let leaf = 0; leaf < indices.length; leaf += 1) { - requireNumber(indices[leaf], `index ${leaf}`) - if (indices[leaf] < 0 || indices[leaf] >= this.size) { - throw new Error(`Index ${leaf} is out of range`) + for (let i = 0; i < indices.length; i += 1) { + requireNumber(indices[i], `index ${i}`) + if (indices[i] < 0 || indices[i] >= this.size) { + throw new Error(`Index ${i} is out of range`) } } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 4ed1d0325..e3e9ee768 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -295,6 +295,22 @@ describe("Lean IMT", () => { expect(tree1.root).toBe(tree2.root) }) + it(`'updateMany' should be the same as executing the 'update' function multiple times`, () => { + const tree1 = new LeanIMT(poseidon, leaves) + const tree2 = new LeanIMT(poseidon, leaves) + + const indices = [0, 2, 4] + + const nodes = [BigInt(10), BigInt(11), BigInt(12)] + + for (let i = 0; i < indices.length; i += 1) { + tree1.update(indices[i], nodes[i]) + } + tree2.updateMany(indices, nodes) + + expect(tree1.root).toBe(tree2.root) + }) + it(`'updateMany' with repeated indices should overwrite the last update`, () => { const tree = new LeanIMT(poseidon, leaves) From 29638d50fef78f00db83d710e6378ab1e4817a65 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sun, 1 Sep 2024 18:36:28 -0300 Subject: [PATCH 08/21] feat(lean-imt): added repeated indices check re #117 --- packages/lean-imt/src/lean-imt.ts | 9 +++++++-- packages/lean-imt/tests/lean-imt.test.ts | 11 +++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 49eed4163..a7f27f9e6 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -266,15 +266,20 @@ export default class LeanIMT { if (leaves.length !== indices.length) { throw new Error("There is no correspondence between indices and leaves") } + // This will keep track of the outdated nodes of each level. + let modifiedIndices = new Set() for (let i = 0; i < indices.length; i += 1) { requireNumber(indices[i], `index ${i}`) if (indices[i] < 0 || indices[i] >= this.size) { throw new Error(`Index ${i} is out of range`) } + if (modifiedIndices.has(indices[i])) { + throw new Error(`Index ${indices[i]} is repeated`) + } + modifiedIndices.add(indices[i]) } - // This will keep track of the outdated nodes of each level. - let modifiedIndices = new Set() + modifiedIndices.clear() // 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] diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index e3e9ee768..007f77a78 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -311,17 +311,12 @@ describe("Lean IMT", () => { expect(tree1.root).toBe(tree2.root) }) - it(`'updateMany' with repeated indices should overwrite the last update`, () => { + it(`'updateMany' with repeated indices should fail`, () => { const tree = new LeanIMT(poseidon, leaves) - tree.updateMany([4, 1, 4, 4], [BigInt(-100), BigInt(-17), BigInt(1), BigInt(24)]) + const fun = () => tree.updateMany([4, 1, 4], [BigInt(-100), BigInt(-17), BigInt(1)]) - const n1_0 = poseidon(leaves[0], BigInt(-17)) - const n1_1 = poseidon(leaves[2], leaves[3]) - const n2_0 = poseidon(n1_0, n1_1) - const newRoot = poseidon(n2_0, BigInt(24)) - - expect(tree.root).toBe(newRoot) + expect(fun).toThrow("Index 4 is repeated") }) it(`Should update leaves correctly`, () => { From 880812c2b33b9440e09c385045bc654f4af4cfd3 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sun, 1 Sep 2024 18:49:42 -0300 Subject: [PATCH 09/21] feat(lean-imt): changed error message to be more accurate re #117 --- packages/lean-imt/src/lean-imt.ts | 2 +- packages/lean-imt/tests/lean-imt.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index a7f27f9e6..6b1f8477c 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -274,7 +274,7 @@ export default class LeanIMT { throw new Error(`Index ${i} is out of range`) } if (modifiedIndices.has(indices[i])) { - throw new Error(`Index ${indices[i]} is repeated`) + throw new Error(`Leaf ${indices[i]} is repeated`) } modifiedIndices.add(indices[i]) } diff --git a/packages/lean-imt/tests/lean-imt.test.ts b/packages/lean-imt/tests/lean-imt.test.ts index 007f77a78..38212c1c5 100644 --- a/packages/lean-imt/tests/lean-imt.test.ts +++ b/packages/lean-imt/tests/lean-imt.test.ts @@ -316,7 +316,7 @@ describe("Lean IMT", () => { const fun = () => tree.updateMany([4, 1, 4], [BigInt(-100), BigInt(-17), BigInt(1)]) - expect(fun).toThrow("Index 4 is repeated") + expect(fun).toThrow("Leaf 4 is repeated") }) it(`Should update leaves correctly`, () => { From 681239eae47c61148f3c6a2cca7adc1a98e8bb21 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Sun, 1 Sep 2024 19:16:46 -0300 Subject: [PATCH 10/21] feat(lean-imt): added complexity in terms only of n re #117 --- packages/lean-imt/src/lean-imt.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 6b1f8477c..66a635dfa 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -254,6 +254,9 @@ export default class LeanIMT { * Therefore, the number of nodes to update is at least m*(d-k) + m/2 and at most * m*(d-k) + 2m. This gives us that the number of nodes to update, which is the most * expensive operation of the method, is O(m*(log(n)-log(m)) + m) since k ~ log(m). + * If we consider the worst case of m, which is m = n, the complexity is O(n). + * This is mainly because each node will be modified at most once, and the total + * number of nodes of the tree is <= 2*n. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From 1bfffd4b21a33387fd77d0ed5b88884c9d2ec200 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Mon, 2 Sep 2024 14:18:23 -0300 Subject: [PATCH 11/21] feat(lean-imt): changed documentation to add discussion in another issue re #117 --- packages/lean-imt/src/lean-imt.ts | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 66a635dfa..e14da95b8 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -235,28 +235,12 @@ export default class LeanIMT { /** * Updates m leaves all at once. - * It is more efficient than using the {@link LeanIMT#update} method m times because - * it prevents updating middle nodes several times, which would happen when - * updating leaves with common ancestors. The worst-case time complexity - * of the naive approach of calling 'update' m times is O(m*log(n)) where n is the size - * of the tree, while this algorithm offers a complexity of O(m*(log(n)-log(m)) + m). - * This is a slight improvement, mostly when m ~ n. This makes sense because - * if we update a lot of leaves, most of them will have common ancestors. - * The proof for this would be as follows: If we have a list of m updates in - * a tree of depth d ~ log(n), let k be the smallest integer such that m <= 2^k. - * In the worst case, the paths of the m updates will pass through m different nodes - * of the level k of the tree. In other words, the m leaves to update have - * all different ancestors up to the level k (which has 2^k nodes). In this scenario, - * the number of nodes of level k or higher that need to be updated is exactly m*(d-k), - * and the number of nodes of level k-1 or lower to update is at least m/2 (because - * there are at least m/2 different ancestors of a set of m nodes) and at most - * 2*m (because there are not even 2^k of nodes among all those levels, and 2^k < 2m). - * Therefore, the number of nodes to update is at least m*(d-k) + m/2 and at most - * m*(d-k) + 2m. This gives us that the number of nodes to update, which is the most - * expensive operation of the method, is O(m*(log(n)-log(m)) + m) since k ~ log(m). - * If we consider the worst case of m, which is m = n, the complexity is O(n). - * This is mainly because each node will be modified at most once, and the total - * number of nodes of the tree is <= 2*n. + * It is more efficient than using the {@link LeanIMT#update} method m times because it + * prevents updating middle nodes several times. This would happen when updating leaves + * with common ancestors. The naive approach of calling 'update' m times has complexity + * O(m*log(n)) (where n is the number of leaves of the tree), which ends up in + * O(n*log(n)) when m ~ n. With this new approach, this ends up being O(n) because every + * node is updated at most once and there are < 2*n in the tree. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From bd67b35ecf4322af384896e41b9f2b967adb7051 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Mon, 2 Sep 2024 14:35:12 -0300 Subject: [PATCH 12/21] feat(lean-imt): fixed typo on documentation re #117 --- packages/lean-imt/src/lean-imt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index e14da95b8..251a6ac2a 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -240,7 +240,7 @@ export default class LeanIMT { * with common ancestors. The naive approach of calling 'update' m times has complexity * O(m*log(n)) (where n is the number of leaves of the tree), which ends up in * O(n*log(n)) when m ~ n. With this new approach, this ends up being O(n) because every - * node is updated at most once and there are < 2*n in the tree. + * node is updated at most once and there are < 2*n nodes in the tree. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From c5e836d4d26bd5d0c032185f134b40c89f53e3bb Mon Sep 17 00:00:00 2001 From: ChinoCribioli <54038430+ChinoCribioli@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:52:18 -0300 Subject: [PATCH 13/21] Update packages/lean-imt/src/lean-imt.ts Co-authored-by: Vivian Plasencia --- packages/lean-imt/src/lean-imt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lean-imt/src/lean-imt.ts b/packages/lean-imt/src/lean-imt.ts index 251a6ac2a..f453ff1dc 100644 --- a/packages/lean-imt/src/lean-imt.ts +++ b/packages/lean-imt/src/lean-imt.ts @@ -240,7 +240,7 @@ export default class LeanIMT { * with common ancestors. The naive approach of calling 'update' m times has complexity * O(m*log(n)) (where n is the number of leaves of the tree), which ends up in * O(n*log(n)) when m ~ n. With this new approach, this ends up being O(n) because every - * node is updated at most once and there are < 2*n nodes in the tree. + * node is updated at most once and there are around 2*n nodes in the tree. * @param indices The list of indices of the respective leaves. * @param leaves The list of leaves to be updated. */ From fe22dace5431030742cd586e15b01996f3efcd1b Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Tue, 10 Sep 2024 19:29:31 -0300 Subject: [PATCH 14/21] perf(baby-jubjub): implemented montgomery ladder --- packages/baby-jubjub/src/baby-jubjub.ts | 31 ++++++++----- packages/baby-jubjub/tests/index.test.ts | 56 ++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts index 7f523d7e8..cc56e23c3 100644 --- a/packages/baby-jubjub/src/baby-jubjub.ts +++ b/packages/baby-jubjub/src/baby-jubjub.ts @@ -70,20 +70,31 @@ export function addPoint(p1: Point, p2: Point): Point { * @returns The resulting point representing the public key. */ export function mulPointEscalar(base: Point, e: bigint): Point { - let res: Point = [Fr.e(BigInt(0)), Fr.e(BigInt(1))] - let rem: bigint = e - let exp: Point = base - - while (!scalar.isZero(rem)) { - if (scalar.isOdd(rem)) { - res = addPoint(res, exp) + console.log(e) + const eBits: Array = [] + while (!scalar.isZero(e)) { + if (scalar.isOdd(e)) { + eBits.push(true) + } else { + eBits.push(false) } + e = scalar.shiftRight(e, BigInt(1)) + } - exp = addPoint(exp, exp) - rem = scalar.shiftRight(rem, BigInt(1)) + let R0: Point = base + let R1: Point = addPoint(base, base) + + for (const bit of eBits.slice(0, -1).reverse()) { + if (bit) { + R0 = addPoint(R0, R1) + R1 = addPoint(R1, R1) + } else { + R1 = addPoint(R0, R1) + R0 = addPoint(R0, R0) + } } - return res + return R0 } /** diff --git a/packages/baby-jubjub/tests/index.test.ts b/packages/baby-jubjub/tests/index.test.ts index b1de333b9..f89a946d2 100644 --- a/packages/baby-jubjub/tests/index.test.ts +++ b/packages/baby-jubjub/tests/index.test.ts @@ -1,5 +1,6 @@ import { babyjub } from "circomlibjs" import { utils } from "ffjavascript" +import * as scalar from "@zk-kit/utils/scalar" import { Base8, Point, addPoint, inCurve, mulPointEscalar, packPoint, r, unpackPoint } from "../src" import { tonelliShanks } from "../src/sqrt" @@ -8,6 +9,61 @@ describe("BabyJubjub", () => { let publicKey: Point + it("Test curve multiplication", async () => { + const P: Point = [ + BigInt("17777552123799933955779906779655732241715742912184938656739573121738514868268"), + BigInt("2626589144620713026669568689430873010625803728049924121243784502389097019475") + ] + + expect(mulPointEscalar(P, BigInt(1)).toString()).toBe(P.toString()) + expect(mulPointEscalar(P, BigInt(2)).toString()).toBe(addPoint(P, P).toString()) + }) + + it("Test base point order", async () => { + const order = BigInt("21888242871839275222246405745257275088614511777268538073601725287587578984328") + const subOrder: bigint = BigInt("2736030358979909402780800718157159386076813972158567259200215660948447373041") + expect(scalar.shiftRight(order, BigInt(3))).toBe(subOrder) + const G: Point = [ + BigInt("995203441582195749578291179787384436505546430278305826713579947235728471134"), + BigInt("5472060717959818805561601436314318772137091100104008585924551046643952123905") + ] + const p1: Point = mulPointEscalar(G, BigInt(8) * subOrder) + expect(p1[0]).toBe(BigInt(0)) + expect(p1[1]).toBe(BigInt(1)) + const neutral = mulPointEscalar(Base8, subOrder) + expect(neutral[0]).toBe(BigInt(0)) + expect(neutral[1]).toBe(BigInt(1)) + + const random = BigInt("38275423985628165") + expect(mulPointEscalar(Base8, random)[0]).toBe(mulPointEscalar(Base8, random + BigInt(2) * subOrder)[0]) + }) + + it("Test curve implementation", async () => { + expect(inCurve([BigInt(0), BigInt(1)])).toBeTruthy() + expect(inCurve([BigInt(1), BigInt(0)])).toBeFalsy() + + const p1: Point = [ + BigInt("17777552123799933955779906779655732241715742912184938656739573121738514868268"), + BigInt("2626589144620713026669568689430873010625803728049924121243784502389097019475") + ] + const p2: Point = [ + BigInt("16540640123574156134436876038791482806971768689494387082833631921987005038935"), + BigInt("20819045374670962167435360035096875258406992893633759881276124905556507972311") + ] + const p3: Point = [ + BigInt("7916061937171219682591368294088513039687205273691143098332585753343424131937"), + BigInt("14035240266687799601661095864649209771790948434046947201833777492504781204499") + ] + expect(addPoint(p1, p2)[0].toString()).toBe(p3[0].toString()) + expect(addPoint(p1, p2)[1].toString()).toBe(p3[1].toString()) + + const id: Point = [BigInt(0), BigInt(1)] + expect(addPoint(id, id)[0].toString()).toBe(id[0].toString()) + expect(addPoint(id, id)[1].toString()).toBe(id[1].toString()) + expect(mulPointEscalar(id, BigInt(2143231423))[0].toString()).toBe(id[0].toString()) + expect(mulPointEscalar(id, BigInt(2143231423))[1].toString()).toBe(id[1].toString()) + }) + it("Should add 1 point to the curve", async () => { const p1: Point = [BigInt(0), BigInt(1)] From 6665e36308b2c6676d162a407c3483803d54ae27 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Wed, 11 Sep 2024 15:44:18 -0300 Subject: [PATCH 15/21] test(baby-jubjub): added more tests to curve basic operations --- packages/baby-jubjub/src/baby-jubjub.ts | 4 +- packages/baby-jubjub/tests/index.test.ts | 75 ++++++++++++------------ 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts index cc56e23c3..a0969f568 100644 --- a/packages/baby-jubjub/src/baby-jubjub.ts +++ b/packages/baby-jubjub/src/baby-jubjub.ts @@ -70,7 +70,9 @@ export function addPoint(p1: Point, p2: Point): Point { * @returns The resulting point representing the public key. */ export function mulPointEscalar(base: Point, e: bigint): Point { - console.log(e) + if (scalar.isZero(e)) { + return [BigInt(0), BigInt(1)] + } const eBits: Array = [] while (!scalar.isZero(e)) { if (scalar.isOdd(e)) { diff --git a/packages/baby-jubjub/tests/index.test.ts b/packages/baby-jubjub/tests/index.test.ts index f89a946d2..94c557794 100644 --- a/packages/baby-jubjub/tests/index.test.ts +++ b/packages/baby-jubjub/tests/index.test.ts @@ -1,67 +1,70 @@ import { babyjub } from "circomlibjs" import { utils } from "ffjavascript" import * as scalar from "@zk-kit/utils/scalar" -import { Base8, Point, addPoint, inCurve, mulPointEscalar, packPoint, r, unpackPoint } from "../src" +import { Base8, Point, addPoint, inCurve, mulPointEscalar, packPoint, r, unpackPoint, order, subOrder } from "../src" import { tonelliShanks } from "../src/sqrt" describe("BabyJubjub", () => { const secretScalar = BigInt(324) + const id: Point = [BigInt(0), BigInt(1)] let publicKey: Point - it("Test curve multiplication", async () => { + it("Test point addition and inCurve", async () => { + expect(inCurve(id)).toBeTruthy() + expect(inCurve([BigInt(1), BigInt(0)])).toBeFalsy() + expect(addPoint(id, id).toString()).toBe(id.toString()) + + const p1: Point = [ + BigInt("17777552123799933955779906779655732241715742912184938656739573121738514868268"), + BigInt("2626589144620713026669568689430873010625803728049924121243784502389097019475") + ] + const p2: Point = [ + BigInt("16540640123574156134436876038791482806971768689494387082833631921987005038935"), + BigInt("20819045374670962167435360035096875258406992893633759881276124905556507972311") + ] + const p3: Point = [ + BigInt("7916061937171219682591368294088513039687205273691143098332585753343424131937"), + BigInt("14035240266687799601661095864649209771790948434046947201833777492504781204499") + ] + + expect(inCurve(p1)).toBeTruthy() + expect(inCurve(p2)).toBeTruthy() + expect(inCurve(p3)).toBeTruthy() + + expect(addPoint(p1, p2).toString()).toBe(p3.toString()) + }) + + it("Test point multiplication with small values", async () => { const P: Point = [ BigInt("17777552123799933955779906779655732241715742912184938656739573121738514868268"), BigInt("2626589144620713026669568689430873010625803728049924121243784502389097019475") ] + expect(mulPointEscalar(P, BigInt(0)).toString()).toBe(id.toString()) expect(mulPointEscalar(P, BigInt(1)).toString()).toBe(P.toString()) expect(mulPointEscalar(P, BigInt(2)).toString()).toBe(addPoint(P, P).toString()) + expect(mulPointEscalar(P, BigInt(3)).toString()).toBe(addPoint(addPoint(P, P), P).toString()) + + expect(mulPointEscalar(id, BigInt(1)).toString()).toBe(id.toString()) + expect(mulPointEscalar(id, BigInt(14134324)).toString()).toBe(id.toString()) }) it("Test base point order", async () => { - const order = BigInt("21888242871839275222246405745257275088614511777268538073601725287587578984328") - const subOrder: bigint = BigInt("2736030358979909402780800718157159386076813972158567259200215660948447373041") expect(scalar.shiftRight(order, BigInt(3))).toBe(subOrder) const G: Point = [ BigInt("995203441582195749578291179787384436505546430278305826713579947235728471134"), BigInt("5472060717959818805561601436314318772137091100104008585924551046643952123905") ] const p1: Point = mulPointEscalar(G, BigInt(8) * subOrder) - expect(p1[0]).toBe(BigInt(0)) - expect(p1[1]).toBe(BigInt(1)) - const neutral = mulPointEscalar(Base8, subOrder) - expect(neutral[0]).toBe(BigInt(0)) - expect(neutral[1]).toBe(BigInt(1)) + expect(p1.toString()).toBe(id.toString()) + const p2 = mulPointEscalar(Base8, subOrder) + expect(p2.toString()).toBe(id.toString()) const random = BigInt("38275423985628165") - expect(mulPointEscalar(Base8, random)[0]).toBe(mulPointEscalar(Base8, random + BigInt(2) * subOrder)[0]) - }) - - it("Test curve implementation", async () => { - expect(inCurve([BigInt(0), BigInt(1)])).toBeTruthy() - expect(inCurve([BigInt(1), BigInt(0)])).toBeFalsy() - - const p1: Point = [ - BigInt("17777552123799933955779906779655732241715742912184938656739573121738514868268"), - BigInt("2626589144620713026669568689430873010625803728049924121243784502389097019475") - ] - const p2: Point = [ - BigInt("16540640123574156134436876038791482806971768689494387082833631921987005038935"), - BigInt("20819045374670962167435360035096875258406992893633759881276124905556507972311") - ] - const p3: Point = [ - BigInt("7916061937171219682591368294088513039687205273691143098332585753343424131937"), - BigInt("14035240266687799601661095864649209771790948434046947201833777492504781204499") - ] - expect(addPoint(p1, p2)[0].toString()).toBe(p3[0].toString()) - expect(addPoint(p1, p2)[1].toString()).toBe(p3[1].toString()) - - const id: Point = [BigInt(0), BigInt(1)] - expect(addPoint(id, id)[0].toString()).toBe(id[0].toString()) - expect(addPoint(id, id)[1].toString()).toBe(id[1].toString()) - expect(mulPointEscalar(id, BigInt(2143231423))[0].toString()).toBe(id[0].toString()) - expect(mulPointEscalar(id, BigInt(2143231423))[1].toString()).toBe(id[1].toString()) + expect(mulPointEscalar(Base8, random).toString()).toBe( + mulPointEscalar(Base8, random + BigInt(543523) * subOrder).toString() + ) }) it("Should add 1 point to the curve", async () => { From fc0f3b8e78d378e063e49658bcd0986f3b8a0c55 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Wed, 11 Sep 2024 16:11:05 -0300 Subject: [PATCH 16/21] docs(baby-jubjub): documented montogmery ladder --- packages/baby-jubjub/src/baby-jubjub.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts index a0969f568..ed209198b 100644 --- a/packages/baby-jubjub/src/baby-jubjub.ts +++ b/packages/baby-jubjub/src/baby-jubjub.ts @@ -65,6 +65,11 @@ export function addPoint(p1: Point, p2: Point): Point { /** * Performs a scalar multiplication by starting from the 'base' point and 'adding' * it to itself 'e' times. + * This works given the following invariant: At each step, R0 will be r_0*base where r_0 is the prefix of e + * written in binary and R1 will be (r_0+1)*base. In other words: at iteration i of the loop, r_0's binary + * representation will be the first i+1 bits of e. If the upcoming bit is a 0, we just have to double R0 + * and add R0 to R1 to maintain the invariant. If it is a 1, we have to double R0 and add 1*base + * (or add R1, which is the same as (r_0+1)*base), and double R1 to maintain the invariant. * @param base The base point used as a starting point. * @param e A secret number representing the private key. * @returns The resulting point representing the public key. From 8127d10867d05768099a81413b7a4ce6a2f77b3d Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Wed, 11 Sep 2024 16:56:40 -0300 Subject: [PATCH 17/21] docs(baby-jubjub): fixed documentation re #324 --- packages/baby-jubjub/src/baby-jubjub.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts index ed209198b..f17c0b919 100644 --- a/packages/baby-jubjub/src/baby-jubjub.ts +++ b/packages/baby-jubjub/src/baby-jubjub.ts @@ -67,8 +67,8 @@ export function addPoint(p1: Point, p2: Point): Point { * it to itself 'e' times. * This works given the following invariant: At each step, R0 will be r_0*base where r_0 is the prefix of e * written in binary and R1 will be (r_0+1)*base. In other words: at iteration i of the loop, r_0's binary - * representation will be the first i+1 bits of e. If the upcoming bit is a 0, we just have to double R0 - * and add R0 to R1 to maintain the invariant. If it is a 1, we have to double R0 and add 1*base + * representation will be the first i+1 most significant bits of e. If the upcoming bit is a 0, we just have to + * double R0 and add R0 to R1 to maintain the invariant. If it is a 1, we have to double R0 and add 1*base * (or add R1, which is the same as (r_0+1)*base), and double R1 to maintain the invariant. * @param base The base point used as a starting point. * @param e A secret number representing the private key. From 3c122c5fc256b2567c5835ad950ba3c3228d3c0c Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Thu, 12 Sep 2024 16:25:03 -0300 Subject: [PATCH 18/21] docs(baby-jubjub): added resource of Montgomery Ladder re #324 --- packages/baby-jubjub/src/baby-jubjub.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts index f17c0b919..f0f654344 100644 --- a/packages/baby-jubjub/src/baby-jubjub.ts +++ b/packages/baby-jubjub/src/baby-jubjub.ts @@ -65,6 +65,7 @@ export function addPoint(p1: Point, p2: Point): Point { /** * Performs a scalar multiplication by starting from the 'base' point and 'adding' * it to itself 'e' times. + * This algorithm is called 'Montgomery Ladder'. See {@link https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder} * This works given the following invariant: At each step, R0 will be r_0*base where r_0 is the prefix of e * written in binary and R1 will be (r_0+1)*base. In other words: at iteration i of the loop, r_0's binary * representation will be the first i+1 most significant bits of e. If the upcoming bit is a 0, we just have to From 56fcd9f62a43538ee4152b1be4f2b176bf3e17b0 Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Thu, 12 Sep 2024 17:09:02 -0300 Subject: [PATCH 19/21] fix(baby-jubjub): replaced undetermined while with hardcoded for loop re #324 --- packages/baby-jubjub/src/baby-jubjub.ts | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts index f0f654344..2b8051a7d 100644 --- a/packages/baby-jubjub/src/baby-jubjub.ts +++ b/packages/baby-jubjub/src/baby-jubjub.ts @@ -76,24 +76,14 @@ export function addPoint(p1: Point, p2: Point): Point { * @returns The resulting point representing the public key. */ export function mulPointEscalar(base: Point, e: bigint): Point { - if (scalar.isZero(e)) { - return [BigInt(0), BigInt(1)] - } - const eBits: Array = [] - while (!scalar.isZero(e)) { - if (scalar.isOdd(e)) { - eBits.push(true) - } else { - eBits.push(false) - } - e = scalar.shiftRight(e, BigInt(1)) - } + e %= order - let R0: Point = base - let R1: Point = addPoint(base, base) + let R0: Point = [0n, 1n] + let R1: Point = base - for (const bit of eBits.slice(0, -1).reverse()) { - if (bit) { + // 'order' is a number of 254 bits, such as 1n<<253n. Therefore, we initialize the mask as 1<<253 + for (let mask = 1n << 253n; mask > 0; mask >>= 1n) { + if (e & mask) { R0 = addPoint(R0, R1) R1 = addPoint(R1, R1) } else { From b7dec5b0195200729272eca16ab68c746f16046e Mon Sep 17 00:00:00 2001 From: ChinoCribioli Date: Thu, 12 Sep 2024 19:01:32 -0300 Subject: [PATCH 20/21] refactor(baby-jubjub): created and exported identity point --- packages/baby-jubjub/src/baby-jubjub.ts | 4 +++- packages/baby-jubjub/tests/index.test.ts | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts index 2b8051a7d..703bffed1 100644 --- a/packages/baby-jubjub/src/baby-jubjub.ts +++ b/packages/baby-jubjub/src/baby-jubjub.ts @@ -26,6 +26,8 @@ export const Base8: Point = [ const a = Fr.e(BigInt("168700")) const d = Fr.e(BigInt("168696")) +export const id: Point = [0n, 1n] + // The Baby JubJub curve 'E(F_r)' is equal to the subgroup of 'F_r'-rational points of 'E'. export const order = BigInt("21888242871839275222246405745257275088614511777268538073601725287587578984328") export const subOrder = scalar.shiftRight(order, BigInt(3)) @@ -78,7 +80,7 @@ export function addPoint(p1: Point, p2: Point): Point { export function mulPointEscalar(base: Point, e: bigint): Point { e %= order - let R0: Point = [0n, 1n] + let R0: Point = id let R1: Point = base // 'order' is a number of 254 bits, such as 1n<<253n. Therefore, we initialize the mask as 1<<253 diff --git a/packages/baby-jubjub/tests/index.test.ts b/packages/baby-jubjub/tests/index.test.ts index 94c557794..ab51707d7 100644 --- a/packages/baby-jubjub/tests/index.test.ts +++ b/packages/baby-jubjub/tests/index.test.ts @@ -1,12 +1,23 @@ import { babyjub } from "circomlibjs" import { utils } from "ffjavascript" import * as scalar from "@zk-kit/utils/scalar" -import { Base8, Point, addPoint, inCurve, mulPointEscalar, packPoint, r, unpackPoint, order, subOrder } from "../src" +import { + Base8, + Point, + addPoint, + inCurve, + mulPointEscalar, + packPoint, + r, + unpackPoint, + order, + subOrder, + id +} from "../src" import { tonelliShanks } from "../src/sqrt" describe("BabyJubjub", () => { const secretScalar = BigInt(324) - const id: Point = [BigInt(0), BigInt(1)] let publicKey: Point From f0ad8e177a962f2dd86994121e3ee798f78ed3d0 Mon Sep 17 00:00:00 2001 From: ChinoCribioli <54038430+ChinoCribioli@users.noreply.github.com> Date: Thu, 12 Sep 2024 21:36:01 -0300 Subject: [PATCH 21/21] Update packages/baby-jubjub/src/baby-jubjub.ts Co-authored-by: Chance --- packages/baby-jubjub/src/baby-jubjub.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/baby-jubjub/src/baby-jubjub.ts b/packages/baby-jubjub/src/baby-jubjub.ts index 703bffed1..e6304b4d2 100644 --- a/packages/baby-jubjub/src/baby-jubjub.ts +++ b/packages/baby-jubjub/src/baby-jubjub.ts @@ -79,6 +79,10 @@ export function addPoint(p1: Point, p2: Point): Point { */ export function mulPointEscalar(base: Point, e: bigint): Point { e %= order + // set a bit above the maximum value so that the exponent + // variable will always be 254 bits for subsequent operations + // the 254th bit should be ignored in any operations below + e += 1n << 254n let R0: Point = id let R1: Point = base