diff --git a/CHANGELOG.md b/CHANGELOG.md index 80bfc20398..2786bad10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Remove `CircuitValue`, `prop`, `arrayProp` and `matrixProp` https://github.com/o1-labs/o1js/pull/1507 - Remove `Mina.accountCreationFee()`, `Mina.BerkeleyQANet`, all APIs which accept private keys for feepayers, `Token`, `AccountUpdate.tokenSymbol`, `SmartContract.{token, setValue, setPermissions}`, "assert" methods for preconditions, `MerkleTee.calculateRootSlow()`, `Scalar.fromBigInt()`, `UInt64.lt()` and friends, deprecated static methods on `Group`, utility methods on `Circuit` like `Circuit.if()`, `Field.isZero()`, `isReady` and `shutdown()` https://github.com/o1-labs/o1js/pull/1515 - Remove `privateKey` from the accepted arguments of `SmartContract.deploy()` https://github.com/o1-labs/o1js/pull/1515 +- **Efficient comparisons**. Support arbitrary bit lengths for `Field` comparisons and massively reduce their constraints https://github.com/o1-labs/o1js/pull/1523 + - `Field.assertLessThan()` goes from 510 to 24 constraints, `Field.lessThan()` from 509 to 38 + - Moderately improve other comparisons: `UInt64.assertLessThan()` from 27 to 14, `UInt64.lessThan()` from 27 to 15, `UInt32` similar. + - 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()` - 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) @@ -53,6 +59,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Internal benchmarking tooling to keep track of performance https://github.com/o1-labs/o1js/pull/1481 - Add `toInput` method for `Group` instance https://github.com/o1-labs/o1js/pull/1483 +### Changed + +- `field.assertBool()` now also returns the `Field` as a `Bool` for ergonomics https://github.com/o1-labs/o1js/pull/1523 + ## [0.17.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...74948acac) - 2024-03-06 ### Breaking changes diff --git a/src/bindings b/src/bindings index 046fc002db..3c68a0dad4 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 046fc002db7c35f14c8f9b819da81485f072fb52 +Subproject commit 3c68a0dad42df20e5ef89ed408c83c41fdfcfd24 diff --git a/src/build/build-example.js b/src/build/build-example.js index 966bf60881..ffde281aa4 100644 --- a/src/build/build-example.js +++ b/src/build/build-example.js @@ -35,7 +35,7 @@ async function build(srcPath, isWeb = false) { resolveExtensions: ['.node.js', '.ts', '.js'], logLevel: 'error', plugins: isWeb - ? [typescriptPlugin(tsConfig)] + ? [typescriptPlugin(tsConfig), makeO1jsExternal()] : [ typescriptPlugin(tsConfig), makeNodeModulesExternal(), @@ -123,6 +123,18 @@ function makeNodeModulesExternal() { }; } +function makeO1jsExternal() { + return { + name: 'plugin-external', + setup(build) { + build.onResolve({ filter: /^o1js$/ }, () => ({ + path: './index.js', + external: true, + })); + }, + }; +} + function makeJsooExternal() { let isJsoo = /(bc.cjs|plonk_wasm.cjs)$/; return { diff --git a/src/lib/provable/core/field-constructor.ts b/src/lib/provable/core/field-constructor.ts index 3bf4781c93..1b27e36b6d 100644 --- a/src/lib/provable/core/field-constructor.ts +++ b/src/lib/provable/core/field-constructor.ts @@ -10,8 +10,11 @@ import type { FieldVar, FieldConst } from './fieldvar.js'; export { createField, createBool, + createBoolUnsafe, isField, isBool, + getField, + getBool, setFieldConstructor, setBoolConstructor, }; @@ -34,12 +37,16 @@ function createField( return new fieldConstructor(value); } -function createBool(value: boolean | Bool | FieldVar): Bool { +function createBool(value: boolean | Bool): Bool { if (boolConstructor === undefined) throw Error('Cannot construct a Bool before the class was defined.'); return new boolConstructor(value); } +function createBoolUnsafe(value: Field): Bool { + return getBool().Unsafe.fromField(value); +} + function isField(x: unknown): x is Field { if (fieldConstructor === undefined) throw Error( @@ -55,3 +62,14 @@ function isBool(x: unknown): x is Bool { ); return x instanceof boolConstructor; } + +function getField(): typeof Field { + if (fieldConstructor === undefined) + throw Error('Field class not defined yet.'); + return fieldConstructor; +} + +function getBool(): typeof Bool { + if (boolConstructor === undefined) throw Error('Bool class not defined yet.'); + return boolConstructor; +} diff --git a/src/lib/provable/crypto/signature.ts b/src/lib/provable/crypto/signature.ts index 5dd1f5929d..9a6159f933 100644 --- a/src/lib/provable/crypto/signature.ts +++ b/src/lib/provable/crypto/signature.ts @@ -139,15 +139,14 @@ class PublicKey extends CircuitValue { */ toGroup(): Group { // compute y from elliptic curve equation y^2 = x^3 + 5 - // TODO: we have to improve constraint efficiency by using range checks let { x, isOdd } = this; - let ySquared = x.mul(x).mul(x).add(5); - let someY = ySquared.sqrt(); - let isTheRightY = isOdd.equals(someY.toBits()[0]); - let y = isTheRightY - .toField() - .mul(someY) - .add(isTheRightY.not().toField().mul(someY.neg())); + let y = x.square().mul(x).add(5).sqrt(); + + // negate y if its parity is different from the public key's + let sameParity = y.isOdd().equals(isOdd).toField(); + let sign = sameParity.mul(2).sub(1); // (2*sameParity - 1) == 1 if same parity, -1 if different parity + y = y.mul(sign); + return new Group({ x, y }); } @@ -156,8 +155,7 @@ class PublicKey extends CircuitValue { * @returns a {@link PublicKey}. */ static fromGroup({ x, y }: Group): PublicKey { - let isOdd = y.toBits()[0]; - return PublicKey.fromObject({ x, isOdd }); + return PublicKey.fromObject({ x, isOdd: y.isOdd() }); } /** @@ -260,12 +258,12 @@ class Signature extends CircuitValue { deriveNonce( { fields: msg.map((f) => f.toBigInt()) }, { x: publicKey.x.toBigInt(), y: publicKey.y.toBigInt() }, - BigInt(d.toJSON()), + d.toBigInt(), 'testnet' ) ); let { x: r, y: ry } = Group.generator.scale(kPrime); - const k = ry.toBits()[0].toBoolean() ? kPrime.neg() : kPrime; + const k = ry.isOdd().toBoolean() ? kPrime.neg() : kPrime; let h = hashWithPrefix( signaturePrefix('testnet'), msg.concat([publicKey.x, publicKey.y, r]) @@ -294,7 +292,7 @@ class Signature extends CircuitValue { // 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)); - return Bool.and(r.x.equals(this.r), r.y.toBits()[0].equals(false)); + return r.x.equals(this.r).and(r.y.isEven()); } /** @@ -302,17 +300,14 @@ class Signature extends CircuitValue { */ static fromBase58(signatureBase58: string) { let { r, s } = SignatureBigint.fromBase58(signatureBase58); - return Signature.fromObject({ - r: Field(r), - s: Scalar.fromJSON(s.toString()), - }); + return Signature.fromObject({ r: Field(r), s: Scalar.from(s) }); } /** * Encodes a {@link Signature} in base58 format. */ toBase58() { let r = this.r.toBigInt(); - let s = BigInt(this.s.toJSON()); + let s = this.s.toBigInt(); return SignatureBigint.toBase58({ r, s }); } } diff --git a/src/lib/provable/field.ts b/src/lib/provable/field.ts index 8db0e78349..45ad9059db 100644 --- a/src/lib/provable/field.ts +++ b/src/lib/provable/field.ts @@ -3,7 +3,7 @@ import { Fp } from '../../bindings/crypto/finite-field.js'; import { BinableFp, SignableFp } from '../../mina-signer/src/field-bigint.js'; import { defineBinable } from '../../bindings/lib/binable.js'; import type { NonNegativeInteger } from '../../bindings/crypto/non-negative.js'; -import { asProver, inCheckedComputation } from './core/provable-context.js'; +import { inCheckedComputation } from './core/provable-context.js'; import { Bool } from './bool.js'; import { assert } from '../util/errors.js'; import { Provable } from './provable.js'; @@ -23,7 +23,12 @@ import { } from './core/fieldvar.js'; import { exists, existsOne } from './core/exists.js'; import { setFieldConstructor } from './core/field-constructor.js'; -import { compareCompatible } from './gadgets/comparison.js'; +import { + assertLessThanFull, + assertLessThanOrEqualFull, + lessThanFull, + lessThanOrEqualFull, +} from './gadgets/comparison.js'; // external API export { Field }; @@ -308,6 +313,32 @@ class Field { return this.add(Field.from(y).neg()); } + /** + * Checks if this {@link Field} is odd. Returns `true` for odd elements and `false` for even elements. + * + * 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); + } + /** * Checks if this {@link Field} is even. Returns `true` for even elements and `false` for odd elements. * @@ -315,35 +346,13 @@ class Field { * ```ts * let a = Field(5); * a.isEven(); // false - * a.isEven().assertTrue(); // throws, as expected! * * let b = Field(4); * b.isEven(); // true - * b.isEven().assertTrue(); // does not throw, as expected! * ``` */ isEven() { - if (this.isConstant()) return new Bool(this.toBigInt() % 2n === 0n); - - let [isOdd, xDiv2] = exists(2, () => { - let bits = BinableFp.toBits(this.toBigInt()); - let isOdd = bits.shift()! ? 1n : 0n; - return [isOdd, BinableFp.fromBits(bits)]; - }); - - // range check for 253 bits - // WARNING: this makes use of a special property of the Pasta curves, - // namely that a random field element is < 2^254 with overwhelming probability - // TODO use 88-bit RCs to make this more efficient - xDiv2.toBits(253); - - // boolean check - assertBoolean(isOdd); - - // check composition - xDiv2.mul(2).add(isOdd).assertEquals(this); - - return Bool.Unsafe.fromField(isOdd).not(); + return this.isOdd().not(); } /** @@ -560,17 +569,14 @@ class Field { * * @example * ```ts - * Field(2).lessThan(3).assertEquals(Bool(true)); + * let isTrue = Field(2).lessThan(3); * ``` * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behavior when used with negative inputs or modular division. * * @example * ```ts - * Field(1).div(Field(3)).lessThan(Field(1).div(Field(2))).assertEquals(Bool(true)); // This code will throw an error + * let isFalse = Field(1).div(3).lessThan(Field(1).div(2)); // in fact, 1/3 > 1/2 * ``` * * @param value - the "field-like" value to compare with this {@link Field}. @@ -581,7 +587,7 @@ class Field { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() < toFp(y)); } - return compareCompatible(this, Field.from(y)).less; + return lessThanFull(this, Field.from(y)); } /** @@ -590,17 +596,14 @@ class Field { * * @example * ```ts - * Field(3).lessThanOrEqual(3).assertEquals(Bool(true)); + * let isTrue = Field(3).lessThanOrEqual(3); * ``` * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. * * @example * ```ts - * Field(1).div(Field(3)).lessThanOrEqual(Field(1).div(Field(2))).assertEquals(Bool(true)); // This code will throw an error + * let isFalse = Field(1).div(3).lessThanOrEqual(Field(1).div(2)); // in fact, 1/3 > 1/2 * ``` * * @param value - the "field-like" value to compare with this {@link Field}. @@ -611,7 +614,7 @@ class Field { if (this.isConstant() && isConstant(y)) { return new Bool(this.toBigInt() <= toFp(y)); } - return compareCompatible(this, Field.from(y)).lessOrEqual; + return lessThanOrEqualFull(this, Field.from(y)); } /** @@ -620,17 +623,14 @@ class Field { * * @example * ```ts - * Field(5).greaterThan(3).assertEquals(Bool(true)); + * let isTrue = Field(5).greaterThan(3); * ``` * - * **Warning**: Comparison methods currently only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. * * @example * ```ts - * Field(1).div(Field(2)).greaterThan(Field(1).div(Field(3))).assertEquals(Bool(true)); // This code will throw an error + * let isFalse = Field(1).div(2).greaterThan(Field(1).div(3); // in fact, 1/3 > 1/2 * ``` * * @param value - the "field-like" value to compare with this {@link Field}. @@ -647,17 +647,14 @@ class Field { * * @example * ```ts - * Field(3).greaterThanOrEqual(3).assertEquals(Bool(true)); + * let isTrue = Field(3).greaterThanOrEqual(3); * ``` * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. * * @example * ```ts - * Field(1).div(Field(2)).greaterThanOrEqual(Field(1).div(Field(3))).assertEquals(Bool(true)); // This code will throw an error + * let isFalse = Field(1).div(2).greaterThanOrEqual(Field(1).div(3); // in fact, 1/3 > 1/2 * ``` * * @param value - the "field-like" value to compare with this {@link Field}. @@ -670,14 +667,12 @@ class Field { /** * Assert that this {@link Field} is less than another "field-like" value. - * Calling this function is equivalent to `Field(...).lessThan(...).assertEquals(Bool(true))`. + * + * Note: This uses fewer constraints than `x.lessThan(y).assertTrue()`. * See {@link Field.lessThan} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -689,8 +684,7 @@ class Field { } return; } - let { less } = compareCompatible(this, Field.from(y)); - less.assertTrue(); + assertLessThanFull(this, Field.from(y)); } catch (err) { throw withMessage(err, message); } @@ -698,14 +692,12 @@ class Field { /** * Assert that this {@link Field} is less than or equal to another "field-like" value. - * Calling this function is equivalent to `Field(...).lessThanOrEqual(...).assertEquals(Bool(true))`. + * + * Note: This uses fewer constraints than `x.lessThanOrEqual(y).assertTrue()`. * See {@link Field.lessThanOrEqual} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -717,8 +709,7 @@ class Field { } return; } - let { lessOrEqual } = compareCompatible(this, Field.from(y)); - lessOrEqual.assertTrue(); + assertLessThanOrEqualFull(this, Field.from(y)); } catch (err) { throw withMessage(err, message); } @@ -726,14 +717,12 @@ class Field { /** * Assert that this {@link Field} is greater than another "field-like" value. - * Calling this function is equivalent to `Field(...).greaterThan(...).assertEquals(Bool(true))`. + * + * Note: This uses fewer constraints than `x.greaterThan(y).assertTrue()`. * See {@link Field.greaterThan} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -743,14 +732,12 @@ class Field { /** * Assert that this {@link Field} is greater than or equal to another "field-like" value. - * Calling this function is equivalent to `Field(...).greaterThanOrEqual(...).assertEquals(Bool(true))`. + * + * Note: This uses fewer constraints than `x.greaterThanOrEqual(y).assertTrue()`. * See {@link Field.greaterThanOrEqual} for more details. * * **Important**: If an assertion fails, the code throws an error. * - * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. - * The method will throw if one of the inputs exceeds 253 bits. - * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -789,6 +776,7 @@ class Field { /** * Prove that this {@link Field} is equal to 0 or 1. + * Returns the Field wrapped in a {@link Bool}. * * If the assertion fails, the code throws an error. * @@ -798,12 +786,14 @@ class Field { try { if (this.isConstant()) { let x = this.toBigInt(); - if (x !== 0n && x !== 1n) { - throw Error(`Field.assertBool(): expected ${x} to be 0 or 1`); - } - return; + assert( + x === 0n || x === 1n, + `Field.assertBool(): expected ${x} to be 0 or 1` + ); + return new Bool(x === 1n); } assertBoolean(this); + return Bool.Unsafe.fromField(this); } catch (err) { throw withMessage(err, message); } diff --git a/src/lib/provable/gadgets/basic.ts b/src/lib/provable/gadgets/basic.ts index 31f2f1aac4..4e811adf3d 100644 --- a/src/lib/provable/gadgets/basic.ts +++ b/src/lib/provable/gadgets/basic.ts @@ -2,7 +2,7 @@ * Basic gadgets that only use generic gates */ import { Fp } from '../../../bindings/crypto/finite-field.js'; -import { Field, VarField } from '../field.js'; +import type { Field, VarField } from '../field.js'; import { FieldType, FieldVar, @@ -13,6 +13,7 @@ import { toVar } from './common.js'; import { Gates, fieldVar } from '../gates.js'; import { TupleN } from '../../util/types.js'; import { existsOne } from '../core/exists.js'; +import { createField } from '../core/field-constructor.js'; export { assertMul, arrayGet, assertOneOf }; @@ -140,7 +141,7 @@ function assertOneOf(x: Field, allowed: [bigint, bigint, ...bigint[]]) { */ function linear(x: VarField | VarFieldVar, [a, b]: TupleN) { let z = existsOne(() => { - let x0 = new Field(x).toBigInt(); + let x0 = createField(x).toBigInt(); return a * x0 + b; }); // a*x - z + b === 0 @@ -161,8 +162,8 @@ function bilinear( [a, b, c, d]: TupleN ) { let z = existsOne(() => { - let x0 = new Field(x).toBigInt(); - let y0 = new Field(y).toBigInt(); + let x0 = createField(x).toBigInt(); + let y0 = createField(y).toBigInt(); return a * x0 * y0 + b * x0 + c * y0 + d; }); // b*x + c*y - z + a*x*y + d === 0 diff --git a/src/lib/provable/gadgets/common.ts b/src/lib/provable/gadgets/common.ts index 1fbd9c53f4..a1a065f2fd 100644 --- a/src/lib/provable/gadgets/common.ts +++ b/src/lib/provable/gadgets/common.ts @@ -1,9 +1,10 @@ -import { Field, VarField } from '../field.js'; +import type { Field, VarField } from '../field.js'; import { FieldVar, VarFieldVar } from '../core/fieldvar.js'; 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'; export { toVars, toVar, isVar, assert, bitSlice, divideWithRemainder }; @@ -16,7 +17,7 @@ export { toVars, toVar, isVar, assert, bitSlice, divideWithRemainder }; * Same as `Field.seal()` with the difference that `seal()` leaves constants as is. */ function toVar(x_: Field | FieldVar | bigint): VarField { - let x = new Field(x_); + let x = createField(x_); // don't change existing vars if (isVar(x)) return x; let xVar = existsOne(() => x.toBigInt()); diff --git a/src/lib/provable/gadgets/comparison.ts b/src/lib/provable/gadgets/comparison.ts index f34c0e6709..15ae06061c 100644 --- a/src/lib/provable/gadgets/comparison.ts +++ b/src/lib/provable/gadgets/comparison.ts @@ -1,13 +1,203 @@ import type { Field } from '../field.js'; import type { Bool } from '../bool.js'; -import { createBool, createField } from '../core/field-constructor.js'; +import { createBoolUnsafe, createField } from '../core/field-constructor.js'; import { Fp } from '../../../bindings/crypto/finite-field.js'; import { assert } from '../../../lib/util/assert.js'; -import { exists } from '../core/exists.js'; +import { exists, existsOne } from '../core/exists.js'; import { assertMul } from './compatible.js'; -import { asProver } from '../core/provable-context.js'; +import { Field3, ForeignField } from './foreign-field.js'; +import { l, l2, multiRangeCheck } from './range-check.js'; +import { witness } from '../types/witness.js'; -export { compareCompatible }; +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, +}; + +/** + * Prove x <= y assuming 0 <= x, y < c. + * The upper bound c must satisfy 2c <= p, where p is the field order. + * + * Expects a function `rangeCheck(v: Field)` which proves that v is in [0, p-c). + * (Note: the range check on v can be looser than the assumption on x and y, but it doesn't have to be) + * The efficiency of the gadget largely depends on the efficiency of `rangeCheck()`. + * + * **Warning:** The gadget does not prove x <= y if either 2c > p or x or y are not in [0, c). + * Neither of these conditions are enforced by the gadget. + */ +function assertLessThanOrEqualGeneric( + x: Field, + y: Field, + rangeCheck: (v: Field) => void +) { + // since 0 <= x, y < c, we have y - x in [0, c) u (p-c, p) + // because of c <= p-c, the two ranges are disjoint. therefore, + // y - x in [0, p-c) is equivalent to x <= y + rangeCheck(y.sub(x).seal()); +} + +/** + * Prove x < y assuming 0 <= x, y < c. + * + * Assumptions are the same as in {@link assertLessThanOrEqualGeneric}. + */ +function assertLessThanGeneric( + x: Field, + y: Field, + rangeCheck: (v: Field) => void +) { + // since 0 <= x, y < c, we have y - 1 - x in [0, c) u [p-c, p) + // because of c <= p-c, the two ranges are disjoint. therefore, + // y - 1 - x in [0, p-c) is equivalent to x <= y - 1 which is equivalent to x < y + rangeCheck(y.sub(1).sub(x).seal()); +} + +/** + * Return a Bool b that is true if and only if x < y. + * + * Assumptions are similar as in {@link assertLessThanOrEqualGeneric}, with some important differences: + * - c is a required input + * - the `rangeCheck` function must fully prove that its input is in [0, c) + */ +function lessThanGeneric( + x: Field, + y: Field, + c: bigint, + rangeCheck: (v: Field) => void +) { + // we prove that there exists b such that b*c + x - y is in [0, c) + // if b = 0, this implies x - y is in [0, c), and so x >= y + // if b = 1, this implies x - y is in [p-c, p), and so x < y because p-c >= c + let b = existsOne(() => BigInt(x.toBigInt() < y.toBigInt())); + let isLessThan = b.assertBool(); + + // b*c + x - y in [0, c) + rangeCheck(b.mul(c).add(x).sub(y).seal()); + + return isLessThan; +} + +/** + * Return a Bool b that is true if and only if x <= y. + * + * Assumptions are similar as in {@link assertLessThanOrEqualGeneric}, with some important differences: + * - c is a required input + * - the `rangeCheck` function must fully prove that its input is in [0, c) + */ +function lessThanOrEqualGeneric( + x: Field, + y: Field, + c: bigint, + rangeCheck: (v: Field) => void +) { + // we prove that there exists b such that b*c + x - y - 1 is in [0, c) + // if b = 0, this implies x - y - 1 is in [0, c), and so x > y + // if b = 1, this implies x - y - 1 is in [p-c, p), and so x <= y because p-c >= c + let b = existsOne(() => BigInt(x.toBigInt() <= y.toBigInt())); + let isLessThanOrEqual = b.assertBool(); + + // b*c + x - y - 1 in [0, c) + rangeCheck(b.mul(c).add(x).sub(y).sub(1).seal()); + + return isLessThanOrEqual; +} + +/** + * Assert that x < y. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function assertLessThanFull(x: Field, y: Field) { + let xBig = fieldToField3(x); + let yBig = fieldToField3(y); + + // x < y as bigints + ForeignField.assertLessThan(xBig, yBig); + + // y < p, so y is canonical. implies x < p as well. + // (if we didn't do this check, we would prove nothing. + // e.g. yBig could be the bigint representation of y + p, and only _therefore_ larger than xBig) + ForeignField.assertLessThan(yBig, Fp.modulus); +} + +/** + * Assert that x <= y. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function assertLessThanOrEqualFull(x: Field, y: Field) { + let xBig = fieldToField3(x); + let yBig = fieldToField3(y); + ForeignField.assertLessThanOrEqual(xBig, yBig); + ForeignField.assertLessThan(yBig, Fp.modulus); +} + +/** + * Return a Bool b that is true if and only if x < y. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function lessThanFull(x: Field, y: Field) { + // same logic as in lessThanGeneric: + // we witness b such that b*p + x - y is in [0, p), where the sum is done in bigint arithmetic + // if b = 0, x - y is in [0, p), and so x >= y + // if b = 1, x - y is in [-p, 0), and so x < y + // we must also check that both x and y are canonical, or else the connection between the bigint and the Field is lost + let b = existsOne(() => BigInt(x.toBigInt() < y.toBigInt())); + let isLessThan = b.assertBool(); + + let xBig = fieldToField3(x); + let yBig = fieldToField3(y); + ForeignField.assertLessThan(xBig, Fp.modulus); + ForeignField.assertLessThan(yBig, Fp.modulus); + + let [p0, p1, p2] = Field3.from(Fp.modulus); + let bTimesP: Field3 = [p0.mul(b), p1.mul(b), p2.mul(b)]; + + // b*p + x - y in [0, p) + let z = ForeignField.sum([bTimesP, xBig, yBig], [1n, -1n], 0n); + ForeignField.assertLessThan(z, Fp.modulus); + + return isLessThan; +} + +/** + * Return a Bool b that is true if and only if x <= y. + * + * There are no assumptions on the range of x and y, they can occupy the full range [0, p). + */ +function lessThanOrEqualFull(x: Field, y: Field) { + // keep it simple and just use x <= y <=> !(y < x) + return lessThanFull(y, x).not(); +} + +/** + * internal helper, split Field into a 3-limb bigint + * + * **Warning:** the output is underconstrained up to a multiple of the modulus that could be added to the bigint. + */ +function fieldToField3(x: Field) { + if (x.isConstant()) return Field3.from(x.toBigInt()); + + let xBig = witness(Field3.provable, () => Field3.from(x.toBigInt())); + multiRangeCheck(xBig); + let [x0, x1, x2] = xBig; + + // prove that x == x0 + x1*2^l + x2*2^2l + let x_ = x0.add(x1.mul(1n << l)).add(x2.mul(1n << l2)); + x_.assertEquals(x); + return xBig; +} /** * Compare x and y assuming both have at most `n` bits. @@ -16,24 +206,12 @@ export { compareCompatible }; * It is up to the caller to prove that `x` and `y` have at most `n` bits. * * **Warning:** This was created for 1:1 compatibility with snarky's `compare` gadget. - * It was designed for R1CS and is extremeley inefficient when used with plonkish arithmetization. + * It was designed for R1CS and is extremely inefficient when used with plonkish arithmetization. */ function compareCompatible(x: Field, y: Field, n = Fp.sizeInBits - 2) { let maxLength = Fp.sizeInBits - 2; assert(n <= maxLength, `bitLength must be at most ${maxLength}`); - // as prover check - asProver(() => { - let actualLength = Math.max( - x.toBigInt().toString(2).length, - y.toBigInt().toString(2).length - ); - if (actualLength > maxLength) - throw Error( - `Provable comparison functions can only be used on Fields of size <= ${maxLength} bits, got ${actualLength} bits.` - ); - }); - // z = 2^n + y - x let z = createField(1n << BigInt(n)) .add(y) @@ -67,7 +245,7 @@ function unpack(x: Field, length: number) { createField(0) ); assertMul(lc, createField(1), x); - return bits.map((b) => createBool(b.value)); + return bits.map((b) => createBoolUnsafe(b)); } function any(xs: Bool[]) { @@ -89,5 +267,5 @@ function isZero(x: Field): Bool { assertMul(b, x, createField(0)); // z * x === 1 - b assertMul(z, x, createField(1).sub(b)); - return createBool(b.value); + return createBoolUnsafe(b); } diff --git a/src/lib/provable/gadgets/foreign-field.ts b/src/lib/provable/gadgets/foreign-field.ts index 71744eb4b3..df864a7a6b 100644 --- a/src/lib/provable/gadgets/foreign-field.ts +++ b/src/lib/provable/gadgets/foreign-field.ts @@ -6,9 +6,8 @@ import { mod, } from '../../../bindings/crypto/finite-field.js'; import { provableTuple } from '../types/provable-derivers.js'; -import { Bool } from '../bool.js'; import { Unconstrained } from '../types/unconstrained.js'; -import { Field } from '../field.js'; +import type { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; import { exists } from '../core/exists.js'; import { modifiedField } from '../types/fields.js'; @@ -24,6 +23,7 @@ import { l3, compactMultiRangeCheck, } from './range-check.js'; +import { createBool, createField } from '../core/field-constructor.js'; // external API export { ForeignField, Field3 }; @@ -60,20 +60,8 @@ const ForeignField = { assertAlmostReduced, - assertLessThan(x: Field3, f: bigint) { - assert(f > 0n, 'assertLessThan: upper bound must be positive'); - - // constant case - if (Field3.isConstant(x)) { - assert(Field3.toBigint(x) < f, 'assertLessThan: got x >= f'); - return; - } - // provable case - // we can just use negation `(f - 1) - x`. because the result is range-checked, it proves that x < f: - // `f - 1 - x \in [0, 2^3l) => x <= x + (f - 1 - x) = f - 1 < f` - // (note: ffadd can't add higher multiples of (f - 1). it must always use an overflow of -1, except for x = 0) - ForeignField.negate(x, f - 1n); - }, + assertLessThan, + assertLessThanOrEqual, equals, }; @@ -102,7 +90,7 @@ function sum(x: Field3[], sign: Sign[], f: bigint) { Gates.zero(...result); // range check result - multiRangeCheck(result); + indirectMultiRangeChange(result); return result; } @@ -182,12 +170,12 @@ function inverse(x: Field3, f: bigint): Field3 { // we need to bound xInv because it's a multiplication input let xInv2Bound = weakBound(xInv[2], f); - let one: Field2 = [Field.from(1n), Field.from(0n)]; + let one: Field2 = [createField(1n), createField(0n)]; assertMulInternal(x, xInv, one, f); // range check on result bound // TODO: this uses two RCs too many.. need global RC stack - multiRangeCheck([xInv2Bound, Field.from(0n), Field.from(0n)]); + multiRangeCheck([xInv2Bound, createField(0n), createField(0n)]); return xInv; } @@ -219,7 +207,7 @@ function divide( assertMulInternal(z, y, x, f); // range check on result bound - multiRangeCheck([z2Bound, Field.from(0n), Field.from(0n)]); + multiRangeCheck([z2Bound, createField(0n), createField(0n)]); if (!allowZeroOverZero) { ForeignField.equals(y, 0n, f).assertFalse(); @@ -385,10 +373,10 @@ function assertAlmostReduced(xs: Field3[], f: bigint, skipMrc = false) { } } if (TupleN.hasLength(1, bounds)) { - multiRangeCheck([...bounds, Field.from(0n), Field.from(0n)]); + multiRangeCheck([...bounds, createField(0n), createField(0n)]); } if (TupleN.hasLength(2, bounds)) { - multiRangeCheck([...bounds, Field.from(0n)]); + multiRangeCheck([...bounds, createField(0n)]); } } @@ -404,7 +392,7 @@ function equals(x: Field3, c: bigint, f: bigint) { // constant case if (Field3.isConstant(x)) { - return new Bool(mod(Field3.toBigint(x), f) === c); + return createBool(mod(Field3.toBigint(x), f) === c); } // provable case @@ -439,8 +427,9 @@ const Field3 = { /** * Turn a bigint into a 3-tuple of Fields */ - from(x: bigint): Field3 { - return Tuple.map(split(x), Field.from); + from(x: bigint | Field3): Field3 { + if (Array.isArray(x)) return x; + return Tuple.map(split(x), createField); }, /** @@ -720,3 +709,86 @@ class Sum { return new Sum(x); } } + +// Field3 comparison + +function assertLessThan(x: Field3, y: bigint | Field3) { + let y_ = Field3.from(y); + + // constant case + + if (Field3.isConstant(x) && Field3.isConstant(y_)) { + assert( + Field3.toBigint(x) < Field3.toBigint(y_), + 'assertLessThan: got x >= y' + ); + return; + } + + // case of one variable, one constant + + if (Field3.isConstant(x)) return assertLessThan(y_, x); + if (Field3.isConstant(y_)) { + y = typeof y === 'bigint' ? y : Field3.toBigint(y); + // this case is not included below, because ffadd doesn't support negative moduli + assert(y > 0n, 'assertLessThan: y <= 0, so x < y is impossible'); + + // we can just use negation `(y - 1) - x`. because the result is range-checked, it proves that x < y: + // `y - 1 - x \in [0, 2^3l) => x <= x + (y - 1 - x) = y - 1 < y` + // (note: ffadd can't add higher multiples of (f - 1). it must always use an overflow of -1, except for x = 0) + + ForeignField.negate(x, y - 1n); + return; + } + + // case of two variables + // we compute z = y - x - 1 and check that z \in [0, 2^3l), which implies x < y as above + + // we use modulo 0 here, which means we're proving: + // z = y - x - 1 - 0*(o1 + o2) for some overflows o1, o2 + sum([y_, x, Field3.from(1n)], [-1n, -1n], 0n); +} + +function assertLessThanOrEqual(x: Field3, y: bigint | Field3) { + assert( + typeof y !== 'bigint' || y >= 0n, + 'assertLessThanOrEqual: upper bound must be positive' + ); + let y_ = Field3.from(y); + + // constant case + if (Field3.isConstant(x) && Field3.isConstant(y_)) { + assert( + Field3.toBigint(x) <= Field3.toBigint(y_), + 'assertLessThan: got x > y' + ); + return; + } + + // provable case + // we compute z = y - x and check that z \in [0, 2^3l), which implies x <= y + sum([y_, x], [-1n], 0n); +} + +// helpers + +/** + * Version of `multiRangeCheck` which does the check on a truncated version of the input, + * so that it always succeeds, and then checks equality of the truncated and full input. + * + * This is a hack to get an error when the constraint fails, around the fact that multiRangeCheck + * is not checked by snarky. + */ +function indirectMultiRangeChange( + x: Field3, + message = 'multi-range check failed' +) { + let xTrunc = exists(3, () => { + let [x0, x1, x2] = toBigint3(x); + return [x0 & lMask, x1 & lMask, x2 & lMask]; + }); + multiRangeCheck(xTrunc); + x[0].assertEquals(xTrunc[0], message); + x[1].assertEquals(xTrunc[1], message); + x[2].assertEquals(xTrunc[2], message); +} diff --git a/src/lib/provable/gadgets/gadgets.ts b/src/lib/provable/gadgets/gadgets.ts index fa56fb47e9..6a67973693 100644 --- a/src/lib/provable/gadgets/gadgets.ts +++ b/src/lib/provable/gadgets/gadgets.ts @@ -739,7 +739,7 @@ const Gadgets = { }, /** - * Prove that x < f for any constant f < 2^264. + * Prove that x < f for any constant f < 2^264, or for another `Field3` f. * * If f is a finite field modulus, this means that the given field element is fully reduced modulo f. * This is a stronger statement than {@link ForeignField.assertAlmostReduced} @@ -761,9 +761,18 @@ const Gadgets = { * Gadgets.ForeignField.assertLessThan(x, f); * ``` */ - assertLessThan(x: Field3, f: bigint) { + assertLessThan(x: Field3, f: bigint | Field3) { ForeignField.assertLessThan(x, f); }, + + /** + * Prove that x <= f for any constant f < 2^264, or for another `Field3` f. + * + * See {@link ForeignField.assertLessThan} for details and usage examples. + */ + assertLessThanOrEqual(x: Field3, f: bigint | Field3) { + ForeignField.assertLessThanOrEqual(x, f); + }, }, /** diff --git a/src/lib/provable/gadgets/range-check.ts b/src/lib/provable/gadgets/range-check.ts index 860b8628cf..3ceb74bd93 100644 --- a/src/lib/provable/gadgets/range-check.ts +++ b/src/lib/provable/gadgets/range-check.ts @@ -1,11 +1,11 @@ import { Snarky } from '../../../snarky.js'; import { Fp } from '../../../bindings/crypto/finite-field.js'; import { BinableFp } from '../../../mina-signer/src/field-bigint.js'; -import { Field } from '../field.js'; +import type { Field } from '../field.js'; import { Gates } from '../gates.js'; import { assert, bitSlice, toVar, toVars } from './common.js'; -import { Bool } from '../bool.js'; import { exists } from '../core/exists.js'; +import { createBool, createField } from '../core/field-constructor.js'; export { rangeCheck64, @@ -73,7 +73,7 @@ function rangeCheck64(x: Field) { Gates.rangeCheck0( x, - [new Field(0), new Field(0), x52, x40, x28, x16], + [createField(0), createField(0), x52, x40, x28, x16], [x14, x12, x10, x8, x6, x4, x2, x0], false // not using compact mode ); @@ -121,7 +121,7 @@ function compactMultiRangeCheck(xy: Field, z: Field): [Field, Field, Field] { ); } let [x, y] = splitCompactLimb(xy.toBigInt()); - return [new Field(x), new Field(y), z]; + return [createField(x), createField(y), z]; } // ensure we are using pure variables [xy, z] = toVars([xy, z]); @@ -259,10 +259,10 @@ function rangeCheckHelper(length: number, x: Field) { let bits = BinableFp.toBits(x.toBigInt()) .slice(0, length) .concat(Array(Fp.sizeInBits - length).fill(false)); - return new Field(BinableFp.fromBits(bits)); + return createField(BinableFp.fromBits(bits)); } let y = Snarky.field.truncateToBits16(lengthDiv16, x.value); - return new Field(y); + return createField(y); } /** @@ -307,7 +307,7 @@ function isDefinitelyInRangeN(n: number, x: Field) { assert(n % 16 === 0, '`length` has to be a multiple of 16.'); if (x.isConstant()) { - return new Bool(x.toBigInt() < 1n << BigInt(n)); + return createBool(x.toBigInt() < 1n << BigInt(n)); } let actual = rangeCheckHelper(n, x); diff --git a/src/lib/provable/int.ts b/src/lib/provable/int.ts index 8a3f791278..2b2fa32110 100644 --- a/src/lib/provable/int.ts +++ b/src/lib/provable/int.ts @@ -10,6 +10,13 @@ import type { Gadgets } from './gadgets/gadgets.js'; import { withMessage } from './field.js'; import { FieldVar } from './core/fieldvar.js'; import { CircuitValue, prop } from './types/circuit-value.js'; +import { + assertLessThanGeneric, + assertLessThanOrEqualGeneric, + lessThanGeneric, + lessThanOrEqualGeneric, +} from './gadgets/comparison.js'; +import { assert } from '../util/assert.js'; // external API export { UInt8, UInt32, UInt64, Int64, Sign }; @@ -401,24 +408,10 @@ class UInt64 extends CircuitValue { lessThanOrEqual(y: UInt64) { if (this.value.isConstant() && y.value.isConstant()) { return Bool(this.value.toBigInt() <= y.value.toBigInt()); - } else { - let xMinusY = this.value.sub(y.value).seal(); - let yMinusX = xMinusY.neg(); - - let xMinusYFits = RangeCheck.isDefinitelyInRangeN( - UInt64.NUM_BITS, - xMinusY - ); - - let yMinusXFits = RangeCheck.isDefinitelyInRangeN( - UInt64.NUM_BITS, - yMinusX - ); - - xMinusYFits.or(yMinusXFits).assertEquals(true); - // x <= y if y - x fits in 64 bits - return yMinusXFits; } + return lessThanOrEqualGeneric(this.value, y.value, 1n << 64n, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v) + ); } /** @@ -426,16 +419,15 @@ class UInt64 extends CircuitValue { */ assertLessThanOrEqual(y: UInt64, message?: string) { if (this.value.isConstant() && y.value.isConstant()) { - let x0 = this.value.toBigInt(); - let y0 = y.value.toBigInt(); - if (x0 > y0) { - if (message !== undefined) throw Error(message); - throw Error(`UInt64.assertLessThanOrEqual: expected ${x0} <= ${y0}`); - } - return; + let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()]; + return assert( + x0 <= y0, + message ?? `UInt64.assertLessThanOrEqual: expected ${x0} <= ${y0}` + ); } - let yMinusX = y.value.sub(this.value).seal(); - RangeCheck.rangeCheckN(UInt64.NUM_BITS, yMinusX, message); + assertLessThanOrEqualGeneric(this.value, y.value, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v, message) + ); } /** @@ -443,14 +435,28 @@ class UInt64 extends CircuitValue { * Checks if a {@link UInt64} is less than another one. */ lessThan(y: UInt64) { - return this.lessThanOrEqual(y).and(this.value.equals(y.value).not()); + if (this.value.isConstant() && y.value.isConstant()) { + return Bool(this.value.toBigInt() < y.value.toBigInt()); + } + return lessThanGeneric(this.value, y.value, 1n << 64n, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v) + ); } /** * Asserts that a {@link UInt64} is less than another one. */ assertLessThan(y: UInt64, message?: string) { - this.lessThan(y).assertEquals(true, message); + if (this.value.isConstant() && y.value.isConstant()) { + let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()]; + return assert( + x0 < y0, + message ?? `UInt64.assertLessThan: expected ${x0} < ${y0}` + ); + } + assertLessThanGeneric(this.value, y.value, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v, message) + ); } /** @@ -471,7 +477,7 @@ class UInt64 extends CircuitValue { * Checks if a {@link UInt64} is greater than or equal to another one. */ greaterThanOrEqual(y: UInt64) { - return this.lessThan(y).not(); + return y.lessThanOrEqual(this); } /** @@ -852,21 +858,10 @@ class UInt32 extends CircuitValue { lessThanOrEqual(y: UInt32) { if (this.value.isConstant() && y.value.isConstant()) { return Bool(this.value.toBigInt() <= y.value.toBigInt()); - } else { - let xMinusY = this.value.sub(y.value).seal(); - let yMinusX = xMinusY.neg(); - let xMinusYFits = RangeCheck.isDefinitelyInRangeN( - UInt32.NUM_BITS, - xMinusY - ); - let yMinusXFits = RangeCheck.isDefinitelyInRangeN( - UInt32.NUM_BITS, - yMinusX - ); - xMinusYFits.or(yMinusXFits).assertEquals(true); - // x <= y if y - x fits in 64 bits - return yMinusXFits; } + return lessThanOrEqualGeneric(this.value, y.value, 1n << 32n, (v) => + RangeCheck.rangeCheckN(UInt32.NUM_BITS, v) + ); } /** @@ -874,30 +869,43 @@ class UInt32 extends CircuitValue { */ assertLessThanOrEqual(y: UInt32, message?: string) { if (this.value.isConstant() && y.value.isConstant()) { - let x0 = this.value.toBigInt(); - let y0 = y.value.toBigInt(); - if (x0 > y0) { - if (message !== undefined) throw Error(message); - throw Error(`UInt32.assertLessThanOrEqual: expected ${x0} <= ${y0}`); - } - return; + let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()]; + return assert( + x0 <= y0, + message ?? `UInt32.assertLessThanOrEqual: expected ${x0} <= ${y0}` + ); } - let yMinusX = y.value.sub(this.value).seal(); - RangeCheck.rangeCheckN(UInt32.NUM_BITS, yMinusX, message); + assertLessThanOrEqualGeneric(this.value, y.value, (v) => + RangeCheck.rangeCheckN(UInt32.NUM_BITS, v, message) + ); } /** * Checks if a {@link UInt32} is less than another one. */ lessThan(y: UInt32) { - return this.lessThanOrEqual(y).and(this.value.equals(y.value).not()); + if (this.value.isConstant() && y.value.isConstant()) { + return Bool(this.value.toBigInt() < y.value.toBigInt()); + } + return lessThanGeneric(this.value, y.value, 1n << 64n, (v) => + RangeCheck.rangeCheckN(UInt64.NUM_BITS, v) + ); } /** * Asserts that a {@link UInt32} is less than another one. */ assertLessThan(y: UInt32, message?: string) { - this.lessThan(y).assertEquals(true, message); + if (this.value.isConstant() && y.value.isConstant()) { + let [x0, y0] = [this.value.toBigInt(), y.value.toBigInt()]; + return assert( + x0 < y0, + message ?? `UInt32.assertLessThan: expected ${x0} < ${y0}` + ); + } + assertLessThanGeneric(this.value, y.value, (v) => + RangeCheck.rangeCheckN(UInt32.NUM_BITS, v, message) + ); } /** @@ -918,7 +926,7 @@ class UInt32 extends CircuitValue { * Checks if a {@link UInt32} is greater than or equal to another one. */ greaterThanOrEqual(y: UInt32) { - return this.lessThan(y).not(); + return y.lessThanOrEqual(this); } /** @@ -1348,7 +1356,12 @@ class UInt8 extends Struct({ if (this.value.isConstant() && y_.value.isConstant()) { return Bool(this.toBigInt() <= y_.toBigInt()); } - throw Error('Not implemented'); + return lessThanOrEqualGeneric( + this.value, + y_.value, + 1n << 8n, + RangeCheck.rangeCheck8 + ); } /** @@ -1365,7 +1378,12 @@ class UInt8 extends Struct({ if (this.value.isConstant() && y_.value.isConstant()) { return Bool(this.toBigInt() < y_.toBigInt()); } - throw Error('Not implemented'); + return lessThanGeneric( + this.value, + y_.value, + 1n << 8n, + RangeCheck.rangeCheck8 + ); } /** @@ -1379,17 +1397,18 @@ class UInt8 extends Struct({ assertLessThan(y: UInt8 | bigint | number, message?: string) { let y_ = UInt8.from(y); if (this.value.isConstant() && y_.value.isConstant()) { - let x0 = this.toBigInt(); - let y0 = y_.toBigInt(); - if (x0 >= y0) { - if (message !== undefined) throw Error(message); - throw Error(`UInt8.assertLessThan: expected ${x0} < ${y0}`); - } - return; + let [x0, y0] = [this.value.toBigInt(), y_.value.toBigInt()]; + return assert( + x0 < y0, + message ?? `UInt8.assertLessThan: expected ${x0} < ${y0}` + ); + } + try { + // 2^16 < p - 2^8, so we satisfy the assumption of `assertLessThanGeneric` + assertLessThanGeneric(this.value, y_.value, RangeCheck.rangeCheck16); + } catch (err) { + throw withMessage(err, message); } - // x < y <=> x + 1 <= y - let xPlus1 = new UInt8(this.value.add(1).value); - xPlus1.assertLessThanOrEqual(y, message); } /** @@ -1403,18 +1422,19 @@ class UInt8 extends Struct({ assertLessThanOrEqual(y: UInt8 | bigint | number, message?: string) { let y_ = UInt8.from(y); if (this.value.isConstant() && y_.value.isConstant()) { - let x0 = this.toBigInt(); - let y0 = y_.toBigInt(); - if (x0 > y0) { - if (message !== undefined) throw Error(message); - throw Error(`UInt8.assertLessThanOrEqual: expected ${x0} <= ${y0}`); - } - return; + let [x0, y0] = [this.value.toBigInt(), y_.value.toBigInt()]; + return assert( + x0 <= y0, + message ?? `UInt8.assertLessThanOrEqual: expected ${x0} <= ${y0}` + ); } try { - // x <= y <=> y - x >= 0 which is implied by y - x in [0, 2^16) - let yMinusX = y_.value.sub(this.value).seal(); - RangeCheck.rangeCheck16(yMinusX); + // 2^16 < p - 2^8, so we satisfy the assumption of `assertLessThanOrEqualGeneric` + assertLessThanOrEqualGeneric( + this.value, + y_.value, + RangeCheck.rangeCheck16 + ); } catch (err) { throw withMessage(err, message); } diff --git a/src/lib/provable/provable.ts b/src/lib/provable/provable.ts index 765ba86d20..20c4bb6f01 100644 --- a/src/lib/provable/provable.ts +++ b/src/lib/provable/provable.ts @@ -17,12 +17,11 @@ import { import { inCheckedComputation, inProver, - snarkContext, asProver, constraintSystem, generateWitness, } from './core/provable-context.js'; -import { exists, existsAsync } from './core/exists.js'; +import { witness, witnessAsync } from './types/witness.js'; // external API export { Provable }; @@ -225,76 +224,6 @@ const Provable = { }, }; -function witness = FlexibleProvable>( - type: S, - compute: () => T -): T { - let ctx = snarkContext.get(); - - // outside provable code, we just call the callback and return its cloned result - if (!inCheckedComputation() || ctx.inWitnessBlock) { - return clone(type, compute()); - } - let proverValue: T | undefined = undefined; - let fields: Field[]; - - let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); - try { - fields = exists(type.sizeInFields(), () => { - proverValue = compute(); - let fields = type.toFields(proverValue); - return fields.map((x) => x.toBigInt()); - }); - } finally { - snarkContext.leave(id); - } - - // rebuild the value from its fields (which are now variables) and aux data - let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); - - // add type-specific constraints - type.check(value); - - return value; -} - -async function witnessAsync< - T, - S extends FlexibleProvable = FlexibleProvable ->(type: S, compute: () => Promise): Promise { - let ctx = snarkContext.get(); - - // outside provable code, we just call the callback and return its cloned result - if (!inCheckedComputation() || ctx.inWitnessBlock) { - let value: T = await compute(); - return clone(type, value); - } - let proverValue: T | undefined = undefined; - let fields: Field[]; - - // call into `existsAsync` to witness the raw field elements - let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); - try { - fields = await existsAsync(type.sizeInFields(), async () => { - proverValue = await compute(); - let fields = type.toFields(proverValue); - return fields.map((x) => x.toBigInt()); - }); - } finally { - snarkContext.leave(id); - } - - // rebuild the value from its fields (which are now variables) and aux data - let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); - - // add type-specific constraints - type.check(value); - - return value; -} - type ToFieldable = { toFields(): Field[] }; // general provable methods diff --git a/src/lib/provable/test/field.unit-test.ts b/src/lib/provable/test/field.unit-test.ts index 2465df3adb..d0a4efebde 100644 --- a/src/lib/provable/test/field.unit-test.ts +++ b/src/lib/provable/test/field.unit-test.ts @@ -15,10 +15,10 @@ import { throwError, unit, bool, - Spec, } from '../../testing/equivalent.js'; import { runAndCheckSync } from '../core/provable-context.js'; import { ProvablePure } from '../types/provable-intf.js'; +import { assert } from '../../util/assert.js'; // types Field satisfies Provable; @@ -70,15 +70,6 @@ test(Random.field, Random.int(-5, 5), (x, k) => { // Field | bigint parameter let fieldOrBigint = oneOf(field, bigintField); -// special generator -let SmallField = Random.reject( - Random.field, - (x) => x.toString(2).length > Fp.sizeInBits - 2 -); -let smallField: Spec = { ...field, rng: SmallField }; -let smallBigint: Spec = { ...bigintField, rng: SmallField }; -let smallFieldOrBigint = oneOf(smallField, smallBigint); - // arithmetic, both in- and outside provable code let equivalent1 = equivalent({ from: [field], to: field }); let equivalent2 = equivalent({ from: [field, fieldOrBigint], to: field }); @@ -105,11 +96,11 @@ equivalent({ from: [field, fieldOrBigint], to: bool })( (x, y) => x.equals(y) ); -equivalent({ from: [smallField, smallFieldOrBigint], to: bool })( +equivalent({ from: [field, fieldOrBigint], to: bool })( (x, y) => x < y, (x, y) => x.lessThan(y) ); -equivalent({ from: [smallField, smallFieldOrBigint], to: bool })( +equivalent({ from: [field, fieldOrBigint], to: bool })( (x, y) => x <= y, (x, y) => x.lessThanOrEqual(y) ); @@ -121,16 +112,19 @@ equivalent({ from: [field, fieldOrBigint], to: unit })( (x, y) => x !== y || throwError('equal'), (x, y) => x.assertNotEquals(y) ); -equivalent({ from: [smallField, smallFieldOrBigint], to: unit })( +equivalent({ from: [field, fieldOrBigint], to: unit })( (x, y) => x < y || throwError('not less than'), (x, y) => x.assertLessThan(y) ); -equivalent({ from: [smallField, smallFieldOrBigint], to: unit })( +equivalent({ from: [field, fieldOrBigint], to: unit })( (x, y) => x <= y || throwError('not less than or equal'), (x, y) => x.assertLessThanOrEqual(y) ); -equivalent({ from: [field], to: unit })( - (x) => x === 0n || x === 1n || throwError('not boolean'), +equivalent({ from: [field], to: bool })( + (x) => { + assert(x === 0n || x === 1n, 'not boolean'); + return x === 1n; + }, (x) => x.assertBool() ); equivalent({ from: [field], to: unit })( @@ -140,7 +134,7 @@ equivalent({ from: [field], to: unit })( y.mul(2).assertBool(); } ); -equivalent({ from: [smallField], to: bool })( +equivalent({ from: [field], to: bool })( (x) => (x & 1n) === 0n, (x) => x.isEven() ); diff --git a/src/lib/provable/test/foreign-field-gadgets.unit-test.ts b/src/lib/provable/test/foreign-field-gadgets.unit-test.ts index 3f0fdaaca5..c4f4c517d1 100644 --- a/src/lib/provable/test/foreign-field-gadgets.unit-test.ts +++ b/src/lib/provable/test/foreign-field-gadgets.unit-test.ts @@ -92,7 +92,8 @@ for (let F of fields) { let big264 = unreducedForeignField(264, F); // this is the max size supported by our range checks / ffadd let big258 = unreducedForeignField(258, F); // rough max size supported by ffmul - equivalentProvable({ from: [big264, big264], to: big264 })( + // addition can fail on two unreduced inputs because we can get x + y - f > 2^264 + equivalentProvable({ from: [big264, f], to: big264 })( F.add, (x, y) => ForeignField.add(x, y, F.modulus), 'add unreduced' @@ -127,6 +128,16 @@ for (let F of fields) { (x) => ForeignField.assertAlmostReduced([x], F.modulus) ); + equivalentProvable({ from: [big264, big264], to: unit })( + (x, y) => assert(x < y, 'not less than'), + (x, y) => ForeignField.assertLessThan(x, y) + ); + + equivalentProvable({ from: [big264, big264], to: unit })( + (x, y) => assert(x <= y, 'not less than or equal'), + (x, y) => ForeignField.assertLessThanOrEqual(x, y) + ); + // sumchain of 5 equivalentProvable({ from: [array(f, 5), array(sign, 4)], to: f })( (xs, signs) => sum(xs, signs, F), @@ -195,6 +206,13 @@ let ffProgram = ZkProgram({ return ForeignField.div(x, y, F.modulus); }, }, + assertLessThan: { + privateInputs: [Field3.provable, Field3.provable], + async method(x, y) { + ForeignField.assertLessThan(x, y); + return x; + }, + }, }, }); @@ -274,6 +292,15 @@ await equivalentAsync({ from: [f, f], to: f }, { runs })( 'prove div' ); +await equivalentAsync({ from: [f, f], to: unit }, { runs })( + (x, y) => assert(x < y, 'not less than'), + async (x, y) => { + let proof = await ffProgram.assertLessThan(x, y); + assert(await ffProgram.verify(proof), 'verifies'); + }, + 'prove less than' +); + // assert mul example // (x - y) * (x + y) = x^2 - y^2 diff --git a/src/lib/provable/test/foreign-field.unit-test.ts b/src/lib/provable/test/foreign-field.unit-test.ts index 64f5fb65c3..c7f228ddaf 100644 --- a/src/lib/provable/test/foreign-field.unit-test.ts +++ b/src/lib/provable/test/foreign-field.unit-test.ts @@ -94,9 +94,7 @@ equivalent({ from: [f, f], to: unit })( (x, y) => x === y || throwError('not equal'), (x, y) => x.assertEquals(y) ); -// doesn't fail in provable mode just because the range check is not checked by runAndCheck -// TODO check all gates -equivalentNonProvable({ from: [f, first(u264)], to: unit })( +equivalent({ from: [f, first(u264)], to: unit })( (x, y) => x < y || throwError('not less than'), (x, y) => x.assertLessThan(y) ); diff --git a/src/lib/provable/types/fields.ts b/src/lib/provable/types/fields.ts index e92cd0d51e..d020ac6fb3 100644 --- a/src/lib/provable/types/fields.ts +++ b/src/lib/provable/types/fields.ts @@ -1,10 +1,9 @@ import { ProvablePureExtended } from './struct.js'; -import { Field } from '../field.js'; +import type { Field } from '../field.js'; +import { createField, getField } from '../core/field-constructor.js'; export { modifiedField, fields }; -const zero = new Field(0); - // provable for a single field element const ProvableField: ProvablePureExtended = { @@ -14,9 +13,9 @@ const ProvableField: ProvablePureExtended = { fromFields: ([x]) => x, check: () => {}, toInput: (x) => ({ fields: [x] }), - toJSON: Field.toJSON, - fromJSON: Field.fromJSON, - empty: () => zero, + toJSON: (x) => getField().toJSON(x), + fromJSON: (x) => getField().fromJSON(x), + empty: () => createField(0), }; function modifiedField( @@ -37,8 +36,11 @@ function fields(length: number): ProvablePureExtended { fromFields: id, check: () => {}, toInput: (x) => ({ fields: x }), - toJSON: (x) => x.map(Field.toJSON), - fromJSON: (x) => x.map(Field.fromJSON), - empty: () => new Array(length).fill(zero), + toJSON: (x) => x.map(getField().toJSON), + fromJSON: (x) => x.map(getField().fromJSON), + empty: () => { + let zero = createField(0); + return new Array(length).fill(zero); + }, }; } diff --git a/src/lib/provable/types/provable-derivers.ts b/src/lib/provable/types/provable-derivers.ts index 552e11b3b2..9e9844056a 100644 --- a/src/lib/provable/types/provable-derivers.ts +++ b/src/lib/provable/types/provable-derivers.ts @@ -1,5 +1,5 @@ import { Provable, ProvablePure } from './provable-intf.js'; -import { Field } from '../wrapped.js'; +import type { Field } from '../wrapped.js'; import { createDerivers, NonMethods, diff --git a/src/lib/provable/types/unconstrained.ts b/src/lib/provable/types/unconstrained.ts index c9ada25352..a29531a1a6 100644 --- a/src/lib/provable/types/unconstrained.ts +++ b/src/lib/provable/types/unconstrained.ts @@ -1,8 +1,9 @@ import { Snarky } from '../../../snarky.js'; -import { Field } from '../field.js'; -import { Provable } from '../provable.js'; +import type { Field } from '../field.js'; +import type { Provable } from '../provable.js'; import { assert } from '../../util/errors.js'; -import { inCheckedComputation } from '../core/provable-context.js'; +import { asProver, inCheckedComputation } from '../core/provable-context.js'; +import { witness } from './witness.js'; export { Unconstrained }; @@ -93,7 +94,7 @@ and Provable.asProver() blocks, which execute outside the proof. * Create an `Unconstrained` from a witness computation. */ static witness(compute: () => T) { - return Provable.witness( + return witness( Unconstrained.provable, () => new Unconstrained(true, compute()) ); @@ -103,7 +104,7 @@ and Provable.asProver() blocks, which execute outside the proof. * Update an `Unconstrained` by a witness computation. */ updateAsProver(compute: (value: T) => T) { - return Provable.asProver(() => { + return asProver(() => { let value = this.get(); this.set(compute(value)); }); diff --git a/src/lib/provable/types/witness.ts b/src/lib/provable/types/witness.ts new file mode 100644 index 0000000000..c432eac1b3 --- /dev/null +++ b/src/lib/provable/types/witness.ts @@ -0,0 +1,86 @@ +import type { Field } from '../field.js'; +import type { FlexibleProvable } from './struct.js'; +import type { Provable } from './provable-intf.js'; +import { + inCheckedComputation, + snarkContext, +} from '../core/provable-context.js'; +import { exists, existsAsync } from '../core/exists.js'; + +export { witness, witnessAsync }; + +function witness = FlexibleProvable>( + type: S, + compute: () => T +): T { + let ctx = snarkContext.get(); + + // outside provable code, we just call the callback and return its cloned result + if (!inCheckedComputation() || ctx.inWitnessBlock) { + return clone(type, compute()); + } + let proverValue: T | undefined = undefined; + let fields: Field[]; + + let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); + try { + fields = exists(type.sizeInFields(), () => { + proverValue = compute(); + let fields = type.toFields(proverValue); + return fields.map((x) => x.toBigInt()); + }); + } finally { + snarkContext.leave(id); + } + + // rebuild the value from its fields (which are now variables) and aux data + let aux = type.toAuxiliary(proverValue); + let value = (type as Provable).fromFields(fields, aux); + + // add type-specific constraints + type.check(value); + + return value; +} + +async function witnessAsync< + T, + S extends FlexibleProvable = FlexibleProvable +>(type: S, compute: () => Promise): Promise { + let ctx = snarkContext.get(); + + // outside provable code, we just call the callback and return its cloned result + if (!inCheckedComputation() || ctx.inWitnessBlock) { + let value: T = await compute(); + return clone(type, value); + } + let proverValue: T | undefined = undefined; + let fields: Field[]; + + // call into `existsAsync` to witness the raw field elements + let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); + try { + fields = await existsAsync(type.sizeInFields(), async () => { + proverValue = await compute(); + let fields = type.toFields(proverValue); + return fields.map((x) => x.toBigInt()); + }); + } finally { + snarkContext.leave(id); + } + + // rebuild the value from its fields (which are now variables) and aux data + let aux = type.toAuxiliary(proverValue); + let value = (type as Provable).fromFields(fields, aux); + + // add type-specific constraints + type.check(value); + + return value; +} + +function clone>(type: S, value: T): T { + let fields = type.toFields(value); + let aux = type.toAuxiliary?.(value) ?? []; + return (type as Provable).fromFields(fields, aux); +} diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index c15b162639..b5a8406cee 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -35,6 +35,7 @@ export { fromRandom, first, second, + constant, }; export { Spec, @@ -405,6 +406,10 @@ function second(spec: Spec): Spec { }; } +function constant(spec: Spec, value: T): Spec { + return { ...spec, rng: Random.constant(value) }; +} + // helper to ensure two functions throw equivalent errors function handleErrors( diff --git a/tests/vk-regression/diverse-zk-program.ts b/tests/vk-regression/diverse-zk-program.ts index 3f8411de2c..0261c72b94 100644 --- a/tests/vk-regression/diverse-zk-program.ts +++ b/tests/vk-regression/diverse-zk-program.ts @@ -37,7 +37,7 @@ const diverse = ZkProgram({ Secp256k1Signature.provable, Secp256k1.provable, ], - method( + async method( message: Secp256k1Scalar, signature: Secp256k1Signature, publicKey: Secp256k1 @@ -49,7 +49,7 @@ const diverse = ZkProgram({ // bitwise gadgets sha3: { privateInputs: [Bytes128.provable], - method(xs: Bytes128) { + async method(xs: Bytes128) { Hash.SHA3_256.hash(xs); }, }, @@ -57,7 +57,7 @@ const diverse = ZkProgram({ // poseidon poseidon: { privateInputs: [AccountUpdate, MerkleWitness30], - method(accountUpdate: AccountUpdate, witness: MerkleWitness30) { + async method(accountUpdate: AccountUpdate, witness: MerkleWitness30) { let leaf = accountUpdate.hash(); let root = witness.calculateRoot(leaf); let index = witness.calculateIndex(); @@ -68,7 +68,7 @@ const diverse = ZkProgram({ // native EC ops pallas: { privateInputs: [PublicKey, PrivateKey, Signature], - method(pk: PublicKey, sk: PrivateKey, sig: Signature) { + async method(pk: PublicKey, sk: PrivateKey, sig: Signature) { let pk2 = sk.toPublicKey(); pk.assertEquals(pk2); @@ -79,7 +79,7 @@ const diverse = ZkProgram({ // only generic gates generic: { privateInputs: [Field, Field], - method(x: Field, y: Field) { + async method(x: Field, y: Field) { x.square().equals(5).assertFalse(); let z = Provable.if(y.equals(0), x, y); z.assertEquals(x); @@ -89,7 +89,7 @@ const diverse = ZkProgram({ // recursive proof recursive: { privateInputs: [SelfProof], - method(proof: SelfProof) { + async method(proof: SelfProof) { proof.verify(); }, }, diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 4421a8c73d..5f672b9cc8 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -1,22 +1,22 @@ { "Voting_": { - "digest": "2689d97f83b58006eafcc9d34fefe448b969ac539a11daa4f8ee1b423594d3cc", + "digest": "159ec5e8d925eb999c0a7b4863847bc0ec522405d300a4084f98b770ead07b90", "methods": { "voterRegistration": { - "rows": 1185, - "digest": "e0a6026956ebde7c5325337742f8e0ef" + "rows": 1178, + "digest": "66973c111b7b93bf096938a5944902c7" }, "candidateRegistration": { - "rows": 1185, - "digest": "7eeeade1a032912a69a802c8299c2ab0" + "rows": 1178, + "digest": "673a5021030c226256a4ceb599c8eb0a" }, "approveRegistrations": { "rows": 1146, "digest": "f45b6638b6d99e13a7c1c05c42959286" }, "vote": { - "rows": 1526, - "digest": "303bd21763794fcabcbe78e1bf9858a8" + "rows": 1508, + "digest": "12e6e31e8293967b826d5a5c3fad8bca" }, "countVotes": { "rows": 5732, @@ -24,8 +24,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "28740323451263376422147195980826831705287136279743929447998060058715271242374" + "data": "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", + "hash": "25284560686062115619679317846768604779257819999953937945142709167062218940700" } }, "Membership_": { @@ -50,16 +50,16 @@ } }, "HelloWorld": { - "digest": "375389fb51cab85f4cd4ce965dd53c2c90bb4dbf9daf5fe7f7aa009001a2a56d", + "digest": "3a585b00d88cee594f508ec18c5625a189fc7c3f1308c05fd1ac118cfc8c8705", "methods": { "update": { - "rows": 824, - "digest": "8e3d96b838937437c386dd13d307a7d2" + "rows": 585, + "digest": "2bb2e4397d8fda256fcbe6ac1677991d" } }, "verificationKey": { - "data": "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", - "hash": "415559726899752466798349869887051584660923621395325655695487627339344362000" + "data": "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", + "hash": "11707695774788847345167402900704548924949509205484364347348209235004076233330" } }, "TokenContract": { @@ -84,15 +84,15 @@ } }, "Dex": { - "digest": "20164a4e8a983a8b6501f376ab7c0c6976e9af8054a12011b5811ea6b46caf9b", + "digest": "5e345af7ebdc81f2c6106127bc717b4cdc203da6f4f8e02ad1684f5aad22963", "methods": { "approveBase": { "rows": 13232, "digest": "f9218123446ce86c1e984c60e9ea281c" }, "supplyLiquidityBase": { - "rows": 2854, - "digest": "f76a1e9435c07f973bfcaf6a3c32e5ca" + "rows": 2841, + "digest": "75d1154c07ee819a6533942622347cbd" }, "swapX": { "rows": 1512, @@ -108,8 +108,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "27861428472680659480582730203195071400017645299496499672708746624076239406640" + "data": "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", + "hash": "9987657096518087584579706527095438080171607593510766635946934619628994098856" } }, "Group Primitive": { @@ -227,20 +227,20 @@ "digest": "49e0fdd67c13bd800f641f2375301322" }, "assertLessThan": { - "rows": 510, - "digest": "4d72e2e7b6073ac9279accec2bc3889b" + "rows": 24, + "digest": "f7979c1aef4fb5672c7bd77ea927c10f" }, "lessThan": { - "rows": 509, - "digest": "163f60bf937f5a5961f08be58009d06a" + "rows": 38, + "digest": "eed5490ecb8d1d6a8e9d3699cf5e175a" }, "assertLessThanUInt64": { - "rows": 27, - "digest": "4c2e6066727ece164bd9f1609bd564ef" + "rows": 14, + "digest": "caadec00ee68eb4510555fed0dc4f0d6" }, "lessThanUInt64": { - "rows": 27, - "digest": "6d139dfc61137449010049ee51dd1de6" + "rows": 15, + "digest": "87c70877d218c093b8ab5e2848a44611" } }, "verificationKey": { @@ -288,7 +288,7 @@ } }, "diverse": { - "digest": "1ef8986e905c798250a4072628aa5e52f3ac71e41eef87f7488a384703bb2f29", + "digest": "3cf637b1eb3fb7e15263493b9778078605e6c9b4921976b46e08451e770bd665", "methods": { "ecdsa": { "rows": 28182, @@ -299,8 +299,8 @@ "digest": "c23e00e466878466ae8ab23bde562792" }, "pallas": { - "rows": 1609, - "digest": "c06c8da9154f40356aac829c9f90753f" + "rows": 891, + "digest": "e5f666ba87d050daea47081212cec058" }, "poseidon": { "rows": 946, @@ -316,8 +316,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "21073713073974678425291625894317209524148583158680099308251167711500330246765" + "data": "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", + "hash": "10231236168606718499352904475791249038227547944081537794871338618093559489302" } } } \ No newline at end of file diff --git a/tests/vk-regression/vk-regression.ts b/tests/vk-regression/vk-regression.ts index 5952de5a53..9af98395a9 100644 --- a/tests/vk-regression/vk-regression.ts +++ b/tests/vk-regression/vk-regression.ts @@ -19,7 +19,10 @@ import { diverse } from './diverse-zk-program.js'; // toggle this for quick iteration when debugging vk regressions const skipVerificationKeys = false; -// usage ./run ./tests/regression/vk-regression.ts --bundle --dump ./tests/vk-regression/vk-regression.json +// toggle to override caches +const forceRecompile = false; + +// usage ./run ./tests/vk-regression/vk-regression.ts --bundle --dump ./tests/vk-regression/vk-regression.json let dump = process.argv[4] === '--dump'; let jsonPath = process.argv[dump ? 5 : 4]; @@ -33,7 +36,7 @@ type MinimumConstraintSystem = { } > >; - compile(): Promise<{ + compile(options?: { forceRecompile?: boolean }): Promise<{ verificationKey: { hash: { toString(): string }; data: string; @@ -94,7 +97,7 @@ async function checkVk(contracts: typeof ConstraintSystems) { let { verificationKey: { data, hash }, - } = await c.compile(); + } = await c.compile({ forceRecompile }); let methodData = await c.analyzeMethods(); @@ -106,20 +109,14 @@ async function checkVk(contracts: typeof ConstraintSystems) { errorStack += `\n\nMethod digest mismatch for ${c.name}.${methodKey}() Actual ${JSON.stringify( - { - digest: actualMethod.digest, - rows: actualMethod.rows, - }, + { digest: actualMethod.digest, rows: actualMethod.rows }, undefined, 2 )} \n Expected ${JSON.stringify( - { - digest: expectedMethod.digest, - rows: expectedMethod.rows, - }, + { digest: expectedMethod.digest, rows: expectedMethod.rows }, undefined, 2 )}`; @@ -131,14 +128,7 @@ async function checkVk(contracts: typeof ConstraintSystems) { c.name } failed, because of a verification key mismatch. Contract has - ${JSON.stringify( - { - data, - hash, - }, - undefined, - 2 - )} + ${JSON.stringify({ data, hash }, undefined, 2)} \n but expected was ${JSON.stringify(ref.verificationKey, undefined, 2)}`; @@ -158,7 +148,8 @@ async function dumpVk(contracts: typeof ConstraintSystems) { let verificationKey: | { data: string; hash: { toString(): string } } | undefined; - if (!skipVerificationKeys) ({ verificationKey } = await c.compile()); + if (!skipVerificationKeys) + ({ verificationKey } = await c.compile({ forceRecompile })); newEntries[c.name] = { digest, methods: Object.fromEntries(