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

Efficient scalar mul and other Scalar improvements #1530

Merged
merged 48 commits into from
Apr 16, 2024

Conversation

mitschabaude
Copy link
Collaborator

@mitschabaude mitschabaude commented Apr 1, 2024

This revises the scalar multiplication gadgets and internal representation of Scalar.

BEFORE:

  • Scalar used to be represented as 255 bits, which imposed an unavoidable cost of 255 rows on any gadget that wanted to scale a group element by a Field. This is a very typical use case for scaling, see Signature.verify() and Nullifier.verify()
  • On top of that, the 255-bit representation was shifted and we lacked a good way of handling that. As a hack, we used a scaleShifted() helper which unshifts "in the exponent" at the cost of doing 3 full scalar muls instead of 1.
  • Group.scale() didn't support -1, 0 and 1 as scalars

AFTER:

  • Scalar uses the representation that Pickles uses as well and which our scalar mul gadgets were designed for: 1 low bit and 254 high bits. The high bits are a single field element. The representation is still shifted, but in a slightly different way than before.
  • We add efficient gadgets for scaling by a Field, and for converting a Field to a Scalar
  • We no longer expose shifting in any API: Scalar.fromBits() performs the shift behind the scenes in provable code now, as do Group.scale() and Scalar.fromField()
  • Group.scale() supports all scalars, and there's a new test to check that
  • Some results:
    • Signature.verify() now uses 350 instead of 800 rows
    • The nullifier example zkapp uses 9790 instead of 10774 rows

Closes #1440
Closes #984
Closes #980

@mitschabaude mitschabaude marked this pull request as ready for review April 8, 2024 19:16
@@ -319,24 +320,7 @@ class Field {
* See {@link Field.isEven} for examples.
*/
isOdd() {
if (this.isConstant()) return new Bool((this.toBigInt() & 1n) === 1n);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I moved this gadget into gadgets/comparison.ts to reuse it for scaleField()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is where they new scaling gadgets live! needs careful crypto review

@@ -101,67 +106,49 @@ class Group {
return fromProjective(g_proj);
}
} else {
const { x: x1, y: y1 } = this;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this code was moved to native-group.ts to reuse there. it was also rewritten a bit and a bug fixed

* **Warning**: If one of the inputs is zero, the result will be garbage and the proof useless.
* This case has to be prevented or handled separately by the caller of this method.
*/
addNonZero(g2: Group, allowZeroOutput = false): Group {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added this, I think it's actually a bit more common to use this than fully complete addition

@@ -108,83 +102,3 @@ equivalent({ from: [f], to: f })(
return ForeignScalar.fromBits(bits);
}
);

// scalar shift in foreign field arithmetic vs in the exponent
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this test was no longer valid, so I removed it

@@ -62,6 +66,26 @@ function bytes(length: number) {
});
}

function pointSpec<T>(field: ProvableSpec<bigint, T>, Curve: CurveAffine) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

just a testing utility, ends up unused (I used it during development to monitor the size of some circuits)

* `numBits <= n - 2` where `n` is the bit length of the scalar field.
* In our case, n=255 so numBits <= 253.
*/
scaleFastUnpack(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is the scaling gadget that Pickles uses as well

@@ -173,6 +174,16 @@ const BasicCS = constraintSystem('Basic', {
},
});

const CryptoCS = constraintSystem('Crypto', {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

new vk regression test

let tHi0 = existsOne(() => t0.toBigInt() >> 5n);

// prove split
// since we know that t0 < 2^88, this proves that t0High < 2^83
Copy link
Member

Choose a reason for hiding this comment

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

tHi0 you mean?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm afraid you're reviewing outdated code :O
yeah I meant tHi0

Copy link
Member

Choose a reason for hiding this comment

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

yeah, yesterday I was commit-reviewing to gain more context. Today I will focus on the updated native-curve.ts file

src/lib/provable/gadgets/scalar.ts Outdated Show resolved Hide resolved
@@ -61,7 +61,7 @@ function scale(P: { x: Field; y: Field }, s: Field): Group {

/**
* Internal helper to compute `(t + 2^254)*P`.
* `t` is expected to be split into 250 high bits (t >> 5) and 5 low bits (t & 0xf1).
* `t` is expected to be split into 250 high bits (t >> 5) and 5 low bits (t & 0x1f).
Copy link
Member

Choose a reason for hiding this comment

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

ah okay, that's now 5 bits

let tHi0 = existsOne(() => t0.toBigInt() >> 5n);

// prove split
// since we know that t0 < 2^88, this proves that t0High < 2^83
Copy link
Member

Choose a reason for hiding this comment

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

yeah, yesterday I was commit-reviewing to gain more context. Today I will focus on the updated native-curve.ts file

Copy link
Member

@querolita querolita left a comment

Choose a reason for hiding this comment

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

Very clear comments to understand the technique for an outsider. Couldn't find anything fishy after review.

src/lib/provable/gadgets/native-curve.ts Outdated Show resolved Hide resolved
@mitschabaude mitschabaude merged commit cfdb2a5 into main Apr 16, 2024
12 checks passed
@mitschabaude mitschabaude deleted the feature/no-shifted-scale branch April 16, 2024 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
v1 Prerequisite for o1js v1.0
Projects
None yet
3 participants