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
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5008583
simplify internal repr of scalar
mitschabaude Apr 1, 2024
5420cb2
expose scale_fast_unpack
mitschabaude Apr 1, 2024
b8c19b9
first attempt at efficient scale gadget
mitschabaude Apr 1, 2024
a00a15a
wip debugging
mitschabaude Apr 1, 2024
57de1f4
fix accessing ml curve point
mitschabaude Apr 3, 2024
2eb6354
improve efficiency by using incomplete additions
mitschabaude Apr 3, 2024
d659493
rename gadget file
mitschabaude Apr 3, 2024
43a0637
refactor 3 statements to loop
mitschabaude Apr 3, 2024
dcc58ee
move addition gadget to gadget file
mitschabaude Apr 3, 2024
2192f29
remove cyclic dependency on group
mitschabaude Apr 3, 2024
bb0716c
simplify witness generation code
mitschabaude Apr 3, 2024
ddbec87
change scalar type to shifted 5 / 250 representation
mitschabaude Apr 3, 2024
011d6a8
bring back fromBits, remove unused shifting methods
mitschabaude Apr 3, 2024
bc9b4ae
add scale from field element to test
mitschabaude Apr 3, 2024
b508437
bindings
mitschabaude Apr 3, 2024
0f93cc2
dump vks
mitschabaude Apr 3, 2024
04f8648
switch to repr with 1 low bit
mitschabaude Apr 4, 2024
9f11f0c
but scaling with 0 or 1 doesn't work now
mitschabaude Apr 4, 2024
32fd2b3
0 doesn't work anyway
mitschabaude Apr 4, 2024
7ce21a7
vk regression
mitschabaude Apr 4, 2024
5bc9543
fix
mitschabaude Apr 4, 2024
4c06ce1
Revert "switch to repr with 1 low bit"
mitschabaude Apr 5, 2024
6886184
add missing range checks, document more assumptions, add comments
mitschabaude Apr 5, 2024
8fbfe9b
remove scalar limitations from group unit test
mitschabaude Apr 5, 2024
5107a95
dump vks
mitschabaude Apr 5, 2024
d8fb076
move isOdd gadget to reuse it
mitschabaude Apr 5, 2024
51ee9f5
dedicated gadget of 130 rows for scaling by Field
mitschabaude Apr 5, 2024
f88222d
use 1, 254 split and handle edge cases
mitschabaude Apr 8, 2024
00680ed
reduce constraints
mitschabaude Apr 8, 2024
0aad84e
tighten fromBits gadget
mitschabaude Apr 8, 2024
37534df
remove unused split5 gadgets
mitschabaude Apr 8, 2024
ad7e7a3
dump vks
mitschabaude Apr 8, 2024
8d5d338
renaming and test utils
mitschabaude Apr 8, 2024
494e627
test utils
mitschabaude Apr 8, 2024
a8fea23
minor
mitschabaude Apr 8, 2024
fddead2
Merge branch 'main' into feature/no-shifted-scale
mitschabaude Apr 8, 2024
35eb475
changelog
mitschabaude Apr 8, 2024
7bcc84b
remove shifting stuff from foreign field unit test
mitschabaude Apr 8, 2024
e21fa33
comment
mitschabaude Apr 8, 2024
9c799ed
add nullifier to vk-regression
mitschabaude Apr 8, 2024
9e9ca06
mina
mitschabaude Apr 8, 2024
4b4787e
Merge branch 'main' into feature/no-shifted-scale
mitschabaude Apr 10, 2024
224b95c
fix changelog
mitschabaude Apr 10, 2024
dab6eb3
dump vks
mitschabaude Apr 10, 2024
0877e26
Merge branch 'main' into feature/no-shifted-scale
mitschabaude Apr 15, 2024
523330a
fixes and tweaks to scaling gadgets
mitschabaude Apr 16, 2024
45837fc
remove redundant constraint
mitschabaude Apr 16, 2024
9822654
dump vks
mitschabaude Apr 16, 2024
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
39 changes: 37 additions & 2 deletions src/lib/provable/gadgets/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@ import { Tuple } from '../../util/types.js';
import type { Bool } from '../bool.js';
import { fieldVar } from '../gates.js';
import { existsOne } from '../core/exists.js';
import { createField } from '../core/field-constructor.js';
import { createField, isBool } from '../core/field-constructor.js';

export { toVars, toVar, isVar, assert, bitSlice, divideWithRemainder };
export {
toVars,
toVar,
isVar,
assert,
bitSlice,
bit,
divideWithRemainder,
packBits,
isConstant,
};

/**
* Given a Field, collapse its AST to a pure Var. See {@link FieldVar}.
Expand Down Expand Up @@ -56,8 +66,33 @@ function bitSlice(x: bigint, start: number, length: number) {
return (x >> BigInt(start)) & ((1n << BigInt(length)) - 1n);
}

function bit(x: bigint, i: number) {
return (x >> BigInt(i)) & 1n;
}

function divideWithRemainder(numerator: bigint, denominator: bigint) {
const quotient = numerator / denominator;
const remainder = numerator - denominator * quotient;
return { quotient, remainder };
}

// pack bools into a single field element

/**
* Helper function to pack bits into a single field element.
* Just returns the sum without any boolean checks.
*/
function packBits(bits: (Field | Bool)[]): Field {
let n = bits.length;
let sum = createField(0n);
for (let i = 0; i < n; i++) {
let bit = bits[i];
if (isBool(bit)) bit = bit.toField();
sum = sum.add(bit.mul(1n << BigInt(i)));
}
return sum.seal();
}

