From 50085837d9ab96d48754628d6e242282f95029fe Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 1 Apr 2024 10:36:32 +0200 Subject: [PATCH 01/45] simplify internal repr of scalar --- src/lib/ml/conversion.ts | 8 +++--- src/lib/provable/group.ts | 2 +- src/lib/provable/scalar.ts | 56 ++++++++++++++------------------------ 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/lib/ml/conversion.ts b/src/lib/ml/conversion.ts index 91b938d105..4ad09fe971 100644 --- a/src/lib/ml/conversion.ts +++ b/src/lib/ml/conversion.ts @@ -71,18 +71,18 @@ function varToField(x: FieldVar): Field { return Field(x); } -function fromScalar(s: Scalar) { - return s.toConstant().constantValue; +function fromScalar(s: Scalar): ScalarConst { + return [0, s.toConstant().constantValue]; } function toScalar(s: ScalarConst) { - return Scalar.from(s); + return Scalar.from(s[1]); } function fromPrivateKey(sk: PrivateKey) { return fromScalar(sk.s); } function toPrivateKey(sk: ScalarConst) { - return new PrivateKey(Scalar.from(sk)); + return new PrivateKey(Scalar.from(sk[1])); } function fromPublicKey(pk: PublicKey): MlPublicKey { diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index 31cee90cfd..3dcc5e6cc2 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -196,7 +196,7 @@ class Group { let g_proj = Pallas.scale(toProjective(this), scalar.toBigInt()); return fromProjective(g_proj); } else { - let [, ...bits] = scalar.value; + let [...bits] = scalar.shiftedBits; bits.reverse(); let [, x, y] = Snarky.group.scale(toTuple(this), [0, ...bits]); return new Group({ x, y }); diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index 87b9f1ce76..45b310e043 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -3,7 +3,6 @@ import { Fq } from '../../bindings/crypto/finite-field.js'; import { Scalar as SignableFq } from '../../mina-signer/src/curve-bigint.js'; import { Field } from './field.js'; import { FieldVar, FieldConst } from './core/fieldvar.js'; -import { MlArray } from '../ml/base.js'; import { Bool } from './bool.js'; export { Scalar, ScalarConst, unshift, shift }; @@ -11,36 +10,27 @@ export { Scalar, ScalarConst, unshift, shift }; // internal API export { constantScalarToBigint }; -type BoolVar = FieldVar; type ScalarConst = [0, bigint]; -const ScalarConst = { - fromBigint: constFromBigint, - toBigint: constToBigint, - is(x: any): x is ScalarConst { - return Array.isArray(x) && x[0] === 0 && typeof x[1] === 'bigint'; - }, -}; - let scalarShift = Fq.mod(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; -type ConstantScalar = Scalar & { constantValue: ScalarConst }; +type ConstantScalar = Scalar & { constantValue: bigint }; /** * Represents a {@link Scalar}. */ class Scalar { - value: MlArray; - constantValue?: ScalarConst; + shiftedBits: FieldVar[]; + constantValue?: bigint; static ORDER = Fq.modulus; - private constructor(bits: MlArray, constantValue?: bigint) { - this.value = bits; + private constructor(bits: FieldVar[], constantValue?: bigint) { + this.shiftedBits = bits; constantValue ??= toConstantScalar(bits); if (constantValue !== undefined) { - this.constantValue = ScalarConst.fromBigint(constantValue); + this.constantValue = constantValue; } } @@ -49,10 +39,9 @@ class Scalar { * * If the input is too large, it is reduced modulo the scalar field size. */ - static from(x: Scalar | ScalarConst | bigint | number | string) { + static from(x: Scalar | bigint | number | string) { if (x instanceof Scalar) return x; - let x_ = ScalarConst.is(x) ? constToBigint(x) : x; - let scalar = Fq.mod(BigInt(x_)); + let scalar = Fq.mod(BigInt(x)); let bits = toBits(scalar); return new Scalar(bits, scalar); } @@ -61,7 +50,7 @@ class Scalar { * Check whether this {@link Scalar} is a hard-coded constant in the constraint system. * If a {@link Scalar} is constructed outside provable code, it is a constant. */ - isConstant(): this is Scalar & { constantValue: ScalarConst } { + isConstant(): this is Scalar & { constantValue: bigint } { return this.constantValue !== undefined; } @@ -74,9 +63,10 @@ class Scalar { */ toConstant(): ConstantScalar { if (this.constantValue !== undefined) return this as ConstantScalar; - let [, ...bits] = this.value; - let constBits = bits.map((b) => FieldVar.constant(Snarky.field.readVar(b))); - return new Scalar([0, ...constBits]) as ConstantScalar; + let constBits = this.shiftedBits.map((b) => + FieldVar.constant(Snarky.field.readVar(b)) + ); + return new Scalar(constBits) as ConstantScalar; } /** @@ -213,8 +203,7 @@ class Scalar { * The fields are not constrained to be boolean. */ static toFields(x: Scalar) { - let [, ...bits] = x.value; - return bits.map((b) => new Field(b)); + return x.shiftedBits.map((b) => new Field(b)); } /** @@ -260,7 +249,7 @@ class Scalar { * Creates a data structure from an array of serialized {@link Field} elements. */ static fromFields(fields: Field[]): Scalar { - return new Scalar([0, ...fields.map((x) => x.value)]); + return new Scalar(fields.map((x) => x.value)); } /** @@ -326,7 +315,7 @@ function assertConstant(x: Scalar, name: string) { return constantScalarToBigint(x, `Scalar.${name}`); } -function toConstantScalar([, ...bits]: MlArray): bigint | undefined { +function toConstantScalar(bits: FieldVar[]): bigint | undefined { if (bits.length !== Fq.sizeInBits) throw Error( `Scalar: expected bits array of length ${Fq.sizeInBits}, got ${bits.length}` @@ -341,13 +330,10 @@ function toConstantScalar([, ...bits]: MlArray): bigint | undefined { return shift(sShifted); } -function toBits(constantValue: bigint): MlArray { - return [ - 0, - ...SignableFq.toBits(unshift(constantValue)).map((b) => - FieldVar.constant(BigInt(b)) - ), - ]; +function toBits(constantValue: bigint): FieldVar[] { + return SignableFq.toBits(unshift(constantValue)).map((b) => + FieldVar.constant(BigInt(b)) + ); } /** @@ -377,5 +363,5 @@ function constantScalarToBigint(s: Scalar, name: string) { `${name}() is not available in provable code. That means it can't be called in a @method or similar environment, and there's no alternative implemented to achieve that.` ); - return ScalarConst.toBigint(s.constantValue); + return s.constantValue; } From 5420cb2ae13b47d4977430e91223a59ba3ff9585 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 1 Apr 2024 18:04:26 +0200 Subject: [PATCH 02/45] expose scale_fast_unpack --- src/bindings | 2 +- src/snarky.d.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 3c68a0dad4..3fca906694 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 3c68a0dad42df20e5ef89ed408c83c41fdfcfd24 +Subproject commit 3fca906694f102dc60bf64745324de234cd6812d diff --git a/src/snarky.d.ts b/src/snarky.d.ts index e8f72dca55..0e09081798 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -348,6 +348,20 @@ declare const Snarky: { group: { scale(p: MlGroup, s: MlArray): MlGroup; + + /** + * Computes `(2*s + 1 + 2^numBits) * P` and also returns the bits of s (which are proven correct). + * + * `numBits` must be a multiple of 5, and s must be in the range [0, 2^numBits). + * The [soundness proof](https://github.com/zcash/zcash/issues/3924) assumes + * `numBits <= n - 2` where `n` is the bit length of the scalar field. + * In our case, n=255 so numBits <= 253. + */ + scaleFastUnpack( + P: MlGroup, + shiftedValue: [_: 0, s: FieldVar], + numBits: number + ): MlPair>; }; /** From b8c19b94f05a0b13c7342c275eabce304b8efb85 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 1 Apr 2024 18:05:06 +0200 Subject: [PATCH 03/45] first attempt at efficient scale gadget --- src/lib/provable/gadgets/common.ts | 39 ++++++++- src/lib/provable/gadgets/comparison.ts | 6 ++ src/lib/provable/gadgets/scalar.ts | 114 +++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/lib/provable/gadgets/scalar.ts diff --git a/src/lib/provable/gadgets/common.ts b/src/lib/provable/gadgets/common.ts index a1a065f2fd..b49acecae2 100644 --- a/src/lib/provable/gadgets/common.ts +++ b/src/lib/provable/gadgets/common.ts @@ -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}. @@ -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()); +} diff --git a/src/lib/provable/gadgets/comparison.ts b/src/lib/provable/gadgets/comparison.ts index 15ae06061c..e8d2ae9f74 100644 --- a/src/lib/provable/gadgets/comparison.ts +++ b/src/lib/provable/gadgets/comparison.ts @@ -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. diff --git a/src/lib/provable/gadgets/scalar.ts b/src/lib/provable/gadgets/scalar.ts new file mode 100644 index 0000000000..9d23973c17 --- /dev/null +++ b/src/lib/provable/gadgets/scalar.ts @@ -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 + 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). + * + * 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 +): 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; +} From a00a15ad760423291591a2c1694dcb8cf4ec4568 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 1 Apr 2024 19:52:20 +0200 Subject: [PATCH 04/45] wip debugging --- src/lib/provable/test/group.unit-test.ts | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index a6d3205b5d..d7bd437201 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -3,9 +3,22 @@ import { test, Random } from '../../testing/property.js'; import { Provable } from '../provable.js'; import { Poseidon } from '../../../mina-signer/src/poseidon-bigint.js'; import { runAndCheckSync } from '../core/provable-context.js'; +import { scale } from '../gadgets/scalar.js'; +import { Field } from '../field.js'; console.log('group consistency tests'); +test(Random.field, Random.field, (a, s0, assert) => { + const { + x: x1, + y: { x0: y1 }, + } = Poseidon.hashToGroup([a])!; + const g = Group.from(x1, y1); + const s = Field.from(s0); + + runScale(g, Field.from(1n), (g, s) => scale(g, s), assert); +}); + // tests consistency between in- and out-circuit implementations test(Random.field, Random.field, (a, b, assert) => { const { @@ -71,3 +84,31 @@ function run( }); }); } + +function runScale( + g: Group, + s: Field, + f: (g1: Group, s: Field) => Group, + assert: (b: boolean, message?: string | undefined) => void +) { + let result_out_circuit = f(g, s); + + runAndCheckSync(() => { + let result_in_circuit = f( + Provable.witness(Group, () => g), + Provable.witness(Field, () => s) + ); + + Provable.asProver(() => { + assert( + result_out_circuit.equals(result_in_circuit).toBoolean(), + `Result for x does not match. g: ${JSON.stringify( + g + )}, s: ${JSON.stringify(s)} + + out_circuit: ${JSON.stringify(result_out_circuit)} + in_circuit: ${JSON.stringify(result_in_circuit)}` + ); + }); + }); +} From 57de1f40645eb467d1eaa3102502ec83517bb193 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 3 Apr 2024 11:41:30 +0200 Subject: [PATCH 05/45] fix accessing ml curve point --- src/lib/provable/gadgets/scalar.ts | 2 +- src/lib/provable/test/group.unit-test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/provable/gadgets/scalar.ts b/src/lib/provable/gadgets/scalar.ts index 9d23973c17..5efd382c39 100644 --- a/src/lib/provable/gadgets/scalar.ts +++ b/src/lib/provable/gadgets/scalar.ts @@ -86,7 +86,7 @@ function scaleShiftedSplit5( 250 ); let P = new Group({ x, y }); - let R = new Group({ x: RMl[0], y: RMl[1] }); + let R = new Group({ x: RMl[1], y: RMl[2] }); let [t0, t1, t2, t3, t4] = tLo; // TODO: use faster group ops which don't allow zero inputs diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index d7bd437201..2385c0883e 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -16,7 +16,7 @@ test(Random.field, Random.field, (a, s0, assert) => { const g = Group.from(x1, y1); const s = Field.from(s0); - runScale(g, Field.from(1n), (g, s) => scale(g, s), assert); + runScale(g, s, (g, s) => scale(g, s), assert); }); // tests consistency between in- and out-circuit implementations From 2eb6354ea8c00d9383607cd0031e978c94b62dda Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 3 Apr 2024 12:45:30 +0200 Subject: [PATCH 06/45] improve efficiency by using incomplete additions --- src/lib/provable/gadgets/scalar.ts | 23 +++-- src/lib/provable/group.ts | 147 +++++++++++++++++++---------- src/lib/provable/provable.ts | 4 +- 3 files changed, 110 insertions(+), 64 deletions(-) diff --git a/src/lib/provable/gadgets/scalar.ts b/src/lib/provable/gadgets/scalar.ts index 5efd382c39..ee2b878d08 100644 --- a/src/lib/provable/gadgets/scalar.ts +++ b/src/lib/provable/gadgets/scalar.ts @@ -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). * * The gadget proves that `tHi` is in [0, 2^250) but assumes that `tLo` consists of bits. */ @@ -89,26 +89,25 @@ function scaleShiftedSplit5( let R = new Group({ x: RMl[1], y: RMl[2] }); 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 = Provable.if(t4, R, R.addNonZero(P.neg())); // R = ((t >> 3) + 2^251)P - R = R.add(R); - R = Provable.if(t3, R.add(P), R); + R = R.addNonZero(R); + R = Provable.if(t3, R.addNonZero(P), R); // R = ((t >> 2) + 2^252)P - R = R.add(R); - R = Provable.if(t2, R.add(P), R); + R = R.addNonZero(R); + R = Provable.if(t2, R.addNonZero(P), R); // R = ((t >> 1) + 2^253)P - R = R.add(R); - R = Provable.if(t1, R.add(P), R); + R = R.addNonZero(R); + R = Provable.if(t1, R.addNonZero(P), R); // R = (t + 2^254)P - R = R.add(R); - R = Provable.if(t0, R.add(P), R); + R = R.addNonZero(R); + // in the final step, we allow a zero output to make it work for the 0 scalar + R = Provable.if(t0, R.addNonZero(P, true), R); return R; } diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index 3dcc5e6cc2..62f450db56 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -3,9 +3,14 @@ import { FieldVar } from './core/fieldvar.js'; import { Scalar } from './scalar.js'; import { Snarky } from '../../snarky.js'; import { Fp } from '../../bindings/crypto/finite-field.js'; -import { GroupAffine, Pallas } from '../../bindings/crypto/elliptic-curve.js'; +import { + GroupAffine, + Pallas, + PallasAffine, +} from '../../bindings/crypto/elliptic-curve.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; +import { assert } from '../util/assert.js'; export { Group }; @@ -101,70 +106,49 @@ class Group { return fromProjective(g_proj); } } else { - const { x: x1, y: y1 } = this; - const { x: x2, y: y2 } = g; - - let zero = new Field(0); - - let same_x = Provable.witness(Field, () => x1.equals(x2).toField()); - - let inf = Provable.witness(Bool, () => - x1.equals(x2).and(y1.equals(y2).not()) - ); - - let inf_z = Provable.witness(Field, () => { - if (y1.equals(y2).toBoolean()) return zero; - else if (x1.equals(x2).toBoolean()) return y2.sub(y1).inv(); - else return zero; - }); - - let x21_inv = Provable.witness(Field, () => { - if (x1.equals(x2).toBoolean()) return zero; - else return x2.sub(x1).inv(); - }); - - let s = Provable.witness(Field, () => { - if (x1.equals(x2).toBoolean()) { - let x1_squared = x1.square(); - return x1_squared.add(x1_squared).add(x1_squared).div(y1.add(y1)); - } else return y2.sub(y1).div(x2.sub(x1)); - }); - - let x3 = Provable.witness(Field, () => { - return s.square().sub(x1.add(x2)); - }); - - let y3 = Provable.witness(Field, () => { - return s.mul(x1.sub(x3)).sub(y1); - }); - - let [, x, y] = Snarky.gates.ecAdd( - toTuple(Group.from(x1.seal(), y1.seal())), - toTuple(Group.from(x2.seal(), y2.seal())), - toTuple(Group.from(x3, y3)), - inf.toField().value, - same_x.value, - s.value, - inf_z.value, - x21_inv.value - ); - + let { result, isInfinity } = addBase(this, g); // similarly to the constant implementation, we check if either operand is zero // and the implementation above (original OCaml implementation) returns something wild -> g + 0 != g where it should be g + 0 = g let gIsZero = g.isZero(); let onlyThisIsZero = this.isZero().and(gIsZero.not()); - let isNegation = inf; + let isNegation = isInfinity; let isNormalAddition = gIsZero.or(onlyThisIsZero).or(isNegation).not(); // note: gIsZero and isNegation are not mutually exclusive, but if both are true, we add 1*0 + 1*0 = 0 which is correct return Provable.switch( [gIsZero, onlyThisIsZero, isNegation, isNormalAddition], Group, - [this, g, Group.zero, new Group({ x, y })] + [this, g, Group.zero, new Group(result)], + { allowNonExclusive: true } ); } } + /** + * Lower-level variant of {@link add} which doesn't handle the case where one of the operands is zero, and + * asserts that the output is non-zero. + * + * Optionally, zero outputs can be allowed by setting `allowZeroOutput` to `true`. + * + * **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 { + if (isConstant(this) && isConstant(g2)) { + let { x, y, infinity } = PallasAffine.add(toAffine(this), toAffine(g2)); + assert(infinity === false, 'Group.addNonzero(): Result is zero'); + return fromAffine({ x, y, infinity }); + } + let { result, isInfinity } = addBase(this, g2); + + if (allowZeroOutput) { + return Provable.if(isInfinity, Group.zero, new Group(result)); + } else { + isInfinity.assertFalse('Group.addNonzero(): Result is zero'); + return new Group(result); + } + } + /** * Subtracts another {@link Group} element from this one. */ @@ -358,6 +342,63 @@ class Group { } } +type GroupBase = { x: Field; y: Field }; + +/** + * Wraps the `EC_add` gate to perform complete addition of two non-zero curve points. + */ +function addBase(g: GroupBase, h: GroupBase) { + const { x: x1, y: y1 } = g; + const { x: x2, y: y2 } = h; + + let zero = new Field(0); + + let same_x = Provable.witness(Field, () => x1.equals(x2).toField()); + + let inf = Provable.witness(Bool, () => + x1.equals(x2).and(y1.equals(y2).not()) + ); + + let inf_z = Provable.witness(Field, () => { + if (y1.equals(y2).toBoolean()) return zero; + else if (x1.equals(x2).toBoolean()) return y2.sub(y1).inv(); + else return zero; + }); + + let x21_inv = Provable.witness(Field, () => { + if (x1.equals(x2).toBoolean()) return zero; + else return x2.sub(x1).inv(); + }); + + let s = Provable.witness(Field, () => { + if (x1.equals(x2).toBoolean()) { + let x1_squared = x1.square(); + return x1_squared.add(x1_squared).add(x1_squared).div(y1.add(y1)); + } else return y2.sub(y1).div(x2.sub(x1)); + }); + + let x3 = Provable.witness(Field, () => { + return s.square().sub(x1.add(x2)); + }); + + let y3 = Provable.witness(Field, () => { + return s.mul(x1.sub(x3)).sub(y1); + }); + + Snarky.gates.ecAdd( + toTuple(Group.from(x1.seal(), y1.seal())), + toTuple(Group.from(x2.seal(), y2.seal())), + toTuple(Group.from(x3, y3)), + inf.toField().value, + same_x.value, + s.value, + inf_z.value, + x21_inv.value + ); + + return { result: { x: x3, y: y3 }, isInfinity: inf }; +} + // internal helpers function isConstant(g: Group) { @@ -383,3 +424,7 @@ function fromProjective({ x, y, z }: { x: bigint; y: bigint; z: bigint }) { function fromAffine({ x, y, infinity }: GroupAffine) { return infinity ? Group.zero : new Group({ x, y }); } + +function toAffine(g: Group): GroupAffine { + return PallasAffine.from({ x: g.x.toBigInt(), y: g.y.toBigInt() }); +} diff --git a/src/lib/provable/provable.ts b/src/lib/provable/provable.ts index 20c4bb6f01..e386bc17e1 100644 --- a/src/lib/provable/provable.ts +++ b/src/lib/provable/provable.ts @@ -339,7 +339,8 @@ function ifImplicit(condition: Bool, x: T, y: T): T { function switch_>( mask: Bool[], type: A, - values: T[] + values: T[], + { allowNonExclusive = false } = {} ): T { // picks the value at the index where mask is true let nValues = values.length; @@ -348,6 +349,7 @@ function switch_>( `Provable.switch: \`values\` and \`mask\` have different lengths (${values.length} vs. ${mask.length}), which is not allowed.` ); let checkMask = () => { + if (allowNonExclusive) return; let nTrue = mask.filter((b) => b.toBoolean()).length; if (nTrue > 1) { throw Error( From d659493a064d6e6cc88e4bc0d2d676e3102cd8c7 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 3 Apr 2024 12:47:09 +0200 Subject: [PATCH 07/45] rename gadget file --- src/lib/provable/gadgets/{scalar.ts => native-curve.ts} | 0 src/lib/provable/test/group.unit-test.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/lib/provable/gadgets/{scalar.ts => native-curve.ts} (100%) diff --git a/src/lib/provable/gadgets/scalar.ts b/src/lib/provable/gadgets/native-curve.ts similarity index 100% rename from src/lib/provable/gadgets/scalar.ts rename to src/lib/provable/gadgets/native-curve.ts diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index 2385c0883e..8b15066770 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -3,7 +3,7 @@ import { test, Random } from '../../testing/property.js'; import { Provable } from '../provable.js'; import { Poseidon } from '../../../mina-signer/src/poseidon-bigint.js'; import { runAndCheckSync } from '../core/provable-context.js'; -import { scale } from '../gadgets/scalar.js'; +import { scale } from '../gadgets/native-curve.js'; import { Field } from '../field.js'; console.log('group consistency tests'); From 43a0637b6d17d55ee33e364168402d4851d61023 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 3 Apr 2024 12:51:46 +0200 Subject: [PATCH 08/45] refactor 3 statements to loop --- src/lib/provable/gadgets/native-curve.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index ee2b878d08..cce3f52dbb 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -93,20 +93,16 @@ function scaleShiftedSplit5( R = Provable.if(t4, R, R.addNonZero(P.neg())); // R = ((t >> 3) + 2^251)P - R = R.addNonZero(R); - R = Provable.if(t3, R.addNonZero(P), R); - // R = ((t >> 2) + 2^252)P - R = R.addNonZero(R); - R = Provable.if(t2, R.addNonZero(P), R); - // R = ((t >> 1) + 2^253)P - R = R.addNonZero(R); - R = Provable.if(t1, R.addNonZero(P), R); + for (let t of [t3, t2, t1]) { + R = R.addNonZero(R); + R = Provable.if(t, R.addNonZero(P), R); + } // R = (t + 2^254)P - R = R.addNonZero(R); // in the final step, we allow a zero output to make it work for the 0 scalar + R = R.addNonZero(R); R = Provable.if(t0, R.addNonZero(P, true), R); return R; From dcc58ee2a46e870b44ca816e876383552684aac9 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 3 Apr 2024 13:02:00 +0200 Subject: [PATCH 09/45] move addition gadget to gadget file --- src/lib/provable/gadgets/native-curve.ts | 66 ++++++++++++++++++++++-- src/lib/provable/group.ts | 62 ++-------------------- 2 files changed, 66 insertions(+), 62 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index cce3f52dbb..c861375ba1 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -8,17 +8,20 @@ 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 { createField, getBool, getField } from '../core/field-constructor.js'; import { Snarky } from '../../../snarky.js'; import { Provable } from '../provable.js'; import { Group } from '../group.js'; +import { MlPair } from '../../ml/base.js'; -export { scale, scaleShiftedSplit5 }; +export { scale, scaleShiftedSplit5, add }; + +type Point = { x: Field; y: Field }; /** * 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 { +function scale(P: Point, s: Field): Group { // constant case let { x, y } = P; if (x.isConstant() && y.isConstant() && s.isConstant()) { @@ -107,3 +110,60 @@ function scaleShiftedSplit5( return R; } + +/** + * Wraps the `EC_add` gate to perform complete addition of two non-zero curve points. + */ +function add(g: Point, h: Point) { + const { x: x1, y: y1 } = g; + const { x: x2, y: y2 } = h; + + let zero = createField(0); + const Field = getField(); + const Bool = getBool(); + + let same_x = Provable.witness(Field, () => x1.equals(x2).toField()); + + let inf = Provable.witness(Bool, () => + x1.equals(x2).and(y1.equals(y2).not()) + ); + + let inf_z = Provable.witness(Field, () => { + if (y1.equals(y2).toBoolean()) return zero; + else if (x1.equals(x2).toBoolean()) return y2.sub(y1).inv(); + else return zero; + }); + + let x21_inv = Provable.witness(Field, () => { + if (x1.equals(x2).toBoolean()) return zero; + else return x2.sub(x1).inv(); + }); + + let s = Provable.witness(Field, () => { + if (x1.equals(x2).toBoolean()) { + let x1_squared = x1.square(); + return x1_squared.add(x1_squared).add(x1_squared).div(y1.add(y1)); + } else return y2.sub(y1).div(x2.sub(x1)); + }); + + let x3 = Provable.witness(Field, () => { + return s.square().sub(x1.add(x2)); + }); + + let y3 = Provable.witness(Field, () => { + return s.mul(x1.sub(x3)).sub(y1); + }); + + Snarky.gates.ecAdd( + MlPair(x1.seal().value, y1.seal().value), + MlPair(x2.seal().value, y2.seal().value), + MlPair(x3.value, y3.value), + inf.toField().value, + same_x.value, + s.value, + inf_z.value, + x21_inv.value + ); + + return { result: { x: x3, y: y3 }, isInfinity: inf }; +} diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index 62f450db56..ecf29d0f4a 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -11,6 +11,7 @@ import { import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { assert } from '../util/assert.js'; +import { add } from './gadgets/native-curve.js'; export { Group }; @@ -106,7 +107,7 @@ class Group { return fromProjective(g_proj); } } else { - let { result, isInfinity } = addBase(this, g); + let { result, isInfinity } = add(this, g); // similarly to the constant implementation, we check if either operand is zero // and the implementation above (original OCaml implementation) returns something wild -> g + 0 != g where it should be g + 0 = g let gIsZero = g.isZero(); @@ -139,7 +140,7 @@ class Group { assert(infinity === false, 'Group.addNonzero(): Result is zero'); return fromAffine({ x, y, infinity }); } - let { result, isInfinity } = addBase(this, g2); + let { result, isInfinity } = add(this, g2); if (allowZeroOutput) { return Provable.if(isInfinity, Group.zero, new Group(result)); @@ -342,63 +343,6 @@ class Group { } } -type GroupBase = { x: Field; y: Field }; - -/** - * Wraps the `EC_add` gate to perform complete addition of two non-zero curve points. - */ -function addBase(g: GroupBase, h: GroupBase) { - const { x: x1, y: y1 } = g; - const { x: x2, y: y2 } = h; - - let zero = new Field(0); - - let same_x = Provable.witness(Field, () => x1.equals(x2).toField()); - - let inf = Provable.witness(Bool, () => - x1.equals(x2).and(y1.equals(y2).not()) - ); - - let inf_z = Provable.witness(Field, () => { - if (y1.equals(y2).toBoolean()) return zero; - else if (x1.equals(x2).toBoolean()) return y2.sub(y1).inv(); - else return zero; - }); - - let x21_inv = Provable.witness(Field, () => { - if (x1.equals(x2).toBoolean()) return zero; - else return x2.sub(x1).inv(); - }); - - let s = Provable.witness(Field, () => { - if (x1.equals(x2).toBoolean()) { - let x1_squared = x1.square(); - return x1_squared.add(x1_squared).add(x1_squared).div(y1.add(y1)); - } else return y2.sub(y1).div(x2.sub(x1)); - }); - - let x3 = Provable.witness(Field, () => { - return s.square().sub(x1.add(x2)); - }); - - let y3 = Provable.witness(Field, () => { - return s.mul(x1.sub(x3)).sub(y1); - }); - - Snarky.gates.ecAdd( - toTuple(Group.from(x1.seal(), y1.seal())), - toTuple(Group.from(x2.seal(), y2.seal())), - toTuple(Group.from(x3, y3)), - inf.toField().value, - same_x.value, - s.value, - inf_z.value, - x21_inv.value - ); - - return { result: { x: x3, y: y3 }, isInfinity: inf }; -} - // internal helpers function isConstant(g: Group) { From 2192f297184e605a4828dff2c75bcfa6e619d8ef Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 3 Apr 2024 13:15:27 +0200 Subject: [PATCH 10/45] remove cyclic dependency on group --- src/lib/provable/gadgets/native-curve.ts | 48 +++++++++++++++++------- src/lib/provable/test/group.unit-test.ts | 2 +- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index c861375ba1..48610bedc8 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -11,8 +11,8 @@ import { l } from './range-check.js'; import { createField, getBool, getField } from '../core/field-constructor.js'; import { Snarky } from '../../../snarky.js'; import { Provable } from '../provable.js'; -import { Group } from '../group.js'; import { MlPair } from '../../ml/base.js'; +import { provable } from '../types/provable-derivers.js'; export { scale, scaleShiftedSplit5, add }; @@ -21,7 +21,7 @@ type Point = { x: Field; y: Field }; /** * Gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. */ -function scale(P: Point, s: Field): Group { +function scale(P: Point, s: Field): Point { // constant case let { x, y } = P; if (x.isConstant() && y.isConstant() && s.isConstant()) { @@ -29,9 +29,8 @@ function scale(P: Point, s: Field): Group { PallasAffine.fromNonzero({ x: x.toBigInt(), y: y.toBigInt() }), s.toBigInt() ); - return new Group({ x: createField(sP.x), y: createField(sP.y) }); + return { 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); @@ -69,18 +68,21 @@ function scale(P: Point, s: Field): Group { * 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 }, + { x, y }: Point, tHi: Field, tLo: TupleN -): Group { +): Point { // 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) }); + return { x: createField(sP.x), y: createField(sP.y) }; } + const Field = getField(); + const Point = provable({ x: Field, y: Field }); + const zero = createField(0n); // R = (2*(t >> 5) + 1 + 2^250)P let [, RMl] = Snarky.group.scaleFastUnpack( @@ -88,25 +90,27 @@ function scaleShiftedSplit5( [0, tHi.value], 250 ); - let P = new Group({ x, y }); - let R = new Group({ x: RMl[1], y: RMl[2] }); + let P = { x, y }; + let R = { x: createField(RMl[1]), y: createField(RMl[2]) }; let [t0, t1, t2, t3, t4] = tLo; // R = t4 ? R : R - P = ((t >> 4) + 2^250)P - R = Provable.if(t4, R, R.addNonZero(P.neg())); + R = Provable.if(t4, Point, R, addNonZero(R, negate(P))); // R = ((t >> 3) + 2^251)P // R = ((t >> 2) + 2^252)P // R = ((t >> 1) + 2^253)P for (let t of [t3, t2, t1]) { - R = R.addNonZero(R); - R = Provable.if(t, R.addNonZero(P), R); + R = addNonZero(R, R); + R = Provable.if(t, Point, addNonZero(R, P), R); } // R = (t + 2^254)P // in the final step, we allow a zero output to make it work for the 0 scalar - R = R.addNonZero(R); - R = Provable.if(t0, R.addNonZero(P, true), R); + R = addNonZero(R, R); + let { result, isInfinity } = add(R, P); + result = Provable.if(isInfinity, Point, { x: zero, y: zero }, result); + R = Provable.if(t0, Point, result, R); return R; } @@ -167,3 +171,19 @@ function add(g: Point, h: Point) { return { result: { x: x3, y: y3 }, isInfinity: inf }; } + +/** + * Addition that asserts the result is non-zero. + */ +function addNonZero(g: Point, h: Point) { + let { result, isInfinity } = add(g, h); + isInfinity.assertFalse(); + return result; +} + +/** + * Negates a point. + */ +function negate(g: Point): Point { + return { x: g.x, y: g.y.neg() }; +} diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index 8b15066770..c5853eb015 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -16,7 +16,7 @@ test(Random.field, Random.field, (a, s0, assert) => { const g = Group.from(x1, y1); const s = Field.from(s0); - runScale(g, s, (g, s) => scale(g, s), assert); + runScale(g, s, (g, s) => new Group(scale(g, s)), assert); }); // tests consistency between in- and out-circuit implementations From bb0716c58749522d17eed56a2cb51127f55d12ab Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Wed, 3 Apr 2024 13:54:29 +0200 Subject: [PATCH 11/45] simplify witness generation code --- src/lib/provable/gadgets/native-curve.ts | 72 ++++++++++-------------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 48610bedc8..3b8e020b2f 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -1,6 +1,6 @@ import type { Field } from '../field.js'; import type { Bool } from '../bool.js'; -import { Fq } from '../../../bindings/crypto/finite-field.js'; +import { Fp, 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'; @@ -8,7 +8,7 @@ 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, getBool, getField } from '../core/field-constructor.js'; +import { createField, getField } from '../core/field-constructor.js'; import { Snarky } from '../../../snarky.js'; import { Provable } from '../provable.js'; import { MlPair } from '../../ml/base.js'; @@ -118,58 +118,44 @@ function scaleShiftedSplit5( /** * Wraps the `EC_add` gate to perform complete addition of two non-zero curve points. */ -function add(g: Point, h: Point) { - const { x: x1, y: y1 } = g; - const { x: x2, y: y2 } = h; - - let zero = createField(0); - const Field = getField(); - const Bool = getBool(); - - let same_x = Provable.witness(Field, () => x1.equals(x2).toField()); - - let inf = Provable.witness(Bool, () => - x1.equals(x2).and(y1.equals(y2).not()) - ); - - let inf_z = Provable.witness(Field, () => { - if (y1.equals(y2).toBoolean()) return zero; - else if (x1.equals(x2).toBoolean()) return y2.sub(y1).inv(); - else return zero; +function add(g: Point, h: Point): { result: Point; isInfinity: Bool } { + // compute witnesses + let witnesses = exists(7, () => { + let x1 = g.x.toBigInt(); + let y1 = g.y.toBigInt(); + let x2 = h.x.toBigInt(); + let y2 = h.y.toBigInt(); + + let sameX = BigInt(x1 === x2); + let inf = BigInt(sameX && y1 !== y2); + let infZ = sameX ? Fp.inverse(y2 - y1) ?? 0n : 0n; + let x21Inv = Fp.inverse(x2 - x1) ?? 0n; + + let slopeDouble = Fp.div(3n * x1 ** 2n, 2n * y1) ?? 0n; + let slopeAdd = Fp.mul(y2 - y1, x21Inv); + let s = sameX ? slopeDouble : slopeAdd; + + let x3 = Fp.mod(s ** 2n - x1 - x2); + let y3 = Fp.mod(s * (x1 - x3) - y1); + + return [sameX, inf, infZ, x21Inv, s, x3, y3]; }); - let x21_inv = Provable.witness(Field, () => { - if (x1.equals(x2).toBoolean()) return zero; - else return x2.sub(x1).inv(); - }); - - let s = Provable.witness(Field, () => { - if (x1.equals(x2).toBoolean()) { - let x1_squared = x1.square(); - return x1_squared.add(x1_squared).add(x1_squared).div(y1.add(y1)); - } else return y2.sub(y1).div(x2.sub(x1)); - }); - - let x3 = Provable.witness(Field, () => { - return s.square().sub(x1.add(x2)); - }); - - let y3 = Provable.witness(Field, () => { - return s.mul(x1.sub(x3)).sub(y1); - }); + let [same_x, inf, inf_z, x21_inv, s, x3, y3] = witnesses; + let isInfinity = inf.assertBool(); Snarky.gates.ecAdd( - MlPair(x1.seal().value, y1.seal().value), - MlPair(x2.seal().value, y2.seal().value), + MlPair(g.x.seal().value, g.y.seal().value), + MlPair(h.x.seal().value, h.y.seal().value), MlPair(x3.value, y3.value), - inf.toField().value, + inf.value, same_x.value, s.value, inf_z.value, x21_inv.value ); - return { result: { x: x3, y: y3 }, isInfinity: inf }; + return { result: { x: x3, y: y3 }, isInfinity }; } /** From ddbec8708490f4cb2dbdcf2f7f23b7edc12159e2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 3 Apr 2024 16:32:20 +0200 Subject: [PATCH 12/45] change scalar type to shifted 5 / 250 representation --- src/lib/ml/conversion.ts | 2 +- src/lib/provable/crypto/nullifier.ts | 9 +- src/lib/provable/crypto/signature.ts | 53 ++----- src/lib/provable/gadgets/common.ts | 2 +- src/lib/provable/gadgets/native-curve.ts | 32 +++- src/lib/provable/group.ts | 9 +- src/lib/provable/scalar.ts | 180 +++++++++-------------- src/lib/provable/test/group.unit-test.ts | 14 +- src/lib/provable/test/scalar.test.ts | 4 +- 9 files changed, 129 insertions(+), 176 deletions(-) diff --git a/src/lib/ml/conversion.ts b/src/lib/ml/conversion.ts index 4ad09fe971..94a9358c51 100644 --- a/src/lib/ml/conversion.ts +++ b/src/lib/ml/conversion.ts @@ -72,7 +72,7 @@ function varToField(x: FieldVar): Field { } function fromScalar(s: Scalar): ScalarConst { - return [0, s.toConstant().constantValue]; + return [0, s.toBigInt()]; } function toScalar(s: ScalarConst) { return Scalar.from(s[1]); diff --git a/src/lib/provable/crypto/nullifier.ts b/src/lib/provable/crypto/nullifier.ts index f0ce72a65b..80ad805f71 100644 --- a/src/lib/provable/crypto/nullifier.ts +++ b/src/lib/provable/crypto/nullifier.ts @@ -3,7 +3,7 @@ import { Struct } from '../types/struct.js'; import { Field, Group, Scalar } from '../wrapped.js'; import { Poseidon } from './poseidon.js'; import { MerkleMapWitness } from '../merkle-map.js'; -import { PrivateKey, PublicKey, scaleShifted } from './signature.js'; +import { PrivateKey, PublicKey } from './signature.js'; import { Provable } from '../provable.js'; export { Nullifier }; @@ -50,6 +50,7 @@ class Nullifier extends Struct({ public: { nullifier, s }, private: { c }, } = this; + let cScalar = Scalar.fromNativeField(c); // generator let G = Group.generator; @@ -70,7 +71,7 @@ class Nullifier extends Struct({ // shifted scalar see https://github.com/o1-labs/o1js/blob/5333817a62890c43ac1b9cb345748984df271b62/src/lib/signature.ts#L220 // pk^c - let pk_c = scaleShifted(this.publicKey, Scalar.fromBits(c.toBits())); + let pk_c = this.publicKey.scale(cScalar); // g^r = g^s / pk^c let g_r = G.scale(s).sub(pk_c); @@ -79,9 +80,7 @@ class Nullifier extends Struct({ let h_m_pk_s = h_m_pk.scale(s); // h_m_pk_r = h(m,pk)^s / nullifier^c - let h_m_pk_s_div_nullifier_s = h_m_pk_s.sub( - scaleShifted(nullifier, Scalar.fromBits(c.toBits())) - ); + let h_m_pk_s_div_nullifier_s = h_m_pk_s.sub(nullifier.scale(cScalar)); // this is supposed to match the entries generated on "the other side" of the nullifier (mina-signer, in an wallet enclave) Poseidon.hash([ diff --git a/src/lib/provable/crypto/signature.ts b/src/lib/provable/crypto/signature.ts index 9a6159f933..3dc6d4bbc1 100644 --- a/src/lib/provable/crypto/signature.ts +++ b/src/lib/provable/crypto/signature.ts @@ -1,7 +1,6 @@ import { Field, Bool, Group, Scalar } from '../wrapped.js'; import { AnyConstructor } from '../types/struct.js'; import { hashWithPrefix } from './poseidon.js'; -import { Fq } from '../../../bindings/crypto/finite-field.js'; import { deriveNonce, Signature as SignatureBigint, @@ -11,16 +10,12 @@ import { PrivateKey as PrivateKeyBigint, PublicKey as PublicKeyBigint, } from '../../../mina-signer/src/curve-bigint.js'; -import { constantScalarToBigint } from '../scalar.js'; import { toConstantField } from '../field.js'; import { CircuitValue, prop } from '../types/circuit-value.js'; // external API export { PrivateKey, PublicKey, Signature }; -// internal API -export { scaleShifted }; - /** * A signing key. You can generate one via {@link PrivateKey.random}. */ @@ -71,7 +66,7 @@ class PrivateKey extends CircuitValue { * Convert this {@link PrivateKey} to a bigint */ toBigInt() { - return constantScalarToBigint(this.s, 'PrivateKey.toBigInt'); + return this.s.toBigInt(); } /** @@ -117,9 +112,7 @@ class PrivateKey extends CircuitValue { * @returns a base58 encoded string */ static toBase58(privateKey: { s: Scalar }) { - return PrivateKeyBigint.toBase58( - constantScalarToBigint(privateKey.s, 'PrivateKey.toBase58') - ); + return PrivateKeyBigint.toBase58(privateKey.s.toBigInt()); } } @@ -249,12 +242,13 @@ class Signature extends CircuitValue { * @returns a {@link Signature} */ static create(privKey: PrivateKey, msg: Field[]): Signature { - const publicKey = PublicKey.fromPrivateKey(privKey).toGroup(); - const d = privKey.s; + let publicKey = PublicKey.fromPrivateKey(privKey).toGroup(); + let d = privKey.s; + // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' // there's no consequences in practice and the signatures can be used with any network // if there needs to be a custom nonce, include it in the message itself - const kPrime = Scalar.from( + let kPrime = Scalar.from( deriveNonce( { fields: msg.map((f) => f.toBigInt()) }, { x: publicKey.x.toBigInt(), y: publicKey.y.toBigInt() }, @@ -262,16 +256,15 @@ class Signature extends CircuitValue { 'testnet' ) ); + let { x: r, y: ry } = Group.generator.scale(kPrime); - const k = ry.isOdd().toBoolean() ? kPrime.neg() : kPrime; + let k = ry.isOdd().toBoolean() ? kPrime.neg() : kPrime; let h = hashWithPrefix( signaturePrefix('testnet'), msg.concat([publicKey.x, publicKey.y, r]) ); - // TODO: Scalar.fromBits interprets the input as a "shifted scalar" - // therefore we have to unshift e before using it - let e = unshift(Scalar.fromBits(h.toBits())); - const s = e.mul(d).add(k); + let e = Scalar.fromNativeField(h); + let s = e.mul(d).add(k); return new Signature(r, s); } @@ -280,7 +273,8 @@ class Signature extends CircuitValue { * @returns a {@link Bool} */ verify(publicKey: PublicKey, msg: Field[]): Bool { - const point = publicKey.toGroup(); + let point = publicKey.toGroup(); + // we chose an arbitrary prefix for the signature, and it happened to be 'testnet' // there's no consequences in practice and the signatures can be used with any network // if there needs to be a custom nonce, include it in the message itself @@ -288,10 +282,9 @@ class Signature extends CircuitValue { signaturePrefix('testnet'), msg.concat([point.x, point.y, this.r]) ); - // TODO: Scalar.fromBits interprets the input as a "shifted scalar" - // therefore we have to use scaleShifted which is very inefficient - let e = Scalar.fromBits(h.toBits()); - let r = scaleShifted(point, e).neg().add(Group.generator.scale(this.s)); + + let e = Scalar.fromNativeField(h); + let r = point.scale(e).neg().add(Group.generator.scale(this.s)); return r.x.equals(this.r).and(r.y.isEven()); } @@ -311,19 +304,3 @@ class Signature extends CircuitValue { return SignatureBigint.toBase58({ r, s }); } } - -// performs scalar multiplication s*G assuming that instead of s, we got s' = 2s + 1 + 2^255 -// cost: 2x scale by constant, 1x scale by variable -function scaleShifted(point: Group, shiftedScalar: Scalar) { - let oneHalfGroup = point.scale(Scalar.from(oneHalf)); - let shiftGroup = oneHalfGroup.scale(Scalar.from(shift)); - return oneHalfGroup.scale(shiftedScalar).sub(shiftGroup); -} -// returns s, assuming that instead of s, we got s' = 2s + 1 + 2^255 -// (only works out of snark) -function unshift(shiftedScalar: Scalar) { - return shiftedScalar.sub(Scalar.from(shift)).mul(Scalar.from(oneHalf)); -} - -let shift = Fq.mod(1n + 2n ** 255n); -let oneHalf = Fq.inverse(2n)!; diff --git a/src/lib/provable/gadgets/common.ts b/src/lib/provable/gadgets/common.ts index b49acecae2..4c8c1ddca0 100644 --- a/src/lib/provable/gadgets/common.ts +++ b/src/lib/provable/gadgets/common.ts @@ -79,7 +79,7 @@ function divideWithRemainder(numerator: bigint, denominator: bigint) { // pack bools into a single field element /** - * Helper function to pack bits into a single field element. + * Helper function to provably pack bits into a single field element. * Just returns the sum without any boolean checks. */ function packBits(bits: (Field | Bool)[]): Field { diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 3b8e020b2f..4e2c2251cf 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -14,9 +14,10 @@ import { Provable } from '../provable.js'; import { MlPair } from '../../ml/base.js'; import { provable } from '../types/provable-derivers.js'; -export { scale, scaleShiftedSplit5, add }; +export { scale, fieldToShiftedSplit5, scaleShiftedSplit5, add }; type Point = { x: Field; y: Field }; +type ShiftedScalar = { low5: TupleN; high250: Field }; /** * Gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. @@ -31,6 +32,28 @@ function scale(P: Point, s: Field): Point { ); return { x: createField(sP.x), y: createField(sP.y) }; } + // compute t = s - 2^254 mod q using foreign field subtraction, and split into 5 low bits and 250 high bits + let t = fieldToShiftedSplit5(s); + + // return (t + 2^254)*P = (s - 2^254 + 2^254)*P = s*P + return scaleShiftedSplit5(P, t); +} + +/** + * Converts a field element s to a shifted representation t = s = 2^254 mod q, + * where t is represented as a 5-bit low part and a 250-bit high part. + * + * This is the representation we use for scalars, since it can be used as input to `scaleShiftedSplit5()`. + */ +function fieldToShiftedSplit5(s: Field): ShiftedScalar { + // constant case + if (s.isConstant()) { + let t = Fq.mod(s.toBigInt() - (1n << 254n)); + let low5 = createField(t & 0x1fn).toBits(5); + let high250 = createField(t >> 5n); + return { low5: TupleN.fromArray(5, low5), high250 }; + } + // compute t = s - 2^254 mod q using foreign field subtraction let sBig = fieldToField3(s); let twoTo254 = Field3.from(1n << 254n); @@ -52,13 +75,13 @@ function scale(P: Point, s: Field): Point { .assertEquals(t0); // pack tHi + // proves that tHi is in [0, 2^250) 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); + return { low5: tLoBools, high250: tHi }; } /** @@ -69,8 +92,7 @@ function scale(P: Point, s: Field): Point { */ function scaleShiftedSplit5( { x, y }: Point, - tHi: Field, - tLo: TupleN + { low5: tLo, high250: tHi }: ShiftedScalar ): Point { // constant case if (isConstant(x, y, tHi, ...tLo)) { diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index ecf29d0f4a..503fe522ea 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -1,7 +1,6 @@ import { Field } from './field.js'; import { FieldVar } from './core/fieldvar.js'; import { Scalar } from './scalar.js'; -import { Snarky } from '../../snarky.js'; import { Fp } from '../../bindings/crypto/finite-field.js'; import { GroupAffine, @@ -11,7 +10,7 @@ import { import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { assert } from '../util/assert.js'; -import { add } from './gadgets/native-curve.js'; +import { add, scaleShiftedSplit5 } from './gadgets/native-curve.js'; export { Group }; @@ -181,10 +180,8 @@ class Group { let g_proj = Pallas.scale(toProjective(this), scalar.toBigInt()); return fromProjective(g_proj); } else { - let [...bits] = scalar.shiftedBits; - bits.reverse(); - let [, x, y] = Snarky.group.scale(toTuple(this), [0, ...bits]); - return new Group({ x, y }); + let result = scaleShiftedSplit5(this, scalar); + return new Group(result); } } diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index 45b310e043..058646105a 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -1,37 +1,39 @@ -import { Snarky } from '../../snarky.js'; import { Fq } from '../../bindings/crypto/finite-field.js'; import { Scalar as SignableFq } from '../../mina-signer/src/curve-bigint.js'; import { Field } from './field.js'; -import { FieldVar, FieldConst } from './core/fieldvar.js'; +import { FieldVar } from './core/fieldvar.js'; import { Bool } from './bool.js'; +import { TupleN } from '../util/types.js'; +import { fieldToShiftedSplit5 } from './gadgets/native-curve.js'; +import { isConstant, packBits } from './gadgets/common.js'; +import { Provable } from './provable.js'; +import { assert } from '../util/assert.js'; +import type { HashInput } from './types/provable-derivers.js'; -export { Scalar, ScalarConst, unshift, shift }; - -// internal API -export { constantScalarToBigint }; +export { Scalar, ScalarConst }; type ScalarConst = [0, bigint]; let scalarShift = Fq.mod(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; -type ConstantScalar = Scalar & { constantValue: bigint }; - /** * Represents a {@link Scalar}. */ class Scalar { - shiftedBits: FieldVar[]; - constantValue?: bigint; + /** + * We represent a scalar s in shifted form `t = s - 2^254 mod q, + * split into its low 5 bits (t & 0x1f) and high 250 bits (t >> 5). + * The reason is that we can efficiently compute the scalar multiplication `(t + 2^254) * P = s * P`. + */ + low5: TupleN; + high250: Field; static ORDER = Fq.modulus; - private constructor(bits: FieldVar[], constantValue?: bigint) { - this.shiftedBits = bits; - constantValue ??= toConstantScalar(bits); - if (constantValue !== undefined) { - this.constantValue = constantValue; - } + private constructor(low5: TupleN, high250: Field) { + this.low5 = low5; + this.high250 = high250; } /** @@ -39,19 +41,31 @@ class Scalar { * * If the input is too large, it is reduced modulo the scalar field size. */ - static from(x: Scalar | bigint | number | string) { - if (x instanceof Scalar) return x; - let scalar = Fq.mod(BigInt(x)); - let bits = toBits(scalar); - return new Scalar(bits, scalar); + static from(s: Scalar | bigint | number | string): Scalar { + if (s instanceof Scalar) return s; + let t = Fq.mod(BigInt(s) - (1n << 254n)); + let low5 = new Field(t & 0x1fn).toBits(5); + let high250 = new Field(t >> 5n); + return new Scalar(TupleN.fromArray(5, low5), high250); + } + + /** + * Provable method to convert a {@link Field} into a {@link Scalar}. + * + * This is always possible and unambiguous, since the scalar field is larger than the base field. + */ + static fromNativeField(s: Field): Scalar { + let { low5, high250 } = fieldToShiftedSplit5(s); + return new Scalar(low5, high250); } /** * Check whether this {@link Scalar} is a hard-coded constant in the constraint system. * If a {@link Scalar} is constructed outside provable code, it is a constant. */ - isConstant(): this is Scalar & { constantValue: bigint } { - return this.constantValue !== undefined; + isConstant() { + let { low5, high250 } = this; + return isConstant(high250, ...low5); } /** @@ -61,33 +75,25 @@ class Scalar { * * See {@link FieldVar} for an explanation of constants vs. variables. */ - toConstant(): ConstantScalar { - if (this.constantValue !== undefined) return this as ConstantScalar; - let constBits = this.shiftedBits.map((b) => - FieldVar.constant(Snarky.field.readVar(b)) - ); - return new Scalar(constBits) as ConstantScalar; + toConstant() { + if (this.isConstant()) return this; + return Provable.toConstant(Scalar, this); } /** * Convert this {@link Scalar} into a bigint */ toBigInt() { - return assertConstant(this, 'toBigInt'); + let { low5, high250 } = this.toConstant(); + return Fq.add( + packBits(low5).toBigInt() + (high250.toBigInt() << 5n), + 1n << 254n + ); } - // TODO: fix this API. we should represent "shifted status" internally and use - // and use shifted Group.scale only if the scalar bits representation is shifted - /** - * Creates a data structure from an array of serialized {@link Bool}. - * - * **Warning**: The bits are interpreted as the bits of 2s + 1 + 2^255, where s is the Scalar. - */ - static fromBits(bits: Bool[]) { - return Scalar.fromFields([ - ...bits.map((b) => b.toField()), - ...Array(Fq.sizeInBits - bits.length).fill(new Bool(false)), - ]); + static fromBits(bits: Bool[]): Scalar { + // TODO + throw Error('Not implemented'); } /** @@ -161,16 +167,6 @@ class Scalar { return Scalar.from(z); } - // TODO don't leak 'shifting' to the user and remove these methods - shift() { - let x = assertConstant(this, 'shift'); - return Scalar.from(shift(x)); - } - unshift() { - let x = assertConstant(this, 'unshift'); - return Scalar.from(unshift(x)); - } - /** * Serialize a Scalar into a Field element plus one bit, where the bit is represented as a Bool. * @@ -203,7 +199,7 @@ class Scalar { * The fields are not constrained to be boolean. */ static toFields(x: Scalar) { - return x.shiftedBits.map((b) => new Field(b)); + return [...x.low5.map((b) => b.toField()), x.high250]; } /** @@ -230,8 +226,8 @@ class Scalar { * @return An object where the `fields` key is a {@link Field} array of length 1 created from this {@link Field}. * */ - static toInput(x: Scalar): { packed: [Field, number][] } { - return { packed: Scalar.toFields(x).map((f) => [f, 1]) }; + static toInput(x: Scalar): HashInput { + return { fields: [x.high250], packed: x.low5.map((f) => [f.toField(), 1]) }; } /** @@ -249,7 +245,13 @@ class Scalar { * Creates a data structure from an array of serialized {@link Field} elements. */ static fromFields(fields: Field[]): Scalar { - return new Scalar(fields.map((x) => x.value)); + assert( + fields.length === 6, + `Scalar.fromFields(): expected 6 fields, got ${fields.length}` + ); + let low5 = fields.slice(0, 5).map(Bool.Unsafe.fromField); + let high250 = fields[5]; + return new Scalar(TupleN.fromArray(5, low5), high250); } /** @@ -258,28 +260,18 @@ class Scalar { * Returns the size of this type in {@link Field} elements. */ static sizeInFields(): number { - return Fq.sizeInBits; + return 6; } /** * Part of the {@link Provable} interface. - * - * Does nothing. */ - static check() { - /* It is not necessary to boolean constrain the bits of a scalar for the following - reasons: - - The only provable methods which can be called with a scalar value are - - - if - - assertEqual - - equal - - Group.scale - - The only one of these whose behavior depends on the bit values of the input scalars - is Group.scale, and that function boolean constrains the scalar input itself. + static check(s: Scalar) { + /** + * It is not necessary to constrain the range of high250, because the only provable operation on Scalar + * which relies on that range is scalar multiplication -- which constrains the range itself. */ + return s.low5.forEach(Bool.check); } // ProvableExtended @@ -311,29 +303,13 @@ class Scalar { // internal helpers -function assertConstant(x: Scalar, name: string) { - return constantScalarToBigint(x, `Scalar.${name}`); -} - -function toConstantScalar(bits: FieldVar[]): bigint | undefined { - if (bits.length !== Fq.sizeInBits) - throw Error( - `Scalar: expected bits array of length ${Fq.sizeInBits}, got ${bits.length}` - ); - let constantBits = Array(bits.length); - for (let i = 0; i < bits.length; i++) { - let bool = bits[i]; - if (!FieldVar.isConstant(bool)) return undefined; - constantBits[i] = FieldConst.equal(bool[1], FieldConst[1]); - } - let sShifted = SignableFq.fromBits(constantBits); - return shift(sShifted); -} - -function toBits(constantValue: bigint): FieldVar[] { - return SignableFq.toBits(unshift(constantValue)).map((b) => - FieldVar.constant(BigInt(b)) +function assertConstant(x: Scalar, name: string): bigint { + assert( + x.isConstant(), + `${name}() is not available in provable code. +That means it can't be called in a @method or similar environment, and there's no alternative implemented to achieve that.` ); + return x.toBigInt(); } /** @@ -349,19 +325,3 @@ function shift(s: bigint) { function unshift(s: bigint) { return Fq.mul(Fq.sub(s, scalarShift), oneHalf); } - -function constToBigint(x: ScalarConst) { - return x[1]; -} -function constFromBigint(x: bigint): ScalarConst { - return [0, x]; -} - -function constantScalarToBigint(s: Scalar, name: string) { - if (s.constantValue === undefined) - throw Error( - `${name}() is not available in provable code. -That means it can't be called in a @method or similar environment, and there's no alternative implemented to achieve that.` - ); - return s.constantValue; -} diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index c5853eb015..b8889797a8 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -3,8 +3,7 @@ import { test, Random } from '../../testing/property.js'; import { Provable } from '../provable.js'; import { Poseidon } from '../../../mina-signer/src/poseidon-bigint.js'; import { runAndCheckSync } from '../core/provable-context.js'; -import { scale } from '../gadgets/native-curve.js'; -import { Field } from '../field.js'; +import { Scalar } from '../scalar.js'; console.log('group consistency tests'); @@ -14,9 +13,8 @@ test(Random.field, Random.field, (a, s0, assert) => { y: { x0: y1 }, } = Poseidon.hashToGroup([a])!; const g = Group.from(x1, y1); - const s = Field.from(s0); - - runScale(g, s, (g, s) => new Group(scale(g, s)), assert); + const s = Scalar.from(s0); + runScale(g, s, (g, s) => g.scale(s), assert); }); // tests consistency between in- and out-circuit implementations @@ -87,8 +85,8 @@ function run( function runScale( g: Group, - s: Field, - f: (g1: Group, s: Field) => Group, + s: Scalar, + f: (g1: Group, s: Scalar) => Group, assert: (b: boolean, message?: string | undefined) => void ) { let result_out_circuit = f(g, s); @@ -96,7 +94,7 @@ function runScale( runAndCheckSync(() => { let result_in_circuit = f( Provable.witness(Group, () => g), - Provable.witness(Field, () => s) + Provable.witness(Scalar, () => s) ); Provable.asProver(() => { diff --git a/src/lib/provable/test/scalar.test.ts b/src/lib/provable/test/scalar.test.ts index caf418c362..bf0f717d21 100644 --- a/src/lib/provable/test/scalar.test.ts +++ b/src/lib/provable/test/scalar.test.ts @@ -53,10 +53,10 @@ describe('scalar', () => { }); describe('fromBits', () => { - it('should return a shifted scalar', () => { + it('should return a scalar with the same bigint value', () => { let x = Field.random(); let bits_ = x.toBits(); - let s = Scalar.fromBits(bits_).unshift(); + let s = Scalar.fromBits(bits_); expect(x.toBigInt()).toEqual(s.toBigInt()); }); }); From 011d6a81088e2bccdb8f674fec2660f00cb48f79 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 3 Apr 2024 17:21:26 +0200 Subject: [PATCH 13/45] bring back fromBits, remove unused shifting methods --- src/lib/provable/gadgets/foreign-field.ts | 29 ++++++++++++++-- src/lib/provable/gadgets/native-curve.ts | 27 +++++++++++---- src/lib/provable/scalar.ts | 42 +++++++++++------------ 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/src/lib/provable/gadgets/foreign-field.ts b/src/lib/provable/gadgets/foreign-field.ts index df864a7a6b..685c138881 100644 --- a/src/lib/provable/gadgets/foreign-field.ts +++ b/src/lib/provable/gadgets/foreign-field.ts @@ -23,13 +23,27 @@ import { l3, compactMultiRangeCheck, } from './range-check.js'; -import { createBool, createField } from '../core/field-constructor.js'; +import { + createBool, + createField, + getField, +} from '../core/field-constructor.js'; +import type { Bool } from '../bool.js'; // external API export { ForeignField, Field3 }; // internal API -export { bigint3, Sign, split, combine, weakBound, Sum, assertMul }; +export { + bigint3, + Sign, + split, + combine, + weakBound, + Sum, + assertMul, + field3FromBits, +}; /** * A 3-tuple of Fields, representing a 3-limb bigint. @@ -770,6 +784,17 @@ function assertLessThanOrEqual(x: Field3, y: bigint | Field3) { sum([y_, x], [-1n], 0n); } +// Field3 from/to bits + +function field3FromBits(bits: Bool[]): Field3 { + const Field = getField(); + let limbSize = Number(l); + let l0 = Field.fromBits(bits.slice(0 * limbSize, 1 * limbSize)); + let l1 = Field.fromBits(bits.slice(1 * limbSize, 2 * limbSize)); + let l2 = Field.fromBits(bits.slice(2 * limbSize, 3 * limbSize)); + return [l0, l1, l2]; +} + // helpers /** diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 4e2c2251cf..7f759fde1c 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -14,7 +14,13 @@ import { Provable } from '../provable.js'; import { MlPair } from '../../ml/base.js'; import { provable } from '../types/provable-derivers.js'; -export { scale, fieldToShiftedSplit5, scaleShiftedSplit5, add }; +export { + scale, + fieldToShiftedScalar, + field3ToShiftedScalar, + scaleShiftedSplit5, + add, +}; type Point = { x: Field; y: Field }; type ShiftedScalar = { low5: TupleN; high250: Field }; @@ -33,7 +39,7 @@ function scale(P: Point, s: Field): Point { return { x: createField(sP.x), y: createField(sP.y) }; } // compute t = s - 2^254 mod q using foreign field subtraction, and split into 5 low bits and 250 high bits - let t = fieldToShiftedSplit5(s); + let t = fieldToShiftedScalar(s); // return (t + 2^254)*P = (s - 2^254 + 2^254)*P = s*P return scaleShiftedSplit5(P, t); @@ -45,19 +51,26 @@ function scale(P: Point, s: Field): Point { * * This is the representation we use for scalars, since it can be used as input to `scaleShiftedSplit5()`. */ -function fieldToShiftedSplit5(s: Field): ShiftedScalar { +function fieldToShiftedScalar(s: Field): ShiftedScalar { + return field3ToShiftedScalar(fieldToField3(s)); +} + +/** + * Converts a 3-limb bigint to a shifted representation t = s = 2^254 mod q, + * where t is represented as a 5-bit low part and a 250-bit high part. + */ +function field3ToShiftedScalar(s: Field3): ShiftedScalar { // constant case - if (s.isConstant()) { - let t = Fq.mod(s.toBigInt() - (1n << 254n)); + if (Field3.isConstant(s)) { + let t = Fq.mod(Field3.toBigint(s) - (1n << 254n)); let low5 = createField(t & 0x1fn).toBits(5); let high250 = createField(t >> 5n); return { low5: TupleN.fromArray(5, low5), high250 }; } // 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); + let [t0, t1, t2] = ForeignField.sub(s, twoTo254, Fq.modulus); // split t into 250 high bits and 5 low bits // => split t0 into [5, 83] diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index 058646105a..ce39c53aa8 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -1,22 +1,23 @@ import { Fq } from '../../bindings/crypto/finite-field.js'; import { Scalar as SignableFq } from '../../mina-signer/src/curve-bigint.js'; -import { Field } from './field.js'; +import { Field, checkBitLength } from './field.js'; import { FieldVar } from './core/fieldvar.js'; import { Bool } from './bool.js'; import { TupleN } from '../util/types.js'; -import { fieldToShiftedSplit5 } from './gadgets/native-curve.js'; +import { + field3ToShiftedScalar, + fieldToShiftedScalar, +} from './gadgets/native-curve.js'; import { isConstant, packBits } from './gadgets/common.js'; import { Provable } from './provable.js'; import { assert } from '../util/assert.js'; import type { HashInput } from './types/provable-derivers.js'; +import { field3FromBits } from './gadgets/foreign-field.js'; export { Scalar, ScalarConst }; type ScalarConst = [0, bigint]; -let scalarShift = Fq.mod(1n + 2n ** 255n); -let oneHalf = Fq.inverse(2n)!; - /** * Represents a {@link Scalar}. */ @@ -55,7 +56,7 @@ class Scalar { * This is always possible and unambiguous, since the scalar field is larger than the base field. */ static fromNativeField(s: Field): Scalar { - let { low5, high250 } = fieldToShiftedSplit5(s); + let { low5, high250 } = fieldToShiftedScalar(s); return new Scalar(low5, high250); } @@ -91,9 +92,20 @@ class Scalar { ); } + /** + * Creates a Scalar from an array of {@link Bool}. + * This method is provable. + */ static fromBits(bits: Bool[]): Scalar { - // TODO - throw Error('Not implemented'); + let length = bits.length; + checkBitLength('Scalar.fromBits()', length, 255); + + // convert bits to a 3-limb bigint + let sBig = field3FromBits(bits); + + // convert to shifted representation + let { low5, high250 } = field3ToShiftedScalar(sBig); + return new Scalar(low5, high250); } /** @@ -311,17 +323,3 @@ That means it can't be called in a @method or similar environment, and there's n ); return x.toBigInt(); } - -/** - * s -> 2s + 1 + 2^255 - */ -function shift(s: bigint) { - return Fq.add(Fq.add(s, s), scalarShift); -} - -/** - * inverse of shift, 2s + 1 + 2^255 -> s - */ -function unshift(s: bigint) { - return Fq.mul(Fq.sub(s, scalarShift), oneHalf); -} From bc9b4ae476c192f3c978fda6b8cb3ab412a8d778 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 3 Apr 2024 17:27:11 +0200 Subject: [PATCH 14/45] add scale from field element to test --- src/lib/provable/test/group.unit-test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index b8889797a8..188c04285c 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -4,6 +4,7 @@ import { Provable } from '../provable.js'; import { Poseidon } from '../../../mina-signer/src/poseidon-bigint.js'; import { runAndCheckSync } from '../core/provable-context.js'; import { Scalar } from '../scalar.js'; +import { Field } from '../field.js'; console.log('group consistency tests'); @@ -15,6 +16,9 @@ test(Random.field, Random.field, (a, s0, assert) => { const g = Group.from(x1, y1); const s = Scalar.from(s0); runScale(g, s, (g, s) => g.scale(s), assert); + + const sField = Field.from(s0); + runScale(g, sField, (g, s) => g.scale(Scalar.fromNativeField(s)), assert); }); // tests consistency between in- and out-circuit implementations @@ -83,10 +87,10 @@ function run( }); } -function runScale( +function runScale( g: Group, - s: Scalar, - f: (g1: Group, s: Scalar) => Group, + s: T, + f: (g1: Group, s: T) => Group, assert: (b: boolean, message?: string | undefined) => void ) { let result_out_circuit = f(g, s); @@ -94,7 +98,7 @@ function runScale( runAndCheckSync(() => { let result_in_circuit = f( Provable.witness(Group, () => g), - Provable.witness(Scalar, () => s) + Provable.witness(s.constructor as any, (): T => s) ); Provable.asProver(() => { From b508437789fb4b55f148b05d4d874540a34cb506 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 3 Apr 2024 17:33:35 +0200 Subject: [PATCH 15/45] bindings --- src/bindings | 2 +- src/snarky.d.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/bindings b/src/bindings index 3fca906694..ace27f1ab1 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 3fca906694f102dc60bf64745324de234cd6812d +Subproject commit ace27f1ab1778c6e533baf523a1a0d2355d6d0ea diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 0e09081798..f86e4d564d 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -347,8 +347,6 @@ declare const Snarky: { }; group: { - scale(p: MlGroup, s: MlArray): MlGroup; - /** * Computes `(2*s + 1 + 2^numBits) * P` and also returns the bits of s (which are proven correct). * From 0f93cc2b9872f1c9f51510b554cb662ff463eb04 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 3 Apr 2024 17:38:14 +0200 Subject: [PATCH 16/45] dump vks --- src/lib/provable/gadgets/native-curve.ts | 1 + tests/vk-regression/vk-regression.json | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 7f759fde1c..4998993805 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -120,6 +120,7 @@ function scaleShiftedSplit5( const zero = createField(0n); // R = (2*(t >> 5) + 1 + 2^250)P + // also proves that tHi is in [0, 2^250) let [, RMl] = Snarky.group.scaleFastUnpack( [0, x.value, y.value], [0, tHi.value], diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 5f672b9cc8..475ec3c697 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -50,16 +50,16 @@ } }, "HelloWorld": { - "digest": "3a585b00d88cee594f508ec18c5625a189fc7c3f1308c05fd1ac118cfc8c8705", + "digest": "e04e3a9ffe27c83ecbbcb72ea2e775df96de8f6215db698139b5d5f4898a230", "methods": { "update": { - "rows": 585, - "digest": "2bb2e4397d8fda256fcbe6ac1677991d" + "rows": 492, + "digest": "0fa169ea122f0b26ef92f1112836828f" } }, "verificationKey": { - "data": "AABnIUtBjexnheGU45jDLZlzdEIAYFcRi+aW1GqRaoZWFXXDP+NCOlX280XhOT9Cbb9LPxS0no5odvWi86abqw8UZxrm8etZg82vugnGjikslSBGhtwipjyp0Qo5LfTmwzO6qmGnWieyABMDgCGqCmwJxW2g+adyu9hhmuse/WhnJbMQAiQEtmzoyrLb5qe7Lm/8aanBXocSGEpuD7eIL4IO2z42fOrH0VqcIrp8qLy460w1R0aAMaPZ02npDOxvrxTLtpfVAyIY06XCc4grEuoHcIf8lic76syVofMXaoIDE0mDadgIeIR1VrdeEeufn2VabqLDTAs8gWTlv2fCeeMp8I6db/g6R2avb6C2WoKszKThWN6K8SB184sqMuT3lhFWAIno6nX2SUuxQVWLMT2T6a3DAC5C1qYDN3WlsH2cFlk2VoGAmZ3cM+LDtcGgSWePM3bfiGr3lZWBaorws4QFIB40UOKCK4BWvbl7fcSRY1+3LR1e04nf0kxrfIYZ3wapKc6XGVKPJjpLvlBEk0AQPNXIQTdRWTddWJUPA5SiEP04iqgbpetZqbx82xQ06Dr4xtOP0vwcZwGIY+LGndM7ABi1UZKVDIh9JMJG5gFlguXwEaL1jxv51PzVpMQabi0nn8MD3S6QIaaHCV1IqFZKZ8WMkwiam27QuHmocobreDwkCdYBNnekfSPubsOHugj7msm+wpsabOGPdol/QHJNDAlUk77WjZEpQmxHOiE66K7r77+16lRbgbrkbIXxAXsKVrMzwyMWVfWzHqZkN2FsAMBYWJO1x8lqY4XukKRCnjtelAq+gZ4ljp7YlQFnM3GLA3oGCvQyIqwePiCWoZpJA4SwswCa2uNPZ+7PMzZbMzddVkwj921337rtBzP9OYwXIe6aPdqI21kTf4KBc1Nu+071d484vfsxBEpIj2VWET0bk94MYH2w1ThMqEOjvdLH7Rjb2yq6S/pHxzA0+6u7PW8S6rE/B0hQj+15Yefwb0l3QTzgj2x0JDSZLXfM9v4efnApb41IrA79gkBNuxttHYzqHYYibN2U2SngEk5X3CGet4XEfqSkA6c4ccipAqKRh0zlbk5WpvyxxgfhOphEAxSB3dphR1+4/fKCaDZiM7W1Ehtv7L91QEgycB7lkOo31xGBWvFCmOja1I2j18ANyznh7ppyfrE0KDk4f6tL+TyakQKlZim7WJBz45cwQ/Y+wzWQL4N92uh2awCSzau1Kn4/CBjw+Oq7HSz/k1oL0J5PGao68tAiQD0tOcEhUIsHD36inwHMxroxJVL8zLKdkAE8BM7WITJyXL9FWp5q/BAVTIML5L7BEZzQkLVIiVW66mRV1+jKE4Y1u3exah/mJR43GZoyn4SNlnjtlQG9S+VLerddhdyF3qnc3t3o8ik6x/0qD6g+IBuY1OIl7EG6UUp1avg2YGgPiOrfGnX6LBrtboOkA0iWI1TRushJ744l87XpZKLfHr90QzBQkgAPCcEAG08LLim/FcnS3kAjfAE0lxyjrSMY5s1+SbnnTlAMOacaPE09xwW9FDvwkV3FNaBTX7srLovC9VgavOPx5QN0gP+hy8/zdnwG0aZvLCDNHa2NOxe8q35gdA42Ru+QMdKLYIuw/xGXYjzoOdYr/WDYjNQzzDKeHwzx/vqyFLkNA/mMzaNMxjQTlfrFnuTzj8oyY7EuUdwif18wcTsqXAMUVhRtN690neD7VWL63HgR7q5p3BA5J6NltbjPXNJLP5dMTi71vUoBndyX3XlcObmIkZKa2YD927QsfATwyg4mfkcjMw1nboag8T9VcExC6cY5nrNcBvXmtLz3pUiTcSj/UgCgZWa0GOGBS+6P5PsrtGDsvLrmqA2ed33hv1UiHwDLSTXvh+zFNDPSPTnoT53wP7DE0FjilgeWv+F5o851CnFU50J/gtMwWrrmw5bjAKk+Dh39+0B0OfLxG5DFjnwmv3JkQzrnB+GFZ22PEHdZZ9PiP6C7/kMFaB6+V2gvrDJ6BTesrreFShEyHJKvtdPWeE9WWm8GmSY6C/PjGryIERm7Z2za+3ze7ZEUQx9RxrzYnNrGSqGJdtDNk0pHDG4khS3+AfieLdNKHdr19IfUDzCgCAvY5ukD0N7A9qxW6C7VPVvqWINu1zU27wupHjnu85MW+cIs5RPIrVS6Xry0Api3cCFuPK2pfEpmDy/K3AtLptp7NDJRcD6ym5K26rcdle9voOz4VLuCe98Z/rCV2W8fPfBGjPWMX2pjZL78oDFdbwKSBi1ipV79IKNCY6d3kSZpeyFlJZX+MBY5NXKLHygje3kDLDQ1eBKb4ORKOrEVw/Mxp7jGrAlG0Ac7y/s+gkC01hulcJ6CIkgIUZiS/TYfsH+F3oqmNXzue0jHLwQ=", - "hash": "11707695774788847345167402900704548924949509205484364347348209235004076233330" + "data": "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", + "hash": "27370250573863895519732939618815084579192113056549266298181666785275942621518" } }, "TokenContract": { @@ -124,8 +124,8 @@ "digest": "ddb709883792aa08b3bdfb69206a9f69" }, "scale": { - "rows": 113, - "digest": "b912611500f01c57177285f538438abc" + "rows": 145, + "digest": "831dd3a44b52fb75ef3dbbc34b4ed8c0" }, "equals": { "rows": 37, @@ -288,7 +288,7 @@ } }, "diverse": { - "digest": "3cf637b1eb3fb7e15263493b9778078605e6c9b4921976b46e08451e770bd665", + "digest": "264204561bed0bf5488984c4088912525376de470891ff04907b63277980a2eb", "methods": { "ecdsa": { "rows": 28182, @@ -299,8 +299,8 @@ "digest": "c23e00e466878466ae8ab23bde562792" }, "pallas": { - "rows": 891, - "digest": "e5f666ba87d050daea47081212cec058" + "rows": 525, + "digest": "cee3f71cfd75be56a2cc801be8959ea8" }, "poseidon": { "rows": 946, @@ -316,8 +316,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "10231236168606718499352904475791249038227547944081537794871338618093559489302" + "data": "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", + "hash": "28944110445825075951165306337055835067870337140854205539564223784418561300672" } } } \ No newline at end of file From 04f8648f332c924f1e5db8a89a940bd60b2a4899 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 4 Apr 2024 15:51:39 +0200 Subject: [PATCH 17/45] switch to repr with 1 low bit --- src/lib/provable/gadgets/native-curve.ts | 132 +++++++++++------------ src/lib/provable/group.ts | 4 +- src/lib/provable/scalar.ts | 70 ++++++------ 3 files changed, 100 insertions(+), 106 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 4998993805..625fade1d4 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -4,11 +4,14 @@ import { Fp, 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 { exists } from '../core/exists.js'; +import { bit, isConstant } from './common.js'; import { l } from './range-check.js'; -import { createField, getField } from '../core/field-constructor.js'; +import { + createBool, + createField, + getField, +} from '../core/field-constructor.js'; import { Snarky } from '../../../snarky.js'; import { Provable } from '../provable.js'; import { MlPair } from '../../ml/base.js'; @@ -18,12 +21,13 @@ export { scale, fieldToShiftedScalar, field3ToShiftedScalar, - scaleShiftedSplit5, + scaleShifted, add, + ShiftedScalar, }; type Point = { x: Field; y: Field }; -type ShiftedScalar = { low5: TupleN; high250: Field }; +type ShiftedScalar = { lowBit: Bool; high254: Field }; /** * Gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. @@ -42,7 +46,7 @@ function scale(P: Point, s: Field): Point { let t = fieldToShiftedScalar(s); // return (t + 2^254)*P = (s - 2^254 + 2^254)*P = s*P - return scaleShiftedSplit5(P, t); + return scaleShifted(P, t); } /** @@ -52,66 +56,75 @@ function scale(P: Point, s: Field): Point { * This is the representation we use for scalars, since it can be used as input to `scaleShiftedSplit5()`. */ function fieldToShiftedScalar(s: Field): ShiftedScalar { - return field3ToShiftedScalar(fieldToField3(s)); + let sBig = fieldToField3(s); + + // assert that sBig is canonical mod p, so that we can't add (kp mod q) factors by doing things modulo q + ForeignField.assertLessThan(sBig, Fp.modulus); + + return field3ToShiftedScalar(sBig); } /** - * Converts a 3-limb bigint to a shifted representation t = s = 2^254 mod q, - * where t is represented as a 5-bit low part and a 250-bit high part. + * Converts a 3-limb bigint to a shifted representation t = s - 2^255 mod q, + * where t is represented as a low bit and a 254-bit high part. */ function field3ToShiftedScalar(s: Field3): ShiftedScalar { // constant case if (Field3.isConstant(s)) { - let t = Fq.mod(Field3.toBigint(s) - (1n << 254n)); - let low5 = createField(t & 0x1fn).toBits(5); - let high250 = createField(t >> 5n); - return { low5: TupleN.fromArray(5, low5), high250 }; + let t = Fq.mod(Field3.toBigint(s) - (1n << 255n)); + let lowBit = createBool((t & 1n) === 1n); + let high254 = createField(t >> 1n); + return { lowBit, high254 }; } - // compute t = s - 2^254 mod q using foreign field subtraction - let twoTo254 = Field3.from(1n << 254n); - let [t0, t1, t2] = ForeignField.sub(s, twoTo254, Fq.modulus); + // compute t = s - 2^255 mod q using foreign field subtraction + let twoTo255 = Field3.from(Fq.mod(1n << 255n)); + let t = ForeignField.sub(s, twoTo255, 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)]; + // it's necessary to prove that t is canonical -- otherwise its bit representation is ambiguous + ForeignField.assertLessThan(t, Fq.modulus); + + let [t0, t1, t2] = t; + + // split t into 254 high bits and a low bit + // => split t0 into [1, 87] + let [tLo, tHi0] = exists(2, () => { + let t0_ = t0.toBigInt(); + return [bit(t0_, 0), t0_ >> 1n]; }); - let tLoBools = TupleN.map(tLo, (x) => x.assertBool()); - let tHi0 = existsOne(() => t0.toBigInt() >> 5n); + let tLoBool = tLo.assertBool(); // prove split - // since we know that t0 < 2^88, this proves that t0High < 2^83 - packBits(tLo) - .add(tHi0.mul(1n << 5n)) - .assertEquals(t0); + // since we know that t0 < 2^88, this proves that t0High < 2^87 + tLo.add(tHi0.mul(2n)).assertEquals(t0); // pack tHi - // proves that tHi is in [0, 2^250) let tHi = tHi0 - .add(t1.mul(1n << (l - 5n))) - .add(t2.mul(1n << (2n * l - 5n))) + .add(t1.mul(1n << (l - 1n))) + .add(t2.mul(1n << (2n * l - 1n))) .seal(); - return { low5: tLoBools, high250: tHi }; + return { lowBit: tLoBool, high254: tHi }; } /** - * 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 & 0x1f). + * Internal helper to compute `(t + 2^255)*P`. + * `t` is expected to be split into 254 high bits (t >> 1) and a low bit (t & 1). + * + * The gadget proves that `tHi` is in [0, 2^254) but assumes that `tLo` consists of bits. * - * The gadget proves that `tHi` is in [0, 2^250) but assumes that `tLo` consists of bits. + * Optionally, you can specify a different number of high bits by passing in `numHighBits`. */ -function scaleShiftedSplit5( +function scaleShifted( { x, y }: Point, - { low5: tLo, high250: tHi }: ShiftedScalar + { lowBit: tLo, high254: tHi }: ShiftedScalar, + numHighBits = 254 ): Point { // constant case - if (isConstant(x, y, tHi, ...tLo)) { + 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) + Fq.mod(tLo.toField().toBigInt() + 2n * tHi.toBigInt() + (1n << 255n)) ); return { x: createField(sP.x), y: createField(sP.y) }; } @@ -119,34 +132,26 @@ function scaleShiftedSplit5( const Point = provable({ x: Field, y: Field }); const zero = createField(0n); - // R = (2*(t >> 5) + 1 + 2^250)P - // also proves that tHi is in [0, 2^250) - let [, RMl] = Snarky.group.scaleFastUnpack( + // R = (2*(t >> 1) + 1 + 2^255)P + // also returns a 255-bit representation of tHi + let [, RMl, [, ...tHiBitsMl]] = Snarky.group.scaleFastUnpack( [0, x.value, y.value], [0, tHi.value], - 250 + 255 ); let P = { x, y }; let R = { x: createField(RMl[1]), y: createField(RMl[2]) }; - let [t0, t1, t2, t3, t4] = tLo; - // R = t4 ? R : R - P = ((t >> 4) + 2^250)P - R = Provable.if(t4, Point, R, addNonZero(R, negate(P))); - - // R = ((t >> 3) + 2^251)P - // R = ((t >> 2) + 2^252)P - // R = ((t >> 1) + 2^253)P - for (let t of [t3, t2, t1]) { - R = addNonZero(R, R); - R = Provable.if(t, Point, addNonZero(R, P), R); + // prove that tHi has only `numHighBits` bits set + for (let i = numHighBits; i < 255; i++) { + createField(tHiBitsMl[i]).assertEquals(zero); } - // R = (t + 2^254)P - // in the final step, we allow a zero output to make it work for the 0 scalar - R = addNonZero(R, R); - let { result, isInfinity } = add(R, P); - result = Provable.if(isInfinity, Point, { x: zero, y: zero }, result); - R = Provable.if(t0, Point, result, R); + // R = tLo ? R : R - P = (t + 2^255)P + // we also handle a zero R-P result to make scaling work for the 0 scalar + let { result, isInfinity } = add(R, negate(P)); + let RmP = Provable.if(isInfinity, Point, { x: zero, y: zero }, result); + R = Provable.if(tLo, Point, R, RmP); return R; } @@ -194,15 +199,6 @@ function add(g: Point, h: Point): { result: Point; isInfinity: Bool } { return { result: { x: x3, y: y3 }, isInfinity }; } -/** - * Addition that asserts the result is non-zero. - */ -function addNonZero(g: Point, h: Point) { - let { result, isInfinity } = add(g, h); - isInfinity.assertFalse(); - return result; -} - /** * Negates a point. */ diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index 503fe522ea..da79585271 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -10,7 +10,7 @@ import { import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { assert } from '../util/assert.js'; -import { add, scaleShiftedSplit5 } from './gadgets/native-curve.js'; +import { add, scaleShifted } from './gadgets/native-curve.js'; export { Group }; @@ -180,7 +180,7 @@ class Group { let g_proj = Pallas.scale(toProjective(this), scalar.toBigInt()); return fromProjective(g_proj); } else { - let result = scaleShiftedSplit5(this, scalar); + let result = scaleShifted(this, scalar); return new Group(result); } } diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index ce39c53aa8..4304dc60d3 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -3,12 +3,12 @@ import { Scalar as SignableFq } from '../../mina-signer/src/curve-bigint.js'; import { Field, checkBitLength } from './field.js'; import { FieldVar } from './core/fieldvar.js'; import { Bool } from './bool.js'; -import { TupleN } from '../util/types.js'; import { + ShiftedScalar, field3ToShiftedScalar, fieldToShiftedScalar, } from './gadgets/native-curve.js'; -import { isConstant, packBits } from './gadgets/common.js'; +import { isConstant } from './gadgets/common.js'; import { Provable } from './provable.js'; import { assert } from '../util/assert.js'; import type { HashInput } from './types/provable-derivers.js'; @@ -21,20 +21,20 @@ type ScalarConst = [0, bigint]; /** * Represents a {@link Scalar}. */ -class Scalar { +class Scalar implements ShiftedScalar { /** - * We represent a scalar s in shifted form `t = s - 2^254 mod q, - * split into its low 5 bits (t & 0x1f) and high 250 bits (t >> 5). - * The reason is that we can efficiently compute the scalar multiplication `(t + 2^254) * P = s * P`. + * We represent a scalar s in shifted form `t = s - 2^255 mod q, + * split into its low bit (t & 1) and high 254 bits (t >> 1). + * The reason is that we can efficiently compute the scalar multiplication `(t + 2^255) * P = s * P`. */ - low5: TupleN; - high250: Field; + lowBit: Bool; + high254: Field; static ORDER = Fq.modulus; - private constructor(low5: TupleN, high250: Field) { - this.low5 = low5; - this.high250 = high250; + private constructor(lowBit: Bool, high254: Field) { + this.lowBit = lowBit; + this.high254 = high254; } /** @@ -44,10 +44,10 @@ class Scalar { */ static from(s: Scalar | bigint | number | string): Scalar { if (s instanceof Scalar) return s; - let t = Fq.mod(BigInt(s) - (1n << 254n)); - let low5 = new Field(t & 0x1fn).toBits(5); - let high250 = new Field(t >> 5n); - return new Scalar(TupleN.fromArray(5, low5), high250); + let t = Fq.mod(BigInt(s) - (1n << 255n)); + let lowBit = new Bool((t & 1n) === 1n); + let high254 = new Field(t >> 1n); + return new Scalar(lowBit, high254); } /** @@ -56,8 +56,8 @@ class Scalar { * This is always possible and unambiguous, since the scalar field is larger than the base field. */ static fromNativeField(s: Field): Scalar { - let { low5, high250 } = fieldToShiftedScalar(s); - return new Scalar(low5, high250); + let { lowBit, high254 } = fieldToShiftedScalar(s); + return new Scalar(lowBit, high254); } /** @@ -65,8 +65,8 @@ class Scalar { * If a {@link Scalar} is constructed outside provable code, it is a constant. */ isConstant() { - let { low5, high250 } = this; - return isConstant(high250, ...low5); + let { lowBit, high254 } = this; + return isConstant(lowBit, high254); } /** @@ -85,11 +85,9 @@ class Scalar { * Convert this {@link Scalar} into a bigint */ toBigInt() { - let { low5, high250 } = this.toConstant(); - return Fq.add( - packBits(low5).toBigInt() + (high250.toBigInt() << 5n), - 1n << 254n - ); + let { lowBit, high254 } = this.toConstant(); + let t = lowBit.toField().toBigInt() + 2n * high254.toBigInt(); + return Fq.mod(t + (1n << 255n)); } /** @@ -104,8 +102,8 @@ class Scalar { let sBig = field3FromBits(bits); // convert to shifted representation - let { low5, high250 } = field3ToShiftedScalar(sBig); - return new Scalar(low5, high250); + let { lowBit, high254 } = field3ToShiftedScalar(sBig); + return new Scalar(lowBit, high254); } /** @@ -211,7 +209,7 @@ class Scalar { * The fields are not constrained to be boolean. */ static toFields(x: Scalar) { - return [...x.low5.map((b) => b.toField()), x.high250]; + return [x.lowBit.toField(), x.high254]; } /** @@ -239,7 +237,7 @@ class Scalar { * */ static toInput(x: Scalar): HashInput { - return { fields: [x.high250], packed: x.low5.map((f) => [f.toField(), 1]) }; + return { fields: [x.high254], packed: [[x.lowBit.toField(), 1]] }; } /** @@ -258,12 +256,12 @@ class Scalar { */ static fromFields(fields: Field[]): Scalar { assert( - fields.length === 6, - `Scalar.fromFields(): expected 6 fields, got ${fields.length}` + fields.length === 2, + `Scalar.fromFields(): expected 2 fields, got ${fields.length}` ); - let low5 = fields.slice(0, 5).map(Bool.Unsafe.fromField); - let high250 = fields[5]; - return new Scalar(TupleN.fromArray(5, low5), high250); + let lowBit = Bool.Unsafe.fromField(fields[0]); + let high254 = fields[1]; + return new Scalar(lowBit, high254); } /** @@ -272,7 +270,7 @@ class Scalar { * Returns the size of this type in {@link Field} elements. */ static sizeInFields(): number { - return 6; + return 2; } /** @@ -280,10 +278,10 @@ class Scalar { */ static check(s: Scalar) { /** - * It is not necessary to constrain the range of high250, because the only provable operation on Scalar + * It is not necessary to constrain the range of high254, because the only provable operation on Scalar * which relies on that range is scalar multiplication -- which constrains the range itself. */ - return s.low5.forEach(Bool.check); + return Bool.check(s.lowBit); } // ProvableExtended From 9f11f0cddc937a9d576bcc7dfe3d27388d88729d Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 4 Apr 2024 15:52:29 +0200 Subject: [PATCH 18/45] but scaling with 0 or 1 doesn't work now --- src/lib/provable/test/group.unit-test.ts | 30 ++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index 188c04285c..7dc7394876 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -8,18 +8,24 @@ import { Field } from '../field.js'; console.log('group consistency tests'); -test(Random.field, Random.field, (a, s0, assert) => { - const { - x: x1, - y: { x0: y1 }, - } = Poseidon.hashToGroup([a])!; - const g = Group.from(x1, y1); - const s = Scalar.from(s0); - runScale(g, s, (g, s) => g.scale(s), assert); - - const sField = Field.from(s0); - runScale(g, sField, (g, s) => g.scale(Scalar.fromNativeField(s)), assert); -}); +test( + Random.field, + // TODO + // Random.field, + Random.reject(Random.field, (x) => x === 0n || x === 1n), + (a, s0, assert) => { + const { + x: x1, + y: { x0: y1 }, + } = Poseidon.hashToGroup([a])!; + const g = Group.from(x1, y1); + const s = Scalar.from(s0); + runScale(g, s, (g, s) => g.scale(s), assert); + + const sField = Field.from(s0); + runScale(g, sField, (g, s) => g.scale(Scalar.fromNativeField(s)), assert); + } +); // tests consistency between in- and out-circuit implementations test(Random.field, Random.field, (a, b, assert) => { From 32fd2b38dbdb5cccd73f6c5b8bc71b06a0e08900 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 5 Apr 2024 00:04:57 +0200 Subject: [PATCH 19/45] 0 doesn't work anyway --- src/lib/provable/gadgets/native-curve.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 625fade1d4..d2ce95792b 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -148,10 +148,9 @@ function scaleShifted( } // R = tLo ? R : R - P = (t + 2^255)P - // we also handle a zero R-P result to make scaling work for the 0 scalar let { result, isInfinity } = add(R, negate(P)); - let RmP = Provable.if(isInfinity, Point, { x: zero, y: zero }, result); - R = Provable.if(tLo, Point, R, RmP); + isInfinity.assertFalse(); + R = Provable.if(tLo, Point, R, result); return R; } From 7ce21a73cd18d1f4c0309507b4f8ffb64744d5fa Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 5 Apr 2024 00:05:19 +0200 Subject: [PATCH 20/45] vk regression --- tests/vk-regression/vk-regression.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 475ec3c697..66a2814377 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -50,16 +50,16 @@ } }, "HelloWorld": { - "digest": "e04e3a9ffe27c83ecbbcb72ea2e775df96de8f6215db698139b5d5f4898a230", + "digest": "38eead180db3a6dfbc541ad48285924924041bffad8966dfa11d33e26b415778", "methods": { "update": { - "rows": 492, - "digest": "0fa169ea122f0b26ef92f1112836828f" + "rows": 464, + "digest": "805cae3f9bca2ae59c45bfcd11ca593c" } }, "verificationKey": { - "data": "AABnIUtBjexnheGU45jDLZlzdEIAYFcRi+aW1GqRaoZWFXXDP+NCOlX280XhOT9Cbb9LPxS0no5odvWi86abqw8UZxrm8etZg82vugnGjikslSBGhtwipjyp0Qo5LfTmwzO6qmGnWieyABMDgCGqCmwJxW2g+adyu9hhmuse/WhnJbMQAiQEtmzoyrLb5qe7Lm/8aanBXocSGEpuD7eIL4IO2z42fOrH0VqcIrp8qLy460w1R0aAMaPZ02npDOxvrxTLtpfVAyIY06XCc4grEuoHcIf8lic76syVofMXaoIDE0mDadgIeIR1VrdeEeufn2VabqLDTAs8gWTlv2fCeeMp8I6db/g6R2avb6C2WoKszKThWN6K8SB184sqMuT3lhFWAIno6nX2SUuxQVWLMT2T6a3DAC5C1qYDN3WlsH2cFlk2VoGAmZ3cM+LDtcGgSWePM3bfiGr3lZWBaorws4QFIB40UOKCK4BWvbl7fcSRY1+3LR1e04nf0kxrfIYZ3wapKc6XGVKPJjpLvlBEk0AQPNXIQTdRWTddWJUPA5SiEP04iqgbpetZqbx82xQ06Dr4xtOP0vwcZwGIY+LGndM7AHjmDRq4xWQ+U5arpEs3T/btJYbD6a1ul9rAEETOgLQICwqgjK0qqbVrHV+9d8DNzRkVoWmDILhA6IoD4SQ08CIkCdYBNnekfSPubsOHugj7msm+wpsabOGPdol/QHJNDAlUk77WjZEpQmxHOiE66K7r77+16lRbgbrkbIXxAXsKVrMzwyMWVfWzHqZkN2FsAMBYWJO1x8lqY4XukKRCnjtelAq+gZ4ljp7YlQFnM3GLA3oGCvQyIqwePiCWoZpJA4SwswCa2uNPZ+7PMzZbMzddVkwj921337rtBzP9OYwXIe6aPdqI21kTf4KBc1Nu+071d484vfsxBEpIj2VWET0bk94MYH2w1ThMqEOjvdLH7Rjb2yq6S/pHxzA0+6u7PW8S6rE/B0hQj+15Yefwb0l3QTzgj2x0JDSZLXfM9v4er/yqFfGfQqUIbLe4D6WbU8JCs7WteAajzSO8LV/qVBzR6IJjRc7fBywKBsHdmNCaQ09RiShjfao1REfGXGxFCRSB3dphR1+4/fKCaDZiM7W1Ehtv7L91QEgycB7lkOo31xGBWvFCmOja1I2j18ANyznh7ppyfrE0KDk4f6tL+TyakQKlZim7WJBz45cwQ/Y+wzWQL4N92uh2awCSzau1Kn4/CBjw+Oq7HSz/k1oL0J5PGao68tAiQD0tOcEhUIsHD36inwHMxroxJVL8zLKdkAE8BM7WITJyXL9FWp5q/BAVTIML5L7BEZzQkLVIiVW66mRV1+jKE4Y1u3exah/mJR43GZoyn4SNlnjtlQG9S+VLerddhdyF3qnc3t3o8ik6x/0qD6g+IBuY1OIl7EG6UUp1avg2YGgPiOrfGnX6LBrtboOkA0iWI1TRushJ744l87XpZKLfHr90QzBQkgAPCcEAG08LLim/FcnS3kAjfAE0lxyjrSMY5s1+SbnnTlAMOacaPE09xwW9FDvwkV3FNaBTX7srLovC9VgavOPx5QN0gP+hy8/zdnwG0aZvLCDNHa2NOxe8q35gdA42Ru+QMdKLYIuw/xGXYjzoOdYr/WDYjNQzzDKeHwzx/vqyFLkNA/mMzaNMxjQTlfrFnuTzj8oyY7EuUdwif18wcTsqXAMUVhRtN690neD7VWL63HgR7q5p3BA5J6NltbjPXNJLP5dMTi71vUoBndyX3XlcObmIkZKa2YD927QsfATwyg4mfkcjMw1nboag8T9VcExC6cY5nrNcBvXmtLz3pUiTcSj/UgCgZWa0GOGBS+6P5PsrtGDsvLrmqA2ed33hv1UiHwDLSTXvh+zFNDPSPTnoT53wP7DE0FjilgeWv+F5o851CnFU50J/gtMwWrrmw5bjAKk+Dh39+0B0OfLxG5DFjnwmv3JkQzrnB+GFZ22PEHdZZ9PiP6C7/kMFaB6+V2gvrDJ6BTesrreFShEyHJKvtdPWeE9WWm8GmSY6C/PjGryIERm7Z2za+3ze7ZEUQx9RxrzYnNrGSqGJdtDNk0pHDG4khS3+AfieLdNKHdr19IfUDzCgCAvY5ukD0N7A9qxW6C7VPVvqWINu1zU27wupHjnu85MW+cIs5RPIrVS6Xry0Api3cCFuPK2pfEpmDy/K3AtLptp7NDJRcD6ym5K26rcdle9voOz4VLuCe98Z/rCV2W8fPfBGjPWMX2pjZL78oDFdbwKSBi1ipV79IKNCY6d3kSZpeyFlJZX+MBY5NXKLHygje3kDLDQ1eBKb4ORKOrEVw/Mxp7jGrAlG0Ac7y/s+gkC01hulcJ6CIkgIUZiS/TYfsH+F3oqmNXzue0jHLwQ=", - "hash": "27370250573863895519732939618815084579192113056549266298181666785275942621518" + "data": "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", + "hash": "15242986859817717007115267674757376570296163014234388126124358751994973392059" } }, "TokenContract": { @@ -124,8 +124,8 @@ "digest": "ddb709883792aa08b3bdfb69206a9f69" }, "scale": { - "rows": 145, - "digest": "831dd3a44b52fb75ef3dbbc34b4ed8c0" + "rows": 119, + "digest": "8c9b16d8582380cca5392110b1a2df40" }, "equals": { "rows": 37, @@ -288,7 +288,7 @@ } }, "diverse": { - "digest": "264204561bed0bf5488984c4088912525376de470891ff04907b63277980a2eb", + "digest": "e448834d1f1e827e50878cc75121dd8f17ed5cf8f86b4b4637323de0a4bcfb4", "methods": { "ecdsa": { "rows": 28182, @@ -299,8 +299,8 @@ "digest": "c23e00e466878466ae8ab23bde562792" }, "pallas": { - "rows": 525, - "digest": "cee3f71cfd75be56a2cc801be8959ea8" + "rows": 458, + "digest": "25cd49debfe953f1ad2a8a53cb5f0a5c" }, "poseidon": { "rows": 946, @@ -316,8 +316,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "28944110445825075951165306337055835067870337140854205539564223784418561300672" + "data": "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", + "hash": "7187205879049138311802783214788214840185722735818768520847699889049438283310" } } } \ No newline at end of file From 5bc95432d01128a3802a161bce59bc8bb5dbab86 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 5 Apr 2024 00:09:42 +0200 Subject: [PATCH 21/45] fix --- src/lib/provable/group.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index da79585271..017edd2d3b 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -136,7 +136,10 @@ class Group { addNonZero(g2: Group, allowZeroOutput = false): Group { if (isConstant(this) && isConstant(g2)) { let { x, y, infinity } = PallasAffine.add(toAffine(this), toAffine(g2)); - assert(infinity === false, 'Group.addNonzero(): Result is zero'); + assert( + !infinity || allowZeroOutput, + 'Group.addNonzero(): Result is zero' + ); return fromAffine({ x, y, infinity }); } let { result, isInfinity } = add(this, g2); From 4c06ce1239b17db1e2b5a5fb5f750d925bda8c33 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 5 Apr 2024 10:08:16 +0200 Subject: [PATCH 22/45] Revert "switch to repr with 1 low bit" This reverts commit 04f8648f332c924f1e5db8a89a940bd60b2a4899. --- src/lib/provable/gadgets/native-curve.ts | 131 ++++++++++++----------- src/lib/provable/group.ts | 4 +- src/lib/provable/scalar.ts | 70 ++++++------ 3 files changed, 106 insertions(+), 99 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index d2ce95792b..4998993805 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -4,14 +4,11 @@ import { Fp, 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 } from '../core/exists.js'; -import { bit, isConstant } from './common.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 { - createBool, - createField, - getField, -} from '../core/field-constructor.js'; +import { createField, getField } from '../core/field-constructor.js'; import { Snarky } from '../../../snarky.js'; import { Provable } from '../provable.js'; import { MlPair } from '../../ml/base.js'; @@ -21,13 +18,12 @@ export { scale, fieldToShiftedScalar, field3ToShiftedScalar, - scaleShifted, + scaleShiftedSplit5, add, - ShiftedScalar, }; type Point = { x: Field; y: Field }; -type ShiftedScalar = { lowBit: Bool; high254: Field }; +type ShiftedScalar = { low5: TupleN; high250: Field }; /** * Gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. @@ -46,7 +42,7 @@ function scale(P: Point, s: Field): Point { let t = fieldToShiftedScalar(s); // return (t + 2^254)*P = (s - 2^254 + 2^254)*P = s*P - return scaleShifted(P, t); + return scaleShiftedSplit5(P, t); } /** @@ -56,75 +52,66 @@ function scale(P: Point, s: Field): Point { * This is the representation we use for scalars, since it can be used as input to `scaleShiftedSplit5()`. */ function fieldToShiftedScalar(s: Field): ShiftedScalar { - let sBig = fieldToField3(s); - - // assert that sBig is canonical mod p, so that we can't add (kp mod q) factors by doing things modulo q - ForeignField.assertLessThan(sBig, Fp.modulus); - - return field3ToShiftedScalar(sBig); + return field3ToShiftedScalar(fieldToField3(s)); } /** - * Converts a 3-limb bigint to a shifted representation t = s - 2^255 mod q, - * where t is represented as a low bit and a 254-bit high part. + * Converts a 3-limb bigint to a shifted representation t = s = 2^254 mod q, + * where t is represented as a 5-bit low part and a 250-bit high part. */ function field3ToShiftedScalar(s: Field3): ShiftedScalar { // constant case if (Field3.isConstant(s)) { - let t = Fq.mod(Field3.toBigint(s) - (1n << 255n)); - let lowBit = createBool((t & 1n) === 1n); - let high254 = createField(t >> 1n); - return { lowBit, high254 }; + let t = Fq.mod(Field3.toBigint(s) - (1n << 254n)); + let low5 = createField(t & 0x1fn).toBits(5); + let high250 = createField(t >> 5n); + return { low5: TupleN.fromArray(5, low5), high250 }; } - // compute t = s - 2^255 mod q using foreign field subtraction - let twoTo255 = Field3.from(Fq.mod(1n << 255n)); - let t = ForeignField.sub(s, twoTo255, Fq.modulus); - - // it's necessary to prove that t is canonical -- otherwise its bit representation is ambiguous - ForeignField.assertLessThan(t, Fq.modulus); + // compute t = s - 2^254 mod q using foreign field subtraction + let twoTo254 = Field3.from(1n << 254n); + let [t0, t1, t2] = ForeignField.sub(s, twoTo254, Fq.modulus); - let [t0, t1, t2] = t; - - // split t into 254 high bits and a low bit - // => split t0 into [1, 87] - let [tLo, tHi0] = exists(2, () => { - let t0_ = t0.toBigInt(); - return [bit(t0_, 0), t0_ >> 1n]; + // 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 tLoBool = tLo.assertBool(); + 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^87 - tLo.add(tHi0.mul(2n)).assertEquals(t0); + // since we know that t0 < 2^88, this proves that t0High < 2^83 + packBits(tLo) + .add(tHi0.mul(1n << 5n)) + .assertEquals(t0); // pack tHi + // proves that tHi is in [0, 2^250) let tHi = tHi0 - .add(t1.mul(1n << (l - 1n))) - .add(t2.mul(1n << (2n * l - 1n))) + .add(t1.mul(1n << (l - 5n))) + .add(t2.mul(1n << (2n * l - 5n))) .seal(); - return { lowBit: tLoBool, high254: tHi }; + return { low5: tLoBools, high250: tHi }; } /** - * Internal helper to compute `(t + 2^255)*P`. - * `t` is expected to be split into 254 high bits (t >> 1) and a low bit (t & 1). - * - * The gadget proves that `tHi` is in [0, 2^254) but assumes that `tLo` consists of bits. + * 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 & 0x1f). * - * Optionally, you can specify a different number of high bits by passing in `numHighBits`. + * The gadget proves that `tHi` is in [0, 2^250) but assumes that `tLo` consists of bits. */ -function scaleShifted( +function scaleShiftedSplit5( { x, y }: Point, - { lowBit: tLo, high254: tHi }: ShiftedScalar, - numHighBits = 254 + { low5: tLo, high250: tHi }: ShiftedScalar ): Point { // constant case - if (isConstant(x, y, tHi, tLo)) { + if (isConstant(x, y, tHi, ...tLo)) { let sP = PallasAffine.scale( PallasAffine.fromNonzero({ x: x.toBigInt(), y: y.toBigInt() }), - Fq.mod(tLo.toField().toBigInt() + 2n * tHi.toBigInt() + (1n << 255n)) + Fq.add(packBits(tLo).toBigInt() + (tHi.toBigInt() << 5n), 1n << 254n) ); return { x: createField(sP.x), y: createField(sP.y) }; } @@ -132,25 +119,34 @@ function scaleShifted( const Point = provable({ x: Field, y: Field }); const zero = createField(0n); - // R = (2*(t >> 1) + 1 + 2^255)P - // also returns a 255-bit representation of tHi - let [, RMl, [, ...tHiBitsMl]] = Snarky.group.scaleFastUnpack( + // R = (2*(t >> 5) + 1 + 2^250)P + // also proves that tHi is in [0, 2^250) + let [, RMl] = Snarky.group.scaleFastUnpack( [0, x.value, y.value], [0, tHi.value], - 255 + 250 ); let P = { x, y }; let R = { x: createField(RMl[1]), y: createField(RMl[2]) }; + let [t0, t1, t2, t3, t4] = tLo; + + // R = t4 ? R : R - P = ((t >> 4) + 2^250)P + R = Provable.if(t4, Point, R, addNonZero(R, negate(P))); - // prove that tHi has only `numHighBits` bits set - for (let i = numHighBits; i < 255; i++) { - createField(tHiBitsMl[i]).assertEquals(zero); + // R = ((t >> 3) + 2^251)P + // R = ((t >> 2) + 2^252)P + // R = ((t >> 1) + 2^253)P + for (let t of [t3, t2, t1]) { + R = addNonZero(R, R); + R = Provable.if(t, Point, addNonZero(R, P), R); } - // R = tLo ? R : R - P = (t + 2^255)P - let { result, isInfinity } = add(R, negate(P)); - isInfinity.assertFalse(); - R = Provable.if(tLo, Point, R, result); + // R = (t + 2^254)P + // in the final step, we allow a zero output to make it work for the 0 scalar + R = addNonZero(R, R); + let { result, isInfinity } = add(R, P); + result = Provable.if(isInfinity, Point, { x: zero, y: zero }, result); + R = Provable.if(t0, Point, result, R); return R; } @@ -198,6 +194,15 @@ function add(g: Point, h: Point): { result: Point; isInfinity: Bool } { return { result: { x: x3, y: y3 }, isInfinity }; } +/** + * Addition that asserts the result is non-zero. + */ +function addNonZero(g: Point, h: Point) { + let { result, isInfinity } = add(g, h); + isInfinity.assertFalse(); + return result; +} + /** * Negates a point. */ diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index 017edd2d3b..3dc46da93d 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -10,7 +10,7 @@ import { import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { assert } from '../util/assert.js'; -import { add, scaleShifted } from './gadgets/native-curve.js'; +import { add, scaleShiftedSplit5 } from './gadgets/native-curve.js'; export { Group }; @@ -183,7 +183,7 @@ class Group { let g_proj = Pallas.scale(toProjective(this), scalar.toBigInt()); return fromProjective(g_proj); } else { - let result = scaleShifted(this, scalar); + let result = scaleShiftedSplit5(this, scalar); return new Group(result); } } diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index 4304dc60d3..ce39c53aa8 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -3,12 +3,12 @@ import { Scalar as SignableFq } from '../../mina-signer/src/curve-bigint.js'; import { Field, checkBitLength } from './field.js'; import { FieldVar } from './core/fieldvar.js'; import { Bool } from './bool.js'; +import { TupleN } from '../util/types.js'; import { - ShiftedScalar, field3ToShiftedScalar, fieldToShiftedScalar, } from './gadgets/native-curve.js'; -import { isConstant } from './gadgets/common.js'; +import { isConstant, packBits } from './gadgets/common.js'; import { Provable } from './provable.js'; import { assert } from '../util/assert.js'; import type { HashInput } from './types/provable-derivers.js'; @@ -21,20 +21,20 @@ type ScalarConst = [0, bigint]; /** * Represents a {@link Scalar}. */ -class Scalar implements ShiftedScalar { +class Scalar { /** - * We represent a scalar s in shifted form `t = s - 2^255 mod q, - * split into its low bit (t & 1) and high 254 bits (t >> 1). - * The reason is that we can efficiently compute the scalar multiplication `(t + 2^255) * P = s * P`. + * We represent a scalar s in shifted form `t = s - 2^254 mod q, + * split into its low 5 bits (t & 0x1f) and high 250 bits (t >> 5). + * The reason is that we can efficiently compute the scalar multiplication `(t + 2^254) * P = s * P`. */ - lowBit: Bool; - high254: Field; + low5: TupleN; + high250: Field; static ORDER = Fq.modulus; - private constructor(lowBit: Bool, high254: Field) { - this.lowBit = lowBit; - this.high254 = high254; + private constructor(low5: TupleN, high250: Field) { + this.low5 = low5; + this.high250 = high250; } /** @@ -44,10 +44,10 @@ class Scalar implements ShiftedScalar { */ static from(s: Scalar | bigint | number | string): Scalar { if (s instanceof Scalar) return s; - let t = Fq.mod(BigInt(s) - (1n << 255n)); - let lowBit = new Bool((t & 1n) === 1n); - let high254 = new Field(t >> 1n); - return new Scalar(lowBit, high254); + let t = Fq.mod(BigInt(s) - (1n << 254n)); + let low5 = new Field(t & 0x1fn).toBits(5); + let high250 = new Field(t >> 5n); + return new Scalar(TupleN.fromArray(5, low5), high250); } /** @@ -56,8 +56,8 @@ class Scalar implements ShiftedScalar { * This is always possible and unambiguous, since the scalar field is larger than the base field. */ static fromNativeField(s: Field): Scalar { - let { lowBit, high254 } = fieldToShiftedScalar(s); - return new Scalar(lowBit, high254); + let { low5, high250 } = fieldToShiftedScalar(s); + return new Scalar(low5, high250); } /** @@ -65,8 +65,8 @@ class Scalar implements ShiftedScalar { * If a {@link Scalar} is constructed outside provable code, it is a constant. */ isConstant() { - let { lowBit, high254 } = this; - return isConstant(lowBit, high254); + let { low5, high250 } = this; + return isConstant(high250, ...low5); } /** @@ -85,9 +85,11 @@ class Scalar implements ShiftedScalar { * Convert this {@link Scalar} into a bigint */ toBigInt() { - let { lowBit, high254 } = this.toConstant(); - let t = lowBit.toField().toBigInt() + 2n * high254.toBigInt(); - return Fq.mod(t + (1n << 255n)); + let { low5, high250 } = this.toConstant(); + return Fq.add( + packBits(low5).toBigInt() + (high250.toBigInt() << 5n), + 1n << 254n + ); } /** @@ -102,8 +104,8 @@ class Scalar implements ShiftedScalar { let sBig = field3FromBits(bits); // convert to shifted representation - let { lowBit, high254 } = field3ToShiftedScalar(sBig); - return new Scalar(lowBit, high254); + let { low5, high250 } = field3ToShiftedScalar(sBig); + return new Scalar(low5, high250); } /** @@ -209,7 +211,7 @@ class Scalar implements ShiftedScalar { * The fields are not constrained to be boolean. */ static toFields(x: Scalar) { - return [x.lowBit.toField(), x.high254]; + return [...x.low5.map((b) => b.toField()), x.high250]; } /** @@ -237,7 +239,7 @@ class Scalar implements ShiftedScalar { * */ static toInput(x: Scalar): HashInput { - return { fields: [x.high254], packed: [[x.lowBit.toField(), 1]] }; + return { fields: [x.high250], packed: x.low5.map((f) => [f.toField(), 1]) }; } /** @@ -256,12 +258,12 @@ class Scalar implements ShiftedScalar { */ static fromFields(fields: Field[]): Scalar { assert( - fields.length === 2, - `Scalar.fromFields(): expected 2 fields, got ${fields.length}` + fields.length === 6, + `Scalar.fromFields(): expected 6 fields, got ${fields.length}` ); - let lowBit = Bool.Unsafe.fromField(fields[0]); - let high254 = fields[1]; - return new Scalar(lowBit, high254); + let low5 = fields.slice(0, 5).map(Bool.Unsafe.fromField); + let high250 = fields[5]; + return new Scalar(TupleN.fromArray(5, low5), high250); } /** @@ -270,7 +272,7 @@ class Scalar implements ShiftedScalar { * Returns the size of this type in {@link Field} elements. */ static sizeInFields(): number { - return 2; + return 6; } /** @@ -278,10 +280,10 @@ class Scalar implements ShiftedScalar { */ static check(s: Scalar) { /** - * It is not necessary to constrain the range of high254, because the only provable operation on Scalar + * It is not necessary to constrain the range of high250, because the only provable operation on Scalar * which relies on that range is scalar multiplication -- which constrains the range itself. */ - return Bool.check(s.lowBit); + return s.low5.forEach(Bool.check); } // ProvableExtended From 68861842c56240d563160cd3727644a52250f358 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 5 Apr 2024 12:06:56 +0200 Subject: [PATCH 23/45] add missing range checks, document more assumptions, add comments --- src/lib/provable/gadgets/native-curve.ts | 63 ++++++++++++++++++------ 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 4998993805..7bc037dce8 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -4,10 +4,10 @@ import { Fp, 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 { exists } from '../core/exists.js'; +import { bit, bitSlice, isConstant, packBits } from './common.js'; import { TupleN } from '../../util/types.js'; -import { l } from './range-check.js'; +import { l, rangeCheck64 } from './range-check.js'; import { createField, getField } from '../core/field-constructor.js'; import { Snarky } from '../../../snarky.js'; import { Provable } from '../provable.js'; @@ -46,20 +46,31 @@ function scale(P: Point, s: Field): Point { } /** - * Converts a field element s to a shifted representation t = s = 2^254 mod q, + * Converts a field element s to a shifted representation t = s - 2^254 mod q, * where t is represented as a 5-bit low part and a 250-bit high part. * * This is the representation we use for scalars, since it can be used as input to `scaleShiftedSplit5()`. */ function fieldToShiftedScalar(s: Field): ShiftedScalar { - return field3ToShiftedScalar(fieldToField3(s)); + let sBig = fieldToField3(s); + + // assert that sBig is canonical mod p, so that we can't add (kp mod q) factors by doing things modulo q + ForeignField.assertLessThan(sBig, Fp.modulus); + + return field3ToShiftedScalar(sBig); } /** - * Converts a 3-limb bigint to a shifted representation t = s = 2^254 mod q, + * Converts a 3-limb bigint to a shifted representation t = s - 2^254 mod q, * where t is represented as a 5-bit low part and a 250-bit high part. + * + * This assumes that `s` is range-checked to some extent, for example a safe bound is s < 2^258 or anything less. + * If s is > 2^259, the high part computation can overflow the base field and the result is incorrect. */ -function field3ToShiftedScalar(s: Field3): ShiftedScalar { +function field3ToShiftedScalar( + s: Field3, + { proveUnique = false } = {} +): ShiftedScalar { // constant case if (Field3.isConstant(s)) { let t = Fq.mod(Field3.toBigint(s) - (1n << 254n)); @@ -70,25 +81,46 @@ function field3ToShiftedScalar(s: Field3): ShiftedScalar { // compute t = s - 2^254 mod q using foreign field subtraction let twoTo254 = Field3.from(1n << 254n); - let [t0, t1, t2] = ForeignField.sub(s, twoTo254, Fq.modulus); + let t = ForeignField.sub(s, twoTo254, Fq.modulus); + let [t0, t1, t2] = t; + + if (proveUnique) { + // to fully constrain the output scalar, we need to prove that t is canonical + // otherwise, the subtraction above can add +q to the result, which yields an alternative bit representation + // if the scalar is just used for scaling points, this isn't necessary, because (s + kq)P = sP + ForeignField.assertLessThan(t, Fq.modulus); + } // split t into 250 high bits and 5 low bits - // => split t0 into [5, 83] - let tLo = exists(5, () => { + // => split t0 into [5, 83] => split t0 into [5, 64, 19] so we can efficiently range-check + let [tHi00, tHi01, ...tLo] = exists(7, () => { let t = t0.toBigInt(); - return [bit(t, 0), bit(t, 1), bit(t, 2), bit(t, 3), bit(t, 4)]; + return [ + bitSlice(t, 5, 64), + bitSlice(t, 69, 19), + 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); + rangeCheck64(tHi00); + rangeCheck64(tHi01); - // prove split - // since we know that t0 < 2^88, this proves that t0High < 2^83 + // prove (tLo, tHi0) split + // since we know that t0 < 2^88 and tHi0 < 2^128, this even proves that t0Hi < 2^83 + // (the bound on tHi0 is necessary so that 32*tHi0 can't overflow) + let tHi0 = tHi00.add(tHi01.mul(1n << 64n)); packBits(tLo) .add(tHi0.mul(1n << 5n)) .assertEquals(t0); // pack tHi - // proves that tHi is in [0, 2^250) + // this can't overflow the native field if e.g. s < 2^258: + // -) t <= s - 2^254 + q < 2^259 + // -) we proved tHi = (t >> 5) < 2^254, and all the parts are precisely range-checked let tHi = tHi0 .add(t1.mul(1n << (l - 5n))) .add(t2.mul(1n << (2n * l - 5n))) @@ -136,6 +168,7 @@ function scaleShiftedSplit5( // R = ((t >> 3) + 2^251)P // R = ((t >> 2) + 2^252)P // R = ((t >> 1) + 2^253)P + // note: t is in [0, q) so none of these can overflow and create a completeness issue: q/2 + 2^253 < q for (let t of [t3, t2, t1]) { R = addNonZero(R, R); R = Provable.if(t, Point, addNonZero(R, P), R); From 8fbfe9b09a062df1daac7769db1c0111f54bf48d Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 5 Apr 2024 12:08:01 +0200 Subject: [PATCH 24/45] remove scalar limitations from group unit test --- src/lib/provable/test/group.unit-test.ts | 30 ++++++++++-------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index 7dc7394876..1ce478eb7c 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -8,24 +8,18 @@ import { Field } from '../field.js'; console.log('group consistency tests'); -test( - Random.field, - // TODO - // Random.field, - Random.reject(Random.field, (x) => x === 0n || x === 1n), - (a, s0, assert) => { - const { - x: x1, - y: { x0: y1 }, - } = Poseidon.hashToGroup([a])!; - const g = Group.from(x1, y1); - const s = Scalar.from(s0); - runScale(g, s, (g, s) => g.scale(s), assert); - - const sField = Field.from(s0); - runScale(g, sField, (g, s) => g.scale(Scalar.fromNativeField(s)), assert); - } -); +test(Random.field, Random.scalar, (a, s0, assert) => { + const { + x: x1, + y: { x0: y1 }, + } = Poseidon.hashToGroup([a])!; + const g = Group.from(x1, y1); + const s = Scalar.from(s0); + runScale(g, s, (g, s) => g.scale(s), assert); + + const sField = Field.from(s0); + runScale(g, sField, (g, s) => g.scale(Scalar.fromNativeField(s)), assert); +}); // tests consistency between in- and out-circuit implementations test(Random.field, Random.field, (a, b, assert) => { From 5107a9565611b303082d0be9910e1b10588af03c Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 5 Apr 2024 12:16:21 +0200 Subject: [PATCH 25/45] dump vks --- tests/vk-regression/vk-regression.json | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 66a2814377..3dd2b409b2 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -50,16 +50,16 @@ } }, "HelloWorld": { - "digest": "38eead180db3a6dfbc541ad48285924924041bffad8966dfa11d33e26b415778", + "digest": "e04e3a9ffe27c83ecbbcb72ea2e775df96de8f6215db698139b5d5f4898a230", "methods": { "update": { - "rows": 464, - "digest": "805cae3f9bca2ae59c45bfcd11ca593c" + "rows": 492, + "digest": "0fa169ea122f0b26ef92f1112836828f" } }, "verificationKey": { - "data": "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", - "hash": "15242986859817717007115267674757376570296163014234388126124358751994973392059" + "data": "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", + "hash": "27370250573863895519732939618815084579192113056549266298181666785275942621518" } }, "TokenContract": { @@ -124,8 +124,8 @@ "digest": "ddb709883792aa08b3bdfb69206a9f69" }, "scale": { - "rows": 119, - "digest": "8c9b16d8582380cca5392110b1a2df40" + "rows": 145, + "digest": "831dd3a44b52fb75ef3dbbc34b4ed8c0" }, "equals": { "rows": 37, @@ -257,8 +257,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "24251722424479837874260221363500409460039706763291687811835114093949142046847" + "data": "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", + "hash": "16281019584570658053446528661472458867843562527104002261354898556646562170501" } }, "ecdsa": { @@ -270,8 +270,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "28215601045091561251986150549207635700837312049267319716641606207490091416721" + "data": "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", + "hash": "20361741487252922523526350205895309196897600153365572953464741435104645844230" } }, "sha256": { @@ -283,12 +283,12 @@ } }, "verificationKey": { - "data": "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", - "hash": "22296391645667701199385692837408020819294441951376164803693884547686842878882" + "data": "AACa0OPxg0UDEf5DBLZf/TtdB4TzIAMNQC467+/R1yGnL1tRXJnn0BDcLG0fGWdqFcWK1q2zKdAYfORKVOHgvKEwxtxZYv4CjM7SwlG09G52oNZmOgGCilD0ntmby4rzJTaoyCimx5ynQ/oYjslJgSM/1DTAkpW6IIdFjjkmjlOSHqRNRAMtNtl44D9lbhci4blHbDvzQnlYynwRh58jAzoDj3bCkPsByviAyFBoPhUx2M13h0/VPK1ND/69djzZgi9lQKaON74XvbnvJdSAYLQSIBbOE0yo7tS1vGTPqDqJGzc1f0+2QhZttE2UEUV4PPEGB6LUFEuQPXK8lXyXHVQTOU+omjpvKHLLs/dQZLTSvvZ3UFqxUvxjSEG9eTPo3Cyt4wXcPEi1b993ePSSxP6njj1SoPBA4BvLCCcvaxz5DegUu7nZDKkC8tuaoNbqYcayaf/8+oZ+dA+Ek1Xe08og1Hz9gB9cfPvgI9EiL2Na2cgiOF2klHHEAj3fORN8UQVzs0ioqLcQeq2tH2UMMtZS4JNcohHUah1T0jKmS3eZDqe+aMuf5qzKA5oKRF2ejRwmsxI161YlgXXpmHs+kCQGAHG2rb5JGVcfTdiU9dvNTdAeFisR7QVKk4aDDJoqIGkmvTH2QdWHIgJu1DPOfHNwirY+VCAZHCqV77vFuhZu/Rz4U20AHCGYSahuSFbBO6dDhRCPGRCJJgxktY+CSO+3B9mLtCe2X23FeFsUnaAFlzmsWeKR1tMyPupNsUXPFugubYZYd1EwxUDeBIOLeahcqX78yDDMOdsr0QHNG8XteyXcRXGFUSzTTKNEXdD+BOMY6FfMroKO4y3FffyrZef1PRsu/kVwaZb4UthvhBwet8DBcMa26kISlsI1ItcvsdwST5x1+iLCM4hBcFB0ly3N18uR7+MLHU0AdV13INFo8indyz1XEZCHlm+tKF3LX8Z7G0v68uRmjKRgy/S9NjKPA+M3rbj4pDU+jS4EKN++nKYxXjwdKJXu4XvbUU9ITKM7V0JYl8CpaqkYzEghRdwI4OkLjNGc3VeQPtJXGcBITDBvYjl5e9IjW6TAUKumkqkAGy6n8EEt8KHM3tjwF9QwPk0OzQ8/vAiYAdVmcHuS+M1etSdnWerxU4E3vC2odvcjNq2yh/VyODIwPt/UPYT1soPpv6M3XSyvpE1kVb0TmD91v6mXvfq23wSDn7ndgLx52m+CGnEGzA/OwwVQnZaFNq+sZjKjOa8ALFNcjyS8IuYEz4XYMiSy/wCl2n2AHVIc6djnQZySvECly6HHJSpWpWv8zvNfLUiWozg4ActV4QzQsd2nagCedaw1z82uWcKLraPboPGke+1dGOqg8OFiBJUBoW/bROBgw2H8WnPIhcofMwGOCrXpMGvA4LKnn3okzY3hTNfex1t7zCpxQC2YRBN0Ze8qOELHTYvOlSwG1AQqZDhC//VDGFvvQ1ZgzsmpnbyNGTGMaCKRMGI0evgxD6Ziitu5k9ogVW6dGSR9cBM0NryOxQl6JcwUXd0twqsLI0pbH7cjlGzWsylGuufx/m77GPRqnysk6r+ibZ4fDBFDumJFxxbS4AqzXLR+nNg42hJbvuxsyZnCRpkB2N9xH3VX8/Il8BX40HdEE/08l3JmL3jn8Bznqwj8z3TqBFMdRGOOq2/5DjwcjaNh9pYRn6bDl/1qVPrJkUExPECrQRymHQ3JERZjm1G+a3bhAJFeb+Ak4D8PlK2RgPcMCS7bBXWuPRh0z4FKGnhZnecNmuWMSvUFsSZcoaFKfyNanX8qMsu+KWtWEbDwwQ49NrfCmg45/WAOQxX8LKMYgUrDpSVdE/bM+JqYpq0AmOHAhoIdlOC7jVMIPI6LEAVJC1PrFQBS3HbH+u5IMQ684sJehPFtd1pjpfboDrnbgfhnjFf9HqS5bG1sR1Dh2mXGXpQ+ni+O3FvEYCEZY+UU9d9XCGwL2OZIhtMi6M6qcnrn11w2MyaZ8U6aZxVTMFvKQ2JLtwVGVnMDuSkNC+iN711acwssgbTpsgHLdVbtFR1oYcbpJHe6a9SFquG+q9qfzT15IzKpBzxn6BVXfhhFGpJRAbU0bXbjOpeceg7vaV7RykTzBoIzAe5aUVAdKNM6fzGlPw16xx7QeOW+LFlOm6HJyxYAZfbpB/BLza4ZhoqmVx+ALUXHFIztgGQK9rzm+jBwiuwuLqdD2W00cbTZcbgKTo48XD6NJ+8T4J9B3rPzht3qbgpN//TyYkfrzAercAa/HCvFeBNXl1slCj8cF/EO6iX/NnIxBkuqmXfQnGUfcFK0LZPsvd7RInaLEYTeA4ZDfChiuw+5nTmrJFOywwOYdIA+NiMfCh24dPYAAwEGb9KLEP9u7/Rp5uPi0S3tuTw67yg=", + "hash": "22056812984041684882470029014547988687055758305009416178517735204127529614267" } }, "diverse": { - "digest": "e448834d1f1e827e50878cc75121dd8f17ed5cf8f86b4b4637323de0a4bcfb4", + "digest": "1254572f16452c387616f43d4776e6fae1c48b5ddf1711d1d24f7f0224261c0b", "methods": { "ecdsa": { "rows": 28182, @@ -299,8 +299,8 @@ "digest": "c23e00e466878466ae8ab23bde562792" }, "pallas": { - "rows": 458, - "digest": "25cd49debfe953f1ad2a8a53cb5f0a5c" + "rows": 534, + "digest": "3aa941d5660eefd97277bf6a9ea95bc7" }, "poseidon": { "rows": 946, @@ -316,8 +316,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "7187205879049138311802783214788214840185722735818768520847699889049438283310" + "data": "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", + "hash": "228660131678223074273190600255634833879027584418616002912925867203675143125" } } } \ No newline at end of file From d8fb076e1c6a38283e3863959f67ccd8bf41a653 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 5 Apr 2024 16:07:22 +0200 Subject: [PATCH 26/45] move isOdd gadget to reuse it --- src/lib/provable/field.ts | 20 ++---------- src/lib/provable/gadgets/comparison.ts | 45 +++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/lib/provable/field.ts b/src/lib/provable/field.ts index 45ad9059db..1d09de139d 100644 --- a/src/lib/provable/field.ts +++ b/src/lib/provable/field.ts @@ -26,6 +26,7 @@ import { setFieldConstructor } from './core/field-constructor.js'; import { assertLessThanFull, assertLessThanOrEqualFull, + isOddAndHigh, lessThanFull, lessThanOrEqualFull, } from './gadgets/comparison.js'; @@ -319,24 +320,7 @@ class Field { * See {@link Field.isEven} for examples. */ isOdd() { - if (this.isConstant()) return new Bool((this.toBigInt() & 1n) === 1n); - - // witness a bit b such that x = b + 2z for some z <= (p-1)/2 - // this is always possible, and unique _except_ in the edge case where x = 0 = 0 + 2*0 = 1 + 2*(p-1)/2 - // so we can compute isOdd = b AND (x != 0) - let [b, z] = exists(2, () => { - let x = this.toBigInt(); - return [x & 1n, x >> 1n]; - }); - let isOdd = b.assertBool(); - z.assertLessThan((Field.ORDER + 1n) / 2n); - - // x == b + 2z - b.add(z.mul(2)).assertEquals(this); - - // avoid overflow case when x = 0 - let isNonZero = this.equals(0).not(); - return isOdd.and(isNonZero); + return isOddAndHigh(this).isOdd; } /** diff --git a/src/lib/provable/gadgets/comparison.ts b/src/lib/provable/gadgets/comparison.ts index e8d2ae9f74..2c5e083cbf 100644 --- a/src/lib/provable/gadgets/comparison.ts +++ b/src/lib/provable/gadgets/comparison.ts @@ -1,6 +1,10 @@ import type { Field } from '../field.js'; import type { Bool } from '../bool.js'; -import { createBoolUnsafe, createField } from '../core/field-constructor.js'; +import { + createBool, + createBoolUnsafe, + createField, +} from '../core/field-constructor.js'; import { Fp } from '../../../bindings/crypto/finite-field.js'; import { assert } from '../../../lib/util/assert.js'; import { exists, existsOne } from '../core/exists.js'; @@ -9,7 +13,6 @@ 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, @@ -23,12 +26,15 @@ export { lessThanFull, lessThanOrEqualFull, + // gadgets that are based on full comparisons + isOddAndHigh, + // legacy, unused compareCompatible, -}; -// internal API -export { fieldToField3 }; + // internal helper + fieldToField3, +}; /** * Prove x <= y assuming 0 <= x, y < c. @@ -187,6 +193,35 @@ function lessThanOrEqualFull(x: Field, y: Field) { return lessThanFull(y, x).not(); } +/** + * Splits a field element into a low bit `isOdd` and a 254-bit `high` part. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function isOddAndHigh(x: Field) { + if (x.isConstant()) { + let x0 = x.toBigInt(); + return { isOdd: createBool((x0 & 1n) === 1n), high: createField(x0 >> 1n) }; + } + + // witness a bit b such that x = b + 2z for some z <= (p-1)/2 + // this is always possible, and unique _except_ in the edge case where x = 0 = 0 + 2*0 = 1 + 2*(p-1)/2 + // so we can compute isOdd = b AND (x != 0) + let [b, z] = exists(2, () => { + let x0 = x.toBigInt(); + return [x0 & 1n, x0 >> 1n]; + }); + let isOdd = b.assertBool(); + z.assertLessThan((Fp.modulus + 1n) / 2n); + + // x == b + 2z + b.add(z.mul(2)).assertEquals(x); + + // avoid overflow case when x = 0 + let isNonZero = x.equals(0).not(); + return { isOdd: isOdd.and(isNonZero), high: z }; +} + /** * internal helper, split Field into a 3-limb bigint * From 51ee9f5c0e49c31e06c9638ddb12de292e5491e4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 5 Apr 2024 17:50:58 +0200 Subject: [PATCH 27/45] dedicated gadget of 130 rows for scaling by Field --- src/lib/provable/crypto/nullifier.ts | 6 +- src/lib/provable/crypto/signature.ts | 5 +- src/lib/provable/gadgets/native-curve.ts | 80 ++++++++++++++++++++++-- src/lib/provable/group.ts | 9 ++- src/lib/provable/scalar.ts | 2 +- src/lib/provable/test/group.unit-test.ts | 9 ++- tests/vk-regression/vk-regression.json | 10 +-- 7 files changed, 99 insertions(+), 22 deletions(-) diff --git a/src/lib/provable/crypto/nullifier.ts b/src/lib/provable/crypto/nullifier.ts index 80ad805f71..589c8ac441 100644 --- a/src/lib/provable/crypto/nullifier.ts +++ b/src/lib/provable/crypto/nullifier.ts @@ -50,8 +50,6 @@ class Nullifier extends Struct({ public: { nullifier, s }, private: { c }, } = this; - let cScalar = Scalar.fromNativeField(c); - // generator let G = Group.generator; @@ -71,7 +69,7 @@ class Nullifier extends Struct({ // shifted scalar see https://github.com/o1-labs/o1js/blob/5333817a62890c43ac1b9cb345748984df271b62/src/lib/signature.ts#L220 // pk^c - let pk_c = this.publicKey.scale(cScalar); + let pk_c = this.publicKey.scale(c); // g^r = g^s / pk^c let g_r = G.scale(s).sub(pk_c); @@ -80,7 +78,7 @@ class Nullifier extends Struct({ let h_m_pk_s = h_m_pk.scale(s); // h_m_pk_r = h(m,pk)^s / nullifier^c - let h_m_pk_s_div_nullifier_s = h_m_pk_s.sub(nullifier.scale(cScalar)); + let h_m_pk_s_div_nullifier_s = h_m_pk_s.sub(nullifier.scale(c)); // this is supposed to match the entries generated on "the other side" of the nullifier (mina-signer, in an wallet enclave) Poseidon.hash([ diff --git a/src/lib/provable/crypto/signature.ts b/src/lib/provable/crypto/signature.ts index 3dc6d4bbc1..396e50cdea 100644 --- a/src/lib/provable/crypto/signature.ts +++ b/src/lib/provable/crypto/signature.ts @@ -263,7 +263,7 @@ class Signature extends CircuitValue { signaturePrefix('testnet'), msg.concat([publicKey.x, publicKey.y, r]) ); - let e = Scalar.fromNativeField(h); + let e = Scalar.fromField(h); let s = e.mul(d).add(k); return new Signature(r, s); } @@ -283,8 +283,7 @@ class Signature extends CircuitValue { msg.concat([point.x, point.y, this.r]) ); - let e = Scalar.fromNativeField(h); - let r = point.scale(e).neg().add(Group.generator.scale(this.s)); + let r = point.scale(h).neg().add(Group.generator.scale(this.s)); return r.x.equals(this.r).and(r.y.isEven()); } diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 7bc037dce8..793578a4d7 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -2,10 +2,10 @@ import type { Field } from '../field.js'; import type { Bool } from '../bool.js'; import { Fp, Fq } from '../../../bindings/crypto/finite-field.js'; import { PallasAffine } from '../../../bindings/crypto/elliptic-curve.js'; -import { fieldToField3 } from './comparison.js'; +import { fieldToField3, isOddAndHigh } from './comparison.js'; import { Field3, ForeignField } from './foreign-field.js'; import { exists } from '../core/exists.js'; -import { bit, bitSlice, isConstant, packBits } from './common.js'; +import { assert, bit, bitSlice, isConstant, packBits } from './common.js'; import { TupleN } from '../../util/types.js'; import { l, rangeCheck64 } from './range-check.js'; import { createField, getField } from '../core/field-constructor.js'; @@ -15,7 +15,8 @@ import { MlPair } from '../../ml/base.js'; import { provable } from '../types/provable-derivers.js'; export { - scale, + scaleFieldDirect, + scaleField, fieldToShiftedScalar, field3ToShiftedScalar, scaleShiftedSplit5, @@ -25,10 +26,81 @@ export { type Point = { x: Field; y: Field }; type ShiftedScalar = { low5: TupleN; high250: Field }; +/** + * Dedicated gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. + */ +function scaleFieldDirect(P: Point, s: Field): Point { + // 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 { x: createField(sP.x), y: createField(sP.y) }; + } + const Field = getField(); + const Point = provable({ x: Field, y: Field }); + + /** + * Strategy: + * - use a (1, 254) split and compute s - 2^255 with manual add-and-carry + * - use all 255 rounds of `scaleFastUnpack` for the high part + * - pass in s or a dummy replacement if s = 0, 1 (which are the disallowed values) + * - return sP or 0P = 0 or 1P = P + */ + + // compute t = s + (-2^255 mod q) in (1, 254) arithmetic + let { isOdd: sLoBool, high: sHi } = isOddAndHigh(s); + let sLo = sLoBool.toField(); + + let shift = Fq.mod(-(1n << 255n)); + let shiftLo = shift & 1n; + let shiftHi = shift >> 1n; + + let carry = sLo.mul(shiftLo).seal(); // = either 0 or lowBit + let tLo = sLo.add(shiftLo).sub(carry).assertBool(); + let tHi = sHi.add(shiftHi).add(carry).seal(); + + // tHi does not overflow: + // tHi = sHi + shiftHi + carry < p/2 + (p/2 - 1) + 1 = p + // sHi < p/2 is guaranteed by isOddAndHigh + assert(shiftHi < Fp.modulus / 2n - 1n); + + // the 4 values for s not supported by `scaleFastUnpack` are q-2, q-1, 0, 1 + // since s came from a `Field`, we can exclude q-2, q-1 + // s = 0 or 1 iff sHi = 0 + let isEdgeCase = sHi.equals(0n); + let tHiSafe = Provable.if(isEdgeCase, createField(0n), tHi); + + // R = (2*(t >> 1) + 1 + 2^255)P + // also returns a 255-bit representation of tHi + let [, RMl, [, ...tHiBitsMl]] = Snarky.group.scaleFastUnpack( + [0, x.value, y.value], + [0, tHiSafe.value], + 255 + ); + let R = { x: createField(RMl[1]), y: createField(RMl[2]) }; + + // prove that tHi has only 254 bits set + createField(tHiBitsMl[254]).assertEquals(0n); + + // R = tLo ? R : R - P = (t + 2^255)P = sP + let { result: RminusP, isInfinity } = add(R, negate(P)); + isInfinity.assertFalse(); // can only be zero if s = 0, which we handle later + R = Provable.if(tLo, Point, R, RminusP); + + // now handle the two edge cases s=0 and s=1 + let zero = createField(0n); + let zeroPoint = { x: zero, y: zero }; + let edgeCaseResult = Provable.if(sLoBool, Point, P, zeroPoint); + return Provable.if(isEdgeCase, Point, edgeCaseResult, R); +} + /** * Gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. */ -function scale(P: Point, s: Field): Point { +function scaleField(P: Point, s: Field): Point { // constant case let { x, y } = P; if (x.isConstant() && y.isConstant() && s.isConstant()) { diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index 3dc46da93d..079605595d 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -10,7 +10,11 @@ import { import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { assert } from '../util/assert.js'; -import { add, scaleShiftedSplit5 } from './gadgets/native-curve.js'; +import { + add, + scaleFieldDirect, + scaleShiftedSplit5, +} from './gadgets/native-curve.js'; export { Group }; @@ -176,7 +180,8 @@ class Group { * let 5g = g.scale(s); * ``` */ - scale(s: Scalar | number | bigint) { + scale(s: Scalar | Field | number | bigint) { + if (s instanceof Field) return new Group(scaleFieldDirect(this, s)); let scalar = Scalar.from(s); if (isConstant(this) && scalar.isConstant()) { diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index ce39c53aa8..7862eb26f9 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -55,7 +55,7 @@ class Scalar { * * This is always possible and unambiguous, since the scalar field is larger than the base field. */ - static fromNativeField(s: Field): Scalar { + static fromField(s: Field): Scalar { let { low5, high250 } = fieldToShiftedScalar(s); return new Scalar(low5, high250); } diff --git a/src/lib/provable/test/group.unit-test.ts b/src/lib/provable/test/group.unit-test.ts index 1ce478eb7c..3eba9d1b5e 100644 --- a/src/lib/provable/test/group.unit-test.ts +++ b/src/lib/provable/test/group.unit-test.ts @@ -8,17 +8,20 @@ import { Field } from '../field.js'; console.log('group consistency tests'); -test(Random.field, Random.scalar, (a, s0, assert) => { +test(Random.field, Random.scalar, Random.field, (a, s0, x0, assert) => { const { x: x1, y: { x0: y1 }, } = Poseidon.hashToGroup([a])!; const g = Group.from(x1, y1); + + // scale by a scalar const s = Scalar.from(s0); runScale(g, s, (g, s) => g.scale(s), assert); - const sField = Field.from(s0); - runScale(g, sField, (g, s) => g.scale(Scalar.fromNativeField(s)), assert); + // scale by a field + const x = Field.from(x0); + runScale(g, x, (g, x) => g.scale(x), assert); }); // tests consistency between in- and out-circuit implementations diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 3dd2b409b2..7c10c02462 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -288,7 +288,7 @@ } }, "diverse": { - "digest": "1254572f16452c387616f43d4776e6fae1c48b5ddf1711d1d24f7f0224261c0b", + "digest": "28aff9bf8061aa25a536586453c1b82bb004f2c64c13da207409d2daa7a9f036", "methods": { "ecdsa": { "rows": 28182, @@ -299,8 +299,8 @@ "digest": "c23e00e466878466ae8ab23bde562792" }, "pallas": { - "rows": 534, - "digest": "3aa941d5660eefd97277bf6a9ea95bc7" + "rows": 505, + "digest": "7c72616c97a255f802f555f4cf87ebc9" }, "poseidon": { "rows": 946, @@ -316,8 +316,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "228660131678223074273190600255634833879027584418616002912925867203675143125" + "data": "AQF7dPFDw6gfOnpKU01SomZATrwO3LZ+rYyF+2Z6WBRXC9al4xzlaSDb/aliUZrsNskvVsZaMD1ERwQlwm6u/LwYltTzUPlyKitiM8XhDU4hqin8dql4vS37u8JNYatwiSVu5MbTYpJX2hvn6e8XADxGucO6BW4mLGaLS4RHD1GjO4hiB9gYfKA5HCuaEemLMInl6kTVdsi5mkFGuGYiDJk9Bc8xL96TS1b9MdFN8yYwdix1I8VWhl9IN+/tPYvWazseC8RvtGfQ/5JQlvIYwQKQvJD2sYuRbaRavoOfywZ0OTT9xting/5hzbGwFoHPBRq4vZTGmw/9NcxbOJnESdEQ4jyG5U9xcUoS/ZihRPrsNCPxQDjk7nLo3fO4EIPs1QG9uz5YhkkHWtDGkXdkaC4mIVqUoHNZTHiUPEH2Cy7tB8shddphO2gDhsTOYKDml+ASLqpDapu9vSAEcyNAZW4TNv53WS2Kx8tGGwsUXss0tnjtkc2kGXL+k/KVdfgFHy7gM2RKnP+xm9nv8xEIG6IzEdbsbrATqHz8p3o/9a1nIRkl2z63JrDxpxsRvLHlQBNvnPHiANflIWomFuW6VJ8VAA2NngnkeK+2DmF9q2VBz7bWhF7R1U6+GihCfsLw8swAtR7YDF2WfPpE7L4UtyT7mE7cCZSh348SWVtVfaRMxSCaVzP1VKKjfUMQqipCyv3isdqbaNJhY/3ygF9a/sM2AeqYN5NDxqoqJMCjmqCLB7cvfZ45wNoBZ5iyajgM92oMKPzWwrPNqx5fNeszqennJ0ddaXhYxeJOnl7WZLOeZhImZDYJEteIkq/ApsKCl/AweQ6YpzfGQfcqDz/371mfOaJnHQfkJ1xNZk5AWvZPR5ac4YfHyrHcLAq9YBXDkLsQ3GIGLeVGU+YwBsUcKfzQ2REmsZGeQNU7+ynJkePgwgm9gOSUL3TuW4M5f0cpSuFFM+OiR2wMyH7/PqZf0stkJDuG2OTF9Ml1njepMB9u2cjM830ZcRja8wnITg1ZUdMyr5PCjQ3zDooGAWoyhKfNU3cYLxH/U47EBVoiwPML3hRA05e4sy2pK4+Q0xP1N8ZVxmN4Ckrd/gdkQamk9toHDSEx3i3GwXmS1i9BR+wly28OoNeouPy/cIQft47ESOECMcRaNf/lQQ++jEuJy7a48XyQOsZO3kVA+mhs1d/MlRDb2zE/HXKChdo+fIljdH+QsMUA2UhUAFf3vOyqeeQoF4NwuVJZY3nY5ewiK/yOefv6mR0WVk4Wzu00vFzThokNAOGxnLXLv2jZwWnfFuh3hKLIZ/pLyOKMGsu0ggAWFilVsW8wBcUefvdx/jZThlwm5EbJLoLuGOQ6albp6l1fP16pT3BPS6XeeZmv5lEPtb1M9TH3jzwY+AJGcAsx3eQo6/ffLrE3LKW+04nx+fJOsF4vhij6xPcaetjoKIs+mgsLmaDTI0KInfGdlZJrAEfQitUkMQDEPbKqvs/3s46jKbbgmb/+vr1paXwUX8gjIHpI5AtDeQT/VjM5Sc7JzXQJWqBCHtTyllIo5/mqEbit19RHtgMU77IeB0cgr6Rhzz9oKFEvzsI86dMnlmd12WQeJWtEs7OuxXdRvUe7H/tFC0DA2bah4diaXKtEiAxYsn8tMVVNJyWQmpiro6joJdEJoX6ddthN9PBzoFqzxHhN5F7gXELvBhvCnYtSxifcTDizGzLDw+M9VjL7Timz9HgTWpraWZVvTKDnfU2PHD6MO/AglJ3D4G7/ecSg563bm4I8Sx9JOcDvGQv4znXQLfQDORa5S2nkftBYO/f+26UvwkPu2YVIzroJYLVLDISYRRWsE6XRdo/Zqu5YWlHKhDm0+GvjCUdg3Z6mUlReHckBDwA8D1JNTIovdwo7iK2R+hjqGEsH0UwypDT5EBcco3EPMaJYlAWUU6n3eFkdSn7YHF5lTvT6JaD2SJVRlmauf60o6xPH1+BaAcwsMtWZe3OYi6zdn+MYPgI2RPHI5xog1xcFPfJOzu4j+88oyO9YQPIGPKqqboXVWXdhCzECkAyZIuc6h/nBrc+C+qtC2WX+Ex6q5dUIaGk5OCGIS+xE2boaMtPpUhjrRUWQ8eiFZd4fCPcRodjVejJtVHzx1cS3nh8brMJGX+BVKdnoHYkypoPuo2Cw+UZaQbqniRTedIptGJThxgkBm8MuGODHsz0N7DCC02fUK+F1BPTNdDVwhpQ+wWtS1aiyE7KTGkbm7WxvdmQqfHV6lQ2o0XMvwQHihTtmH/m13hSG+UoAbZzhNYtQ+OnRi+c7C29KfVY6JM24BonfyRlpVfcTiOG1KfYs3ZQ+ZjG2bRsdLVI4f0qu8Nk4dn5E41NzXD+Fd1Kuv35E5qN3D7YZ0T3u+1J0y7pGbh0=", + "hash": "9141527858419493676659965468935555446669173934340605986585721765602387935902" } } } \ No newline at end of file From f88222d480ffb6e00ef3077c2a91a083c1efa106 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 15:04:58 +0200 Subject: [PATCH 28/45] use 1, 254 split and handle edge cases --- src/lib/provable/gadgets/native-curve.ts | 177 ++++++++++++++++++++++- src/lib/provable/group.ts | 8 +- src/lib/provable/scalar.ts | 70 +++++---- 3 files changed, 205 insertions(+), 50 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 793578a4d7..a05d8ad01a 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -8,7 +8,11 @@ import { exists } from '../core/exists.js'; import { assert, bit, bitSlice, isConstant, packBits } from './common.js'; import { TupleN } from '../../util/types.js'; import { l, rangeCheck64 } from './range-check.js'; -import { createField, getField } from '../core/field-constructor.js'; +import { + createBool, + createField, + getField, +} from '../core/field-constructor.js'; import { Snarky } from '../../../snarky.js'; import { Provable } from '../provable.js'; import { MlPair } from '../../ml/base.js'; @@ -18,13 +22,17 @@ export { scaleFieldDirect, scaleField, fieldToShiftedScalar, + fieldToShiftedScalarSplit5, field3ToShiftedScalar, + scaleShifted, scaleShiftedSplit5, add, + ShiftedScalar, }; type Point = { x: Field; y: Field }; -type ShiftedScalar = { low5: TupleN; high250: Field }; +type ShiftedScalar = { lowBit: Bool; high254: Field }; +type ShiftedScalarSplit5 = { low5: TupleN; high250: Field }; /** * Dedicated gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. @@ -111,25 +119,103 @@ function scaleField(P: Point, s: Field): Point { return { x: createField(sP.x), y: createField(sP.y) }; } // compute t = s - 2^254 mod q using foreign field subtraction, and split into 5 low bits and 250 high bits - let t = fieldToShiftedScalar(s); + let t = fieldToShiftedScalarSplit5(s); // return (t + 2^254)*P = (s - 2^254 + 2^254)*P = s*P return scaleShiftedSplit5(P, t); } +/** + * Converts a field element s to a shifted representation t = s - 2^254 mod q, + * where t is represented as a low bit and a 254-bit high part. + * + * This is the representation we use for scalars, since it can be used as input to `scaleShifted()`. + */ +function fieldToShiftedScalar(s: Field): ShiftedScalar { + // constant case + if (s.isConstant()) { + let t = Fq.mod(s.toBigInt() - (1n << 255n)); + let lowBit = createBool((t & 1n) === 1n); + let high254 = createField(t >> 1n); + return { lowBit, high254 }; + } + + // compute t = s + (-2^255 mod q) in (1, 254) arithmetic + let { isOdd: sLoBool, high: sHi } = isOddAndHigh(s); + let sLo = sLoBool.toField(); + + let shift = Fq.mod(-(1n << 255n)); + let shiftLo = shift & 1n; + let shiftHi = shift >> 1n; + + let carry = sLo.mul(shiftLo).seal(); // = either 0 or lowBit + let tLo = sLo.add(shiftLo).sub(carry).assertBool(); + let tHi = sHi.add(shiftHi).add(carry).seal(); + + // tHi does not overflow: + // tHi = sHi + shiftHi + carry < p/2 + (p/2 - 1) + 1 = p + // sHi < p/2 is guaranteed by isOddAndHigh + assert(shiftHi < Fp.modulus / 2n - 1n); + + return { lowBit: tLo, high254: tHi }; +} + /** * Converts a field element s to a shifted representation t = s - 2^254 mod q, * where t is represented as a 5-bit low part and a 250-bit high part. * * This is the representation we use for scalars, since it can be used as input to `scaleShiftedSplit5()`. */ -function fieldToShiftedScalar(s: Field): ShiftedScalar { +function fieldToShiftedScalarSplit5(s: Field): ShiftedScalarSplit5 { let sBig = fieldToField3(s); // assert that sBig is canonical mod p, so that we can't add (kp mod q) factors by doing things modulo q ForeignField.assertLessThan(sBig, Fp.modulus); - return field3ToShiftedScalar(sBig); + return field3ToShiftedScalarSplit5(sBig); +} + +/** + * Converts a 3-limb bigint to a shifted representation t = s - 2^255 mod q, + * where t is represented as a low bit and a 254-bit high part. + */ +function field3ToShiftedScalar(s: Field3): ShiftedScalar { + // constant case + if (Field3.isConstant(s)) { + let t = Fq.mod(Field3.toBigint(s) - (1n << 255n)); + let lowBit = createBool((t & 1n) === 1n); + let high254 = createField(t >> 1n); + return { lowBit, high254 }; + } + + // compute t = s - 2^255 mod q using foreign field subtraction + let twoTo255 = Field3.from(Fq.mod(1n << 255n)); + let t = ForeignField.sub(s, twoTo255, Fq.modulus); + + // it's necessary to prove that t is canonical -- otherwise its bit representation is ambiguous + ForeignField.assertLessThan(t, Fq.modulus); + + let [t0, t1, t2] = t; + + // split t into 254 high bits and a low bit + // => split t0 into [1, 87] + let [tLo, tHi0] = exists(2, () => { + let t0_ = t0.toBigInt(); + return [bit(t0_, 0), t0_ >> 1n]; + }); + let tLoBool = tLo.assertBool(); + + // prove split + // since we know that t0 < 2^88, this proves that t0High < 2^87 + tLo.add(tHi0.mul(2n)).assertEquals(t0); + + // pack tHi + let tHi = tHi0 + .add(t1.mul(1n << (l - 1n))) + .add(t2.mul(1n << (2n * l - 1n))) + .seal(); + + return { lowBit: tLoBool, high254: tHi }; } /** @@ -139,10 +225,10 @@ function fieldToShiftedScalar(s: Field): ShiftedScalar { * This assumes that `s` is range-checked to some extent, for example a safe bound is s < 2^258 or anything less. * If s is > 2^259, the high part computation can overflow the base field and the result is incorrect. */ -function field3ToShiftedScalar( +function field3ToShiftedScalarSplit5( s: Field3, { proveUnique = false } = {} -): ShiftedScalar { +): ShiftedScalarSplit5 { // constant case if (Field3.isConstant(s)) { let t = Fq.mod(Field3.toBigint(s) - (1n << 254n)); @@ -201,6 +287,81 @@ function field3ToShiftedScalar( return { low5: tLoBools, high250: tHi }; } +/** + * Internal helper to compute `(t + 2^255)*P`. + * `t` is expected to be split into 254 high bits (t >> 1) and a low bit (t & 1). + * + * The gadget proves that `tHi` is in [0, 2^254) but assumes that `tLo` consists of bits. + * + * Optionally, you can specify a different number of high bits by passing in `numHighBits`. + */ +function scaleShifted( + { x, y }: Point, + { lowBit: tLo, high254: tHi }: ShiftedScalar, + numHighBits = 254 +): Point { + // constant case + if (isConstant(x, y, tHi, tLo)) { + let sP = PallasAffine.scale( + PallasAffine.fromNonzero({ x: x.toBigInt(), y: y.toBigInt() }), + Fq.mod(tLo.toField().toBigInt() + 2n * tHi.toBigInt() + (1n << 255n)) + ); + return { x: createField(sP.x), y: createField(sP.y) }; + } + const Field = getField(); + const Point = provable({ x: Field, y: Field }); + let zero = createField(0n); + + /** + * Strategy: + * - use all 255 rounds of `scaleFastUnpack` for the high part + * - handle two disallowed tHi values separately: -2^254, -2^254 - 1 + * - don't handle disallowed tHi = -2^254 - 1/2 because it wouldn't normally be used, as it's > q/2 + */ + let equalsMinusShift = tHi.equals(Fq.modulus - (1n << 254n)); + let equalsMinusShiftMinus1 = tHi.equals(Fq.modulus - (1n << 254n) - 1n); + let isEdgeCase = equalsMinusShift.or(equalsMinusShiftMinus1); + let tHiSafe = Provable.if(isEdgeCase, zero, tHi); + + // R = (2*(t >> 1) + 1 + 2^255)P + // also returns a 255-bit representation of tHi + let [, RMl, [, ...tHiBitsMl]] = Snarky.group.scaleFastUnpack( + [0, x.value, y.value], + [0, tHiSafe.value], + 255 + ); + let P = { x, y }; + let R = { x: createField(RMl[1]), y: createField(RMl[2]) }; + + // prove that tHi has only `numHighBits` bits set + for (let i = numHighBits; i < 255; i++) { + createField(tHiBitsMl[i]).assertEquals(zero); + } + + // R = tLo ? R : R - P = (t + 2^255)P + // we also handle a zero R-P result to make scaling work for the 0 scalar + let minusP = negate(P); + let RminusP = addNonZero(R, minusP); + R = Provable.if(tLo, Point, R, RminusP); + + // handle the edge cases + // 2*(-2^254) + 1 + 2^255 = 1 + // 2*(-2^254 - 1) + 1 + 2^255 = -1 + let minus2P = addNonZero(minusP, minusP); + let zeroPoint = { x: zero, y: zero }; + let minusShiftResult = Provable.if(tLo, Point, P, zeroPoint); + let minusShiftMinus1Result = Provable.if(tLo, Point, minusP, minus2P); + let edgeCaseResult = Provable.if( + equalsMinusShift, + Point, + minusShiftResult, + minusShiftMinus1Result + ); + R = Provable.if(isEdgeCase, Point, edgeCaseResult, R); + + return R; +} + /** * 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 & 0x1f). @@ -209,7 +370,7 @@ function field3ToShiftedScalar( */ function scaleShiftedSplit5( { x, y }: Point, - { low5: tLo, high250: tHi }: ShiftedScalar + { low5: tLo, high250: tHi }: ShiftedScalarSplit5 ): Point { // constant case if (isConstant(x, y, tHi, ...tLo)) { diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index 079605595d..e9aca7d4a6 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -10,11 +10,7 @@ import { import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { assert } from '../util/assert.js'; -import { - add, - scaleFieldDirect, - scaleShiftedSplit5, -} from './gadgets/native-curve.js'; +import { add, scaleFieldDirect, scaleShifted } from './gadgets/native-curve.js'; export { Group }; @@ -188,7 +184,7 @@ class Group { let g_proj = Pallas.scale(toProjective(this), scalar.toBigInt()); return fromProjective(g_proj); } else { - let result = scaleShiftedSplit5(this, scalar); + let result = scaleShifted(this, scalar); return new Group(result); } } diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index 7862eb26f9..8d788b4666 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -3,12 +3,12 @@ import { Scalar as SignableFq } from '../../mina-signer/src/curve-bigint.js'; import { Field, checkBitLength } from './field.js'; import { FieldVar } from './core/fieldvar.js'; import { Bool } from './bool.js'; -import { TupleN } from '../util/types.js'; import { + ShiftedScalar, field3ToShiftedScalar, fieldToShiftedScalar, } from './gadgets/native-curve.js'; -import { isConstant, packBits } from './gadgets/common.js'; +import { isConstant } from './gadgets/common.js'; import { Provable } from './provable.js'; import { assert } from '../util/assert.js'; import type { HashInput } from './types/provable-derivers.js'; @@ -21,20 +21,20 @@ type ScalarConst = [0, bigint]; /** * Represents a {@link Scalar}. */ -class Scalar { +class Scalar implements ShiftedScalar { /** - * We represent a scalar s in shifted form `t = s - 2^254 mod q, - * split into its low 5 bits (t & 0x1f) and high 250 bits (t >> 5). - * The reason is that we can efficiently compute the scalar multiplication `(t + 2^254) * P = s * P`. + * We represent a scalar s in shifted form `t = s - 2^255 mod q, + * split into its low bit (t & 1) and high 254 bits (t >> 1). + * The reason is that we can efficiently compute the scalar multiplication `(t + 2^255) * P = s * P`. */ - low5: TupleN; - high250: Field; + lowBit: Bool; + high254: Field; static ORDER = Fq.modulus; - private constructor(low5: TupleN, high250: Field) { - this.low5 = low5; - this.high250 = high250; + private constructor(lowBit: Bool, high254: Field) { + this.lowBit = lowBit; + this.high254 = high254; } /** @@ -44,10 +44,10 @@ class Scalar { */ static from(s: Scalar | bigint | number | string): Scalar { if (s instanceof Scalar) return s; - let t = Fq.mod(BigInt(s) - (1n << 254n)); - let low5 = new Field(t & 0x1fn).toBits(5); - let high250 = new Field(t >> 5n); - return new Scalar(TupleN.fromArray(5, low5), high250); + let t = Fq.mod(BigInt(s) - (1n << 255n)); + let lowBit = new Bool((t & 1n) === 1n); + let high254 = new Field(t >> 1n); + return new Scalar(lowBit, high254); } /** @@ -56,8 +56,8 @@ class Scalar { * This is always possible and unambiguous, since the scalar field is larger than the base field. */ static fromField(s: Field): Scalar { - let { low5, high250 } = fieldToShiftedScalar(s); - return new Scalar(low5, high250); + let { lowBit, high254 } = fieldToShiftedScalar(s); + return new Scalar(lowBit, high254); } /** @@ -65,8 +65,8 @@ class Scalar { * If a {@link Scalar} is constructed outside provable code, it is a constant. */ isConstant() { - let { low5, high250 } = this; - return isConstant(high250, ...low5); + let { lowBit, high254 } = this; + return isConstant(lowBit, high254); } /** @@ -85,11 +85,9 @@ class Scalar { * Convert this {@link Scalar} into a bigint */ toBigInt() { - let { low5, high250 } = this.toConstant(); - return Fq.add( - packBits(low5).toBigInt() + (high250.toBigInt() << 5n), - 1n << 254n - ); + let { lowBit, high254 } = this.toConstant(); + let t = lowBit.toField().toBigInt() + 2n * high254.toBigInt(); + return Fq.mod(t + (1n << 255n)); } /** @@ -104,8 +102,8 @@ class Scalar { let sBig = field3FromBits(bits); // convert to shifted representation - let { low5, high250 } = field3ToShiftedScalar(sBig); - return new Scalar(low5, high250); + let { lowBit, high254 } = field3ToShiftedScalar(sBig); + return new Scalar(lowBit, high254); } /** @@ -211,7 +209,7 @@ class Scalar { * The fields are not constrained to be boolean. */ static toFields(x: Scalar) { - return [...x.low5.map((b) => b.toField()), x.high250]; + return [x.lowBit.toField(), x.high254]; } /** @@ -239,7 +237,7 @@ class Scalar { * */ static toInput(x: Scalar): HashInput { - return { fields: [x.high250], packed: x.low5.map((f) => [f.toField(), 1]) }; + return { fields: [x.high254], packed: [[x.lowBit.toField(), 1]] }; } /** @@ -258,12 +256,12 @@ class Scalar { */ static fromFields(fields: Field[]): Scalar { assert( - fields.length === 6, - `Scalar.fromFields(): expected 6 fields, got ${fields.length}` + fields.length === 2, + `Scalar.fromFields(): expected 2 fields, got ${fields.length}` ); - let low5 = fields.slice(0, 5).map(Bool.Unsafe.fromField); - let high250 = fields[5]; - return new Scalar(TupleN.fromArray(5, low5), high250); + let lowBit = Bool.Unsafe.fromField(fields[0]); + let high254 = fields[1]; + return new Scalar(lowBit, high254); } /** @@ -272,7 +270,7 @@ class Scalar { * Returns the size of this type in {@link Field} elements. */ static sizeInFields(): number { - return 6; + return 2; } /** @@ -280,10 +278,10 @@ class Scalar { */ static check(s: Scalar) { /** - * It is not necessary to constrain the range of high250, because the only provable operation on Scalar + * It is not necessary to constrain the range of high254, because the only provable operation on Scalar * which relies on that range is scalar multiplication -- which constrains the range itself. */ - return s.low5.forEach(Bool.check); + return Bool.check(s.lowBit); } // ProvableExtended From 00680edba1b3a620c433fba2b24c72d9c7d13a3d Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 15:27:34 +0200 Subject: [PATCH 29/45] reduce constraints --- src/lib/provable/gadgets/native-curve.ts | 30 +++++++++--------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index a05d8ad01a..a09d785196 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -94,8 +94,7 @@ function scaleFieldDirect(P: Point, s: Field): Point { createField(tHiBitsMl[254]).assertEquals(0n); // R = tLo ? R : R - P = (t + 2^255)P = sP - let { result: RminusP, isInfinity } = add(R, negate(P)); - isInfinity.assertFalse(); // can only be zero if s = 0, which we handle later + let RminusP = addNonZero(R, negate(P)); // can only be zero if s = 0, which we handle later R = Provable.if(tLo, Point, R, RminusP); // now handle the two edge cases s=0 and s=1 @@ -338,27 +337,20 @@ function scaleShifted( createField(tHiBitsMl[i]).assertEquals(zero); } - // R = tLo ? R : R - P = (t + 2^255)P - // we also handle a zero R-P result to make scaling work for the 0 scalar - let minusP = negate(P); - let RminusP = addNonZero(R, minusP); - R = Provable.if(tLo, Point, R, RminusP); - - // handle the edge cases + // handle edge cases // 2*(-2^254) + 1 + 2^255 = 1 // 2*(-2^254 - 1) + 1 + 2^255 = -1 - let minus2P = addNonZero(minusP, minusP); - let zeroPoint = { x: zero, y: zero }; - let minusShiftResult = Provable.if(tLo, Point, P, zeroPoint); - let minusShiftMinus1Result = Provable.if(tLo, Point, minusP, minus2P); - let edgeCaseResult = Provable.if( - equalsMinusShift, - Point, - minusShiftResult, - minusShiftMinus1Result - ); + // so the result is (x,+-y) + let edgeCaseY = y.mul(equalsMinusShift.toField().mul(2n).sub(1n)); // y*(2b - 1) = y or -y + let edgeCaseResult = { x, y: edgeCaseY }; R = Provable.if(isEdgeCase, Point, edgeCaseResult, R); + // R = tLo ? R : R - P = (t + 2^255)P + // we also handle a zero R-P result to make the 0 scalar work + let { result: RminusP, isInfinity } = add(R, negate(P)); + RminusP = Provable.if(isInfinity, Point, { x: zero, y: zero }, RminusP); + R = Provable.if(tLo, Point, R, RminusP); + return R; } From 0aad84e54f804848ad0e01eee4c4d402d374e56f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 16:11:51 +0200 Subject: [PATCH 30/45] tighten fromBits gadget --- src/lib/provable/gadgets/native-curve.ts | 28 +++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index a09d785196..ddc366c532 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -187,28 +187,36 @@ function field3ToShiftedScalar(s: Field3): ShiftedScalar { return { lowBit, high254 }; } - // compute t = s - 2^255 mod q using foreign field subtraction + // compute t = s - (2^255 mod q) using foreign field subtraction let twoTo255 = Field3.from(Fq.mod(1n << 255n)); let t = ForeignField.sub(s, twoTo255, Fq.modulus); + let [t0, t1, t2] = t; - // it's necessary to prove that t is canonical -- otherwise its bit representation is ambiguous + // to fully constrain the output scalar, we need to prove that t is canonical + // otherwise, the subtraction above can add +q to the result, which yields an alternative bit representation + // this also provides a bound on the high part, to that the computation of tHi can't overflow ForeignField.assertLessThan(t, Fq.modulus); - let [t0, t1, t2] = t; - // split t into 254 high bits and a low bit - // => split t0 into [1, 87] - let [tLo, tHi0] = exists(2, () => { - let t0_ = t0.toBigInt(); - return [bit(t0_, 0), t0_ >> 1n]; + // => split t0 into [1, 87] => split t0 into [1, 64, 23] so we can efficiently range-check + let [tLo, tHi00, tHi01] = exists(3, () => { + let t = t0.toBigInt(); + return [bit(t, 0), bitSlice(t, 1, 64), bitSlice(t, 65, 23)]; }); let tLoBool = tLo.assertBool(); + rangeCheck64(tHi00); + rangeCheck64(tHi01); - // prove split - // since we know that t0 < 2^88, this proves that t0High < 2^87 + // prove (tLo, tHi0) split + // since we know that t0 < 2^88 and tHi0 < 2^128, this even proves that t0Hi < 2^87 + // (the bound on tHi0 is necessary so that 2*tHi0 can't overflow) + let tHi0 = tHi00.add(tHi01.mul(1n << 64n)); tLo.add(tHi0.mul(2n)).assertEquals(t0); // pack tHi + // this can't overflow the native field because: + // -) we showed t < q + // -) the three combined limbs here represent the bigint tHi = (t >> 1) < q/2 < p let tHi = tHi0 .add(t1.mul(1n << (l - 1n))) .add(t2.mul(1n << (2n * l - 1n))) From 37534dfd0586fb453e872dc0595ddb11a862e261 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 16:15:02 +0200 Subject: [PATCH 31/45] remove unused split5 gadgets --- src/lib/provable/gadgets/native-curve.ts | 284 +++++------------------ 1 file changed, 60 insertions(+), 224 deletions(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index ddc366c532..611055cd19 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -2,11 +2,10 @@ import type { Field } from '../field.js'; import type { Bool } from '../bool.js'; import { Fp, Fq } from '../../../bindings/crypto/finite-field.js'; import { PallasAffine } from '../../../bindings/crypto/elliptic-curve.js'; -import { fieldToField3, isOddAndHigh } from './comparison.js'; +import { isOddAndHigh } from './comparison.js'; import { Field3, ForeignField } from './foreign-field.js'; import { exists } from '../core/exists.js'; -import { assert, bit, bitSlice, isConstant, packBits } from './common.js'; -import { TupleN } from '../../util/types.js'; +import { assert, bit, bitSlice, isConstant } from './common.js'; import { l, rangeCheck64 } from './range-check.js'; import { createBool, @@ -19,25 +18,21 @@ import { MlPair } from '../../ml/base.js'; import { provable } from '../types/provable-derivers.js'; export { - scaleFieldDirect, scaleField, fieldToShiftedScalar, - fieldToShiftedScalarSplit5, field3ToShiftedScalar, scaleShifted, - scaleShiftedSplit5, add, ShiftedScalar, }; type Point = { x: Field; y: Field }; type ShiftedScalar = { lowBit: Bool; high254: Field }; -type ShiftedScalarSplit5 = { low5: TupleN; high250: Field }; /** * Dedicated gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. */ -function scaleFieldDirect(P: Point, s: Field): Point { +function scaleField(P: Point, s: Field): Point { // constant case let { x, y } = P; if (x.isConstant() && y.isConstant() && s.isConstant()) { @@ -105,23 +100,71 @@ function scaleFieldDirect(P: Point, s: Field): Point { } /** - * Gadget to scale a point by a scalar, where the scalar is represented as a _native_ Field. + * Internal helper to compute `(t + 2^255)*P`. + * `t` is expected to be split into 254 high bits (t >> 1) and a low bit (t & 1). + * + * The gadget proves that `tHi` is in [0, 2^254) but assumes that `tLo` consists of bits. + * + * Optionally, you can specify a different number of high bits by passing in `numHighBits`. */ -function scaleField(P: Point, s: Field): Point { +function scaleShifted( + { x, y }: Point, + { lowBit: tLo, high254: tHi }: ShiftedScalar, + numHighBits = 254 +): Point { // constant case - let { x, y } = P; - if (x.isConstant() && y.isConstant() && s.isConstant()) { + if (isConstant(x, y, tHi, tLo)) { let sP = PallasAffine.scale( PallasAffine.fromNonzero({ x: x.toBigInt(), y: y.toBigInt() }), - s.toBigInt() + Fq.mod(tLo.toField().toBigInt() + 2n * tHi.toBigInt() + (1n << 255n)) ); return { x: createField(sP.x), y: createField(sP.y) }; } - // compute t = s - 2^254 mod q using foreign field subtraction, and split into 5 low bits and 250 high bits - let t = fieldToShiftedScalarSplit5(s); + const Field = getField(); + const Point = provable({ x: Field, y: Field }); + let zero = createField(0n); + + /** + * Strategy: + * - use all 255 rounds of `scaleFastUnpack` for the high part + * - handle two disallowed tHi values separately: -2^254, -2^254 - 1 + * - don't handle disallowed tHi = -2^254 - 1/2 because it wouldn't normally be used, as it's > q/2 + */ + let equalsMinusShift = tHi.equals(Fq.modulus - (1n << 254n)); + let equalsMinusShiftMinus1 = tHi.equals(Fq.modulus - (1n << 254n) - 1n); + let isEdgeCase = equalsMinusShift.or(equalsMinusShiftMinus1); + let tHiSafe = Provable.if(isEdgeCase, zero, tHi); + + // R = (2*(t >> 1) + 1 + 2^255)P + // also returns a 255-bit representation of tHi + let [, RMl, [, ...tHiBitsMl]] = Snarky.group.scaleFastUnpack( + [0, x.value, y.value], + [0, tHiSafe.value], + 255 + ); + let P = { x, y }; + let R = { x: createField(RMl[1]), y: createField(RMl[2]) }; + + // prove that tHi has only `numHighBits` bits set + for (let i = numHighBits; i < 255; i++) { + createField(tHiBitsMl[i]).assertEquals(zero); + } - // return (t + 2^254)*P = (s - 2^254 + 2^254)*P = s*P - return scaleShiftedSplit5(P, t); + // handle edge cases + // 2*(-2^254) + 1 + 2^255 = 1 + // 2*(-2^254 - 1) + 1 + 2^255 = -1 + // so the result is (x,+-y) + let edgeCaseY = y.mul(equalsMinusShift.toField().mul(2n).sub(1n)); // y*(2b - 1) = y or -y + let edgeCaseResult = { x, y: edgeCaseY }; + R = Provable.if(isEdgeCase, Point, edgeCaseResult, R); + + // R = tLo ? R : R - P = (t + 2^255)P + // we also handle a zero R-P result to make the 0 scalar work + let { result: RminusP, isInfinity } = add(R, negate(P)); + RminusP = Provable.if(isInfinity, Point, { x: zero, y: zero }, RminusP); + R = Provable.if(tLo, Point, R, RminusP); + + return R; } /** @@ -159,21 +202,6 @@ function fieldToShiftedScalar(s: Field): ShiftedScalar { return { lowBit: tLo, high254: tHi }; } -/** - * Converts a field element s to a shifted representation t = s - 2^254 mod q, - * where t is represented as a 5-bit low part and a 250-bit high part. - * - * This is the representation we use for scalars, since it can be used as input to `scaleShiftedSplit5()`. - */ -function fieldToShiftedScalarSplit5(s: Field): ShiftedScalarSplit5 { - let sBig = fieldToField3(s); - - // assert that sBig is canonical mod p, so that we can't add (kp mod q) factors by doing things modulo q - ForeignField.assertLessThan(sBig, Fp.modulus); - - return field3ToShiftedScalarSplit5(sBig); -} - /** * Converts a 3-limb bigint to a shifted representation t = s - 2^255 mod q, * where t is represented as a low bit and a 254-bit high part. @@ -225,198 +253,6 @@ function field3ToShiftedScalar(s: Field3): ShiftedScalar { return { lowBit: tLoBool, high254: tHi }; } -/** - * Converts a 3-limb bigint to a shifted representation t = s - 2^254 mod q, - * where t is represented as a 5-bit low part and a 250-bit high part. - * - * This assumes that `s` is range-checked to some extent, for example a safe bound is s < 2^258 or anything less. - * If s is > 2^259, the high part computation can overflow the base field and the result is incorrect. - */ -function field3ToShiftedScalarSplit5( - s: Field3, - { proveUnique = false } = {} -): ShiftedScalarSplit5 { - // constant case - if (Field3.isConstant(s)) { - let t = Fq.mod(Field3.toBigint(s) - (1n << 254n)); - let low5 = createField(t & 0x1fn).toBits(5); - let high250 = createField(t >> 5n); - return { low5: TupleN.fromArray(5, low5), high250 }; - } - - // compute t = s - 2^254 mod q using foreign field subtraction - let twoTo254 = Field3.from(1n << 254n); - let t = ForeignField.sub(s, twoTo254, Fq.modulus); - let [t0, t1, t2] = t; - - if (proveUnique) { - // to fully constrain the output scalar, we need to prove that t is canonical - // otherwise, the subtraction above can add +q to the result, which yields an alternative bit representation - // if the scalar is just used for scaling points, this isn't necessary, because (s + kq)P = sP - ForeignField.assertLessThan(t, Fq.modulus); - } - - // split t into 250 high bits and 5 low bits - // => split t0 into [5, 83] => split t0 into [5, 64, 19] so we can efficiently range-check - let [tHi00, tHi01, ...tLo] = exists(7, () => { - let t = t0.toBigInt(); - return [ - bitSlice(t, 5, 64), - bitSlice(t, 69, 19), - bit(t, 0), - bit(t, 1), - bit(t, 2), - bit(t, 3), - bit(t, 4), - ]; - }); - let tLoBools = TupleN.map(tLo, (x) => x.assertBool()); - rangeCheck64(tHi00); - rangeCheck64(tHi01); - - // prove (tLo, tHi0) split - // since we know that t0 < 2^88 and tHi0 < 2^128, this even proves that t0Hi < 2^83 - // (the bound on tHi0 is necessary so that 32*tHi0 can't overflow) - let tHi0 = tHi00.add(tHi01.mul(1n << 64n)); - packBits(tLo) - .add(tHi0.mul(1n << 5n)) - .assertEquals(t0); - - // pack tHi - // this can't overflow the native field if e.g. s < 2^258: - // -) t <= s - 2^254 + q < 2^259 - // -) we proved tHi = (t >> 5) < 2^254, and all the parts are precisely range-checked - let tHi = tHi0 - .add(t1.mul(1n << (l - 5n))) - .add(t2.mul(1n << (2n * l - 5n))) - .seal(); - - return { low5: tLoBools, high250: tHi }; -} - -/** - * Internal helper to compute `(t + 2^255)*P`. - * `t` is expected to be split into 254 high bits (t >> 1) and a low bit (t & 1). - * - * The gadget proves that `tHi` is in [0, 2^254) but assumes that `tLo` consists of bits. - * - * Optionally, you can specify a different number of high bits by passing in `numHighBits`. - */ -function scaleShifted( - { x, y }: Point, - { lowBit: tLo, high254: tHi }: ShiftedScalar, - numHighBits = 254 -): Point { - // constant case - if (isConstant(x, y, tHi, tLo)) { - let sP = PallasAffine.scale( - PallasAffine.fromNonzero({ x: x.toBigInt(), y: y.toBigInt() }), - Fq.mod(tLo.toField().toBigInt() + 2n * tHi.toBigInt() + (1n << 255n)) - ); - return { x: createField(sP.x), y: createField(sP.y) }; - } - const Field = getField(); - const Point = provable({ x: Field, y: Field }); - let zero = createField(0n); - - /** - * Strategy: - * - use all 255 rounds of `scaleFastUnpack` for the high part - * - handle two disallowed tHi values separately: -2^254, -2^254 - 1 - * - don't handle disallowed tHi = -2^254 - 1/2 because it wouldn't normally be used, as it's > q/2 - */ - let equalsMinusShift = tHi.equals(Fq.modulus - (1n << 254n)); - let equalsMinusShiftMinus1 = tHi.equals(Fq.modulus - (1n << 254n) - 1n); - let isEdgeCase = equalsMinusShift.or(equalsMinusShiftMinus1); - let tHiSafe = Provable.if(isEdgeCase, zero, tHi); - - // R = (2*(t >> 1) + 1 + 2^255)P - // also returns a 255-bit representation of tHi - let [, RMl, [, ...tHiBitsMl]] = Snarky.group.scaleFastUnpack( - [0, x.value, y.value], - [0, tHiSafe.value], - 255 - ); - let P = { x, y }; - let R = { x: createField(RMl[1]), y: createField(RMl[2]) }; - - // prove that tHi has only `numHighBits` bits set - for (let i = numHighBits; i < 255; i++) { - createField(tHiBitsMl[i]).assertEquals(zero); - } - - // handle edge cases - // 2*(-2^254) + 1 + 2^255 = 1 - // 2*(-2^254 - 1) + 1 + 2^255 = -1 - // so the result is (x,+-y) - let edgeCaseY = y.mul(equalsMinusShift.toField().mul(2n).sub(1n)); // y*(2b - 1) = y or -y - let edgeCaseResult = { x, y: edgeCaseY }; - R = Provable.if(isEdgeCase, Point, edgeCaseResult, R); - - // R = tLo ? R : R - P = (t + 2^255)P - // we also handle a zero R-P result to make the 0 scalar work - let { result: RminusP, isInfinity } = add(R, negate(P)); - RminusP = Provable.if(isInfinity, Point, { x: zero, y: zero }, RminusP); - R = Provable.if(tLo, Point, R, RminusP); - - return R; -} - -/** - * 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 & 0x1f). - * - * The gadget proves that `tHi` is in [0, 2^250) but assumes that `tLo` consists of bits. - */ -function scaleShiftedSplit5( - { x, y }: Point, - { low5: tLo, high250: tHi }: ShiftedScalarSplit5 -): Point { - // 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 { x: createField(sP.x), y: createField(sP.y) }; - } - const Field = getField(); - const Point = provable({ x: Field, y: Field }); - const zero = createField(0n); - - // R = (2*(t >> 5) + 1 + 2^250)P - // also proves that tHi is in [0, 2^250) - let [, RMl] = Snarky.group.scaleFastUnpack( - [0, x.value, y.value], - [0, tHi.value], - 250 - ); - let P = { x, y }; - let R = { x: createField(RMl[1]), y: createField(RMl[2]) }; - let [t0, t1, t2, t3, t4] = tLo; - - // R = t4 ? R : R - P = ((t >> 4) + 2^250)P - R = Provable.if(t4, Point, R, addNonZero(R, negate(P))); - - // R = ((t >> 3) + 2^251)P - // R = ((t >> 2) + 2^252)P - // R = ((t >> 1) + 2^253)P - // note: t is in [0, q) so none of these can overflow and create a completeness issue: q/2 + 2^253 < q - for (let t of [t3, t2, t1]) { - R = addNonZero(R, R); - R = Provable.if(t, Point, addNonZero(R, P), R); - } - - // R = (t + 2^254)P - // in the final step, we allow a zero output to make it work for the 0 scalar - R = addNonZero(R, R); - let { result, isInfinity } = add(R, P); - result = Provable.if(isInfinity, Point, { x: zero, y: zero }, result); - R = Provable.if(t0, Point, result, R); - - return R; -} - /** * Wraps the `EC_add` gate to perform complete addition of two non-zero curve points. */ From ad7e7a372a69f3a3cbde57896fe2ef91baaf9500 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 16:34:57 +0200 Subject: [PATCH 32/45] dump vks --- tests/vk-regression/vk-regression.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 7c10c02462..8e353f8ea2 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -50,16 +50,16 @@ } }, "HelloWorld": { - "digest": "e04e3a9ffe27c83ecbbcb72ea2e775df96de8f6215db698139b5d5f4898a230", + "digest": "266926dd336350438f6b539b3de75371dfa2079428734f5b265f3209ed1b81f5", "methods": { "update": { - "rows": 492, - "digest": "0fa169ea122f0b26ef92f1112836828f" + "rows": 477, + "digest": "59847fee7ecafc2d2fe5508b7128be90" } }, "verificationKey": { - "data": "AABnIUtBjexnheGU45jDLZlzdEIAYFcRi+aW1GqRaoZWFXXDP+NCOlX280XhOT9Cbb9LPxS0no5odvWi86abqw8UZxrm8etZg82vugnGjikslSBGhtwipjyp0Qo5LfTmwzO6qmGnWieyABMDgCGqCmwJxW2g+adyu9hhmuse/WhnJbMQAiQEtmzoyrLb5qe7Lm/8aanBXocSGEpuD7eIL4IO2z42fOrH0VqcIrp8qLy460w1R0aAMaPZ02npDOxvrxTLtpfVAyIY06XCc4grEuoHcIf8lic76syVofMXaoIDE0mDadgIeIR1VrdeEeufn2VabqLDTAs8gWTlv2fCeeMp8I6db/g6R2avb6C2WoKszKThWN6K8SB184sqMuT3lhFWAIno6nX2SUuxQVWLMT2T6a3DAC5C1qYDN3WlsH2cFlk2VoGAmZ3cM+LDtcGgSWePM3bfiGr3lZWBaorws4QFIB40UOKCK4BWvbl7fcSRY1+3LR1e04nf0kxrfIYZ3wapKc6XGVKPJjpLvlBEk0AQPNXIQTdRWTddWJUPA5SiEP04iqgbpetZqbx82xQ06Dr4xtOP0vwcZwGIY+LGndM7AHjmDRq4xWQ+U5arpEs3T/btJYbD6a1ul9rAEETOgLQICwqgjK0qqbVrHV+9d8DNzRkVoWmDILhA6IoD4SQ08CIkCdYBNnekfSPubsOHugj7msm+wpsabOGPdol/QHJNDAlUk77WjZEpQmxHOiE66K7r77+16lRbgbrkbIXxAXsKVrMzwyMWVfWzHqZkN2FsAMBYWJO1x8lqY4XukKRCnjtelAq+gZ4ljp7YlQFnM3GLA3oGCvQyIqwePiCWoZpJA4SwswCa2uNPZ+7PMzZbMzddVkwj921337rtBzP9OYwXIe6aPdqI21kTf4KBc1Nu+071d484vfsxBEpIj2VWET0bk94MYH2w1ThMqEOjvdLH7Rjb2yq6S/pHxzA0+6u7PW8S6rE/B0hQj+15Yefwb0l3QTzgj2x0JDSZLXfM9v4er/yqFfGfQqUIbLe4D6WbU8JCs7WteAajzSO8LV/qVBzR6IJjRc7fBywKBsHdmNCaQ09RiShjfao1REfGXGxFCRSB3dphR1+4/fKCaDZiM7W1Ehtv7L91QEgycB7lkOo31xGBWvFCmOja1I2j18ANyznh7ppyfrE0KDk4f6tL+TyakQKlZim7WJBz45cwQ/Y+wzWQL4N92uh2awCSzau1Kn4/CBjw+Oq7HSz/k1oL0J5PGao68tAiQD0tOcEhUIsHD36inwHMxroxJVL8zLKdkAE8BM7WITJyXL9FWp5q/BAVTIML5L7BEZzQkLVIiVW66mRV1+jKE4Y1u3exah/mJR43GZoyn4SNlnjtlQG9S+VLerddhdyF3qnc3t3o8ik6x/0qD6g+IBuY1OIl7EG6UUp1avg2YGgPiOrfGnX6LBrtboOkA0iWI1TRushJ744l87XpZKLfHr90QzBQkgAPCcEAG08LLim/FcnS3kAjfAE0lxyjrSMY5s1+SbnnTlAMOacaPE09xwW9FDvwkV3FNaBTX7srLovC9VgavOPx5QN0gP+hy8/zdnwG0aZvLCDNHa2NOxe8q35gdA42Ru+QMdKLYIuw/xGXYjzoOdYr/WDYjNQzzDKeHwzx/vqyFLkNA/mMzaNMxjQTlfrFnuTzj8oyY7EuUdwif18wcTsqXAMUVhRtN690neD7VWL63HgR7q5p3BA5J6NltbjPXNJLP5dMTi71vUoBndyX3XlcObmIkZKa2YD927QsfATwyg4mfkcjMw1nboag8T9VcExC6cY5nrNcBvXmtLz3pUiTcSj/UgCgZWa0GOGBS+6P5PsrtGDsvLrmqA2ed33hv1UiHwDLSTXvh+zFNDPSPTnoT53wP7DE0FjilgeWv+F5o851CnFU50J/gtMwWrrmw5bjAKk+Dh39+0B0OfLxG5DFjnwmv3JkQzrnB+GFZ22PEHdZZ9PiP6C7/kMFaB6+V2gvrDJ6BTesrreFShEyHJKvtdPWeE9WWm8GmSY6C/PjGryIERm7Z2za+3ze7ZEUQx9RxrzYnNrGSqGJdtDNk0pHDG4khS3+AfieLdNKHdr19IfUDzCgCAvY5ukD0N7A9qxW6C7VPVvqWINu1zU27wupHjnu85MW+cIs5RPIrVS6Xry0Api3cCFuPK2pfEpmDy/K3AtLptp7NDJRcD6ym5K26rcdle9voOz4VLuCe98Z/rCV2W8fPfBGjPWMX2pjZL78oDFdbwKSBi1ipV79IKNCY6d3kSZpeyFlJZX+MBY5NXKLHygje3kDLDQ1eBKb4ORKOrEVw/Mxp7jGrAlG0Ac7y/s+gkC01hulcJ6CIkgIUZiS/TYfsH+F3oqmNXzue0jHLwQ=", - "hash": "27370250573863895519732939618815084579192113056549266298181666785275942621518" + "data": "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", + "hash": "12331292821859724266874290366886158319751913508867754308768249617842137697518" } }, "TokenContract": { @@ -124,8 +124,8 @@ "digest": "ddb709883792aa08b3bdfb69206a9f69" }, "scale": { - "rows": 145, - "digest": "831dd3a44b52fb75ef3dbbc34b4ed8c0" + "rows": 133, + "digest": "ada575a54f9114ac2e4e4b8a2c3ddfe0" }, "equals": { "rows": 37, @@ -288,7 +288,7 @@ } }, "diverse": { - "digest": "28aff9bf8061aa25a536586453c1b82bb004f2c64c13da207409d2daa7a9f036", + "digest": "294a11dd2a8befa0e11ed571042d27eff6fce3704acd4a0ddcb86ea3db5c81a7", "methods": { "ecdsa": { "rows": 28182, @@ -299,8 +299,8 @@ "digest": "c23e00e466878466ae8ab23bde562792" }, "pallas": { - "rows": 505, - "digest": "7c72616c97a255f802f555f4cf87ebc9" + "rows": 479, + "digest": "e3a662b20a3aa5fdc594ef3124852824" }, "poseidon": { "rows": 946, @@ -316,8 +316,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "9141527858419493676659965468935555446669173934340605986585721765602387935902" + "data": "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", + "hash": "21354814197389690100272056052485241665750903895751126842366851158875105797820" } } } \ No newline at end of file From 8d5d3387294f90265d93b4362468d8808f81bc71 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 18:14:42 +0200 Subject: [PATCH 33/45] renaming and test utils --- src/lib/provable/group.ts | 4 ++-- src/lib/provable/test/test-utils.ts | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/lib/provable/group.ts b/src/lib/provable/group.ts index e9aca7d4a6..7b154eeae0 100644 --- a/src/lib/provable/group.ts +++ b/src/lib/provable/group.ts @@ -10,7 +10,7 @@ import { import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { assert } from '../util/assert.js'; -import { add, scaleFieldDirect, scaleShifted } from './gadgets/native-curve.js'; +import { add, scaleField, scaleShifted } from './gadgets/native-curve.js'; export { Group }; @@ -177,7 +177,7 @@ class Group { * ``` */ scale(s: Scalar | Field | number | bigint) { - if (s instanceof Field) return new Group(scaleFieldDirect(this, s)); + if (s instanceof Field) return new Group(scaleField(this, s)); let scalar = Scalar.from(s); if (isConstant(this) && scalar.isConstant()) { diff --git a/src/lib/provable/test/test-utils.ts b/src/lib/provable/test/test-utils.ts index 0406cc0d2a..208fdccc68 100644 --- a/src/lib/provable/test/test-utils.ts +++ b/src/lib/provable/test/test-utils.ts @@ -1,15 +1,19 @@ import type { FiniteField } from '../../../bindings/crypto/finite-field.js'; -import { ProvableSpec, spec } from '../../testing/equivalent.js'; +import { ProvableSpec, map, spec } from '../../testing/equivalent.js'; import { Random } from '../../testing/random.js'; import { Field3 } from '../gadgets/gadgets.js'; import { assert } from '../gadgets/common.js'; import { Bytes } from '../wrapped-classes.js'; +import { CurveAffine } from '../../../bindings/crypto/elliptic-curve.js'; +import { simpleMapToCurve } from '../gadgets/elliptic-curve.js'; +import { provable } from '../types/struct.js'; export { foreignField, unreducedForeignField, uniformForeignField, bytes, + pointSpec, throwError, }; @@ -62,6 +66,26 @@ function bytes(length: number) { }); } +function pointSpec(field: ProvableSpec, Curve: CurveAffine) { + // point but with independently random components, which will never form a valid point + let pointShape = spec({ + rng: Random.record({ x: field.rng, y: field.rng }), + there({ x, y }) { + return { x: field.there(x), y: field.there(y) }; + }, + back({ x, y }) { + return { x: field.back(x), y: field.back(y), infinity: false }; + }, + provable: provable({ x: field.provable, y: field.provable }), + }); + + // valid random point + let point = map({ from: field, to: pointShape }, (x) => + simpleMapToCurve(x, Curve) + ); + return point; +} + // helper function throwError(message: string): T { From 494e627afacda5a953f585dac22ebc27d767c73a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 18:14:52 +0200 Subject: [PATCH 34/45] test utils --- src/lib/testing/equivalent.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index b5a8406cee..60fc75ecaa 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -370,6 +370,14 @@ function record }>( }; } +function map( + { from, to }: { from: ProvableSpec; to: ProvableSpec }, + there: (t: T1) => S1 +): ProvableSpec; +function map( + { from, to }: { from: FromSpec; to: Spec }, + there: (t: T1) => S1 +): Spec; function map( { from, to }: { from: FromSpec; to: Spec }, there: (t: T1) => S1 From a8fea239cea2766297ad02a7bdd83ffd960d93bc Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 18:19:35 +0200 Subject: [PATCH 35/45] minor --- src/lib/provable/scalar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index 8d788b4666..d44cdbe0a8 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -23,7 +23,7 @@ type ScalarConst = [0, bigint]; */ class Scalar implements ShiftedScalar { /** - * We represent a scalar s in shifted form `t = s - 2^255 mod q, + * We represent a scalar s in shifted form t = s - 2^255 mod q, * split into its low bit (t & 1) and high 254 bits (t >> 1). * The reason is that we can efficiently compute the scalar multiplication `(t + 2^255) * P = s * P`. */ From 35eb475a80c663b183ec5532ef8fd48c5fe07a83 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 20:11:00 +0200 Subject: [PATCH 36/45] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc3fc91975..c909515a9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Massively improve `Field.isEven()`, add `Field.isOdd()` - `PrivateKey.toPublicKey()` from 358 to 119 constraints thanks to `isOdd()` - Add `Gadgets.ForeignField.assertLessThanOrEqual()` and support two variables as input to `ForeignField.assertLessThan()` +- Native curve improvements https://github.com/o1-labs/o1js/pull/1530 + - Change the internal representation of `Scalar` from 255 Bools to 1 Bool and 1 Field (low bit and high 254 bits) + - Make `Group.scale()` support all scalars (previously did not support 0, 1 and -1) + - Make `Group.scale()` directly accept `Field` elements, and much more efficient than previous methods of scaling by Fields + - As a result, `Signature.verify()` and `Nullifier.verify()` use much fewer constraints + - Fix `Scalar.fromBits()` to not produce a shifted scalar; shifting is no longer exposed to users of `Scalar`. - Remove `this.sender` which unintuitively did not prove that its value was the actual sender of the transaction https://github.com/o1-labs/o1js/pull/1464 [@julio4](https://github.com/julio4) Replaced by more explicit APIs: - `this.sender.getUnconstrained()` which has the old behavior of `this.sender`, and returns an unconstrained value (which means that the prover can set it to any value they want) From 7bcc84b4c858de4178ac3e90d46c19c873fd8adf Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 20:39:02 +0200 Subject: [PATCH 37/45] remove shifting stuff from foreign field unit test --- .../provable/test/foreign-field.unit-test.ts | 90 +------------------ 1 file changed, 2 insertions(+), 88 deletions(-) diff --git a/src/lib/provable/test/foreign-field.unit-test.ts b/src/lib/provable/test/foreign-field.unit-test.ts index c7f228ddaf..fb8872ab13 100644 --- a/src/lib/provable/test/foreign-field.unit-test.ts +++ b/src/lib/provable/test/foreign-field.unit-test.ts @@ -1,23 +1,17 @@ -import { Field, Group } from '../wrapped.js'; -import { ForeignField, createForeignField } from '../foreign-field.js'; +import { Field } from '../wrapped.js'; +import { createForeignField } from '../foreign-field.js'; import { Fq } from '../../../bindings/crypto/finite-field.js'; -import { Pallas } from '../../../bindings/crypto/elliptic-curve.js'; import { expect } from 'expect'; import { bool, equivalentProvable as equivalent, - equivalent as equivalentNonProvable, first, spec, throwError, unit, } from '../../testing/equivalent.js'; import { test, Random } from '../../testing/property.js'; -import { Provable } from '../provable.js'; -import { Circuit, circuitMain } from '../../proof-system/circuit.js'; -import { Scalar } from '../scalar.js'; import { l } from '../gadgets/range-check.js'; -import { assert } from '../gadgets/common.js'; import { ProvablePure } from '../types/provable-intf.js'; // toy example - F_17 @@ -108,83 +102,3 @@ equivalent({ from: [f], to: f })( return ForeignScalar.fromBits(bits); } ); - -// scalar shift in foreign field arithmetic vs in the exponent - -let scalarShift = Fq.mod(1n + 2n ** 255n); -let oneHalf = Fq.inverse(2n)!; - -function unshift(s: ForeignField) { - return s.sub(scalarShift).assertAlmostReduced().mul(oneHalf); -} -function scaleShifted(point: Group, shiftedScalar: Scalar) { - let oneHalfGroup = point.scale(oneHalf); - let shiftGroup = oneHalfGroup.scale(scalarShift); - return oneHalfGroup.scale(shiftedScalar).sub(shiftGroup); -} - -let scalarBigint = Fq.random(); -let pointBigint = Pallas.toAffine(Pallas.scale(Pallas.one, scalarBigint)); - -// perform a "scalar unshift" in foreign field arithmetic, -// then convert to scalar from bits (which shifts it back) and scale a point by the scalar -function main0() { - let ffScalar = Provable.witness( - ForeignScalar.provable, - () => new ForeignScalar(scalarBigint) - ); - let bitsUnshifted = unshift(ffScalar).toBits(); - let scalar = Scalar.fromBits(bitsUnshifted); - - let generator = Provable.witness(Group, () => Group.generator); - let point = generator.scale(scalar); - point.assertEquals(Group(pointBigint)); -} - -// go directly from foreign scalar to scalar and perform a shifted scale -// = same end result as main0 -function main1() { - let ffScalar = Provable.witness( - ForeignScalar.provable, - () => new ForeignScalar(scalarBigint) - ); - let bits = ffScalar.toBits(); - let scalarShifted = Scalar.fromBits(bits); - - let generator = Provable.witness(Group, () => Group.generator); - let point = scaleShifted(generator, scalarShifted); - point.assertEquals(Group(pointBigint)); -} - -// check provable and non-provable versions are correct -main0(); -main1(); -await Provable.runAndCheck(main0); -await Provable.runAndCheck(main1); - -// using foreign field arithmetic should result in much fewer constraints -let { rows: rows0 } = await Provable.constraintSystem(main0); -let { rows: rows1 } = await Provable.constraintSystem(main1); -expect(rows0 + 100).toBeLessThan(rows1); - -// test with proving - -class Main extends Circuit { - @circuitMain - static main() { - main0(); - } -} - -let kp = await Main.generateKeypair(); - -let cs = kp.constraintSystem(); -assert( - cs.length === 1 << 13, - `should have ${cs.length} = 2^13 rows, the smallest supported number` -); - -let proof = await Main.prove([], [], kp); - -let ok = await Main.verify([], kp.verificationKey(), proof); -assert(ok, 'proof should verify'); From e21fa33b577b1cbdade7d864a64ae01effdd26a3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 20:54:22 +0200 Subject: [PATCH 38/45] comment --- src/lib/provable/crypto/nullifier.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/provable/crypto/nullifier.ts b/src/lib/provable/crypto/nullifier.ts index 589c8ac441..de7fb11428 100644 --- a/src/lib/provable/crypto/nullifier.ts +++ b/src/lib/provable/crypto/nullifier.ts @@ -67,7 +67,6 @@ class Nullifier extends Struct({ let h_m_pk = Group.fromFields([x, x0]); - // shifted scalar see https://github.com/o1-labs/o1js/blob/5333817a62890c43ac1b9cb345748984df271b62/src/lib/signature.ts#L220 // pk^c let pk_c = this.publicKey.scale(c); From 9c799ed508297bf1918dd40c68f99d9c1bff0961 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 21:15:26 +0200 Subject: [PATCH 39/45] add nullifier to vk-regression --- src/examples/nullifier.ts | 1 - tests/vk-regression/plain-constraint-system.ts | 13 ++++++++++++- tests/vk-regression/vk-regression.json | 13 +++++++++++++ tests/vk-regression/vk-regression.ts | 2 ++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/examples/nullifier.ts b/src/examples/nullifier.ts index 9693583dcf..52dec59094 100644 --- a/src/examples/nullifier.ts +++ b/src/examples/nullifier.ts @@ -7,7 +7,6 @@ import { State, method, MerkleMap, - Circuit, MerkleMapWitness, Mina, AccountUpdate, diff --git a/tests/vk-regression/plain-constraint-system.ts b/tests/vk-regression/plain-constraint-system.ts index 17414694e6..740abf170b 100644 --- a/tests/vk-regression/plain-constraint-system.ts +++ b/tests/vk-regression/plain-constraint-system.ts @@ -8,9 +8,10 @@ import { Bytes, Bool, UInt64, + Nullifier, } from 'o1js'; -export { GroupCS, BitwiseCS, HashCS, BasicCS }; +export { GroupCS, BitwiseCS, HashCS, BasicCS, CryptoCS }; const GroupCS = constraintSystem('Group Primitive', { add() { @@ -173,6 +174,16 @@ const BasicCS = constraintSystem('Basic', { }, }); +const CryptoCS = constraintSystem('Crypto', { + nullifier() { + let nullifier = Provable.witness(Nullifier, (): Nullifier => { + throw Error('not implemented'); + }); + let x = Provable.witness(Field, () => Field(0)); + nullifier.verify([x, x, x]); + }, +}); + // mock ZkProgram API for testing function constraintSystem( diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 23b985cba3..602957f67f 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -248,6 +248,19 @@ "hash": "" } }, + "Crypto": { + "digest": "Crypto", + "methods": { + "nullifier": { + "rows": 740, + "digest": "46c4ab1e0dcc243284c1dffa8285a64b" + } + }, + "verificationKey": { + "data": "", + "hash": "" + } + }, "ecdsa-only": { "digest": "39205ab5c3c80677719cb409d9b798a8f07dd71fde09cef6d59719bd37ecc739", "methods": { diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index 9af98395a9..fe2c921407 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -13,6 +13,7 @@ import { BitwiseCS, HashCS, BasicCS, + CryptoCS, } from './plain-constraint-system.js'; import { diverse } from './diverse-zk-program.js'; @@ -56,6 +57,7 @@ const ConstraintSystems: MinimumConstraintSystem[] = [ BitwiseCS, HashCS, BasicCS, + CryptoCS, ecdsa, keccakAndEcdsa, SHA256Program, From 9e9ca06b687631073c8d00befd81ef8e86147253 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 Apr 2024 21:15:44 +0200 Subject: [PATCH 40/45] mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index 03e10fc739..48443f5506 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 03e10fc739d74f375cedd7388a84767bee4c7eb0 +Subproject commit 48443f5506933f0ef71d61e1ab18e48a814f3d55 From 224b95c882143376c0d5dcd7e3870ac729e3b6ce Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 10 Apr 2024 16:06:53 +0200 Subject: [PATCH 41/45] fix changelog --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed939cfe8a..fdba7533af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Breaking changes +- Native curve improvements https://github.com/o1-labs/o1js/pull/1530 + - Change the internal representation of `Scalar` from 255 Bools to 1 Bool and 1 Field (low bit and high 254 bits) + - Make `Group.scale()` support all scalars (previously did not support 0, 1 and -1) + - Make `Group.scale()` directly accept `Field` elements, and much more efficient than previous methods of scaling by Fields + - As a result, `Signature.verify()` and `Nullifier.verify()` use much fewer constraints + - Fix `Scalar.fromBits()` to not produce a shifted scalar; shifting is no longer exposed to users of `Scalar`. - Add assertion to the foreign EC addition gadget that prevents degenerate cases https://github.com/o1-labs/o1js/pull/1545 - Fixes soundness of ECDSA; slightly increases its constraints from ~28k to 29k - Breaks circuits that used EC addition, like ECDSA @@ -44,12 +50,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Massively improve `Field.isEven()`, add `Field.isOdd()` - `PrivateKey.toPublicKey()` from 358 to 119 constraints thanks to `isOdd()` - Add `Gadgets.ForeignField.assertLessThanOrEqual()` and support two variables as input to `ForeignField.assertLessThan()` -- Native curve improvements https://github.com/o1-labs/o1js/pull/1530 - - Change the internal representation of `Scalar` from 255 Bools to 1 Bool and 1 Field (low bit and high 254 bits) - - Make `Group.scale()` support all scalars (previously did not support 0, 1 and -1) - - Make `Group.scale()` directly accept `Field` elements, and much more efficient than previous methods of scaling by Fields - - As a result, `Signature.verify()` and `Nullifier.verify()` use much fewer constraints - - Fix `Scalar.fromBits()` to not produce a shifted scalar; shifting is no longer exposed to users of `Scalar`. - Remove `this.sender` which unintuitively did not prove that its value was the actual sender of the transaction https://github.com/o1-labs/o1js/pull/1464 [@julio4](https://github.com/julio4) Replaced by more explicit APIs: - `this.sender.getUnconstrained()` which has the old behavior of `this.sender`, and returns an unconstrained value (which means that the prover can set it to any value they want) From dab6eb3b9529e91a9fde14563a0d3f6726f86772 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 10 Apr 2024 16:13:52 +0200 Subject: [PATCH 42/45] dump vks --- tests/vk-regression/vk-regression.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index d581ec56de..9ebf913a38 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -301,7 +301,7 @@ } }, "diverse": { - "digest": "294a11dd2a8befa0e11ed571042d27eff6fce3704acd4a0ddcb86ea3db5c81a7", + "digest": "e669969ce93c0d3dc95b92b9dff968bb80849b9492f39ed76de22d58bda62c6", "methods": { "ecdsa": { "rows": 29105, @@ -329,8 +329,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "12902967385210953378329225110499328575680034617396023005849324714429861499125" + "data": "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", + "hash": "15177586404047215407402385013564067740048233298246813050936904763238748848160" } } -} +} \ No newline at end of file From 523330a96cb5c46c882436f19a17d7c72d18ffcf Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 16 Apr 2024 08:39:20 +0200 Subject: [PATCH 43/45] fixes and tweaks to scaling gadgets --- src/lib/provable/gadgets/comparison.ts | 15 ++++++---- src/lib/provable/gadgets/native-curve.ts | 36 +++++++++++------------- src/lib/provable/provable.ts | 3 ++ 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/lib/provable/gadgets/comparison.ts b/src/lib/provable/gadgets/comparison.ts index 2c5e083cbf..e784f54caf 100644 --- a/src/lib/provable/gadgets/comparison.ts +++ b/src/lib/provable/gadgets/comparison.ts @@ -206,7 +206,7 @@ function isOddAndHigh(x: Field) { // witness a bit b such that x = b + 2z for some z <= (p-1)/2 // this is always possible, and unique _except_ in the edge case where x = 0 = 0 + 2*0 = 1 + 2*(p-1)/2 - // so we can compute isOdd = b AND (x != 0) + // so we must assert that x = 0 implies b = 0 let [b, z] = exists(2, () => { let x0 = x.toBigInt(); return [x0 & 1n, x0 >> 1n]; @@ -215,11 +215,16 @@ function isOddAndHigh(x: Field) { z.assertLessThan((Fp.modulus + 1n) / 2n); // x == b + 2z - b.add(z.mul(2)).assertEquals(x); + b.add(z.mul(2n)).assertEquals(x); - // avoid overflow case when x = 0 - let isNonZero = x.equals(0).not(); - return { isOdd: isOdd.and(isNonZero), high: z }; + // prevent overflow case when x = 0 + // we witness x' such that b == x * x', which makes it impossible to have x = 0 and b = 1 + let x_ = existsOne(() => + b.toBigInt() === 0n ? 0n : Fp.inverse(x.toBigInt()) ?? 0n + ); + x.mul(x_).assertEquals(b); + + return { isOdd, high: z }; } /** diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index 611055cd19..a0c91dc208 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -54,21 +54,19 @@ function scaleField(P: Point, s: Field): Point { */ // compute t = s + (-2^255 mod q) in (1, 254) arithmetic - let { isOdd: sLoBool, high: sHi } = isOddAndHigh(s); - let sLo = sLoBool.toField(); + let { isOdd: sLo, high: sHi } = isOddAndHigh(s); let shift = Fq.mod(-(1n << 255n)); - let shiftLo = shift & 1n; + assert((shift & 1n) === 0n); // shift happens to be even, so we don't need to worry about a carry let shiftHi = shift >> 1n; - let carry = sLo.mul(shiftLo).seal(); // = either 0 or lowBit - let tLo = sLo.add(shiftLo).sub(carry).assertBool(); - let tHi = sHi.add(shiftHi).add(carry).seal(); + let tLo = sLo; + let tHi = sHi.add(shiftHi).seal(); // tHi does not overflow: - // tHi = sHi + shiftHi + carry < p/2 + (p/2 - 1) + 1 = p + // tHi = sHi + shiftHi < p/2 + p/2 = p // sHi < p/2 is guaranteed by isOddAndHigh - assert(shiftHi < Fp.modulus / 2n - 1n); + assert(shiftHi < Fp.modulus / 2n); // the 4 values for s not supported by `scaleFastUnpack` are q-2, q-1, 0, 1 // since s came from a `Field`, we can exclude q-2, q-1 @@ -95,7 +93,7 @@ function scaleField(P: Point, s: Field): Point { // now handle the two edge cases s=0 and s=1 let zero = createField(0n); let zeroPoint = { x: zero, y: zero }; - let edgeCaseResult = Provable.if(sLoBool, Point, P, zeroPoint); + let edgeCaseResult = Provable.if(sLo, Point, P, zeroPoint); return Provable.if(isEdgeCase, Point, edgeCaseResult, R); } @@ -103,7 +101,7 @@ function scaleField(P: Point, s: Field): Point { * Internal helper to compute `(t + 2^255)*P`. * `t` is expected to be split into 254 high bits (t >> 1) and a low bit (t & 1). * - * The gadget proves that `tHi` is in [0, 2^254) but assumes that `tLo` consists of bits. + * The gadget proves that `tHi` is in [0, 2^254) but assumes that `tLo` is a single bit. * * Optionally, you can specify a different number of high bits by passing in `numHighBits`. */ @@ -183,21 +181,19 @@ function fieldToShiftedScalar(s: Field): ShiftedScalar { } // compute t = s + (-2^255 mod q) in (1, 254) arithmetic - let { isOdd: sLoBool, high: sHi } = isOddAndHigh(s); - let sLo = sLoBool.toField(); + let { isOdd: sLo, high: sHi } = isOddAndHigh(s); let shift = Fq.mod(-(1n << 255n)); - let shiftLo = shift & 1n; + assert((shift & 1n) === 0n); // shift happens to be even, so we don't need to worry about a carry let shiftHi = shift >> 1n; - let carry = sLo.mul(shiftLo).seal(); // = either 0 or lowBit - let tLo = sLo.add(shiftLo).sub(carry).assertBool(); - let tHi = sHi.add(shiftHi).add(carry).seal(); + let tLo = sLo; + let tHi = sHi.add(shiftHi).seal(); // tHi does not overflow: - // tHi = sHi + shiftHi + carry < p/2 + (p/2 - 1) + 1 = p + // tHi = sHi + shiftHi < p/2 + p/2 = p // sHi < p/2 is guaranteed by isOddAndHigh - assert(shiftHi < Fp.modulus / 2n - 1n); + assert(shiftHi < Fp.modulus / 2n); return { lowBit: tLo, high254: tHi }; } @@ -236,7 +232,7 @@ function field3ToShiftedScalar(s: Field3): ShiftedScalar { rangeCheck64(tHi01); // prove (tLo, tHi0) split - // since we know that t0 < 2^88 and tHi0 < 2^128, this even proves that t0Hi < 2^87 + // since we know that t0 < 2^88 and tHi0 < 2^128, this even proves that tHi0 < 2^87 // (the bound on tHi0 is necessary so that 2*tHi0 can't overflow) let tHi0 = tHi00.add(tHi01.mul(1n << 64n)); tLo.add(tHi0.mul(2n)).assertEquals(t0); @@ -244,7 +240,7 @@ function field3ToShiftedScalar(s: Field3): ShiftedScalar { // pack tHi // this can't overflow the native field because: // -) we showed t < q - // -) the three combined limbs here represent the bigint tHi = (t >> 1) < q/2 < p + // -) the three combined limbs here represent the bigint (t >> 1) < q/2 < p let tHi = tHi0 .add(t1.mul(1n << (l - 1n))) .add(t2.mul(1n << (2n * l - 1n))) diff --git a/src/lib/provable/provable.ts b/src/lib/provable/provable.ts index e386bc17e1..6db7e9c47d 100644 --- a/src/lib/provable/provable.ts +++ b/src/lib/provable/provable.ts @@ -288,6 +288,9 @@ function if_(condition: Bool, typeOrX: any, xOrY: any, yOrUndefined?: any) { } function ifField(b: Field, x: Field, y: Field) { + // TODO: this is suboptimal if one of x, y is constant + // it uses 2-3 generic gates in that case, where 1 would be enough + // b*(x - y) + y // NOTE: the R1CS constraint used by Field.if_ in snarky-ml // leads to a different but equivalent layout (same # constraints) From 45837fc43affe6ec3569273ce3a93946d5de2ee5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 16 Apr 2024 08:48:42 +0200 Subject: [PATCH 44/45] remove redundant constraint --- src/lib/provable/gadgets/native-curve.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/provable/gadgets/native-curve.ts b/src/lib/provable/gadgets/native-curve.ts index a0c91dc208..21a9531747 100644 --- a/src/lib/provable/gadgets/native-curve.ts +++ b/src/lib/provable/gadgets/native-curve.ts @@ -9,6 +9,7 @@ import { assert, bit, bitSlice, isConstant } from './common.js'; import { l, rangeCheck64 } from './range-check.js'; import { createBool, + createBoolUnsafe, createField, getField, } from '../core/field-constructor.js'; @@ -276,7 +277,6 @@ function add(g: Point, h: Point): { result: Point; isInfinity: Bool } { }); let [same_x, inf, inf_z, x21_inv, s, x3, y3] = witnesses; - let isInfinity = inf.assertBool(); Snarky.gates.ecAdd( MlPair(g.x.seal().value, g.y.seal().value), @@ -289,6 +289,9 @@ function add(g: Point, h: Point): { result: Point; isInfinity: Bool } { x21_inv.value ); + // the ecAdd gate constrains `inf` to be boolean + let isInfinity = createBoolUnsafe(inf); + return { result: { x: x3, y: y3 }, isInfinity }; } From 98226546c893c57fadd1d2b485f86d3b664cd16e Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 16 Apr 2024 09:37:33 +0200 Subject: [PATCH 45/45] dump vks --- tests/vk-regression/vk-regression.json | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 9ebf913a38..bca6e7274d 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -50,16 +50,16 @@ } }, "HelloWorld": { - "digest": "177c55b9fae6f26c436e3d0bff9db874e6ca7f07c2e1a27528b5b904fc46d240", + "digest": "309ebdd97930d6c753207d62c84147fe540f62d1623a1251f1d0b4f5f8bd400d", "methods": { "update": { - "rows": 477, - "digest": "629f45aa0a823273d0b3b34ac82cd33d" + "rows": 475, + "digest": "f1ace9d626cb47fb5612579a1a608fc5" } }, "verificationKey": { - "data": "AADhxKnCltp91j7lM6RAu89sefjgjm48WPsvw3D5wAJbEiIqMl4W9F14fHz2jMZFsZu5xXDmJyd1HCpIh/5tR2Y2Zxrm8etZg82vugnGjikslSBGhtwipjyp0Qo5LfTmwzO6qmGnWieyABMDgCGqCmwJxW2g+adyu9hhmuse/WhnJSe5NhoztPWGnxmDEmL0sATa/AmOl/TZwB9kV0MPU8I6nwZkkoHL2ZZz6FGbchI0HeCEViyzV+f9PMIPhDiQlyfLtpfVAyIY06XCc4grEuoHcIf8lic76syVofMXaoIDE0mDadgIeIR1VrdeEeufn2VabqLDTAs8gWTlv2fCeeMp8I6db/g6R2avb6C2WoKszKThWN6K8SB184sqMuT3lhFWAIno6nX2SUuxQVWLMT2T6a3DAC5C1qYDN3WlsH2cFlk2VoGAmZ3cM+LDtcGgSWePM3bfiGr3lZWBaorws4QFIB40UOKCK4BWvbl7fcSRY1+3LR1e04nf0kxrfIYZ3wapKc6XGVKPJjpLvlBEk0AQPNXIQTdRWTddWJUPA5SiEP04iqgbpetZqbx82xQ06Dr4xtOP0vwcZwGIY+LGndM7ALooYeuqDzrwMzD8z9LbIXu6G9uziT36kZfmzJ3vkXUh5FCahlPTyKnbY4FGdhY4Y4vrmyyi9YIFWo0PY3yZSAkkCdYBNnekfSPubsOHugj7msm+wpsabOGPdol/QHJNDAlUk77WjZEpQmxHOiE66K7r77+16lRbgbrkbIXxAXsKVrMzwyMWVfWzHqZkN2FsAMBYWJO1x8lqY4XukKRCnjtelAq+gZ4ljp7YlQFnM3GLA3oGCvQyIqwePiCWoZpJA4SwswCa2uNPZ+7PMzZbMzddVkwj921337rtBzP9OYwXIe6aPdqI21kTf4KBc1Nu+071d484vfsxBEpIj2VWET0bk94MYH2w1ThMqEOjvdLH7Rjb2yq6S/pHxzA0+6u7PW8S6rE/B0hQj+15Yefwb0l3QTzgj2x0JDSZLXfM9v4eMHc4OtNINsHkdfFaAQmAhuPdSisdXvEOlQsW6XO6uj81JY9Ha9drCCPNFDWKTl8YepztSNO7WtXWjLiCICpKPhSB3dphR1+4/fKCaDZiM7W1Ehtv7L91QEgycB7lkOo31xGBWvFCmOja1I2j18ANyznh7ppyfrE0KDk4f6tL+TyakQKlZim7WJBz45cwQ/Y+wzWQL4N92uh2awCSzau1Kn4/CBjw+Oq7HSz/k1oL0J5PGao68tAiQD0tOcEhUIsHD36inwHMxroxJVL8zLKdkAE8BM7WITJyXL9FWp5q/BAVTIML5L7BEZzQkLVIiVW66mRV1+jKE4Y1u3exah/mJR43GZoyn4SNlnjtlQG9S+VLerddhdyF3qnc3t3o8ik6x/0qD6g+IBuY1OIl7EG6UUp1avg2YGgPiOrfGnX6LBrtboOkA0iWI1TRushJ744l87XpZKLfHr90QzBQkgAPCcEAG08LLim/FcnS3kAjfAE0lxyjrSMY5s1+SbnnTlAMOacaPE09xwW9FDvwkV3FNaBTX7srLovC9VgavOPx5QN0gP+hy8/zdnwG0aZvLCDNHa2NOxe8q35gdA42Ru+QMdKLYIuw/xGXYjzoOdYr/WDYjNQzzDKeHwzx/vqyFLkNA/mMzaNMxjQTlfrFnuTzj8oyY7EuUdwif18wcTsqXAMUVhRtN690neD7VWL63HgR7q5p3BA5J6NltbjPXNJLP5dMTi71vUoBndyX3XlcObmIkZKa2YD927QsfATwyg4mfkcjMw1nboag8T9VcExC6cY5nrNcBvXmtLz3pUiTcSj/UgCgZWa0GOGBS+6P5PsrtGDsvLrmqA2ed33hv1UiHwDLSTXvh+zFNDPSPTnoT53wP7DE0FjilgeWv+F5o851CnFU50J/gtMwWrrmw5bjAKk+Dh39+0B0OfLxG5DFjnwmv3JkQzrnB+GFZ22PEHdZZ9PiP6C7/kMFaB6+V2gvrDJ6BTesrreFShEyHJKvtdPWeE9WWm8GmSY6C/PjGryIERm7Z2za+3ze7ZEUQx9RxrzYnNrGSqGJdtDNk0pHDG4khS3+AfieLdNKHdr19IfUDzCgCAvY5ukD0N7A9qxW6C7VPVvqWINu1zU27wupHjnu85MW+cIs5RPIrVS6Xry0Api3cCFuPK2pfEpmDy/K3AtLptp7NDJRcD6ym5K26rcdle9voOz4VLuCe98Z/rCV2W8fPfBGjPWMX2pjZL78oDFdbwKSBi1ipV79IKNCY6d3kSZpeyFlJZX+MBY5NXKLHygje3kDLDQ1eBKb4ORKOrEVw/Mxp7jGrAlG0Ac7y/s+gkC01hulcJ6CIkgIUZiS/TYfsH+F3oqmNXzue0jHLwQ=", - "hash": "25346822498442768900871022347324970359427559985314236992504323011810147410972" + "data": "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", + "hash": "4671417744577823084023376770685664948710807365406389658915321449520576005042" } }, "TokenContract": { @@ -116,16 +116,16 @@ "digest": "Group Primitive", "methods": { "add": { - "rows": 30, - "digest": "8179f9497cc9b6624912033324c27b6d" + "rows": 29, + "digest": "c9da933b8ee30b9467e4b0abacb503bf" }, "sub": { "rows": 30, - "digest": "ddb709883792aa08b3bdfb69206a9f69" + "digest": "ad4216530ea5b80273e0115825a5dce1" }, "scale": { - "rows": 133, - "digest": "ada575a54f9114ac2e4e4b8a2c3ddfe0" + "rows": 132, + "digest": "752c83aacb96abdf457a54018fcfda1a" }, "equals": { "rows": 37, @@ -252,8 +252,8 @@ "digest": "Crypto", "methods": { "nullifier": { - "rows": 740, - "digest": "46c4ab1e0dcc243284c1dffa8285a64b" + "rows": 730, + "digest": "aa8ec2538a8d3133af3f2dd7eb682738" } }, "verificationKey": { @@ -301,7 +301,7 @@ } }, "diverse": { - "digest": "e669969ce93c0d3dc95b92b9dff968bb80849b9492f39ed76de22d58bda62c6", + "digest": "36308476aa4b0f23cef29b266a581832b549576aa604e4275670781feebd74d3", "methods": { "ecdsa": { "rows": 29105, @@ -312,8 +312,8 @@ "digest": "c23e00e466878466ae8ab23bde562792" }, "pallas": { - "rows": 479, - "digest": "e3a662b20a3aa5fdc594ef3124852824" + "rows": 469, + "digest": "6ee38bce85ca4c6a852fdbe25bf096a9" }, "poseidon": { "rows": 946, @@ -329,8 +329,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "15177586404047215407402385013564067740048233298246813050936904763238748848160" + "data": "AQExT2L4AWXqY4ibNvWbfrA+7V/BnY/QWtvoN8gCy/JoK2hoLkbLhY0cXy/P3pMEBR6AEWp46cxsmmIv50za/70dltTzUPlyKitiM8XhDU4hqin8dql4vS37u8JNYatwiSVu5MbTYpJX2hvn6e8XADxGucO6BW4mLGaLS4RHD1GjO28HNv5fyab1Ndl8JofA+8AFbpG1Jlxm6pgfamXQHUkkf+LISFpx6biNeSIiss3EOiyebcqIPwd+QPz49DwhvA4eC8RvtGfQ/5JQlvIYwQKQvJD2sYuRbaRavoOfywZ0OTT9xting/5hzbGwFoHPBRq4vZTGmw/9NcxbOJnESdEQ4jyG5U9xcUoS/ZihRPrsNCPxQDjk7nLo3fO4EIPs1QG9uz5YhkkHWtDGkXdkaC4mIVqUoHNZTHiUPEH2Cy7tB8shddphO2gDhsTOYKDml+ASLqpDapu9vSAEcyNAZW4TNv53WS2Kx8tGGwsUXss0tnjtkc2kGXL+k/KVdfgFHy7gM2RKnP+xm9nv8xEIG6IzEdbsbrATqHz8p3o/9a1nIRkl2z63JrDxpxsRvLHlQBNvnPHiANflIWomFuW6VJ8VAJll8jToy7Gx3zxfRRORnCbUcZXBqQAzq6+db2/hC+0hwibSrNCgPVWoIrDj3bSv/fH9w0VoCVefEqie5tub/gMVCTFLnG+DwuHBmVeY9bK5pVmakFC74vbj0uHCYd8sHwcRlhM9lwhUo5W0ATxUiQ78/RbKHfQPZaR22VsHxG8sKPzWwrPNqx5fNeszqennJ0ddaXhYxeJOnl7WZLOeZhImZDYJEteIkq/ApsKCl/AweQ6YpzfGQfcqDz/371mfOaJnHQfkJ1xNZk5AWvZPR5ac4YfHyrHcLAq9YBXDkLsQ3GIGLeVGU+YwBsUcKfzQ2REmsZGeQNU7+ynJkePgwgm9gOSUL3TuW4M5f0cpSuFFM+OiR2wMyH7/PqZf0stkJDuG2OTF9Ml1njepMB9u2cjM830ZcRja8wnITg1ZUdMyuViF2F9j7hNqo3JHwnb2vFpopA0bbwhUVBaWWpgMigqGHUVBdblQfp2Ty6pGAB3HX5V1m3SfYnJCQb02HTvENpjyG6GQp3NsLh0DwuIWdzZxNQJmI1lvUjqRIR+aEDAekNz0ZiTwlxNVOkQuTgpMan5Tx4c37Cy8cSWy83fJlhHb2zE/HXKChdo+fIljdH+QsMUA2UhUAFf3vOyqeeQoF4NwuVJZY3nY5ewiK/yOefv6mR0WVk4Wzu00vFzThokNAOGxnLXLv2jZwWnfFuh3hKLIZ/pLyOKMGsu0ggAWFilVsW8wBcUefvdx/jZThlwm5EbJLoLuGOQ6albp6l1fP16pT3BPS6XeeZmv5lEPtb1M9TH3jzwY+AJGcAsx3eQo6/ffLrE3LKW+04nx+fJOsF4vhij6xPcaetjoKIs+mgsLmaDTI0KInfGdlZJrAEfQitUkMQDEPbKqvs/3s46jKbbgmb/+vr1paXwUX8gjIHpI5AtDeQT/VjM5Sc7JzXQJWqBCHtTyllIo5/mqEbit19RHtgMU77IeB0cgr6Rhzz9oKFEvzsI86dMnlmd12WQeJWtEs7OuxXdRvUe7H/tFC0DA2bah4diaXKtEiAxYsn8tMVVNJyWQmpiro6joJdEJoX6ddthN9PBzoFqzxHhN5F7gXELvBhvCnYtSxifcTDizGzLDw+M9VjL7Timz9HgTWpraWZVvTKDnfU2PHD6MO/AglJ3D4G7/ecSg563bm4I8Sx9JOcDvGQv4znXQLfQDORa5S2nkftBYO/f+26UvwkPu2YVIzroJYLVLDISYRRWsE6XRdo/Zqu5YWlHKhDm0+GvjCUdg3Z6mUlReHckBDwA8D1JNTIovdwo7iK2R+hjqGEsH0UwypDT5EBcco3EPMaJYlAWUU6n3eFkdSn7YHF5lTvT6JaD2SJVRlmauf60o6xPH1+BaAcwsMtWZe3OYi6zdn+MYPgI2RPHI5xog1xcFPfJOzu4j+88oyO9YQPIGPKqqboXVWXdhCzECkAyZIuc6h/nBrc+C+qtC2WX+Ex6q5dUIaGk5OCGIS+xE2boaMtPpUhjrRUWQ8eiFZd4fCPcRodjVejJtVHzx1cS3nh8brMJGX+BVKdnoHYkypoPuo2Cw+UZaQbqniRTedIptGJThxgkBm8MuGODHsz0N7DCC02fUK+F1BPTNdDVwhpQ+wWtS1aiyE7KTGkbm7WxvdmQqfHV6lQ2o0XMvwQHihTtmH/m13hSG+UoAbZzhNYtQ+OnRi+c7C29KfVY6JM24BonfyRlpVfcTiOG1KfYs3ZQ+ZjG2bRsdLVI4f0qu8Nk4dn5E41NzXD+Fd1Kuv35E5qN3D7YZ0T3u+1J0y7pGbh0=", + "hash": "12697506505683930771562355619882993515591767596875424801784257474849667200879" } } } \ No newline at end of file