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

Move low-level operations to JS #967

Merged
merged 24 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/3fbd9678e...HEAD)

> no unreleased changes yet
### Breaking changes

- `Group` operations now generate a different set of constraints. This breaks deployed contracts, because the circuit changed. https://github.com/o1-labs/snarkyjs/pull/967

## [0.11.0](https://github.com/o1-labs/snarkyjs/compare/a632313a...3fbd9678e)

Expand Down
2 changes: 1 addition & 1 deletion src/bindings
2 changes: 1 addition & 1 deletion src/examples/primitive_constraint_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const GroupMock = {
},
scale() {
let g1 = Provable.witness(Group, () => Group.generator);
let s = Provable.witness(Scalar, () => Scalar.fromBigInt(5n));
let s = Provable.witness(Scalar, () => Scalar.from(5n));
g1.scale(s);
},
equals() {
Expand Down
20 changes: 10 additions & 10 deletions src/examples/regression_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,24 +140,24 @@
"digest": "Group Primitive",
"methods": {
"add": {
"rows": 6,
"digest": "ef763443bed39d397cb02e683ea9a877"
"rows": 33,
"digest": "0cc19b3dc379f8706aedfd731f08145f"
},
"sub": {
"rows": 6,
"digest": "329c0221e60d62aaf26ee1631f3f3c5a"
"rows": 34,
"digest": "ff55b39b569856e1c2025cae39d19e38"
},
"scale": {
"rows": 106,
"digest": "1296dd40b232fb0bc4cd908d47fd88a9"
"rows": 114,
"digest": "17d1f373892d2c437dc9ce340e17e3d6"
},
"equals": {
"rows": 27,
"digest": "adbcdc756d41853e24612e50b99be285"
"rows": 42,
"digest": "8d5ab248aa4602e6f77a8ab1015615df"
Comment on lines +155 to +156
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain this blowup in Group.equals() constraints @Trivo25?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it just caused by the added constraints in check()?

Copy link
Member Author

@Trivo25 Trivo25 Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Group Primitive.equals check bundles a couple additional equality checks than just a simple equals(), it does the following:

    let g1 = Provable.witness(Group, () => Group.generator);
    let g2 = Provable.witness(Group, () => Group.generator);
    g1.equals(g2).assertTrue();
    g1.equals(g2).assertFalse();
    g1.equals(g2).assertEquals(true);
    g1.equals(g2).assertEquals(false);

So its calling equals multiple times, which blows up the constraints a bit more. The single equals constraints aren't that bad, just inflated by the batch. The additional constraints come from zero-related checks (such as .check)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I understand that it has more, but the only thing that changed in that circuit should be check() and equals(), so I was wondering what explains the increase in circuit size compared to the previous version

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, its the change in check

},
"assertions": {
"rows": 4,
"digest": "8750eb002126017bc297e7b63d4429b9"
"rows": 20,
"digest": "f4be74b3b20a0f3c3279f0def39c6b35"
}
},
"verificationKey": {
Expand Down
208 changes: 188 additions & 20 deletions src/lib/group.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
import {
shutdown,
isReady,
Field,
Bool,
Circuit,
Group,
Scalar,
Provable,
} from 'snarkyjs';
import { Bool, Group, Scalar, Provable } from 'snarkyjs';

describe('group', () => {
let g = Group({
x: -1,
y: 2,
});
beforeAll(async () => {
await isReady;
});

afterAll(async () => {
setTimeout(async () => {
await shutdown();
}, 0);
});

describe('Inside circuit', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use the quickcheck generator Gregor made earlier to check some of the algebraic properties for edge cases?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that as well, however, I think there is no easy way to sample valid Group elements other than brute forcing (or is there?) that's why the existing tests mostly work with the generator and (-1, 2)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used to sample public keys (= group elements) by picking a random private key (= scalar) and scaling the group generator with it.
That's not super efficient but good enough.

However, now we have hashToCurve which should be faster, especially if you skip the hash and only do random field element + mapToCurve

Copy link
Member Author

@Trivo25 Trivo25 Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, I didn't consider that at all 😵‍💫 I'll add some more tests, just to be sure!

describe('group membership', () => {
it('valid element does not throw', () => {
expect(() => {
Provable.runAndCheck(() => {
Provable.witness(Group, () => g);
});
}).not.toThrow();
});

it('valid element does not throw', () => {
expect(() => {
Provable.runAndCheck(() => {
Provable.witness(Group, () => Group.generator);
});
}).not.toThrow();
});

it('Group.zero element does not throw', () => {
expect(() => {
Provable.runAndCheck(() => {
Provable.witness(Group, () => Group.zero);
});
}).not.toThrow();
});

it('invalid group element throws', () => {
expect(() => {
Provable.runAndCheck(() => {
Provable.witness(Group, () => Group({ x: 2, y: 2 }));
});
}).toThrow();
});
});

describe('add', () => {
it('g+g does not throw', () => {
expect(() => {
Expand All @@ -35,6 +51,55 @@ describe('group', () => {
});
}).not.toThrow();
});

it('g+zero = g', () => {
expect(() => {
Provable.runAndCheck(() => {
const x = Provable.witness(Group, () => g);
const zero = Provable.witness(Group, () => Group.zero);
x.add(zero).assertEquals(x);
});
}).not.toThrow();
});

it('zero+g = g', () => {
expect(() => {
Provable.runAndCheck(() => {
const x = Provable.witness(Group, () => g);
const zero = Provable.witness(Group, () => Group.zero);
zero.add(x).assertEquals(x);
});
}).not.toThrow();
});

it('g+(-g) = zero', () => {
expect(() => {
Provable.runAndCheck(() => {
const x = Provable.witness(Group, () => g);
const zero = Provable.witness(Group, () => Group.zero);
x.add(x.neg()).assertEquals(zero);
Trivo25 marked this conversation as resolved.
Show resolved Hide resolved
});
}).not.toThrow();
});

it('(-g)+g = zero', () => {
expect(() => {
Provable.runAndCheck(() => {
const x = Provable.witness(Group, () => g);
const zero = Provable.witness(Group, () => Group.zero);
x.neg().add(x).assertEquals(zero);
});
}).not.toThrow();
});
Trivo25 marked this conversation as resolved.
Show resolved Hide resolved

it('zero + zero = zero', () => {
expect(() => {
Provable.runAndCheck(() => {
const zero = Provable.witness(Group, () => Group.zero);
zero.add(zero).assertEquals(zero);
});
}).not.toThrow();
});
});

describe('sub', () => {
Expand All @@ -49,6 +114,35 @@ describe('group', () => {
});
}).not.toThrow();
});

it('g-zero = g', () => {
expect(() => {
Provable.runAndCheck(() => {
const x = Provable.witness(Group, () => g);
const zero = Provable.witness(Group, () => Group.zero);
x.sub(zero).assertEquals(x);
});
}).not.toThrow();
});

it('zero - g = -g', () => {
expect(() => {
Provable.runAndCheck(() => {
const x = Provable.witness(Group, () => g);
const zero = Provable.witness(Group, () => Group.zero);
zero.sub(x).assertEquals(x.neg());
});
}).not.toThrow();
});

it('zero - zero = zero', () => {
expect(() => {
Provable.runAndCheck(() => {
const zero = Provable.witness(Group, () => Group.zero);
zero.sub(zero).assertEquals(zero);
});
}).not.toThrow();
});
});

describe('neg', () => {
Expand All @@ -60,6 +154,15 @@ describe('group', () => {
});
}).not.toThrow();
});

