From 66b1a5703582eca76dfba7573dcb0d93ca194771 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Mar 2023 10:47:17 +0200 Subject: [PATCH 1/5] improve merkle hashing by 1 constraint per level --- src/lib/merkle_tree.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/lib/merkle_tree.ts b/src/lib/merkle_tree.ts index bb0e9e0d99..ee9ba1fc47 100644 --- a/src/lib/merkle_tree.ts +++ b/src/lib/merkle_tree.ts @@ -9,6 +9,9 @@ import { Bool, Field } from './core.js'; // external API export { Witness, MerkleTree, MerkleWitness, BaseMerkleWitness }; +// internal API +export { maybeSwap, maybeSwapBad }; + type Witness = { isLeft: boolean; sibling: Field }[]; /** @@ -181,8 +184,8 @@ class BaseMerkleWitness extends CircuitValue { let n = this.height(); for (let i = 1; i < n; ++i) { - const left = Circuit.if(this.isLeft[i - 1], hash, this.path[i - 1]); - const right = Circuit.if(this.isLeft[i - 1], this.path[i - 1], hash); + let isLeft = this.isLeft[i - 1]; + const [left, right] = maybeSwap(isLeft, hash, this.path[i - 1]); hash = Poseidon.hash([left, right]); } @@ -220,3 +223,17 @@ function MerkleWitness(height: number): typeof BaseMerkleWitness { arrayProp(Bool, height - 1)(MerkleWitness_.prototype, 'isLeft'); return MerkleWitness_; } + +function maybeSwapBad(b: Bool, x: Field, y: Field): [Field, Field] { + const x_ = Circuit.if(b, x, y); // y + b*(x - y) + const y_ = Circuit.if(b, y, x); // x + b*(y - x) + return [x_, y_]; +} + +// more efficient version of `maybeSwapBad` which reuses an intermediate variable +function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] { + let m = b.toField().mul(x.sub(y)); // b*(x - y) + const x_ = y.add(m); // y + b*(x - y) + const y_ = x.sub(m); // x - b*(x - y) = x + b*(y - x) + return [x_, y_]; +} From d069c8cc61564f63e39607076a04eb386bc96d50 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Mar 2023 10:47:26 +0200 Subject: [PATCH 2/5] add test for new helper function --- src/lib/merkle-tree.unit-test.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/lib/merkle-tree.unit-test.ts diff --git a/src/lib/merkle-tree.unit-test.ts b/src/lib/merkle-tree.unit-test.ts new file mode 100644 index 0000000000..1c32fe30bf --- /dev/null +++ b/src/lib/merkle-tree.unit-test.ts @@ -0,0 +1,25 @@ +import { Bool, Field } from './core.js'; +import { maybeSwap, maybeSwapBad } from './merkle_tree.js'; +import { Random, test } from './testing/property.js'; +import { expect } from 'expect'; +import { isReady } from '../snarky.js'; + +await isReady; + +test(Random.bool, Random.field, Random.field, (b, x, y) => { + let [x0, y0] = maybeSwap(Bool(!!b), Field(x), Field(y)); + let [x1, y1] = maybeSwapBad(Bool(!!b), Field(x), Field(y)); + + // both versions of `maybeSwap` should behave the same + expect(x0).toEqual(x1); + expect(y0).toEqual(y1); + + // if the boolean is true, it shouldn't swap the fields; otherwise, it should + if (b) { + expect(x0.toBigInt()).toEqual(x); + expect(y0.toBigInt()).toEqual(y); + } else { + expect(x0.toBigInt()).toEqual(y); + expect(y0.toBigInt()).toEqual(x); + } +}); From fb38fe5418275566bbeb6bff267e1dc0208bface Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Mar 2023 10:54:05 +0200 Subject: [PATCH 3/5] fix changelog --- CHANGELOG.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de355089c6..64129d29ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,20 +17,19 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/4573252d...HEAD) -### Breaking changes - -- Change type of verification key returned by `SmartContract.compile()` to match `VerificationKey` https://github.com/o1-labs/snarkyjs/pull/812 +> There are no unreleased changes yet -### Fixed +## [0.9.5](https://github.com/o1-labs/snarkyjs/compare/21de489...4573252d) -- Update the zkApp verification key from within one of its own methods, via proof https://github.com/o1-labs/snarkyjs/pull/812 +### Breaking changes -## [0.9.5](https://github.com/o1-labs/snarkyjs/compare/21de489...4573252d) +- Change type of verification key returned by `SmartContract.compile()` to match `VerificationKey` https://github.com/o1-labs/snarkyjs/pull/812 ### Fixed - Failing `Mina.transaction` on Berkeley because of unsatisfied constraints caused by dummy data before we fetched account state https://github.com/o1-labs/snarkyjs/pull/807 - Previously, you could work around this by calling `fetchAccount()` for every account invovled in a transaction. This is not necessary anymore. +- Update the zkApp verification key from within one of its own methods, via proof https://github.com/o1-labs/snarkyjs/pull/812 ## [0.9.4](https://github.com/o1-labs/snarkyjs/compare/9acec55...21de489) From 3a0fb039bcce846ac3f5c60d0912c6cf25c48803 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Mar 2023 11:41:11 +0200 Subject: [PATCH 4/5] acknowledge circuit changes and provide a non-breaking option --- CHANGELOG.md | 6 +++++- src/examples/zkapps/voting/membership.ts | 4 ++-- src/examples/zkapps/voting/voting.ts | 2 +- src/lib/merkle_tree.ts | 17 +++++++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64129d29ba..3d09387246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/4573252d...HEAD) -> There are no unreleased changes yet +### Breaking changes + +- Improve number of constraints needed for Merkle tree hashing https://github.com/o1-labs/snarkyjs/pull/820 + - This breaks deployed zkApps which use `MerkleWitness.calculateRoot()`, because the circuit is changed + - You can make your existing contracts compatible again by switching to `MerkleWitness.calculateRootSlow()`, which has the old circuit ## [0.9.5](https://github.com/o1-labs/snarkyjs/compare/21de489...4573252d) diff --git a/src/examples/zkapps/voting/membership.ts b/src/examples/zkapps/voting/membership.ts index 3afd45b718..3407ff4df2 100644 --- a/src/examples/zkapps/voting/membership.ts +++ b/src/examples/zkapps/voting/membership.ts @@ -151,7 +151,7 @@ export class Membership_ extends SmartContract { this.committedMembers.assertEquals(committedMembers); return member.witness - .calculateRoot(member.getHash()) + .calculateRootSlow(member.getHash()) .equals(committedMembers); } @@ -187,7 +187,7 @@ export class Membership_ extends SmartContract { // otherwise, we simply return the unmodified state - this is our way of branching return Circuit.if( isRealMember, - action.witness.calculateRoot(action.getHash()), + action.witness.calculateRootSlow(action.getHash()), state ); }, diff --git a/src/examples/zkapps/voting/voting.ts b/src/examples/zkapps/voting/voting.ts index 735c899f5f..ebb12fd8ea 100644 --- a/src/examples/zkapps/voting/voting.ts +++ b/src/examples/zkapps/voting/voting.ts @@ -279,7 +279,7 @@ export class Voting_ extends SmartContract { // apply one vote action = action.addVote(); // this is the new root after we added one vote - return action.votesWitness.calculateRoot(action.getHash()); + return action.votesWitness.calculateRootSlow(action.getHash()); }, // initial state { state: committedVotes, actionsHash: accumulatedVotes } diff --git a/src/lib/merkle_tree.ts b/src/lib/merkle_tree.ts index ee9ba1fc47..316ffaef5f 100644 --- a/src/lib/merkle_tree.ts +++ b/src/lib/merkle_tree.ts @@ -192,6 +192,23 @@ class BaseMerkleWitness extends CircuitValue { return hash; } + /** + * Calculates a root depending on the leaf value. + * @deprecated This is a less efficient version of {@link calculateRoot} which was added for compatibility with existing deployed contracts + */ + calculateRootSlow(leaf: Field): Field { + let hash = leaf; + let n = this.height(); + + for (let i = 1; i < n; ++i) { + let isLeft = this.isLeft[i - 1]; + const [left, right] = maybeSwapBad(isLeft, hash, this.path[i - 1]); + hash = Poseidon.hash([left, right]); + } + + return hash; + } + /** * Calculates the index of the leaf node that belongs to this Witness. * @returns Index of the leaf. From 816e05c727a7b5982872119a469f94842730c22d Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 30 Mar 2023 12:26:50 +0200 Subject: [PATCH 5/5] shutdown -.- --- src/lib/merkle-tree.unit-test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/merkle-tree.unit-test.ts b/src/lib/merkle-tree.unit-test.ts index 1c32fe30bf..53a57f0659 100644 --- a/src/lib/merkle-tree.unit-test.ts +++ b/src/lib/merkle-tree.unit-test.ts @@ -2,7 +2,7 @@ import { Bool, Field } from './core.js'; import { maybeSwap, maybeSwapBad } from './merkle_tree.js'; import { Random, test } from './testing/property.js'; import { expect } from 'expect'; -import { isReady } from '../snarky.js'; +import { isReady, shutdown } from '../snarky.js'; await isReady; @@ -23,3 +23,5 @@ test(Random.bool, Random.field, Random.field, (b, x, y) => { expect(y0.toBigInt()).toEqual(x); } }); + +shutdown();