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 Group to JavaScript #932

Merged
merged 41 commits into from
Jun 8, 2023
Merged

Move Group to JavaScript #932

merged 41 commits into from
Jun 8, 2023

Conversation

Trivo25
Copy link
Member

@Trivo25 Trivo25 commented May 28, 2023

This PR aims to move the Group implementation from OCaml to JavaScript, reducing OCaml dependencies as best as possible. The new Group implementation covers the same API surface as the old one and produces the exact same constraints (important for compatibility with already existent projects)

This will allow us to easier maintain more of the SnarkyJS elements by relying less on OCaml as well as giving us more control over what we are doing. Additionally, in the future this will enable a snarkyjs/light version by relying on native JS arithmetic instead of requiring users to load a lot of bindings for simple calculations -- builds on top of #902 and is motivated by the same reasons

o1-labs/o1js-bindings#25

closes #924

Base automatically changed from refactor/field to main May 31, 2023 14:14
@Trivo25 Trivo25 marked this pull request as ready for review June 1, 2023 16:16
@@ -0,0 +1,66 @@
import { Field, Group, Poseidon, Provable, Scalar } from 'snarkyjs';

function mock(obj: { [K: string]: (...args: any) => void }, name: string) {
Copy link
Member Author

Choose a reason for hiding this comment

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

We didn't really have any regression tests that tested Group and its constraints, I just hacked this mock contract-like thing together to integrate it into the contract regression tests

};
}