it('neg(zero) = zero', () => {
expect(() => {
Provable.runAndCheck(() => {
const zero = Provable.witness(Group, () => Group.zero);
zero.neg().assertEquals(zero);
});
}).not.toThrow();
});
});

describe('scale', () => {
Expand Down Expand Up @@ -161,6 +264,13 @@ describe('group', () => {
g.neg();
}).not.toThrow();
});

it('zero.neg = zero', () => {
expect(() => {
const zero = Group.zero;
zero.neg().assertEquals(zero);
}).not.toThrow();
});
});

describe('add', () => {
Expand All @@ -169,6 +279,41 @@ describe('group', () => {
g.add(g);
}).not.toThrow();
});

it('g + zero = g', () => {
expect(() => {
const zero = Group.zero;
g.add(zero).assertEquals(g);
}).not.toThrow();
});

it('zero + g = g', () => {
expect(() => {
const zero = Group.zero;
zero.add(g).assertEquals(g);
}).not.toThrow();
});

it('g + (-g) = zero', () => {
expect(() => {
const zero = Group.zero;
g.add(g.neg()).assertEquals(zero);
}).not.toThrow();
});

it('(-g) + g = zero', () => {
expect(() => {
const zero = Group.zero;
g.neg().add(g).assertEquals(zero);
}).not.toThrow();
});

it('zero + zero = zero', () => {
expect(() => {
const zero = Group.zero;
zero.add(zero).assertEquals(zero);
}).not.toThrow();
});
});

describe('sub', () => {
Expand All @@ -177,6 +322,27 @@ describe('group', () => {
Group.generator.sub(g);
}).not.toThrow();
});

it('g - zero = g', () => {
expect(() => {
const zero = Group.zero;
g.sub(zero).assertEquals(g);
}).not.toThrow();
});

it('zero - g = -g', () => {
expect(() => {
const zero = Group.zero;
zero.sub(g).assertEquals(g.neg());
}).not.toThrow();
});

it('zero - zero = -zero', () => {
expect(() => {
const zero = Group.zero;
zero.sub(zero).assertEquals(zero);
}).not.toThrow();
});
});

describe('scale', () => {
Expand Down Expand Up @@ -231,18 +397,20 @@ describe('group', () => {
y.assertEquals(z);
});
});

it('sub', () => {
let y = Provable.witness(Group, () => g).sub(
Provable.witness(Group, () => Group.generator)
);
let z = g.sub(Group.generator);
y.assertEquals(z);
});

it('sub', () => {
let y = Provable.witness(Group, () => g).assertEquals(
Provable.witness(Group, () => g)
);
let z = g.assertEquals(g);
g.assertEquals(g);
});
});
});
Loading