function isConstant(...args: (Field | Bool)[]): boolean {
return args.every((x) => x.isConstant());
}
6 changes: 6 additions & 0 deletions src/lib/provable/gadgets/comparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,27 @@ import { Field3, ForeignField } from './foreign-field.js';
import { l, l2, multiRangeCheck } from './range-check.js';
import { witness } from '../types/witness.js';

// external API
export {
// generic comparison gadgets for inputs in a narrower range < p/2
assertLessThanGeneric,
assertLessThanOrEqualGeneric,
lessThanGeneric,
lessThanOrEqualGeneric,

// comparison gadgets for full range inputs
assertLessThanFull,
assertLessThanOrEqualFull,
lessThanFull,
lessThanOrEqualFull,

// legacy, unused
compareCompatible,
};

// internal API
export { fieldToField3 };

/**
* Prove x <= y assuming 0 <= x, y < c.
* The upper bound c must satisfy 2c <= p, where p is the field order.
Expand Down
114 changes: 114 additions & 0 deletions src/lib/provable/gadgets/scalar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type { Field } from '../field.js';
import type { Bool } from '../bool.js';
import { Fq } from '../../../bindings/crypto/finite-field.js';
import { PallasAffine } from '../../../bindings/crypto/elliptic-curve.js';
import { fieldToField3 } from './comparison.js';
import { Field3, ForeignField } from './foreign-field.js';
import { exists, existsOne } from '../core/exists.js';
import { bit, isConstant, packBits } from './common.js';
import { TupleN } from '../../util/types.js';
import { l } from './range-check.js';
import { createField } from '../core/field-constructor.js';
import { Snarky } from '../../../snarky.js';
import { Provable } from '../provable.js';
import { Group } from '../group.js';

export { scale, scaleShiftedSplit5 };

/**
* Gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field.
*/
function scale(P: { x: Field; y: Field }, s: Field): Group {
// constant case
let { x, y } = P;
if (x.isConstant() && y.isConstant() && s.isConstant()) {
let sP = PallasAffine.scale(
PallasAffine.fromNonzero({ x: x.toBigInt(), y: y.toBigInt() }),
s.toBigInt()
);
return new Group({ x: createField(sP.x), y: createField(sP.y) });
}

// compute t = s - 2^254 mod q using foreign field subtraction
let sBig = fieldToField3(s);
let twoTo254 = Field3.from(1n << 254n);
let [t0, t1, t2] = ForeignField.sub(sBig, twoTo254, Fq.modulus);

// split t into 250 high bits and 5 low bits
// => split t0 into [5, 83]
let tLo = exists(5, () => {
let t = t0.toBigInt();
return [bit(t, 0), bit(t, 1), bit(t, 2), bit(t, 3), bit(t, 4)];
});
let tLoBools = TupleN.map(tLo, (x) => x.assertBool());
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

packBits(tLo)
.add(tHi0.mul(1n << 5n))
.assertEquals(t0);

// pack tHi
let tHi = tHi0
.add(t1.mul(1n << (l - 5n)))
.add(t2.mul(1n << (2n * l - 5n)))
.seal();

// return (t + 2^254)*P = (s - 2^254 + 2^254)*P = s*P
return scaleShiftedSplit5(P, tHi, tLoBools);
}

/**
* 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).
querolita marked this conversation as resolved.
Show resolved Hide resolved
*
* The gadget proves that `tHi` is in [0, 2^250) but assumes that `tLo` consists of bits.
*/
function scaleShiftedSplit5(
{ x, y }: { x: Field; y: Field },
tHi: Field,
tLo: TupleN<Bool, 5>
): Group {
// constant case
if (isConstant(x, y, tHi, ...tLo)) {
let sP = PallasAffine.scale(
PallasAffine.fromNonzero({ x: x.toBigInt(), y: y.toBigInt() }),
Fq.add(packBits(tLo).toBigInt() + (tHi.toBigInt() << 5n), 1n << 254n)
);
return new Group({ x: createField(sP.x), y: createField(sP.y) });
}

// R = (2*(t >> 5) + 1 + 2^250)P
let [, RMl] = Snarky.group.scaleFastUnpack(
[0, x.value, y.value],
[0, tHi.value],
250
);
let P = new Group({ x, y });
let R = new Group({ x: RMl[0], y: RMl[1] });
let [t0, t1, t2, t3, t4] = tLo;

// TODO: use faster group ops which don't allow zero inputs

// R = t4 ? R : R - P = ((t >> 4) + 2^250)P
R = Provable.if(t4, R, R.sub(P));

// R = ((t >> 3) + 2^251)P
R = R.add(R);
R = Provable.if(t3, R.add(P), R);

// R = ((t >> 2) + 2^252)P
R = R.add(R);
R = Provable.if(t2, R.add(P), R);

// R = ((t >> 1) + 2^253)P
R = R.add(R);
R = Provable.if(t1, R.add(P), R);

// R = (t + 2^254)P
R = R.add(R);
R = Provable.if(t0, R.add(P), R);

return R;
}
Loading