const GroupMock = {
Copy link
Member Author

Choose a reason for hiding this comment

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

this one adds some "tests" for Group constraints


// usage ./run ./src/examples/vk_regression.ts --bundle --dump ./src/examples/regression_test.json
let dump = process.argv[4] === '--dump';
let jsonPath = process.argv[dump ? 5 : 4];

const Contracts: (typeof SmartContract)[] = [
type MinimumConstraintSystem = {
Copy link
Member Author

Choose a reason for hiding this comment

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

some minor changes to accommodate the Group constraint tests, see above

* Coerces anything group-like to a {@link Group}.
*/
constructor(g: {
x: FieldVar | Field | number | string | bigint;
Copy link
Member Author

Choose a reason for hiding this comment

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

Using the overloaded constructor so that the new implementation is equivalent to the old OCaml one, which allowed both (x, y) as well as {x, y} as arguments. I am not using the FieldLike or GroupLike type from above so users arent confused by it and see "the actual type".

Copy link
Member

Choose a reason for hiding this comment

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

is this a spot where you can use satisfies to assert it's the same as FieldLike? Would add nice info to the vscode hints too

Copy link
Member Author

@Trivo25 Trivo25 Jun 5, 2023

Choose a reason for hiding this comment

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

I don't think so (or at least I am unsure?) plus I actually just pushed a change that doesnt overload the constructor since that didn't work out with the toFunctionConstructor we use now so we dont need that anymore

src/lib/group.ts Outdated

export { Group };

type FieldLike = FieldVar | Field | number | string | bigint;
Copy link
Member Author

Choose a reason for hiding this comment

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

just to make the internal code a bit more readable

src/lib/group.ts Outdated Show resolved Hide resolved
src/lib/group.ts Outdated
let equal_y = y1.equals(y2);
return equal_x.and(equal_y);*/

let z = Snarky.group.equals(Group.#toTuple(this), Group.#toTuple(g));
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 am still trying to figure out why this isn't producing the same set of constraints:

I tried implementing it with x1.equals(x2).and(y1.equals(y2)) - which is (at least to my knowledge) equaivalent to the OCaml implementation of

Boolean.all [ Impl.Field.equal x1 x2; Impl.Field.equal y1 y2 ]
where Boolean.all just calls b1 && b2 for tuples. it produces the same amount of rows/gates but with slightly different coefficients?

src/lib/group.ts Outdated
return Group.#fromProjective(g_proj);
}
} else {
let [, x, y] = Snarky.group.add(Group.#toTuple(this), Group.#toTuple(g));
Copy link
Member Author

@Trivo25 Trivo25 Jun 2, 2023

Choose a reason for hiding this comment

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

I would like to move this entirely to JavaScript, but wanted to establish a baseline first. I will stack another PR with the detailed implementation on top of this one soon

src/lib/group.ts Outdated
let g_proj = Pallas.scale(this.#toAffine(), BigInt(scalar.toJSON()));
return Group.#fromProjective(g_proj);
} else {
let [, x, y] = Snarky.group.scale(Group.#toTuple(this), [
Copy link
Member Author

Choose a reason for hiding this comment

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

similar to the comment above

@Trivo25 Trivo25 changed the title refactor group WIP Move Group to JavaScript Jun 2, 2023
let { x: x2, y: y2 } = g;

x1.assertEquals(x2, message);
// y1.assertEquals(y2, message); need to enable this later on, but it breaks constraint backwards compatibility
Copy link
Member Author

Choose a reason for hiding this comment

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

The OCaml implementation only checked if the x coordinates are equal, allowing devs to prove g1 === g2 even if g1.y !== g2.y

src/lib/group.ts Outdated
) {
let x, y: FieldLike;

if (isGroupLike(arg1)) {
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 feel like this is a bit hacky, if anyone has recommendations let me know :D

Copy link
Member

@mitschabaude mitschabaude left a comment

Choose a reason for hiding this comment

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

Awesome work, I like how this keeps the OCaml API slim.

A few comments, most notably regarding consistent treatment of the zero Group element. I haven't looked up myself how the in-snark OCaml methods treat zero, so I'll leave it to you to identify a consistent way of doing it (and ideally document it)

src/lib/core.ts Outdated Show resolved Hide resolved
src/examples/regression_test.json Show resolved Hide resolved
src/lib/group.test.ts Show resolved Hide resolved
src/lib/group.test.ts Outdated Show resolved Hide resolved
src/lib/group.ts Outdated Show resolved Hide resolved
} else {
let [, x, y] = Snarky.group.scale(this.#toTuple(), [
0,
...fields.map((f) => f.value).reverse(),
Copy link
Member

Choose a reason for hiding this comment

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

while I appreciate keeping the Snarky interface low-level and having logic like reversing the scalar bits on the JS side, it would be good if a doccomment on Snarky.group.scale documented this unexpected behaviour

src/lib/group.ts Outdated Show resolved Hide resolved
src/lib/group.ts Show resolved Hide resolved
/**
* Adds a {@link Group} element to another one.
*/
static add(g1: Group, g2: Group) {
Copy link
Member

Choose a reason for hiding this comment

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

IMO all these extra static methods are weird and they should either be deprecated or be present consistently on all classes, i.e. added to Field for example

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 am also not a massive fan, I would rather deprecate them

Copy link
Member

Choose a reason for hiding this comment

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

We can always recreate them with bind anyway -- JS devs are used to this; so I agree we should deprecate

src/snarky.d.ts Outdated Show resolved Hide resolved
@mitschabaude
Copy link
Member

note: this PR has some conflicts with the Scalar move. I prepared a branch refactor/group-and-scalar (snarkyjs + bindings) which combines the two, that can be merged into whichever of the two PRs gets merged second

Copy link
Member

@bkase bkase left a comment

Choose a reason for hiding this comment

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

This great!

There are a lot of subtle security gotchas in this sort of code -- as a side-effect of moving more of our logic to TypeScript, it becomes more likely that auditors will be productive reviewing this kind of code. Maybe we should be making a queue of std library code that we should get third-party audited and figuring out when to prioritize it.

@@ -1,22 +1,39 @@
import fs from 'fs';
Copy link
Member

Choose a reason for hiding this comment

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

can you add this to CI? We can remove it later when we make breaking changes to the backwards compatibility on purpose

Copy link
Member Author

Choose a reason for hiding this comment

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

it already is running in the CI! The PR gets blocked if a constrained mismatch is detected

}).not.toThrow();
});

it('((2^64/2)+(2^64/2)) does not throw', () => {
Copy link
Member

Choose a reason for hiding this comment

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

why did you remove these other test cases?

Copy link
Member Author

@Trivo25 Trivo25 Jun 5, 2023

Choose a reason for hiding this comment

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

The values we used to test weren't valid Group elements (OCaml implementation never checked that, except for variables), and since I added a check that makes sure we only accept valid elements, the old tests started to fail - I added new ones

* Coerces anything group-like to a {@link Group}.
*/
constructor(g: {
x: FieldVar | Field | number | string | bigint;
Copy link
Member

Choose a reason for hiding this comment

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

is this a spot where you can use satisfies to assert it's the same as FieldLike? Would add nice info to the vscode hints too

Comment on lines +48 to +50
let onCurve =
add(mul(x_bigint, mul(x_bigint, x_bigint)), Pallas.b) ===
square(y_bigint);
Copy link
Member

Choose a reason for hiding this comment

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

I think we should support zero and pay the constraint costs if we have to pick one -- we can make a TODO to make some way to create a more efficient Group later.

Comment on lines +48 to +50
let onCurve =
add(mul(x_bigint, mul(x_bigint, x_bigint)), Pallas.b) ===
square(y_bigint);
Copy link
Member

Choose a reason for hiding this comment

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

safe and unsafe is a great idea 👍 , but yeah can come after

@Trivo25 Trivo25 merged commit 7b5eb01 into main Jun 8, 2023
9 checks passed
@Trivo25 Trivo25 deleted the refactor/group branch June 8, 2023 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Move Group to JS
3 participants